@aidc-toolkit/dev 1.0.23-beta → 1.0.24-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,875 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import * as fs from "node:fs";
3
- import * as process from "node:process";
4
- import {
5
- type Configuration,
6
- loadConfiguration,
7
- type Phase,
8
- type PhaseState,
9
- type Repository,
10
- saveConfiguration,
11
- SHARED_CONFIGURATION_PATH
12
- } from "./configuration.js";
13
- import { logger, setLogLevel } from "./logger.js";
14
- import { pick } from "./type-helper.js";
15
-
16
- export const PACKAGE_CONFIGURATION_PATH = "package.json";
17
-
18
- export const PACKAGE_LOCK_CONFIGURATION_PATH = "package-lock.json";
19
-
20
- /**
21
- * Configuration layout of package.json (relevant attributes only).
22
- */
23
- export interface PackageConfiguration {
24
- /**
25
- * Name.
26
- */
27
- readonly name: string;
28
-
29
- /**
30
- * Version.
31
- */
32
- version: string;
33
-
34
- /**
35
- * Development dependencies.
36
- */
37
- readonly devDependencies?: Record<string, string>;
38
-
39
- /**
40
- * Dependencies.
41
- */
42
- readonly dependencies?: Record<string, string>;
43
- }
44
-
45
- /**
46
- * Repository state, derived from package configuration and updated during publishing.
47
- */
48
- interface RepositoryState {
49
- /**
50
- * Repository name from configuration.
51
- */
52
- readonly repositoryName: string;
53
-
54
- /**
55
- * Repository from configuration.
56
- */
57
- repository: Repository;
58
-
59
- /**
60
- * Phase state.
61
- */
62
- phaseState: PhaseState;
63
-
64
- /**
65
- * Phase date/time or undefined if phase never before published.
66
- */
67
- phaseDateTime: Date | undefined;
68
-
69
- /**
70
- * NPM platform arguments if any.
71
- */
72
- readonly npmPlatformArgs: readonly string[];
73
-
74
- /**
75
- * Branch.
76
- */
77
- readonly branch: string;
78
-
79
- /**
80
- * Package configuration.
81
- */
82
- readonly packageConfiguration: PackageConfiguration;
83
-
84
- /**
85
- * Major version.
86
- */
87
- majorVersion: number;
88
-
89
- /**
90
- * Minor version.
91
- */
92
- minorVersion: number;
93
-
94
- /**
95
- * Patch version.
96
- */
97
- patchVersion: number;
98
-
99
- /**
100
- * Pre-release identifier or null if none.
101
- */
102
- preReleaseIdentifier: string | null;
103
-
104
- /**
105
- * Dependency package names extracted directly from package configuration.
106
- */
107
- readonly dependencyPackageNames: readonly string[];
108
-
109
- /**
110
- * All dependency package names in publication order.
111
- */
112
- readonly allDependencyPackageNames: readonly string[];
113
-
114
- /**
115
- * True if any dependencies have been updated.
116
- */
117
- readonly anyDependenciesUpdated: boolean;
118
- }
119
-
120
- /**
121
- * Publish base class.
122
- */
123
- export abstract class Publish {
124
- /**
125
- * Phase.
126
- */
127
- private readonly _phase: Phase;
128
-
129
- /**
130
- * If true, outputs what would be run rather than running it.
131
- */
132
- private readonly _dryRun: boolean;
133
-
134
- /**
135
- * Configuration. Merger of shared and local configurations.
136
- */
137
- private readonly _configuration: Configuration;
138
-
139
- /**
140
- * At organization.
141
- */
142
- private readonly _atOrganization: string;
143
-
144
- /**
145
- * At organization registry parameter.
146
- */
147
- private readonly _atOrganizationRegistry: string;
148
-
149
- /**
150
- * Repository states, keyed on repository name.
151
- */
152
- private readonly _repositoryStates: Record<string, Readonly<RepositoryState>>;
153
-
154
- /**
155
- * Current repository state.
156
- */
157
- private _repositoryState: RepositoryState | undefined;
158
-
159
- /**
160
- * Constructor.
161
- *
162
- * @param releaseType
163
- * Release type.
164
- *
165
- * @param dryRun
166
- * If true, outputs what would be run rather than running it.
167
- */
168
- protected constructor(releaseType: Phase, dryRun: boolean) {
169
- this._phase = releaseType;
170
- this._dryRun = dryRun;
171
-
172
- this._configuration = loadConfiguration();
173
-
174
- this._atOrganization = `@${this.configuration.organization}`;
175
-
176
- this._atOrganizationRegistry = `${this.atOrganization}:registry${releaseType === "alpha" ? `=${this.configuration.alphaRegistry}` : ""}`;
177
-
178
- if (this._configuration.logLevel !== undefined) {
179
- setLogLevel(this._configuration.logLevel);
180
- }
181
-
182
- this._repositoryStates = {};
183
- this._repositoryState = undefined;
184
- }
185
-
186
- /**
187
- * Get the phase.
188
- */
189
- protected get phase(): Phase {
190
- return this._phase;
191
- }
192
-
193
- /**
194
- * Determine if outputs what would be run rather than running it.
195
- */
196
- protected get dryRun(): boolean {
197
- return this._dryRun;
198
- }
199
-
200
- /**
201
- * Get the configuration.
202
- */
203
- protected get configuration(): Configuration {
204
- return this._configuration;
205
- }
206
-
207
- /**
208
- * Get the at organization.
209
- */
210
- protected get atOrganization(): string {
211
- return this._atOrganization;
212
- }
213
-
214
- /**
215
- * Get the at organization registry parameter.
216
- */
217
- protected get atOrganizationRegistry(): string {
218
- return this._atOrganizationRegistry;
219
- }
220
-
221
- /**
222
- * Get the repository states, keyed on repository name.
223
- */
224
- protected get repositoryStates(): Record<string, Readonly<RepositoryState>> {
225
- return this._repositoryStates;
226
- }
227
-
228
- /**
229
- * Get the current repository state.
230
- */
231
- protected get repositoryState(): RepositoryState {
232
- // Repository state should be accessed only during active publication.
233
- if (this._repositoryState === undefined) {
234
- throw new Error("Repository state not defined");
235
- }
236
-
237
- return this._repositoryState;
238
- }
239
-
240
- /**
241
- * Get the dependency version for a dependency repository.
242
- *
243
- * @param dependencyRepositoryName
244
- * Dependency repository name.
245
- *
246
- * @param dependencyRepository
247
- * Dependency repository.
248
- *
249
- * @returns
250
- * Dependency version.
251
- */
252
- protected abstract dependencyVersionFor(dependencyRepositoryName: string, dependencyRepository: Repository): string;
253
-
254
- /**
255
- * Determine the latest date/time or undefined if all undefined.
256
- *
257
- * @param initialDateTime
258
- * Initial date/time.
259
- *
260
- * @param additionalDateTimes
261
- * Additional date/times.
262
- *
263
- * @returns
264
- * Latest date/time.
265
- */
266
- protected latestDateTime(initialDateTime: Date, ...additionalDateTimes: Array<Date | undefined>): Date {
267
- let latestDateTime = initialDateTime;
268
-
269
- for (const dateTime of additionalDateTimes) {
270
- if (dateTime !== undefined && latestDateTime.getTime() < dateTime.getTime()) {
271
- latestDateTime = dateTime;
272
- }
273
- }
274
-
275
- return latestDateTime;
276
- }
277
-
278
- /**
279
- * Get the phase date/time for a repository.
280
- *
281
- * @param repository
282
- * Repository.
283
- *
284
- * @param phaseDateTime
285
- * Initial phase date/time.
286
- *
287
- * @returns
288
- * Phase date/time or undefined if phase never before published.
289
- */
290
- protected abstract getPhaseDateTime(repository: Repository, phaseDateTime: Date): Date;
291
-
292
- /**
293
- * Determine if branch is valid for the phase.
294
- *
295
- * @returns
296
- * True if branch is valid for the phase.
297
- */
298
- protected abstract isValidBranch(): boolean;
299
-
300
- /**
301
- * Run a command and optionally capture its output.
302
- *
303
- * @param ignoreDryRun
304
- * If true, dry run setting is ignored.
305
- *
306
- * @param captureOutput
307
- * If true, output is captured and returned.
308
- *
309
- * @param command
310
- * Command to run.
311
- *
312
- * @param args
313
- * Arguments to command.
314
- *
315
- * @returns
316
- * Output if captured or empty array if not.
317
- */
318
- protected run(ignoreDryRun: boolean, captureOutput: boolean, command: string, ...args: string[]): string[] {
319
- if (!ignoreDryRun && captureOutput) {
320
- throw new Error("Cannot capture output in dry run");
321
- }
322
-
323
- let output: string[];
324
-
325
- const runningCommand = `Running command "${command}" with arguments [${args.join(", ")}].`;
326
-
327
- if (this.dryRun && !ignoreDryRun) {
328
- logger.info(`Dry run: ${runningCommand}`);
329
-
330
- output = [];
331
- } else {
332
- logger.debug(runningCommand);
333
-
334
- const spawnResult = spawnSync(command, args, {
335
- stdio: ["inherit", captureOutput ? "pipe" : "inherit", "inherit"]
336
- });
337
-
338
- if (spawnResult.error !== undefined) {
339
- throw spawnResult.error;
340
- }
341
-
342
- if (spawnResult.status === null) {
343
- throw new Error(`Terminated by signal ${spawnResult.signal}`);
344
- }
345
-
346
- if (spawnResult.status !== 0) {
347
- throw new Error(`Failed with status ${spawnResult.status}`);
348
- }
349
-
350
- // Last line is also terminated by newline and split() places empty string at the end, so use slice() to remove it.
351
- output = captureOutput ? spawnResult.stdout.toString().split("\n").slice(0, -1) : [];
352
-
353
- if (captureOutput) {
354
- logger.trace(`Output:\n${output.join("\n")}`);
355
- }
356
- }
357
-
358
- return output;
359
- }
360
-
361
- /**
362
- * Get the repository name for a dependency if it belongs to the organization or null if not.
363
- *
364
- * @param dependency
365
- * Dependency.
366
- *
367
- * @returns
368
- * Repository name for dependency or null.
369
- */
370
- protected dependencyRepositoryName(dependency: string): string | null {
371
- const parsedDependency = dependency.split("/");
372
-
373
- return parsedDependency.length === 2 && parsedDependency[0] === this.atOrganization ? parsedDependency[1] : null;
374
- }
375
-
376
- /**
377
- * Determine if an organization dependency has been updated.
378
- *
379
- * @param phaseDateTime
380
- * Phase date/time of the current repository.
381
- *
382
- * @param dependencyRepositoryName
383
- * Dependency repository name.
384
- *
385
- * @param isAdditional
386
- * True if this is an additional dependency.
387
- *
388
- * @returns
389
- * True if organization dependency has been updated.
390
- */
391
- private isOrganizationDependencyUpdated(phaseDateTime: Date, dependencyRepositoryName: string, isAdditional: boolean): boolean {
392
- const dependencyString = !isAdditional ? "Dependency" : "Additional dependency";
393
-
394
- // If dependency repository state exists, so does dependency repository.
395
- if (!(dependencyRepositoryName in this.repositoryStates)) {
396
- throw new Error(`${dependencyString} repository ${dependencyRepositoryName} not yet published`);
397
- }
398
-
399
- const dependencyRepository = this.configuration.repositories[dependencyRepositoryName];
400
- const dependencyPhaseState = dependencyRepository.phaseStates[this.phase];
401
-
402
- if (dependencyPhaseState === undefined) {
403
- throw new Error(`*** Internal error *** ${dependencyString} ${dependencyRepositoryName} does not have state for ${this.phase} phase`);
404
- }
405
-
406
- const isUpdated = phaseDateTime.getTime() < this.getPhaseDateTime(dependencyRepository, dependencyPhaseState.dateTime).getTime();
407
-
408
- if (isUpdated) {
409
- logger.trace(`Dependency repository ${dependencyRepositoryName} updated`);
410
- }
411
-
412
- return isUpdated;
413
- }
414
-
415
- /**
416
- * Determine if there have been any changes to the current repository.
417
- *
418
- * @param phaseDateTime
419
- * Phase date/time to check against or undefined if phase never before published.
420
- *
421
- * @param ignoreGitHub
422
- * If true, ignore .github directory.
423
- *
424
- * @returns
425
- * True if there have been any changes since the phase date/time.
426
- */
427
- protected anyChanges(phaseDateTime: Date | undefined, ignoreGitHub: boolean): boolean {
428
- let anyChanges: boolean;
429
-
430
- const excludePaths = this.repositoryState.repository.excludePaths ?? [];
431
-
432
- const changedFilesSet = new Set<string>();
433
-
434
- /**
435
- * Process a changed file.
436
- *
437
- * @param status
438
- * "R" if the file has been renamed, "D" if the file has been deleted, otherwise file has been added.
439
- *
440
- * @param file
441
- * Original file name if status is "R", otherwise file to be added or deleted.
442
- *
443
- * @param newFile
444
- * New file name if status is "R", undefined otherwise.
445
- */
446
- function processChangedFile(status: string, file: string, newFile: string | undefined): void {
447
- // Status is "D" if deleted, "R" if renamed.
448
- const deleteFile = status === "D" || status === "R" ? file : undefined;
449
- const addFile = status === "R" ? newFile : status !== "D" ? file : undefined;
450
-
451
- // Remove deleted file; anything that depends on a deleted file will have been modified.
452
- if (deleteFile !== undefined && changedFilesSet.delete(deleteFile)) {
453
- logger.debug(`-${deleteFile}`);
454
- }
455
-
456
- if (addFile !== undefined && !changedFilesSet.has(addFile)) {
457
- // Exclude hidden files and directories except possibly .github directory, package-lock.json, test directory, and any explicitly excluded files or directories.
458
- if (((!addFile.startsWith(".") && !addFile.includes("/.")) || (!ignoreGitHub && addFile.startsWith(".github/"))) && addFile !== PACKAGE_LOCK_CONFIGURATION_PATH && !addFile.startsWith("test/") && excludePaths.filter(excludePath => addFile === excludePath || (excludePath.endsWith("/") && addFile.startsWith(excludePath))).length === 0) {
459
- logger.debug(`+${addFile}`);
460
-
461
- changedFilesSet.add(addFile);
462
- } else {
463
- // File is excluded.
464
- logger.debug(`*${addFile}`);
465
- }
466
- }
467
- }
468
-
469
- if (this.phase !== "alpha" && this.run(true, true, "git", "fetch", "--porcelain", "--dry-run").length !== 0) {
470
- throw new Error("Remote repository has outstanding changes");
471
- }
472
-
473
- // Phase date/time is undefined if never before published.
474
- if (phaseDateTime !== undefined) {
475
- // Get all files committed since last published.
476
- for (const line of this.run(true, true, "git", "log", "--since", phaseDateTime.toISOString(), "--name-status", "--pretty=oneline")) {
477
- // Header starts with 40-character SHA.
478
- if (/^[0-9a-f]{40} /.test(line)) {
479
- logger.debug(`Commit SHA ${line.substring(0, 40)}`);
480
- } else {
481
- const [status, file, newFile] = line.split("\t");
482
-
483
- processChangedFile(status.charAt(0), file, newFile);
484
- }
485
- }
486
-
487
- // Get all uncommitted files.
488
- const output = this.run(true, true, "git", "status", "--porcelain");
489
-
490
- if (output.length !== 0) {
491
- const committedCount = changedFilesSet.size;
492
-
493
- logger.debug("Uncommitted");
494
-
495
- for (const line of output) {
496
- // Line is two-character status, space, and detail.
497
- const status = line.substring(0, 1);
498
- const [file, newFile] = line.substring(3).split(" -> ");
499
-
500
- processChangedFile(status, file, newFile);
501
- }
502
-
503
- // Beta or production publication requires that repository be fully committed except for excluded paths.
504
- if (this.phase !== "alpha" && changedFilesSet.size !== committedCount) {
505
- throw new Error("Repository has uncommitted changes");
506
- }
507
- }
508
-
509
- const lastPublishedDateTime = new Date(phaseDateTime);
510
-
511
- anyChanges = false;
512
-
513
- for (const changedFile of changedFilesSet) {
514
- if (fs.lstatSync(changedFile).mtime > lastPublishedDateTime) {
515
- if (!anyChanges) {
516
- logger.info("Changes");
517
-
518
- anyChanges = true;
519
- }
520
-
521
- logger.info(`>${changedFile}`);
522
- }
523
- }
524
-
525
- if (!anyChanges) {
526
- logger.info("No changes");
527
- }
528
- } else {
529
- logger.info("Never published");
530
-
531
- // No last published, so there must have been changes.
532
- anyChanges = true;
533
- }
534
-
535
- return anyChanges;
536
- }
537
-
538
- /**
539
- * Commit files that have been modified.
540
- *
541
- * @param message
542
- * Commit message.
543
- *
544
- * @param files
545
- * Files to commit; if none, defaults to "--all".
546
- */
547
- protected commitModified(message: string, ...files: string[]): void {
548
- const modifiedFiles: string[] = [];
549
-
550
- if (files.length === 0) {
551
- modifiedFiles.push("--all");
552
- } else {
553
- for (const line of this.run(true, true, "git", "status", ...files, "--porcelain")) {
554
- const status = line.substring(0, 3);
555
- const modifiedFile = line.substring(3);
556
-
557
- // Only interest is in local additions and modifications with no conflicts.
558
- if (status !== "A " && status !== " M " && status !== "AM ") {
559
- throw new Error(`Unsupported status "${status}" for ${modifiedFile}`);
560
- }
561
-
562
- modifiedFiles.push(modifiedFile);
563
- }
564
- }
565
-
566
- if (modifiedFiles.length !== 0) {
567
- this.run(false, false, "git", "commit", ...modifiedFiles, "--message", message);
568
- }
569
- }
570
-
571
- /**
572
- * Save package configuration.
573
- */
574
- protected savePackageConfiguration(): void {
575
- const packageConfiguration = this.repositoryState.packageConfiguration;
576
-
577
- if (this.dryRun) {
578
- logger.info(`Dry run: Saving package configuration\n${JSON.stringify(pick(packageConfiguration, "name", "version", "devDependencies", "dependencies"), null, 2)}\n`);
579
- } else {
580
- fs.writeFileSync(PACKAGE_CONFIGURATION_PATH, `${JSON.stringify(packageConfiguration, null, 2)}\n`);
581
- }
582
- }
583
-
584
- /**
585
- * Update the package version.
586
- *
587
- * @param majorVersion
588
- * Major version or undefined if no change.
589
- *
590
- * @param minorVersion
591
- * Minor version or undefined if no change.
592
- *
593
- * @param patchVersion
594
- * Patch version or undefined if no change.
595
- *
596
- * @param preReleaseIdentifier
597
- * Pre-release identifier or undefined if no change.
598
- */
599
- protected updatePackageVersion(majorVersion: number | undefined, minorVersion: number | undefined, patchVersion: number | undefined, preReleaseIdentifier: string | null | undefined): void {
600
- const repositoryState = this.repositoryState;
601
-
602
- if (majorVersion !== undefined) {
603
- repositoryState.majorVersion = majorVersion;
604
- }
605
-
606
- if (minorVersion !== undefined) {
607
- repositoryState.minorVersion = minorVersion;
608
- }
609
-
610
- if (patchVersion !== undefined) {
611
- repositoryState.patchVersion = patchVersion;
612
- }
613
-
614
- if (preReleaseIdentifier !== undefined) {
615
- repositoryState.preReleaseIdentifier = preReleaseIdentifier;
616
- }
617
-
618
- repositoryState.packageConfiguration.version = `${repositoryState.majorVersion}.${repositoryState.minorVersion}.${repositoryState.patchVersion}${repositoryState.preReleaseIdentifier !== null ? `-${repositoryState.preReleaseIdentifier}` : ""}`;
619
-
620
- this.savePackageConfiguration();
621
- }
622
-
623
- /**
624
- * Update organization dependencies.
625
- */
626
- protected updateOrganizationDependencies(): void {
627
- const repositoryState = this.repositoryState;
628
-
629
- logger.debug(`Updating organization dependencies [${repositoryState.allDependencyPackageNames.join(", ")}]`);
630
-
631
- this.run(false, false, "npm", "update", ...repositoryState.allDependencyPackageNames, ...this.repositoryState.npmPlatformArgs);
632
- }
633
-
634
- /**
635
- * Commit changes resulting from updating the package version.
636
- *
637
- * @param files
638
- * Files to commit; if none, defaults to "--all".
639
- */
640
- protected commitUpdatedPackageVersion(...files: string[]): void {
641
- this.commitModified(`Updated to version ${this.repositoryState.packageConfiguration.version}.`, ...files);
642
- }
643
-
644
- /**
645
- * Update the phase state. This will replace the phase state object in the repository and repository state and may
646
- * update the phase date/time in the repository state.
647
- *
648
- * @param phaseState
649
- * Partial phases state. Only those properties provided will be updated.
650
- */
651
- protected updatePhaseState(phaseState: Partial<PhaseState>): void {
652
- const repositoryState = this.repositoryState;
653
-
654
- const phaseStateDateTime = phaseState.dateTime !== undefined ?
655
- {
656
- // Git resolution is one second so round up to next second to ensure that comparisons work as expected.
657
- dateTime: new Date(phaseState.dateTime.getTime() - phaseState.dateTime.getMilliseconds() + 1000)
658
- } :
659
- {};
660
-
661
- const updatedPhaseState = {
662
- ...this.repositoryState.phaseState,
663
- ...phaseState,
664
- ...phaseStateDateTime
665
- };
666
-
667
- repositoryState.repository.phaseStates[this.phase] = updatedPhaseState;
668
- repositoryState.phaseState = updatedPhaseState;
669
-
670
- // Setting the phase date/time overrides the logic of its initial determination.
671
- if (phaseStateDateTime.dateTime !== undefined) {
672
- repositoryState.phaseDateTime = phaseStateDateTime.dateTime;
673
- }
674
- }
675
-
676
- /**
677
- * Save the configuration.
678
- */
679
- private saveConfiguration(): void {
680
- saveConfiguration(this.configuration, this.dryRun);
681
- }
682
-
683
- /**
684
- * Publish current repository.
685
- */
686
- protected abstract publish(): void | Promise<void>;
687
-
688
- /**
689
- * Publish all repositories.
690
- */
691
- async publishAll(): Promise<void> {
692
- const startDirectory = process.cwd();
693
-
694
- for (const [repositoryName, repository] of Object.entries(this.configuration.repositories)) {
695
- // All repositories are expected to be children of the parent of this repository.
696
- const directory = `../${repository.directory ?? repositoryName}`;
697
-
698
- if (fs.existsSync(directory) && fs.statSync(directory).isDirectory()) {
699
- logger.info(`Repository ${repositoryName}...`);
700
-
701
- process.chdir(directory);
702
-
703
- let phaseState = repository.phaseStates[this.phase];
704
-
705
- // Create phase state if necessary.
706
- if (phaseState === undefined) {
707
- phaseState = {
708
- dateTime: new Date(0)
709
- };
710
-
711
- repository.phaseStates[this.phase] = phaseState;
712
- }
713
-
714
- try {
715
- const phaseDateTime = this.getPhaseDateTime(repository, phaseState.dateTime);
716
-
717
- const npmPlatformArgs = repository.platform !== undefined ?
718
- [
719
- "--cpu",
720
- repository.platform.cpu,
721
- "--os",
722
- repository.platform.os
723
- ] :
724
- [];
725
-
726
- const branch = this.run(true, true, "git", "branch", "--show-current")[0];
727
-
728
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Package configuration format is known.
729
- const packageConfiguration = JSON.parse(fs.readFileSync(PACKAGE_CONFIGURATION_PATH).toString()) as PackageConfiguration;
730
-
731
- const version = packageConfiguration.version;
732
-
733
- const parsedVersion = /^(\d+)\.(\d+)\.(\d+)(-(alpha|beta))?$/.exec(version);
734
-
735
- if (parsedVersion === null) {
736
- throw new Error(`Invalid package version ${version}`);
737
- }
738
-
739
- const majorVersion = Number(parsedVersion[1]);
740
- const minorVersion = Number(parsedVersion[2]);
741
- const patchVersion = Number(parsedVersion[3]);
742
- const preReleaseIdentifier = parsedVersion.length === 6 ? parsedVersion[5] : null;
743
-
744
- const dependencyPackageNames: string[] = [];
745
- const allDependencyPackageNames: string[] = [];
746
-
747
- let anyDependenciesUpdated = false;
748
-
749
- for (const dependencies of [packageConfiguration.devDependencies ?? {}, packageConfiguration.dependencies ?? {}]) {
750
- for (const dependencyPackageName of Object.keys(dependencies)) {
751
- const dependencyRepositoryName = this.dependencyRepositoryName(dependencyPackageName);
752
-
753
- // Dependency repository name is null if dependency is not within the organization.
754
- if (dependencyRepositoryName !== null) {
755
- logger.trace(`Organization dependency ${dependencyPackageName} from package configuration`);
756
-
757
- // Check every dependency for logging purposes.
758
- if (this.isOrganizationDependencyUpdated(phaseDateTime, dependencyRepositoryName, false)) {
759
- anyDependenciesUpdated = true;
760
- }
761
-
762
- for (const dependencyDependencyPackageName of this.repositoryStates[dependencyRepositoryName].allDependencyPackageNames) {
763
- if (!allDependencyPackageNames.includes(dependencyDependencyPackageName)) {
764
- logger.trace(`Organization dependency ${dependencyDependencyPackageName} from dependencies`);
765
-
766
- allDependencyPackageNames.push(dependencyDependencyPackageName);
767
- }
768
- }
769
-
770
- dependencyPackageNames.push(dependencyPackageName);
771
-
772
- // Current dependency package name goes in last to preserve hierarchy.
773
- allDependencyPackageNames.push(dependencyPackageName);
774
-
775
- // Dependency changes will ultimately be discarded if there are no changes and no updates to repository states.
776
- dependencies[dependencyPackageName] = this.dependencyVersionFor(dependencyRepositoryName, this.configuration.repositories[dependencyRepositoryName]);
777
- }
778
- }
779
- }
780
-
781
- if (repository.additionalDependencies !== undefined) {
782
- const additionalRepositoryNames: string[] = [];
783
-
784
- for (const additionalDependencyRepositoryName of repository.additionalDependencies) {
785
- const additionalDependencyPackageName = `${this.atOrganization}/${additionalDependencyRepositoryName}`;
786
-
787
- if (allDependencyPackageNames.includes(additionalDependencyPackageName) || additionalRepositoryNames.includes(additionalDependencyRepositoryName)) {
788
- logger.warn(`Additional dependency repository ${additionalDependencyRepositoryName} already a dependency`);
789
- } else {
790
- logger.trace(`Organization dependency ${additionalDependencyRepositoryName} from additional dependencies`);
791
-
792
- // Check every dependency for logging purposes.
793
- if (this.isOrganizationDependencyUpdated(phaseDateTime, additionalDependencyRepositoryName, true)) {
794
- anyDependenciesUpdated = true;
795
- }
796
-
797
- additionalRepositoryNames.push(additionalDependencyRepositoryName);
798
- }
799
- }
800
- }
801
-
802
- this._repositoryState = {
803
- repositoryName,
804
- repository,
805
- phaseState,
806
- phaseDateTime,
807
- npmPlatformArgs,
808
- branch,
809
- packageConfiguration,
810
- majorVersion,
811
- minorVersion,
812
- patchVersion,
813
- preReleaseIdentifier,
814
- dependencyPackageNames,
815
- allDependencyPackageNames,
816
- anyDependenciesUpdated
817
- };
818
-
819
- // Save repository state for future repositories.
820
- this.repositoryStates[repositoryName] = this._repositoryState;
821
-
822
- if (!this.isValidBranch()) {
823
- throw new Error(`Branch ${branch} is not valid for ${this.phase} phase`);
824
- }
825
-
826
- const parsedBranch = /^v(\d+)\.(\d+)/.exec(branch);
827
-
828
- // If this is a version branch, update the package version if required.
829
- if (parsedBranch !== null) {
830
- const branchMajorVersion = Number(parsedBranch[1]);
831
- const branchMinorVersion = Number(parsedBranch[2]);
832
-
833
- // If in a version branch and version doesn't match, update it.
834
- if (majorVersion !== branchMajorVersion || minorVersion !== branchMinorVersion) {
835
- if (majorVersion !== branchMajorVersion ? majorVersion !== branchMajorVersion - 1 : minorVersion !== branchMinorVersion - 1) {
836
- throw new Error(`Invalid transition from ${majorVersion}.${minorVersion} to ${branchMajorVersion}.${branchMinorVersion}`);
837
- }
838
-
839
- this.updatePackageVersion(branchMajorVersion, branchMinorVersion, 0, null);
840
- this.commitUpdatedPackageVersion(PACKAGE_CONFIGURATION_PATH);
841
- }
842
- }
843
-
844
- // eslint-disable-next-line no-await-in-loop -- Next iteration requires previous to finish.
845
- await this.publish();
846
- } finally {
847
- // Clear repository state to prevent accidental access.
848
- this._repositoryState = undefined;
849
-
850
- // Return to the start directory.
851
- process.chdir(startDirectory);
852
-
853
- this.saveConfiguration();
854
- }
855
- // Non-external repositories may be private and not accessible to all developers.
856
- } else if (repository.dependencyType === "external") {
857
- throw new Error(`Repository ${repositoryName} not found`);
858
- }
859
- }
860
-
861
- this.finalizeAll();
862
-
863
- this.saveConfiguration();
864
-
865
- if (this.phase !== "alpha") {
866
- this.commitModified(`Published ${this.phase} release.`, SHARED_CONFIGURATION_PATH);
867
- }
868
- }
869
-
870
- /**
871
- * Finalize publishing all repositories.
872
- */
873
- protected finalizeAll(): void {
874
- }
875
- }