@aidc-toolkit/dev 1.0.23-beta → 1.0.25-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.
@@ -1,171 +0,0 @@
1
- import * as fs from "node:fs";
2
- import type { Repository } from "./configuration";
3
- import { PACKAGE_CONFIGURATION_PATH, PACKAGE_LOCK_CONFIGURATION_PATH, Publish } from "./publish.js";
4
- import { logger } from "./logger.js";
5
-
6
- const BACKUP_PACKAGE_CONFIGURATION_PATH = ".package.json";
7
-
8
- /**
9
- * Publish alpha versions.
10
- */
11
- class PublishAlpha extends Publish {
12
- /**
13
- * If true, update all dependencies automatically.
14
- */
15
- private readonly _updateAll: boolean;
16
-
17
- /**
18
- * Constructor.
19
- *
20
- * If true, outputs what would be run rather than running it.
21
- *
22
- * @param updateAll
23
- * If true, update all dependencies automatically.
24
- *
25
- * @param dryRun
26
- * If true, outputs what would be run rather than running it.
27
- */
28
- constructor(updateAll: boolean, dryRun: boolean) {
29
- super("alpha", dryRun);
30
-
31
- this._updateAll = updateAll;
32
- }
33
-
34
- /**
35
- * @inheritDoc
36
- */
37
- protected dependencyVersionFor(): string {
38
- // Dependency version is always "alpha".
39
- return "alpha";
40
- }
41
-
42
- /**
43
- * @inheritDoc
44
- */
45
- protected getPhaseDateTime(repository: Repository, phaseDateTime: Date): Date {
46
- // If beta or production has been published since the last alpha, use that instead.
47
- return this.latestDateTime(phaseDateTime, repository.phaseStates.beta?.dateTime, repository.phaseStates.production?.dateTime);
48
- }
49
-
50
- /**
51
- * @inheritDoc
52
- */
53
- protected isValidBranch(): boolean {
54
- // Any branch is valid for alpha publication.
55
- return true;
56
- }
57
-
58
- /**
59
- * @inheritDoc
60
- */
61
- protected publish(): void {
62
- let anyExternalUpdates = false;
63
-
64
- const repositoryState = this.repositoryState;
65
- const packageConfiguration = repositoryState.packageConfiguration;
66
-
67
- // Check for external updates, even if there are no changes.
68
- for (const currentDependencies of [packageConfiguration.devDependencies, packageConfiguration.dependencies]) {
69
- if (currentDependencies !== undefined) {
70
- for (const [dependencyPackageName, version] of Object.entries(currentDependencies)) {
71
- // Ignore organization dependencies.
72
- if (this.dependencyRepositoryName(dependencyPackageName) === null && version.startsWith("^")) {
73
- const [latestVersion] = this.run(true, true, "npm", "view", dependencyPackageName, "version");
74
-
75
- if (latestVersion !== version.substring(1)) {
76
- logger.info(`Dependency ${dependencyPackageName}@${version} ${!this._updateAll ? "pending update" : "updating"} to version ${latestVersion}.`);
77
-
78
- if (this._updateAll) {
79
- currentDependencies[dependencyPackageName] = `^${latestVersion}`;
80
-
81
- anyExternalUpdates = true;
82
- }
83
- }
84
- }
85
- }
86
- }
87
- }
88
-
89
- if (anyExternalUpdates) {
90
- // Save the dependency updates; this will be detected by call to anyChanges().
91
- this.savePackageConfiguration();
92
- }
93
-
94
- if (this._updateAll) {
95
- logger.debug("Updating all dependencies");
96
-
97
- // Running this even if there are no dependency updates will update dependencies of dependencies.
98
- this.run(false, false, "npm", "update", ...repositoryState.npmPlatformArgs);
99
- }
100
-
101
- const anyChanges = this.anyChanges(repositoryState.phaseDateTime, true) || repositoryState.anyDependenciesUpdated;
102
-
103
- if (anyChanges) {
104
- const switchToAlpha = repositoryState.preReleaseIdentifier !== "alpha";
105
-
106
- if (switchToAlpha) {
107
- // Previous publication was beta or production.
108
- this.updatePackageVersion(undefined, undefined, repositoryState.patchVersion + 1, "alpha");
109
-
110
- // Use specified registry for organization until no longer in alpha mode.
111
- this.run(false, false, "npm", "config", "set", this.atOrganizationRegistry, "--location", "project");
112
- }
113
-
114
- if (repositoryState.anyDependenciesUpdated && (switchToAlpha || !this._updateAll)) {
115
- this.updateOrganizationDependencies();
116
- }
117
- }
118
-
119
- // Run lint if present.
120
- this.run(false, false, "npm", "run", "lint", "--if-present");
121
-
122
- // Run development build if present.
123
- this.run(false, false, "npm", "run", "build:dev", "--if-present");
124
-
125
- if (anyChanges) {
126
- const now = new Date();
127
- const nowISOString = now.toISOString();
128
-
129
- // Nothing further required if this repository is not a dependency of others.
130
- if (repositoryState.repository.dependencyType !== "none") {
131
- if (!this.dryRun) {
132
- // Backup the package configuration file.
133
- fs.renameSync(PACKAGE_CONFIGURATION_PATH, BACKUP_PACKAGE_CONFIGURATION_PATH);
134
- }
135
-
136
- try {
137
- // Package version is transient.
138
- this.updatePackageVersion(undefined, undefined, undefined, `alpha.${nowISOString.replaceAll(/\D/g, "").substring(0, 12)}`);
139
-
140
- // Publish to development NPM registry.
141
- this.run(false, false, "npm", "publish", "--tag", "alpha");
142
-
143
- // Unpublish all prior alpha versions.
144
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Output is a JSON array.
145
- for (const version of JSON.parse(this.run(true, true, "npm", "view", packageConfiguration.name, "versions", "--json").join("\n")) as string[]) {
146
- if (/^\d+.\d+.\d+-alpha.\d+$/.test(version) && version !== packageConfiguration.version) {
147
- this.run(false, false, "npm", "unpublish", `${packageConfiguration.name}@${version}`);
148
- }
149
- }
150
- } finally {
151
- if (!this.dryRun) {
152
- // Restore the package configuration file.
153
- fs.rmSync(PACKAGE_CONFIGURATION_PATH);
154
- fs.renameSync(BACKUP_PACKAGE_CONFIGURATION_PATH, PACKAGE_CONFIGURATION_PATH);
155
- }
156
- }
157
- }
158
-
159
- this.commitUpdatedPackageVersion(PACKAGE_CONFIGURATION_PATH, PACKAGE_LOCK_CONFIGURATION_PATH);
160
-
161
- this.updatePhaseState({
162
- dateTime: now
163
- });
164
- }
165
- }
166
- }
167
-
168
- // Detailed syntax checking not required as this is an internal tool.
169
- await new PublishAlpha(process.argv.includes("--update-all"), process.argv.includes("--dry-run")).publishAll().catch((e: unknown) => {
170
- logger.error(e);
171
- });
@@ -1,361 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { setTimeout } from "node:timers/promises";
4
- import { Octokit } from "octokit";
5
- import { parse as yamlParse } from "yaml";
6
- import secureConfigurationJSON from "../../config/publish.secure.json";
7
- import type { Repository } from "./configuration";
8
- import { Publish } from "./publish.js";
9
- import { logger } from "./logger.js";
10
-
11
- /**
12
- * Configuration layout of publish.secure.json.
13
- */
14
- interface SecureConfiguration {
15
- token: string;
16
- }
17
-
18
- /**
19
- * Configuration layout of release.yml workflow (relevant attributes only).
20
- */
21
- interface WorkflowConfiguration {
22
- /**
23
- * Workflow name.
24
- */
25
- name: string;
26
-
27
- /**
28
- * Workflow trigger.
29
- */
30
- on: {
31
- /**
32
- * Push trigger.
33
- */
34
- push?: {
35
- /**
36
- * Push branches.
37
- */
38
- branches?: string[];
39
- } | null;
40
-
41
- /**
42
- * Release trigger.
43
- */
44
- release?: {
45
- /**
46
- * Release types.
47
- */
48
- types?: string[];
49
- } | null;
50
- };
51
- }
52
-
53
- /**
54
- * Publish steps.
55
- */
56
- type Step = "update" | "build" | "commit" | "tag" | "push" | "workflow (push)" | "release" | "workflow (release)" | "complete";
57
-
58
- /**
59
- * Publish beta versions.
60
- */
61
- class PublishBeta extends Publish {
62
- /**
63
- * Secure configuration.
64
- */
65
- private readonly _secureConfiguration: SecureConfiguration = secureConfigurationJSON;
66
-
67
- /**
68
- * Octokit.
69
- */
70
- private readonly _octokit: Octokit;
71
-
72
- /**
73
- * Constructor.
74
- *
75
- * @param dryRun
76
- * If true, outputs what would be run rather than running it.
77
- */
78
- constructor(dryRun: boolean) {
79
- super("beta", dryRun);
80
-
81
- this._octokit = new Octokit({
82
- auth: this._secureConfiguration.token,
83
- userAgent: `${this.configuration.organization} release`
84
- });
85
- }
86
-
87
- /**
88
- * @inheritDoc
89
- */
90
- protected dependencyVersionFor(dependencyRepositoryName: string, dependencyRepository: Repository): string {
91
- let dependencyVersion: string;
92
-
93
- switch (dependencyRepository.dependencyType) {
94
- case "external":
95
- dependencyVersion = "beta";
96
- break;
97
-
98
- case "internal": {
99
- const betaTag = dependencyRepository.phaseStates.beta?.tag;
100
-
101
- if (betaTag === undefined) {
102
- throw new Error(`*** Internal error *** Beta tag not set for ${dependencyRepositoryName}`);
103
- }
104
-
105
- dependencyVersion = `${this.configuration.organization}/${dependencyRepositoryName}#${betaTag}`;
106
- }
107
- break;
108
-
109
- default:
110
- throw new Error(`Invalid dependency type "${dependencyRepository.dependencyType}" for dependency ${dependencyRepositoryName}`);
111
- }
112
-
113
- return dependencyVersion;
114
- }
115
-
116
- /**
117
- * @inheritDoc
118
- */
119
- protected getPhaseDateTime(repository: Repository, phaseDateTime: Date): Date {
120
- return this.latestDateTime(phaseDateTime, repository.phaseStates.production?.dateTime);
121
- }
122
-
123
- /**
124
- * @inheritDoc
125
- */
126
- protected isValidBranch(): boolean {
127
- const repositoryState = this.repositoryState;
128
-
129
- // Branch for beta phase must match version.
130
- return repositoryState.branch === `v${repositoryState.majorVersion}.${repositoryState.minorVersion}`;
131
- }
132
-
133
- /**
134
- * Run a step.
135
- *
136
- * Repository.
137
- *
138
- * @param step
139
- * State at which step takes place.
140
- *
141
- * @param stepRunner
142
- * Callback to execute step.
143
- */
144
- private async runStep(step: Step, stepRunner: () => (void | Promise<void>)): Promise<void> {
145
- const phaseStateStep = this.repositoryState.phaseState.step;
146
-
147
- if (phaseStateStep === undefined || phaseStateStep === step) {
148
- logger.debug(`Running step ${step}`);
149
-
150
- this.updatePhaseState({
151
- step
152
- });
153
-
154
- await stepRunner();
155
-
156
- this.updatePhaseState({
157
- step: undefined
158
- });
159
- } else {
160
- logger.debug(`Skipping step ${step}`);
161
- }
162
- }
163
-
164
- /**
165
- * Validate the workflow by waiting for it to complete.
166
- *
167
- * Branch on which workflow is running.
168
- */
169
- private async validateWorkflow(): Promise<void> {
170
- if (this.dryRun) {
171
- logger.info("Dry run: Validate workflow");
172
- } else {
173
- const commitSHA = this.run(true, true, "git", "rev-parse", this.repositoryState.branch)[0];
174
-
175
- let completed = false;
176
- let queryCount = 0;
177
- let workflowRunID = -1;
178
-
179
- do {
180
- // eslint-disable-next-line no-await-in-loop -- Loop depends on awaited response.
181
- const response = await setTimeout(2000).then(
182
- async () => this._octokit.rest.actions.listWorkflowRunsForRepo({
183
- owner: this.configuration.organization,
184
- repo: this.repositoryState.repositoryName,
185
- head_sha: commitSHA
186
- })
187
- );
188
-
189
- for (const workflowRun of response.data.workflow_runs) {
190
- if (workflowRun.status !== "completed") {
191
- if (workflowRun.id === workflowRunID) {
192
- process.stdout.write(".");
193
- } else if (workflowRunID === -1) {
194
- workflowRunID = workflowRun.id;
195
-
196
- logger.info(`Workflow run ID ${workflowRunID}`);
197
- } else {
198
- throw new Error(`Parallel workflow runs for SHA ${commitSHA}`);
199
- }
200
- } else if (workflowRun.id === workflowRunID) {
201
- process.stdout.write("\n");
202
-
203
- if (workflowRun.conclusion !== "success") {
204
- throw new Error(`Workflow ${workflowRun.conclusion}`);
205
- }
206
-
207
- completed = true;
208
- }
209
- }
210
-
211
- // Abort if workflow run not started after 10 queries.
212
- if (++queryCount === 10 && workflowRunID === -1) {
213
- throw new Error(`Workflow run not started for SHA ${commitSHA}`);
214
- }
215
- } while (!completed);
216
- }
217
- }
218
-
219
- /**
220
- * @inheritDoc
221
- */
222
- protected async publish(): Promise<void> {
223
- let publish: boolean;
224
-
225
- const repositoryState = this.repositoryState;
226
-
227
- // Scrap any incomplete publishing if pre-release identifier is not beta.
228
- if (repositoryState.preReleaseIdentifier !== "beta") {
229
- repositoryState.phaseState.step = undefined;
230
- }
231
-
232
- if (repositoryState.preReleaseIdentifier === "alpha") {
233
- if (this.anyChanges(repositoryState.repository.phaseStates.alpha?.dateTime, false)) {
234
- throw new Error("Repository has changed since last alpha published");
235
- }
236
-
237
- publish = true;
238
-
239
- this.updatePackageVersion(undefined, undefined, undefined, "beta");
240
-
241
- // Revert to default registry for organization.
242
- this.run(false, false, "npm", "config", "delete", this.atOrganizationRegistry, "--location", "project");
243
- } else {
244
- const step = repositoryState.phaseState.step;
245
- const startingPublication = step === undefined;
246
-
247
- // Step is defined and not "complete" if previous attempt failed at that step.
248
- publish = !startingPublication && step !== "complete";
249
-
250
- // Ignore changes after publication process has started.
251
- if (startingPublication && this.anyChanges(repositoryState.repository.phaseStates.alpha?.dateTime, false)) {
252
- throw new Error("Repository has changed since last alpha published");
253
- }
254
- }
255
-
256
- if (publish) {
257
- const tag = `v${repositoryState.packageConfiguration.version}`;
258
-
259
- if (repositoryState.phaseState.step !== undefined) {
260
- logger.debug(`Repository failed at step "${repositoryState.phaseState.step}" on prior run`);
261
- }
262
-
263
- const workflowsPath = ".github/workflows/";
264
-
265
- let hasPushWorkflow = false;
266
- let hasReleaseWorkflow = false;
267
-
268
- if (fs.existsSync(workflowsPath)) {
269
- logger.debug("Checking workflows");
270
-
271
- for (const workflowFile of fs.readdirSync(workflowsPath).filter(workflowFile => workflowFile.endsWith(".yml"))) {
272
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Workflow configuration format is known.
273
- const workflowOn = (yamlParse(fs.readFileSync(path.resolve(workflowsPath, workflowFile)).toString()) as WorkflowConfiguration).on;
274
-
275
- if (workflowOn.push !== undefined && (workflowOn.push?.branches === undefined || workflowOn.push.branches.includes("v*"))) {
276
- logger.debug("Repository has push workflow");
277
-
278
- hasPushWorkflow = true;
279
- }
280
-
281
- if (workflowOn.release !== undefined && (workflowOn.release?.types === undefined || workflowOn.release.types.includes("published"))) {
282
- logger.debug("Repository has release workflow");
283
-
284
- hasReleaseWorkflow = true;
285
- }
286
- }
287
- }
288
-
289
- await this.runStep("update", () => {
290
- this.updateOrganizationDependencies();
291
- });
292
-
293
- await this.runStep("build", () => {
294
- this.run(false, false, "npm", "run", "build:release", "--if-present");
295
-
296
- // Run test if present; must be part of build as correcting errors will require rebuild.
297
- this.run(false, false, "npm", "run", "test", "--if-present");
298
- });
299
-
300
- await this.runStep("commit", () => {
301
- this.commitUpdatedPackageVersion();
302
- });
303
-
304
- await this.runStep("tag", () => {
305
- this.run(false, false, "git", "tag", tag);
306
- });
307
-
308
- await this.runStep("push", () => {
309
- this.run(false, false, "git", "push", "--atomic", "origin", repositoryState.branch, tag);
310
- });
311
-
312
- if (hasPushWorkflow) {
313
- await this.runStep("workflow (push)", async () => {
314
- await this.validateWorkflow();
315
- });
316
- }
317
-
318
- await this.runStep("release", async () => {
319
- if (this.dryRun) {
320
- logger.info("Dry run: Create release");
321
- } else {
322
- await this._octokit.rest.repos.createRelease({
323
- owner: this.configuration.organization,
324
- repo: repositoryState.repositoryName,
325
- tag_name: tag,
326
- name: `Release ${tag}`,
327
- prerelease: true
328
- });
329
- }
330
- });
331
-
332
- if (hasReleaseWorkflow) {
333
- await this.runStep("workflow (release)", async () => {
334
- await this.validateWorkflow();
335
- });
336
- }
337
-
338
- this.updatePhaseState({
339
- dateTime: new Date(),
340
- tag,
341
- step: "complete"
342
- });
343
- }
344
- }
345
-
346
- /**
347
- * @inheritDoc
348
- */
349
- protected override finalizeAll(): void {
350
- // Publication complete; reset steps to undefined for next run.
351
- for (const repository of Object.values(this.configuration.repositories)) {
352
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- All beta phase states are defined by this point.
353
- repository.phaseStates.beta!.step = undefined;
354
- }
355
- }
356
- }
357
-
358
- // Detailed syntax checking not required as this is an internal tool.
359
- await new PublishBeta(process.argv.includes("--dry-run")).publishAll().catch((e: unknown) => {
360
- logger.error(e);
361
- });