@stll/oxlint-config 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/dist/toolchain-guard.cjs +84 -41
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +84 -41
- package/dist/toolchain-guard.mjs.map +1 -1
- package/package.json +1 -1
package/dist/toolchain-guard.mjs
CHANGED
|
@@ -267,19 +267,28 @@ const parseCloudSetup = (input) => {
|
|
|
267
267
|
if (input === void 0) return void 0;
|
|
268
268
|
if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("cloud must be an object");
|
|
269
269
|
for (const key of Object.keys(input)) if (key !== "services" && key !== "install" && key !== "envFile") throw new Error(`unsupported cloud key: ${key}`);
|
|
270
|
-
|
|
270
|
+
const selected = "services" in input ? input.services : [];
|
|
271
|
+
if (!Array.isArray(selected)) throw new Error("cloud.services must be an array when provided");
|
|
271
272
|
if (!("install" in input) || input.install !== "bun install --frozen-lockfile") throw new Error(`cloud.install must be exactly ${cloudInstallCommand}`);
|
|
273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
274
|
+
const services = Array.from(selected, (service) => {
|
|
275
|
+
if (!cloudService(service)) throw new Error("cloud.services contains an unsupported service");
|
|
276
|
+
if (seen.has(service)) throw new Error(`cloud.services contains duplicate service: ${service}`);
|
|
277
|
+
seen.add(service);
|
|
278
|
+
return service;
|
|
279
|
+
});
|
|
280
|
+
if (services.length === 0) {
|
|
281
|
+
if ("envFile" in input) throw new Error("cloud.envFile is not allowed without services");
|
|
282
|
+
return {
|
|
283
|
+
services,
|
|
284
|
+
install: cloudInstallCommand
|
|
285
|
+
};
|
|
286
|
+
}
|
|
272
287
|
if (!("envFile" in input) || typeof input.envFile !== "string") throw new Error("cloud.envFile must be a canonical repository-relative path");
|
|
273
288
|
const envFile = input.envFile;
|
|
274
289
|
if (envFile.startsWith("/") || /^[A-Za-z]:/.test(envFile) || envFile.includes("\\") || /\p{Cc}/u.test(envFile) || envFile.split("/").some((part) => part === "" || part === "." || part === "..")) throw new Error("cloud.envFile must be a canonical repository-relative path");
|
|
275
|
-
const seen = /* @__PURE__ */ new Set();
|
|
276
290
|
return {
|
|
277
|
-
services
|
|
278
|
-
if (!cloudService(service)) throw new Error("cloud.services contains an unsupported service");
|
|
279
|
-
if (seen.has(service)) throw new Error(`cloud.services contains duplicate service: ${service}`);
|
|
280
|
-
seen.add(service);
|
|
281
|
-
return service;
|
|
282
|
-
}),
|
|
291
|
+
services,
|
|
283
292
|
install: cloudInstallCommand,
|
|
284
293
|
envFile
|
|
285
294
|
};
|
|
@@ -491,8 +500,7 @@ NODE_DIR="/opt/stll-cloud/node/$NODE_VERSION"
|
|
|
491
500
|
BUN_DIR="/opt/stll-cloud/bun/$BUN_VERSION"
|
|
492
501
|
export PATH="$NODE_DIR/bin:$BUN_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
493
502
|
BUN_CACHE="/var/cache/stll-cloud/bun/$OUTPUT_UID"
|
|
494
|
-
|
|
495
|
-
ENV_MARKER='@ENV_MARKER@'
|
|
503
|
+
@ENV_ASSIGNMENTS@
|
|
496
504
|
[[ -f "$REPO_ROOT/.node-version" && ! -L "$REPO_ROOT/.node-version" ]] || fail 'A regular root .node-version is required'
|
|
497
505
|
[[ "$(awk 'NF { if (++count > 1 || NF != 1) exit 1; value=$1 } END { if (count != 1) exit 1; print value }' "$REPO_ROOT/.node-version")" == "$NODE_VERSION" ]] || fail 'Node selector changed; regenerate cloud-setup.sh'
|
|
498
506
|
cd -- "$REPO_ROOT"
|
|
@@ -561,6 +569,21 @@ dependency_install() {
|
|
|
561
569
|
@SERVICE_INSTALL@
|
|
562
570
|
@SERVICE_START@
|
|
563
571
|
|
|
572
|
+
@ENV_FUNCTIONS@
|
|
573
|
+
|
|
574
|
+
if [[ "$MODE" == install ]]; then
|
|
575
|
+
install_runtimes
|
|
576
|
+
install_services
|
|
577
|
+
dependency_install
|
|
578
|
+
exit 0
|
|
579
|
+
fi
|
|
580
|
+
runtime_ready
|
|
581
|
+
@ENV_VALIDATE@
|
|
582
|
+
prepare_state
|
|
583
|
+
start_services
|
|
584
|
+
@ENV_WRITE@
|
|
585
|
+
`;
|
|
586
|
+
const environmentFunctions = String.raw`
|
|
564
587
|
validate_environment() {
|
|
565
588
|
local part target="$REPO_ROOT" env_path="$REPO_ROOT/$ENV_FILE" first_line temporary
|
|
566
589
|
while IFS= read -r part; do
|
|
@@ -587,20 +610,6 @@ write_environment() {
|
|
|
587
610
|
chown "$OUTPUT_UID:$OUTPUT_GID" "$temporary"
|
|
588
611
|
mv -f -- "$temporary" "$env_path"
|
|
589
612
|
}
|
|
590
|
-
|
|
591
|
-
if [[ "$MODE" == install ]]; then
|
|
592
|
-
install_runtimes
|
|
593
|
-
install_services
|
|
594
|
-
dependency_install
|
|
595
|
-
exit 0
|
|
596
|
-
fi
|
|
597
|
-
runtime_ready
|
|
598
|
-
DATABASE_URL=''
|
|
599
|
-
REDIS_URL=''
|
|
600
|
-
validate_environment
|
|
601
|
-
prepare_state
|
|
602
|
-
start_services
|
|
603
|
-
write_environment
|
|
604
613
|
`;
|
|
605
614
|
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
606
615
|
const generateCloudSetup = ({ policy, cloud, nodeVersion }) => {
|
|
@@ -612,8 +621,10 @@ const generateCloudSetup = ({ policy, cloud, nodeVersion }) => {
|
|
|
612
621
|
const replacements = /* @__PURE__ */ new Map([
|
|
613
622
|
["@BUN@", policy.bun],
|
|
614
623
|
["@NODE@", exactNode],
|
|
615
|
-
["@
|
|
616
|
-
["@
|
|
624
|
+
["@ENV_ASSIGNMENTS@", selected.envFile === void 0 ? "" : `ENV_FILE=${shellQuote(selected.envFile)}\nENV_MARKER=${shellQuote(cloudEnvironmentMarker)}`],
|
|
625
|
+
["@ENV_FUNCTIONS@", selected.envFile === void 0 ? "" : environmentFunctions],
|
|
626
|
+
["@ENV_VALIDATE@", selected.envFile === void 0 ? "" : "DATABASE_URL=''\nREDIS_URL=''\nvalidate_environment"],
|
|
627
|
+
["@ENV_WRITE@", selected.envFile === void 0 ? "" : "write_environment"],
|
|
617
628
|
["@SERVICE_INSTALL@", renderCloudServiceInstall({
|
|
618
629
|
services,
|
|
619
630
|
postgres: policy.postgres,
|
|
@@ -2174,31 +2185,71 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2174
2185
|
});
|
|
2175
2186
|
});
|
|
2176
2187
|
const checkRuntimeImage = ({ image, line, label }) => {
|
|
2177
|
-
|
|
2188
|
+
const unknownRuntime = () => {
|
|
2178
2189
|
add({
|
|
2179
2190
|
rule: "runtime-docker",
|
|
2180
2191
|
line,
|
|
2181
2192
|
message: `cannot determine ${label} runtime; use a static image or a reasoned runtime-docker opt-out`
|
|
2182
2193
|
});
|
|
2194
|
+
};
|
|
2195
|
+
if (typeof image !== "string") {
|
|
2196
|
+
unknownRuntime();
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const canonical = canonicalDockerRuntime(image);
|
|
2200
|
+
const structuralImage = canonical.replace(/\$\{[^}]*\}|\$[A-Za-z_][A-Za-z_\d]*/g, (variable) => "x".repeat(variable.length));
|
|
2201
|
+
const digestSeparator = structuralImage.indexOf("@");
|
|
2202
|
+
const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
|
|
2203
|
+
const structuralReference = digestSeparator < 0 ? structuralImage : structuralImage.slice(0, digestSeparator);
|
|
2204
|
+
const managedFamily = /^(node|python|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
2205
|
+
const tagSeparator = managedFamily === void 0 ? structuralReference.lastIndexOf(":") : structuralReference.indexOf(":", managedFamily.length);
|
|
2206
|
+
const hasTag = managedFamily === void 0 ? tagSeparator > structuralReference.lastIndexOf("/") : tagSeparator !== -1;
|
|
2207
|
+
const imageRepository = hasTag ? reference.slice(0, tagSeparator) : reference;
|
|
2208
|
+
if (imageRepository.length === 0 || imageRepository.includes("$")) {
|
|
2209
|
+
unknownRuntime();
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (!/^(node|python|oven\/bun)$/i.test(imageRepository)) return;
|
|
2213
|
+
const tool = imageRepository.toLowerCase();
|
|
2214
|
+
if (image.includes("$")) {
|
|
2215
|
+
unknownRuntime();
|
|
2183
2216
|
return;
|
|
2184
2217
|
}
|
|
2185
|
-
const
|
|
2186
|
-
if (
|
|
2187
|
-
const tool = runtime[1]?.toLowerCase();
|
|
2188
|
-
if (image.includes("@")) {
|
|
2218
|
+
const digest = digestSeparator < 0 ? void 0 : canonical.slice(digestSeparator + 1);
|
|
2219
|
+
if (digest !== void 0 && !/^sha256:[a-fA-F\d]{64}$/.test(digest)) {
|
|
2189
2220
|
add({
|
|
2190
2221
|
rule: tool === "oven/bun" ? "bun-pins" : "runtime-docker",
|
|
2191
2222
|
line,
|
|
2192
|
-
message: `${label} ${tool} digest
|
|
2223
|
+
message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
|
|
2193
2224
|
});
|
|
2194
2225
|
return;
|
|
2195
2226
|
}
|
|
2196
|
-
const tag =
|
|
2227
|
+
const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
|
|
2197
2228
|
const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
|
|
2198
2229
|
const version = tag?.replace(variantSuffix, "");
|
|
2199
2230
|
let expected = policy.bun;
|
|
2200
2231
|
if (tool === "node") expected = policy.node;
|
|
2201
2232
|
else if (tool === "python") expected = policy.python;
|
|
2233
|
+
if (digest !== void 0) {
|
|
2234
|
+
if (version === void 0 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version)) {
|
|
2235
|
+
add({
|
|
2236
|
+
rule: tool === "oven/bun" ? "bun-pins" : "runtime-docker",
|
|
2237
|
+
line,
|
|
2238
|
+
message: `${label} ${tool} digest requires an exact stable patch tag matching ${expected}`
|
|
2239
|
+
});
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (tool === "node" && trackedFiles.has(".node-version")) {
|
|
2243
|
+
if (readFile(".node-version")?.trim() !== version) {
|
|
2244
|
+
add({
|
|
2245
|
+
rule: "runtime-docker",
|
|
2246
|
+
line,
|
|
2247
|
+
message: `${label} node digest tag must match tracked .node-version, found ${version}`
|
|
2248
|
+
});
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2202
2253
|
pin({
|
|
2203
2254
|
rule: tool === "oven/bun" ? "bun-pins" : "runtime-docker",
|
|
2204
2255
|
value: version,
|
|
@@ -2311,14 +2362,6 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2311
2362
|
if (from === null || from[1] === void 0) return;
|
|
2312
2363
|
argumentScope = "stage";
|
|
2313
2364
|
const image = expand(from[1]);
|
|
2314
|
-
if (image.includes("$")) {
|
|
2315
|
-
add({
|
|
2316
|
-
rule: "runtime-docker",
|
|
2317
|
-
line,
|
|
2318
|
-
message: "cannot determine FROM runtime; declare an image ARG default or a reasoned runtime-docker opt-out"
|
|
2319
|
-
});
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
2365
|
const stage = from[2]?.toLowerCase();
|
|
2323
2366
|
const previousStage = stages.has(image.toLowerCase());
|
|
2324
2367
|
if (stage !== void 0) stages.add(stage);
|