@git.zone/cli 6.7.3 → 6.9.0
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/mod_release/helpers.npmartifact.d.ts +8 -4
- package/dist_ts/mod_release/helpers.npmartifact.js +51 -9
- package/dist_ts/mod_release/helpers.releasepreparation.d.ts +21 -0
- package/dist_ts/mod_release/helpers.releasepreparation.js +196 -0
- package/dist_ts/mod_release/helpers.releasepublication.d.ts +2 -1
- package/dist_ts/mod_release/helpers.releasepublication.js +5 -3
- package/dist_ts/mod_release/index.d.ts +4 -0
- package/dist_ts/mod_release/index.js +133 -11
- package/package.json +4 -4
- package/readme.md +57 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/mod_release/helpers.npmartifact.ts +88 -12
- package/ts/mod_release/helpers.releasepreparation.ts +308 -0
- package/ts/mod_release/helpers.releasepublication.ts +6 -0
- package/ts/mod_release/index.ts +191 -10
|
@@ -9,6 +9,7 @@ const supportedPnpmVersions = [
|
|
|
9
9
|
"11.25.0",
|
|
10
10
|
"12.1.0",
|
|
11
11
|
"12.2.1",
|
|
12
|
+
"12.3.0",
|
|
12
13
|
"12.3.4",
|
|
13
14
|
] as const;
|
|
14
15
|
const pnpmHelpRequirements = {
|
|
@@ -49,6 +50,19 @@ const pnpmHelpRequirements = {
|
|
|
49
50
|
"[possible values: public, restricted]",
|
|
50
51
|
],
|
|
51
52
|
},
|
|
53
|
+
"12.3.0": {
|
|
54
|
+
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
55
|
+
publish: [
|
|
56
|
+
"Usage: pnpm publish [OPTIONS] [PACKAGE]",
|
|
57
|
+
"Tarball or directory to publish",
|
|
58
|
+
"--no-git-checks",
|
|
59
|
+
"--ignore-scripts",
|
|
60
|
+
"--json",
|
|
61
|
+
"--tag <TAG>",
|
|
62
|
+
"--access <ACCESS>",
|
|
63
|
+
"[possible values: public, restricted]",
|
|
64
|
+
],
|
|
65
|
+
},
|
|
52
66
|
"12.3.4": {
|
|
53
67
|
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
54
68
|
publish: [
|
|
@@ -70,10 +84,31 @@ const publishCommandTimeoutMs = 5 * 60_000;
|
|
|
70
84
|
const packageNameRegex =
|
|
71
85
|
/^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/;
|
|
72
86
|
|
|
87
|
+
export function assertNpmPackageName(value: unknown): asserts value is string {
|
|
88
|
+
if (typeof value !== "string" || !packageNameRegex.test(value)) {
|
|
89
|
+
throw new Error("Invalid release package name.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
export interface IPnpmReleaseCapability {
|
|
74
|
-
version: (typeof supportedPnpmVersions)[number];
|
|
94
|
+
readonly version: (typeof supportedPnpmVersions)[number];
|
|
75
95
|
}
|
|
76
96
|
|
|
97
|
+
export const buildQualifiedPnpmArgs = (
|
|
98
|
+
capabilityArg: IPnpmReleaseCapability | undefined,
|
|
99
|
+
argsArg: readonly string[],
|
|
100
|
+
): string[] => {
|
|
101
|
+
if (
|
|
102
|
+
!capabilityArg ||
|
|
103
|
+
!supportedPnpmVersions.includes(capabilityArg.version)
|
|
104
|
+
) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"npm release commands require a qualified pnpm invocation.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return ["with", capabilityArg.version, ...argsArg];
|
|
110
|
+
};
|
|
111
|
+
|
|
77
112
|
export type TNpmArtifactProbeStatus =
|
|
78
113
|
"absent" | "exact" | "conflict" | "inconclusive";
|
|
79
114
|
|
|
@@ -163,21 +198,28 @@ export const assertPnpmReleaseCapability = async (
|
|
|
163
198
|
);
|
|
164
199
|
}
|
|
165
200
|
const requirements = pnpmHelpRequirements[supportedVersion];
|
|
201
|
+
const capability = Object.freeze({ version: supportedVersion });
|
|
166
202
|
|
|
167
|
-
const packHelp = await smartshellArg.execSpawn(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
const packHelp = await smartshellArg.execSpawn(
|
|
204
|
+
"pnpm",
|
|
205
|
+
buildQualifiedPnpmArgs(capability, ["pack", "--help"]),
|
|
206
|
+
{
|
|
207
|
+
cwd: cwdArg,
|
|
208
|
+
silent: true,
|
|
209
|
+
timeout: capabilityCommandTimeoutMs,
|
|
210
|
+
timeoutKillGraceMs: 5_000,
|
|
211
|
+
},
|
|
212
|
+
);
|
|
173
213
|
if (packHelp.exitCode !== 0) {
|
|
174
|
-
throw new Error(
|
|
214
|
+
throw new Error(
|
|
215
|
+
"Unable to inspect the selected pnpm pack capabilities. Exact release commands require standalone pnpm with support; Corepack launchers are unsupported.",
|
|
216
|
+
);
|
|
175
217
|
}
|
|
176
218
|
requireHelpFragments(packHelp.combinedOutput, requirements.pack, "pack");
|
|
177
219
|
|
|
178
220
|
const publishHelp = await smartshellArg.execSpawn(
|
|
179
221
|
"pnpm",
|
|
180
|
-
["publish", "--help"],
|
|
222
|
+
buildQualifiedPnpmArgs(capability, ["publish", "--help"]),
|
|
181
223
|
{
|
|
182
224
|
cwd: cwdArg,
|
|
183
225
|
silent: true,
|
|
@@ -193,7 +235,7 @@ export const assertPnpmReleaseCapability = async (
|
|
|
193
235
|
requirements.publish,
|
|
194
236
|
"publish",
|
|
195
237
|
);
|
|
196
|
-
return
|
|
238
|
+
return capability;
|
|
197
239
|
};
|
|
198
240
|
|
|
199
241
|
/**
|
|
@@ -352,6 +394,7 @@ export const packNpmArtifact = async (
|
|
|
352
394
|
cwdArg: string,
|
|
353
395
|
destinationDirectoryArg: string,
|
|
354
396
|
expectedVersionArg: string,
|
|
397
|
+
capabilityArg: IPnpmReleaseCapability | undefined,
|
|
355
398
|
fileArg: string = releaseArtifactFileName,
|
|
356
399
|
): Promise<IReleaseArtifact> => {
|
|
357
400
|
if (
|
|
@@ -369,7 +412,12 @@ export const packNpmArtifact = async (
|
|
|
369
412
|
const artifactPath = plugins.path.join(destinationDirectoryArg, fileArg);
|
|
370
413
|
const result = await smartshellArg.execSpawn(
|
|
371
414
|
"pnpm",
|
|
372
|
-
|
|
415
|
+
buildQualifiedPnpmArgs(capabilityArg, [
|
|
416
|
+
"pack",
|
|
417
|
+
"--out",
|
|
418
|
+
artifactPath,
|
|
419
|
+
"--json",
|
|
420
|
+
]),
|
|
373
421
|
{
|
|
374
422
|
cwd: cwdArg,
|
|
375
423
|
timeout: packCommandTimeoutMs,
|
|
@@ -464,10 +512,14 @@ export const publishNpmArtifact = async (
|
|
|
464
512
|
cwdArg: string,
|
|
465
513
|
artifactPathArg: string,
|
|
466
514
|
registryArg: string,
|
|
515
|
+
capabilityArg: IPnpmReleaseCapability | undefined,
|
|
467
516
|
): Promise<{ exitCode: number; output: string }> => {
|
|
468
517
|
const result = await smartshellArg.execSpawn(
|
|
469
518
|
"pnpm",
|
|
470
|
-
|
|
519
|
+
buildQualifiedPnpmArgs(
|
|
520
|
+
capabilityArg,
|
|
521
|
+
buildPnpmPublishArgs(artifactPathArg, registryArg),
|
|
522
|
+
),
|
|
471
523
|
{
|
|
472
524
|
cwd: cwdArg,
|
|
473
525
|
timeout: publishCommandTimeoutMs,
|
|
@@ -500,6 +552,30 @@ const cancelResponseBody = (responseArg: Response): void => {
|
|
|
500
552
|
}
|
|
501
553
|
};
|
|
502
554
|
|
|
555
|
+
/** Recovery without an original artifact must never adopt a published version. */
|
|
556
|
+
export const assertNpmVersionAbsent = async (
|
|
557
|
+
registryArg: string,
|
|
558
|
+
packageNameArg: string,
|
|
559
|
+
versionArg: string,
|
|
560
|
+
optionsArg: INpmArtifactProbeOptions = {},
|
|
561
|
+
): Promise<void> => {
|
|
562
|
+
const registry = normalizeNpmRegistryUrl(registryArg);
|
|
563
|
+
assertNpmPackageName(packageNameArg);
|
|
564
|
+
const response = await fetchWithoutRedirects(
|
|
565
|
+
new URL(
|
|
566
|
+
`${registry}/${encodeURIComponent(packageNameArg)}/${encodeURIComponent(versionArg)}`,
|
|
567
|
+
),
|
|
568
|
+
"application/json",
|
|
569
|
+
optionsArg,
|
|
570
|
+
);
|
|
571
|
+
cancelResponseBody(response);
|
|
572
|
+
if (response.status !== 404) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`Cannot prove ${packageNameArg}@${versionArg} is unpublished at ${registry} (HTTP ${response.status}).`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
503
579
|
const readBoundedResponse = async (
|
|
504
580
|
responseArg: Response,
|
|
505
581
|
maximumSizeArg: number,
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import * as plugins from "./mod.plugins.js";
|
|
2
|
+
import type { IResolvedReleaseWorkflow } from "../helpers.workflow.js";
|
|
3
|
+
import { readPendingChangelog } from "../helpers.changelog.js";
|
|
4
|
+
import { InterProcessLock } from "../mod_services/classes.interprocesslock.js";
|
|
5
|
+
import { ReleaseJournalStore } from "./classes.releasejournal.js";
|
|
6
|
+
import {
|
|
7
|
+
releaseGitEnv,
|
|
8
|
+
type IReleaseBranchContext,
|
|
9
|
+
} from "./helpers.releasebranch.js";
|
|
10
|
+
import {
|
|
11
|
+
assertNpmVersionAbsent,
|
|
12
|
+
assertNpmPackageName,
|
|
13
|
+
hashReleaseDestination,
|
|
14
|
+
} from "./helpers.npmartifact.js";
|
|
15
|
+
import { canonicalReleaseJson } from "./helpers.tsdockerprotocol.js";
|
|
16
|
+
|
|
17
|
+
interface IRecoveryRefs {
|
|
18
|
+
mainOid: string;
|
|
19
|
+
tagOid: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IReleasePreparationRecovery {
|
|
23
|
+
smartshell: plugins.smartshell.Smartshell;
|
|
24
|
+
workflow: IResolvedReleaseWorkflow;
|
|
25
|
+
branch: IReleaseBranchContext;
|
|
26
|
+
refs: IRecoveryRefs;
|
|
27
|
+
version: string;
|
|
28
|
+
store: ReleaseJournalStore;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const git = async (
|
|
32
|
+
options: IReleasePreparationRecovery,
|
|
33
|
+
args: string[],
|
|
34
|
+
cwd = options.branch.sourceCwd,
|
|
35
|
+
) => {
|
|
36
|
+
const result = await options.smartshell.execSpawn("git", args, {
|
|
37
|
+
cwd,
|
|
38
|
+
env: releaseGitEnv,
|
|
39
|
+
silent: true,
|
|
40
|
+
timeout: 60_000,
|
|
41
|
+
timeoutKillGraceMs: 5_000,
|
|
42
|
+
});
|
|
43
|
+
if (result.exitCode !== 0)
|
|
44
|
+
throw new Error(`Release recovery Git validation failed: ${args[0]}.`);
|
|
45
|
+
return result.stdout.trim();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const assertPackageReleaseRecovery = async (
|
|
49
|
+
options: IReleasePreparationRecovery,
|
|
50
|
+
): Promise<string> => {
|
|
51
|
+
const { workflow, branch, version } = options;
|
|
52
|
+
if (
|
|
53
|
+
workflow.targets.length !== 2 ||
|
|
54
|
+
!workflow.targets.includes("git") ||
|
|
55
|
+
!workflow.targets.includes("npm") ||
|
|
56
|
+
workflow.npmPackageSource !== "root" ||
|
|
57
|
+
!workflow.pushBranch ||
|
|
58
|
+
!workflow.pushTags ||
|
|
59
|
+
branch.sourceBranch !== "main" ||
|
|
60
|
+
!branch.pushUrl ||
|
|
61
|
+
!branch.remoteMainOid
|
|
62
|
+
) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Release recover requires configured Git and root npm targets from clean main.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const manifest = JSON.parse(
|
|
68
|
+
await plugins.fs.readFile(
|
|
69
|
+
plugins.path.join(branch.sourceCwd, "package.json"),
|
|
70
|
+
"utf8",
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
if (manifest.version !== version || typeof manifest.name !== "string") {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Recovery package identity does not match the existing release version.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
assertNpmPackageName(manifest.name);
|
|
79
|
+
const pending = await readPendingChangelog(
|
|
80
|
+
plugins.path.join(branch.sourceCwd, workflow.changelogFile),
|
|
81
|
+
workflow.changelogPendingSection,
|
|
82
|
+
);
|
|
83
|
+
const changelog = await plugins.fs.readFile(
|
|
84
|
+
plugins.path.join(branch.sourceCwd, workflow.changelogFile),
|
|
85
|
+
"utf8",
|
|
86
|
+
);
|
|
87
|
+
const headings = changelog
|
|
88
|
+
.split(/\r?\n/)
|
|
89
|
+
.filter(
|
|
90
|
+
(line) =>
|
|
91
|
+
/^##\s/.test(line) && line !== `## ${workflow.changelogPendingSection}`,
|
|
92
|
+
);
|
|
93
|
+
if (
|
|
94
|
+
!pending.isEmpty ||
|
|
95
|
+
!new RegExp(
|
|
96
|
+
`^## \\d{4}-\\d{2}-\\d{2} - ${version.replace(/\./g, "\\.")}\\s*$`,
|
|
97
|
+
).test(headings[0] ?? "")
|
|
98
|
+
) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Recovery requires the completed changelog section for this version and no Pending changes.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
(await git(options, [
|
|
105
|
+
"show",
|
|
106
|
+
"-s",
|
|
107
|
+
"--format=%s",
|
|
108
|
+
options.refs.mainOid,
|
|
109
|
+
])) !== `v${version}` ||
|
|
110
|
+
(
|
|
111
|
+
await git(options, [
|
|
112
|
+
"rev-list",
|
|
113
|
+
"--parents",
|
|
114
|
+
"-n",
|
|
115
|
+
"1",
|
|
116
|
+
options.refs.mainOid,
|
|
117
|
+
])
|
|
118
|
+
).split(" ").length !== 2
|
|
119
|
+
) {
|
|
120
|
+
throw new Error("Recovery requires an existing non-merge release commit.");
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
await git(options, [
|
|
124
|
+
"diff",
|
|
125
|
+
`${options.refs.mainOid}^`,
|
|
126
|
+
options.refs.mainOid,
|
|
127
|
+
"--",
|
|
128
|
+
".smartconfig.json",
|
|
129
|
+
])
|
|
130
|
+
) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Release configuration changed inside the release metadata commit.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return manifest.name;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const assertRecoveryUnpublished = async (
|
|
139
|
+
options: IReleasePreparationRecovery,
|
|
140
|
+
packageName: string,
|
|
141
|
+
): Promise<void> => {
|
|
142
|
+
const output = await git(options, [
|
|
143
|
+
"ls-remote",
|
|
144
|
+
options.branch.pushUrl!,
|
|
145
|
+
`refs/tags/v${options.version}`,
|
|
146
|
+
`refs/tags/v${options.version}^{}`,
|
|
147
|
+
]);
|
|
148
|
+
if (output)
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Recovery cannot adopt an existing remote release tag. Use its original journal.",
|
|
151
|
+
);
|
|
152
|
+
for (const registry of options.workflow.npmRegistries)
|
|
153
|
+
await assertNpmVersionAbsent(registry, packageName, options.version);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const preparationBinding = (options: IReleasePreparationRecovery) => ({
|
|
157
|
+
kind: "gitzone-package-release-preparation",
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
version: options.version,
|
|
160
|
+
...options.refs,
|
|
161
|
+
destinationHash: hashReleaseDestination(options.branch.pushUrl!),
|
|
162
|
+
expectedRemoteMainOid: options.branch.remoteMainOid,
|
|
163
|
+
// Capture resolved configuration, including build/test commands, so retries
|
|
164
|
+
// cannot silently prepare a different release.
|
|
165
|
+
workflow: JSON.parse(
|
|
166
|
+
JSON.stringify({ ...options.workflow, confirmation: "auto" }),
|
|
167
|
+
),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const bindPreparation = async (
|
|
171
|
+
path: string,
|
|
172
|
+
expected: string,
|
|
173
|
+
): Promise<void> => {
|
|
174
|
+
try {
|
|
175
|
+
const stat = await plugins.fs.lstat(path);
|
|
176
|
+
if (
|
|
177
|
+
!stat.isFile() ||
|
|
178
|
+
stat.isSymbolicLink() ||
|
|
179
|
+
stat.size > 128 * 1024 ||
|
|
180
|
+
(await plugins.fs.readFile(path, "utf8")) !== expected
|
|
181
|
+
) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
"Release recovery identity or configuration differs from its original preparation.",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
188
|
+
const handle = await plugins.fs.open(path, "wx", 0o600);
|
|
189
|
+
try {
|
|
190
|
+
await handle.writeFile(expected, "utf8");
|
|
191
|
+
await handle.sync();
|
|
192
|
+
} finally {
|
|
193
|
+
await handle.close();
|
|
194
|
+
}
|
|
195
|
+
const directory = await plugins.fs.open(plugins.path.dirname(path), "r");
|
|
196
|
+
try {
|
|
197
|
+
await directory.sync();
|
|
198
|
+
} finally {
|
|
199
|
+
await directory.close();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Build and seal one unpublished, already tagged package without changing refs. */
|
|
205
|
+
export const withReleasePreparationRecovery = async <T>(
|
|
206
|
+
options: IReleasePreparationRecovery,
|
|
207
|
+
ensureState: () => Promise<void>,
|
|
208
|
+
prepare: (
|
|
209
|
+
checkout: string,
|
|
210
|
+
ensureCheckout: () => Promise<void>,
|
|
211
|
+
) => Promise<T>,
|
|
212
|
+
): Promise<T> => {
|
|
213
|
+
if (!options.branch.planAncestryVerified) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
"Release recovery requires verified remote ancestry before preparation.",
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const directory = options.store.getReleaseDirectory(options.version);
|
|
219
|
+
return new InterProcessLock({
|
|
220
|
+
lockPath: `${directory}.prepare.lock`,
|
|
221
|
+
description: `Release v${options.version} preparation`,
|
|
222
|
+
}).runExclusive(async () => {
|
|
223
|
+
await ensureState();
|
|
224
|
+
try {
|
|
225
|
+
await plugins.fs.lstat(directory);
|
|
226
|
+
throw new Error(
|
|
227
|
+
"Release journal already exists. Use release resume; do not rebuild its artifact.",
|
|
228
|
+
);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
231
|
+
}
|
|
232
|
+
await bindPreparation(
|
|
233
|
+
`${directory}.preparation.json`,
|
|
234
|
+
`${canonicalReleaseJson(preparationBinding(options))}\n`,
|
|
235
|
+
);
|
|
236
|
+
const root = await plugins.fs.mkdtemp(
|
|
237
|
+
plugins.path.join(plugins.os.tmpdir(), "gitzone-release-prepare-"),
|
|
238
|
+
);
|
|
239
|
+
const checkout = plugins.path.join(root, "source");
|
|
240
|
+
let added = false;
|
|
241
|
+
try {
|
|
242
|
+
await git(options, [
|
|
243
|
+
"worktree",
|
|
244
|
+
"add",
|
|
245
|
+
"--detach",
|
|
246
|
+
checkout,
|
|
247
|
+
options.refs.mainOid,
|
|
248
|
+
]);
|
|
249
|
+
added = true;
|
|
250
|
+
const install = await options.smartshell.execSpawn(
|
|
251
|
+
"pnpm",
|
|
252
|
+
["install", "--frozen-lockfile"],
|
|
253
|
+
{
|
|
254
|
+
cwd: checkout,
|
|
255
|
+
timeout: 10 * 60_000,
|
|
256
|
+
timeoutKillGraceMs: 5_000,
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
if (install.exitCode !== 0)
|
|
260
|
+
throw new Error("Release recovery dependency installation failed.");
|
|
261
|
+
if (options.workflow.runTests)
|
|
262
|
+
await run(options, checkout, options.workflow.testCommand, "tests");
|
|
263
|
+
if (options.workflow.runBuild)
|
|
264
|
+
await run(options, checkout, options.workflow.buildCommand, "build");
|
|
265
|
+
await assertCheckout(options, checkout);
|
|
266
|
+
await ensureState();
|
|
267
|
+
const result = await prepare(checkout, () =>
|
|
268
|
+
assertCheckout(options, checkout),
|
|
269
|
+
);
|
|
270
|
+
await assertCheckout(options, checkout);
|
|
271
|
+
return result;
|
|
272
|
+
} finally {
|
|
273
|
+
// This path is a uniquely owned disposable checkout, never the user's
|
|
274
|
+
// source tree or runtime data. Keep registration evidence if cleanup fails.
|
|
275
|
+
if (added)
|
|
276
|
+
await git(options, ["worktree", "remove", "--force", checkout]);
|
|
277
|
+
await plugins.fs.rm(root, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const run = async (
|
|
283
|
+
options: IReleasePreparationRecovery,
|
|
284
|
+
cwd: string,
|
|
285
|
+
command: string,
|
|
286
|
+
phase: string,
|
|
287
|
+
) => {
|
|
288
|
+
const result = await options.smartshell.exec(command, { cwd });
|
|
289
|
+
if (result.exitCode !== 0)
|
|
290
|
+
throw new Error(`Release recovery ${phase} failed.`);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const assertCheckout = async (
|
|
294
|
+
options: IReleasePreparationRecovery,
|
|
295
|
+
checkout: string,
|
|
296
|
+
) => {
|
|
297
|
+
if (
|
|
298
|
+
(await git(options, ["rev-parse", "HEAD"], checkout)) !==
|
|
299
|
+
options.refs.mainOid ||
|
|
300
|
+
(await git(
|
|
301
|
+
options,
|
|
302
|
+
["status", "--porcelain", "--untracked-files=all"],
|
|
303
|
+
checkout,
|
|
304
|
+
))
|
|
305
|
+
) {
|
|
306
|
+
throw new Error("Release recovery build changed its exact tracked source.");
|
|
307
|
+
}
|
|
308
|
+
};
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
publishNpmArtifact,
|
|
19
19
|
verifyStoredNpmArtifact,
|
|
20
20
|
waitForAnonymousNpmArtifact,
|
|
21
|
+
buildQualifiedPnpmArgs,
|
|
22
|
+
type IPnpmReleaseCapability,
|
|
21
23
|
type INpmArtifactProbeOptions,
|
|
22
24
|
type INpmArtifactProbeResult,
|
|
23
25
|
} from "./helpers.npmartifact.js";
|
|
@@ -85,6 +87,7 @@ interface IGitProbeResult {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export interface IReleasePublicationOptions {
|
|
90
|
+
pnpmCapability?: IPnpmReleaseCapability;
|
|
88
91
|
smartshell: plugins.smartshell.Smartshell;
|
|
89
92
|
cwd: string;
|
|
90
93
|
store: ReleaseJournalStore;
|
|
@@ -713,12 +716,15 @@ const executeNpmRegistry = async (
|
|
|
713
716
|
artifact.file,
|
|
714
717
|
);
|
|
715
718
|
await verifyStoredNpmArtifact(artifactPath, artifact);
|
|
719
|
+
// Reject a missing invocation before claiming a potentially publishing target.
|
|
720
|
+
buildQualifiedPnpmArgs(optionsArg.pnpmCapability, []);
|
|
716
721
|
journal = await claimTarget(optionsArg.store, journal, selector);
|
|
717
722
|
const publishResult = await publishNpmArtifact(
|
|
718
723
|
optionsArg.smartshell,
|
|
719
724
|
optionsArg.cwd,
|
|
720
725
|
artifactPath,
|
|
721
726
|
registryArg,
|
|
727
|
+
optionsArg.pnpmCapability,
|
|
722
728
|
);
|
|
723
729
|
probe = await probeNpm(
|
|
724
730
|
journal,
|