@stll/oxlint-config 1.0.2 → 1.1.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 (49) hide show
  1. package/README.md +85 -4
  2. package/dist/cloud-setup-cli.cjs +62 -21
  3. package/dist/cloud-setup-cli.cjs.map +1 -1
  4. package/dist/cloud-setup-cli.mjs +63 -22
  5. package/dist/cloud-setup-cli.mjs.map +1 -1
  6. package/dist/consumer-compat-cli.cjs +7 -6
  7. package/dist/consumer-compat-cli.cjs.map +1 -1
  8. package/dist/consumer-compat-cli.mjs +7 -6
  9. package/dist/consumer-compat-cli.mjs.map +1 -1
  10. package/dist/git-tracked-output.cjs.map +1 -1
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/publish-build-target-vite-dts-helper.mjs +1 -2
  13. package/dist/publish-build-target-vite-dts-helper2.cjs +5 -5
  14. package/dist/publish-build-target-vite-dts-helper2.cjs.map +1 -1
  15. package/dist/publish-contract-cli.cjs +17 -16
  16. package/dist/publish-contract-cli.cjs.map +1 -1
  17. package/dist/publish-contract-cli.mjs +6 -5
  18. package/dist/publish-contract-cli.mjs.map +1 -1
  19. package/dist/publish-contract.cjs +1 -1
  20. package/dist/publish-contract.cjs.map +1 -1
  21. package/dist/publish-contract.mjs +1 -1
  22. package/dist/rolldown-runtime.cjs +1 -1
  23. package/dist/toolchain-changed.cjs +19 -14
  24. package/dist/toolchain-changed.cjs.map +1 -1
  25. package/dist/toolchain-changed.mjs +21 -16
  26. package/dist/toolchain-changed.mjs.map +1 -1
  27. package/dist/toolchain-check-cli.cjs.map +1 -1
  28. package/dist/toolchain-check-cli.mjs.map +1 -1
  29. package/dist/toolchain-guard.cjs +66 -50
  30. package/dist/toolchain-guard.cjs.map +1 -1
  31. package/dist/toolchain-guard.mjs +73 -57
  32. package/dist/toolchain-guard.mjs.map +1 -1
  33. package/dist/toolchain-inputs.cjs +670 -32
  34. package/dist/toolchain-inputs.cjs.map +1 -1
  35. package/dist/toolchain-inputs.mjs +647 -33
  36. package/dist/toolchain-inputs.mjs.map +1 -1
  37. package/dist/toolchain-schema.cjs +1 -1
  38. package/dist/toolchain-schema.cjs.map +1 -1
  39. package/dist/toolchain-schema.mjs +2 -2
  40. package/dist/typecheck-parity-cli.cjs.map +1 -1
  41. package/dist/typecheck-parity-cli.mjs.map +1 -1
  42. package/dist/typecheck-parity.cjs +8 -7
  43. package/dist/typecheck-parity.cjs.map +1 -1
  44. package/dist/typecheck-parity.mjs +6 -5
  45. package/dist/typecheck-parity.mjs.map +1 -1
  46. package/dist/typecheck-probe-cli.cjs +1 -1
  47. package/dist/typecheck-probe-cli.cjs.map +1 -1
  48. package/dist/typecheck-probe-cli.mjs +1 -1
  49. package/package.json +6 -6
package/README.md CHANGED
@@ -298,7 +298,16 @@ Add an explicit declaration to tracked root `stll-toolchain.json`:
298
298
  "cloud": {
299
299
  "services": ["postgres", "valkey"],
300
300
  "install": "bun install --frozen-lockfile",
301
- "envFile": ".env.cloud"
301
+ "envFile": ".env.cloud",
302
+ "rust": {
303
+ "components": ["clippy", "rustfmt"],
304
+ "targets": ["wasm32-unknown-unknown"]
305
+ },
306
+ "tools": {
307
+ "cargo-nextest": "0.9.148",
308
+ "wasm-bindgen-cli": "0.2.126",
309
+ "uv": "0.13.1"
310
+ }
302
311
  }
303
312
  }
304
313
  ```
@@ -314,6 +323,26 @@ repository-relative path; its existing parent must be safe at runtime. Track an
314
323
  bunx --no-install stll-cloud-setup
315
324
  ```
316
325
 
326
+ Rust is optional. A tracked root `rust-toolchain.toml` enables it automatically;
327
+ otherwise declare `cloud.rust` to enable it. `cloud.rust.toolchain`, when given,
328
+ must be an exact stable Rust patch. The root toolchain file supplies the pin when
329
+ present; without either pin, the installed shared policy supplies it. Conflicting
330
+ pins fail generation. The selected pin must match the shared policy or a recorded
331
+ `rustExceptions` pin. `components` and `targets` select additional rustup
332
+ components and compilation targets; the root toolchain file also contributes
333
+ its selections. A declared root profile must be `minimal`. Rust installation uses
334
+ the minimal profile and a pinned, checksum-verified rustup installer. Rust-enabled
335
+ setup also provisions Ubuntu's `build-essential` package for native compilation
336
+ and linking.
337
+
338
+ `cloud.tools` is a closed object: the supported keys are `cargo-nextest`,
339
+ `wasm-bindgen-cli`, and `uv`. Each value must be an exact release supported by
340
+ the installed tooling package. Unknown tools and unsupported versions fail
341
+ generation. These tools use versioned upstream Linux binaries with committed
342
+ SHA256 pins for amd64 and arm64; setup does not build them with `cargo install`.
343
+ `wasm-bindgen-cli` requires the matching `wasm-bindgen` crate version in tracked
344
+ root `Cargo.lock`. Regenerate after updating the lockfile or any tool pin.
345
+
317
346
  Generation uses the installed policy's Bun, Node, PostgreSQL, and Valkey pins.
318
347
  PostgreSQL uses the PGDG major-version packages; minor updates within the pinned
319
348
  major are accepted for the disposable database. Before adding PGDG, provisioning
@@ -323,8 +352,18 @@ binary DEBs for the declared release and architecture, verifies the policy's
323
352
  SHA256 pins, and installs those local files with apt. It does not resolve Valkey
324
353
  from a mutable package index.
325
354
  The command writes `.agents/cloud-setup.sh` with executable permissions and
326
- refuses symlink parents or destinations and hard-linked destinations. `stll-toolchain-check` rejects missing,
327
- drifted, or undeclared tracked scripts with mandatory `cloud-setup-drift`.
355
+ merges a `SessionStart` hook into `.claude/settings.json`. The hook runs
356
+ `bash .agents/cloud-setup.sh start` only when `CLAUDE_CODE_REMOTE=true`, including
357
+ sessions that reuse cached filesystems. After successful startup, it appends the
358
+ managed shell profile's literal exports to `CLAUDE_ENV_FILE` when available, so
359
+ subsequent Claude commands select pinned runtimes and tools before preinstalled
360
+ versions while retaining their inherited `PATH`. Existing environment-file
361
+ content, unrelated settings, and hooks are preserved. Invalid JSON, disabled
362
+ hooks, or conflicting cloud startup entries stop generation with a repair
363
+ instruction. Commit both generated files.
364
+ Generation refuses symlink parents or destinations and hard-linked destinations.
365
+ `stll-toolchain-check` rejects missing, drifted, or undeclared tracked scripts and
366
+ missing or conflicting startup hooks with mandatory `cloud-setup-drift`.
328
367
  Regenerate after changing the policy or declaration. `stll-cloud-setup --help`
329
368
  prints usage without reading repository configuration.
330
369
 
@@ -337,11 +376,19 @@ Both commands can be rerun. Missing or unknown subcommands print usage and
337
376
  exit 2; there is no implicit lifecycle mode. The script supports Ubuntu 24.04
338
377
  with root access or passwordless sudo and fails when required capabilities
339
378
  or pinned installations are unavailable. `install` provisions runtimes,
340
- dependencies, and declared services; `start` restores local services after
379
+ dependencies, and declared services; `start` refreshes the managed shell environment
380
+ and restores local services after
341
381
  cached environments lose running processes. Services bind only to localhost.
342
382
  System provisioning runs as root; frozen dependencies run as the invoking user
343
383
  with that user's home and a private, user-owned Bun cache. Package installation
344
384
  does not permit automatic downgrades.
385
+ Runtime commands and declared tools are exposed through `/usr/local/bin`, so a
386
+ fresh shell can use Bun, Node, Rust, and the tools without sourcing `.cargo/env`
387
+ or changing `PATH`. Rust proxies select the provisioned system toolchain.
388
+ Installation also writes `/etc/profile.d/stll-cloud.sh` and sources it from the
389
+ invoking user's Bash startup files. The profile puts pinned Node and Bun ahead
390
+ of inherited runtime directories while preserving unrelated paths and existing
391
+ startup settings. Rerunning installation does not duplicate these source entries.
345
392
  If a newer Valkey package is already installed, provisioning fails rather than
346
393
  downgrading it; use an environment compatible with the pinned release.
347
394
  The environment file contains `NODE_ENV=test` and declared local connection
@@ -353,6 +400,40 @@ Environments may cache filesystems without preserving service processes.
353
400
  Run `start` at every session start, including sessions that reuse installed files.
354
401
  The supported OS, privileges, and pinned runtime requirements are checked explicitly.
355
402
 
403
+ ### Network hosts
404
+
405
+ Downloads use a 20-second connection timeout, three retries, and a total deadline
406
+ (45 seconds for manifests and keys; 120 seconds for binary archives). APT uses
407
+ 20-second Acquire timeouts, three retries, and a 180-second command deadline.
408
+ Rust provisioning commands have a 180-second deadline. Frozen dependency
409
+ installation uses a 20-second Bun HTTP idle timeout and a 240-second overall
410
+ deadline, including lifecycle scripts. Timeout failures name the download host
411
+ or the configured package hosts; a redirected download names its original host.
412
+
413
+ Allow these direct download hosts for the generated installer:
414
+
415
+ | Host | Downloads |
416
+ | ---------------------- | ------------------------------------------------------------------------ |
417
+ | `nodejs.org` | Node archives and SHA256 manifests |
418
+ | `github.com` | Bun archives and checksums; pinned nextest, wasm-bindgen and uv releases |
419
+ | `static.rust-lang.org` | Pinned rustup-init, Rust manifests, toolchains, components and targets |
420
+ | `www.postgresql.org` | PostgreSQL repository signing key |
421
+ | `apt.postgresql.org` | PostgreSQL packages and repository metadata |
422
+ | `download.valkey.io` | Pinned Valkey binary packages |
423
+
424
+ GitHub release downloads redirect to `release-assets.githubusercontent.com`;
425
+ allow that host as well. This includes Bun and the optional prebuilt tools.
426
+ Rust provisioning uses `static.rust-lang.org` for both distribution and update endpoints.
427
+
428
+ System prerequisites use the image's configured Ubuntu apt repositories:
429
+ standard Ubuntu 24.04 images use `archive.ubuntu.com`, `security.ubuntu.com`,
430
+ or `ports.ubuntu.com`, depending on architecture. Images configured with a
431
+ mirror need that mirror's host instead. Frozen Bun installation also needs
432
+ the package registry (normally `registry.npmjs.org`) and any download hosts
433
+ referenced by that repository's lockfile or dependency lifecycle scripts.
434
+ Those repository-specific hosts are not chosen by this generator. `start`
435
+ performs no downloads.
436
+
356
437
  ## Published package contracts
357
438
 
358
439
  Run `stll-publish-contract` in PR CI. Every published root or declared workspace
@@ -16,6 +16,46 @@ const optionalStat = async (path) => {
16
16
  throw error;
17
17
  }
18
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
+ const readClaudeSettings = async (destination) => {
42
+ try {
43
+ if (await checkOutput(destination) === void 0) return void 0;
44
+ const input = await (0, node_fs_promises.open)(destination, node_fs.constants.O_RDONLY | node_fs.constants.O_NOFOLLOW);
45
+ try {
46
+ const opened = await input.stat();
47
+ if (!opened.isFile() || opened.nlink !== 1) throw new Error("settings must be a single-link regular repository file");
48
+ return {
49
+ text: await input.readFile("utf8"),
50
+ mode: opened.mode & 511
51
+ };
52
+ } finally {
53
+ await input.close();
54
+ }
55
+ } catch (error) {
56
+ throw new Error(`cannot safely merge ${require_toolchain_inputs.cloudClaudeSettingsPath}: ${error instanceof Error ? error.message : String(error)}; ${require_toolchain_inputs.cloudSessionStartFix}`);
57
+ }
58
+ };
19
59
  const main = async () => {
20
60
  const args = process.argv.slice(2);
21
61
  if (args.length === 1 && args.at(0) === "--help") {
@@ -25,36 +65,37 @@ const main = async () => {
25
65
  if (args.length !== 0) throw new Error(usage);
26
66
  const root = await (0, node_fs_promises.realpath)(process.cwd());
27
67
  const { files, diagnostics } = require_toolchain_guard.readToolchainInputs(root);
28
- if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}`).join("\n"));
68
+ if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}${path === ".claude/settings.json" ? `; ${require_toolchain_inputs.cloudSessionStartFix}` : ""}`).join("\n"));
29
69
  const text = files["stll-toolchain.json"];
30
70
  if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
31
- const { cloud } = require_toolchain_guard.parseToolchainConfiguration(JSON.parse(text));
71
+ const { cloud, rustExceptions } = require_toolchain_guard.parseToolchainConfiguration(JSON.parse(text));
32
72
  if (cloud === void 0) throw new Error("requires an explicit cloud declaration");
33
73
  const nodeVersion = files[".node-version"];
34
74
  if (nodeVersion === void 0) throw new Error("requires tracked root .node-version");
75
+ const policy = require_toolchain_schema.parseToolchainPolicy(JSON.parse(await (0, node_fs_promises.readFile)(new URL("../toolchain.json", require("url").pathToFileURL(__filename).href), "utf8")));
35
76
  const script = require_toolchain_inputs.generateCloudSetup({
36
- policy: require_toolchain_schema.parseToolchainPolicy(JSON.parse(await (0, node_fs_promises.readFile)(new URL("../toolchain.json", require("url").pathToFileURL(__filename).href), "utf8"))),
77
+ policy,
37
78
  cloud,
38
- nodeVersion: nodeVersion.trim()
79
+ nodeVersion: nodeVersion.trim(),
80
+ files,
81
+ rustExceptions
39
82
  });
40
83
  const destination = (0, node_path.join)(root, require_toolchain_inputs.cloudSetupPath);
41
- const parent = (0, node_path.dirname)(destination);
42
- const parentStat = await optionalStat(parent);
43
- if (parentStat === void 0) await (0, node_fs_promises.mkdir)(parent);
44
- else if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) throw new Error("cloud setup parent must be a real repository directory");
45
- const destinationStat = await optionalStat(destination);
46
- 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");
47
- const output = await (0, node_fs_promises.open)(destination, node_fs.constants.O_WRONLY | node_fs.constants.O_CREAT | node_fs.constants.O_NOFOLLOW, 493);
48
- try {
49
- const opened = await output.stat();
50
- if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
51
- await output.truncate(0);
52
- await output.writeFile(script, "utf8");
53
- await output.chmod(493);
54
- } finally {
55
- await output.close();
56
- }
57
- process.stdout.write(`Generated ${require_toolchain_inputs.cloudSetupPath}\n`);
84
+ await checkOutput(destination);
85
+ const settingsDestination = (0, node_path.join)(root, require_toolchain_inputs.cloudClaudeSettingsPath);
86
+ const settings = await readClaudeSettings(settingsDestination);
87
+ const mergedSettings = require_toolchain_inputs.mergeCloudSessionStartHook(settings?.text);
88
+ await writeOutput({
89
+ destination,
90
+ text: script,
91
+ mode: 493
92
+ });
93
+ if (mergedSettings !== settings?.text) await writeOutput({
94
+ destination: settingsDestination,
95
+ text: mergedSettings,
96
+ mode: settings?.mode ?? 420
97
+ });
98
+ process.stdout.write(`Generated ${require_toolchain_inputs.cloudSetupPath} and ${require_toolchain_inputs.cloudClaudeSettingsPath}\n`);
58
99
  };
59
100
  main().catch((error) => {
60
101
  process.stderr.write(`cloud-setup: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-setup-cli.cjs","names":["readToolchainInputs","parseToolchainConfiguration","generateCloudSetup","parseToolchainPolicy","cloudSetupPath","constants"],"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 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 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.map(({ path, message }) => `${path}: ${message}`).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 } = parseToolchainConfiguration(JSON.parse(text));\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 });\n const destination = join(root, cloudSetupPath);\n const parent = dirname(destination);\n const parentStat = await optionalStat(parent);\n if (parentStat === undefined) await mkdir(parent);\n else if (parentStat.isSymbolicLink() || !parentStat.isDirectory())\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 const output = await open(\n destination,\n constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW,\n 0o755,\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(script, \"utf8\");\n await output.chmod(0o755);\n } finally {\n await output.close();\n }\n process.stdout.write(`Generated ${cloudSetupPath}\\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":";;;;;;;;;AAYA,MAAM,QACJ;AACF,MAAM,eAAe,OAAO,SAAiB;CAC3C,IAAI;EACF,OAAO,OAAA,GAAA,iBAAA,MAAA,CAAY,IAAI;CACzB,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC9D,OAAO,KAAA;EACT,MAAM;CACR;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,OAAA,GAAA,iBAAA,SAAA,CAAe,QAAQ,IAAI,CAAC;CACzC,MAAM,EAAE,OAAO,gBAAgBA,wBAAAA,oBAAoB,IAAI;CACvD,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,MACR,YAAY,KAAK,EAAE,MAAM,cAAc,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,KAAK,IAAI,CACzE;CACF,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,sCAAsC;CACxD,MAAM,EAAE,UAAUC,wBAAAA,4BAA4B,KAAK,MAAM,IAAI,CAAC;CAC9D,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,wCAAwC;CAC1D,MAAM,cAAc,MAAM;CAC1B,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,MAAM,qCAAqC;CAMvD,MAAM,SAASC,yBAAAA,mBAAmB;EAChC,QANaC,yBAAAA,qBACb,KAAK,MACH,OAAA,GAAA,iBAAA,SAAA,CAAe,IAAI,IAAI,qBAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAAoC,GAAG,MAAM,CACtE,CAGK;EACL;EACA,aAAa,YAAY,KAAK;CAChC,CAAC;CACD,MAAM,eAAA,GAAA,UAAA,KAAA,CAAmB,MAAMC,yBAAAA,cAAc;CAC7C,MAAM,UAAA,GAAA,UAAA,QAAA,CAAiB,WAAW;CAClC,MAAM,aAAa,MAAM,aAAa,MAAM;CAC5C,IAAI,eAAe,KAAA,GAAW,OAAA,GAAA,iBAAA,MAAA,CAAY,MAAM;MAC3C,IAAI,WAAW,eAAe,KAAK,CAAC,WAAW,YAAY,GAC9D,MAAM,IAAI,MAAM,wDAAwD;CAC1E,MAAM,kBAAkB,MAAM,aAAa,WAAW;CACtD,IACE,oBAAoB,KAAA,MACnB,gBAAgB,eAAe,KAC9B,CAAC,gBAAgB,OAAO,KACxB,gBAAgB,UAAU,IAE5B,MAAM,IAAI,MACR,uEACF;CACF,MAAM,SAAS,OAAA,GAAA,iBAAA,KAAA,CACb,aACAC,QAAAA,UAAU,WAAWA,QAAAA,UAAU,UAAUA,QAAAA,UAAU,YACnD,GACF;CACA,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,KAAK;EACjC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,UAAU,GACvC,MAAM,IAAI,MACR,uEACF;EACF,MAAM,OAAO,SAAS,CAAC;EACvB,MAAM,OAAO,UAAU,QAAQ,MAAM;EACrC,MAAM,OAAO,MAAM,GAAK;CAC1B,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;CACA,QAAQ,OAAO,MAAM,aAAaD,yBAAAA,eAAe,GAAG;AACtD;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"}
1
+ {"version":3,"file":"cloud-setup-cli.cjs","names":["lstat","dirname","mkdir","open","constants","cloudClaudeSettingsPath","cloudSessionStartFix","realpath","readToolchainInputs","parseToolchainConfiguration","parseToolchainPolicy","readFile","generateCloudSetup","join","cloudSetupPath","mergeCloudSessionStartHook"],"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 mergeCloudSessionStartHook,\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 = mergeCloudSessionStartHook(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":";;;;;;;;;AAiBA,MAAM,QACJ;AACF,MAAM,eAAe,OAAO,SAAiB;CAC3C,IAAI;EACF,OAAO,OAAA,GAAMA,iBAAAA,MAAAA,CAAM,IAAI;CACzB,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC9D,OAAO,KAAA;EACT,MAAM;CACR;AACF;AAEA,MAAM,cAAc,OAAO,gBAAwB;CACjD,MAAM,UAAA,GAASC,UAAAA,QAAAA,CAAQ,WAAW;CAClC,MAAM,aAAa,MAAM,aAAa,MAAM;CAC5C,IACE,eAAe,KAAA,MACd,WAAW,eAAe,KAAK,CAAC,WAAW,YAAY,IAExD,MAAM,IAAI,MAAM,wDAAwD;CAC1E,MAAM,kBAAkB,MAAM,aAAa,WAAW;CACtD,IACE,oBAAoB,KAAA,MACnB,gBAAgB,eAAe,KAC9B,CAAC,gBAAgB,OAAO,KACxB,gBAAgB,UAAU,IAE5B,MAAM,IAAI,MACR,uEACF;CACF,OAAO;AACT;AAQA,MAAM,cAAc,OAAO,EAAE,aAAa,MAAM,WAA+B;CAC7E,MAAM,YAAY,WAAW;CAC7B,OAAA,GAAMC,iBAAAA,MAAAA,EAAAA,GAAMD,UAAAA,QAAAA,CAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;CACrD,MAAM,SAAS,OAAA,GAAME,iBAAAA,KAAAA,CACnB,aACAC,QAAAA,UAAU,WAAWA,QAAAA,UAAU,UAAUA,QAAAA,UAAU,YACnD,IACF;CACA,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,KAAK;EACjC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,UAAU,GACvC,MAAM,IAAI,MACR,uEACF;EACF,MAAM,OAAO,SAAS,CAAC;EACvB,MAAM,OAAO,UAAU,MAAM,MAAM;EACnC,MAAM,OAAO,MAAM,IAAI;CACzB,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,MAAM,qBAAqB,OAAO,gBAAwB;CACxD,IAAI;EACF,IAAK,MAAM,YAAY,WAAW,MAAO,KAAA,GAAW,OAAO,KAAA;EAC3D,MAAM,QAAQ,OAAA,GAAMD,iBAAAA,KAAAA,CAClB,aACAC,QAAAA,UAAU,WAAWA,QAAAA,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,uBAAuBC,yBAAAA,wBAAwB,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,IAAIC,yBAAAA,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,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,QAAQ,IAAI,CAAC;CACzC,MAAM,EAAE,OAAO,gBAAgBC,wBAAAA,oBAAoB,IAAI;CACvD,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,MACR,YACG,KACE,EAAE,MAAM,cACP,GAAG,KAAK,IAAI,UAAU,SAAA,0BAAmC,KAAKF,yBAAAA,yBAAyB,IAC3F,CAAC,CACA,KAAK,IAAI,CACd;CACF,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,sCAAsC;CACxD,MAAM,EAAE,OAAO,mBAAmBG,wBAAAA,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,SAASC,yBAAAA,qBACb,KAAK,MACH,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,IAAI,IAAI,qBAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAAoC,GAAG,MAAM,CACtE,CACF;CACA,MAAM,SAASC,yBAAAA,mBAAmB;EAChC;EACA;EACA,aAAa,YAAY,KAAK;EAC9B;EACA;CACF,CAAC;CACD,MAAM,eAAA,GAAcC,UAAAA,KAAAA,CAAK,MAAMC,yBAAAA,cAAc;CAC7C,MAAM,YAAY,WAAW;CAC7B,MAAM,uBAAA,GAAsBD,UAAAA,KAAAA,CAAK,MAAMR,yBAAAA,uBAAuB;CAC9D,MAAM,WAAW,MAAM,mBAAmB,mBAAmB;CAC7D,MAAM,iBAAiBU,yBAAAA,2BAA2B,UAAU,IAAI;CAChE,MAAM,YAAY;EAAE;EAAa,MAAM;EAAQ,MAAM;CAAM,CAAC;CAC5D,IAAI,mBAAmB,UAAU,MAC/B,MAAM,YAAY;EAChB,aAAa;EACb,MAAM;EACN,MAAM,UAAU,QAAQ;CAC1B,CAAC;CACH,QAAQ,OAAO,MACb,aAAaD,yBAAAA,eAAe,OAAOT,yBAAAA,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"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { h as generateCloudSetup, m as cloudSetupPath } from "./toolchain-inputs.mjs";
2
+ import { _ as mergeCloudSessionStartHook, h as cloudSessionStartFix, m as cloudClaudeSettingsPath, v as cloudSetupPath, y as generateCloudSetup } 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
5
  import { constants } from "node:fs";
@@ -15,6 +15,46 @@ const optionalStat = async (path) => {
15
15
  throw error;
16
16
  }
17
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
+ const readClaudeSettings = async (destination) => {
41
+ try {
42
+ if (await checkOutput(destination) === void 0) return void 0;
43
+ const input = await open(destination, constants.O_RDONLY | constants.O_NOFOLLOW);
44
+ try {
45
+ const opened = await input.stat();
46
+ if (!opened.isFile() || opened.nlink !== 1) throw new Error("settings must be a single-link regular repository file");
47
+ return {
48
+ text: await input.readFile("utf8"),
49
+ mode: opened.mode & 511
50
+ };
51
+ } finally {
52
+ await input.close();
53
+ }
54
+ } catch (error) {
55
+ throw new Error(`cannot safely merge ${cloudClaudeSettingsPath}: ${error instanceof Error ? error.message : String(error)}; ${cloudSessionStartFix}`);
56
+ }
57
+ };
18
58
  const main = async () => {
19
59
  const args = process.argv.slice(2);
20
60
  if (args.length === 1 && args.at(0) === "--help") {
@@ -24,36 +64,37 @@ const main = async () => {
24
64
  if (args.length !== 0) throw new Error(usage);
25
65
  const root = await realpath(process.cwd());
26
66
  const { files, diagnostics } = readToolchainInputs(root);
27
- if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}`).join("\n"));
67
+ if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}${path === ".claude/settings.json" ? `; ${cloudSessionStartFix}` : ""}`).join("\n"));
28
68
  const text = files["stll-toolchain.json"];
29
69
  if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
30
- const { cloud } = parseToolchainConfiguration(JSON.parse(text));
70
+ const { cloud, rustExceptions } = parseToolchainConfiguration(JSON.parse(text));
31
71
  if (cloud === void 0) throw new Error("requires an explicit cloud declaration");
32
72
  const nodeVersion = files[".node-version"];
33
73
  if (nodeVersion === void 0) throw new Error("requires tracked root .node-version");
74
+ const policy = parseToolchainPolicy(JSON.parse(await readFile(new URL("../toolchain.json", import.meta.url), "utf8")));
34
75
  const script = generateCloudSetup({
35
- policy: parseToolchainPolicy(JSON.parse(await readFile(new URL("../toolchain.json", import.meta.url), "utf8"))),
76
+ policy,
36
77
  cloud,
37
- nodeVersion: nodeVersion.trim()
78
+ nodeVersion: nodeVersion.trim(),
79
+ files,
80
+ rustExceptions
38
81
  });
39
82
  const destination = join(root, cloudSetupPath);
40
- const parent = dirname(destination);
41
- const parentStat = await optionalStat(parent);
42
- if (parentStat === void 0) await mkdir(parent);
43
- else if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) throw new Error("cloud setup parent must be a real repository directory");
44
- const destinationStat = await optionalStat(destination);
45
- 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");
46
- const output = await open(destination, constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW, 493);
47
- try {
48
- const opened = await output.stat();
49
- if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
50
- await output.truncate(0);
51
- await output.writeFile(script, "utf8");
52
- await output.chmod(493);
53
- } finally {
54
- await output.close();
55
- }
56
- process.stdout.write(`Generated ${cloudSetupPath}\n`);
83
+ await checkOutput(destination);
84
+ const settingsDestination = join(root, cloudClaudeSettingsPath);
85
+ const settings = await readClaudeSettings(settingsDestination);
86
+ const mergedSettings = mergeCloudSessionStartHook(settings?.text);
87
+ await writeOutput({
88
+ destination,
89
+ text: script,
90
+ mode: 493
91
+ });
92
+ if (mergedSettings !== settings?.text) await writeOutput({
93
+ destination: settingsDestination,
94
+ text: mergedSettings,
95
+ mode: settings?.mode ?? 420
96
+ });
97
+ process.stdout.write(`Generated ${cloudSetupPath} and ${cloudClaudeSettingsPath}\n`);
57
98
  };
58
99
  main().catch((error) => {
59
100
  process.stderr.write(`cloud-setup: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -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 { lstat, mkdir, open, readFile, realpath } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { cloudSetupPath, generateCloudSetup } from \"./cloud-setup\";\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 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.map(({ path, message }) => `${path}: ${message}`).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 } = parseToolchainConfiguration(JSON.parse(text));\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 });\n const destination = join(root, cloudSetupPath);\n const parent = dirname(destination);\n const parentStat = await optionalStat(parent);\n if (parentStat === undefined) await mkdir(parent);\n else if (parentStat.isSymbolicLink() || !parentStat.isDirectory())\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 const output = await open(\n destination,\n constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW,\n 0o755,\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(script, \"utf8\");\n await output.chmod(0o755);\n } finally {\n await output.close();\n }\n process.stdout.write(`Generated ${cloudSetupPath}\\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":";;;;;;;;AAYA,MAAM,QACJ;AACF,MAAM,eAAe,OAAO,SAAiB;CAC3C,IAAI;EACF,OAAO,MAAM,MAAM,IAAI;CACzB,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC9D,OAAO,KAAA;EACT,MAAM;CACR;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,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,MACR,YAAY,KAAK,EAAE,MAAM,cAAc,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,KAAK,IAAI,CACzE;CACF,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,sCAAsC;CACxD,MAAM,EAAE,UAAU,4BAA4B,KAAK,MAAM,IAAI,CAAC;CAC9D,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,wCAAwC;CAC1D,MAAM,cAAc,MAAM;CAC1B,IAAI,gBAAgB,KAAA,GAClB,MAAM,IAAI,MAAM,qCAAqC;CAMvD,MAAM,SAAS,mBAAmB;EAChC,QANa,qBACb,KAAK,MACH,MAAM,SAAS,IAAI,IAAI,qBAAqB,OAAO,KAAK,GAAG,GAAG,MAAM,CACtE,CAGK;EACL;EACA,aAAa,YAAY,KAAK;CAChC,CAAC;CACD,MAAM,cAAc,KAAK,MAAM,cAAc;CAC7C,MAAM,SAAS,QAAQ,WAAW;CAClC,MAAM,aAAa,MAAM,aAAa,MAAM;CAC5C,IAAI,eAAe,KAAA,GAAW,MAAM,MAAM,MAAM;MAC3C,IAAI,WAAW,eAAe,KAAK,CAAC,WAAW,YAAY,GAC9D,MAAM,IAAI,MAAM,wDAAwD;CAC1E,MAAM,kBAAkB,MAAM,aAAa,WAAW;CACtD,IACE,oBAAoB,KAAA,MACnB,gBAAgB,eAAe,KAC9B,CAAC,gBAAgB,OAAO,KACxB,gBAAgB,UAAU,IAE5B,MAAM,IAAI,MACR,uEACF;CACF,MAAM,SAAS,MAAM,KACnB,aACA,UAAU,WAAW,UAAU,UAAU,UAAU,YACnD,GACF;CACA,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,KAAK;EACjC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,UAAU,GACvC,MAAM,IAAI,MACR,uEACF;EACF,MAAM,OAAO,SAAS,CAAC;EACvB,MAAM,OAAO,UAAU,QAAQ,MAAM;EACrC,MAAM,OAAO,MAAM,GAAK;CAC1B,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;CACA,QAAQ,OAAO,MAAM,aAAa,eAAe,GAAG;AACtD;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"}
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 { 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 mergeCloudSessionStartHook,\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 = mergeCloudSessionStartHook(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":";;;;;;;;AAiBA,MAAM,QACJ;AACF,MAAM,eAAe,OAAO,SAAiB;CAC3C,IAAI;EACF,OAAO,MAAM,MAAM,IAAI;CACzB,SAAS,OAAO;EACd,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC9D,OAAO,KAAA;EACT,MAAM;CACR;AACF;AAEA,MAAM,cAAc,OAAO,gBAAwB;CACjD,MAAM,SAAS,QAAQ,WAAW;CAClC,MAAM,aAAa,MAAM,aAAa,MAAM;CAC5C,IACE,eAAe,KAAA,MACd,WAAW,eAAe,KAAK,CAAC,WAAW,YAAY,IAExD,MAAM,IAAI,MAAM,wDAAwD;CAC1E,MAAM,kBAAkB,MAAM,aAAa,WAAW;CACtD,IACE,oBAAoB,KAAA,MACnB,gBAAgB,eAAe,KAC9B,CAAC,gBAAgB,OAAO,KACxB,gBAAgB,UAAU,IAE5B,MAAM,IAAI,MACR,uEACF;CACF,OAAO;AACT;AAQA,MAAM,cAAc,OAAO,EAAE,aAAa,MAAM,WAA+B;CAC7E,MAAM,YAAY,WAAW;CAC7B,MAAM,MAAM,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;CACrD,MAAM,SAAS,MAAM,KACnB,aACA,UAAU,WAAW,UAAU,UAAU,UAAU,YACnD,IACF;CACA,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,KAAK;EACjC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,UAAU,GACvC,MAAM,IAAI,MACR,uEACF;EACF,MAAM,OAAO,SAAS,CAAC;EACvB,MAAM,OAAO,UAAU,MAAM,MAAM;EACnC,MAAM,OAAO,MAAM,IAAI;CACzB,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,MAAM,qBAAqB,OAAO,gBAAwB;CACxD,IAAI;EACF,IAAK,MAAM,YAAY,WAAW,MAAO,KAAA,GAAW,OAAO,KAAA;EAC3D,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,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,MACR,YACG,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,cAAc,KAAK,MAAM,cAAc;CAC7C,MAAM,YAAY,WAAW;CAC7B,MAAM,sBAAsB,KAAK,MAAM,uBAAuB;CAC9D,MAAM,WAAW,MAAM,mBAAmB,mBAAmB;CAC7D,MAAM,iBAAiB,2BAA2B,UAAU,IAAI;CAChE,MAAM,YAAY;EAAE;EAAa,MAAM;EAAQ,MAAM;CAAM,CAAC;CAC5D,IAAI,mBAAmB,UAAU,MAC/B,MAAM,YAAY;EAChB,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"}
@@ -6,14 +6,14 @@ const require_publish_contract = require("./publish-contract.cjs");
6
6
  let node_child_process = require("node:child_process");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
9
+ let node_crypto = require("node:crypto");
9
10
  let semver = require("semver");
10
- let node_os = require("node:os");
11
- let yaml = require("yaml");
12
11
  let node_util = require("node:util");
12
+ let yaml = require("yaml");
13
+ let node_os = require("node:os");
13
14
  let node_fs_promises = require("node:fs/promises");
14
15
  let node_module = require("node:module");
15
16
  let acorn = require("acorn");
16
- let node_crypto = require("node:crypto");
17
17
  //#region src/consumer-build-approvals.ts
18
18
  const consumerBuildApprovals = async ({ manifest, lock, manager, directory }) => {
19
19
  if (!require_toolchain_schema.consumerRecord(lock)) throw new Error("invalid consumer dependency lock");
@@ -325,7 +325,7 @@ const checkPackedJavaScriptWithNode = async ({ files, target, node, executable }
325
325
  };
326
326
  /** Read bytes without extracting archive-controlled paths or links. */
327
327
  const readPackedSyntaxFiles = async (archive) => {
328
- const { stdout: listing } = await command("tar", ["-tzf", archive], { maxBuffer: 16 * 1024 * 1024 });
328
+ const { stdout: listing } = await command("tar", ["-tzf", archive], { maxBuffer: 16777216 });
329
329
  const files = /* @__PURE__ */ new Map();
330
330
  const members = archiveMembers(listing.split("\n").filter((entry) => entry !== ""));
331
331
  const read = async (file) => {
@@ -340,7 +340,7 @@ const readPackedSyntaxFiles = async (archive) => {
340
340
  "-xOf",
341
341
  archive,
342
342
  file
343
- ], { maxBuffer: 16 * 1024 * 1024 });
343
+ ], { maxBuffer: 16777216 });
344
344
  files.set(file, stdout);
345
345
  };
346
346
  for (const file of members) if (javascript.test(file) || node_path.default.posix.basename(file) === "package.json") await read(file);
@@ -770,9 +770,10 @@ const installConsumerFixtureDependencies = async (options) => {
770
770
  const manifest = JSON.parse(await (0, node_fs_promises.readFile)(manifestFile, "utf8"));
771
771
  if (!require_toolchain_schema.consumerRecord(manifest)) throw new Error("invalid consumer fixture manifest");
772
772
  const lockSource = await (0, node_fs_promises.readFile)(node_path.default.join(options.directory, options.manager === "npm" ? "package-lock.json" : "pnpm-lock.yaml"), "utf8");
773
+ const lock = options.manager === "npm" ? JSON.parse(lockSource) : (0, yaml.parse)(lockSource);
773
774
  const { approvals, rebuildTargets } = await consumerBuildApprovals({
774
775
  manifest,
775
- lock: options.manager === "npm" ? JSON.parse(lockSource) : (0, yaml.parse)(lockSource),
776
+ lock,
776
777
  manager: options.manager,
777
778
  directory: options.directory
778
779
  });