@aidc-toolkit/dev 0.9.6-beta → 0.9.7-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,24 +2,24 @@
2
2
  "organization": "aidc-toolkit",
3
3
  "repositories": {
4
4
  "dev": {
5
- "version": "0.9.6-beta"
5
+ "version": "0.9.7-beta"
6
6
  },
7
7
  "core": {
8
- "version": "0.9.6-beta"
8
+ "version": "0.9.7-beta"
9
9
  },
10
10
  "utility": {
11
- "version": "0.9.6-beta"
11
+ "version": "0.9.7-beta"
12
12
  },
13
13
  "gs1": {
14
14
  "name": "gs1",
15
- "version": "0.9.6-beta"
15
+ "version": "0.9.7-beta"
16
16
  },
17
17
  "demo": {
18
- "version": "0.9.6-beta"
18
+ "version": "0.9.7-beta"
19
19
  },
20
20
  "aidc-toolkit.github.io": {
21
21
  "directory": "doc",
22
- "version": "0.9.6-beta"
22
+ "version": "0.9.7-beta"
23
23
  }
24
24
  }
25
25
  }
@@ -1,25 +1,5 @@
1
1
  {
2
2
  "copyFiles": [
3
- {
4
- "from": ".github/workflows/tag.yml",
5
- "to": "../core/.github/workflows/"
6
- },
7
- {
8
- "from": ".github/workflows/tag.yml",
9
- "to": "../utility/.github/workflows/"
10
- },
11
- {
12
- "from": ".github/workflows/tag.yml",
13
- "to": "../gs1/.github/workflows/"
14
- },
15
- {
16
- "from": ".github/workflows/tag.yml",
17
- "to": "../demo/.github/workflows/"
18
- },
19
- {
20
- "from": ".github/workflows/tag.yml",
21
- "to": "../doc/.github/workflows/"
22
- },
23
3
  {
24
4
  "from": ".github/workflows/release.yml",
25
5
  "to": "../core/.github/workflows/"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aidc-toolkit/dev",
3
- "version": "0.9.6-beta",
3
+ "version": "0.9.7-beta",
4
4
  "description": "Shared development artefacts for AIDC Toolkit",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,18 +33,15 @@
33
33
  "tsx": "^4.19.2",
34
34
  "typescript": "^5.7.2"
35
35
  },
36
- "peerDependencies": {
37
- "eslint": ">=9"
38
- },
39
36
  "dependencies": {
40
37
  "@eslint/js": "^9.16.0",
41
38
  "@octokit/types": "^13.6.2",
42
- "@rollup/rollup-linux-x64-gnu": "^4.28.0",
43
- "@stylistic/eslint-plugin": "^2.11.0",
44
- "eslint-config-love": "^109.0.0",
39
+ "@rollup/rollup-linux-x64-gnu": "^4.28.1",
40
+ "@stylistic/eslint-plugin": "^2.12.0",
41
+ "eslint-config-love": "^110.0.0",
45
42
  "eslint-plugin-jsdoc": "^50.6.0",
46
43
  "octokit": "^4.0.2",
47
- "typescript-eslint": "^8.16.0",
44
+ "typescript-eslint": "^8.17.0",
48
45
  "yaml": "^2.6.1"
49
46
  }
50
47
  }
package/src/release.ts CHANGED
@@ -56,6 +56,11 @@ interface PackageConfiguration {
56
56
  */
57
57
  version: string;
58
58
 
59
+ /**
60
+ * If true, package is private and not linked by others.
61
+ */
62
+ private?: boolean;
63
+
59
64
  /**
60
65
  * Development dependencies.
61
66
  */
@@ -137,13 +142,18 @@ function run(captureOutput: boolean, command: string, ...args: string[]): string
137
142
  return captureOutput ? spawnResult.stdout.toString().split("\n").slice(0, -1) : [];
138
143
  }
139
144
 
145
+ /**
146
+ * Supported states.
147
+ */
148
+ type State = "skipped" | "install" | "build" | "link" | "commit" | "tag" | "push" | "workflow (push)" | "release" | "workflow (release)" | "complete";
149
+
140
150
  /**
141
151
  * Release.
142
152
  */
143
153
  async function release(): Promise<void> {
144
154
  const statePath = path.resolve("config/release.state.json");
145
155
 
146
- let state: Record<string, string | undefined> = {};
156
+ let state: Record<string, State | undefined> = {};
147
157
 
148
158
  if (fs.existsSync(statePath)) {
149
159
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Format is controlled by this process.
@@ -172,7 +182,7 @@ async function release(): Promise<void> {
172
182
  * @returns
173
183
  * Promise.
174
184
  */
175
- async function step(name: string, stepState: string, callback: () => (void | Promise<void>)): Promise<void> {
185
+ async function step(name: string, stepState: State, callback: () => (void | Promise<void>)): Promise<void> {
176
186
  const repositoryState = state[name];
177
187
 
178
188
  if (repositoryState === undefined || repositoryState === stepState) {
@@ -187,7 +197,7 @@ async function release(): Promise<void> {
187
197
 
188
198
  state[name] = undefined;
189
199
  } finally {
190
- fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`);
200
+ saveState();
191
201
  }
192
202
  }
193
203
  }
@@ -197,6 +207,8 @@ async function release(): Promise<void> {
197
207
  userAgent: `${configuration.organization} release`
198
208
  });
199
209
 
210
+ let allSkipped = true;
211
+
200
212
  for (const name of Object.keys(configuration.repositories)) {
201
213
  const repository = configuration.repositories[name];
202
214
 
@@ -215,26 +227,6 @@ async function release(): Promise<void> {
215
227
  throw new Error("Repository has uncommitted changes");
216
228
  }
217
229
 
218
- const workflowsPath = ".github/workflows/";
219
-
220
- let hasPushWorkflow = false;
221
- let hasReleaseWorkflow = false;
222
-
223
- for (const workflowFile of fs.readdirSync(workflowsPath)) {
224
- if (workflowFile.endsWith(".yml")) {
225
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Workflow configuration format is known.
226
- const workflowOn = (yamlParse(fs.readFileSync(path.resolve(workflowsPath, workflowFile)).toString()) as WorkflowConfiguration).on;
227
-
228
- if (workflowOn.push !== undefined && (workflowOn.push.branches === undefined || workflowOn.push.branches.includes("main"))) {
229
- hasPushWorkflow = true;
230
- }
231
-
232
- if (workflowOn.release !== undefined && (workflowOn.release.types === undefined || workflowOn.release.types.includes("published"))) {
233
- hasReleaseWorkflow = true;
234
- }
235
- }
236
- }
237
-
238
230
  const tag = `v${repository.version}`;
239
231
 
240
232
  const octokitParameterBase = {
@@ -242,141 +234,219 @@ async function release(): Promise<void> {
242
234
  repo: name
243
235
  };
244
236
 
245
- const commitSHA = run(true, "git", "rev-parse", "HEAD")[0];
237
+ const packageConfigurationPath = "package.json";
246
238
 
247
- /**
248
- * Validate the workflow by waiting for it to complete.
249
- */
250
- async function validateWorkflow(): Promise<void> {
251
- while (!await new Promise<void>((resolve) => {
252
- setTimeout(resolve, 2000);
253
- }).then(async () => await octokit.rest.actions.listWorkflowRunsForRepo({
254
- ...octokitParameterBase,
255
- head_sha: commitSHA
256
- })).then((response) => {
257
- let workflowRunID = -1;
239
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Package configuration format is known.
240
+ const packageConfiguration: PackageConfiguration = JSON.parse(fs.readFileSync(packageConfigurationPath).toString());
258
241
 
259
- let queryCount = 0;
260
- let completed = false;
242
+ let skipRepository: boolean;
261
243
 
262
- for (const workflowRun of response.data.workflow_runs) {
263
- if (workflowRun.status !== "completed") {
264
- if (workflowRun.id === workflowRunID) {
265
- process.stdout.write(".");
266
- } else if (workflowRunID === -1) {
267
- workflowRunID = workflowRun.id;
244
+ switch (state[name]) {
245
+ case undefined:
246
+ // No steps have yet been taken; skip if repository is already at the required version.
247
+ skipRepository = packageConfiguration.version === repository.version;
268
248
 
269
- console.log(`Workflow run ID ${workflowRunID}`);
270
- } else {
271
- throw new Error(`Parallel workflow runs for SHA ${commitSHA}`);
272
- }
273
- } else if (workflowRun.id === workflowRunID) {
274
- process.stdout.write("\n");
249
+ if (!skipRepository) {
250
+ allSkipped = false;
251
+
252
+ packageConfiguration.version = repository.version;
253
+
254
+ const atOrganization = `@${configuration.organization}`;
255
+
256
+ /**
257
+ * Update dependencies from the organization.
258
+ *
259
+ * @param dependencies
260
+ * Dependencies.
261
+ *
262
+ * @returns
263
+ * List of dependencies that require linking.
264
+ */
265
+ function updateDependencies(dependencies: Record<string, string> | undefined): string[] {
266
+ const linkDependencies = new Array<string>();
275
267
 
276
- if (workflowRun.conclusion !== "success") {
277
- throw new Error(`Workflow ${workflowRun.conclusion}`);
268
+ if (dependencies !== undefined) {
269
+ // eslint-disable-next-line guard-for-in -- Dependency record type is shallow.
270
+ for (const dependency in dependencies) {
271
+ const [dependencyAtOrganization, dependencyRepositoryName] = dependency.split("/");
272
+
273
+ if (dependencyAtOrganization === atOrganization) {
274
+ dependencies[dependency] = `^${configuration.repositories[dependencyRepositoryName].version}`;
275
+
276
+ linkDependencies.push(dependency);
277
+ }
278
+ }
278
279
  }
279
280
 
280
- completed = true;
281
+ return linkDependencies;
281
282
  }
282
- }
283
283
 
284
- // Abort if workflow run not started after 10 queries.
285
- if (++queryCount === 10 && workflowRunID === -1) {
286
- throw new Error(`Workflow run not started for SHA ${commitSHA}`);
284
+ const linkDependencies = updateDependencies(packageConfiguration.devDependencies);
285
+ linkDependencies.push(...updateDependencies(packageConfiguration.dependencies));
286
+
287
+ fs.writeFileSync(packageConfigurationPath, `${JSON.stringify(packageConfiguration, null, 2)}\n`);
288
+
289
+ for (const dependency of linkDependencies) {
290
+ run(false, "npm", "link", dependency);
291
+ }
292
+ } else if (!allSkipped) {
293
+ throw new Error(`Repository ${name} is supposed to be skipped but at least one prior repository has been updated`);
287
294
  }
295
+ break;
288
296
 
289
- return completed;
290
- })) {
291
- // Execution within conditional.
292
- }
297
+ case "skipped":
298
+ // Repository was skipped on the prior run.
299
+ skipRepository = true;
300
+ break;
301
+
302
+ case "complete":
303
+ // Repository was fully updated on the prior run.
304
+ skipRepository = true;
305
+
306
+ allSkipped = false;
307
+ break;
308
+
309
+ default:
310
+ // Repository failed at some step on the prior run.
311
+ skipRepository = false;
312
+
313
+ allSkipped = false;
314
+ break;
293
315
  }
294
316
 
295
- const packageConfigurationPath = "package.json";
317
+ if (!skipRepository) {
318
+ const workflowsPath = ".github/workflows/";
296
319
 
297
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Package configuration format is known.
298
- const packageConfiguration: PackageConfiguration = JSON.parse(fs.readFileSync(packageConfigurationPath).toString());
320
+ let hasPushWorkflow = false;
321
+ let hasReleaseWorkflow = false;
299
322
 
300
- const skipRepository = state[name] === undefined && packageConfiguration.version === repository.version;
323
+ if (fs.existsSync(workflowsPath)) {
324
+ for (const workflowFile of fs.readdirSync(workflowsPath)) {
325
+ if (workflowFile.endsWith(".yml")) {
326
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Workflow configuration format is known.
327
+ const workflowOn = (yamlParse(fs.readFileSync(path.resolve(workflowsPath, workflowFile)).toString()) as WorkflowConfiguration).on;
301
328
 
302
- if (packageConfiguration.version !== repository.version) {
303
- packageConfiguration.version = repository.version;
329
+ if (workflowOn.push !== undefined && (workflowOn.push.branches === undefined || workflowOn.push.branches.includes("main"))) {
330
+ hasPushWorkflow = true;
331
+ }
304
332
 
305
- const atOrganization = `@${configuration.organization}/`;
333
+ if (workflowOn.release !== undefined && (workflowOn.release.types === undefined || workflowOn.release.types.includes("published"))) {
334
+ hasReleaseWorkflow = true;
335
+ }
336
+ }
337
+ }
338
+ }
306
339
 
307
340
  /**
308
- * Update dependencies from the organization.
309
- *
310
- * @param dependencies
311
- * Dependencies.
341
+ * Validate the workflow by waiting for it to complete.
312
342
  */
313
- function updateDependencies(dependencies: Record<string, string> | undefined): void {
314
- if (dependencies !== undefined) {
315
- for (const dependency in dependencies) {
316
- const [dependencyAtOrganization, dependencyRepositoryName] = dependency.split("/");
343
+ async function validateWorkflow(): Promise<void> {
344
+ const commitSHA = run(true, "git", "rev-parse", "HEAD")[0];
345
+
346
+ let completed = false;
347
+ let queryCount = 0;
348
+ let workflowRunID = -1;
349
+
350
+ do {
351
+ await new Promise<void>((resolve) => {
352
+ setTimeout(resolve, 2000);
353
+ });
354
+
355
+ const response = await octokit.rest.actions.listWorkflowRunsForRepo({
356
+ ...octokitParameterBase,
357
+ head_sha: commitSHA
358
+ });
317
359
 
318
- if (dependencyAtOrganization === atOrganization) {
319
- dependencies[dependency] = `^${configuration.repositories[dependencyRepositoryName].version}`;
360
+ for (const workflowRun of response.data.workflow_runs) {
361
+ if (workflowRun.status !== "completed") {
362
+ if (workflowRun.id === workflowRunID) {
363
+ process.stdout.write(".");
364
+ } else if (workflowRunID === -1) {
365
+ workflowRunID = workflowRun.id;
366
+
367
+ console.log(`Workflow run ID ${workflowRunID}`);
368
+ } else {
369
+ throw new Error(`Parallel workflow runs for SHA ${commitSHA}`);
370
+ }
371
+ } else if (workflowRun.id === workflowRunID) {
372
+ process.stdout.write("\n");
373
+
374
+ if (workflowRun.conclusion !== "success") {
375
+ throw new Error(`Workflow ${workflowRun.conclusion}`);
376
+ }
377
+
378
+ completed = true;
320
379
  }
321
380
  }
322
- }
381
+
382
+ // Abort if workflow run not started after 10 queries.
383
+ if (++queryCount === 10 && workflowRunID === -1) {
384
+ throw new Error(`Workflow run not started for SHA ${commitSHA}`);
385
+ }
386
+ } while (!completed);
323
387
  }
324
388
 
325
- updateDependencies(packageConfiguration.devDependencies);
326
- updateDependencies(packageConfiguration.dependencies);
389
+ await step(name, "install", () => {
390
+ run(false, "npm", "install");
391
+ });
327
392
 
328
- fs.writeFileSync(packageConfigurationPath, `${JSON.stringify(packageConfiguration, null, 2)}\n`);
329
- }
393
+ await step(name, "build", () => {
394
+ run(false, "npm", "run", "build", "--if-present");
395
+ });
330
396
 
331
- if (!skipRepository) {
332
- await step(name, "npm install", () => {
333
- run(false, "npm", "install");
334
- }).then(async () => {
335
- await step(name, "git commit", () => {
336
- run(false, "git", "commit", "--all", `--message=Updated to version ${repository.version}`);
397
+ if (!(packageConfiguration.private ?? false)) {
398
+ await step(name, "link", () => {
399
+ run(false, "npm", "link");
337
400
  });
338
- }).then(async () => {
339
- await step(name, "git tag", () => {
340
- run(false, "git", "tag", tag);
341
- });
342
- }).then(async () => {
343
- await step(name, "git push", () => {
344
- run(false, "git", "push", "--atomic", "origin", "main", tag);
345
- });
346
- }).then(async () => {
347
- await step(name, "push workflow", async () => {
348
- if (hasPushWorkflow) {
349
- await validateWorkflow();
350
- }
351
- });
352
- }).then(async () => {
353
- await step(name, "release", async () => {
354
- const versionSplit = repository.version.split("-");
355
- const prerelease = versionSplit.length !== 1;
401
+ }
356
402
 
357
- await octokit.rest.repos.createRelease({
358
- ...octokitParameterBase,
359
- tag_name: tag,
360
- name: `${prerelease ? `${versionSplit[1].substring(0, 1).toUpperCase()}${versionSplit[1].substring(1)} r` : "R"}elease ${versionSplit[0]}`,
361
- // TODO Remove "false" override.
362
- prerelease: false
363
- });
403
+ await step(name, "commit", () => {
404
+ run(false, "git", "commit", "--all", `--message=Updated to version ${repository.version}`);
405
+ });
406
+
407
+ await step(name, "tag", () => {
408
+ run(false, "git", "tag", tag);
409
+ });
410
+
411
+ await step(name, "push", () => {
412
+ run(false, "git", "push", "--atomic", "origin", "main", tag);
413
+ });
414
+
415
+ if (hasPushWorkflow) {
416
+ await step(name, "workflow (push)", async () => {
417
+ await validateWorkflow();
364
418
  });
365
- }).then(async () => {
366
- await step(name, "release workflow", async () => {
367
- if (hasReleaseWorkflow) {
368
- await validateWorkflow();
369
- }
419
+ }
420
+
421
+ await step(name, "release", async () => {
422
+ const versionSplit = repository.version.split("-");
423
+ const prerelease = versionSplit.length !== 1;
424
+
425
+ await octokit.rest.repos.createRelease({
426
+ ...octokitParameterBase,
427
+ tag_name: tag,
428
+ name: `${prerelease ? `${versionSplit[1].substring(0, 1).toUpperCase()}${versionSplit[1].substring(1)} r` : "R"}elease ${versionSplit[0]}`,
429
+ // TODO Remove "false" override.
430
+ prerelease: false
370
431
  });
371
432
  });
372
433
 
434
+ if (hasReleaseWorkflow) {
435
+ await step(name, "workflow (release)", async () => {
436
+ await validateWorkflow();
437
+ });
438
+ }
439
+
373
440
  state[name] = "complete";
374
- saveState();
441
+ } else {
442
+ state[name] = "skipped";
375
443
  }
444
+
445
+ saveState();
376
446
  }
377
447
 
378
- state = {};
379
- saveState();
448
+ // All repositories released.
449
+ fs.rmSync(statePath);
380
450
  }
381
451
 
382
452
  await release().catch((e: unknown) => {