@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.
@@ -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
- if (!("services" in input) || !Array.isArray(input.services)) throw new Error("cloud.services must be an explicit array");
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: Array.from(input.services, (service) => {
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
- ENV_FILE=@ENV_FILE@
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
- ["@ENV_FILE@", shellQuote(selected.envFile)],
616
- ["@ENV_MARKER@", cloudEnvironmentMarker],
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
- if (typeof image !== "string" || image.includes("$")) {
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 runtime = /^(node|python|oven\/bun)(?::([^@]+))?(?:@.*)?$/i.exec(canonicalDockerRuntime(image));
2186
- if (runtime === null) return;
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 requires an approved digest policy; use a tag-only runtime image`
2223
+ message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
2193
2224
  });
2194
2225
  return;
2195
2226
  }
2196
- const tag = runtime[2];
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);