@stll/oxlint-config 1.3.0 → 1.4.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 +254 -17
- package/dist/cloud-setup-cli.cjs +18 -38
- package/dist/cloud-setup-cli.cjs.map +1 -1
- package/dist/cloud-setup-cli.mjs +21 -41
- package/dist/cloud-setup-cli.mjs.map +1 -1
- package/dist/consumer-compat-cli.cjs +50 -249
- package/dist/consumer-compat-cli.cjs.map +1 -1
- package/dist/consumer-compat-cli.mjs +6 -205
- package/dist/consumer-compat-cli.mjs.map +1 -1
- package/dist/consumer-node-support.cjs +497 -0
- package/dist/consumer-node-support.cjs.map +1 -0
- package/dist/consumer-node-support.mjs +381 -0
- package/dist/consumer-node-support.mjs.map +1 -0
- package/dist/dependabot-cli.cjs +67 -0
- package/dist/dependabot-cli.cjs.map +1 -0
- package/dist/dependabot-cli.d.cts +1 -0
- package/dist/dependabot-cli.d.mts +1 -0
- package/dist/dependabot-cli.mjs +67 -0
- package/dist/dependabot-cli.mjs.map +1 -0
- package/dist/dependabot.cjs +5 -0
- package/dist/dependabot.d.cts +108 -0
- package/dist/dependabot.d.mts +108 -0
- package/dist/dependabot.mjs +3 -0
- package/dist/generated-file.cjs +61 -0
- package/dist/generated-file.cjs.map +1 -0
- package/dist/generated-file.mjs +44 -0
- package/dist/generated-file.mjs.map +1 -0
- package/dist/publish-artifact-syntax.cjs +1309 -0
- package/dist/publish-artifact-syntax.cjs.map +1 -0
- package/dist/publish-artifact-syntax.mjs +1265 -0
- package/dist/publish-artifact-syntax.mjs.map +1 -0
- package/dist/publish-contract-cli.cjs +53 -12
- package/dist/publish-contract-cli.cjs.map +1 -1
- package/dist/publish-contract-cli.mjs +49 -8
- package/dist/publish-contract-cli.mjs.map +1 -1
- package/dist/toolchain-changed.cjs +5 -1
- package/dist/toolchain-changed.cjs.map +1 -1
- package/dist/toolchain-changed.mjs +6 -2
- package/dist/toolchain-changed.mjs.map +1 -1
- package/dist/toolchain-dependabot.cjs +455 -0
- package/dist/toolchain-dependabot.cjs.map +1 -0
- package/dist/toolchain-dependabot.mjs +423 -0
- package/dist/toolchain-dependabot.mjs.map +1 -0
- package/dist/toolchain-guard.cjs +1096 -424
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +1085 -413
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +122 -22
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +117 -23
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/dist/toolchain-schema.cjs +0 -493
- package/dist/toolchain-schema.cjs.map +1 -1
- package/dist/toolchain-schema.mjs +3 -379
- package/dist/toolchain-schema.mjs.map +1 -1
- package/dist/typecheck-parity.cjs +1 -1
- package/dist/typecheck-probe-cli.cjs +1 -1
- package/dist/typecheck-probe-cli.cjs.map +1 -1
- package/dist/typecheck-probe-cli.mjs +1 -1
- package/dist/typecheck-probe-cli.mjs.map +1 -1
- package/package.json +13 -1
- package/dist/publish-contract.cjs +0 -431
- package/dist/publish-contract.cjs.map +0 -1
- package/dist/publish-contract.mjs +0 -396
- 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
|
|
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`.
|
|
263
|
-
|
|
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,37 @@ 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
|
+
For programmatic generation, the `@stll/oxlint-config/dependabot` subpath exports
|
|
304
|
+
`generateDependabotConfig({ files, policy })` and `parseToolchainPolicy`:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import {
|
|
308
|
+
generateDependabotConfig,
|
|
309
|
+
parseToolchainPolicy,
|
|
310
|
+
} from "@stll/oxlint-config/dependabot";
|
|
311
|
+
import sharedPolicy from "@stll/oxlint-config/toolchain.json";
|
|
312
|
+
|
|
313
|
+
const yaml = generateDependabotConfig({
|
|
314
|
+
files: trackedConfiguration,
|
|
315
|
+
policy: parseToolchainPolicy(sharedPolicy).dependabot,
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The root lint-config entry does not load the tooling parser dependencies
|
|
320
|
+
`yaml`, `picomatch` or `smol-toml`; they belong to the tooling subpath and CLIs.
|
|
321
|
+
`files` maps existing tracked repository-relative configuration paths to their text.
|
|
322
|
+
Deleted tracked manifests are absent from the generator snapshot. Use the
|
|
323
|
+
CLI when a tracked snapshot is not already available; no source imports are needed.
|
|
324
|
+
|
|
291
325
|
## Shared cloud setup
|
|
292
326
|
|
|
293
327
|
Add an explicit declaration to tracked root `stll-toolchain.json`:
|
|
@@ -305,6 +339,7 @@ Add an explicit declaration to tracked root `stll-toolchain.json`:
|
|
|
305
339
|
},
|
|
306
340
|
"tools": {
|
|
307
341
|
"cargo-nextest": "0.9.148",
|
|
342
|
+
"gitleaks": "8.30.1",
|
|
308
343
|
"wasm-bindgen-cli": "0.2.126",
|
|
309
344
|
"uv": "0.13.1",
|
|
310
345
|
"time": "1.9"
|
|
@@ -336,10 +371,17 @@ the minimal profile and a pinned, checksum-verified rustup installer. Rust-enabl
|
|
|
336
371
|
setup also provisions Ubuntu's `build-essential` package for native compilation
|
|
337
372
|
and linking.
|
|
338
373
|
|
|
374
|
+
`cloud.rust.additionalToolchains` explicitly selects extra exact compiler releases.
|
|
375
|
+
Each must match the shared pin or a valid `rustExceptions` entry and differ from the
|
|
376
|
+
primary compiler. Extras are installed once with the minimal profile and verified
|
|
377
|
+
offline at session start. An exception alone authorizes a version; it does not
|
|
378
|
+
provision it. This setting installs neither Hawk nor extra compiler components.
|
|
379
|
+
Generated scripts contain no trailing whitespace and end with one newline.
|
|
380
|
+
|
|
339
381
|
`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
|
|
382
|
+
`wasm-bindgen-cli`, `gitleaks`, `uv`, and `time`. Each value must be an exact release supported by
|
|
341
383
|
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
|
|
384
|
+
generation. `cargo-nextest`, `wasm-bindgen-cli`, `gitleaks`, and `uv` use versioned upstream Linux binaries with committed
|
|
343
385
|
SHA256 pins for amd64 and arm64; setup does not build them with `cargo install`.
|
|
344
386
|
`time` installs GNU time (`/usr/bin/time`, needed for `/usr/bin/time -v`) from
|
|
345
387
|
Ubuntu's `time` package with bounded apt; its value is the upstream release
|
|
@@ -370,8 +412,8 @@ The command writes `.agents/cloud-setup.sh` with executable permissions and
|
|
|
370
412
|
merges a `SessionStart` hook into `.claude/settings.json`. The hook runs
|
|
371
413
|
`bash "$CLAUDE_PROJECT_DIR/.agents/cloud-setup.sh" start` only when
|
|
372
414
|
`CLAUDE_CODE_REMOTE=true`, including sessions that reuse cached filesystems and
|
|
373
|
-
sessions started from a repository subdirectory. Generation replaces the
|
|
374
|
-
|
|
415
|
+
sessions started from a repository subdirectory. Generation replaces the exact hooks written by 1.1.0 and 1.3.1 in place.
|
|
416
|
+
After successful startup, it appends the
|
|
375
417
|
managed shell profile's literal exports to `CLAUDE_ENV_FILE` when available, so
|
|
376
418
|
subsequent Claude commands select pinned runtimes and tools before preinstalled
|
|
377
419
|
versions while retaining their inherited `PATH`. The settings also set
|
|
@@ -379,8 +421,12 @@ versions while retaining their inherited `PATH`. The settings also set
|
|
|
379
421
|
attribution to commits or pull requests. Existing environment-file
|
|
380
422
|
content, unrelated settings, and hooks are preserved. Invalid JSON, disabled
|
|
381
423
|
hooks, conflicting cloud startup entries, or a different `attribution` value stop
|
|
382
|
-
generation with a repair instruction.
|
|
424
|
+
generation with a repair instruction. Startup or environment persistence failures
|
|
425
|
+
produce actionable session context and a stderr reason; SessionStart cannot block
|
|
426
|
+
a session. Successful startup is silent on stdout. Commit both generated files.
|
|
383
427
|
Generation refuses symlink parents or destinations and hard-linked destinations.
|
|
428
|
+
Generation recreates its deleted tracked output files and requires unrelated
|
|
429
|
+
tracked inputs to remain readable.
|
|
384
430
|
`stll-toolchain-check` rejects missing, drifted, or undeclared tracked scripts,
|
|
385
431
|
missing or conflicting startup hooks, and missing or different attribution
|
|
386
432
|
settings with mandatory `cloud-setup-drift`.
|
|
@@ -392,6 +438,11 @@ prints usage without reading repository configuration.
|
|
|
392
438
|
| Networked install | `bash .agents/cloud-setup.sh install` |
|
|
393
439
|
| Offline session startup | `bash .agents/cloud-setup.sh start` |
|
|
394
440
|
|
|
441
|
+
Runtime installation repairs and verifies a missing or empty system CA bundle
|
|
442
|
+
offline before any apt refresh or download, including when the certificate package
|
|
443
|
+
is already current. If the certificate updater is unavailable or cannot restore
|
|
444
|
+
trust, restore the base image offline before rerunning installation.
|
|
445
|
+
|
|
395
446
|
Both commands can be rerun. Missing or unknown subcommands print usage and
|
|
396
447
|
exit 2; there is no implicit lifecycle mode. The script supports Ubuntu 24.04
|
|
397
448
|
with root access or passwordless sudo and fails when required capabilities
|
|
@@ -432,14 +483,14 @@ or the configured package hosts; a redirected download names its original host.
|
|
|
432
483
|
|
|
433
484
|
Allow these direct download hosts for the generated installer:
|
|
434
485
|
|
|
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
|
|
486
|
+
| Host | Downloads |
|
|
487
|
+
| ---------------------- | ---------------------------------------------------------------------------------- |
|
|
488
|
+
| `nodejs.org` | Node archives and SHA256 manifests |
|
|
489
|
+
| `github.com` | Bun archives and checksums; pinned nextest, wasm-bindgen, gitleaks and uv releases |
|
|
490
|
+
| `static.rust-lang.org` | Pinned rustup-init, Rust manifests, toolchains, components and targets |
|
|
491
|
+
| `www.postgresql.org` | PostgreSQL repository signing key |
|
|
492
|
+
| `apt.postgresql.org` | PostgreSQL packages and repository metadata |
|
|
493
|
+
| `download.valkey.io` | Pinned Valkey binary packages |
|
|
443
494
|
|
|
444
495
|
GitHub release downloads redirect to `release-assets.githubusercontent.com`;
|
|
445
496
|
allow that host as well. This includes Bun and the optional prebuilt tools.
|
|
@@ -463,6 +514,66 @@ resolved JavaScript targets, and entry points. `stll-publish-contract --write`
|
|
|
463
514
|
records an explicit contract decision as a reviewable diff; it also validates
|
|
464
515
|
consumer support and cannot accept development-only requirements.
|
|
465
516
|
|
|
517
|
+
Use `stll-publish-contract --artifact path/to/package.tgz` to check an existing
|
|
518
|
+
release archive after the workspace metadata checks. It validates the static
|
|
519
|
+
literal import closure within the packed package and compares the archive's
|
|
520
|
+
manifest with its declared public workspace package's committed contract and
|
|
521
|
+
resolved build target. The command never packs
|
|
522
|
+
or executes the archive, accepts exactly one archive, and cannot combine
|
|
523
|
+
`--artifact` with `--write`. Unknown or private package artifacts fail. Bare external
|
|
524
|
+
package dependencies are outside this local archive check. Dynamic imports require
|
|
525
|
+
literal specifiers; computed values cannot exclude the module loader namespace.
|
|
526
|
+
The reader follows literal ESM imports/re-exports, CommonJS
|
|
527
|
+
require/resolve calls and named, destructured or namespace `createRequire` bindings based on
|
|
528
|
+
`import.meta.url` or `__filename`. Asset references must be regular packed files;
|
|
529
|
+
non-JavaScript ESM assets are not parsed as scripts. Recursive import mappings
|
|
530
|
+
are rejected, including expanding wildcard aliases.
|
|
531
|
+
Node resolves collected specifiers against a temporary archive filesystem:
|
|
532
|
+
`createRequire(importer).resolve` for CommonJS and `import.meta.resolve` with the
|
|
533
|
+
importer's URL for ESM, using their respective default conditions. The worker
|
|
534
|
+
reads manifests and file existence without loading package code. Only manifests
|
|
535
|
+
need contents; other members use inert placeholders, and selected targets must
|
|
536
|
+
still be regular packed members. External import-map dependencies use inert
|
|
537
|
+
metadata outside the packed package boundary. Unknown external mapping forms
|
|
538
|
+
fail explicitly. Temporary files and the worker are removed after validation.
|
|
539
|
+
Regular-file validation and script traversal are tracked separately: an asset
|
|
540
|
+
later reached through CommonJS is parsed regardless of archive member order.
|
|
541
|
+
Exported declarations use the same scope collection as ordinary declarations.
|
|
542
|
+
Loader references are allowed only within recognized static declarations and
|
|
543
|
+
calls; inline module factories and unsupported namespace references fail closed.
|
|
544
|
+
Known loader calls require one literal specifier. Unresolved loader aliases,
|
|
545
|
+
computed bases, unsupported propagation and conflicting bindings report an
|
|
546
|
+
`Unresolvable dynamic loader` diagnostic instead of omitting dependencies.
|
|
547
|
+
In CommonJS scopes, declarations, parameters and assignments that shadow wrapper
|
|
548
|
+
loader names or inherited loader aliases must resolve to a recognized static
|
|
549
|
+
binding. Unknown bindings fail closed; uninitialized wrapper `var` declarations
|
|
550
|
+
retain the existing loader binding.
|
|
551
|
+
Imports from `node:module` or `module` must declare recognized factory or namespace
|
|
552
|
+
bindings, including string-named `createRequire` imports. Unsupported named imports,
|
|
553
|
+
side-effect imports, re-exports and dynamic namespace imports fail closed. Derived
|
|
554
|
+
aliases retain this policy in ESM scopes. A `__filename` factory base must resolve
|
|
555
|
+
to the unshadowed CommonJS wrapper binding; ESM factories use `import.meta.url`.
|
|
556
|
+
Loader bindings, factory results, module namespaces and their aliases cannot be
|
|
557
|
+
exported through ESM declarations/defaults/re-exports or CommonJS exports. They
|
|
558
|
+
produce an `unsupported loader propagation` diagnostic; analysis does not infer
|
|
559
|
+
loader behavior across module boundaries. Ordinary exported functions may still
|
|
560
|
+
make recognized static local loader calls.
|
|
561
|
+
|
|
562
|
+
This check proves the import closure of ordinary authored code in our own
|
|
563
|
+
packages within the supported static forms. It is not a security boundary against
|
|
564
|
+
deliberately obfuscated code. Unrecognized loader forms are rejected rather than
|
|
565
|
+
inferred; the check does not interpret dynamic code or prove arbitrary JavaScript
|
|
566
|
+
data flow. Known escape hatches are rejected: CommonJS wrapper `arguments`,
|
|
567
|
+
`eval`, `Function` construction/calls, `module.constructor`, `module.require`,
|
|
568
|
+
`process.mainModule`, global `require` properties, and `require.cache`,
|
|
569
|
+
`require.extensions` or `require.main` access. The strict metadata-read policy
|
|
570
|
+
also rejects aliases such as `const a = require.main`, which would otherwise
|
|
571
|
+
propagate an unclassified loader-capable object.
|
|
572
|
+
|
|
573
|
+
Wildcard exports exclude directory entries and treat only JavaScript-like
|
|
574
|
+
matches as scripts. Licenses and other extensionless wildcard matches remain
|
|
575
|
+
opaque; an import that reaches extensionless code still traverses its closure.
|
|
576
|
+
|
|
466
577
|
The consumer policy pins Node 22.23.3, npm 12.2.0, pnpm 12.9.1, and TypeScript
|
|
467
578
|
6.0.3 independently of the development toolchain. Published engines and TypeScript
|
|
468
579
|
peers must support the consumer versions; ranges may include newer versions.
|
|
@@ -542,6 +653,9 @@ static contract; no older Node-to-ECMAScript mapping is inferred. Browser target
|
|
|
542
653
|
are unsupported here: declare an explicit ECMAScript year. Types-only contracts reject any packed JavaScript. Nested
|
|
543
654
|
package manifests and explicit module extensions determine parsing mode.
|
|
544
655
|
Linked syntax inputs and unsafe archive paths fail before staging.
|
|
656
|
+
Invalid JavaScript encountered during import-closure analysis throws a `SyntaxError`
|
|
657
|
+
that names the packed member and retains the original parser error as its cause.
|
|
658
|
+
This can precede the declared-target and native Node checks.
|
|
545
659
|
|
|
546
660
|
The reusable consumer job declares its exact scope in `stll-toolchain.json`:
|
|
547
661
|
|
|
@@ -623,6 +737,118 @@ exits successfully even when history is unreadable, reporting `changed: true`
|
|
|
623
737
|
and `status: "unreadable"`. Use `changed` to force the full CI depth on both
|
|
624
738
|
pull requests and merge-queue runs. An unreadable snapshot never skips checks.
|
|
625
739
|
|
|
740
|
+
Run repository-installed tools only after the workflow's existing trust decision.
|
|
741
|
+
The mandatory `workflow-trust` rule applies to PR workflows declaring a
|
|
742
|
+
`trust-check` job. Repository execution requires a direct dependency and a
|
|
743
|
+
condition requiring its `trusted == 'true'` output for every PR trigger.
|
|
744
|
+
Before that boundary, only literal output, bounded comparisons, `true`, `false`,
|
|
745
|
+
`exit`, bounded Git `rev-parse`/`merge-base` object queries, and GET-only `gh api`
|
|
746
|
+
reads are accepted. The trust job's structural `if`/`elif` comparisons use the
|
|
747
|
+
same checks; output may go only to `GITHUB_OUTPUT` or `/dev/null`. Variables must
|
|
748
|
+
be quoted, and command names and `printf` formats must be literal.
|
|
749
|
+
|
|
750
|
+
The following catalog is the production allowlist. Command kinds select the
|
|
751
|
+
corresponding bounded argument validator; a listed command does not authorize
|
|
752
|
+
arbitrary arguments. Git requires the stated number of explicit object refs.
|
|
753
|
+
Bootstrap action inputs are restricted to the listed keys; Bun requires a literal
|
|
754
|
+
version rather than a repository version file.
|
|
755
|
+
|
|
756
|
+
<!-- workflow-trust-bootstrap-catalog:start -->
|
|
757
|
+
|
|
758
|
+
```json
|
|
759
|
+
{
|
|
760
|
+
"commands": {
|
|
761
|
+
"test": "comparison",
|
|
762
|
+
"[": "comparison",
|
|
763
|
+
"[[": "comparison",
|
|
764
|
+
"true": "status",
|
|
765
|
+
"false": "status",
|
|
766
|
+
"exit": "exit",
|
|
767
|
+
"echo": "output",
|
|
768
|
+
"printf": "format",
|
|
769
|
+
"git": "git",
|
|
770
|
+
"gh": "github"
|
|
771
|
+
},
|
|
772
|
+
"git": {
|
|
773
|
+
"rev-parse": {
|
|
774
|
+
"flags": ["--short", "--verify"],
|
|
775
|
+
"references": 1
|
|
776
|
+
},
|
|
777
|
+
"merge-base": {
|
|
778
|
+
"flags": ["--all", "--is-ancestor"],
|
|
779
|
+
"references": 2
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
"github": ["api"],
|
|
783
|
+
"actions": {
|
|
784
|
+
"actions/checkout": [
|
|
785
|
+
"fetch-depth",
|
|
786
|
+
"persist-credentials",
|
|
787
|
+
"ref",
|
|
788
|
+
"repository",
|
|
789
|
+
"submodules"
|
|
790
|
+
],
|
|
791
|
+
"oven-sh/setup-bun": ["bun-version", "no-cache"]
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
<!-- workflow-trust-bootstrap-catalog:end -->
|
|
797
|
+
|
|
798
|
+
Job containers and nonempty services require the job-level trust gate because
|
|
799
|
+
they start before steps and supply executable code. A step condition cannot
|
|
800
|
+
authorize their startup. Digest pinning alone does not approve an image for
|
|
801
|
+
pre-trust execution; the guard has no implicit image approval catalog.
|
|
802
|
+
Before trust, step actions must use a full SHA and match the explicit bootstrap
|
|
803
|
+
catalog: `actions/checkout` or `oven-sh/setup-bun`, with
|
|
804
|
+
bounded selector inputs. Other actions and unsupported inputs require the
|
|
805
|
+
job-level gate; a step condition cannot authorize them. Every job-level reusable workflow
|
|
806
|
+
call requires the job-level trust condition, including pinned calls without inputs.
|
|
807
|
+
|
|
808
|
+
Interpreters, sourced files, executable paths, package managers (including
|
|
809
|
+
metadata and script-disabled install modes), launchers, custom shells, and
|
|
810
|
+
unknown commands or syntax require trust. File readers, file-presence tests,
|
|
811
|
+
`jq`/`yq`, Git working-tree/history commands, and `actions/setup-node` require the
|
|
812
|
+
job-level gate. Unsupported options and query programs also require the gate. For example, gate the entire
|
|
813
|
+
detector job, including dependency installation:
|
|
814
|
+
|
|
815
|
+
```yaml
|
|
816
|
+
check-changes:
|
|
817
|
+
needs: [trust-check]
|
|
818
|
+
if: >-
|
|
819
|
+
always() && !cancelled()
|
|
820
|
+
&& ((github.event_name != 'pull_request'
|
|
821
|
+
&& github.event_name != 'pull_request_target')
|
|
822
|
+
|| needs.trust-check.outputs.trusted == 'true')
|
|
823
|
+
runs-on: ubuntu-latest
|
|
824
|
+
steps:
|
|
825
|
+
# Check out, set up Bun, install dependencies, then run the shared detector.
|
|
826
|
+
- run: bunx --no-install stll-toolchain-changed --since "$BASE_REF"
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
Actions interpolation is checked before shell quoting or comments. Only the
|
|
830
|
+
bounded identity expressions `github.sha`, `github.run_id`, `github.run_attempt`
|
|
831
|
+
and `github.repository` may appear directly in pre-trust scripts. Other contexts,
|
|
832
|
+
functions and matrix values require environment indirection or the trust gate;
|
|
833
|
+
the guard does not infer that an expression is safe from its shell quotes.
|
|
834
|
+
Pass contributor-controlled metadata through a supported data environment key
|
|
835
|
+
(`PR_TITLE`, `HEAD_REF`, `ISSUE_BODY` or `COMMENT_BODY`) and quote its expansion:
|
|
836
|
+
|
|
837
|
+
```yaml
|
|
838
|
+
- env:
|
|
839
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
840
|
+
run: echo "$PR_TITLE"
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
The trust job must classify each PR trigger used by the workflow. Main/manual
|
|
844
|
+
jobs remain runnable when the PR-only trust job is skipped. Job or step
|
|
845
|
+
conditions may prove that execution is limited to non-PR events; event string
|
|
846
|
+
comparisons follow Actions case-insensitive equality. The guard
|
|
847
|
+
proves only supported static metadata commands and boolean conditions; unknown
|
|
848
|
+
commands and conditions cannot authorize execution. It does not validate the
|
|
849
|
+
trust job's authorization policy.
|
|
850
|
+
Workflows with no declared trust job retain their existing fork-CI policy.
|
|
851
|
+
|
|
626
852
|
The comparison includes Bun declarations, Node selectors and support ranges,
|
|
627
853
|
resolved TypeScript compiler versions (including workspace and compatibility
|
|
628
854
|
aliases), oxlint, oxlint-tsgolint, oxfmt, and shared tooling pins. Lockfile
|
|
@@ -701,3 +927,14 @@ another exact stable release. Each record contains exactly `pin`, `reason`,
|
|
|
701
927
|
`tracking` (a GitHub issue URL) and `expires` (a future `YYYY-MM-DD` date).
|
|
702
928
|
Channels, expired records and `rust-version` rule opt-outs fail validation.
|
|
703
929
|
See the [root policy guide](../../README.md) for a complete declaration.
|
|
930
|
+
|
|
931
|
+
`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.
|
|
932
|
+
|
|
933
|
+
The project-ownership guard models declared package-manager scripts, including
|
|
934
|
+
literal filtered Bun build bodies and their script aliases with the selected
|
|
935
|
+
workspace as the working directory. It inventories supported linter invocations;
|
|
936
|
+
it never executes build bodies. Unrecognized linter launchers and script forms
|
|
937
|
+
fail closed. This is an ownership check for ordinary authored package scripts,
|
|
938
|
+
not a security boundary against deliberately obfuscated code or lint invocations
|
|
939
|
+
hidden inside arbitrary program bodies. Unsupported forms require an explicit
|
|
940
|
+
supported script invocation or a separately tested extension to the model.
|
package/dist/cloud-setup-cli.cjs
CHANGED
|
@@ -3,44 +3,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_toolchain_inputs = require("./toolchain-inputs.cjs");
|
|
4
4
|
const require_toolchain_guard = require("./toolchain-guard.cjs");
|
|
5
5
|
const require_toolchain_schema = require("./toolchain-schema.cjs");
|
|
6
|
+
const require_generated_file = require("./generated-file.cjs");
|
|
6
7
|
let node_fs = require("node:fs");
|
|
7
8
|
let node_path = require("node:path");
|
|
8
9
|
let node_fs_promises = require("node:fs/promises");
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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":["
|
|
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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"}
|
package/dist/cloud-setup-cli.mjs
CHANGED
|
@@ -1,45 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { S as generateCloudSetup, _ as cloudSessionStartFix, b as mergeCloudClaudeSettings, g as cloudClaudeSettingsPath, x as cloudSetupPath } from "./toolchain-inputs.mjs";
|
|
3
3
|
import { n as parseToolchainConfiguration, r as readToolchainInputs } from "./toolchain-guard.mjs";
|
|
4
4
|
import { t as parseToolchainPolicy } from "./toolchain-schema.mjs";
|
|
5
|
+
import { n as generationDiagnostics, r as writeGeneratedFile, t as checkGeneratedOutput } from "./generated-file.mjs";
|
|
5
6
|
import { constants } from "node:fs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { open, readFile, realpath } from "node:fs/promises";
|
|
8
9
|
//#region src/cloud-setup-cli.ts
|
|
9
10
|
const usage = "Usage: stll-cloud-setup (run from repository root)\n stll-cloud-setup --help";
|
|
10
|
-
const optionalStat = async (path) => {
|
|
11
|
-
try {
|
|
12
|
-
return await lstat(path);
|
|
13
|
-
} catch (error) {
|
|
14
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
15
|
-
throw error;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const checkOutput = async (destination) => {
|
|
19
|
-
const parent = dirname(destination);
|
|
20
|
-
const parentStat = await optionalStat(parent);
|
|
21
|
-
if (parentStat !== void 0 && (parentStat.isSymbolicLink() || !parentStat.isDirectory())) throw new Error("cloud setup parent must be a real repository directory");
|
|
22
|
-
const destinationStat = await optionalStat(destination);
|
|
23
|
-
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");
|
|
24
|
-
return destinationStat;
|
|
25
|
-
};
|
|
26
|
-
const writeOutput = async ({ destination, text, mode }) => {
|
|
27
|
-
await checkOutput(destination);
|
|
28
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
29
|
-
const output = await open(destination, constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW, mode);
|
|
30
|
-
try {
|
|
31
|
-
const opened = await output.stat();
|
|
32
|
-
if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
|
|
33
|
-
await output.truncate(0);
|
|
34
|
-
await output.writeFile(text, "utf8");
|
|
35
|
-
await output.chmod(mode);
|
|
36
|
-
} finally {
|
|
37
|
-
await output.close();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
11
|
const readClaudeSettings = async (destination) => {
|
|
41
12
|
try {
|
|
42
|
-
if (await
|
|
13
|
+
if (await checkGeneratedOutput(destination) === void 0) return void 0;
|
|
43
14
|
const input = await open(destination, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
44
15
|
try {
|
|
45
16
|
const opened = await input.stat();
|
|
@@ -64,7 +35,20 @@ const main = async () => {
|
|
|
64
35
|
if (args.length !== 0) throw new Error(usage);
|
|
65
36
|
const root = await realpath(process.cwd());
|
|
66
37
|
const { files, diagnostics } = readToolchainInputs(root);
|
|
67
|
-
|
|
38
|
+
const destination = join(root, cloudSetupPath);
|
|
39
|
+
const settingsDestination = join(root, cloudClaudeSettingsPath);
|
|
40
|
+
const settings = await readClaudeSettings(settingsDestination);
|
|
41
|
+
const remainingDiagnostics = await generationDiagnostics({
|
|
42
|
+
diagnostics,
|
|
43
|
+
outputs: [{
|
|
44
|
+
relative: cloudSetupPath,
|
|
45
|
+
destination
|
|
46
|
+
}, {
|
|
47
|
+
relative: cloudClaudeSettingsPath,
|
|
48
|
+
destination: settingsDestination
|
|
49
|
+
}]
|
|
50
|
+
});
|
|
51
|
+
if (remainingDiagnostics.length > 0) throw new Error(remainingDiagnostics.map(({ path, message }) => `${path}: ${message}${path === ".claude/settings.json" ? `; ${cloudSessionStartFix}` : ""}`).join("\n"));
|
|
68
52
|
const text = files["stll-toolchain.json"];
|
|
69
53
|
if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
|
|
70
54
|
const { cloud, rustExceptions } = parseToolchainConfiguration(JSON.parse(text));
|
|
@@ -79,17 +63,13 @@ const main = async () => {
|
|
|
79
63
|
files,
|
|
80
64
|
rustExceptions
|
|
81
65
|
});
|
|
82
|
-
const destination = join(root, cloudSetupPath);
|
|
83
|
-
await checkOutput(destination);
|
|
84
|
-
const settingsDestination = join(root, cloudClaudeSettingsPath);
|
|
85
|
-
const settings = await readClaudeSettings(settingsDestination);
|
|
86
66
|
const mergedSettings = mergeCloudClaudeSettings(settings?.text);
|
|
87
|
-
await
|
|
67
|
+
await writeGeneratedFile({
|
|
88
68
|
destination,
|
|
89
69
|
text: script,
|
|
90
70
|
mode: 493
|
|
91
71
|
});
|
|
92
|
-
if (mergedSettings !== settings?.text) await
|
|
72
|
+
if (mergedSettings !== settings?.text) await writeGeneratedFile({
|
|
93
73
|
destination: settingsDestination,
|
|
94
74
|
text: mergedSettings,
|
|
95
75
|
mode: settings?.mode ?? 420
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-setup-cli.mjs","names":[],"sources":["../src/cloud-setup-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { constants } from \"node:fs\";\nimport {
|
|
1
|
+
{"version":3,"file":"cloud-setup-cli.mjs","names":[],"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":";;;;;;;;;AAsBA,MAAM,QACJ;AACF,MAAM,qBAAqB,OAAO,gBAAwB;CACxD,IAAI;EACF,IAAK,MAAM,qBAAqB,WAAW,MAAO,KAAA,GAChD,OAAO,KAAA;EACT,MAAM,QAAQ,MAAM,KAClB,aACA,UAAU,WAAW,UAAU,UACjC;EACA,IAAI;GACF,MAAM,SAAS,MAAM,MAAM,KAAK;GAChC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,UAAU,GACvC,MAAM,IAAI,MACR,wDACF;GACF,OAAO;IAAE,MAAM,MAAM,MAAM,SAAS,MAAM;IAAG,MAAM,OAAO,OAAO;GAAM;EACzE,UAAU;GACR,MAAM,MAAM,MAAM;EACpB;CACF,SAAS,OAAO;EACd,MAAM,IAAI,MACR,uBAAuB,wBAAwB,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,IAAI,sBAChH;CACF;AACF;AAEA,MAAM,OAAO,YAAY;CACvB,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;CACjC,IAAI,KAAK,WAAW,KAAK,KAAK,GAAG,CAAC,MAAM,UAAU;EAChD,QAAQ,OAAO,MAAM,GAAG,MAAM,GAAG;EACjC;CACF;CACA,IAAI,KAAK,WAAW,GAAG,MAAM,IAAI,MAAM,KAAK;CAC5C,MAAM,OAAO,MAAM,SAAS,QAAQ,IAAI,CAAC;CACzC,MAAM,EAAE,OAAO,gBAAgB,oBAAoB,IAAI;CACvD,MAAM,cAAc,KAAK,MAAM,cAAc;CAC7C,MAAM,sBAAsB,KAAK,MAAM,uBAAuB;CAE9D,MAAM,WAAW,MAAM,mBAAmB,mBAAmB;CAC7D,MAAM,uBAAuB,MAAM,sBAAsB;EACvD;EACA,SAAS,CACP;GAAE,UAAU;GAAgB;EAAY,GACxC;GAAE,UAAU;GAAyB,aAAa;EAAoB,CACxE;CACF,CAAC;CACD,IAAI,qBAAqB,SAAS,GAChC,MAAM,IAAI,MACR,qBACG,KACE,EAAE,MAAM,cACP,GAAG,KAAK,IAAI,UAAU,SAAA,0BAAmC,KAAK,yBAAyB,IAC3F,CAAC,CACA,KAAK,IAAI,CACd;CACF,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,sCAAsC;CACxD,MAAM,EAAE,OAAO,mBAAmB,4BAChC,KAAK,MAAM,IAAI,CACjB;CACA,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,wCAAwC;CAC1D,MAAM,cAAc,MAAM;CAC1B,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,MAAM,qCAAqC;CACvD,MAAM,SAAS,qBACb,KAAK,MACH,MAAM,SAAS,IAAI,IAAI,qBAAqB,YAAY,GAAG,GAAG,MAAM,CACtE,CACF;CACA,MAAM,SAAS,mBAAmB;EAChC;EACA;EACA,aAAa,YAAY,KAAK;EAC9B;EACA;CACF,CAAC;CACD,MAAM,iBAAiB,yBAAyB,UAAU,IAAI;CAC9D,MAAM,mBAAmB;EAAE;EAAa,MAAM;EAAQ,MAAM;CAAM,CAAC;CACnE,IAAI,mBAAmB,UAAU,MAC/B,MAAM,mBAAmB;EACvB,aAAa;EACb,MAAM;EACN,MAAM,UAAU,QAAQ;CAC1B,CAAC;CACH,QAAQ,OAAO,MACb,aAAa,eAAe,OAAO,wBAAwB,GAC7D;AACF;AAEA,KAAK,CAAC,CAAC,OAAO,UAAmB;CAC/B,QAAQ,OAAO,MACb,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GACzE;CACA,QAAQ,WAAW;AACrB,CAAC"}
|