-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.ts
331 lines (314 loc) · 11.3 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
import { octokit } from "./octokit.ts";
import semver from "semver";
import fs from "fs";
import path from "path";
import tar from "tar";
import { $ } from "bun";
import _ from "lodash";
/* Generic */
// Make the version full semver format with preceding v
export function normalizeVersion(version: string, strict = false) {
const coerced = semver.coerce(version);
const isfull = semver.valid(version) !== null;
if (coerced === null) {
throw new Error("The provided input should be a string convertible to a semver version using `semver.coerce`")
}
if (strict && semver.valid(version) === null) {
throw new Error(`The provided input should be a fully qualified semver version of the form X.Y.Z or vX.Y.Z`)
}
return `v${coerced.version}`;
}
// Extract the semver version from the VERSION file in the calling directory
function versionFromFile() {
let version = fs.readFileSync("VERSION").toString();
if (semver.valid(version) === null) {
throw new Error(`VERSION file contains invalid version ${version}`);
}
version = normalizeVersion(version);
return version;
}
/* Release Extraction */
// Get latest local release
export async function latestLocalRelease() {
return await octokit.rest.repos.getLatestRelease({
owner: process.env.OWNER,
repo: process.env.REPO,
});
}
// Get version of the latest local release
export async function latestLocalVersion() {
const latest_release = await latestLocalRelease();
return latest_release.data.tag_name;
}
// Get local release with target version. Providing "latest" will return the latest release and undefined is returned if the target release version does not exist
export async function getLocalRelease(version: string) {
if (version === "latest") {
return await latestLocalRelease();
}
version = normalizeVersion(version);
try {
return await octokit.rest.repos.getReleaseByTag({
owner: process.env.OWNER,
repo: process.env.REPO,
tag: version,
});
} catch (error) {
return undefined;
}
}
export async function hasLocalTag(version: string) {
version = normalizeVersion(version);
try {
await octokit.rest.git.getRef({
owner: process.env.OWNER,
repo: process.env.REPO,
ref: `tags/${version}`,
});
} catch (error) {
return false;
}
return true
}
export async function latestPlotlyRelease() {
return await octokit.rest.repos.getLatestRelease({
owner: "plotly",
repo: "plotly.js",
});
}
export async function latestPlotlyVersion() {
const latest_release = await latestPlotlyRelease();
return latest_release.data.tag_name;
}
export async function getPlotlyRelease(version: string) {
if (version === "latest") {
return await latestPlotlyRelease();
}
version = normalizeVersion(version);
try {
return await octokit.rest.repos.getReleaseByTag({
owner: "plotly",
repo: "plotly.js",
tag: version,
});
} catch (error) {
return undefined;
}
}
// Returns an object containing the version of the first unreleased plotly version (library vs local) as well as the number of remaining unreleased versions
export async function firstUnreleased() {
const iterator = octokit.paginate.iterator(
"GET /repos/{owner}/{repo}/releases",
{
owner: "plotly",
repo: "plotly.js",
per_page: 100,
}
);
// current might not be the latest in term of semver
const current_version = await latestLocalVersion();
let breakLoop = false;
let n_unreleased = 0
let first_unreleased: string | undefined = undefined;
for await (const { data: releases } of iterator) {
if (breakLoop) break;
for (const release of releases) {
let this_version = release.tag_name;
if (semver.gt(this_version, current_version)) {
// We check if we actually have this plotly release in an older local release (older w.r.t release date, not release version number)
const has_local = (await getLocalRelease(this_version)) !== undefined;
if (has_local) break
first_unreleased = this_version;
n_unreleased += 1
} else {
break;
}
}
}
return { first_unreleased, n_unreleased };
}
interface ReleasesData {
local_release: any;
plotly_release: any;
version: string;
hastag: boolean;
}
export async function extractReleases(version:string) {
version = normalizeVersion(version);
// Make sure that the provided version does not exists already
const release_reponse = await getLocalRelease(version);
const local_release = release_reponse?.data
const hastag = await hasLocalTag(version);
// Extract the plotly.js corresponding release to ensure it exists and to extract the html url
const plotly_release_response = await getPlotlyRelease(version);
return {
local_release,
plotly_release: plotly_release_response?.data,
version,
hastag
}
}
export async function createLocalRelease(inp: string | ReleasesData, options = {}) {
const rdata = (typeof inp === "string") ? await extractReleases(inp) : inp
const { local_release, plotly_release, version } = rdata;
if (plotly_release === undefined) {
throw new Error(`Release with version ${version} does not exist in the plotly.js library`);
}
if (local_release !== undefined) {
throw new Error(`Release with version ${version} already exists locally`);
}
_.defaults(options, {
owner: process.env.OWNER,
repo: process.env.REPO,
tag_name: version,
name: version,
body: `[plotly.js release](${plotly_release.html_url})`,
target_commitish: undefined,
make_latest: "legacy", // This make releases appear in semver order if multiple releases happen on the same day. Otherwise it uses release day for ordering
});
const create_response = await octokit.rest.repos.createRelease(options);
rdata.local_release = create_response.data
return create_response.data
}
// This function tries prepares for releasing a new version by commiting the corresponding change to the VERSION file.
// It returns a boolean specifying whether the release has to be performed
export async function commitNewVersion(inp: string | ReleasesData) {
const rdata = (typeof inp === "string") ? await extractReleases(inp) : inp
const { local_release, plotly_release, version, hastag } = rdata;
if (local_release !== undefined) {
console.log(`Release with version ${version} already exists`);
return false;
}
if (hastag) {
console.log(`Release with version ${version} does not exists, but a tag with the same version already exists.`);
return true
}
// Otherwise, we check if the VERSION file already exists and has the correct version
if (
(await fs.promises.exists("VERSION")) &&
(await fs.promises.readFile("VERSION")).toString() === version
) {
console.log(`VERSION file exists and already contains version ${version}.`);
return true;
}
// We write to the file
await fs.promises.writeFile("VERSION", version);
// We commit the changed version. NOTE: We directly call the shell commands now but we should consider using the API in the future
await $`git config user.name "Add Plotly Version"`;
await $`git config user.email "add_plotly_version@email.com"`;
await $`git add VERSION`;
await $`git commit -m "add plotly.js version $(cat VERSION)`;
await $`git push`;
return true;
}
export async function getReleaseAssets(inp: string | ReleasesData) {
const rdata = (typeof inp === "string") ? await extractReleases(inp) : inp
const { local_release: release, version } = rdata;
if (release === undefined) {
throw new Error(`Release with version ${version} does not exist`);
}
const release_id = release.data.id;
return await octokit.rest.repos.listReleaseAssets({
owner: process.env.OWNER,
repo: process.env.REPO,
release_id: release_id,
});
}
// Build the plotly artifact taking the version from the VERSION file
export async function buildArtifactTar(inp: string | ReleasesData, options = {}) {
_.defaults(options, {
bundle_name: "plotly-esm-min.mjs",
outdir: path.join(process.cwd(),"out"),
tar_name: "plotly-esm-min.tar.gz",
install: true,
});
const version = (typeof inp === "string") ? inp : inp.version;
const { bundle_name, outdir, tar_name } = options;
// ensure that we have a full semver version
// Install the plotly version
if (options.install) {
await $`bun install plotly.js-dist-min${version.replace("v", "@")}`
}
// Build the bundle
await Bun.build({
entrypoints: ["./plotly_esm.ts"],
outdir: outdir,
minify: true,
naming: `[dir]/${bundle_name}`,
});
// Write the specified version to file
await fs.promises.writeFile(path.join(outdir, "VERSION"), version);
// Create the zip containing the module and VERSION
return await tar.create(
{
gzip: true,
file: tar_name,
cwd: outdir,
},
[bundle_name, "VERSION"]
);
}
export async function uploadReleaseArtifacts(inp: string | ReleasesData, options = {}) {
// Extract the version from the VERSION file
const rdata = (typeof inp === "string") ? await extractReleases(inp) : inp
const { local_release: release, version } = rdata;
// Ensure that the release already exists
if (release === undefined) {
throw new Error(
`Release with version ${version} does not exist. Please create it first.`
);
}
const release_id = release.id;
// Build the artifact first
await buildArtifactTar(rdata, options);
const { tar_name, outdir, bundle_name } = options;
// Upload the tar artifact
const tarContents = await fs.promises.readFile(tar_name);
let response = await octokit.rest.repos.uploadReleaseAsset({
headers: {
"content-type": "application/gzip",
},
owner: process.env.OWNER,
repo: process.env.REPO,
release_id: release_id,
data: tarContents,
name: tar_name,
});
// Upload the tar artifact
const filePath = path.join(outdir, bundle_name);
const fileContents = await fs.promises.readFile(filePath);
response = await octokit.rest.repos.uploadReleaseAsset({
headers: {
"content-type": "text/javascript",
},
owner: process.env.OWNER,
repo: process.env.REPO,
release_id: release_id,
data: fileContents,
name: bundle_name,
});
return response
}
export async function maybeReleaseVersion(inp: string | ReleasesData) {
if (typeof inp === "string" && semver.valid(inp) === null) {
throw new Error(`The provided input should be a fully qualified semver version of the form X.Y.Z or vX.Y.Z`)
}
const rdata = (typeof inp === "string") ? await extractReleases(inp) : inp
const { local_release, plotly_release, version } = rdata;
if (plotly_release === undefined) {
throw new Error(`Release with version ${version} does not exist in the plotly library.`)
}
if (local_release !== undefined) {
throw new Error(`Release with version ${version} already exists locally.`)
}
console.log(`Creating new release with version ${version}...`)
// We eventually commit a modification to VERSION
await commitNewVersion(rdata)
// We create the release
const release = await createLocalRelease(rdata)
console.log(`Release ${version} succesfully created on the repository.`)
console.log(`Building and uploading the release artifact for plotly.js ${version} as asset`)
// We build and upload the artifacts
await uploadReleaseArtifacts(rdata)
console.log(`Release assets for version ${version} uploaded succesfully!`)
return release
}