@stll/oxlint-config 1.3.1 → 1.5.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.
Files changed (95) hide show
  1. package/README.md +341 -28
  2. package/dist/cloud-setup-cli.cjs +20 -40
  3. package/dist/cloud-setup-cli.cjs.map +1 -1
  4. package/dist/cloud-setup-cli.mjs +21 -41
  5. package/dist/cloud-setup-cli.mjs.map +1 -1
  6. package/dist/consumer-compat-cli.cjs +52 -251
  7. package/dist/consumer-compat-cli.cjs.map +1 -1
  8. package/dist/consumer-compat-cli.mjs +7 -206
  9. package/dist/consumer-compat-cli.mjs.map +1 -1
  10. package/dist/consumer-node-support.cjs +497 -0
  11. package/dist/consumer-node-support.cjs.map +1 -0
  12. package/dist/consumer-node-support.mjs +381 -0
  13. package/dist/consumer-node-support.mjs.map +1 -0
  14. package/dist/dependabot-cli.cjs +91 -0
  15. package/dist/dependabot-cli.cjs.map +1 -0
  16. package/dist/dependabot-cli.d.cts +1 -0
  17. package/dist/dependabot-cli.d.mts +1 -0
  18. package/dist/dependabot-cli.mjs +91 -0
  19. package/dist/dependabot-cli.mjs.map +1 -0
  20. package/dist/dependabot.cjs +7 -0
  21. package/dist/dependabot.d.cts +118 -0
  22. package/dist/dependabot.d.mts +118 -0
  23. package/dist/dependabot.mjs +3 -0
  24. package/dist/generated-file.cjs +61 -0
  25. package/dist/generated-file.cjs.map +1 -0
  26. package/dist/generated-file.mjs +44 -0
  27. package/dist/generated-file.mjs.map +1 -0
  28. package/dist/git-tracked-output.cjs +2 -2
  29. package/dist/git-tracked-output.mjs +1 -1
  30. package/dist/jsonc.cjs +66 -0
  31. package/dist/jsonc.cjs.map +1 -0
  32. package/dist/jsonc.mjs +61 -0
  33. package/dist/jsonc.mjs.map +1 -0
  34. package/dist/publish-artifact-syntax.cjs +1309 -0
  35. package/dist/publish-artifact-syntax.cjs.map +1 -0
  36. package/dist/publish-artifact-syntax.mjs +1265 -0
  37. package/dist/publish-artifact-syntax.mjs.map +1 -0
  38. package/dist/publish-build-target-vite-dts-helper2.cjs +1 -1
  39. package/dist/publish-build-target-vite-dts-helper2.mjs +1 -1
  40. package/dist/publish-contract-cli.cjs +55 -14
  41. package/dist/publish-contract-cli.cjs.map +1 -1
  42. package/dist/publish-contract-cli.mjs +50 -9
  43. package/dist/publish-contract-cli.mjs.map +1 -1
  44. package/dist/quarantine-cli.cjs +66 -0
  45. package/dist/quarantine-cli.cjs.map +1 -0
  46. package/dist/quarantine-cli.d.cts +1 -0
  47. package/dist/quarantine-cli.d.mts +1 -0
  48. package/dist/quarantine-cli.mjs +66 -0
  49. package/dist/quarantine-cli.mjs.map +1 -0
  50. package/dist/quarantine-policy.cjs +10 -0
  51. package/dist/quarantine-policy.d.cts +27 -0
  52. package/dist/quarantine-policy.d.mts +27 -0
  53. package/dist/quarantine-policy.mjs +1 -0
  54. package/dist/quarantine-policy2.cjs +399 -0
  55. package/dist/quarantine-policy2.cjs.map +1 -0
  56. package/dist/quarantine-policy2.mjs +352 -0
  57. package/dist/quarantine-policy2.mjs.map +1 -0
  58. package/dist/toolchain-changed.cjs +16 -67
  59. package/dist/toolchain-changed.cjs.map +1 -1
  60. package/dist/toolchain-changed.mjs +19 -70
  61. package/dist/toolchain-changed.mjs.map +1 -1
  62. package/dist/toolchain-check-cli.cjs +1 -0
  63. package/dist/toolchain-check-cli.cjs.map +1 -1
  64. package/dist/toolchain-check-cli.mjs +1 -0
  65. package/dist/toolchain-check-cli.mjs.map +1 -1
  66. package/dist/toolchain-dependabot.cjs +529 -0
  67. package/dist/toolchain-dependabot.cjs.map +1 -0
  68. package/dist/toolchain-dependabot.mjs +485 -0
  69. package/dist/toolchain-dependabot.mjs.map +1 -0
  70. package/dist/toolchain-guard.cjs +1143 -432
  71. package/dist/toolchain-guard.cjs.map +1 -1
  72. package/dist/toolchain-guard.mjs +1132 -421
  73. package/dist/toolchain-guard.mjs.map +1 -1
  74. package/dist/toolchain-inputs.cjs +216 -38
  75. package/dist/toolchain-inputs.cjs.map +1 -1
  76. package/dist/toolchain-inputs.mjs +213 -41
  77. package/dist/toolchain-inputs.mjs.map +1 -1
  78. package/dist/toolchain-schema.cjs +0 -493
  79. package/dist/toolchain-schema.cjs.map +1 -1
  80. package/dist/toolchain-schema.mjs +3 -379
  81. package/dist/toolchain-schema.mjs.map +1 -1
  82. package/dist/typecheck-parity.cjs +4 -3
  83. package/dist/typecheck-parity.cjs.map +1 -1
  84. package/dist/typecheck-parity.mjs +4 -3
  85. package/dist/typecheck-parity.mjs.map +1 -1
  86. package/dist/typecheck-probe-cli.cjs +3 -3
  87. package/dist/typecheck-probe-cli.cjs.map +1 -1
  88. package/dist/typecheck-probe-cli.mjs +3 -3
  89. package/dist/typecheck-probe-cli.mjs.map +1 -1
  90. package/package.json +25 -1
  91. package/toolchain.json +11 -4
  92. package/dist/publish-contract.cjs +0 -431
  93. package/dist/publish-contract.cjs.map +0 -1
  94. package/dist/publish-contract.mjs +0 -396
  95. package/dist/publish-contract.mjs.map +0 -1
package/README.md CHANGED
@@ -257,12 +257,15 @@ Peer ranges declare support: they must include the policy compiler release and t
257
257
  compatibility release when the selected layout uses it. Ranges do not contribute
258
258
  to the installed compiler inventory.
259
259
 
260
- JavaScript update roots come from the repository root, declared workspace membership,
260
+ JavaScript update roots include the repository root, declared workspace membership,
261
261
  and directories with their own `bun.lock`, `package-lock.json`, `yarn.lock`, or
262
- `pnpm-lock.yaml`. Other package manifests do not create update roots; directory names
263
- have no special meaning. Workspace members share their owner's update entry.
262
+ `pnpm-lock.yaml`. Independent manifests with dependencies, an explicit package manager,
263
+ or dependency-bearing workspace members also create update roots. Metadata-only
264
+ fixtures do not. Package-manager selection follows the nearest declaration or lock;
265
+ workspace members share their owner's update entry. Bundler `Gemfile`/`Gemfile.lock`
266
+ and Composer `composer.json`/`composer.lock` declare roots at their own directories.
264
267
 
265
- `node-engine` and `cloud-setup-drift` are mandatory: opt-outs cannot bypass the
268
+ `node-engine`, `workflow-trust` and `cloud-setup-drift` are mandatory: opt-outs cannot bypass the
266
269
  shared Node support range or generated cloud setup requirements.
267
270
 
268
271
  Opt-outs apply to the entire repository for that rule. Unknown or duplicate
@@ -288,6 +291,62 @@ discovering or building referenced projects. Missing, unreadable, and zero-input
288
291
  selected configs fail the check. `--help` prints usage and exits successfully;
289
292
  unknown arguments fail.
290
293
 
294
+ ## Generate dependency update configuration
295
+
296
+ Run `bunx --no-install stll-dependabot` from the repository root. It reads tracked
297
+ configuration and writes canonical `.github/dependabot.yml`, preserving an existing
298
+ `.github/dependabot.yaml` spelling, including when the indexed output has been
299
+ deleted from the working tree. Conflicting indexed or existing spellings are
300
+ rejected, and unrelated unreadable inputs still stop generation. Repeated runs
301
+ produce identical bytes; linked output files and directories are rejected.
302
+
303
+ Use `stll-dependabot --check` in CI to compare bytes without writing. Missing or
304
+ stale output exits with status 1; current output exits with status 0. Pass the
305
+ same `--config` argument to write and check commands when using explicit input.
306
+
307
+ An optional `--config stll-dependabot.json` input declares only coverage:
308
+
309
+ ```json
310
+ {
311
+ "updates": [
312
+ { "ecosystem": "bun", "directories": ["/"] },
313
+ { "ecosystem": "github-actions", "directories": ["/"] },
314
+ { "ecosystem": "cargo", "directories": ["/native"] }
315
+ ]
316
+ }
317
+ ```
318
+
319
+ Each ecosystem appears once, and directories must be unique literal absolute
320
+ repository paths. Declarations must cover every detected update root; they may
321
+ also declare ecosystems whose manifests are outside automatic discovery.
322
+ Shared `toolchain.json` owns the schedule, cooldown, groups, and tooling-owned
323
+ package/action/image ignores. The input rejects policy overrides. Without
324
+ `--config`, the tracked manifest inventory supplies ecosystem roots.
325
+
326
+ For programmatic generation, the `@stll/oxlint-config/dependabot` subpath exports
327
+ `generateDependabotConfig({ files, policy, updates? })`, `parseDependabotInput`,
328
+ and `parseToolchainPolicy`. `parseDependabotInput` validates the JSON shape above
329
+ and returns the `updates` array:
330
+
331
+ ```ts
332
+ import {
333
+ generateDependabotConfig,
334
+ parseToolchainPolicy,
335
+ } from "@stll/oxlint-config/dependabot";
336
+ import sharedPolicy from "@stll/oxlint-config/toolchain.json";
337
+
338
+ const yaml = generateDependabotConfig({
339
+ files: trackedConfiguration,
340
+ policy: parseToolchainPolicy(sharedPolicy).dependabot,
341
+ });
342
+ ```
343
+
344
+ The root lint-config entry does not load the tooling parser dependencies
345
+ `yaml`, `picomatch` or `smol-toml`; they belong to the tooling subpath and CLIs.
346
+ `files` maps existing tracked repository-relative configuration paths to their text.
347
+ Deleted tracked manifests are absent from the generator snapshot. Use the
348
+ CLI when a tracked snapshot is not already available; no source imports are needed.
349
+
291
350
  ## Shared cloud setup
292
351
 
293
352
  Add an explicit declaration to tracked root `stll-toolchain.json`:
@@ -305,6 +364,7 @@ Add an explicit declaration to tracked root `stll-toolchain.json`:
305
364
  },
306
365
  "tools": {
307
366
  "cargo-nextest": "0.9.148",
367
+ "gitleaks": "8.30.1",
308
368
  "wasm-bindgen-cli": "0.2.126",
309
369
  "uv": "0.13.1",
310
370
  "time": "1.9"
@@ -324,6 +384,18 @@ repository-relative path; its existing parent must be safe at runtime. Track an
324
384
  bunx --no-install stll-cloud-setup
325
385
  ```
326
386
 
387
+ When PostgreSQL is enabled, the selected environment file contains `DATABASE_URL`
388
+ and `POSTGRES_HOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB_NAME`,
389
+ and `POSTGRES_ADDITIONAL_CONNECTION_DATA`. `DATABASE_URL` retains the
390
+ `postgresql://` spelling; the component values assemble the same endpoint with
391
+ `postgres://`. Additional connection data is empty by default.
392
+ Remote Claude sessions receive these generated values as quoted `export`
393
+ assignments after service startup succeeds. SessionStart does not source the
394
+ selected environment file, and it preserves the configured `envFile` path for
395
+ tools that load that file directly. Settings generation migrates only exact
396
+ SessionStart hooks from releases 1.1.0 and 1.3.1 and the prior main-branch form
397
+ at 783bc97; edited or unknown hook forms fail with the canonical repair.
398
+
327
399
  Rust is optional. A tracked root `rust-toolchain.toml` enables it automatically;
328
400
  otherwise declare `cloud.rust` to enable it. `cloud.rust.toolchain`, when given,
329
401
  must be an exact stable Rust patch. The root toolchain file supplies the pin when
@@ -336,10 +408,17 @@ the minimal profile and a pinned, checksum-verified rustup installer. Rust-enabl
336
408
  setup also provisions Ubuntu's `build-essential` package for native compilation
337
409
  and linking.
338
410
 
411
+ `cloud.rust.additionalToolchains` explicitly selects extra exact compiler releases.
412
+ Each must match the shared pin or a valid `rustExceptions` entry and differ from the
413
+ primary compiler. Extras are installed once with the minimal profile and verified
414
+ offline at session start. An exception alone authorizes a version; it does not
415
+ provision it. This setting installs neither Hawk nor extra compiler components.
416
+ Generated scripts contain no trailing whitespace and end with one newline.
417
+
339
418
  `cloud.tools` is a closed object: the supported keys are `cargo-nextest`,
340
- `wasm-bindgen-cli`, `uv`, and `time`. Each value must be an exact release supported by
419
+ `wasm-bindgen-cli`, `gitleaks`, `uv`, and `time`. Each value must be an exact release supported by
341
420
  the installed tooling package. Unknown tools and unsupported versions fail
342
- generation. `cargo-nextest`, `wasm-bindgen-cli`, and `uv` use versioned upstream Linux binaries with committed
421
+ generation. `cargo-nextest`, `wasm-bindgen-cli`, `gitleaks`, and `uv` use versioned upstream Linux binaries with committed
343
422
  SHA256 pins for amd64 and arm64; setup does not build them with `cargo install`.
344
423
  `time` installs GNU time (`/usr/bin/time`, needed for `/usr/bin/time -v`) from
345
424
  Ubuntu's `time` package with bounded apt; its value is the upstream release
@@ -367,20 +446,29 @@ binary DEBs for the declared release and architecture, verifies the policy's
367
446
  SHA256 pins, and installs those local files with apt. It does not resolve Valkey
368
447
  from a mutable package index.
369
448
  The command writes `.agents/cloud-setup.sh` with executable permissions and
370
- merges a `SessionStart` hook into `.claude/settings.json`. The hook runs
371
- `bash "$CLAUDE_PROJECT_DIR/.agents/cloud-setup.sh" start` only when
372
- `CLAUDE_CODE_REMOTE=true`, including sessions that reuse cached filesystems and
373
- sessions started from a repository subdirectory. Generation replaces the
374
- relative-path hook written by 1.1.0 in place. After successful startup, it appends the
375
- managed shell profile's literal exports to `CLAUDE_ENV_FILE` when available, so
376
- subsequent Claude commands select pinned runtimes and tools before preinstalled
377
- versions while retaining their inherited `PATH`. The settings also set
449
+ merges a `SessionStart` hook into `.claude/settings.json`. Only when
450
+ `CLAUDE_CODE_REMOTE=true`, the hook runs
451
+ `bash "$CLAUDE_PROJECT_DIR/.agents/cloud-setup.sh" session` if `CLAUDE_ENV_FILE`
452
+ is nonempty, and `start` otherwise. This includes sessions that reuse cached
453
+ filesystems and sessions started from a repository subdirectory. Generation
454
+ replaces the exact hooks written by 1.1.0 and 1.3.1 and the prior main-branch
455
+ form in place.
456
+ After successful startup, the hook copies existing `CLAUDE_ENV_FILE` content
457
+ into a temporary file in the same directory, adds the generated service exports
458
+ and managed shell profile's literal exports, then renames it over the destination.
459
+ The resulting file has mode `0600`; an existing destination must be a regular,
460
+ non-symlink file. Subsequent Claude commands select pinned runtimes and tools
461
+ before preinstalled versions while retaining their inherited `PATH`. The settings also set
378
462
  `"attribution": {"commit": "", "pr": ""}`, so cloud sessions add no tool
379
463
  attribution to commits or pull requests. Existing environment-file
380
464
  content, unrelated settings, and hooks are preserved. Invalid JSON, disabled
381
465
  hooks, conflicting cloud startup entries, or a different `attribution` value stop
382
- generation with a repair instruction. Commit both generated files.
466
+ generation with a repair instruction. Startup or environment persistence failures
467
+ produce actionable session context and a stderr reason; SessionStart cannot block
468
+ a session. Successful hook execution is silent on stdout. Commit both generated files.
383
469
  Generation refuses symlink parents or destinations and hard-linked destinations.
470
+ Generation recreates its deleted tracked output files and requires unrelated
471
+ tracked inputs to remain readable.
384
472
  `stll-toolchain-check` rejects missing, drifted, or undeclared tracked scripts,
385
473
  missing or conflicting startup hooks, and missing or different attribution
386
474
  settings with mandatory `cloud-setup-drift`.
@@ -389,16 +477,26 @@ prints usage without reading repository configuration.
389
477
 
390
478
  | Environment phase | Command |
391
479
  | ----------------------- | ------------------------------------- |
392
- | Networked install | `bash .agents/cloud-setup.sh install` |
393
- | Offline session startup | `bash .agents/cloud-setup.sh start` |
394
-
395
- Both commands can be rerun. Missing or unknown subcommands print usage and
480
+ | Networked install | `bash .agents/cloud-setup.sh install` |
481
+ | Offline session startup | `bash .agents/cloud-setup.sh start` |
482
+ | Startup with exports | `bash .agents/cloud-setup.sh session` |
483
+
484
+ Runtime installation repairs and verifies a missing or empty system CA bundle
485
+ offline before any apt refresh or download, including when the certificate package
486
+ is already current. If the certificate updater is unavailable or cannot restore
487
+ trust, restore the base image offline before rerunning installation.
488
+
489
+ All three commands can be rerun. Passwordless sudo rules limited to specific
490
+ script arguments must authorize `install`, `start`, and `session`.
491
+ Missing or unknown subcommands print usage and
396
492
  exit 2; there is no implicit lifecycle mode. The script supports Ubuntu 24.04
397
493
  with root access or passwordless sudo and fails when required capabilities
398
494
  or pinned installations are unavailable. `install` provisions runtimes,
399
495
  dependencies, and declared services; `start` refreshes the managed shell environment
400
496
  and restores local services after
401
- cached environments lose running processes. Services bind only to localhost.
497
+ cached environments lose running processes. `session` performs the same offline
498
+ startup, sends startup output to stderr, and emits the declared service environment
499
+ exports on stdout for the hook to capture. Services bind only to localhost.
402
500
  System provisioning runs as root; frozen dependencies run as the invoking user
403
501
  with that user's home and a private, user-owned Bun cache. Package installation
404
502
  does not permit automatic downgrades.
@@ -432,14 +530,14 @@ or the configured package hosts; a redirected download names its original host.
432
530
 
433
531
  Allow these direct download hosts for the generated installer:
434
532
 
435
- | Host | Downloads |
436
- | ---------------------- | ------------------------------------------------------------------------ |
437
- | `nodejs.org` | Node archives and SHA256 manifests |
438
- | `github.com` | Bun archives and checksums; pinned nextest, wasm-bindgen and uv releases |
439
- | `static.rust-lang.org` | Pinned rustup-init, Rust manifests, toolchains, components and targets |
440
- | `www.postgresql.org` | PostgreSQL repository signing key |
441
- | `apt.postgresql.org` | PostgreSQL packages and repository metadata |
442
- | `download.valkey.io` | Pinned Valkey binary packages |
533
+ | Host | Downloads |
534
+ | ---------------------- | ---------------------------------------------------------------------------------- |
535
+ | `nodejs.org` | Node archives and SHA256 manifests |
536
+ | `github.com` | Bun archives and checksums; pinned nextest, wasm-bindgen, gitleaks and uv releases |
537
+ | `static.rust-lang.org` | Pinned rustup-init, Rust manifests, toolchains, components and targets |
538
+ | `www.postgresql.org` | PostgreSQL repository signing key |
539
+ | `apt.postgresql.org` | PostgreSQL packages and repository metadata |
540
+ | `download.valkey.io` | Pinned Valkey binary packages |
443
541
 
444
542
  GitHub release downloads redirect to `release-assets.githubusercontent.com`;
445
543
  allow that host as well. This includes Bun and the optional prebuilt tools.
@@ -463,6 +561,66 @@ resolved JavaScript targets, and entry points. `stll-publish-contract --write`
463
561
  records an explicit contract decision as a reviewable diff; it also validates
464
562
  consumer support and cannot accept development-only requirements.
465
563
 
564
+ Use `stll-publish-contract --artifact path/to/package.tgz` to check an existing
565
+ release archive after the workspace metadata checks. It validates the static
566
+ literal import closure within the packed package and compares the archive's
567
+ manifest with its declared public workspace package's committed contract and
568
+ resolved build target. The command never packs
569
+ or executes the archive, accepts exactly one archive, and cannot combine
570
+ `--artifact` with `--write`. Unknown or private package artifacts fail. Bare external
571
+ package dependencies are outside this local archive check. Dynamic imports require
572
+ literal specifiers; computed values cannot exclude the module loader namespace.
573
+ The reader follows literal ESM imports/re-exports, CommonJS
574
+ require/resolve calls and named, destructured or namespace `createRequire` bindings based on
575
+ `import.meta.url` or `__filename`. Asset references must be regular packed files;
576
+ non-JavaScript ESM assets are not parsed as scripts. Recursive import mappings
577
+ are rejected, including expanding wildcard aliases.
578
+ Node resolves collected specifiers against a temporary archive filesystem:
579
+ `createRequire(importer).resolve` for CommonJS and `import.meta.resolve` with the
580
+ importer's URL for ESM, using their respective default conditions. The worker
581
+ reads manifests and file existence without loading package code. Only manifests
582
+ need contents; other members use inert placeholders, and selected targets must
583
+ still be regular packed members. External import-map dependencies use inert
584
+ metadata outside the packed package boundary. Unknown external mapping forms
585
+ fail explicitly. Temporary files and the worker are removed after validation.
586
+ Regular-file validation and script traversal are tracked separately: an asset
587
+ later reached through CommonJS is parsed regardless of archive member order.
588
+ Exported declarations use the same scope collection as ordinary declarations.
589
+ Loader references are allowed only within recognized static declarations and
590
+ calls; inline module factories and unsupported namespace references fail closed.
591
+ Known loader calls require one literal specifier. Unresolved loader aliases,
592
+ computed bases, unsupported propagation and conflicting bindings report an
593
+ `Unresolvable dynamic loader` diagnostic instead of omitting dependencies.
594
+ In CommonJS scopes, declarations, parameters and assignments that shadow wrapper
595
+ loader names or inherited loader aliases must resolve to a recognized static
596
+ binding. Unknown bindings fail closed; uninitialized wrapper `var` declarations
597
+ retain the existing loader binding.
598
+ Imports from `node:module` or `module` must declare recognized factory or namespace
599
+ bindings, including string-named `createRequire` imports. Unsupported named imports,
600
+ side-effect imports, re-exports and dynamic namespace imports fail closed. Derived
601
+ aliases retain this policy in ESM scopes. A `__filename` factory base must resolve
602
+ to the unshadowed CommonJS wrapper binding; ESM factories use `import.meta.url`.
603
+ Loader bindings, factory results, module namespaces and their aliases cannot be
604
+ exported through ESM declarations/defaults/re-exports or CommonJS exports. They
605
+ produce an `unsupported loader propagation` diagnostic; analysis does not infer
606
+ loader behavior across module boundaries. Ordinary exported functions may still
607
+ make recognized static local loader calls.
608
+
609
+ This check proves the import closure of ordinary authored code in our own
610
+ packages within the supported static forms. It is not a security boundary against
611
+ deliberately obfuscated code. Unrecognized loader forms are rejected rather than
612
+ inferred; the check does not interpret dynamic code or prove arbitrary JavaScript
613
+ data flow. Known escape hatches are rejected: CommonJS wrapper `arguments`,
614
+ `eval`, `Function` construction/calls, `module.constructor`, `module.require`,
615
+ `process.mainModule`, global `require` properties, and `require.cache`,
616
+ `require.extensions` or `require.main` access. The strict metadata-read policy
617
+ also rejects aliases such as `const a = require.main`, which would otherwise
618
+ propagate an unclassified loader-capable object.
619
+
620
+ Wildcard exports exclude directory entries and treat only JavaScript-like
621
+ matches as scripts. Licenses and other extensionless wildcard matches remain
622
+ opaque; an import that reaches extensionless code still traverses its closure.
623
+
466
624
  The consumer policy pins Node 22.23.3, npm 12.2.0, pnpm 12.9.1, and TypeScript
467
625
  6.0.3 independently of the development toolchain. Published engines and TypeScript
468
626
  peers must support the consumer versions; ranges may include newer versions.
@@ -542,6 +700,9 @@ static contract; no older Node-to-ECMAScript mapping is inferred. Browser target
542
700
  are unsupported here: declare an explicit ECMAScript year. Types-only contracts reject any packed JavaScript. Nested
543
701
  package manifests and explicit module extensions determine parsing mode.
544
702
  Linked syntax inputs and unsafe archive paths fail before staging.
703
+ Invalid JavaScript encountered during import-closure analysis throws a `SyntaxError`
704
+ that names the packed member and retains the original parser error as its cause.
705
+ This can precede the declared-target and native Node checks.
545
706
 
546
707
  The reusable consumer job declares its exact scope in `stll-toolchain.json`:
547
708
 
@@ -623,6 +784,118 @@ exits successfully even when history is unreadable, reporting `changed: true`
623
784
  and `status: "unreadable"`. Use `changed` to force the full CI depth on both
624
785
  pull requests and merge-queue runs. An unreadable snapshot never skips checks.
625
786
 
787
+ Run repository-installed tools only after the workflow's existing trust decision.
788
+ The mandatory `workflow-trust` rule applies to PR workflows declaring a
789
+ `trust-check` job. Repository execution requires a direct dependency and a
790
+ condition requiring its `trusted == 'true'` output for every PR trigger.
791
+ Before that boundary, only literal output, bounded comparisons, `true`, `false`,
792
+ `exit`, bounded Git `rev-parse`/`merge-base` object queries, and GET-only `gh api`
793
+ reads are accepted. The trust job's structural `if`/`elif` comparisons use the
794
+ same checks; output may go only to `GITHUB_OUTPUT` or `/dev/null`. Variables must
795
+ be quoted, and command names and `printf` formats must be literal.
796
+
797
+ The following catalog is the production allowlist. Command kinds select the
798
+ corresponding bounded argument validator; a listed command does not authorize
799
+ arbitrary arguments. Git requires the stated number of explicit object refs.
800
+ Bootstrap action inputs are restricted to the listed keys; Bun requires a literal
801
+ version rather than a repository version file.
802
+
803
+ <!-- workflow-trust-bootstrap-catalog:start -->
804
+
805
+ ```json
806
+ {
807
+ "commands": {
808
+ "test": "comparison",
809
+ "[": "comparison",
810
+ "[[": "comparison",
811
+ "true": "status",
812
+ "false": "status",
813
+ "exit": "exit",
814
+ "echo": "output",
815
+ "printf": "format",
816
+ "git": "git",
817
+ "gh": "github"
818
+ },
819
+ "git": {
820
+ "rev-parse": {
821
+ "flags": ["--short", "--verify"],
822
+ "references": 1
823
+ },
824
+ "merge-base": {
825
+ "flags": ["--all", "--is-ancestor"],
826
+ "references": 2
827
+ }
828
+ },
829
+ "github": ["api"],
830
+ "actions": {
831
+ "actions/checkout": [
832
+ "fetch-depth",
833
+ "persist-credentials",
834
+ "ref",
835
+ "repository",
836
+ "submodules"
837
+ ],
838
+ "oven-sh/setup-bun": ["bun-version", "no-cache"]
839
+ }
840
+ }
841
+ ```
842
+
843
+ <!-- workflow-trust-bootstrap-catalog:end -->
844
+
845
+ Job containers and nonempty services require the job-level trust gate because
846
+ they start before steps and supply executable code. A step condition cannot
847
+ authorize their startup. Digest pinning alone does not approve an image for
848
+ pre-trust execution; the guard has no implicit image approval catalog.
849
+ Before trust, step actions must use a full SHA and match the explicit bootstrap
850
+ catalog: `actions/checkout` or `oven-sh/setup-bun`, with
851
+ bounded selector inputs. Other actions and unsupported inputs require the
852
+ job-level gate; a step condition cannot authorize them. Every job-level reusable workflow
853
+ call requires the job-level trust condition, including pinned calls without inputs.
854
+
855
+ Interpreters, sourced files, executable paths, package managers (including
856
+ metadata and script-disabled install modes), launchers, custom shells, and
857
+ unknown commands or syntax require trust. File readers, file-presence tests,
858
+ `jq`/`yq`, Git working-tree/history commands, and `actions/setup-node` require the
859
+ job-level gate. Unsupported options and query programs also require the gate. For example, gate the entire
860
+ detector job, including dependency installation:
861
+
862
+ ```yaml
863
+ check-changes:
864
+ needs: [trust-check]
865
+ if: >-
866
+ always() && !cancelled()
867
+ && ((github.event_name != 'pull_request'
868
+ && github.event_name != 'pull_request_target')
869
+ || needs.trust-check.outputs.trusted == 'true')
870
+ runs-on: ubuntu-latest
871
+ steps:
872
+ # Check out, set up Bun, install dependencies, then run the shared detector.
873
+ - run: bunx --no-install stll-toolchain-changed --since "$BASE_REF"
874
+ ```
875
+
876
+ Actions interpolation is checked before shell quoting or comments. Only the
877
+ bounded identity expressions `github.sha`, `github.run_id`, `github.run_attempt`
878
+ and `github.repository` may appear directly in pre-trust scripts. Other contexts,
879
+ functions and matrix values require environment indirection or the trust gate;
880
+ the guard does not infer that an expression is safe from its shell quotes.
881
+ Pass contributor-controlled metadata through a supported data environment key
882
+ (`PR_TITLE`, `HEAD_REF`, `ISSUE_BODY` or `COMMENT_BODY`) and quote its expansion:
883
+
884
+ ```yaml
885
+ - env:
886
+ PR_TITLE: ${{ github.event.pull_request.title }}
887
+ run: echo "$PR_TITLE"
888
+ ```
889
+
890
+ The trust job must classify each PR trigger used by the workflow. Main/manual
891
+ jobs remain runnable when the PR-only trust job is skipped. Job or step
892
+ conditions may prove that execution is limited to non-PR events; event string
893
+ comparisons follow Actions case-insensitive equality. The guard
894
+ proves only supported static metadata commands and boolean conditions; unknown
895
+ commands and conditions cannot authorize execution. It does not validate the
896
+ trust job's authorization policy.
897
+ Workflows with no declared trust job retain their existing fork-CI policy.
898
+
626
899
  The comparison includes Bun declarations, Node selectors and support ranges,
627
900
  resolved TypeScript compiler versions (including workspace and compatibility
628
901
  aliases), oxlint, oxlint-tsgolint, oxfmt, and shared tooling pins. Lockfile
@@ -701,3 +974,43 @@ another exact stable release. Each record contains exactly `pin`, `reason`,
701
974
  `tracking` (a GitHub issue URL) and `expires` (a future `YYYY-MM-DD` date).
702
975
  Channels, expired records and `rust-version` rule opt-outs fail validation.
703
976
  See the [root policy guide](../../README.md) for a complete declaration.
977
+
978
+ `stll-toolchain-check` requires tracked JavaScript and TypeScript files selected by type-aware lint to belong to declared TypeScript projects. See the [Node scripts project recipe](https://github.com/stella/tooling/blob/main/README.md) for scripts outside the library project. The check inventories Oxlint-selected files and expands compiler configurations; it does not run lint or type checking.
979
+
980
+ The project-ownership guard models declared package-manager scripts, including
981
+ literal filtered Bun build bodies and their script aliases with the selected
982
+ workspace as the working directory. It inventories supported linter invocations;
983
+ it never executes build bodies. Unrecognized linter launchers and script forms
984
+ fail closed. This is an ownership check for ordinary authored package scripts,
985
+ not a security boundary against deliberately obfuscated code or lint invocations
986
+ hidden inside arbitrary program bodies. Unsupported forms require an explicit
987
+ supported script invocation or a separately tested extension to the model.
988
+
989
+ ## Quarantine preset and expiry checks
990
+
991
+ Run `bunx --no-install stll-quarantine --sync` before installation to create the
992
+ five-day Bun preset and synchronize permanent exact-name `@stll/` exemptions.
993
+ The command derives registry identities from dependency declarations and the
994
+ root Bun lockfile, including transitive platform packages. Existing first-party
995
+ creation timestamps remain stable across runs; they are not expiries.
996
+
997
+ Third-party entries require `# quarantine-expires: YYYY-MM-DDTHH:mm:ss.sssZ`.
998
+ `stll-toolchain-check` warns 48 hours before expiry; warnings continue through
999
+ the existing 24-hour removal grace, after which expired entries fail. The
1000
+ quarantine rule cannot be opted out. `stll-quarantine --check` is read-only;
1001
+ `--prune` removes expired third-party entries without removing first-party ones.
1002
+ `--help` lists modes. Linked output files are rejected.
1003
+
1004
+ `@stll/oxlint-config/quarantine` exports the Node-compatible repository helpers
1005
+ `checkRepositoryQuarantine`, `synchronizeRepositoryQuarantine`, and
1006
+ `pruneRepositoryQuarantine`, accepting a tracked `files` snapshot and optional
1007
+ `now`. The toolchain API reports advisory expiry messages through `onWarning`;
1008
+ its returned diagnostics remain failures. Snapshot readers keep lockfiles
1009
+ opaque by default; `{ bunLock: "content" }` explicitly requests the root graph
1010
+ when quarantine identity checks need it.
1011
+
1012
+ The shared scheduled workflow imports the same dependency-free policy core
1013
+ from an exact tooling commit; it does not install consumer dependencies to
1014
+ perform enforcement or pruning. Its stable removal branch updates one PR per
1015
+ repository. See the [root guide](../../README.md#package-quarantine) for the
1016
+ inline annotation format and adoption boundary.
@@ -1,46 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_generated_file = require("./generated-file.cjs");
3
4
  const require_toolchain_inputs = require("./toolchain-inputs.cjs");
4
5
  const require_toolchain_guard = require("./toolchain-guard.cjs");
5
6
  const require_toolchain_schema = require("./toolchain-schema.cjs");
6
- let node_fs = require("node:fs");
7
- let node_path = require("node:path");
8
7
  let node_fs_promises = require("node:fs/promises");
8
+ let node_path = require("node:path");
9
+ let node_fs = require("node:fs");
9
10
  //#region src/cloud-setup-cli.ts
10
11
  const usage = "Usage: stll-cloud-setup (run from repository root)\n stll-cloud-setup --help";
11
- const optionalStat = async (path) => {
12
- try {
13
- return await (0, node_fs_promises.lstat)(path);
14
- } catch (error) {
15
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
16
- throw error;
17
- }
18
- };
19
- const checkOutput = async (destination) => {
20
- const parent = (0, node_path.dirname)(destination);
21
- const parentStat = await optionalStat(parent);
22
- if (parentStat !== void 0 && (parentStat.isSymbolicLink() || !parentStat.isDirectory())) throw new Error("cloud setup parent must be a real repository directory");
23
- const destinationStat = await optionalStat(destination);
24
- if (destinationStat !== void 0 && (destinationStat.isSymbolicLink() || !destinationStat.isFile() || destinationStat.nlink !== 1)) throw new Error("cloud setup destination must be a single-link regular repository file");
25
- return destinationStat;
26
- };
27
- const writeOutput = async ({ destination, text, mode }) => {
28
- await checkOutput(destination);
29
- await (0, node_fs_promises.mkdir)((0, node_path.dirname)(destination), { recursive: true });
30
- const output = await (0, node_fs_promises.open)(destination, node_fs.constants.O_WRONLY | node_fs.constants.O_CREAT | node_fs.constants.O_NOFOLLOW, mode);
31
- try {
32
- const opened = await output.stat();
33
- if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
34
- await output.truncate(0);
35
- await output.writeFile(text, "utf8");
36
- await output.chmod(mode);
37
- } finally {
38
- await output.close();
39
- }
40
- };
41
12
  const readClaudeSettings = async (destination) => {
42
13
  try {
43
- if (await checkOutput(destination) === void 0) return void 0;
14
+ if (await require_generated_file.checkGeneratedOutput(destination) === void 0) return void 0;
44
15
  const input = await (0, node_fs_promises.open)(destination, node_fs.constants.O_RDONLY | node_fs.constants.O_NOFOLLOW);
45
16
  try {
46
17
  const opened = await input.stat();
@@ -65,7 +36,20 @@ const main = async () => {
65
36
  if (args.length !== 0) throw new Error(usage);
66
37
  const root = await (0, node_fs_promises.realpath)(process.cwd());
67
38
  const { files, diagnostics } = require_toolchain_guard.readToolchainInputs(root);
68
- if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}${path === ".claude/settings.json" ? `; ${require_toolchain_inputs.cloudSessionStartFix}` : ""}`).join("\n"));
39
+ const destination = (0, node_path.join)(root, require_toolchain_inputs.cloudSetupPath);
40
+ const settingsDestination = (0, node_path.join)(root, require_toolchain_inputs.cloudClaudeSettingsPath);
41
+ const settings = await readClaudeSettings(settingsDestination);
42
+ const remainingDiagnostics = await require_generated_file.generationDiagnostics({
43
+ diagnostics,
44
+ outputs: [{
45
+ relative: require_toolchain_inputs.cloudSetupPath,
46
+ destination
47
+ }, {
48
+ relative: require_toolchain_inputs.cloudClaudeSettingsPath,
49
+ destination: settingsDestination
50
+ }]
51
+ });
52
+ if (remainingDiagnostics.length > 0) throw new Error(remainingDiagnostics.map(({ path, message }) => `${path}: ${message}${path === ".claude/settings.json" ? `; ${require_toolchain_inputs.cloudSessionStartFix}` : ""}`).join("\n"));
69
53
  const text = files["stll-toolchain.json"];
70
54
  if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
71
55
  const { cloud, rustExceptions } = require_toolchain_guard.parseToolchainConfiguration(JSON.parse(text));
@@ -80,17 +64,13 @@ const main = async () => {
80
64
  files,
81
65
  rustExceptions
82
66
  });
83
- const destination = (0, node_path.join)(root, require_toolchain_inputs.cloudSetupPath);
84
- await checkOutput(destination);
85
- const settingsDestination = (0, node_path.join)(root, require_toolchain_inputs.cloudClaudeSettingsPath);
86
- const settings = await readClaudeSettings(settingsDestination);
87
67
  const mergedSettings = require_toolchain_inputs.mergeCloudClaudeSettings(settings?.text);
88
- await writeOutput({
68
+ await require_generated_file.writeGeneratedFile({
89
69
  destination,
90
70
  text: script,
91
71
  mode: 493
92
72
  });
93
- if (mergedSettings !== settings?.text) await writeOutput({
73
+ if (mergedSettings !== settings?.text) await require_generated_file.writeGeneratedFile({
94
74
  destination: settingsDestination,
95
75
  text: mergedSettings,
96
76
  mode: settings?.mode ?? 420
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-setup-cli.cjs","names":["lstat","dirname","mkdir","open","constants","cloudClaudeSettingsPath","cloudSessionStartFix","realpath","readToolchainInputs","parseToolchainConfiguration","parseToolchainPolicy","readFile","generateCloudSetup","join","cloudSetupPath","mergeCloudClaudeSettings"],"sources":["../src/cloud-setup-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { constants } from \"node:fs\";\nimport { lstat, mkdir, open, readFile, realpath } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { cloudSetupPath, generateCloudSetup } from \"./cloud-setup\";\nimport {\n cloudClaudeSettingsPath,\n cloudSessionStartFix,\n mergeCloudClaudeSettings,\n} from \"./cloud-setup-claude\";\nimport {\n parseToolchainConfiguration,\n readToolchainInputs,\n} from \"./toolchain-guard\";\nimport { parseToolchainPolicy } from \"./toolchain-schema\";\n\nconst usage =\n \"Usage: stll-cloud-setup (run from repository root)\\n stll-cloud-setup --help\";\nconst optionalStat = async (path: string) => {\n try {\n return await lstat(path);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\")\n return undefined;\n throw error;\n }\n};\n\nconst checkOutput = async (destination: string) => {\n const parent = dirname(destination);\n const parentStat = await optionalStat(parent);\n if (\n parentStat !== undefined &&\n (parentStat.isSymbolicLink() || !parentStat.isDirectory())\n )\n throw new Error(\"cloud setup parent must be a real repository directory\");\n const destinationStat = await optionalStat(destination);\n if (\n destinationStat !== undefined &&\n (destinationStat.isSymbolicLink() ||\n !destinationStat.isFile() ||\n destinationStat.nlink !== 1)\n )\n throw new Error(\n \"cloud setup destination must be a single-link regular repository file\",\n );\n return destinationStat;\n};\n\ntype WriteOutputOptions = {\n destination: string;\n text: string;\n mode: number;\n};\n\nconst writeOutput = async ({ destination, text, mode }: WriteOutputOptions) => {\n await checkOutput(destination);\n await mkdir(dirname(destination), { recursive: true });\n const output = await open(\n destination,\n constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW,\n mode,\n );\n try {\n const opened = await output.stat();\n if (!opened.isFile() || opened.nlink !== 1)\n throw new Error(\n \"cloud setup destination must be a single-link regular repository file\",\n );\n await output.truncate(0);\n await output.writeFile(text, \"utf8\");\n await output.chmod(mode);\n } finally {\n await output.close();\n }\n};\n\nconst readClaudeSettings = async (destination: string) => {\n try {\n if ((await checkOutput(destination)) === undefined) return undefined;\n const input = await open(\n destination,\n constants.O_RDONLY | constants.O_NOFOLLOW,\n );\n try {\n const opened = await input.stat();\n if (!opened.isFile() || opened.nlink !== 1)\n throw new Error(\n \"settings must be a single-link regular repository file\",\n );\n return { text: await input.readFile(\"utf8\"), mode: opened.mode & 0o777 };\n } finally {\n await input.close();\n }\n } catch (error) {\n throw new Error(\n `cannot safely merge ${cloudClaudeSettingsPath}: ${error instanceof Error ? error.message : String(error)}; ${cloudSessionStartFix}`,\n );\n }\n};\n\nconst main = async () => {\n const args = process.argv.slice(2);\n if (args.length === 1 && args.at(0) === \"--help\") {\n process.stdout.write(`${usage}\\n`);\n return;\n }\n if (args.length !== 0) throw new Error(usage);\n const root = await realpath(process.cwd());\n const { files, diagnostics } = readToolchainInputs(root);\n if (diagnostics.length > 0)\n throw new Error(\n diagnostics\n .map(\n ({ path, message }) =>\n `${path}: ${message}${path === cloudClaudeSettingsPath ? `; ${cloudSessionStartFix}` : \"\"}`,\n )\n .join(\"\\n\"),\n );\n const text = files[\"stll-toolchain.json\"];\n if (text === undefined)\n throw new Error(\"requires tracked stll-toolchain.json\");\n const { cloud, rustExceptions } = parseToolchainConfiguration(\n JSON.parse(text),\n );\n if (cloud === undefined)\n throw new Error(\"requires an explicit cloud declaration\");\n const nodeVersion = files[\".node-version\"];\n if (nodeVersion === undefined)\n throw new Error(\"requires tracked root .node-version\");\n const policy = parseToolchainPolicy(\n JSON.parse(\n await readFile(new URL(\"../toolchain.json\", import.meta.url), \"utf8\"),\n ),\n );\n const script = generateCloudSetup({\n policy,\n cloud,\n nodeVersion: nodeVersion.trim(),\n files,\n rustExceptions,\n });\n const destination = join(root, cloudSetupPath);\n await checkOutput(destination);\n const settingsDestination = join(root, cloudClaudeSettingsPath);\n const settings = await readClaudeSettings(settingsDestination);\n const mergedSettings = mergeCloudClaudeSettings(settings?.text);\n await writeOutput({ destination, text: script, mode: 0o755 });\n if (mergedSettings !== settings?.text)\n await writeOutput({\n destination: settingsDestination,\n text: mergedSettings,\n mode: settings?.mode ?? 0o644,\n });\n process.stdout.write(\n `Generated ${cloudSetupPath} and ${cloudClaudeSettingsPath}\\n`,\n );\n};\n\nmain().catch((error: unknown) => {\n process.stderr.write(\n `cloud-setup: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exitCode = 1;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cloud-setup-cli.cjs","names":["checkGeneratedOutput","open","constants","cloudClaudeSettingsPath","cloudSessionStartFix","realpath","readToolchainInputs","join","cloudSetupPath","generationDiagnostics","parseToolchainConfiguration","parseToolchainPolicy","readFile","generateCloudSetup","mergeCloudClaudeSettings","writeGeneratedFile"],"sources":["../src/cloud-setup-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { constants } from \"node:fs\";\nimport { open, readFile, realpath } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { cloudSetupPath, generateCloudSetup } from \"./cloud-setup\";\nimport {\n cloudClaudeSettingsPath,\n cloudSessionStartFix,\n mergeCloudClaudeSettings,\n} from \"./cloud-setup-claude\";\nimport {\n checkGeneratedOutput,\n generationDiagnostics,\n writeGeneratedFile,\n} from \"./generated-file\";\nimport {\n parseToolchainConfiguration,\n readToolchainInputs,\n} from \"./toolchain-guard\";\nimport { parseToolchainPolicy } from \"./toolchain-schema\";\n\nconst usage =\n \"Usage: stll-cloud-setup (run from repository root)\\n stll-cloud-setup --help\";\nconst readClaudeSettings = async (destination: string) => {\n try {\n if ((await checkGeneratedOutput(destination)) === undefined)\n return undefined;\n const input = await open(\n destination,\n constants.O_RDONLY | constants.O_NOFOLLOW,\n );\n try {\n const opened = await input.stat();\n if (!opened.isFile() || opened.nlink !== 1)\n throw new Error(\n \"settings must be a single-link regular repository file\",\n );\n return { text: await input.readFile(\"utf8\"), mode: opened.mode & 0o777 };\n } finally {\n await input.close();\n }\n } catch (error) {\n throw new Error(\n `cannot safely merge ${cloudClaudeSettingsPath}: ${error instanceof Error ? error.message : String(error)}; ${cloudSessionStartFix}`,\n );\n }\n};\n\nconst main = async () => {\n const args = process.argv.slice(2);\n if (args.length === 1 && args.at(0) === \"--help\") {\n process.stdout.write(`${usage}\\n`);\n return;\n }\n if (args.length !== 0) throw new Error(usage);\n const root = await realpath(process.cwd());\n const { files, diagnostics } = readToolchainInputs(root);\n const destination = join(root, cloudSetupPath);\n const settingsDestination = join(root, cloudClaudeSettingsPath);\n // Validate settings separately to retain actionable merge diagnostics.\n const settings = await readClaudeSettings(settingsDestination);\n const remainingDiagnostics = await generationDiagnostics({\n diagnostics,\n outputs: [\n { relative: cloudSetupPath, destination },\n { relative: cloudClaudeSettingsPath, destination: settingsDestination },\n ],\n });\n if (remainingDiagnostics.length > 0)\n throw new Error(\n remainingDiagnostics\n .map(\n ({ path, message }) =>\n `${path}: ${message}${path === cloudClaudeSettingsPath ? `; ${cloudSessionStartFix}` : \"\"}`,\n )\n .join(\"\\n\"),\n );\n const text = files[\"stll-toolchain.json\"];\n if (text === undefined)\n throw new Error(\"requires tracked stll-toolchain.json\");\n const { cloud, rustExceptions } = parseToolchainConfiguration(\n JSON.parse(text),\n );\n if (cloud === undefined)\n throw new Error(\"requires an explicit cloud declaration\");\n const nodeVersion = files[\".node-version\"];\n if (nodeVersion === undefined)\n throw new Error(\"requires tracked root .node-version\");\n const policy = parseToolchainPolicy(\n JSON.parse(\n await readFile(new URL(\"../toolchain.json\", import.meta.url), \"utf8\"),\n ),\n );\n const script = generateCloudSetup({\n policy,\n cloud,\n nodeVersion: nodeVersion.trim(),\n files,\n rustExceptions,\n });\n const mergedSettings = mergeCloudClaudeSettings(settings?.text);\n await writeGeneratedFile({ destination, text: script, mode: 0o755 });\n if (mergedSettings !== settings?.text)\n await writeGeneratedFile({\n destination: settingsDestination,\n text: mergedSettings,\n mode: settings?.mode ?? 0o644,\n });\n process.stdout.write(\n `Generated ${cloudSetupPath} and ${cloudClaudeSettingsPath}\\n`,\n );\n};\n\nmain().catch((error: unknown) => {\n process.stderr.write(\n `cloud-setup: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exitCode = 1;\n});\n"],"mappings":"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"}