@stll/oxlint-config 0.12.2 → 0.15.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/README.md +261 -1
- package/dist/cloud-setup-cli.cjs +6 -4
- package/dist/cloud-setup-cli.cjs.map +1 -1
- package/dist/cloud-setup-cli.mjs +3 -1
- package/dist/cloud-setup-cli.mjs.map +1 -1
- package/dist/compiler-packages.cjs +28 -0
- package/dist/compiler-packages.cjs.map +1 -0
- package/dist/compiler-packages.mjs +17 -0
- package/dist/compiler-packages.mjs.map +1 -0
- package/dist/consumer-compat-cli.cjs +1091 -0
- package/dist/consumer-compat-cli.cjs.map +1 -0
- package/dist/consumer-compat-cli.d.cts +1 -0
- package/dist/consumer-compat-cli.d.mts +1 -0
- package/dist/consumer-compat-cli.mjs +1089 -0
- package/dist/consumer-compat-cli.mjs.map +1 -0
- package/dist/index.cjs +50 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -41
- package/dist/index.d.mts +46 -41
- package/dist/index.mjs +50 -41
- package/dist/index.mjs.map +1 -1
- package/dist/publish-build-target-nuxt-helper.cjs +71 -0
- package/dist/publish-build-target-nuxt-helper.cjs.map +1 -0
- package/dist/publish-build-target-nuxt-helper.d.cts +10 -0
- package/dist/publish-build-target-nuxt-helper.d.mts +10 -0
- package/dist/publish-build-target-nuxt-helper.mjs +68 -0
- package/dist/publish-build-target-nuxt-helper.mjs.map +1 -0
- package/dist/publish-build-target-vite-dts-helper.cjs +3 -0
- package/dist/publish-build-target-vite-dts-helper.d.cts +24 -0
- package/dist/publish-build-target-vite-dts-helper.d.mts +24 -0
- package/dist/publish-build-target-vite-dts-helper.mjs +2 -0
- package/dist/publish-build-target-vite-dts-helper2.cjs +372 -0
- package/dist/publish-build-target-vite-dts-helper2.cjs.map +1 -0
- package/dist/publish-build-target-vite-dts-helper2.mjs +359 -0
- package/dist/publish-build-target-vite-dts-helper2.mjs.map +1 -0
- package/dist/publish-contract-cli.cjs +983 -0
- package/dist/publish-contract-cli.cjs.map +1 -0
- package/dist/publish-contract-cli.d.cts +1 -0
- package/dist/publish-contract-cli.d.mts +1 -0
- package/dist/publish-contract-cli.mjs +981 -0
- package/dist/publish-contract-cli.mjs.map +1 -0
- package/dist/publish-contract.cjs +431 -0
- package/dist/publish-contract.cjs.map +1 -0
- package/dist/publish-contract.mjs +396 -0
- package/dist/publish-contract.mjs.map +1 -0
- package/dist/rolldown-runtime.cjs +28 -0
- package/dist/toolchain-changed-cli.cjs +34 -0
- package/dist/toolchain-changed-cli.cjs.map +1 -0
- package/dist/toolchain-changed-cli.d.cts +1 -0
- package/dist/toolchain-changed-cli.d.mts +1 -0
- package/dist/toolchain-changed-cli.mjs +34 -0
- package/dist/toolchain-changed-cli.mjs.map +1 -0
- package/dist/toolchain-changed.cjs +1470 -0
- package/dist/toolchain-changed.cjs.map +1 -0
- package/dist/toolchain-changed.mjs +1463 -0
- package/dist/toolchain-changed.mjs.map +1 -0
- package/dist/toolchain-check-cli.cjs +2 -1
- package/dist/toolchain-check-cli.cjs.map +1 -1
- package/dist/toolchain-check-cli.mjs +2 -1
- package/dist/toolchain-check-cli.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +360 -1161
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +338 -1100
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +926 -0
- package/dist/toolchain-inputs.cjs.map +1 -0
- package/dist/toolchain-inputs.mjs +816 -0
- package/dist/toolchain-inputs.mjs.map +1 -0
- package/dist/toolchain-schema.cjs +702 -0
- package/dist/toolchain-schema.cjs.map +1 -0
- package/dist/toolchain-schema.mjs +580 -0
- package/dist/toolchain-schema.mjs.map +1 -0
- package/dist/typecheck-parity-cli.cjs +38 -898
- package/dist/typecheck-parity-cli.cjs.map +1 -1
- package/dist/typecheck-parity-cli.mjs +39 -899
- package/dist/typecheck-parity-cli.mjs.map +1 -1
- package/dist/typecheck-parity.cjs +905 -0
- package/dist/typecheck-parity.cjs.map +1 -0
- package/dist/typecheck-parity.mjs +894 -0
- package/dist/typecheck-parity.mjs.map +1 -0
- package/dist/typecheck-probe-cli.cjs +176 -0
- package/dist/typecheck-probe-cli.cjs.map +1 -0
- package/dist/typecheck-probe-cli.d.cts +1 -0
- package/dist/typecheck-probe-cli.d.mts +1 -0
- package/dist/typecheck-probe-cli.mjs +174 -0
- package/dist/typecheck-probe-cli.mjs.map +1 -0
- package/package.json +43 -5
- package/toolchain.json +9 -1
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_rolldown_runtime = require("./rolldown-runtime.cjs");
|
|
4
|
+
const require_toolchain_schema = require("./toolchain-schema.cjs");
|
|
5
|
+
const require_publish_contract = require("./publish-contract.cjs");
|
|
6
|
+
let node_child_process = require("node:child_process");
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
9
|
+
let semver = require("semver");
|
|
10
|
+
let yaml = require("yaml");
|
|
11
|
+
let node_util = require("node:util");
|
|
12
|
+
let node_fs_promises = require("node:fs/promises");
|
|
13
|
+
let node_module = require("node:module");
|
|
14
|
+
let node_os = require("node:os");
|
|
15
|
+
let acorn = require("acorn");
|
|
16
|
+
let node_crypto = require("node:crypto");
|
|
17
|
+
//#region src/consumer-build-approvals.ts
|
|
18
|
+
const consumerBuildApprovals = async ({ manifest, lock, manager, directory }) => {
|
|
19
|
+
if (!require_toolchain_schema.consumerRecord(lock)) throw new Error("invalid consumer dependency lock");
|
|
20
|
+
const approvals = {};
|
|
21
|
+
const rebuildTargets = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const field of [
|
|
23
|
+
"dependencies",
|
|
24
|
+
"devDependencies",
|
|
25
|
+
"optionalDependencies"
|
|
26
|
+
]) {
|
|
27
|
+
const entries = manifest[field];
|
|
28
|
+
if (entries === void 0) continue;
|
|
29
|
+
if (!require_toolchain_schema.consumerRecord(entries)) throw new Error(`invalid fixture ${field}`);
|
|
30
|
+
for (const key of Object.keys(entries)) {
|
|
31
|
+
const packages = lock["packages"];
|
|
32
|
+
const importers = lock["importers"];
|
|
33
|
+
const importer = require_toolchain_schema.consumerRecord(importers) ? importers["."] : void 0;
|
|
34
|
+
const dependencies = require_toolchain_schema.consumerRecord(importer) ? importer[field] : void 0;
|
|
35
|
+
const dependencyTable = manager === "npm" ? packages : dependencies;
|
|
36
|
+
const installedKey = manager === "npm" ? `node_modules/${key}` : key;
|
|
37
|
+
const dependency = require_toolchain_schema.consumerRecord(dependencyTable) ? dependencyTable[installedKey] : void 0;
|
|
38
|
+
if (!require_toolchain_schema.consumerRecord(dependency)) {
|
|
39
|
+
if (field === "optionalDependencies") continue;
|
|
40
|
+
throw new Error(`missing resolved fixture dependency identity: ${key}`);
|
|
41
|
+
}
|
|
42
|
+
const installedDirectory = node_path.default.join(directory, "node_modules", key);
|
|
43
|
+
if (field === "optionalDependencies") try {
|
|
44
|
+
await (0, node_fs_promises.lstat)(installedDirectory);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
const installed = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.join(installedDirectory, "package.json"), "utf8"));
|
|
50
|
+
if (!require_toolchain_schema.consumerRecord(installed) || typeof installed["name"] !== "string" || installed["name"] === "" || typeof installed["version"] !== "string" || (0, semver.valid)(installed["version"]) === null) throw new Error(`invalid installed fixture dependency identity: ${key}`);
|
|
51
|
+
const identity = `${installed["name"]}@${installed["version"]}`;
|
|
52
|
+
rebuildTargets.add(await (0, node_fs_promises.realpath)(installedDirectory));
|
|
53
|
+
const resolved = dependency["resolved"];
|
|
54
|
+
if (manager === "npm" && typeof resolved === "string" && !resolved.startsWith("https://registry.npmjs.org/")) approvals[resolved.startsWith("file:") ? resolved : `file:${resolved}`] = true;
|
|
55
|
+
else {
|
|
56
|
+
const version = dependency["version"];
|
|
57
|
+
const source = manager === "pnpm" && typeof version === "string" && version.startsWith("file:") ? version.replace(/\([^)]*\)/g, "") : void 0;
|
|
58
|
+
approvals[source === void 0 ? identity : `${installed["name"]}@${source}`] = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
approvals,
|
|
64
|
+
rebuildTargets: [...rebuildTargets]
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/consumer-installed-bins.ts
|
|
69
|
+
const consumerReservedToolBins = [
|
|
70
|
+
"node",
|
|
71
|
+
"npm",
|
|
72
|
+
"npx",
|
|
73
|
+
"pnpm"
|
|
74
|
+
];
|
|
75
|
+
const missing = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
76
|
+
/** Dependency lifecycle PATHs include nested and virtual-store node_modules bins. */
|
|
77
|
+
const assertConsumerInstalledToolBins = async (directory) => {
|
|
78
|
+
const root = await (0, node_fs_promises.realpath)(directory);
|
|
79
|
+
const visited = /* @__PURE__ */ new Set();
|
|
80
|
+
const assertContained = (canonical) => {
|
|
81
|
+
const relative = node_path.default.relative(root, canonical);
|
|
82
|
+
if (relative === ".." || relative.startsWith(`..${node_path.default.sep}`) || node_path.default.isAbsolute(relative)) throw new Error("installed consumer dependency path must stay within the fixture");
|
|
83
|
+
};
|
|
84
|
+
const visit = async (location) => {
|
|
85
|
+
const canonical = await (0, node_fs_promises.realpath)(location);
|
|
86
|
+
assertContained(canonical);
|
|
87
|
+
if (node_path.default.basename(location) === ".bin" && node_path.default.basename(node_path.default.dirname(location)) === "node_modules") for (const name of consumerReservedToolBins) {
|
|
88
|
+
try {
|
|
89
|
+
await (0, node_fs_promises.lstat)(node_path.default.join(location, name));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (missing(error)) continue;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`installed consumer binary ${name} conflicts with the pinned tool before lifecycle scripts`);
|
|
95
|
+
}
|
|
96
|
+
if (visited.has(canonical)) return;
|
|
97
|
+
visited.add(canonical);
|
|
98
|
+
for (const entry of await (0, node_fs_promises.readdir)(canonical, { withFileTypes: true })) if (entry.isDirectory()) await visit(node_path.default.join(canonical, entry.name));
|
|
99
|
+
else if (entry.isSymbolicLink()) {
|
|
100
|
+
const target = await (0, node_fs_promises.realpath)(node_path.default.join(canonical, entry.name));
|
|
101
|
+
assertContained(target);
|
|
102
|
+
if ((await (0, node_fs_promises.lstat)(target)).isDirectory()) await visit(node_path.default.join(canonical, entry.name));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const modules = node_path.default.join(root, "node_modules");
|
|
106
|
+
try {
|
|
107
|
+
await (0, node_fs_promises.lstat)(modules);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (missing(error)) return;
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
await visit(modules);
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/consumer-react.ts
|
|
116
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
117
|
+
const publishedVersions = ({ versions, range }) => {
|
|
118
|
+
if (!(0, semver.validRange)(range)) throw new Error(`invalid consumer peer range: ${range}`);
|
|
119
|
+
const candidates = Object.keys(versions).filter((version) => (0, semver.valid)(version) !== null && (0, semver.satisfies)(version, range)).sort(semver.compare);
|
|
120
|
+
if (candidates.length === 0) throw new Error(`no published consumer version satisfies ${range}`);
|
|
121
|
+
return candidates;
|
|
122
|
+
};
|
|
123
|
+
/** Select the oldest published React with a compatible allowed renderer when required. */
|
|
124
|
+
const selectConsumerReactVersions = ({ reactRange, reactVersions, dom }) => {
|
|
125
|
+
const reactCandidates = publishedVersions({
|
|
126
|
+
versions: reactVersions,
|
|
127
|
+
range: reactRange
|
|
128
|
+
});
|
|
129
|
+
if (dom === void 0) {
|
|
130
|
+
const react = reactCandidates.at(0);
|
|
131
|
+
if (react === void 0) throw new Error("Missing React version candidate");
|
|
132
|
+
return { react };
|
|
133
|
+
}
|
|
134
|
+
const domRange = dom.range ?? "*";
|
|
135
|
+
const domCandidates = publishedVersions({
|
|
136
|
+
versions: dom.versions,
|
|
137
|
+
range: domRange
|
|
138
|
+
});
|
|
139
|
+
for (const react of reactCandidates) {
|
|
140
|
+
const renderer = domCandidates.find((version) => {
|
|
141
|
+
const metadata = dom.versions[version];
|
|
142
|
+
if (!record$1(metadata) || !record$1(metadata["peerDependencies"])) return false;
|
|
143
|
+
const range = metadata["peerDependencies"]["react"];
|
|
144
|
+
return typeof range === "string" && (0, semver.validRange)(range) !== null && (0, semver.satisfies)(react, range);
|
|
145
|
+
});
|
|
146
|
+
if (renderer !== void 0) return {
|
|
147
|
+
react,
|
|
148
|
+
"react-dom": renderer
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
throw new Error(`no published React/ReactDOM pair satisfies React ${reactRange} and ReactDOM ${domRange}`);
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/publish-artifact-syntax.ts
|
|
155
|
+
const command = (0, node_util.promisify)(node_child_process.execFile);
|
|
156
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
157
|
+
const javascript = /\.(?:js|mjs|cjs)$/i;
|
|
158
|
+
const syntaxYears = new Map(Object.entries({
|
|
159
|
+
es5: 5,
|
|
160
|
+
es2015: 2015,
|
|
161
|
+
es2016: 2016,
|
|
162
|
+
es2017: 2017,
|
|
163
|
+
es2018: 2018,
|
|
164
|
+
es2019: 2019,
|
|
165
|
+
es2020: 2020,
|
|
166
|
+
es2021: 2021,
|
|
167
|
+
es2022: 2022,
|
|
168
|
+
es2023: 2023,
|
|
169
|
+
es2024: 2024,
|
|
170
|
+
es2025: 2025
|
|
171
|
+
}));
|
|
172
|
+
/** Explicit ECMAScript targets bound syntax; unbounded targets use native Node. */
|
|
173
|
+
const artifactEcmaVersion = ({ target, node }) => {
|
|
174
|
+
if (target.type === "types-only") return "latest";
|
|
175
|
+
if (target.targets.length === 0) throw new Error("Packed JavaScript requires a declared target");
|
|
176
|
+
let version = "latest";
|
|
177
|
+
for (const identifier of target.targets) {
|
|
178
|
+
if (identifier === "esnext") continue;
|
|
179
|
+
const year = syntaxYears.get(identifier);
|
|
180
|
+
if (year !== void 0) {
|
|
181
|
+
if (version === "latest" || year < version) version = year;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (require_publish_contract.publishedNodeTargetSupportsConsumer({
|
|
185
|
+
target: identifier,
|
|
186
|
+
node
|
|
187
|
+
})) continue;
|
|
188
|
+
throw new Error(`Unsupported packed JavaScript syntax target ${identifier}; use an explicit ECMAScript year or a supported Node target`);
|
|
189
|
+
}
|
|
190
|
+
return version;
|
|
191
|
+
};
|
|
192
|
+
const packedBinFiles = (files) => {
|
|
193
|
+
const result = /* @__PURE__ */ new Set();
|
|
194
|
+
for (const [file, source] of files) {
|
|
195
|
+
if (node_path.default.posix.basename(file) !== "package.json") continue;
|
|
196
|
+
const manifest = JSON.parse(source);
|
|
197
|
+
if (!record(manifest)) throw new Error(`Invalid packed manifest: ${file}`);
|
|
198
|
+
const bin = manifest["bin"];
|
|
199
|
+
if (bin === void 0) continue;
|
|
200
|
+
let entries;
|
|
201
|
+
if (typeof bin === "string") entries = [bin];
|
|
202
|
+
else if (record(bin)) entries = Object.values(bin);
|
|
203
|
+
if (entries === void 0) throw new Error(`Unsupported packed bin declaration: ${file}`);
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (typeof entry !== "string" || entry === "" || node_path.default.posix.isAbsolute(entry) || entry.includes("\\") || entry.split("/").includes("..")) throw new Error(`Unsafe packed bin declaration: ${file}`);
|
|
206
|
+
result.add(node_path.default.posix.join(node_path.default.posix.dirname(file), entry));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
};
|
|
211
|
+
const syntaxFiles = (files) => {
|
|
212
|
+
const bins = packedBinFiles(files);
|
|
213
|
+
for (const file of bins) {
|
|
214
|
+
const source = files.get(file);
|
|
215
|
+
if (source === void 0) throw new Error(`Missing packed executable: ${file}`);
|
|
216
|
+
if (!javascript.test(file) && !/^#!(?:[^\n]*\/node|\/usr\/bin\/env(?: -S)? node)(?:[ \r\n]|$)/.test(source)) throw new Error(`Unsupported packed executable language: ${file}`);
|
|
217
|
+
}
|
|
218
|
+
return [...new Set([...files.keys()].filter((file) => javascript.test(file)).concat([...bins]))];
|
|
219
|
+
};
|
|
220
|
+
const packageMode = (files, file) => {
|
|
221
|
+
let directory = node_path.default.posix.dirname(file);
|
|
222
|
+
while (directory !== ".") {
|
|
223
|
+
const source = files.get(`${directory}/package.json`);
|
|
224
|
+
if (source !== void 0) {
|
|
225
|
+
const manifest = JSON.parse(source);
|
|
226
|
+
if (!record(manifest)) throw new Error(`Invalid packed manifest: ${directory}`);
|
|
227
|
+
if (manifest["type"] === "module") return "module";
|
|
228
|
+
if (manifest["type"] === "commonjs") return "script";
|
|
229
|
+
if (manifest["type"] !== void 0) throw new Error(`Invalid packed package type: ${directory}`);
|
|
230
|
+
return "detect";
|
|
231
|
+
}
|
|
232
|
+
directory = node_path.default.posix.dirname(directory);
|
|
233
|
+
}
|
|
234
|
+
return "detect";
|
|
235
|
+
};
|
|
236
|
+
const parseArtifactSource = (source, options) => {
|
|
237
|
+
const script = options.sourceType === "script";
|
|
238
|
+
const content = script ? source.replace(/^\uFEFF/, "").replace(/^#![^\r\n]*/, "") : source;
|
|
239
|
+
try {
|
|
240
|
+
return (0, acorn.parse)(script ? (0, node_module.wrap)(content) : content, options);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (!script || !(error instanceof SyntaxError) || !record(error) || typeof error["pos"] !== "number") throw error;
|
|
243
|
+
const prefixLength = (0, node_module.wrap)("\0").indexOf("\0");
|
|
244
|
+
const position = Math.max(0, Math.min(source.length, error["pos"] - prefixLength + source.length - content.length));
|
|
245
|
+
const lines = source.slice(0, position).split(/\r\n|[\n\r\u2028\u2029]/);
|
|
246
|
+
const location = `(${lines.length}:${lines.at(-1)?.length ?? 0})`;
|
|
247
|
+
throw new SyntaxError(error.message.replace(/\(\d+:\d+\)$/, location));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
/** Enumerate all chunks and bins, including JavaScript outside declared exports. */
|
|
251
|
+
const assertPackedArtifactSyntax = ({ files, target, node }) => {
|
|
252
|
+
const ecmaVersion = artifactEcmaVersion({
|
|
253
|
+
target,
|
|
254
|
+
node
|
|
255
|
+
});
|
|
256
|
+
const selected = syntaxFiles(files);
|
|
257
|
+
if (target.type === "types-only" && selected.length > 0) throw new Error(`Types-only artifact contains JavaScript: ${selected[0]}`);
|
|
258
|
+
for (const file of selected) {
|
|
259
|
+
const source = files.get(file);
|
|
260
|
+
if (source === void 0) throw new Error(`Missing packed JavaScript: ${file}`);
|
|
261
|
+
let mode = packageMode(files, file);
|
|
262
|
+
if (/\.mjs$/i.test(file)) mode = "module";
|
|
263
|
+
if (/\.cjs$/i.test(file)) mode = "script";
|
|
264
|
+
const parseMode = (sourceType) => {
|
|
265
|
+
const tree = parseArtifactSource(source, {
|
|
266
|
+
ecmaVersion,
|
|
267
|
+
sourceType,
|
|
268
|
+
allowHashBang: true,
|
|
269
|
+
allowReturnOutsideFunction: sourceType === "script"
|
|
270
|
+
});
|
|
271
|
+
if (ecmaVersion === 5 && tree.body.some((item) => /^(?:Import|Export)/.test(item.type))) throw new Error("Module declarations exceed ES5");
|
|
272
|
+
};
|
|
273
|
+
try {
|
|
274
|
+
if (mode === "detect") try {
|
|
275
|
+
parseMode("script");
|
|
276
|
+
} catch {
|
|
277
|
+
parseMode("module");
|
|
278
|
+
}
|
|
279
|
+
else parseMode(mode);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
throw new Error(`Packed JavaScript ${file} exceeds its declared syntax target: ${String(error)}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const archiveMembers = (members) => {
|
|
286
|
+
const seen = /* @__PURE__ */ new Set();
|
|
287
|
+
for (const file of members) {
|
|
288
|
+
const parts = file.split("/");
|
|
289
|
+
if (!file.startsWith("package/") || file.includes("\\") || file.split("").some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || parts.some((part) => part === ".." || part === ".") || parts.slice(0, -1).some((part) => part === "") || seen.has(file)) throw new Error(`Unsupported packed artifact member: ${file}`);
|
|
290
|
+
seen.add(file);
|
|
291
|
+
}
|
|
292
|
+
return members;
|
|
293
|
+
};
|
|
294
|
+
const checkPackedJavaScriptWithNode = async ({ files, target, node, executable }) => {
|
|
295
|
+
assertPackedArtifactSyntax({
|
|
296
|
+
files,
|
|
297
|
+
target,
|
|
298
|
+
node
|
|
299
|
+
});
|
|
300
|
+
archiveMembers([...files.keys()]);
|
|
301
|
+
const scratch = await (0, node_fs_promises.mkdtemp)(node_path.default.join((0, node_os.tmpdir)(), "stll-artifact-syntax-"));
|
|
302
|
+
try {
|
|
303
|
+
for (const [file, source] of files) {
|
|
304
|
+
const destination = node_path.default.join(scratch, file);
|
|
305
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(destination), { recursive: true });
|
|
306
|
+
await (0, node_fs_promises.writeFile)(destination, source);
|
|
307
|
+
}
|
|
308
|
+
for (const file of syntaxFiles(files)) try {
|
|
309
|
+
await command(executable, ["--check", node_path.default.join(scratch, file)], {
|
|
310
|
+
cwd: scratch,
|
|
311
|
+
env: {
|
|
312
|
+
PATH: "/usr/bin:/bin",
|
|
313
|
+
HOME: scratch
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw new Error(`Packed JavaScript ${file} is incompatible with consumer Node ${node}: ${String(error)}`);
|
|
318
|
+
}
|
|
319
|
+
} finally {
|
|
320
|
+
await (0, node_fs_promises.rm)(scratch, {
|
|
321
|
+
recursive: true,
|
|
322
|
+
force: true
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
/** Read bytes without extracting archive-controlled paths or links. */
|
|
327
|
+
const readPackedSyntaxFiles = async (archive) => {
|
|
328
|
+
const { stdout: listing } = await command("tar", ["-tzf", archive], { maxBuffer: 16 * 1024 * 1024 });
|
|
329
|
+
const files = /* @__PURE__ */ new Map();
|
|
330
|
+
const members = archiveMembers(listing.split("\n").filter((entry) => entry !== ""));
|
|
331
|
+
const read = async (file) => {
|
|
332
|
+
if (!members.includes(file)) throw new Error(`Missing packed syntax input: ${file}`);
|
|
333
|
+
const { stdout: metadata } = await command("tar", [
|
|
334
|
+
"-tvzf",
|
|
335
|
+
archive,
|
|
336
|
+
file
|
|
337
|
+
]);
|
|
338
|
+
if (!metadata.startsWith("-") || metadata.trimEnd().split("\n").length !== 1) throw new Error(`Packed syntax input must be a regular file: ${file}`);
|
|
339
|
+
const { stdout } = await command("tar", [
|
|
340
|
+
"-xOf",
|
|
341
|
+
archive,
|
|
342
|
+
file
|
|
343
|
+
], { maxBuffer: 16 * 1024 * 1024 });
|
|
344
|
+
files.set(file, stdout);
|
|
345
|
+
};
|
|
346
|
+
for (const file of members) if (javascript.test(file) || node_path.default.posix.basename(file) === "package.json") await read(file);
|
|
347
|
+
for (const file of packedBinFiles(files)) if (!files.has(file)) await read(file);
|
|
348
|
+
return files;
|
|
349
|
+
};
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/consumer-compat.ts
|
|
352
|
+
const execute = (executable, args, { cwd, env = process.env }) => new Promise((resolve, reject) => {
|
|
353
|
+
const child = (0, node_child_process.spawn)(executable, args, {
|
|
354
|
+
cwd,
|
|
355
|
+
env,
|
|
356
|
+
stdio: [
|
|
357
|
+
"ignore",
|
|
358
|
+
"pipe",
|
|
359
|
+
"inherit"
|
|
360
|
+
]
|
|
361
|
+
});
|
|
362
|
+
const chunks = [];
|
|
363
|
+
child.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
364
|
+
child.on("error", reject);
|
|
365
|
+
child.on("close", (code, signal) => {
|
|
366
|
+
const output = Buffer.concat(chunks).toString("utf8");
|
|
367
|
+
if (code !== 0) reject(/* @__PURE__ */ new Error(`${executable} failed (${signal ?? String(code)}): ${output.trim()}`));
|
|
368
|
+
else resolve(output);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
const jsonFile = async (file) => {
|
|
372
|
+
const value = JSON.parse(await (0, node_fs_promises.readFile)(file, "utf8"));
|
|
373
|
+
if (!require_toolchain_schema.consumerRecord(value)) throw new Error(`expected JSON object: ${file}`);
|
|
374
|
+
return value;
|
|
375
|
+
};
|
|
376
|
+
const containedDirectory = async (root, relative) => {
|
|
377
|
+
const location = await (0, node_fs_promises.realpath)(node_path.default.resolve(root, relative));
|
|
378
|
+
const difference = node_path.default.relative(root, location);
|
|
379
|
+
if (difference.startsWith(`..${node_path.default.sep}`) || difference === ".." || node_path.default.isAbsolute(difference)) throw new Error(`path leaves repository: ${relative}`);
|
|
380
|
+
if (!(await (0, node_fs_promises.lstat)(location)).isDirectory()) throw new Error(`not a directory: ${relative}`);
|
|
381
|
+
return location;
|
|
382
|
+
};
|
|
383
|
+
const fetchBytes = async (url) => {
|
|
384
|
+
const response = await fetch(url);
|
|
385
|
+
if (!response.ok) throw new Error(`download failed (${response.status}): ${url}`);
|
|
386
|
+
if (new URL(response.url).hostname !== new URL(url).hostname) throw new Error(`download redirected to another host: ${url}`);
|
|
387
|
+
return Buffer.from(await response.arrayBuffer());
|
|
388
|
+
};
|
|
389
|
+
const verifyConsumerNodeArchive = ({ filename, checksums, archive }) => {
|
|
390
|
+
const hashes = checksums.split(/\r?\n/).flatMap((line) => {
|
|
391
|
+
const match = /^([a-f0-9]{64})\s+\*?(\S+)$/.exec(line);
|
|
392
|
+
return match?.[2] === filename && match[1] ? [match[1]] : [];
|
|
393
|
+
});
|
|
394
|
+
if (hashes.length !== 1 || hashes[0] !== (0, node_crypto.createHash)("sha256").update(archive).digest("hex")) throw new Error(`Node archive checksum mismatch: ${filename}`);
|
|
395
|
+
};
|
|
396
|
+
const resolveInstalledManagerBin = async ({ directory, name }) => {
|
|
397
|
+
const bin = (await jsonFile(node_path.default.join(directory, "package.json")))["bin"];
|
|
398
|
+
const selector = require_toolchain_schema.consumerRecord(bin) ? bin[name] : bin;
|
|
399
|
+
if (typeof selector !== "string" || selector.length === 0) throw new Error(`Installed ${name} package must declare its ${name} bin`);
|
|
400
|
+
const root = await (0, node_fs_promises.realpath)(directory);
|
|
401
|
+
const resolved = node_path.default.resolve(root, selector);
|
|
402
|
+
const relative = node_path.default.relative(root, resolved);
|
|
403
|
+
if (relative === ".." || relative.startsWith(`..${node_path.default.sep}`) || node_path.default.isAbsolute(relative)) throw new Error(`Installed ${name} bin leaves its package`);
|
|
404
|
+
const cli = await (0, node_fs_promises.realpath)(resolved);
|
|
405
|
+
const actualRelative = node_path.default.relative(root, cli);
|
|
406
|
+
if (actualRelative === ".." || actualRelative.startsWith(`..${node_path.default.sep}`) || node_path.default.isAbsolute(actualRelative)) throw new Error(`Installed ${name} bin leaves its package`);
|
|
407
|
+
if (!(await (0, node_fs_promises.lstat)(cli)).isFile()) throw new Error(`Installed ${name} bin must be a file`);
|
|
408
|
+
return cli;
|
|
409
|
+
};
|
|
410
|
+
const writeConsumerToolWrappers = async ({ node, npm, pnpm, directory }) => {
|
|
411
|
+
const wrappers = directory;
|
|
412
|
+
await (0, node_fs_promises.mkdir)(wrappers);
|
|
413
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
414
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(wrappers, "node"), `#!/bin/sh\nexec ${quote(node)} "$@"\n`, { mode: 493 });
|
|
415
|
+
for (const [name, cli] of [
|
|
416
|
+
["npm", npm],
|
|
417
|
+
["npx", node_path.default.join(node_path.default.dirname(npm), "npx-cli.js")],
|
|
418
|
+
["pnpm", pnpm]
|
|
419
|
+
]) {
|
|
420
|
+
if (!name || !cli) throw new Error("missing package manager binding");
|
|
421
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(wrappers, name), `#!/bin/sh\nexec ${quote(node)} ${quote(cli)} "$@"\n`, { mode: 493 });
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
node,
|
|
425
|
+
npm,
|
|
426
|
+
pnpm,
|
|
427
|
+
bin: wrappers
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
const provisionConsumerTools = async (scratch, policy) => {
|
|
431
|
+
if (process.platform !== "linux" || process.arch !== "x64" && process.arch !== "arm64") throw new Error("consumer compatibility requires Linux x64 or arm64");
|
|
432
|
+
const filename = `node-v${policy.consumerNode}-linux-${process.arch}.tar.xz`;
|
|
433
|
+
const base = `https://nodejs.org/dist/v${policy.consumerNode}/`;
|
|
434
|
+
const [archive, checksums] = await Promise.all([fetchBytes(`${base}${filename}`), fetchBytes(`${base}SHASUMS256.txt`)]);
|
|
435
|
+
verifyConsumerNodeArchive({
|
|
436
|
+
filename,
|
|
437
|
+
archive,
|
|
438
|
+
checksums: checksums.toString("utf8")
|
|
439
|
+
});
|
|
440
|
+
const archivePath = node_path.default.join(scratch, filename);
|
|
441
|
+
await (0, node_fs_promises.writeFile)(archivePath, archive);
|
|
442
|
+
await execute("tar", [
|
|
443
|
+
"-xJf",
|
|
444
|
+
archivePath,
|
|
445
|
+
"-C",
|
|
446
|
+
scratch,
|
|
447
|
+
"--no-same-owner"
|
|
448
|
+
], { cwd: scratch });
|
|
449
|
+
const installation = node_path.default.join(scratch, filename.slice(0, -7));
|
|
450
|
+
const bin = node_path.default.join(installation, "bin");
|
|
451
|
+
const node = node_path.default.join(bin, "node");
|
|
452
|
+
const home = node_path.default.join(scratch, "tools-home");
|
|
453
|
+
await (0, node_fs_promises.mkdir)(home);
|
|
454
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(home, "npmrc"), "");
|
|
455
|
+
const env = consumerCommandEnvironment({
|
|
456
|
+
tools: { bin },
|
|
457
|
+
directory: scratch,
|
|
458
|
+
home
|
|
459
|
+
});
|
|
460
|
+
const actual = (await execute(node, ["--version"], {
|
|
461
|
+
cwd: scratch,
|
|
462
|
+
env
|
|
463
|
+
})).trim();
|
|
464
|
+
if (actual !== `v${policy.consumerNode}`) throw new Error(`consumer Node version mismatch: ${actual}`);
|
|
465
|
+
const prefix = node_path.default.join(scratch, "tools");
|
|
466
|
+
const npmBootstrap = node_path.default.join(installation, "lib/node_modules/npm/bin/npm-cli.js");
|
|
467
|
+
await execute(node, [
|
|
468
|
+
npmBootstrap,
|
|
469
|
+
"install",
|
|
470
|
+
"--prefix",
|
|
471
|
+
prefix,
|
|
472
|
+
"--ignore-scripts",
|
|
473
|
+
"--no-audit",
|
|
474
|
+
"--no-fund",
|
|
475
|
+
"--package-lock=false",
|
|
476
|
+
`npm@${policy.consumerNpm}`,
|
|
477
|
+
`pnpm@${policy.consumerPnpm}`
|
|
478
|
+
], {
|
|
479
|
+
cwd: scratch,
|
|
480
|
+
env
|
|
481
|
+
});
|
|
482
|
+
const npm = await resolveInstalledManagerBin({
|
|
483
|
+
directory: node_path.default.join(prefix, "node_modules/npm"),
|
|
484
|
+
name: "npm"
|
|
485
|
+
});
|
|
486
|
+
const pnpm = await resolveInstalledManagerBin({
|
|
487
|
+
directory: node_path.default.join(prefix, "node_modules/pnpm"),
|
|
488
|
+
name: "pnpm"
|
|
489
|
+
});
|
|
490
|
+
for (const [cli, expected] of [[npm, policy.consumerNpm], [pnpm, policy.consumerPnpm]]) if (!cli || !expected || (await execute(node, [cli, "--version"], {
|
|
491
|
+
cwd: scratch,
|
|
492
|
+
env
|
|
493
|
+
})).trim() !== expected) throw new Error("consumer package manager version mismatch");
|
|
494
|
+
return writeConsumerToolWrappers({
|
|
495
|
+
node,
|
|
496
|
+
npm,
|
|
497
|
+
pnpm,
|
|
498
|
+
directory: node_path.default.join(scratch, "consumer-bin")
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
const trackedManifests = async (root) => {
|
|
502
|
+
const listed = await execute("git", [
|
|
503
|
+
"ls-files",
|
|
504
|
+
"-z",
|
|
505
|
+
"--",
|
|
506
|
+
"package.json",
|
|
507
|
+
"**/package.json",
|
|
508
|
+
"pnpm-workspace.yaml",
|
|
509
|
+
"**/pnpm-workspace.yaml",
|
|
510
|
+
"publish-contract.json",
|
|
511
|
+
"**/publish-contract.json",
|
|
512
|
+
".github/workflows/*.yml",
|
|
513
|
+
".github/workflows/*.yaml"
|
|
514
|
+
], { cwd: root });
|
|
515
|
+
const files = {};
|
|
516
|
+
for (const file of listed.split("\0").filter(Boolean)) {
|
|
517
|
+
if (file.split("/").some((part) => part === "node_modules" || part === "vendor")) continue;
|
|
518
|
+
const location = await (0, node_fs_promises.realpath)(node_path.default.join(root, file));
|
|
519
|
+
const relative = node_path.default.relative(root, location);
|
|
520
|
+
if (relative.startsWith(`..${node_path.default.sep}`) || relative === ".." || node_path.default.isAbsolute(relative)) throw new Error(`tracked manifest leaves repository: ${file}`);
|
|
521
|
+
files[file] = await (0, node_fs_promises.readFile)(location, "utf8");
|
|
522
|
+
}
|
|
523
|
+
return files;
|
|
524
|
+
};
|
|
525
|
+
const packFilename = async (directory) => {
|
|
526
|
+
const files = (await (0, node_fs_promises.readdir)(directory)).filter((file) => file.endsWith(".tgz"));
|
|
527
|
+
if (files.length !== 1) throw new Error(`expected exactly one package tarball: ${directory}`);
|
|
528
|
+
const filename = files.at(0);
|
|
529
|
+
if (!filename) throw new Error("missing package tarball");
|
|
530
|
+
return node_path.default.join(directory, filename);
|
|
531
|
+
};
|
|
532
|
+
const copyWithoutDependencies = async (source, destination) => {
|
|
533
|
+
await (0, node_fs_promises.cp)(source, destination, {
|
|
534
|
+
recursive: true,
|
|
535
|
+
filter: async (file) => {
|
|
536
|
+
if ([
|
|
537
|
+
"node_modules",
|
|
538
|
+
".git",
|
|
539
|
+
"pnpm-workspace.yaml"
|
|
540
|
+
].includes(node_path.default.basename(file))) return false;
|
|
541
|
+
if ((await (0, node_fs_promises.lstat)(file)).isSymbolicLink()) throw new Error(`consumer staging does not accept symlinks: ${file}`);
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
};
|
|
546
|
+
const stageConsumerWorkspace = async ({ files, root, staging, packages }) => {
|
|
547
|
+
const directories = /* @__PURE__ */ new Map();
|
|
548
|
+
const paths = require_toolchain_schema.consumerStagingPaths(packages);
|
|
549
|
+
const ordered = [...packages.values()].sort((left, right) => left.directory.length - right.directory.length);
|
|
550
|
+
for (const pkg of ordered) {
|
|
551
|
+
const relative = paths.get(pkg.name);
|
|
552
|
+
if (!relative) throw new Error(`missing staging path: ${pkg.name}`);
|
|
553
|
+
const destination = node_path.default.join(staging, relative);
|
|
554
|
+
if (pkg.directory !== "." || pkg.manifest["private"] !== true) await copyWithoutDependencies(await containedDirectory(root, pkg.directory), destination);
|
|
555
|
+
directories.set(pkg.name, destination);
|
|
556
|
+
}
|
|
557
|
+
const rootManifest = require_toolchain_schema.consumerPackRootManifest(packages, files);
|
|
558
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(staging, "package.json"), JSON.stringify(rootManifest));
|
|
559
|
+
const rootSource = files["pnpm-workspace.yaml"];
|
|
560
|
+
const rootCatalogs = rootSource === void 0 ? require_toolchain_schema.bunCatalogState(rootManifest) : require_toolchain_schema.catalogState(require_toolchain_schema.pnpmWorkspaceState(rootSource, "pnpm-workspace.yaml"), "pnpm-workspace.yaml");
|
|
561
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(staging, "pnpm-workspace.yaml"), (0, yaml.stringify)({
|
|
562
|
+
packages: [...directories.values()].map((directory) => node_path.default.relative(staging, directory).split(node_path.default.sep).join("/") || "."),
|
|
563
|
+
...rootCatalogs
|
|
564
|
+
}));
|
|
565
|
+
for (const [directory, manifest] of require_toolchain_schema.discoverConsumerManifests(files)) {
|
|
566
|
+
if (directory === ".") continue;
|
|
567
|
+
const file = `${directory}/pnpm-workspace.yaml`;
|
|
568
|
+
const source = files[file];
|
|
569
|
+
let workspace;
|
|
570
|
+
if (source !== void 0) workspace = require_toolchain_schema.pnpmWorkspaceState(source, file);
|
|
571
|
+
else {
|
|
572
|
+
const catalogs = require_toolchain_schema.bunCatalogState(manifest);
|
|
573
|
+
if (Object.keys(catalogs).length === 0) continue;
|
|
574
|
+
const declaration = manifest["workspaces"];
|
|
575
|
+
const patterns = require_toolchain_schema.consumerRecord(declaration) ? declaration["packages"] : declaration;
|
|
576
|
+
if (!Array.isArray(patterns) || !patterns.every((entry) => typeof entry === "string")) throw new Error(`catalog owner requires declared workspace packages: ${directory}`);
|
|
577
|
+
workspace = {
|
|
578
|
+
packages: patterns,
|
|
579
|
+
...catalogs
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const destination = node_path.default.join(staging, directory);
|
|
583
|
+
await (0, node_fs_promises.mkdir)(destination, { recursive: true });
|
|
584
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(destination, "package.json"), JSON.stringify(manifest));
|
|
585
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(destination, "pnpm-workspace.yaml"), (0, yaml.stringify)(workspace));
|
|
586
|
+
}
|
|
587
|
+
return directories;
|
|
588
|
+
};
|
|
589
|
+
const assertConsumerFixtureFiles = async (directory) => {
|
|
590
|
+
const visit = async (location) => {
|
|
591
|
+
for (const entry of await (0, node_fs_promises.readdir)(location, { withFileTypes: true })) {
|
|
592
|
+
if (require_toolchain_schema.consumerDependencyConfigFiles.some((name) => name === entry.name)) throw new Error(`consumer fixture must not supply dependency-manager configuration: ${entry.name}`);
|
|
593
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") await visit(node_path.default.join(location, entry.name));
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
await visit(directory);
|
|
597
|
+
require_toolchain_schema.assertConsumerFixtureManifest(await jsonFile(node_path.default.join(directory, "package.json")));
|
|
598
|
+
};
|
|
599
|
+
const provisionConsumerReleasePacker = async ({ tools, scratch, release }) => {
|
|
600
|
+
const prefix = node_path.default.join(scratch, "release-packer");
|
|
601
|
+
const home = node_path.default.join(scratch, "release-packer-home");
|
|
602
|
+
await (0, node_fs_promises.mkdir)(home);
|
|
603
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(home, "npmrc"), "");
|
|
604
|
+
const env = consumerCommandEnvironment({
|
|
605
|
+
tools,
|
|
606
|
+
directory: scratch,
|
|
607
|
+
home
|
|
608
|
+
});
|
|
609
|
+
await execute(tools.node, [
|
|
610
|
+
tools.npm,
|
|
611
|
+
"install",
|
|
612
|
+
"--prefix",
|
|
613
|
+
prefix,
|
|
614
|
+
"--ignore-scripts",
|
|
615
|
+
"--no-audit",
|
|
616
|
+
"--no-fund",
|
|
617
|
+
"--package-lock=false",
|
|
618
|
+
`${release.manager}@${release.version}`
|
|
619
|
+
], {
|
|
620
|
+
cwd: scratch,
|
|
621
|
+
env
|
|
622
|
+
});
|
|
623
|
+
const cli = await resolveInstalledManagerBin({
|
|
624
|
+
directory: node_path.default.join(prefix, "node_modules", release.manager),
|
|
625
|
+
name: release.manager
|
|
626
|
+
});
|
|
627
|
+
const actual = (await execute(tools.node, [cli, "--version"], {
|
|
628
|
+
cwd: scratch,
|
|
629
|
+
env
|
|
630
|
+
})).trim();
|
|
631
|
+
if (actual !== release.version) throw new Error(`release packer version mismatch: expected ${release.version}, got ${actual}`);
|
|
632
|
+
return cli;
|
|
633
|
+
};
|
|
634
|
+
const packConsumerArtifacts = async ({ files, root, scratch, tools, packages, workspacePackages }) => {
|
|
635
|
+
const release = require_publish_contract.resolveConsumerReleasePack(files);
|
|
636
|
+
const releaseCli = await provisionConsumerReleasePacker({
|
|
637
|
+
tools,
|
|
638
|
+
scratch,
|
|
639
|
+
release
|
|
640
|
+
});
|
|
641
|
+
const artifacts = /* @__PURE__ */ new Map();
|
|
642
|
+
const staging = node_path.default.join(scratch, "pack-workspace");
|
|
643
|
+
await (0, node_fs_promises.mkdir)(staging);
|
|
644
|
+
const stagedDirectories = await stageConsumerWorkspace({
|
|
645
|
+
files,
|
|
646
|
+
root,
|
|
647
|
+
staging,
|
|
648
|
+
packages: workspacePackages
|
|
649
|
+
});
|
|
650
|
+
const packHome = node_path.default.join(scratch, "pack-home");
|
|
651
|
+
await (0, node_fs_promises.mkdir)(packHome);
|
|
652
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(packHome, "npmrc"), "");
|
|
653
|
+
for (const [index, pkg] of [...packages.values()].entries()) {
|
|
654
|
+
const packed = node_path.default.join(scratch, `packed-${index}`);
|
|
655
|
+
await (0, node_fs_promises.mkdir)(packed);
|
|
656
|
+
const directory = stagedDirectories.get(pkg.name);
|
|
657
|
+
if (!directory) throw new Error(`missing pack workspace member: ${pkg.name}`);
|
|
658
|
+
await execute(tools.node, [releaseCli, ...require_publish_contract.consumerReleasePackArguments({
|
|
659
|
+
packer: release,
|
|
660
|
+
directory: packed
|
|
661
|
+
})], {
|
|
662
|
+
cwd: directory,
|
|
663
|
+
env: consumerCommandEnvironment({
|
|
664
|
+
tools,
|
|
665
|
+
directory,
|
|
666
|
+
home: packHome
|
|
667
|
+
})
|
|
668
|
+
});
|
|
669
|
+
const artifact = await packFilename(packed);
|
|
670
|
+
artifacts.set(pkg.name, artifact);
|
|
671
|
+
}
|
|
672
|
+
return artifacts;
|
|
673
|
+
};
|
|
674
|
+
const registryMetadata = async (name) => {
|
|
675
|
+
const value = JSON.parse((await fetchBytes(`https://registry.npmjs.org/${encodeURIComponent(name)}`)).toString("utf8"));
|
|
676
|
+
if (!require_toolchain_schema.consumerRecord(value) || !require_toolchain_schema.consumerRecord(value["versions"])) throw new Error(`invalid registry response: ${name}`);
|
|
677
|
+
return value["versions"];
|
|
678
|
+
};
|
|
679
|
+
const consumerCommandEnvironment = ({ tools, directory, home, environment = process.env }) => {
|
|
680
|
+
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !/^(?:npm_config_|pnpm_|yarn_|corepack_)/i.test(key) && key !== "NODE_PATH" && key !== "NODE_OPTIONS"));
|
|
681
|
+
Object.assign(env, {
|
|
682
|
+
PATH: `${tools.bin}${node_path.default.delimiter}${node_path.default.join(directory, "node_modules/.bin")}${node_path.default.delimiter}/usr/bin${node_path.default.delimiter}/bin`,
|
|
683
|
+
HOME: home,
|
|
684
|
+
npm_config_cache: node_path.default.join(home, "npm-cache"),
|
|
685
|
+
npm_config_userconfig: node_path.default.join(home, "npmrc"),
|
|
686
|
+
npm_config_registry: "https://registry.npmjs.org/",
|
|
687
|
+
pnpm_config_pm_on_fail: "ignore",
|
|
688
|
+
npm_config_manage_package_manager_versions: "false",
|
|
689
|
+
COREPACK_ENABLE_PROJECT_SPEC: "0",
|
|
690
|
+
COREPACK_ENABLE_STRICT: "0",
|
|
691
|
+
COREPACK_ENABLE_AUTO_PIN: "0",
|
|
692
|
+
COREPACK_ENABLE_NETWORK: "0",
|
|
693
|
+
COREPACK_ENV_FILE: "0",
|
|
694
|
+
CI: "true",
|
|
695
|
+
NODE_ENV: "development",
|
|
696
|
+
XDG_CONFIG_HOME: node_path.default.join(home, "config"),
|
|
697
|
+
XDG_CACHE_HOME: node_path.default.join(home, "cache"),
|
|
698
|
+
XDG_DATA_HOME: node_path.default.join(home, "data")
|
|
699
|
+
});
|
|
700
|
+
return env;
|
|
701
|
+
};
|
|
702
|
+
const fixtureCommand = (argv, tools) => {
|
|
703
|
+
const executable = argv.at(0);
|
|
704
|
+
if (argv.slice(1).some((argument) => node_path.default.posix.isAbsolute(argument) || /(?:^|\/)\.\.(?:\/|$)/.test(argument))) throw new Error("consumer fixture command arguments must stay within the fixture");
|
|
705
|
+
if (executable === "node") return [tools.node, ...argv.slice(1)];
|
|
706
|
+
if (argv[1] === "run" && executable === "npm") return [
|
|
707
|
+
tools.node,
|
|
708
|
+
tools.npm,
|
|
709
|
+
...argv.slice(1)
|
|
710
|
+
];
|
|
711
|
+
if (argv[1] === "run" && executable === "pnpm") return [
|
|
712
|
+
tools.node,
|
|
713
|
+
tools.pnpm,
|
|
714
|
+
...argv.slice(1)
|
|
715
|
+
];
|
|
716
|
+
throw new Error("consumer fixture commands must use node or a declared npm/pnpm run script");
|
|
717
|
+
};
|
|
718
|
+
const consumerFixtureCommands = ({ manager, tools, directory, home, fixture }) => {
|
|
719
|
+
return [
|
|
720
|
+
manager === "npm" ? [
|
|
721
|
+
tools.node,
|
|
722
|
+
tools.npm,
|
|
723
|
+
"install",
|
|
724
|
+
"--ignore-scripts",
|
|
725
|
+
"--no-audit",
|
|
726
|
+
"--no-fund"
|
|
727
|
+
] : [
|
|
728
|
+
tools.node,
|
|
729
|
+
tools.pnpm,
|
|
730
|
+
"pm",
|
|
731
|
+
"install",
|
|
732
|
+
"--ignore-scripts",
|
|
733
|
+
"--no-frozen-lockfile",
|
|
734
|
+
"--store-dir",
|
|
735
|
+
node_path.default.join(home, "pnpm-store")
|
|
736
|
+
],
|
|
737
|
+
manager === "pnpm" ? [
|
|
738
|
+
tools.node,
|
|
739
|
+
tools.pnpm,
|
|
740
|
+
"pm",
|
|
741
|
+
"rebuild",
|
|
742
|
+
"--pending"
|
|
743
|
+
] : [
|
|
744
|
+
tools.node,
|
|
745
|
+
tools.npm,
|
|
746
|
+
"rebuild"
|
|
747
|
+
],
|
|
748
|
+
[
|
|
749
|
+
tools.node,
|
|
750
|
+
node_path.default.join(directory, "node_modules/typescript/bin/tsc"),
|
|
751
|
+
"--noEmit"
|
|
752
|
+
],
|
|
753
|
+
fixtureCommand(fixture.build, tools),
|
|
754
|
+
fixtureCommand(fixture.smoke, tools)
|
|
755
|
+
];
|
|
756
|
+
};
|
|
757
|
+
const installConsumerFixtureDependencies = async (options) => {
|
|
758
|
+
const env = consumerCommandEnvironment(options);
|
|
759
|
+
const commands = consumerFixtureCommands(options);
|
|
760
|
+
for (const [index, argv] of commands.slice(0, 2).entries()) {
|
|
761
|
+
const executable = argv.at(0);
|
|
762
|
+
if (!executable) throw new Error("missing consumer dependency command");
|
|
763
|
+
process.stdout.write(await execute(executable, argv.slice(1), {
|
|
764
|
+
cwd: options.directory,
|
|
765
|
+
env
|
|
766
|
+
}));
|
|
767
|
+
await assertConsumerInstalledToolBins(options.directory);
|
|
768
|
+
if (index !== 0) continue;
|
|
769
|
+
const manifestFile = node_path.default.join(options.directory, "package.json");
|
|
770
|
+
const manifest = JSON.parse(await (0, node_fs_promises.readFile)(manifestFile, "utf8"));
|
|
771
|
+
if (!require_toolchain_schema.consumerRecord(manifest)) throw new Error("invalid consumer fixture manifest");
|
|
772
|
+
const lockSource = await (0, node_fs_promises.readFile)(node_path.default.join(options.directory, options.manager === "npm" ? "package-lock.json" : "pnpm-lock.yaml"), "utf8");
|
|
773
|
+
const { approvals, rebuildTargets } = await consumerBuildApprovals({
|
|
774
|
+
manifest,
|
|
775
|
+
lock: options.manager === "npm" ? JSON.parse(lockSource) : (0, yaml.parse)(lockSource),
|
|
776
|
+
manager: options.manager,
|
|
777
|
+
directory: options.directory
|
|
778
|
+
});
|
|
779
|
+
if (options.manager === "npm") {
|
|
780
|
+
const rebuild = commands.at(1);
|
|
781
|
+
if (!rebuild) throw new Error("missing consumer rebuild command");
|
|
782
|
+
rebuild.push(...rebuildTargets);
|
|
783
|
+
if (Object.keys(approvals).length === 0) break;
|
|
784
|
+
await (0, node_fs_promises.writeFile)(manifestFile, `${JSON.stringify({
|
|
785
|
+
...manifest,
|
|
786
|
+
allowScripts: approvals
|
|
787
|
+
}, null, 2)}\n`);
|
|
788
|
+
} else {
|
|
789
|
+
const workspaceFile = node_path.default.join(options.directory, "pnpm-workspace.yaml");
|
|
790
|
+
const workspace = (0, yaml.parse)(await (0, node_fs_promises.readFile)(workspaceFile, "utf8"));
|
|
791
|
+
if (!require_toolchain_schema.consumerRecord(workspace)) throw new Error("invalid generated consumer workspace");
|
|
792
|
+
await (0, node_fs_promises.writeFile)(workspaceFile, (0, yaml.stringify)({
|
|
793
|
+
...workspace,
|
|
794
|
+
allowBuilds: approvals
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
const runFixture = async ({ fixtureRoot, fixture, pkg, closure, artifacts, scratch, tools, policy, manager }) => {
|
|
800
|
+
const selectedArtifact = artifacts.get(pkg.name);
|
|
801
|
+
if (!selectedArtifact) throw new Error(`missing consumer artifact: ${pkg.name}`);
|
|
802
|
+
const published = JSON.parse(await execute("tar", [
|
|
803
|
+
"-xOf",
|
|
804
|
+
selectedArtifact,
|
|
805
|
+
"package/package.json"
|
|
806
|
+
], { cwd: scratch }));
|
|
807
|
+
if (!require_toolchain_schema.consumerRecord(published)) throw new Error(`invalid packed package manifest: ${pkg.name}`);
|
|
808
|
+
require_toolchain_schema.assertConsumerFixtureKind({
|
|
809
|
+
fixture,
|
|
810
|
+
pkg: {
|
|
811
|
+
directory: pkg.directory,
|
|
812
|
+
name: pkg.name,
|
|
813
|
+
manifest: published
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
const source = await containedDirectory(fixtureRoot, fixture.fixture);
|
|
817
|
+
const directory = node_path.default.join(scratch, `fixture-${manager}-${(0, node_crypto.createHash)("sha256").update(pkg.name).digest("hex").slice(0, 16)}`);
|
|
818
|
+
await assertConsumerFixtureFiles(source);
|
|
819
|
+
await copyWithoutDependencies(source, directory);
|
|
820
|
+
const manifest = await jsonFile(node_path.default.join(directory, "package.json"));
|
|
821
|
+
const needsReactDom = [
|
|
822
|
+
"dependencies",
|
|
823
|
+
"devDependencies",
|
|
824
|
+
"optionalDependencies"
|
|
825
|
+
].some((field) => {
|
|
826
|
+
const entries = manifest[field];
|
|
827
|
+
return require_toolchain_schema.consumerRecord(entries) && typeof entries["react-dom"] === "string";
|
|
828
|
+
});
|
|
829
|
+
const reactBindings = {};
|
|
830
|
+
if (fixture.kind === "react") {
|
|
831
|
+
const peers = published["peerDependencies"];
|
|
832
|
+
if (!require_toolchain_schema.consumerRecord(peers) || typeof peers["react"] !== "string") throw new Error(`React fixture requires a React peer: ${pkg.name}`);
|
|
833
|
+
const needsDom = typeof peers["react-dom"] === "string" || needsReactDom;
|
|
834
|
+
const bindings = selectConsumerReactVersions({
|
|
835
|
+
reactRange: peers["react"],
|
|
836
|
+
reactVersions: await registryMetadata("react"),
|
|
837
|
+
dom: needsDom ? {
|
|
838
|
+
range: typeof peers["react-dom"] === "string" ? peers["react-dom"] : void 0,
|
|
839
|
+
versions: await registryMetadata("react-dom")
|
|
840
|
+
} : void 0
|
|
841
|
+
});
|
|
842
|
+
Object.assign(reactBindings, bindings);
|
|
843
|
+
process.stdout.write(`${pkg.name}: ${manager} React ${bindings.react}${bindings["react-dom"] === void 0 ? "" : ` / ReactDOM ${bindings["react-dom"]}`}\n`);
|
|
844
|
+
}
|
|
845
|
+
const closureArtifacts = /* @__PURE__ */ new Map();
|
|
846
|
+
for (const name of closure.keys()) {
|
|
847
|
+
const artifact = artifacts.get(name);
|
|
848
|
+
if (!artifact) throw new Error(`missing consumer artifact: ${name}`);
|
|
849
|
+
closureArtifacts.set(name, artifact);
|
|
850
|
+
}
|
|
851
|
+
const boundManifest = require_toolchain_schema.bindConsumerManifest({
|
|
852
|
+
manifest,
|
|
853
|
+
artifacts: closureArtifacts,
|
|
854
|
+
typescript: policy.consumerTypescript,
|
|
855
|
+
react: reactBindings,
|
|
856
|
+
manager
|
|
857
|
+
});
|
|
858
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(directory, "package.json"), `${JSON.stringify(boundManifest.manifest, null, 2)}\n`);
|
|
859
|
+
if (boundManifest.manager === "pnpm") await (0, node_fs_promises.writeFile)(node_path.default.join(directory, "pnpm-workspace.yaml"), (0, yaml.stringify)(boundManifest.workspace));
|
|
860
|
+
for (const lockfile of [
|
|
861
|
+
"package-lock.json",
|
|
862
|
+
"npm-shrinkwrap.json",
|
|
863
|
+
"pnpm-lock.yaml",
|
|
864
|
+
"yarn.lock",
|
|
865
|
+
"bun.lock",
|
|
866
|
+
"bun.lockb"
|
|
867
|
+
]) await (0, node_fs_promises.rm)(node_path.default.join(directory, lockfile), { force: true });
|
|
868
|
+
const home = node_path.default.join(directory, ".consumer-home");
|
|
869
|
+
await (0, node_fs_promises.mkdir)(home);
|
|
870
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(home, "npmrc"), "");
|
|
871
|
+
const options = {
|
|
872
|
+
manager,
|
|
873
|
+
tools,
|
|
874
|
+
directory,
|
|
875
|
+
home,
|
|
876
|
+
fixture
|
|
877
|
+
};
|
|
878
|
+
await installConsumerFixtureDependencies(options);
|
|
879
|
+
if ((await jsonFile(node_path.default.join(directory, "node_modules/typescript/package.json")))["version"] !== policy.consumerTypescript) throw new Error("installed consumer TypeScript does not match policy");
|
|
880
|
+
const env = consumerCommandEnvironment(options);
|
|
881
|
+
for (const argv of consumerFixtureCommands(options).slice(2)) {
|
|
882
|
+
await assertConsumerInstalledToolBins(directory);
|
|
883
|
+
const executable = argv.at(0);
|
|
884
|
+
if (!executable) throw new Error("missing consumer command");
|
|
885
|
+
process.stdout.write(await execute(executable, argv.slice(1), {
|
|
886
|
+
cwd: directory,
|
|
887
|
+
env
|
|
888
|
+
}));
|
|
889
|
+
}
|
|
890
|
+
process.stdout.write(`${pkg.name}: ${manager} consumer build/types/smoke passed\n`);
|
|
891
|
+
};
|
|
892
|
+
const assertConsumerPackedNodeSupport = async ({ artifacts, node, directory }) => {
|
|
893
|
+
for (const [name, artifact] of artifacts) {
|
|
894
|
+
const manifest = JSON.parse(await execute("tar", [
|
|
895
|
+
"-xOf",
|
|
896
|
+
artifact,
|
|
897
|
+
"package/package.json"
|
|
898
|
+
], { cwd: directory }));
|
|
899
|
+
if (!require_toolchain_schema.consumerRecord(manifest)) throw new Error(`invalid packed package manifest: ${name}`);
|
|
900
|
+
require_toolchain_schema.assertConsumerNodeSupport({
|
|
901
|
+
manifest,
|
|
902
|
+
node,
|
|
903
|
+
label: `packed ${name}`
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const assertConsumerPackedSyntax = async ({ artifacts, packages, files, node, typescript, executable }) => {
|
|
908
|
+
const packer = require_publish_contract.resolveConsumerReleasePack(files).manager;
|
|
909
|
+
for (const [name, archive] of artifacts) {
|
|
910
|
+
const pkg = packages.get(name);
|
|
911
|
+
if (pkg === void 0) throw new Error(`Missing packed package: ${name}`);
|
|
912
|
+
const declaration = node_path.default.posix.join(pkg.directory, "publish-contract.json");
|
|
913
|
+
const source = files[declaration];
|
|
914
|
+
if (source === void 0) throw new Error(`Missing tracked publish contract: ${declaration}`);
|
|
915
|
+
const contract = require_publish_contract.parsePublishContract(JSON.parse(source));
|
|
916
|
+
const packed = await readPackedSyntaxFiles(archive);
|
|
917
|
+
const manifest = packed.get("package/package.json");
|
|
918
|
+
if (manifest === void 0) throw new Error(`Missing packed manifest: ${name}`);
|
|
919
|
+
const diagnostics = require_publish_contract.checkPublishContract({
|
|
920
|
+
manifest: JSON.parse(manifest),
|
|
921
|
+
target: contract.target,
|
|
922
|
+
contract,
|
|
923
|
+
packer,
|
|
924
|
+
policy: {
|
|
925
|
+
node,
|
|
926
|
+
typescript
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
if (diagnostics.length > 0) throw new Error(`Packed publish contract differs for ${name}: ${diagnostics.map((entry) => entry.message).join("; ")}`);
|
|
930
|
+
await checkPackedJavaScriptWithNode({
|
|
931
|
+
files: packed,
|
|
932
|
+
target: contract.target,
|
|
933
|
+
node,
|
|
934
|
+
executable
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
const runConsumerCompat = async ({ root: inputRoot, packages: selected, consumerNode, fixturePath, policy: inputPolicy }) => {
|
|
939
|
+
const policy = require_toolchain_schema.parseToolchainPolicy(inputPolicy);
|
|
940
|
+
if (consumerNode !== policy.consumerNode) throw new Error("consumer Node must match the published toolchain policy");
|
|
941
|
+
if (selected.length === 0) throw new Error("consumer compatibility requires at least one package");
|
|
942
|
+
const root = await (0, node_fs_promises.realpath)(inputRoot);
|
|
943
|
+
const fixtureRoot = await containedDirectory(root, require_toolchain_schema.consumerRelativePath(fixturePath, "fixture-path"));
|
|
944
|
+
const fixtures = require_toolchain_schema.parseConsumerFixtures(await jsonFile(node_path.default.join(fixtureRoot, "consumer-compat.json")));
|
|
945
|
+
require_toolchain_schema.assertConsumerFixtureSelection({
|
|
946
|
+
selected,
|
|
947
|
+
fixtures
|
|
948
|
+
});
|
|
949
|
+
const files = await trackedManifests(root);
|
|
950
|
+
const packages = require_toolchain_schema.discoverConsumerPackages(files);
|
|
951
|
+
const selections = [];
|
|
952
|
+
const all = /* @__PURE__ */ new Map();
|
|
953
|
+
const seen = /* @__PURE__ */ new Set();
|
|
954
|
+
for (const input of selected) {
|
|
955
|
+
const directory = require_toolchain_schema.consumerRelativePath(input, "selected package");
|
|
956
|
+
if (seen.has(directory)) throw new Error(`duplicate selected package: ${directory}`);
|
|
957
|
+
seen.add(directory);
|
|
958
|
+
const pkg = [...packages.values()].find((candidate) => candidate.directory === directory);
|
|
959
|
+
if (!pkg || pkg.manifest["private"] === true) throw new Error(`selected package is not a public root or declared workspace package: ${directory}`);
|
|
960
|
+
const fixture = fixtures.find((candidate) => candidate.package === directory);
|
|
961
|
+
if (!fixture) throw new Error(`missing declared consumer fixture: ${directory}`);
|
|
962
|
+
require_toolchain_schema.assertConsumerFixtureKind({
|
|
963
|
+
fixture,
|
|
964
|
+
pkg
|
|
965
|
+
});
|
|
966
|
+
await assertConsumerFixtureFiles(await containedDirectory(fixtureRoot, fixture.fixture));
|
|
967
|
+
fixtureCommand(fixture.build, {
|
|
968
|
+
node: "node",
|
|
969
|
+
npm: "npm",
|
|
970
|
+
pnpm: "pnpm",
|
|
971
|
+
bin: ""
|
|
972
|
+
});
|
|
973
|
+
fixtureCommand(fixture.smoke, {
|
|
974
|
+
node: "node",
|
|
975
|
+
npm: "npm",
|
|
976
|
+
pnpm: "pnpm",
|
|
977
|
+
bin: ""
|
|
978
|
+
});
|
|
979
|
+
const closure = require_toolchain_schema.consumerPackageClosure({
|
|
980
|
+
selected: pkg,
|
|
981
|
+
packages,
|
|
982
|
+
files
|
|
983
|
+
});
|
|
984
|
+
for (const [name, member] of closure) {
|
|
985
|
+
require_toolchain_schema.assertConsumerNodeSupport({
|
|
986
|
+
manifest: member.manifest,
|
|
987
|
+
node: policy.consumerNode,
|
|
988
|
+
label: node_path.default.posix.join(member.directory, "package.json")
|
|
989
|
+
});
|
|
990
|
+
all.set(name, member);
|
|
991
|
+
}
|
|
992
|
+
selections.push({
|
|
993
|
+
pkg,
|
|
994
|
+
fixture,
|
|
995
|
+
closure
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
require_publish_contract.resolveConsumerReleasePack(files);
|
|
999
|
+
const scratch = await (0, node_fs_promises.mkdtemp)(node_path.default.join((0, node_os.tmpdir)(), "stll-consumer-compat-"));
|
|
1000
|
+
try {
|
|
1001
|
+
const tools = await provisionConsumerTools(scratch, policy);
|
|
1002
|
+
const artifacts = await packConsumerArtifacts({
|
|
1003
|
+
files,
|
|
1004
|
+
root,
|
|
1005
|
+
scratch,
|
|
1006
|
+
tools,
|
|
1007
|
+
packages: all,
|
|
1008
|
+
workspacePackages: packages
|
|
1009
|
+
});
|
|
1010
|
+
await assertConsumerPackedNodeSupport({
|
|
1011
|
+
artifacts,
|
|
1012
|
+
node: policy.consumerNode,
|
|
1013
|
+
directory: scratch
|
|
1014
|
+
});
|
|
1015
|
+
await assertConsumerPackedSyntax({
|
|
1016
|
+
artifacts,
|
|
1017
|
+
packages: all,
|
|
1018
|
+
files,
|
|
1019
|
+
node: policy.consumerNode,
|
|
1020
|
+
typescript: policy.consumerTypescript,
|
|
1021
|
+
executable: tools.node
|
|
1022
|
+
});
|
|
1023
|
+
for (const selection of selections) for (const manager of ["npm", "pnpm"]) await runFixture({
|
|
1024
|
+
fixtureRoot,
|
|
1025
|
+
...selection,
|
|
1026
|
+
artifacts,
|
|
1027
|
+
scratch,
|
|
1028
|
+
tools,
|
|
1029
|
+
policy,
|
|
1030
|
+
manager
|
|
1031
|
+
});
|
|
1032
|
+
} finally {
|
|
1033
|
+
await (0, node_fs_promises.rm)(scratch, {
|
|
1034
|
+
recursive: true,
|
|
1035
|
+
force: true
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/consumer-compat-arguments.ts
|
|
1041
|
+
const consumerCompatUsage = "Usage: stll-consumer-compat --packages '[\"packages/example\"]' --consumer-node <exact-patch> --fixture-path <directory>";
|
|
1042
|
+
const parseConsumerCompatArguments = (args) => {
|
|
1043
|
+
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) return { mode: "help" };
|
|
1044
|
+
const options = /* @__PURE__ */ new Map();
|
|
1045
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
1046
|
+
const flag = args[index];
|
|
1047
|
+
const value = args[index + 1];
|
|
1048
|
+
if (!flag || ![
|
|
1049
|
+
"--packages",
|
|
1050
|
+
"--consumer-node",
|
|
1051
|
+
"--fixture-path"
|
|
1052
|
+
].includes(flag) || !value || options.has(flag)) throw new Error(consumerCompatUsage);
|
|
1053
|
+
options.set(flag, value);
|
|
1054
|
+
}
|
|
1055
|
+
const encoded = options.get("--packages");
|
|
1056
|
+
const consumerNode = options.get("--consumer-node");
|
|
1057
|
+
const fixturePath = options.get("--fixture-path");
|
|
1058
|
+
if (!encoded || !consumerNode || !fixturePath) throw new Error(consumerCompatUsage);
|
|
1059
|
+
const packages = JSON.parse(encoded);
|
|
1060
|
+
if (!Array.isArray(packages) || packages.length === 0) throw new Error("--packages must be a nonempty JSON array");
|
|
1061
|
+
return {
|
|
1062
|
+
mode: "run",
|
|
1063
|
+
packages: packages.map((entry) => require_toolchain_schema.consumerRelativePath(entry, "--packages")),
|
|
1064
|
+
consumerNode,
|
|
1065
|
+
fixturePath: require_toolchain_schema.consumerRelativePath(fixturePath, "--fixture-path")
|
|
1066
|
+
};
|
|
1067
|
+
};
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/consumer-compat-cli.ts
|
|
1070
|
+
const main = async () => {
|
|
1071
|
+
const argumentsResult = parseConsumerCompatArguments(process.argv.slice(2));
|
|
1072
|
+
if (argumentsResult.mode === "help") {
|
|
1073
|
+
process.stdout.write(`${consumerCompatUsage}\n`);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const policy = JSON.parse(await (0, node_fs_promises.readFile)(new URL("../toolchain.json", require("url").pathToFileURL(__filename).href), "utf8"));
|
|
1077
|
+
await runConsumerCompat({
|
|
1078
|
+
root: process.cwd(),
|
|
1079
|
+
packages: argumentsResult.packages,
|
|
1080
|
+
consumerNode: argumentsResult.consumerNode,
|
|
1081
|
+
fixturePath: argumentsResult.fixturePath,
|
|
1082
|
+
policy
|
|
1083
|
+
});
|
|
1084
|
+
};
|
|
1085
|
+
main().catch((error) => {
|
|
1086
|
+
process.stderr.write(`consumer-compat: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1087
|
+
process.exitCode = 1;
|
|
1088
|
+
});
|
|
1089
|
+
//#endregion
|
|
1090
|
+
|
|
1091
|
+
//# sourceMappingURL=consumer-compat-cli.cjs.map
|