@git.zone/cli 6.6.1 → 6.7.1
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.
- package/.smartconfig.json +2 -1
- package/assets/templates/asset_tspack_release_gitea/.gitea/workflows/release.yml +4 -3
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/gitzone.cli.js +2 -2
- package/dist_ts/helpers.workflow.d.ts +3 -0
- package/dist_ts/helpers.workflow.js +14 -1
- package/dist_ts/mod_config/index.js +4 -1
- package/dist_ts/mod_deprecate/helpers.deprecation.d.ts +12 -0
- package/dist_ts/mod_deprecate/helpers.deprecation.js +87 -0
- package/dist_ts/mod_deprecate/index.d.ts +1 -1
- package/dist_ts/mod_deprecate/index.js +77 -40
- package/dist_ts/mod_format/classes.baseformatter.d.ts +6 -0
- package/dist_ts/mod_format/classes.baseformatter.js +61 -1
- package/dist_ts/mod_format/classes.formatplanner.js +14 -3
- package/dist_ts/mod_format/classes.formatstats.d.ts +4 -1
- package/dist_ts/mod_format/classes.formatstats.js +11 -1
- package/dist_ts/mod_format/formatters/readme.formatter.d.ts +2 -1
- package/dist_ts/mod_format/formatters/readme.formatter.js +80 -14
- package/dist_ts/mod_format/index.js +2 -1
- package/dist_ts/mod_format/interfaces.format.d.ts +6 -2
- package/dist_ts/mod_format/interfaces.format.js +1 -1
- package/dist_ts/mod_release/classes.releasejournal.d.ts +17 -3
- package/dist_ts/mod_release/classes.releasejournal.js +144 -12
- package/dist_ts/mod_release/helpers.npmartifact.d.ts +3 -1
- package/dist_ts/mod_release/helpers.npmartifact.js +64 -5
- package/dist_ts/mod_release/helpers.releasepublication.js +21 -19
- package/dist_ts/mod_release/index.d.ts +24 -0
- package/dist_ts/mod_release/index.js +82 -22
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +3 -3
- package/readme.md +80 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/gitzone.cli.ts +1 -1
- package/ts/helpers.workflow.ts +17 -0
- package/ts/mod_config/index.ts +3 -0
- package/ts/mod_deprecate/helpers.deprecation.ts +151 -0
- package/ts/mod_deprecate/index.ts +92 -41
- package/ts/mod_format/classes.baseformatter.ts +71 -0
- package/ts/mod_format/classes.formatplanner.ts +16 -3
- package/ts/mod_format/classes.formatstats.ts +14 -1
- package/ts/mod_format/formatters/readme.formatter.ts +89 -14
- package/ts/mod_format/index.ts +1 -0
- package/ts/mod_format/interfaces.format.ts +7 -2
- package/ts/mod_release/classes.releasejournal.ts +219 -20
- package/ts/mod_release/helpers.npmartifact.ts +120 -23
- package/ts/mod_release/helpers.releasepublication.ts +49 -15
- package/ts/mod_release/index.ts +127 -30
- package/ts/plugins.ts +2 -0
- package/readme.hints.md +0 -596
- package/readme.plan.md +0 -176
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
} from "./classes.releasejournal.js";
|
|
6
6
|
import { assertCredentialFreeGitPushDestination } from "./helpers.releasebranch.js";
|
|
7
7
|
|
|
8
|
-
const supportedPnpmVersions = [
|
|
8
|
+
const supportedPnpmVersions = [
|
|
9
|
+
"11.25.0",
|
|
10
|
+
"12.1.0",
|
|
11
|
+
"12.2.1",
|
|
12
|
+
"12.3.4",
|
|
13
|
+
] as const;
|
|
9
14
|
const pnpmHelpRequirements = {
|
|
10
15
|
"11.25.0": {
|
|
11
16
|
pack: ["--out <path>", "--json"],
|
|
@@ -62,17 +67,15 @@ const maximumMetadataResponseBytes = 8 * 1024 * 1024;
|
|
|
62
67
|
const capabilityCommandTimeoutMs = 30_000;
|
|
63
68
|
const packCommandTimeoutMs = 10 * 60_000;
|
|
64
69
|
const publishCommandTimeoutMs = 5 * 60_000;
|
|
65
|
-
const packageNameRegex =
|
|
70
|
+
const packageNameRegex =
|
|
71
|
+
/^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/;
|
|
66
72
|
|
|
67
73
|
export interface IPnpmReleaseCapability {
|
|
68
74
|
version: (typeof supportedPnpmVersions)[number];
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export type TNpmArtifactProbeStatus =
|
|
72
|
-
| "
|
|
73
|
-
| "exact"
|
|
74
|
-
| "conflict"
|
|
75
|
-
| "inconclusive";
|
|
78
|
+
"absent" | "exact" | "conflict" | "inconclusive";
|
|
76
79
|
|
|
77
80
|
export interface INpmArtifactProbeResult {
|
|
78
81
|
status: TNpmArtifactProbeStatus;
|
|
@@ -99,7 +102,10 @@ const readPackageIdentity = async (
|
|
|
99
102
|
let packageJson: unknown;
|
|
100
103
|
try {
|
|
101
104
|
packageJson = JSON.parse(
|
|
102
|
-
await plugins.fs.readFile(
|
|
105
|
+
await plugins.fs.readFile(
|
|
106
|
+
plugins.path.join(cwdArg, "package.json"),
|
|
107
|
+
"utf8",
|
|
108
|
+
),
|
|
103
109
|
);
|
|
104
110
|
} catch (error) {
|
|
105
111
|
throw new Error("Unable to read package.json for npm release packaging.", {
|
|
@@ -190,12 +196,67 @@ export const assertPnpmReleaseCapability = async (
|
|
|
190
196
|
return { version: supportedVersion };
|
|
191
197
|
};
|
|
192
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Development documents must never reach a published tarball.
|
|
201
|
+
*
|
|
202
|
+
* npm-packlist force-includes every root level file whose name starts with
|
|
203
|
+
* "readme" (case insensitive), so neither the package.json files array nor
|
|
204
|
+
* .npmignore can keep readme.plan.md or readme.hints.md out of the package.
|
|
205
|
+
* plan.md and hints.md are not force-included but a broad files array can still
|
|
206
|
+
* capture them, so both spellings are refused here.
|
|
207
|
+
*/
|
|
208
|
+
const developmentDocumentPatterns = [
|
|
209
|
+
/^readme\.plan(?:\..+)?$/i,
|
|
210
|
+
/^readme\.hints(?:\..+)?$/i,
|
|
211
|
+
/^plan\.md$/i,
|
|
212
|
+
/^hints\.md$/i,
|
|
213
|
+
/^readme\..+\.md$/i,
|
|
214
|
+
] as const;
|
|
215
|
+
|
|
216
|
+
export const findDevelopmentDocumentsInPackage = (
|
|
217
|
+
filePathsArg: readonly string[],
|
|
218
|
+
): string[] => {
|
|
219
|
+
const offenders = new Set<string>();
|
|
220
|
+
for (const filePath of filePathsArg) {
|
|
221
|
+
if (typeof filePath !== "string" || !filePath) continue;
|
|
222
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
223
|
+
// Only root level files matter, npm force-inclusion is a root level rule.
|
|
224
|
+
if (normalized.includes("/")) continue;
|
|
225
|
+
if (
|
|
226
|
+
developmentDocumentPatterns.some((patternArg) =>
|
|
227
|
+
patternArg.test(normalized),
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
offenders.add(normalized);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [...offenders].sort();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const assertNoDevelopmentDocumentsInPackage = (
|
|
237
|
+
filePathsArg: readonly string[],
|
|
238
|
+
): void => {
|
|
239
|
+
const offenders = findDevelopmentDocumentsInPackage(filePathsArg);
|
|
240
|
+
if (offenders.length === 0) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
throw new Error(
|
|
244
|
+
`The npm package would publish development documents: ${offenders.join(", ")}. ` +
|
|
245
|
+
'npm force-includes every root file whose name starts with "readme", so neither the ' +
|
|
246
|
+
"package.json files array nor .npmignore can exclude them. Rename them to names that are " +
|
|
247
|
+
"not published (readme.plan.md -> plan.md, readme.hints.md -> hints.md; `gitzone format` " +
|
|
248
|
+
"performs this rename), or drop them from the files array, then release again.",
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
193
252
|
const legacyPublishWorkflowPaths = [
|
|
194
253
|
".gitea/workflows/default_tags.yaml",
|
|
195
254
|
".gitlab-ci.yml",
|
|
196
255
|
] as const;
|
|
197
256
|
|
|
198
|
-
export const assertNoLegacyNpmPublisher = async (
|
|
257
|
+
export const assertNoLegacyNpmPublisher = async (
|
|
258
|
+
cwdArg: string,
|
|
259
|
+
): Promise<void> => {
|
|
199
260
|
for (const relativePath of legacyPublishWorkflowPaths) {
|
|
200
261
|
const filePath = plugins.path.join(cwdArg, relativePath);
|
|
201
262
|
let content: string;
|
|
@@ -205,9 +266,12 @@ export const assertNoLegacyNpmPublisher = async (cwdArg: string): Promise<void>
|
|
|
205
266
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
206
267
|
continue;
|
|
207
268
|
}
|
|
208
|
-
throw new Error(
|
|
209
|
-
|
|
210
|
-
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Unable to inspect ${relativePath} for legacy npm publication.`,
|
|
271
|
+
{
|
|
272
|
+
cause: error,
|
|
273
|
+
},
|
|
274
|
+
);
|
|
211
275
|
}
|
|
212
276
|
const activeContent = content
|
|
213
277
|
.split("\n")
|
|
@@ -288,17 +352,21 @@ export const packNpmArtifact = async (
|
|
|
288
352
|
cwdArg: string,
|
|
289
353
|
destinationDirectoryArg: string,
|
|
290
354
|
expectedVersionArg: string,
|
|
355
|
+
fileArg: string = releaseArtifactFileName,
|
|
291
356
|
): Promise<IReleaseArtifact> => {
|
|
357
|
+
if (
|
|
358
|
+
fileArg !== releaseArtifactFileName &&
|
|
359
|
+
!/^package\.[0-9a-f]{64}\.tgz$/.test(fileArg)
|
|
360
|
+
) {
|
|
361
|
+
throw new Error("Invalid release artifact filename.");
|
|
362
|
+
}
|
|
292
363
|
const identity = await readPackageIdentity(cwdArg);
|
|
293
364
|
if (identity.version !== expectedVersionArg) {
|
|
294
365
|
throw new Error(
|
|
295
366
|
`package.json version ${identity.version} does not match release ${expectedVersionArg}.`,
|
|
296
367
|
);
|
|
297
368
|
}
|
|
298
|
-
const artifactPath = plugins.path.join(
|
|
299
|
-
destinationDirectoryArg,
|
|
300
|
-
releaseArtifactFileName,
|
|
301
|
-
);
|
|
369
|
+
const artifactPath = plugins.path.join(destinationDirectoryArg, fileArg);
|
|
302
370
|
const result = await smartshellArg.execSpawn(
|
|
303
371
|
"pnpm",
|
|
304
372
|
["pack", "--out", artifactPath, "--json"],
|
|
@@ -309,13 +377,17 @@ export const packNpmArtifact = async (
|
|
|
309
377
|
},
|
|
310
378
|
);
|
|
311
379
|
if (result.exitCode !== 0) {
|
|
312
|
-
throw new Error(
|
|
380
|
+
throw new Error(
|
|
381
|
+
"pnpm pack failed while creating the exact release artifact.",
|
|
382
|
+
);
|
|
313
383
|
}
|
|
314
384
|
let report: unknown;
|
|
315
385
|
try {
|
|
316
386
|
const stdout = result.stdout.trim();
|
|
317
387
|
const reportStart = stdout.lastIndexOf("\n{");
|
|
318
|
-
report = JSON.parse(
|
|
388
|
+
report = JSON.parse(
|
|
389
|
+
reportStart === -1 ? stdout : stdout.slice(reportStart + 1),
|
|
390
|
+
);
|
|
319
391
|
} catch (error) {
|
|
320
392
|
throw new Error("pnpm pack did not return its required JSON report.", {
|
|
321
393
|
cause: error,
|
|
@@ -330,12 +402,26 @@ export const packNpmArtifact = async (
|
|
|
330
402
|
) {
|
|
331
403
|
throw new Error("pnpm pack reported a different package identity.");
|
|
332
404
|
}
|
|
405
|
+
const reportedFiles = (report as { files?: unknown }).files;
|
|
406
|
+
if (!Array.isArray(reportedFiles)) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"pnpm pack did not report the packed file list, so the package contents cannot be verified.",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
assertNoDevelopmentDocumentsInPackage(
|
|
412
|
+
reportedFiles.map((entryArg) =>
|
|
413
|
+
typeof entryArg === "string"
|
|
414
|
+
? entryArg
|
|
415
|
+
: String((entryArg as { path?: unknown })?.path ?? ""),
|
|
416
|
+
),
|
|
417
|
+
);
|
|
333
418
|
await syncFile(artifactPath);
|
|
334
|
-
|
|
419
|
+
const artifact = await calculateNpmArtifact(
|
|
335
420
|
artifactPath,
|
|
336
421
|
identity.packageName,
|
|
337
422
|
identity.version,
|
|
338
423
|
);
|
|
424
|
+
return { ...artifact, file: fileArg };
|
|
339
425
|
};
|
|
340
426
|
|
|
341
427
|
export const verifyStoredNpmArtifact = async (
|
|
@@ -353,7 +439,9 @@ export const verifyStoredNpmArtifact = async (
|
|
|
353
439
|
actual.sha256 !== expectedArtifactArg.sha256 ||
|
|
354
440
|
actual.integrity !== expectedArtifactArg.integrity
|
|
355
441
|
) {
|
|
356
|
-
throw new Error(
|
|
442
|
+
throw new Error(
|
|
443
|
+
"Stored npm artifact no longer matches its release journal identity.",
|
|
444
|
+
);
|
|
357
445
|
}
|
|
358
446
|
};
|
|
359
447
|
|
|
@@ -451,10 +539,15 @@ const readBoundedResponse = async (
|
|
|
451
539
|
}
|
|
452
540
|
reader.releaseLock();
|
|
453
541
|
}
|
|
454
|
-
return Buffer.concat(
|
|
542
|
+
return Buffer.concat(
|
|
543
|
+
chunks.map((chunkArg) => Buffer.from(chunkArg)),
|
|
544
|
+
size,
|
|
545
|
+
);
|
|
455
546
|
};
|
|
456
547
|
|
|
457
|
-
const readBoundedJsonResponse = async (
|
|
548
|
+
const readBoundedJsonResponse = async (
|
|
549
|
+
responseArg: Response,
|
|
550
|
+
): Promise<unknown> =>
|
|
458
551
|
JSON.parse(
|
|
459
552
|
(
|
|
460
553
|
await readBoundedResponse(
|
|
@@ -561,7 +654,9 @@ export const probeAnonymousNpmArtifact = async (
|
|
|
561
654
|
cancelResponseBody(packageResponse);
|
|
562
655
|
return { status: "inconclusive", code: "registry-response" };
|
|
563
656
|
}
|
|
564
|
-
const packageMetadata = (await readBoundedJsonResponse(
|
|
657
|
+
const packageMetadata = (await readBoundedJsonResponse(
|
|
658
|
+
packageResponse,
|
|
659
|
+
)) as {
|
|
565
660
|
["dist-tags"]?: Record<string, unknown>;
|
|
566
661
|
};
|
|
567
662
|
if (packageMetadata["dist-tags"]?.[tagArg] !== artifactArg.version) {
|
|
@@ -582,7 +677,9 @@ export const waitForAnonymousNpmArtifact = async (
|
|
|
582
677
|
const attempts = optionsArg.attempts ?? 10;
|
|
583
678
|
const delayMs = optionsArg.delayMs ?? 1_000;
|
|
584
679
|
if (!Number.isSafeInteger(attempts) || attempts < 1) {
|
|
585
|
-
throw new Error(
|
|
680
|
+
throw new Error(
|
|
681
|
+
"npm verification attempts must be a positive safe integer.",
|
|
682
|
+
);
|
|
586
683
|
}
|
|
587
684
|
let lastResult: INpmArtifactProbeResult = {
|
|
588
685
|
status: "inconclusive",
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
createReleaseDockerPromotions,
|
|
6
6
|
finalizeJournalCompletion,
|
|
7
7
|
getReleaseDockerPromotionContext,
|
|
8
|
+
getReleaseArtifacts,
|
|
9
|
+
getReleaseNpmArtifact,
|
|
8
10
|
type TReleaseJournal,
|
|
9
11
|
type IReleaseJournalV2,
|
|
10
12
|
type IReleaseTargetStatus,
|
|
@@ -71,7 +73,7 @@ export function buildReleaseGitPushArgs(
|
|
|
71
73
|
|
|
72
74
|
type TReleaseTargetSelector =
|
|
73
75
|
| { kind: "git" }
|
|
74
|
-
| { kind: "npm"; registry: string }
|
|
76
|
+
| { kind: "npm"; registry: string; packageName?: string }
|
|
75
77
|
| { kind: "docker-qualification" }
|
|
76
78
|
| { kind: "docker-promotion"; promotionId: string }
|
|
77
79
|
| { kind: "docker-cleanup" };
|
|
@@ -128,7 +130,9 @@ const getTargetStatus = (
|
|
|
128
130
|
return promotion;
|
|
129
131
|
}
|
|
130
132
|
const registry = journalArg.npm.registries.find(
|
|
131
|
-
(registryArg) =>
|
|
133
|
+
(registryArg) =>
|
|
134
|
+
registryArg.registry === selectorArg.registry &&
|
|
135
|
+
registryArg.packageName === selectorArg.packageName,
|
|
132
136
|
);
|
|
133
137
|
if (!registry) {
|
|
134
138
|
throw new Error(
|
|
@@ -486,20 +490,19 @@ const probeNpm = async (
|
|
|
486
490
|
registryArg: string,
|
|
487
491
|
optionsArg: IReleasePublicationOptions,
|
|
488
492
|
waitArg = false,
|
|
493
|
+
packageNameArg?: string,
|
|
489
494
|
): Promise<INpmArtifactProbeResult> => {
|
|
490
|
-
|
|
491
|
-
throw new Error("npm release target has no exact artifact.");
|
|
492
|
-
}
|
|
495
|
+
const artifact = getReleaseNpmArtifact(journalArg, packageNameArg);
|
|
493
496
|
return waitArg
|
|
494
497
|
? waitForAnonymousNpmArtifact(
|
|
495
498
|
registryArg,
|
|
496
|
-
|
|
499
|
+
artifact,
|
|
497
500
|
journalArg.npm.tag,
|
|
498
501
|
optionsArg.npmProbeOptions,
|
|
499
502
|
)
|
|
500
503
|
: probeAnonymousNpmArtifact(
|
|
501
504
|
registryArg,
|
|
502
|
-
|
|
505
|
+
artifact,
|
|
503
506
|
journalArg.npm.tag,
|
|
504
507
|
optionsArg.npmProbeOptions,
|
|
505
508
|
);
|
|
@@ -527,14 +530,22 @@ const executeNpmRegistry = async (
|
|
|
527
530
|
optionsArg: IReleasePublicationOptions,
|
|
528
531
|
journalArg: TReleaseJournal,
|
|
529
532
|
registryArg: string,
|
|
533
|
+
packageNameArg?: string,
|
|
530
534
|
): Promise<TReleaseJournal> => {
|
|
531
535
|
const selector: TReleaseTargetSelector = {
|
|
532
536
|
kind: "npm",
|
|
533
537
|
registry: registryArg,
|
|
538
|
+
packageName: packageNameArg,
|
|
534
539
|
};
|
|
535
540
|
let journal = journalArg;
|
|
536
541
|
let status = getTargetStatus(journal, selector);
|
|
537
|
-
let probe = await probeNpm(
|
|
542
|
+
let probe = await probeNpm(
|
|
543
|
+
journal,
|
|
544
|
+
registryArg,
|
|
545
|
+
optionsArg,
|
|
546
|
+
false,
|
|
547
|
+
packageNameArg,
|
|
548
|
+
);
|
|
538
549
|
|
|
539
550
|
if (status.state === "verified") {
|
|
540
551
|
if (probe.status === "exact") {
|
|
@@ -596,7 +607,13 @@ const executeNpmRegistry = async (
|
|
|
596
607
|
throw new Error(`npm registry target is in conflict: ${registryArg}`);
|
|
597
608
|
}
|
|
598
609
|
|
|
599
|
-
probe = await probeNpm(
|
|
610
|
+
probe = await probeNpm(
|
|
611
|
+
journal,
|
|
612
|
+
registryArg,
|
|
613
|
+
optionsArg,
|
|
614
|
+
false,
|
|
615
|
+
packageNameArg,
|
|
616
|
+
);
|
|
600
617
|
if (probe.status === "exact") {
|
|
601
618
|
if (status.attempts === 0 && journal.npm.alreadyPublished === "error") {
|
|
602
619
|
await markUnclaimedTarget(
|
|
@@ -629,13 +646,12 @@ const executeNpmRegistry = async (
|
|
|
629
646
|
throw new Error(`npm registry cannot be verified safely: ${registryArg}`);
|
|
630
647
|
}
|
|
631
648
|
|
|
649
|
+
const artifact = getReleaseNpmArtifact(journal, packageNameArg);
|
|
632
650
|
const artifactPath = optionsArg.store.getArtifactPath(
|
|
633
651
|
journal.release.version,
|
|
652
|
+
artifact.file,
|
|
634
653
|
);
|
|
635
|
-
|
|
636
|
-
throw new Error("npm release journal has no artifact.");
|
|
637
|
-
}
|
|
638
|
-
await verifyStoredNpmArtifact(artifactPath, journal.artifact);
|
|
654
|
+
await verifyStoredNpmArtifact(artifactPath, artifact);
|
|
639
655
|
journal = await claimTarget(optionsArg.store, journal, selector);
|
|
640
656
|
const publishResult = await publishNpmArtifact(
|
|
641
657
|
optionsArg.smartshell,
|
|
@@ -643,7 +659,13 @@ const executeNpmRegistry = async (
|
|
|
643
659
|
artifactPath,
|
|
644
660
|
registryArg,
|
|
645
661
|
);
|
|
646
|
-
probe = await probeNpm(
|
|
662
|
+
probe = await probeNpm(
|
|
663
|
+
journal,
|
|
664
|
+
registryArg,
|
|
665
|
+
optionsArg,
|
|
666
|
+
true,
|
|
667
|
+
packageNameArg,
|
|
668
|
+
);
|
|
647
669
|
if (probe.status === "exact") {
|
|
648
670
|
return finishPublishingTarget(
|
|
649
671
|
optionsArg.store,
|
|
@@ -1346,6 +1368,13 @@ export const executeReleasePublication = async (
|
|
|
1346
1368
|
optionsArg: IReleasePublicationOptions,
|
|
1347
1369
|
): Promise<TReleaseJournal> => {
|
|
1348
1370
|
let journal = optionsArg.journal;
|
|
1371
|
+
// Validate the whole stored set before Git or any registry can change.
|
|
1372
|
+
for (const artifact of getReleaseArtifacts(journal)) {
|
|
1373
|
+
await verifyStoredNpmArtifact(
|
|
1374
|
+
optionsArg.store.getArtifactPath(journal.release.version, artifact.file),
|
|
1375
|
+
artifact,
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1349
1378
|
if (journal.schemaVersion === 2) {
|
|
1350
1379
|
journal = await executeDockerQualification(optionsArg, journal);
|
|
1351
1380
|
if (
|
|
@@ -1362,7 +1391,12 @@ export const executeReleasePublication = async (
|
|
|
1362
1391
|
: journal.git.expectedRemoteMainOid || undefined;
|
|
1363
1392
|
for (const registry of journal.npm.registries) {
|
|
1364
1393
|
await optionsArg.ensurePublicationState(expectedRemoteMainOid);
|
|
1365
|
-
journal = await executeNpmRegistry(
|
|
1394
|
+
journal = await executeNpmRegistry(
|
|
1395
|
+
optionsArg,
|
|
1396
|
+
journal,
|
|
1397
|
+
registry.registry,
|
|
1398
|
+
registry.packageName,
|
|
1399
|
+
);
|
|
1366
1400
|
}
|
|
1367
1401
|
if (
|
|
1368
1402
|
journal.schemaVersion === 2 &&
|
package/ts/mod_release/index.ts
CHANGED
|
@@ -34,6 +34,9 @@ import {
|
|
|
34
34
|
normalizeReleaseGitRemoteName,
|
|
35
35
|
normalizeReleaseVersion,
|
|
36
36
|
resolveGitCommonDirectory,
|
|
37
|
+
getReleaseArtifacts,
|
|
38
|
+
releasePackageArtifactFile,
|
|
39
|
+
type IReleasePackage,
|
|
37
40
|
type IReleaseArtifact,
|
|
38
41
|
type TReleaseJournal,
|
|
39
42
|
type IReleaseJournalV1,
|
|
@@ -115,6 +118,15 @@ const runInternal = async (argvArg: any): Promise<void> => {
|
|
|
115
118
|
planMode: workflow.confirmation === "plan",
|
|
116
119
|
});
|
|
117
120
|
printReleasePlan(workflow, branchContext);
|
|
121
|
+
if (
|
|
122
|
+
workflow.targets.includes("npm") &&
|
|
123
|
+
workflow.npmPackageSource === "tspublish"
|
|
124
|
+
) {
|
|
125
|
+
const packagePlan = await planReleasePackages(branchContext.sourceCwd);
|
|
126
|
+
console.log("npm packages (dependency order):");
|
|
127
|
+
for (const module of packagePlan.modules)
|
|
128
|
+
console.log(` ${module.name} (${module.folder})`);
|
|
129
|
+
}
|
|
118
130
|
if (workflow.confirmation === "plan") {
|
|
119
131
|
return;
|
|
120
132
|
}
|
|
@@ -346,6 +358,14 @@ const assertReleaseConfiguration = (
|
|
|
346
358
|
if (!workflowArg.targets.includes("npm")) {
|
|
347
359
|
return;
|
|
348
360
|
}
|
|
361
|
+
if (
|
|
362
|
+
workflowArg.npmPackageSource === "tspublish" &&
|
|
363
|
+
workflowArg.targets.includes("docker")
|
|
364
|
+
) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
"tspublish package releases support Git and npm targets; use a separate Docker release repository.",
|
|
367
|
+
);
|
|
368
|
+
}
|
|
349
369
|
if (workflowArg.npmRegistries.length === 0) {
|
|
350
370
|
throw new Error("The npm release target requires at least one registry.");
|
|
351
371
|
}
|
|
@@ -410,7 +430,21 @@ const createDockerQualificationRequest = (
|
|
|
410
430
|
version: versionArg,
|
|
411
431
|
});
|
|
412
432
|
|
|
413
|
-
const
|
|
433
|
+
export const planReleasePackages = async (
|
|
434
|
+
cwdArg: string,
|
|
435
|
+
): Promise<plugins.tspublish.ITsPublishPlan> => {
|
|
436
|
+
const plan = await new plugins.tspublish.TsPublish().plan(cwdArg);
|
|
437
|
+
for (const module of plan.modules) {
|
|
438
|
+
if (module.registries.length !== 1 || module.registries[0] !== "useBase") {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`${module.name}: GitZone-managed packages must declare registries: ["useBase"]. Configure destinations in release.targets.npm.`,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return plan;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
export const createReleaseJournal = async (
|
|
414
448
|
optionsArg: ICreateReleaseJournalOptions,
|
|
415
449
|
): Promise<{ store: ReleaseJournalStore; journal: TReleaseJournal }> => {
|
|
416
450
|
const gitCommonDirectory = await resolveGitCommonDirectory(
|
|
@@ -437,13 +471,49 @@ const createReleaseJournal = async (
|
|
|
437
471
|
);
|
|
438
472
|
try {
|
|
439
473
|
let artifact: IReleaseArtifact | null = null;
|
|
474
|
+
const packages: IReleasePackage[] = [];
|
|
440
475
|
if (optionsArg.workflow.targets.includes("npm")) {
|
|
441
|
-
|
|
442
|
-
optionsArg.
|
|
443
|
-
optionsArg.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
476
|
+
if (optionsArg.workflow.npmPackageSource === "tspublish") {
|
|
477
|
+
const plan = await planReleasePackages(optionsArg.releaseCwd);
|
|
478
|
+
if (plan.version !== optionsArg.version)
|
|
479
|
+
throw new Error("tspublish plan version differs from the release.");
|
|
480
|
+
const outputDirectory = plugins.path.join(
|
|
481
|
+
temporaryDirectory,
|
|
482
|
+
"prepared",
|
|
483
|
+
);
|
|
484
|
+
const prepared = await new plugins.tspublish.TsPublish().prepare(
|
|
485
|
+
optionsArg.releaseCwd,
|
|
486
|
+
outputDirectory,
|
|
487
|
+
);
|
|
488
|
+
if (
|
|
489
|
+
JSON.stringify(prepared.map(({ directory, ...module }) => module)) !==
|
|
490
|
+
JSON.stringify(plan.modules)
|
|
491
|
+
) {
|
|
492
|
+
throw new Error("tspublish package plan changed during preparation.");
|
|
493
|
+
}
|
|
494
|
+
for (const module of prepared) {
|
|
495
|
+
const packed = await packNpmArtifact(
|
|
496
|
+
optionsArg.smartshell,
|
|
497
|
+
module.directory,
|
|
498
|
+
temporaryDirectory,
|
|
499
|
+
optionsArg.version,
|
|
500
|
+
releasePackageArtifactFile(module.name),
|
|
501
|
+
);
|
|
502
|
+
if (packed.packageName !== module.name)
|
|
503
|
+
throw new Error(
|
|
504
|
+
"Prepared package identity changed during packing.",
|
|
505
|
+
);
|
|
506
|
+
packages.push({ folder: module.folder, artifact: packed });
|
|
507
|
+
}
|
|
508
|
+
await plugins.fs.rm(outputDirectory, { recursive: true, force: true });
|
|
509
|
+
} else {
|
|
510
|
+
artifact = await packNpmArtifact(
|
|
511
|
+
optionsArg.smartshell,
|
|
512
|
+
optionsArg.releaseCwd,
|
|
513
|
+
temporaryDirectory,
|
|
514
|
+
optionsArg.version,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
447
517
|
await verifyCleanTree(
|
|
448
518
|
optionsArg.smartshell,
|
|
449
519
|
optionsArg.releaseCwd,
|
|
@@ -464,7 +534,7 @@ const createReleaseJournal = async (
|
|
|
464
534
|
const journal = finalizeJournalCompletion(
|
|
465
535
|
{
|
|
466
536
|
kind: "gitzone-release-journal",
|
|
467
|
-
schemaVersion: optionsArg.dockerRequest ? 2 : 1,
|
|
537
|
+
schemaVersion: packages.length ? 3 : optionsArg.dockerRequest ? 2 : 1,
|
|
468
538
|
revision: 1,
|
|
469
539
|
release: {
|
|
470
540
|
version: optionsArg.version,
|
|
@@ -473,6 +543,7 @@ const createReleaseJournal = async (
|
|
|
473
543
|
tagOid: optionsArg.refs.tagOid,
|
|
474
544
|
},
|
|
475
545
|
artifact,
|
|
546
|
+
...(packages.length ? { packages } : {}),
|
|
476
547
|
git: {
|
|
477
548
|
state: gitStatus.state,
|
|
478
549
|
attempts: gitStatus.attempts,
|
|
@@ -492,18 +563,26 @@ const createReleaseJournal = async (
|
|
|
492
563
|
alreadyPublished: optionsArg.workflow.targets.includes("npm")
|
|
493
564
|
? optionsArg.workflow.npmAlreadyPublished
|
|
494
565
|
: "success",
|
|
495
|
-
registries:
|
|
496
|
-
?
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
566
|
+
registries: packages.length
|
|
567
|
+
? packages.flatMap((entry) =>
|
|
568
|
+
optionsArg.workflow.npmRegistries.map((registry) => ({
|
|
569
|
+
...createInitialTargetStatus(true),
|
|
570
|
+
registry,
|
|
571
|
+
packageName: entry.artifact.packageName,
|
|
572
|
+
})),
|
|
573
|
+
)
|
|
574
|
+
: optionsArg.workflow.targets.includes("npm")
|
|
575
|
+
? optionsArg.workflow.npmRegistries.map((registryArg) => {
|
|
576
|
+
const status = createInitialTargetStatus(true);
|
|
577
|
+
return {
|
|
578
|
+
state: status.state,
|
|
579
|
+
attempts: status.attempts,
|
|
580
|
+
attempt: status.attempt,
|
|
581
|
+
error: status.error,
|
|
582
|
+
registry: registryArg,
|
|
583
|
+
};
|
|
584
|
+
})
|
|
585
|
+
: [],
|
|
507
586
|
},
|
|
508
587
|
...(optionsArg.dockerRequest
|
|
509
588
|
? {
|
|
@@ -675,13 +754,15 @@ const runReleaseResume = async (
|
|
|
675
754
|
await tsdocker.assertCapabilities();
|
|
676
755
|
await tsdocker.validate(journal.docker.request);
|
|
677
756
|
}
|
|
678
|
-
if (journal.
|
|
757
|
+
if (getReleaseArtifacts(journal).length) {
|
|
679
758
|
await assertPnpmReleaseCapability(smartshellInstance, paths.cwd);
|
|
680
759
|
await assertNoLegacyNpmPublisher(paths.cwd);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
760
|
+
for (const artifact of getReleaseArtifacts(journal)) {
|
|
761
|
+
await verifyStoredNpmArtifact(
|
|
762
|
+
store.getArtifactPath(version, artifact.file),
|
|
763
|
+
artifact,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
685
766
|
}
|
|
686
767
|
|
|
687
768
|
const branchContext = await inspectReleaseBranch({
|
|
@@ -773,17 +854,24 @@ const assertResumeConfiguration = async (
|
|
|
773
854
|
if (
|
|
774
855
|
journalArg.npm.registries.length > 0 &&
|
|
775
856
|
(JSON.stringify(currentRegistries) !==
|
|
776
|
-
JSON.stringify(
|
|
777
|
-
|
|
778
|
-
|
|
857
|
+
JSON.stringify([
|
|
858
|
+
...new Set(
|
|
859
|
+
journalArg.npm.registries.map((registryArg) => registryArg.registry),
|
|
860
|
+
),
|
|
861
|
+
]) ||
|
|
779
862
|
workflowArg.npmAccessLevel !== journalArg.npm.access ||
|
|
863
|
+
workflowArg.npmPackageSource !==
|
|
864
|
+
(journalArg.schemaVersion === 3 ? "tspublish" : "root") ||
|
|
780
865
|
workflowArg.npmAlreadyPublished !== journalArg.npm.alreadyPublished)
|
|
781
866
|
) {
|
|
782
867
|
throw new Error(
|
|
783
868
|
"Current npm release configuration does not match the journal.",
|
|
784
869
|
);
|
|
785
870
|
}
|
|
786
|
-
if (
|
|
871
|
+
if (
|
|
872
|
+
journalArg.git.state !== "skipped" ||
|
|
873
|
+
getReleaseArtifacts(journalArg).length
|
|
874
|
+
) {
|
|
787
875
|
await assertNoLegacyNpmPublisher(cwdArg);
|
|
788
876
|
}
|
|
789
877
|
};
|
|
@@ -797,7 +885,15 @@ const printReleaseJournalSummary = (journalArg: TReleaseJournal): void => {
|
|
|
797
885
|
);
|
|
798
886
|
console.log(`git: ${journalArg.git.state}`);
|
|
799
887
|
for (const registry of journalArg.npm.registries) {
|
|
800
|
-
console.log(
|
|
888
|
+
console.log(
|
|
889
|
+
`npm ${registry.packageName ? `${registry.packageName} ` : ""}${registry.registry}: ${registry.state}`,
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
if (journalArg.schemaVersion === 3) {
|
|
893
|
+
for (const { artifact } of journalArg.packages)
|
|
894
|
+
console.log(
|
|
895
|
+
`artifact ${artifact.packageName}: ${artifact.integrity} (${artifact.sha256})`,
|
|
896
|
+
);
|
|
801
897
|
}
|
|
802
898
|
if (journalArg.schemaVersion === 2) {
|
|
803
899
|
console.log(
|
|
@@ -1143,6 +1239,7 @@ function printReleasePlan(
|
|
|
1143
1239
|
`changelog: ${workflow.changelogFile}#${workflow.changelogPendingSection}`,
|
|
1144
1240
|
);
|
|
1145
1241
|
if (workflow.targets.includes("npm")) {
|
|
1242
|
+
console.log(`npm package source: ${workflow.npmPackageSource}`);
|
|
1146
1243
|
console.log(
|
|
1147
1244
|
`npm registries: ${workflow.npmRegistries.length > 0 ? workflow.npmRegistries.join(", ") : "none"}`,
|
|
1148
1245
|
);
|
package/ts/plugins.ts
CHANGED
|
@@ -20,6 +20,7 @@ import * as smartdelay from '@push.rocks/smartdelay';
|
|
|
20
20
|
import * as smartbucket from '@push.rocks/smartbucket';
|
|
21
21
|
import * as s3 from '@aws-sdk/client-s3';
|
|
22
22
|
import * as tsdockerReleaseProtocol from '@git.zone/tsdocker/dist_ts/classes.releaseprotocol.js';
|
|
23
|
+
import * as tspublish from '@git.zone/tspublish';
|
|
23
24
|
|
|
24
25
|
export type {
|
|
25
26
|
IReleaseCapabilitiesResult,
|
|
@@ -60,4 +61,5 @@ export {
|
|
|
60
61
|
smartbucket,
|
|
61
62
|
s3,
|
|
62
63
|
tsdockerReleaseProtocol,
|
|
64
|
+
tspublish,
|
|
63
65
|
};
|