@ykdz/template 0.0.33 → 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 +970 -365
- 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/development-container.d.ts +1 -0
- package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +17 -0
- 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 +1 -2
- 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 +61 -35
- 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 -8
- 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 -21
- 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/gitignore +1 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/github-cli.Dockerfile +9 -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-builtin-presets/templates/vike-app/db/src/readiness.ts +1 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/test/global-setup.ts +21 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +6 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/vitest.config.ts +7 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/drizzle.config.ts +5 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +102 -22
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/test/playwright-teardown.ts +12 -0
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/vite.config.ts +39 -1
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +48 -7
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +59 -8
- 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 +3 -3
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/scripts/prepare-database.ts +0 -45
- package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/run-playwright.ts +0 -151
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/scripts/run-playwright.ts +0 -62
- package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/scripts/run-playwright.ts +0 -103
- 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,450 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const expectedFiles = new Set([
|
|
6
|
+
"SHA512SUMS",
|
|
7
|
+
"verified-publication-artifact.json",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export class PublicationFailure extends Error {
|
|
11
|
+
readonly code: string;
|
|
12
|
+
constructor(code: string, message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type AcceptedPublicationArtifact = {
|
|
19
|
+
readonly receipt: Receipt;
|
|
20
|
+
readonly tgz: string;
|
|
21
|
+
readonly checksum: string;
|
|
22
|
+
readonly sha512: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Receipt = {
|
|
26
|
+
readonly schemaVersion: 1;
|
|
27
|
+
readonly publication: {
|
|
28
|
+
readonly packageName: string;
|
|
29
|
+
readonly version: string;
|
|
30
|
+
readonly commandName: string;
|
|
31
|
+
readonly repository: string;
|
|
32
|
+
readonly releaseDate: string;
|
|
33
|
+
readonly releaseNotes: string;
|
|
34
|
+
};
|
|
35
|
+
readonly packedManifest: {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly version: string;
|
|
38
|
+
readonly bin: Record<string, string>;
|
|
39
|
+
readonly repository: {
|
|
40
|
+
readonly type: "git";
|
|
41
|
+
readonly url: string;
|
|
42
|
+
readonly directory: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
readonly artifact: {
|
|
46
|
+
readonly file: string;
|
|
47
|
+
readonly checksumFile: "SHA512SUMS";
|
|
48
|
+
readonly integrity: string;
|
|
49
|
+
readonly size: number;
|
|
50
|
+
};
|
|
51
|
+
readonly files: readonly {
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly mode: number;
|
|
54
|
+
readonly size: number;
|
|
55
|
+
}[];
|
|
56
|
+
readonly bin: {
|
|
57
|
+
readonly path: "package/dist/cli.js";
|
|
58
|
+
readonly shebang: "#!/usr/bin/env node";
|
|
59
|
+
readonly mode: number;
|
|
60
|
+
readonly posixExecutableChecked: boolean;
|
|
61
|
+
};
|
|
62
|
+
readonly smokes: readonly {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly args: readonly string[];
|
|
65
|
+
readonly stdout: string;
|
|
66
|
+
}[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function fail(code: string, message: string): never {
|
|
70
|
+
throw new PublicationFailure(code, message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseJson(value: string, code: string): unknown {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(value);
|
|
76
|
+
} catch {
|
|
77
|
+
return fail(code, "command returned invalid JSON");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stringField(record: Record<string, unknown>, key: string): boolean {
|
|
86
|
+
return typeof record[key] === "string";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function repositoryObject(
|
|
90
|
+
value: unknown,
|
|
91
|
+
):
|
|
92
|
+
| { readonly type: string; readonly url: string; readonly directory: string }
|
|
93
|
+
| undefined {
|
|
94
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
95
|
+
return undefined;
|
|
96
|
+
const record = value as Record<string, unknown>;
|
|
97
|
+
if (
|
|
98
|
+
typeof record.type !== "string" ||
|
|
99
|
+
typeof record.url !== "string" ||
|
|
100
|
+
typeof record.directory !== "string"
|
|
101
|
+
)
|
|
102
|
+
return undefined;
|
|
103
|
+
return { type: record.type, url: record.url, directory: record.directory };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function canonicalGithubRepository(value: string): string | undefined {
|
|
107
|
+
const match =
|
|
108
|
+
/^(?:git\+)?https:\/\/github\.com\/([^/]+\/[^/#]+?)(?:\.git)?(?:#.*)?$/u.exec(
|
|
109
|
+
value,
|
|
110
|
+
);
|
|
111
|
+
return match?.[1];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function safeBasename(value: unknown): value is string {
|
|
115
|
+
return (
|
|
116
|
+
typeof value === "string" &&
|
|
117
|
+
/^[A-Za-z0-9._-]+$/u.test(value) &&
|
|
118
|
+
value !== "." &&
|
|
119
|
+
value !== ".." &&
|
|
120
|
+
!value.includes("/") &&
|
|
121
|
+
!value.includes("\\")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isReceipt(value: unknown): value is Receipt {
|
|
126
|
+
if (!isRecord(value) || value.schemaVersion !== 1) return false;
|
|
127
|
+
const publication = value.publication;
|
|
128
|
+
const packedManifest = value.packedManifest;
|
|
129
|
+
const artifact = value.artifact;
|
|
130
|
+
const bin = value.bin;
|
|
131
|
+
if (
|
|
132
|
+
!isRecord(publication) ||
|
|
133
|
+
!isRecord(packedManifest) ||
|
|
134
|
+
!isRecord(artifact) ||
|
|
135
|
+
!isRecord(bin) ||
|
|
136
|
+
!Array.isArray(value.files) ||
|
|
137
|
+
!Array.isArray(value.smokes)
|
|
138
|
+
)
|
|
139
|
+
return false;
|
|
140
|
+
const releaseDate = publication.releaseDate;
|
|
141
|
+
const releaseNotes = publication.releaseNotes;
|
|
142
|
+
if (
|
|
143
|
+
![
|
|
144
|
+
"packageName",
|
|
145
|
+
"version",
|
|
146
|
+
"commandName",
|
|
147
|
+
"repository",
|
|
148
|
+
"releaseDate",
|
|
149
|
+
"releaseNotes",
|
|
150
|
+
].every((key) => stringField(publication, key)) ||
|
|
151
|
+
typeof releaseDate !== "string" ||
|
|
152
|
+
!/^\d{4}-\d{2}-\d{2}$/u.test(releaseDate) ||
|
|
153
|
+
typeof releaseNotes !== "string" ||
|
|
154
|
+
releaseNotes.trim().length === 0 ||
|
|
155
|
+
!stringField(packedManifest, "name") ||
|
|
156
|
+
!stringField(packedManifest, "version") ||
|
|
157
|
+
!isRecord(packedManifest.bin) ||
|
|
158
|
+
Object.values(packedManifest.bin).some(
|
|
159
|
+
(entry) => typeof entry !== "string",
|
|
160
|
+
) ||
|
|
161
|
+
repositoryObject(packedManifest.repository) === undefined
|
|
162
|
+
)
|
|
163
|
+
return false;
|
|
164
|
+
const artifactSize = artifact.size;
|
|
165
|
+
if (
|
|
166
|
+
!safeBasename(artifact.file) ||
|
|
167
|
+
artifact.checksumFile !== "SHA512SUMS" ||
|
|
168
|
+
typeof artifact.integrity !== "string" ||
|
|
169
|
+
typeof artifactSize !== "number" ||
|
|
170
|
+
!Number.isSafeInteger(artifactSize) ||
|
|
171
|
+
artifactSize < 0 ||
|
|
172
|
+
value.files.length === 0 ||
|
|
173
|
+
value.files.some(
|
|
174
|
+
(file) =>
|
|
175
|
+
!isRecord(file) ||
|
|
176
|
+
typeof file.path !== "string" ||
|
|
177
|
+
!Number.isSafeInteger(file.mode) ||
|
|
178
|
+
!Number.isSafeInteger(file.size),
|
|
179
|
+
) ||
|
|
180
|
+
typeof bin.path !== "string" ||
|
|
181
|
+
typeof bin.shebang !== "string" ||
|
|
182
|
+
!Number.isSafeInteger(bin.mode) ||
|
|
183
|
+
typeof bin.posixExecutableChecked !== "boolean" ||
|
|
184
|
+
value.smokes.some(
|
|
185
|
+
(smoke) =>
|
|
186
|
+
!isRecord(smoke) ||
|
|
187
|
+
typeof smoke.name !== "string" ||
|
|
188
|
+
!Array.isArray(smoke.args) ||
|
|
189
|
+
smoke.args.some((argument) => typeof argument !== "string") ||
|
|
190
|
+
typeof smoke.stdout !== "string",
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
return false;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function readArtifact(directory: string): Promise<{
|
|
198
|
+
readonly receipt: Receipt;
|
|
199
|
+
readonly tgz: string;
|
|
200
|
+
readonly checksum: string;
|
|
201
|
+
readonly sha512: string;
|
|
202
|
+
}> {
|
|
203
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() =>
|
|
204
|
+
fail("artifact-unavailable", "verified artifact directory is unavailable"),
|
|
205
|
+
);
|
|
206
|
+
if (entries.length !== 3 || entries.some((entry) => !entry.isFile())) {
|
|
207
|
+
fail(
|
|
208
|
+
"artifact-set-invalid",
|
|
209
|
+
"artifact must contain exactly three regular files",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const tgz = entries.find((entry) => entry.name.endsWith(".tgz"));
|
|
213
|
+
if (
|
|
214
|
+
tgz === undefined ||
|
|
215
|
+
entries.some((entry) => entry.name.startsWith("."))
|
|
216
|
+
) {
|
|
217
|
+
fail("artifact-set-invalid", "artifact must contain one non-hidden tgz");
|
|
218
|
+
}
|
|
219
|
+
for (const name of expectedFiles)
|
|
220
|
+
if (!entries.some((entry) => entry.name === name)) {
|
|
221
|
+
fail("artifact-set-invalid", `artifact misses ${name}`);
|
|
222
|
+
}
|
|
223
|
+
const receiptPath = path.join(
|
|
224
|
+
directory,
|
|
225
|
+
"verified-publication-artifact.json",
|
|
226
|
+
);
|
|
227
|
+
const receipt = parseJson(
|
|
228
|
+
await readFile(receiptPath, "utf8"),
|
|
229
|
+
"artifact-receipt-invalid",
|
|
230
|
+
);
|
|
231
|
+
if (
|
|
232
|
+
!isReceipt(receipt) ||
|
|
233
|
+
receipt.artifact.file !== tgz.name ||
|
|
234
|
+
receipt.artifact.checksumFile !== "SHA512SUMS"
|
|
235
|
+
) {
|
|
236
|
+
fail(
|
|
237
|
+
"artifact-receipt-invalid",
|
|
238
|
+
"receipt schema or artifact references are invalid",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const tgzPath = path.resolve(directory, tgz.name);
|
|
242
|
+
const tgzStat = await stat(tgzPath);
|
|
243
|
+
if (
|
|
244
|
+
!Number.isSafeInteger(receipt.artifact.size) ||
|
|
245
|
+
receipt.artifact.size !== tgzStat.size
|
|
246
|
+
) {
|
|
247
|
+
fail("artifact-integrity-invalid", "receipt size differs from tgz");
|
|
248
|
+
}
|
|
249
|
+
const bytes = await readFile(tgzPath);
|
|
250
|
+
const sha512 = createHash("sha512").update(bytes).digest("hex");
|
|
251
|
+
if (
|
|
252
|
+
receipt.artifact.integrity !==
|
|
253
|
+
`sha512-${Buffer.from(sha512, "hex").toString("base64")}`
|
|
254
|
+
) {
|
|
255
|
+
fail("artifact-integrity-invalid", "receipt SRI differs from tgz");
|
|
256
|
+
}
|
|
257
|
+
const checksum = path.join(directory, receipt.artifact.checksumFile);
|
|
258
|
+
const checksumBytes = await readFile(checksum, "utf8");
|
|
259
|
+
if (checksumBytes !== `${sha512} ${tgz.name}\n`)
|
|
260
|
+
fail("artifact-integrity-invalid", "checksum file differs from tgz");
|
|
261
|
+
return { receipt, tgz: tgzPath, checksum, sha512 };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sameArguments(
|
|
265
|
+
actual: readonly string[],
|
|
266
|
+
expected: readonly string[],
|
|
267
|
+
): boolean {
|
|
268
|
+
return (
|
|
269
|
+
actual.length === expected.length &&
|
|
270
|
+
actual.every((value, index) => value === expected[index])
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isCanonicalPackageFilePath(value: string): boolean {
|
|
275
|
+
const segments = value.split("/");
|
|
276
|
+
return (
|
|
277
|
+
segments[0] === "package" &&
|
|
278
|
+
segments.length > 1 &&
|
|
279
|
+
segments.slice(1).every(safeBasename)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const ticket09ReceiptFilePaths = [
|
|
284
|
+
"package/CHANGELOG.md",
|
|
285
|
+
"package/LICENSE",
|
|
286
|
+
"package/README.md",
|
|
287
|
+
"package/dist/cli-command-identity.js",
|
|
288
|
+
"package/dist/cli.js",
|
|
289
|
+
"package/dist/main.js",
|
|
290
|
+
"package/package.json",
|
|
291
|
+
] as const;
|
|
292
|
+
|
|
293
|
+
function hasCanonicalReceiptFiles(files: Receipt["files"]): boolean {
|
|
294
|
+
if (
|
|
295
|
+
files.length !== ticket09ReceiptFilePaths.length ||
|
|
296
|
+
files.some((file, index) => file.path !== ticket09ReceiptFilePaths[index])
|
|
297
|
+
) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
let previousPath: string | undefined;
|
|
301
|
+
for (const file of files) {
|
|
302
|
+
if (
|
|
303
|
+
!isCanonicalPackageFilePath(file.path) ||
|
|
304
|
+
!Number.isSafeInteger(file.mode) ||
|
|
305
|
+
file.mode < 0 ||
|
|
306
|
+
file.mode > 0o777 ||
|
|
307
|
+
!Number.isSafeInteger(file.size) ||
|
|
308
|
+
file.size < 0 ||
|
|
309
|
+
(previousPath !== undefined && previousPath >= file.path)
|
|
310
|
+
) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
previousPath = file.path;
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hasStableSmokeEvidence(receipt: Receipt): boolean {
|
|
319
|
+
const [runtimeImport, help, version, greet] = receipt.smokes;
|
|
320
|
+
return (
|
|
321
|
+
receipt.smokes.length === 4 &&
|
|
322
|
+
runtimeImport?.name === "runtime-import" &&
|
|
323
|
+
sameArguments(runtimeImport.args, []) &&
|
|
324
|
+
runtimeImport.stdout === "" &&
|
|
325
|
+
help?.name === "help" &&
|
|
326
|
+
sameArguments(help.args, ["--help"]) &&
|
|
327
|
+
help.stdout.includes(`Usage: ${receipt.publication.commandName}`) &&
|
|
328
|
+
help.stdout.includes("greet") &&
|
|
329
|
+
version?.name === "version" &&
|
|
330
|
+
sameArguments(version.args, ["--version"]) &&
|
|
331
|
+
version.stdout === `${receipt.publication.version}\n` &&
|
|
332
|
+
greet?.name === "greet" &&
|
|
333
|
+
sameArguments(greet.args, ["greet", " Ada Lovelace "]) &&
|
|
334
|
+
greet.stdout === "Hello, Ada Lovelace\n"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function hasStableSemverShape(value: string): boolean {
|
|
339
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.test(
|
|
340
|
+
value,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function assertArtifactIdentity(options: {
|
|
345
|
+
readonly repositoryRoot: string;
|
|
346
|
+
readonly packagePath: string;
|
|
347
|
+
readonly receipt: Receipt;
|
|
348
|
+
readonly githubRepository: string;
|
|
349
|
+
}): Promise<void> {
|
|
350
|
+
const { receipt } = options;
|
|
351
|
+
const manifest = parseJson(
|
|
352
|
+
await readFile(
|
|
353
|
+
path.join(options.repositoryRoot, options.packagePath, "package.json"),
|
|
354
|
+
"utf8",
|
|
355
|
+
),
|
|
356
|
+
"identity-invalid",
|
|
357
|
+
);
|
|
358
|
+
if (!isRecord(manifest))
|
|
359
|
+
fail("identity-invalid", "checkout manifest is not an object");
|
|
360
|
+
const packed = receipt.packedManifest;
|
|
361
|
+
const publication = receipt.publication;
|
|
362
|
+
const bin = packed?.bin;
|
|
363
|
+
const sourceBin = manifest.bin;
|
|
364
|
+
const command = publication?.commandName;
|
|
365
|
+
const packedBinEntries =
|
|
366
|
+
bin !== null && typeof bin === "object" ? Object.entries(bin) : [];
|
|
367
|
+
const sourceBinEntries =
|
|
368
|
+
sourceBin !== null && typeof sourceBin === "object"
|
|
369
|
+
? Object.entries(sourceBin as Record<string, unknown>)
|
|
370
|
+
: [];
|
|
371
|
+
const packedRepository = repositoryObject(packed?.repository);
|
|
372
|
+
const sourceRepository = repositoryObject(manifest.repository);
|
|
373
|
+
const packedBinTarget = packedBinEntries[0]?.[1];
|
|
374
|
+
const binFile = receipt.files.find((file) => file.path === receipt.bin.path);
|
|
375
|
+
const posixExecutableChecked = process.platform !== "win32";
|
|
376
|
+
if (
|
|
377
|
+
packed.name !== publication.packageName ||
|
|
378
|
+
packed.version !== publication.version ||
|
|
379
|
+
manifest.name !== publication.packageName ||
|
|
380
|
+
manifest.version !== publication.version ||
|
|
381
|
+
packedBinEntries.length !== 1 ||
|
|
382
|
+
sourceBinEntries.length !== 1 ||
|
|
383
|
+
packedBinEntries[0]![0] !== command ||
|
|
384
|
+
sourceBinEntries[0]![0] !== command ||
|
|
385
|
+
typeof packedBinEntries[0]![1] !== "string" ||
|
|
386
|
+
packedBinEntries[0]![1] !== sourceBinEntries[0]![1] ||
|
|
387
|
+
packedBinTarget !== "./dist/cli.js" ||
|
|
388
|
+
!/^\d{4}-\d{2}-\d{2}$/u.test(publication.releaseDate) ||
|
|
389
|
+
publication.releaseNotes.trim().length === 0 ||
|
|
390
|
+
receipt.files.length === 0 ||
|
|
391
|
+
!hasCanonicalReceiptFiles(receipt.files) ||
|
|
392
|
+
receipt.bin.path !== "package/dist/cli.js" ||
|
|
393
|
+
receipt.bin.shebang !== "#!/usr/bin/env node" ||
|
|
394
|
+
binFile === undefined ||
|
|
395
|
+
receipt.bin.mode !== binFile.mode ||
|
|
396
|
+
receipt.bin.posixExecutableChecked !== posixExecutableChecked ||
|
|
397
|
+
(posixExecutableChecked && (receipt.bin.mode & 0o111) === 0) ||
|
|
398
|
+
!hasStableSmokeEvidence(receipt) ||
|
|
399
|
+
packedRepository?.type !== "git" ||
|
|
400
|
+
packedRepository.url !== publication.repository ||
|
|
401
|
+
packedRepository.directory !== options.packagePath ||
|
|
402
|
+
sourceRepository?.type !== "git" ||
|
|
403
|
+
sourceRepository.url !== publication.repository ||
|
|
404
|
+
sourceRepository.directory !== options.packagePath ||
|
|
405
|
+
!hasStableSemverShape(publication.version)
|
|
406
|
+
) {
|
|
407
|
+
fail(
|
|
408
|
+
"identity-invalid",
|
|
409
|
+
"receipt, packed manifest, and checkout manifest disagree",
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const receiptRepository = canonicalGithubRepository(publication.repository);
|
|
413
|
+
const packedRepositoryName = canonicalGithubRepository(
|
|
414
|
+
packedRepository?.url ?? "",
|
|
415
|
+
);
|
|
416
|
+
const sourceRepositoryName = canonicalGithubRepository(
|
|
417
|
+
sourceRepository?.url ?? "",
|
|
418
|
+
);
|
|
419
|
+
if (
|
|
420
|
+
receiptRepository !== options.githubRepository ||
|
|
421
|
+
packedRepositoryName !== receiptRepository ||
|
|
422
|
+
sourceRepositoryName !== receiptRepository
|
|
423
|
+
) {
|
|
424
|
+
fail(
|
|
425
|
+
"identity-invalid",
|
|
426
|
+
"publication repository is not the current GitHub repository",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export async function acceptDownloadedPublicationArtifact(options: {
|
|
432
|
+
readonly repositoryRoot: string;
|
|
433
|
+
readonly packagePath: string;
|
|
434
|
+
readonly artifactDirectory: string;
|
|
435
|
+
readonly githubRepository: unknown;
|
|
436
|
+
}): Promise<AcceptedPublicationArtifact> {
|
|
437
|
+
const artifact = await readArtifact(options.artifactDirectory);
|
|
438
|
+
if (
|
|
439
|
+
typeof options.githubRepository !== "string" ||
|
|
440
|
+
options.githubRepository.length === 0
|
|
441
|
+
) {
|
|
442
|
+
fail("identity-invalid", "expected string");
|
|
443
|
+
}
|
|
444
|
+
await assertArtifactIdentity({
|
|
445
|
+
...options,
|
|
446
|
+
githubRepository: options.githubRepository,
|
|
447
|
+
receipt: artifact.receipt,
|
|
448
|
+
});
|
|
449
|
+
return artifact;
|
|
450
|
+
}
|