@ykdz/template 0.0.35 → 0.0.36
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/application.d.ts +2 -1
- package/dist/application.js +64 -75
- package/dist/main.js +6 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
- package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +968 -364
- package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +0 -1
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +53 -25
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -7
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
- package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -20
- package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
- package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
- package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
- package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
- package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
- package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
- package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
- package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
- package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
- package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
- package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
- package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
- package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
- package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
- package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
- package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
- package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
- package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
- package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
- package/package.json +5 -5
- package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
- package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
acceptDownloadedPublicationArtifact,
|
|
10
|
+
type AcceptedPublicationArtifact,
|
|
11
|
+
PublicationFailure,
|
|
12
|
+
} from "./handoff.ts";
|
|
13
|
+
|
|
14
|
+
const packagePath = "{{PUBLIC_CLI_PACKAGE_PATH}}";
|
|
15
|
+
|
|
16
|
+
type ProcessResult = {
|
|
17
|
+
readonly exitCode: number;
|
|
18
|
+
readonly stdout: string;
|
|
19
|
+
readonly stderr: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ReleaseContext = {
|
|
23
|
+
readonly repository: string;
|
|
24
|
+
readonly sha: string;
|
|
25
|
+
readonly runAttempt: number;
|
|
26
|
+
readonly childEnvironment: NodeJS.ProcessEnv;
|
|
27
|
+
readonly gitEnvironment: NodeJS.ProcessEnv;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type RemoteAsset = {
|
|
31
|
+
readonly id: number;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly size: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type RemoteRelease = {
|
|
37
|
+
readonly tag: string;
|
|
38
|
+
readonly title: string;
|
|
39
|
+
readonly body: string;
|
|
40
|
+
readonly draft: boolean;
|
|
41
|
+
readonly immutable: boolean;
|
|
42
|
+
readonly publishedAt: string | null;
|
|
43
|
+
readonly assets: readonly RemoteAsset[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ImmutableGithubReleaseOptions = {
|
|
47
|
+
readonly repositoryRoot: string;
|
|
48
|
+
readonly artifactDirectory: string;
|
|
49
|
+
readonly environment: Readonly<Record<string, string | undefined>>;
|
|
50
|
+
readonly run?: (
|
|
51
|
+
command: string,
|
|
52
|
+
arguments_: readonly string[],
|
|
53
|
+
options: { readonly cwd: string; readonly env: NodeJS.ProcessEnv },
|
|
54
|
+
) => Promise<ProcessResult>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function fail(code: string): never {
|
|
58
|
+
throw new PublicationFailure(code, "release precondition was not accepted");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function stringField(value: unknown): string | undefined {
|
|
66
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function noControl(value: string): boolean {
|
|
70
|
+
for (let index = 0; index < value.length;) {
|
|
71
|
+
const codePoint = value.codePointAt(index);
|
|
72
|
+
if (
|
|
73
|
+
codePoint === undefined ||
|
|
74
|
+
codePoint < 0x20 ||
|
|
75
|
+
codePoint === 0x7f ||
|
|
76
|
+
(codePoint >= 0x80 && codePoint <= 0x9f)
|
|
77
|
+
) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
index += codePoint > 0xffff ? 2 : 1;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function exactEnvironmentValue(
|
|
86
|
+
environment: Readonly<Record<string, string | undefined>>,
|
|
87
|
+
name: string,
|
|
88
|
+
): string {
|
|
89
|
+
const value = environment[name];
|
|
90
|
+
if (typeof value !== "string" || value.length === 0 || !noControl(value))
|
|
91
|
+
fail("release-context-invalid");
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function releaseContext(
|
|
96
|
+
environment: Readonly<Record<string, string | undefined>>,
|
|
97
|
+
): ReleaseContext {
|
|
98
|
+
const repository = exactEnvironmentValue(environment, "GITHUB_REPOSITORY");
|
|
99
|
+
const sha = exactEnvironmentValue(environment, "GITHUB_SHA");
|
|
100
|
+
const branch = exactEnvironmentValue(environment, "GITHUB_DEFAULT_BRANCH");
|
|
101
|
+
const ref = exactEnvironmentValue(environment, "GITHUB_REF");
|
|
102
|
+
const eventName = exactEnvironmentValue(environment, "GITHUB_EVENT_NAME");
|
|
103
|
+
const runId = exactEnvironmentValue(environment, "GITHUB_RUN_ID");
|
|
104
|
+
const attempt = exactEnvironmentValue(environment, "GITHUB_RUN_ATTEMPT");
|
|
105
|
+
const token = exactEnvironmentValue(environment, "GH_TOKEN");
|
|
106
|
+
if (
|
|
107
|
+
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repository) ||
|
|
108
|
+
!/^[0-9a-f]{40}$/u.test(sha) ||
|
|
109
|
+
!/^[A-Za-z0-9._/-]+$/u.test(branch) ||
|
|
110
|
+
eventName !== "workflow_dispatch" ||
|
|
111
|
+
ref !== `refs/heads/${branch}` ||
|
|
112
|
+
!/^\d+$/u.test(runId) ||
|
|
113
|
+
!/^[1-9]\d*$/u.test(attempt)
|
|
114
|
+
) {
|
|
115
|
+
fail("release-context-invalid");
|
|
116
|
+
}
|
|
117
|
+
const runAttempt = Number(attempt);
|
|
118
|
+
if (!Number.isSafeInteger(runAttempt)) fail("release-context-invalid");
|
|
119
|
+
const childEnvironment: NodeJS.ProcessEnv = { GH_TOKEN: token };
|
|
120
|
+
if (environment.PATH !== undefined) childEnvironment.PATH = environment.PATH;
|
|
121
|
+
const gitEnvironment: NodeJS.ProcessEnv = {};
|
|
122
|
+
if (environment.PATH !== undefined) gitEnvironment.PATH = environment.PATH;
|
|
123
|
+
return { repository, sha, runAttempt, childEnvironment, gitEnvironment };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function defaultRun(
|
|
127
|
+
command: string,
|
|
128
|
+
arguments_: readonly string[],
|
|
129
|
+
options: { readonly cwd: string; readonly env: NodeJS.ProcessEnv },
|
|
130
|
+
): Promise<ProcessResult> {
|
|
131
|
+
return await new Promise((resolve, reject) => {
|
|
132
|
+
const child = spawn(command, arguments_, {
|
|
133
|
+
cwd: options.cwd,
|
|
134
|
+
env: options.env,
|
|
135
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
136
|
+
});
|
|
137
|
+
let stdout = "";
|
|
138
|
+
let stderr = "";
|
|
139
|
+
child.stdout.on("data", (value: Buffer) => (stdout += value));
|
|
140
|
+
child.stderr.on("data", (value: Buffer) => (stderr += value));
|
|
141
|
+
child.once("error", reject);
|
|
142
|
+
child.once("close", (exitCode) =>
|
|
143
|
+
resolve({ exitCode: exitCode ?? 1, stdout, stderr }),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function gh(options: {
|
|
149
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
150
|
+
readonly repositoryRoot: string;
|
|
151
|
+
readonly context: ReleaseContext;
|
|
152
|
+
readonly arguments_: readonly string[];
|
|
153
|
+
}): Promise<ProcessResult> {
|
|
154
|
+
try {
|
|
155
|
+
return await options.run("gh", options.arguments_, {
|
|
156
|
+
cwd: options.repositoryRoot,
|
|
157
|
+
env: options.context.childEnvironment,
|
|
158
|
+
});
|
|
159
|
+
} catch {
|
|
160
|
+
fail("release-remote-unavailable");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function assertCheckoutHead(options: {
|
|
165
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
166
|
+
readonly repositoryRoot: string;
|
|
167
|
+
readonly context: ReleaseContext;
|
|
168
|
+
}): Promise<void> {
|
|
169
|
+
let result: ProcessResult;
|
|
170
|
+
try {
|
|
171
|
+
result = await options.run("git", ["rev-parse", "--verify", "HEAD"], {
|
|
172
|
+
cwd: options.repositoryRoot,
|
|
173
|
+
env: options.context.gitEnvironment,
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
fail("release-context-invalid");
|
|
177
|
+
}
|
|
178
|
+
if (result.exitCode !== 0 || result.stdout !== `${options.context.sha}\n`) {
|
|
179
|
+
fail("release-context-invalid");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseJson(value: string, code: string): unknown {
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(value);
|
|
186
|
+
} catch {
|
|
187
|
+
return fail(code);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function responseBody(value: string): string | undefined {
|
|
192
|
+
const match =
|
|
193
|
+
/^HTTP\/\d(?:\.\d)? 200[^\r\n]*\r?\n(?:[^\r\n]*\r?\n)*\r?\n([\s\S]*)$/u.exec(
|
|
194
|
+
value,
|
|
195
|
+
);
|
|
196
|
+
return match?.[1];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isExplicitNotFound(value: string): boolean {
|
|
200
|
+
return /^HTTP\/\d(?:\.\d)? 404(?:\s|$)/mu.test(value);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function tagReference(options: {
|
|
204
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
205
|
+
readonly repositoryRoot: string;
|
|
206
|
+
readonly context: ReleaseContext;
|
|
207
|
+
readonly tag: string;
|
|
208
|
+
}): Promise<string | undefined> {
|
|
209
|
+
const result = await gh({
|
|
210
|
+
...options,
|
|
211
|
+
arguments_: [
|
|
212
|
+
"api",
|
|
213
|
+
"--include",
|
|
214
|
+
"--method",
|
|
215
|
+
"GET",
|
|
216
|
+
`repos/${options.context.repository}/git/ref/tags/${options.tag}`,
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
if (result.exitCode !== 0) {
|
|
220
|
+
if (isExplicitNotFound(result.stdout)) return undefined;
|
|
221
|
+
fail("release-remote-unavailable");
|
|
222
|
+
}
|
|
223
|
+
const body = responseBody(result.stdout);
|
|
224
|
+
if (body === undefined) fail("release-remote-unavailable");
|
|
225
|
+
const value = parseJson(body, "release-remote-unavailable");
|
|
226
|
+
if (
|
|
227
|
+
!isRecord(value) ||
|
|
228
|
+
!isRecord(value.object) ||
|
|
229
|
+
value.object.type !== "tag" ||
|
|
230
|
+
stringField(value.object.sha) === undefined
|
|
231
|
+
) {
|
|
232
|
+
fail("release-tag-conflict");
|
|
233
|
+
}
|
|
234
|
+
return stringField(value.object.sha)!;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function assertAnnotatedTag(options: {
|
|
238
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
239
|
+
readonly repositoryRoot: string;
|
|
240
|
+
readonly context: ReleaseContext;
|
|
241
|
+
readonly tag: string;
|
|
242
|
+
readonly annotation: string;
|
|
243
|
+
}): Promise<void> {
|
|
244
|
+
const reference = await tagReference(options);
|
|
245
|
+
if (reference === undefined) fail("release-tag-conflict");
|
|
246
|
+
const result = await gh({
|
|
247
|
+
...options,
|
|
248
|
+
arguments_: [
|
|
249
|
+
"api",
|
|
250
|
+
"--method",
|
|
251
|
+
"GET",
|
|
252
|
+
`repos/${options.context.repository}/git/tags/${reference}`,
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
if (result.exitCode !== 0) fail("release-remote-unavailable");
|
|
256
|
+
const value = parseJson(result.stdout, "release-remote-unavailable");
|
|
257
|
+
if (
|
|
258
|
+
!isRecord(value) ||
|
|
259
|
+
value.tag !== options.tag ||
|
|
260
|
+
value.message !== options.annotation ||
|
|
261
|
+
!isRecord(value.object) ||
|
|
262
|
+
value.object.type !== "commit" ||
|
|
263
|
+
value.object.sha !== options.context.sha
|
|
264
|
+
) {
|
|
265
|
+
fail("release-tag-conflict");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function remoteAsset(value: unknown): RemoteAsset | undefined {
|
|
270
|
+
if (!isRecord(value)) return undefined;
|
|
271
|
+
const id = value.id;
|
|
272
|
+
const name = stringField(value.name);
|
|
273
|
+
const size = value.size;
|
|
274
|
+
if (
|
|
275
|
+
!Number.isSafeInteger(id) ||
|
|
276
|
+
typeof id !== "number" ||
|
|
277
|
+
id <= 0 ||
|
|
278
|
+
name === undefined ||
|
|
279
|
+
!Number.isSafeInteger(size) ||
|
|
280
|
+
typeof size !== "number" ||
|
|
281
|
+
size < 0
|
|
282
|
+
) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
return { id, name, size };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function remoteRelease(value: unknown): RemoteRelease | undefined {
|
|
289
|
+
if (!isRecord(value)) return undefined;
|
|
290
|
+
const tag = stringField(value.tag_name);
|
|
291
|
+
const title = stringField(value.name);
|
|
292
|
+
const body = typeof value.body === "string" ? value.body : undefined;
|
|
293
|
+
const publishedAt =
|
|
294
|
+
value.published_at === null ? null : stringField(value.published_at);
|
|
295
|
+
if (
|
|
296
|
+
tag === undefined ||
|
|
297
|
+
title === undefined ||
|
|
298
|
+
body === undefined ||
|
|
299
|
+
typeof value.draft !== "boolean" ||
|
|
300
|
+
typeof value.immutable !== "boolean" ||
|
|
301
|
+
publishedAt === undefined ||
|
|
302
|
+
!Array.isArray(value.assets)
|
|
303
|
+
) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const assets = value.assets.map(remoteAsset);
|
|
307
|
+
if (!assets.every((asset): asset is RemoteAsset => asset !== undefined))
|
|
308
|
+
return undefined;
|
|
309
|
+
return {
|
|
310
|
+
tag,
|
|
311
|
+
title,
|
|
312
|
+
body,
|
|
313
|
+
draft: value.draft,
|
|
314
|
+
immutable: value.immutable,
|
|
315
|
+
publishedAt,
|
|
316
|
+
assets,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function taggedRelease(options: {
|
|
321
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
322
|
+
readonly repositoryRoot: string;
|
|
323
|
+
readonly context: ReleaseContext;
|
|
324
|
+
readonly tag: string;
|
|
325
|
+
}): Promise<RemoteRelease | undefined> {
|
|
326
|
+
const result = await gh({
|
|
327
|
+
...options,
|
|
328
|
+
arguments_: [
|
|
329
|
+
"api",
|
|
330
|
+
"--paginate",
|
|
331
|
+
"--slurp",
|
|
332
|
+
`repos/${options.context.repository}/releases?per_page=100`,
|
|
333
|
+
],
|
|
334
|
+
});
|
|
335
|
+
if (result.exitCode !== 0) fail("release-remote-unavailable");
|
|
336
|
+
const pages = parseJson(result.stdout, "release-remote-unavailable");
|
|
337
|
+
if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)))
|
|
338
|
+
fail("release-remote-unavailable");
|
|
339
|
+
const matches = pages
|
|
340
|
+
.flat()
|
|
341
|
+
.filter((item) => isRecord(item) && item.tag_name === options.tag)
|
|
342
|
+
.map(remoteRelease);
|
|
343
|
+
if (matches.some((item) => item === undefined))
|
|
344
|
+
fail("release-remote-unavailable");
|
|
345
|
+
if (matches.length > 1) fail("release-draft-conflict");
|
|
346
|
+
return matches[0];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function assertReleaseIdentity(options: {
|
|
350
|
+
readonly release: RemoteRelease;
|
|
351
|
+
readonly artifact: AcceptedPublicationArtifact;
|
|
352
|
+
readonly tag: string;
|
|
353
|
+
readonly draft: boolean;
|
|
354
|
+
readonly identityFailure: string;
|
|
355
|
+
readonly assetCountFailure: string;
|
|
356
|
+
}): Promise<readonly RemoteAsset[]> {
|
|
357
|
+
const checksum = await readFile(options.artifact.checksum).catch(() =>
|
|
358
|
+
fail("release-artifact-invalid"),
|
|
359
|
+
);
|
|
360
|
+
const expected = [
|
|
361
|
+
{
|
|
362
|
+
name: path.basename(options.artifact.tgz),
|
|
363
|
+
size: options.artifact.receipt.artifact.size,
|
|
364
|
+
},
|
|
365
|
+
{ name: "SHA512SUMS", size: checksum.byteLength },
|
|
366
|
+
];
|
|
367
|
+
const assets = options.release.assets.toSorted((left, right) =>
|
|
368
|
+
left.name.localeCompare(right.name),
|
|
369
|
+
);
|
|
370
|
+
const sortedExpected = expected.toSorted((left, right) =>
|
|
371
|
+
left.name.localeCompare(right.name),
|
|
372
|
+
);
|
|
373
|
+
if (
|
|
374
|
+
options.release.tag !== options.tag ||
|
|
375
|
+
options.release.title !== options.tag ||
|
|
376
|
+
options.release.body !==
|
|
377
|
+
options.artifact.receipt.publication.releaseNotes ||
|
|
378
|
+
options.release.draft !== options.draft
|
|
379
|
+
) {
|
|
380
|
+
fail(options.identityFailure);
|
|
381
|
+
}
|
|
382
|
+
if (assets.length !== sortedExpected.length) fail(options.assetCountFailure);
|
|
383
|
+
if (
|
|
384
|
+
assets.some(
|
|
385
|
+
(asset, index) =>
|
|
386
|
+
asset.name !== sortedExpected[index]!.name ||
|
|
387
|
+
asset.size !== sortedExpected[index]!.size ||
|
|
388
|
+
assets.findIndex((candidate) => candidate.name === asset.name) !==
|
|
389
|
+
index,
|
|
390
|
+
)
|
|
391
|
+
) {
|
|
392
|
+
fail(options.identityFailure);
|
|
393
|
+
}
|
|
394
|
+
return assets;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function verifyAssetBytes(options: {
|
|
398
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
399
|
+
readonly repositoryRoot: string;
|
|
400
|
+
readonly context: ReleaseContext;
|
|
401
|
+
readonly artifact: AcceptedPublicationArtifact;
|
|
402
|
+
readonly assets: readonly RemoteAsset[];
|
|
403
|
+
readonly temporaryDirectory: string;
|
|
404
|
+
}): Promise<void> {
|
|
405
|
+
const expected = new Set([path.basename(options.artifact.tgz), "SHA512SUMS"]);
|
|
406
|
+
const downloaded = new Map<string, string>();
|
|
407
|
+
for (const asset of options.assets) {
|
|
408
|
+
if (!expected.has(asset.name)) fail("release-asset-conflict");
|
|
409
|
+
const destination = path.join(
|
|
410
|
+
options.temporaryDirectory,
|
|
411
|
+
`asset-${asset.id}`,
|
|
412
|
+
);
|
|
413
|
+
const result = await gh({
|
|
414
|
+
...options,
|
|
415
|
+
arguments_: [
|
|
416
|
+
"api",
|
|
417
|
+
"--method",
|
|
418
|
+
"GET",
|
|
419
|
+
"-H",
|
|
420
|
+
"Accept: application/octet-stream",
|
|
421
|
+
`repos/${options.context.repository}/releases/assets/${asset.id}`,
|
|
422
|
+
`--output=${destination}`,
|
|
423
|
+
],
|
|
424
|
+
});
|
|
425
|
+
if (result.exitCode !== 0) fail("release-asset-conflict");
|
|
426
|
+
downloaded.set(asset.name, destination);
|
|
427
|
+
}
|
|
428
|
+
const tgz = downloaded.get(path.basename(options.artifact.tgz));
|
|
429
|
+
const checksum = downloaded.get("SHA512SUMS");
|
|
430
|
+
if (tgz === undefined || checksum === undefined)
|
|
431
|
+
fail("release-asset-conflict");
|
|
432
|
+
const [downloadedTgz, downloadedChecksum, localChecksum] = await Promise.all([
|
|
433
|
+
readFile(tgz),
|
|
434
|
+
readFile(checksum),
|
|
435
|
+
readFile(options.artifact.checksum),
|
|
436
|
+
]).catch(() => fail("release-asset-conflict"));
|
|
437
|
+
if (
|
|
438
|
+
downloadedTgz.byteLength !== options.artifact.receipt.artifact.size ||
|
|
439
|
+
createHash("sha512").update(downloadedTgz).digest("hex") !==
|
|
440
|
+
options.artifact.sha512 ||
|
|
441
|
+
!downloadedChecksum.equals(localChecksum)
|
|
442
|
+
) {
|
|
443
|
+
fail("release-asset-conflict");
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
await Promise.all([...downloaded.values()].map((filePath) => rm(filePath)));
|
|
447
|
+
} catch {
|
|
448
|
+
fail("release-remote-unavailable");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function createTag(options: {
|
|
453
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
454
|
+
readonly repositoryRoot: string;
|
|
455
|
+
readonly context: ReleaseContext;
|
|
456
|
+
readonly tag: string;
|
|
457
|
+
readonly annotation: string;
|
|
458
|
+
}): Promise<void> {
|
|
459
|
+
const tagObject = await gh({
|
|
460
|
+
...options,
|
|
461
|
+
arguments_: [
|
|
462
|
+
"api",
|
|
463
|
+
"--method",
|
|
464
|
+
"POST",
|
|
465
|
+
`repos/${options.context.repository}/git/tags`,
|
|
466
|
+
"-f",
|
|
467
|
+
`tag=${options.tag}`,
|
|
468
|
+
"-f",
|
|
469
|
+
`message=${options.annotation}`,
|
|
470
|
+
"-f",
|
|
471
|
+
`object=${options.context.sha}`,
|
|
472
|
+
"-f",
|
|
473
|
+
"type=commit",
|
|
474
|
+
],
|
|
475
|
+
});
|
|
476
|
+
if (tagObject.exitCode !== 0) fail("release-write-failed");
|
|
477
|
+
const tagObjectValue = parseJson(tagObject.stdout, "release-write-failed");
|
|
478
|
+
const tagObjectSha = isRecord(tagObjectValue)
|
|
479
|
+
? stringField(tagObjectValue.sha)
|
|
480
|
+
: undefined;
|
|
481
|
+
if (tagObjectSha === undefined) fail("release-write-failed");
|
|
482
|
+
const reference = await gh({
|
|
483
|
+
...options,
|
|
484
|
+
arguments_: [
|
|
485
|
+
"api",
|
|
486
|
+
"--method",
|
|
487
|
+
"POST",
|
|
488
|
+
`repos/${options.context.repository}/git/refs`,
|
|
489
|
+
"-f",
|
|
490
|
+
`ref=refs/tags/${options.tag}`,
|
|
491
|
+
"-f",
|
|
492
|
+
`sha=${tagObjectSha}`,
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
if (reference.exitCode !== 0) fail("release-write-failed");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function createDraft(options: {
|
|
499
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
500
|
+
readonly repositoryRoot: string;
|
|
501
|
+
readonly context: ReleaseContext;
|
|
502
|
+
readonly artifact: AcceptedPublicationArtifact;
|
|
503
|
+
readonly tag: string;
|
|
504
|
+
readonly notesPath: string;
|
|
505
|
+
}): Promise<void> {
|
|
506
|
+
const result = await gh({
|
|
507
|
+
...options,
|
|
508
|
+
arguments_: [
|
|
509
|
+
"release",
|
|
510
|
+
"create",
|
|
511
|
+
options.tag,
|
|
512
|
+
options.artifact.tgz,
|
|
513
|
+
options.artifact.checksum,
|
|
514
|
+
"--draft",
|
|
515
|
+
"--verify-tag",
|
|
516
|
+
"--title",
|
|
517
|
+
options.tag,
|
|
518
|
+
`--notes-file=${options.notesPath}`,
|
|
519
|
+
],
|
|
520
|
+
});
|
|
521
|
+
if (result.exitCode !== 0) fail("release-write-failed");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function publishDraft(options: {
|
|
525
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
526
|
+
readonly repositoryRoot: string;
|
|
527
|
+
readonly context: ReleaseContext;
|
|
528
|
+
readonly tag: string;
|
|
529
|
+
}): Promise<void> {
|
|
530
|
+
const result = await gh({
|
|
531
|
+
...options,
|
|
532
|
+
arguments_: ["release", "edit", options.tag, "--draft=false"],
|
|
533
|
+
});
|
|
534
|
+
if (result.exitCode !== 0) fail("release-write-failed");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function verifyAttestations(options: {
|
|
538
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
539
|
+
readonly repositoryRoot: string;
|
|
540
|
+
readonly context: ReleaseContext;
|
|
541
|
+
readonly artifact: AcceptedPublicationArtifact;
|
|
542
|
+
readonly tag: string;
|
|
543
|
+
}): Promise<void> {
|
|
544
|
+
for (const arguments_ of [
|
|
545
|
+
["release", "verify", options.tag],
|
|
546
|
+
["release", "verify-asset", options.tag, options.artifact.tgz],
|
|
547
|
+
["release", "verify-asset", options.tag, options.artifact.checksum],
|
|
548
|
+
] as const) {
|
|
549
|
+
const result = await gh({ ...options, arguments_ });
|
|
550
|
+
if (result.exitCode !== 0) fail("release-attestation-invalid");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function acceptedArtifact(options: {
|
|
555
|
+
readonly repositoryRoot: string;
|
|
556
|
+
readonly artifactDirectory: string;
|
|
557
|
+
readonly repository: string;
|
|
558
|
+
}): Promise<AcceptedPublicationArtifact> {
|
|
559
|
+
try {
|
|
560
|
+
return await acceptDownloadedPublicationArtifact({
|
|
561
|
+
repositoryRoot: options.repositoryRoot,
|
|
562
|
+
packagePath,
|
|
563
|
+
artifactDirectory: options.artifactDirectory,
|
|
564
|
+
githubRepository: options.repository,
|
|
565
|
+
});
|
|
566
|
+
} catch {
|
|
567
|
+
fail("release-artifact-invalid");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function requireRelease(options: {
|
|
572
|
+
readonly run: NonNullable<ImmutableGithubReleaseOptions["run"]>;
|
|
573
|
+
readonly repositoryRoot: string;
|
|
574
|
+
readonly context: ReleaseContext;
|
|
575
|
+
readonly tag: string;
|
|
576
|
+
}): Promise<RemoteRelease> {
|
|
577
|
+
const release = await taggedRelease(options);
|
|
578
|
+
if (release === undefined) fail("release-write-failed");
|
|
579
|
+
return release;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function installSignalCleanup(temporaryDirectory: string): () => void {
|
|
583
|
+
let receivedSignal = false;
|
|
584
|
+
const remove = () => {
|
|
585
|
+
process.off("SIGINT", onSigint);
|
|
586
|
+
process.off("SIGTERM", onSigterm);
|
|
587
|
+
};
|
|
588
|
+
const cleanupAndResend = (signal: NodeJS.Signals) => {
|
|
589
|
+
if (receivedSignal) return;
|
|
590
|
+
receivedSignal = true;
|
|
591
|
+
void rm(temporaryDirectory, { recursive: true, force: true })
|
|
592
|
+
.catch(() => undefined)
|
|
593
|
+
.finally(() => {
|
|
594
|
+
remove();
|
|
595
|
+
process.kill(process.pid, signal);
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
const onSigint = () => cleanupAndResend("SIGINT");
|
|
599
|
+
const onSigterm = () => cleanupAndResend("SIGTERM");
|
|
600
|
+
process.once("SIGINT", onSigint);
|
|
601
|
+
process.once("SIGTERM", onSigterm);
|
|
602
|
+
return remove;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export async function runImmutableGithubRelease(
|
|
606
|
+
options: ImmutableGithubReleaseOptions,
|
|
607
|
+
): Promise<void> {
|
|
608
|
+
const context = releaseContext(options.environment);
|
|
609
|
+
const artifact = await acceptedArtifact({
|
|
610
|
+
repositoryRoot: options.repositoryRoot,
|
|
611
|
+
artifactDirectory: options.artifactDirectory,
|
|
612
|
+
repository: context.repository,
|
|
613
|
+
});
|
|
614
|
+
const tag = `v${artifact.receipt.packedManifest.version}`;
|
|
615
|
+
const annotation = `npm artifact SHA-512: ${artifact.sha512}`;
|
|
616
|
+
const run = options.run ?? defaultRun;
|
|
617
|
+
await assertCheckoutHead({
|
|
618
|
+
run,
|
|
619
|
+
repositoryRoot: options.repositoryRoot,
|
|
620
|
+
context,
|
|
621
|
+
});
|
|
622
|
+
const temporaryDirectory = await mkdtemp(
|
|
623
|
+
path.join(tmpdir(), "npm-github-release-"),
|
|
624
|
+
).catch(() => fail("release-remote-unavailable"));
|
|
625
|
+
const removeSignalCleanup = installSignalCleanup(temporaryDirectory);
|
|
626
|
+
let completed = false;
|
|
627
|
+
try {
|
|
628
|
+
const notesPath = path.join(temporaryDirectory, "notes.md");
|
|
629
|
+
await writeFile(notesPath, artifact.receipt.publication.releaseNotes).catch(
|
|
630
|
+
() => fail("release-write-failed"),
|
|
631
|
+
);
|
|
632
|
+
let alreadyImmutable = false;
|
|
633
|
+
const remoteTag = await tagReference({
|
|
634
|
+
run,
|
|
635
|
+
repositoryRoot: options.repositoryRoot,
|
|
636
|
+
context,
|
|
637
|
+
tag,
|
|
638
|
+
});
|
|
639
|
+
const remoteRelease = await taggedRelease({
|
|
640
|
+
run,
|
|
641
|
+
repositoryRoot: options.repositoryRoot,
|
|
642
|
+
context,
|
|
643
|
+
tag,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
if (context.runAttempt === 1) {
|
|
647
|
+
if (remoteTag !== undefined || remoteRelease !== undefined)
|
|
648
|
+
fail("fresh-release-conflict");
|
|
649
|
+
await createTag({
|
|
650
|
+
run,
|
|
651
|
+
repositoryRoot: options.repositoryRoot,
|
|
652
|
+
context,
|
|
653
|
+
tag,
|
|
654
|
+
annotation,
|
|
655
|
+
});
|
|
656
|
+
await assertAnnotatedTag({
|
|
657
|
+
run,
|
|
658
|
+
repositoryRoot: options.repositoryRoot,
|
|
659
|
+
context,
|
|
660
|
+
tag,
|
|
661
|
+
annotation,
|
|
662
|
+
});
|
|
663
|
+
await createDraft({
|
|
664
|
+
run,
|
|
665
|
+
repositoryRoot: options.repositoryRoot,
|
|
666
|
+
context,
|
|
667
|
+
artifact,
|
|
668
|
+
tag,
|
|
669
|
+
notesPath,
|
|
670
|
+
});
|
|
671
|
+
} else {
|
|
672
|
+
if (remoteTag === undefined || remoteRelease === undefined)
|
|
673
|
+
fail("release-incomplete-conflict");
|
|
674
|
+
await assertAnnotatedTag({
|
|
675
|
+
run,
|
|
676
|
+
repositoryRoot: options.repositoryRoot,
|
|
677
|
+
context,
|
|
678
|
+
tag,
|
|
679
|
+
annotation,
|
|
680
|
+
});
|
|
681
|
+
if (!remoteRelease.draft) {
|
|
682
|
+
if (!remoteRelease.immutable || remoteRelease.publishedAt === null)
|
|
683
|
+
fail("release-immutability-unproven");
|
|
684
|
+
await assertReleaseIdentity({
|
|
685
|
+
release: remoteRelease,
|
|
686
|
+
artifact,
|
|
687
|
+
tag,
|
|
688
|
+
draft: false,
|
|
689
|
+
identityFailure: "release-asset-conflict",
|
|
690
|
+
assetCountFailure: "release-incomplete-conflict",
|
|
691
|
+
});
|
|
692
|
+
await verifyAttestations({
|
|
693
|
+
run,
|
|
694
|
+
repositoryRoot: options.repositoryRoot,
|
|
695
|
+
context,
|
|
696
|
+
artifact,
|
|
697
|
+
tag,
|
|
698
|
+
});
|
|
699
|
+
alreadyImmutable = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (!alreadyImmutable) {
|
|
704
|
+
const draft = await requireRelease({
|
|
705
|
+
run,
|
|
706
|
+
repositoryRoot: options.repositoryRoot,
|
|
707
|
+
context,
|
|
708
|
+
tag,
|
|
709
|
+
});
|
|
710
|
+
const draftAssets = await assertReleaseIdentity({
|
|
711
|
+
release: draft,
|
|
712
|
+
artifact,
|
|
713
|
+
tag,
|
|
714
|
+
draft: true,
|
|
715
|
+
identityFailure:
|
|
716
|
+
context.runAttempt === 1
|
|
717
|
+
? "release-write-failed"
|
|
718
|
+
: "release-draft-conflict",
|
|
719
|
+
assetCountFailure:
|
|
720
|
+
context.runAttempt === 1
|
|
721
|
+
? "release-write-failed"
|
|
722
|
+
: "release-incomplete-conflict",
|
|
723
|
+
});
|
|
724
|
+
await verifyAssetBytes({
|
|
725
|
+
run,
|
|
726
|
+
repositoryRoot: options.repositoryRoot,
|
|
727
|
+
context,
|
|
728
|
+
artifact,
|
|
729
|
+
assets: draftAssets,
|
|
730
|
+
temporaryDirectory,
|
|
731
|
+
});
|
|
732
|
+
await assertAnnotatedTag({
|
|
733
|
+
run,
|
|
734
|
+
repositoryRoot: options.repositoryRoot,
|
|
735
|
+
context,
|
|
736
|
+
tag,
|
|
737
|
+
annotation,
|
|
738
|
+
});
|
|
739
|
+
await publishDraft({
|
|
740
|
+
run,
|
|
741
|
+
repositoryRoot: options.repositoryRoot,
|
|
742
|
+
context,
|
|
743
|
+
tag,
|
|
744
|
+
});
|
|
745
|
+
const published = await requireRelease({
|
|
746
|
+
run,
|
|
747
|
+
repositoryRoot: options.repositoryRoot,
|
|
748
|
+
context,
|
|
749
|
+
tag,
|
|
750
|
+
});
|
|
751
|
+
if (published.draft) fail("release-write-failed");
|
|
752
|
+
await assertReleaseIdentity({
|
|
753
|
+
release: published,
|
|
754
|
+
artifact,
|
|
755
|
+
tag,
|
|
756
|
+
draft: false,
|
|
757
|
+
identityFailure: "release-write-failed",
|
|
758
|
+
assetCountFailure: "release-write-failed",
|
|
759
|
+
});
|
|
760
|
+
if (!published.immutable || published.publishedAt === null)
|
|
761
|
+
fail("release-immutability-unproven");
|
|
762
|
+
await assertAnnotatedTag({
|
|
763
|
+
run,
|
|
764
|
+
repositoryRoot: options.repositoryRoot,
|
|
765
|
+
context,
|
|
766
|
+
tag,
|
|
767
|
+
annotation,
|
|
768
|
+
});
|
|
769
|
+
await verifyAttestations({
|
|
770
|
+
run,
|
|
771
|
+
repositoryRoot: options.repositoryRoot,
|
|
772
|
+
context,
|
|
773
|
+
artifact,
|
|
774
|
+
tag,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
completed = true;
|
|
778
|
+
} finally {
|
|
779
|
+
removeSignalCleanup();
|
|
780
|
+
try {
|
|
781
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
782
|
+
} catch {
|
|
783
|
+
if (completed) fail("release-remote-unavailable");
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (completed) console.log(`Immutable GitHub Release verified: ${tag}`);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (import.meta.main) {
|
|
790
|
+
try {
|
|
791
|
+
const artifactDirectory = process.env.PUBLICATION_ARTIFACT_DIRECTORY;
|
|
792
|
+
if (
|
|
793
|
+
artifactDirectory === undefined ||
|
|
794
|
+
!path.isAbsolute(artifactDirectory)
|
|
795
|
+
) {
|
|
796
|
+
throw new PublicationFailure(
|
|
797
|
+
"release-artifact-invalid",
|
|
798
|
+
"PUBLICATION_ARTIFACT_DIRECTORY must be absolute",
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
await runImmutableGithubRelease({
|
|
802
|
+
repositoryRoot: process.cwd(),
|
|
803
|
+
artifactDirectory,
|
|
804
|
+
environment: process.env,
|
|
805
|
+
});
|
|
806
|
+
} catch (error) {
|
|
807
|
+
const code =
|
|
808
|
+
error instanceof PublicationFailure
|
|
809
|
+
? error.code
|
|
810
|
+
: "release-remote-unavailable";
|
|
811
|
+
console.error(`ERROR ${code}`);
|
|
812
|
+
console.error("Observed: GitHub release evidence was not accepted");
|
|
813
|
+
console.error("Expected: one immutable release for the accepted artifact");
|
|
814
|
+
console.error(
|
|
815
|
+
"Next action: preserve the release state and contact a maintainer",
|
|
816
|
+
);
|
|
817
|
+
process.exitCode = 1;
|
|
818
|
+
}
|
|
819
|
+
}
|