@stll/oxlint-config 0.10.0 → 0.12.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 +248 -0
- package/dist/cloud-setup-cli.cjs +63 -0
- package/dist/cloud-setup-cli.cjs.map +1 -0
- package/dist/cloud-setup-cli.d.cts +1 -0
- package/dist/cloud-setup-cli.d.mts +1 -0
- package/dist/cloud-setup-cli.mjs +63 -0
- package/dist/cloud-setup-cli.mjs.map +1 -0
- package/dist/toolchain-check-cli.cjs +10 -197
- package/dist/toolchain-check-cli.cjs.map +1 -1
- package/dist/toolchain-check-cli.mjs +9 -173
- package/dist/toolchain-check-cli.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +3302 -0
- package/dist/toolchain-guard.cjs.map +1 -0
- package/dist/toolchain-guard.mjs +3243 -0
- package/dist/toolchain-guard.mjs.map +1 -0
- package/dist/typecheck-parity-cli.cjs +77 -16
- package/dist/typecheck-parity-cli.cjs.map +1 -1
- package/dist/typecheck-parity-cli.mjs +77 -16
- package/dist/typecheck-parity-cli.mjs.map +1 -1
- package/package.json +11 -1
- package/toolchain.json +91 -1
package/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# @stll/oxlint-config
|
|
2
|
+
|
|
3
|
+
Shared oxlint presets, JavaScript plugins, and toolchain policy for Stella
|
|
4
|
+
packages. Repository-specific rules and exceptions belong in the consuming
|
|
5
|
+
repository's configuration.
|
|
6
|
+
|
|
7
|
+
## Install and configure
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add -d @stll/oxlint-config
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Install `oxlint`, `oxlint-tsgolint`, and any other shared tools at the exact
|
|
14
|
+
versions in the exported `@stll/oxlint-config/toolchain.json`. That file is the
|
|
15
|
+
source for current pins; peer dependency ranges describe supported versions.
|
|
16
|
+
|
|
17
|
+
Use the library preset in `oxlint.config.ts`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { library } from "@stll/oxlint-config";
|
|
21
|
+
|
|
22
|
+
export default library({
|
|
23
|
+
ignorePatterns: ["dist/"],
|
|
24
|
+
overrides: [
|
|
25
|
+
{
|
|
26
|
+
files: ["scripts/**"],
|
|
27
|
+
rules: { "no-console": "off" },
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The preset includes the shared `stella-lowercase` and `no-raw-colors`
|
|
34
|
+
JavaScript plugins. The separate `@stll/oxlint-plugin` package provides
|
|
35
|
+
portable safety and route-query rules. See the
|
|
36
|
+
[repository usage guide](https://github.com/stella/tooling#usage) for other
|
|
37
|
+
presets, CommonJS configuration, and plugin composition.
|
|
38
|
+
|
|
39
|
+
## Shared toolchain and Bun
|
|
40
|
+
|
|
41
|
+
`@stll/oxlint-config/toolchain.json` is a versioned policy for package pins,
|
|
42
|
+
Bun, TypeScript install layouts, Node, Python, Rust, GitHub Actions, and
|
|
43
|
+
Dependabot. Inherit the fields for tools your repository uses. The checker
|
|
44
|
+
does not require tools absent from the repository.
|
|
45
|
+
|
|
46
|
+
Set `packageManager` to `bun@` followed by the policy's `bun` value, and pin
|
|
47
|
+
`bun-types` to that same value. Dependency pins can live in default or named
|
|
48
|
+
catalogs, including Bun's `workspaces` catalogs. Bun image tags, version
|
|
49
|
+
files, and mise/asdf entries also inherit the policy pin.
|
|
50
|
+
|
|
51
|
+
Configure `oven-sh/setup-bun` with a tracked manifest as its version source:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
with:
|
|
55
|
+
bun-version-file: package.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The action itself must use the policy's approved SHA and version comment.
|
|
59
|
+
A prefixed version-file selector can use a preceding checkout in its job:
|
|
60
|
+
use the exact `${{ job.workflow_repository }}` / `${{ job.workflow_sha }}` pair,
|
|
61
|
+
or a same-repository checkout with no `ref`. For no-ref checkouts, omit
|
|
62
|
+
`repository`, name this repository literally, or use `${{ github.repository }}`.
|
|
63
|
+
The checkout must omit both `if` and `continue-on-error`, and its normalized
|
|
64
|
+
path must be unique among every checkout in that job, including later steps.
|
|
65
|
+
Unknown checkout destinations prevent mapped selectors from proving provenance.
|
|
66
|
+
Sparse checkouts must explicitly list the selected repository-relative file,
|
|
67
|
+
without the checkout prefix, in `sparse-checkout`. This applies in either cone
|
|
68
|
+
mode and to delegated sources. Dynamic, empty, glob, negation, or unsupported
|
|
69
|
+
sparse configurations fail; a cone-mode input alone is insufficient. Directory
|
|
70
|
+
entries cannot substitute for the selected file.
|
|
71
|
+
Other nonempty, nonconstant GitHub expression refs on same-repository checkouts
|
|
72
|
+
(including `${{ job.workflow_repository }}`) delegate a safe static version-file selector
|
|
73
|
+
to that checkout. The CLI reports delegation on stdout without validating the
|
|
74
|
+
current source's version file. Literal refs and unpaired `${{ github.sha }}` /
|
|
75
|
+
`${{ job.workflow_sha }}` remain rejected.
|
|
76
|
+
For snapshot-bound sources, the guard maps the selector to its tracked source
|
|
77
|
+
file. Literal commit SHAs,
|
|
78
|
+
branches, tags, unpaired snapshot contexts, foreign repositories, and traversal
|
|
79
|
+
paths fail.
|
|
80
|
+
Direct `bun-version` inputs are forbidden. For snapshot-bound sources, the
|
|
81
|
+
manifest referenced by `bun-version-file` must declare the shared
|
|
82
|
+
`packageManager` value. Delegated commits own their version validation.
|
|
83
|
+
|
|
84
|
+
The Node policy is `26.x`. Runtime selectors (`.node-version`, `.nvmrc`,
|
|
85
|
+
setup-node's `node-version-file`, Node image tags and mise/asdf entries)
|
|
86
|
+
must select major 26: bare major, major wildcard, minor and stable patch
|
|
87
|
+
selectors within that major are allowed. Open ranges, floating aliases,
|
|
88
|
+
other majors and prereleases fail. Use `node-version-file` for setup-node.
|
|
89
|
+
`engines.node` remains a support range and must include every stable release
|
|
90
|
+
in major 26; `>=26`, `^26` and `^24 || ^26` pass, while ranges that omit
|
|
91
|
+
part of major 26 fail.
|
|
92
|
+
Release and publish workflows use exact patch version-file selectors for
|
|
93
|
+
reproducible builds; this repository pins `.node-version` to `26.10.0`.
|
|
94
|
+
The series policy accepts that patch without weakening release validation.
|
|
95
|
+
GitHub JavaScript action host runtimes (`runs.using`) are platform-managed
|
|
96
|
+
and do not select the project toolchain Node version.
|
|
97
|
+
|
|
98
|
+
A published package can test the minimum Node major in its `engines.node`
|
|
99
|
+
support range in one declared workflow job. Add a scoped `engineFloors` entry
|
|
100
|
+
to tracked root `stll-toolchain.json`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"engineFloors": [
|
|
105
|
+
{
|
|
106
|
+
"package": "packages/library",
|
|
107
|
+
"workflow": ".github/workflows/ci.yml",
|
|
108
|
+
"job": "node-floor"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
That job may use a literal exact stable patch in setup-node's `node-version`
|
|
115
|
+
whose major matches the range's minimum major and whose release satisfies
|
|
116
|
+
the range (for example, `20.10.0` for `>=20.10.0`). The approved action SHA, version comment and input validation
|
|
117
|
+
also apply. Other jobs and runtime files follow the shared Node series.
|
|
118
|
+
Missing packages or jobs, private packages, absent setup-node selectors and
|
|
119
|
+
floor mismatches fail configuration validation. `optOuts` is optional when
|
|
120
|
+
only `engineFloors` is declared.
|
|
121
|
+
|
|
122
|
+
Run the installed checker from the repository root in CI:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
bunx --no-install stll-toolchain-check
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The CLI checks tracked files, prints `path:line: [rule] message` diagnostics,
|
|
129
|
+
and exits with status 1 on a mismatch. Updating the package updates the
|
|
130
|
+
policy; update consumer pins and run the checker together.
|
|
131
|
+
|
|
132
|
+
`typescriptInstallLayouts` declares the compiler package, exact specifier,
|
|
133
|
+
and typecheck command for each supported layout. Prefer the direct layout.
|
|
134
|
+
Use the split compatibility layout for the compiler-API consumers and peer
|
|
135
|
+
blockers listed in `typescript6Compatibility`; it retains the current
|
|
136
|
+
compiler for typechecking while providing the compatibility package.
|
|
137
|
+
|
|
138
|
+
An exception requires a tracked root `stll-toolchain.json` with the named
|
|
139
|
+
rule and a nonempty reason:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"optOuts": [{ "rule": "bun-pins", "reason": "This repository uses npm." }]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Workspace TypeScript toolchains combine the root and declared members' devDependencies.
|
|
148
|
+
An included member may declare the exact TypeScript 6 compatibility API across
|
|
149
|
+
dependencies, devDependencies, and peerDependencies when that shared install uses
|
|
150
|
+
the complete split layout. Ambiguous or partial layouts do not grant this allowance.
|
|
151
|
+
Peer ranges declare support: they must include the policy compiler release and the
|
|
152
|
+
compatibility release when the selected layout uses it. Ranges do not contribute
|
|
153
|
+
to the installed compiler inventory.
|
|
154
|
+
|
|
155
|
+
JavaScript update roots come from the repository root, declared workspace membership,
|
|
156
|
+
and directories with their own `bun.lock`, `package-lock.json`, `yarn.lock`, or
|
|
157
|
+
`pnpm-lock.yaml`. Other package manifests do not create update roots; directory names
|
|
158
|
+
have no special meaning. Workspace members share their owner's update entry.
|
|
159
|
+
|
|
160
|
+
`node-engine` and `cloud-setup-drift` are mandatory: opt-outs cannot bypass the
|
|
161
|
+
shared Node support range or generated cloud setup requirements.
|
|
162
|
+
|
|
163
|
+
Opt-outs apply to the entire repository for that rule. Unknown or duplicate
|
|
164
|
+
rules, empty reasons, and malformed configuration fail validation. See the
|
|
165
|
+
[shared toolchain reference](https://github.com/stella/tooling#shared-toolchain)
|
|
166
|
+
for the schema, rule list, runtime support ranges, and generated Dependabot
|
|
167
|
+
template.
|
|
168
|
+
|
|
169
|
+
## Typecheck parity project selection
|
|
170
|
+
|
|
171
|
+
Run `stll-typecheck-parity` from the repository root. Without flags, it checks
|
|
172
|
+
the root `tsconfig.json` and recursively discovers project references in build
|
|
173
|
+
mode. Repeat `--project` to check exactly the selected configs:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
stll-typecheck-parity --project packages/api/tsconfig.json --project packages/web/tsconfig.json
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Selected configs run independently using the repository-root compiler
|
|
180
|
+
installation; child packages need no compiler declaration and a root tsconfig
|
|
181
|
+
is unnecessary. Their original reference metadata is preserved without
|
|
182
|
+
discovering or building referenced projects. Missing, unreadable, and zero-input
|
|
183
|
+
selected configs fail the check. `--help` prints usage and exits successfully;
|
|
184
|
+
unknown arguments fail.
|
|
185
|
+
|
|
186
|
+
## Shared cloud setup
|
|
187
|
+
|
|
188
|
+
Add an explicit declaration to tracked root `stll-toolchain.json`:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"optOuts": [],
|
|
193
|
+
"cloud": {
|
|
194
|
+
"services": ["postgres", "valkey"],
|
|
195
|
+
"install": "bun install --frozen-lockfile",
|
|
196
|
+
"envFile": ".env.cloud"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Services are optional within the declared list: use `[]` for runtimes and
|
|
202
|
+
dependencies only. No `cloud` declaration means no cloud setup script.
|
|
203
|
+
`envFile` must be a canonical repository-relative path; its existing parent
|
|
204
|
+
must be safe at runtime. Track an exact stable Node patch in root
|
|
205
|
+
`.node-version` within the shared Node series. Generate and commit the script:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
bunx --no-install stll-cloud-setup
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Generation uses the installed policy's Bun, Node, PostgreSQL, and Valkey pins.
|
|
212
|
+
PostgreSQL uses the PGDG major-version packages; minor updates within the pinned
|
|
213
|
+
major are accepted for the disposable database. Before adding PGDG, provisioning
|
|
214
|
+
reuses an existing source only after verifying its signing-key fingerprint;
|
|
215
|
+
conflicts identify the source file. Existing matching-major binaries are reused. Valkey uses official versioned
|
|
216
|
+
binary DEBs for the declared release and architecture, verifies the policy's
|
|
217
|
+
SHA256 pins, and installs those local files with apt. It does not resolve Valkey
|
|
218
|
+
from a mutable package index.
|
|
219
|
+
The command writes `.agents/cloud-setup.sh` with executable permissions and
|
|
220
|
+
refuses symlink parents or destinations and hard-linked destinations. `stll-toolchain-check` rejects missing,
|
|
221
|
+
drifted, or undeclared tracked scripts with mandatory `cloud-setup-drift`.
|
|
222
|
+
Regenerate after changing the policy or declaration. `stll-cloud-setup --help`
|
|
223
|
+
prints usage without reading repository configuration.
|
|
224
|
+
|
|
225
|
+
| Environment phase | Command |
|
|
226
|
+
| ----------------------- | ------------------------------------- |
|
|
227
|
+
| Networked install | `bash .agents/cloud-setup.sh install` |
|
|
228
|
+
| Offline session startup | `bash .agents/cloud-setup.sh start` |
|
|
229
|
+
|
|
230
|
+
Both commands can be rerun. Missing or unknown subcommands print usage and
|
|
231
|
+
exit 2; there is no implicit lifecycle mode. The script supports Ubuntu 24.04
|
|
232
|
+
with root access or passwordless sudo and fails when required capabilities
|
|
233
|
+
or pinned installations are unavailable. `install` provisions runtimes,
|
|
234
|
+
dependencies, and declared services; `start` restores local services after
|
|
235
|
+
cached environments lose running processes. Services bind only to localhost.
|
|
236
|
+
System provisioning runs as root; frozen dependencies run as the invoking user
|
|
237
|
+
with that user's home and a private, user-owned Bun cache. Package installation
|
|
238
|
+
does not permit automatic downgrades.
|
|
239
|
+
If a newer Valkey package is already installed, provisioning fails rather than
|
|
240
|
+
downgrading it; use an environment compatible with the pinned release.
|
|
241
|
+
The environment file contains `NODE_ENV=test` and declared local connection
|
|
242
|
+
URLs, carries a generated ownership marker, and refuses an unowned file or
|
|
243
|
+
symlink. Configure the repository's test runner to load that file; migration
|
|
244
|
+
commands and repository adoption are separate configuration steps.
|
|
245
|
+
|
|
246
|
+
Environments may cache filesystems without preserving service processes.
|
|
247
|
+
Run `start` at every session start, including sessions that reuse installed files.
|
|
248
|
+
The supported OS, privileges, and pinned runtime requirements are checked explicitly.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_toolchain_guard = require("./toolchain-guard.cjs");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
let node_fs_promises = require("node:fs/promises");
|
|
7
|
+
//#region src/cloud-setup-cli.ts
|
|
8
|
+
const usage = "Usage: stll-cloud-setup (run from repository root)\n stll-cloud-setup --help";
|
|
9
|
+
const optionalStat = async (path) => {
|
|
10
|
+
try {
|
|
11
|
+
return await (0, node_fs_promises.lstat)(path);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const main = async () => {
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
if (args.length === 1 && args.at(0) === "--help") {
|
|
20
|
+
process.stdout.write(`${usage}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (args.length !== 0) throw new Error(usage);
|
|
24
|
+
const root = await (0, node_fs_promises.realpath)(process.cwd());
|
|
25
|
+
const { files, diagnostics } = require_toolchain_guard.readToolchainInputs(root);
|
|
26
|
+
if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}`).join("\n"));
|
|
27
|
+
const text = files["stll-toolchain.json"];
|
|
28
|
+
if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
|
|
29
|
+
const { cloud } = require_toolchain_guard.parseToolchainConfiguration(JSON.parse(text));
|
|
30
|
+
if (cloud === void 0) throw new Error("requires an explicit cloud declaration");
|
|
31
|
+
const nodeVersion = files[".node-version"];
|
|
32
|
+
if (nodeVersion === void 0) throw new Error("requires tracked root .node-version");
|
|
33
|
+
const script = require_toolchain_guard.generateCloudSetup({
|
|
34
|
+
policy: require_toolchain_guard.parseToolchainPolicy(JSON.parse(await (0, node_fs_promises.readFile)(new URL("../toolchain.json", require("url").pathToFileURL(__filename).href), "utf8"))),
|
|
35
|
+
cloud,
|
|
36
|
+
nodeVersion: nodeVersion.trim()
|
|
37
|
+
});
|
|
38
|
+
const destination = (0, node_path.join)(root, require_toolchain_guard.cloudSetupPath);
|
|
39
|
+
const parent = (0, node_path.dirname)(destination);
|
|
40
|
+
const parentStat = await optionalStat(parent);
|
|
41
|
+
if (parentStat === void 0) await (0, node_fs_promises.mkdir)(parent);
|
|
42
|
+
else if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) throw new Error("cloud setup parent must be a real repository directory");
|
|
43
|
+
const destinationStat = await optionalStat(destination);
|
|
44
|
+
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");
|
|
45
|
+
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);
|
|
46
|
+
try {
|
|
47
|
+
const opened = await output.stat();
|
|
48
|
+
if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
|
|
49
|
+
await output.truncate(0);
|
|
50
|
+
await output.writeFile(script, "utf8");
|
|
51
|
+
await output.chmod(493);
|
|
52
|
+
} finally {
|
|
53
|
+
await output.close();
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(`Generated ${require_toolchain_guard.cloudSetupPath}\n`);
|
|
56
|
+
};
|
|
57
|
+
main().catch((error) => {
|
|
58
|
+
process.stderr.write(`cloud-setup: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
});
|
|
61
|
+
//#endregion
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=cloud-setup-cli.cjs.map
|
|
@@ -0,0 +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,wBAAAA,mBAAmB;EAChC,QANaC,wBAAAA,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,wBAAAA,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,wBAAAA,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as cloudSetupPath, i as parseToolchainPolicy, n as parseToolchainConfiguration, o as generateCloudSetup, r as readToolchainInputs } from "./toolchain-guard.mjs";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { lstat, mkdir, open, readFile, realpath } from "node:fs/promises";
|
|
6
|
+
//#region src/cloud-setup-cli.ts
|
|
7
|
+
const usage = "Usage: stll-cloud-setup (run from repository root)\n stll-cloud-setup --help";
|
|
8
|
+
const optionalStat = async (path) => {
|
|
9
|
+
try {
|
|
10
|
+
return await lstat(path);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const main = async () => {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
if (args.length === 1 && args.at(0) === "--help") {
|
|
19
|
+
process.stdout.write(`${usage}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (args.length !== 0) throw new Error(usage);
|
|
23
|
+
const root = await realpath(process.cwd());
|
|
24
|
+
const { files, diagnostics } = readToolchainInputs(root);
|
|
25
|
+
if (diagnostics.length > 0) throw new Error(diagnostics.map(({ path, message }) => `${path}: ${message}`).join("\n"));
|
|
26
|
+
const text = files["stll-toolchain.json"];
|
|
27
|
+
if (text === void 0) throw new Error("requires tracked stll-toolchain.json");
|
|
28
|
+
const { cloud } = parseToolchainConfiguration(JSON.parse(text));
|
|
29
|
+
if (cloud === void 0) throw new Error("requires an explicit cloud declaration");
|
|
30
|
+
const nodeVersion = files[".node-version"];
|
|
31
|
+
if (nodeVersion === void 0) throw new Error("requires tracked root .node-version");
|
|
32
|
+
const script = generateCloudSetup({
|
|
33
|
+
policy: parseToolchainPolicy(JSON.parse(await readFile(new URL("../toolchain.json", import.meta.url), "utf8"))),
|
|
34
|
+
cloud,
|
|
35
|
+
nodeVersion: nodeVersion.trim()
|
|
36
|
+
});
|
|
37
|
+
const destination = join(root, cloudSetupPath);
|
|
38
|
+
const parent = dirname(destination);
|
|
39
|
+
const parentStat = await optionalStat(parent);
|
|
40
|
+
if (parentStat === void 0) await mkdir(parent);
|
|
41
|
+
else if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) throw new Error("cloud setup parent must be a real repository directory");
|
|
42
|
+
const destinationStat = await optionalStat(destination);
|
|
43
|
+
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");
|
|
44
|
+
const output = await open(destination, constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW, 493);
|
|
45
|
+
try {
|
|
46
|
+
const opened = await output.stat();
|
|
47
|
+
if (!opened.isFile() || opened.nlink !== 1) throw new Error("cloud setup destination must be a single-link regular repository file");
|
|
48
|
+
await output.truncate(0);
|
|
49
|
+
await output.writeFile(script, "utf8");
|
|
50
|
+
await output.chmod(493);
|
|
51
|
+
} finally {
|
|
52
|
+
await output.close();
|
|
53
|
+
}
|
|
54
|
+
process.stdout.write(`Generated ${cloudSetupPath}\n`);
|
|
55
|
+
};
|
|
56
|
+
main().catch((error) => {
|
|
57
|
+
process.stderr.write(`cloud-setup: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
});
|
|
60
|
+
//#endregion
|
|
61
|
+
export {};
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=cloud-setup-cli.mjs.map
|
|
@@ -0,0 +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,209 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
|
|
4
|
-
var __create = Object.create;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
-
key = keys[i];
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
-
value: mod,
|
|
22
|
-
enumerable: true
|
|
23
|
-
}) : target, mod));
|
|
24
|
-
//#endregion
|
|
3
|
+
const require_toolchain_guard = require("./toolchain-guard.cjs");
|
|
25
4
|
let node_fs = require("node:fs");
|
|
26
|
-
let node_child_process = require("node:child_process");
|
|
27
|
-
let node_path = require("node:path");
|
|
28
|
-
node_path = __toESM(node_path, 1);
|
|
29
|
-
//#region src/toolchain-check.ts
|
|
30
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
-
const scalar = (value) => value.trim().replace(/\s+#.*$/, "").replace(/^["']|["']$/g, "");
|
|
32
|
-
const trackedPath = (file) => !file.split("/").some((part) => part === "node_modules" || part === "vendor");
|
|
33
|
-
/** Check tracked consumer files without traversing dependencies or the working tree. */
|
|
34
|
-
const checkToolchain = ({ root, bunVersion, allowNonBunPackageManagers = [] }) => {
|
|
35
|
-
const files = (0, node_child_process.execFileSync)("git", [
|
|
36
|
-
"ls-files",
|
|
37
|
-
"-z",
|
|
38
|
-
"--cached"
|
|
39
|
-
], {
|
|
40
|
-
cwd: root,
|
|
41
|
-
encoding: "utf8"
|
|
42
|
-
}).split("\0").filter((file) => file !== "" && trackedPath(file));
|
|
43
|
-
const tracked = new Set(files);
|
|
44
|
-
const diagnostics = [];
|
|
45
|
-
const add = (file, line, message) => {
|
|
46
|
-
diagnostics.push({
|
|
47
|
-
path: file,
|
|
48
|
-
line,
|
|
49
|
-
message
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
const read = (file) => {
|
|
53
|
-
try {
|
|
54
|
-
return (0, node_fs.readFileSync)(node_path.default.join(root, file), "utf8");
|
|
55
|
-
} catch {
|
|
56
|
-
add(file, 1, "cannot read tracked file");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const checkVersion = (file, line, value, label) => {
|
|
61
|
-
if (value !== bunVersion) add(file, line, `${label} must be ${bunVersion}, found ${String(value)}`);
|
|
62
|
-
};
|
|
63
|
-
const packages = /* @__PURE__ */ new Map();
|
|
64
|
-
for (const file of files.filter((entry) => node_path.default.basename(entry) === "package.json")) {
|
|
65
|
-
const text = read(file);
|
|
66
|
-
if (text === void 0) continue;
|
|
67
|
-
let json;
|
|
68
|
-
try {
|
|
69
|
-
json = JSON.parse(text);
|
|
70
|
-
} catch {
|
|
71
|
-
add(file, 1, "invalid package.json");
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (!isRecord(json)) {
|
|
75
|
-
add(file, 1, "package.json must contain an object");
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
packages.set(file, {
|
|
79
|
-
manager: json["packageManager"],
|
|
80
|
-
json,
|
|
81
|
-
text
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
const catalogSource = (json) => isRecord(json["workspaces"]) ? json["workspaces"] : json;
|
|
85
|
-
const rootCatalogs = catalogSource(packages.get("package.json")?.json ?? {});
|
|
86
|
-
for (const [file, { json, text }] of packages) {
|
|
87
|
-
const lines = text.split(/\r?\n/);
|
|
88
|
-
const reportedLines = /* @__PURE__ */ new Map();
|
|
89
|
-
const propertyLine = (key, value) => {
|
|
90
|
-
const serialized = value === void 0 ? void 0 : JSON.stringify(value);
|
|
91
|
-
const matchKey = `${key}:${serialized ?? ""}`;
|
|
92
|
-
const previous = reportedLines.get(matchKey) ?? -1;
|
|
93
|
-
const index = lines.findIndex((line, lineIndex) => lineIndex > previous && line.includes(`"${key}"`) && (serialized === void 0 || line.includes(serialized)));
|
|
94
|
-
if (index >= 0) reportedLines.set(matchKey, index);
|
|
95
|
-
return Math.max(1, index + 1);
|
|
96
|
-
};
|
|
97
|
-
if (json["packageManager"] !== void 0) {
|
|
98
|
-
if (typeof json["packageManager"] === "string" && json["packageManager"].startsWith("bun@")) checkVersion(file, propertyLine("packageManager"), json["packageManager"].slice(4), "packageManager Bun version");
|
|
99
|
-
else if (!allowNonBunPackageManagers.includes(file)) add(file, propertyLine("packageManager"), "packageManager is not Bun; use --allow-non-bun-package-manager with this path to allow it");
|
|
100
|
-
}
|
|
101
|
-
const checkDependencies = (value, resolveCatalog = false) => {
|
|
102
|
-
if (!isRecord(value) || value["bun-types"] === void 0) return;
|
|
103
|
-
const specifier = value["bun-types"];
|
|
104
|
-
const resolveVersion = () => {
|
|
105
|
-
if (!resolveCatalog || typeof specifier !== "string" || !specifier.startsWith("catalog:")) return specifier;
|
|
106
|
-
const catalogName = specifier.slice(8);
|
|
107
|
-
const source = file === "package.json" ? catalogSource(json) : rootCatalogs;
|
|
108
|
-
const catalog = catalogName === "" ? source["catalog"] : isRecord(source["catalogs"]) && source["catalogs"][catalogName];
|
|
109
|
-
return isRecord(catalog) ? catalog["bun-types"] : void 0;
|
|
110
|
-
};
|
|
111
|
-
checkVersion(file, propertyLine("bun-types", specifier), resolveVersion(), "bun-types");
|
|
112
|
-
};
|
|
113
|
-
checkDependencies(json["dependencies"], true);
|
|
114
|
-
checkDependencies(json["devDependencies"], true);
|
|
115
|
-
checkDependencies(json["catalog"]);
|
|
116
|
-
if (isRecord(json["catalogs"])) for (const catalog of Object.values(json["catalogs"])) checkDependencies(catalog);
|
|
117
|
-
if (isRecord(json["workspaces"])) {
|
|
118
|
-
checkDependencies(json["workspaces"]["catalog"]);
|
|
119
|
-
if (isRecord(json["workspaces"]["catalogs"])) for (const catalog of Object.values(json["workspaces"]["catalogs"])) checkDependencies(catalog);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for (const file of files) {
|
|
123
|
-
const name = node_path.default.basename(file);
|
|
124
|
-
const workflow = /^\.github\/workflows\/[^/]+\.ya?ml$/.test(file) || /^\.github\/actions\/.+\/action\.ya?ml$/.test(file);
|
|
125
|
-
const dockerfile = name.startsWith("Dockerfile") || name === "Containerfile";
|
|
126
|
-
const versionFile = name === ".bun-version" || name === ".tool-versions";
|
|
127
|
-
const mise = name === "mise.toml" || name === ".mise.toml";
|
|
128
|
-
if (!workflow && !dockerfile && !versionFile && !mise) continue;
|
|
129
|
-
const text = read(file);
|
|
130
|
-
if (text === void 0) continue;
|
|
131
|
-
if (name === ".bun-version") checkVersion(file, 1, scalar(text), ".bun-version");
|
|
132
|
-
let section = "";
|
|
133
|
-
for (const [index, original] of text.split(/\r?\n/).entries()) {
|
|
134
|
-
const line = index + 1;
|
|
135
|
-
if (/^\s*#/.test(original) || original.trim() === "") continue;
|
|
136
|
-
if (workflow) {
|
|
137
|
-
if (original.match(/(?:^\s*(?:-\s*)?|[{,]\s*)["']?bun-version["']?\s*:/) !== null) add(file, line, "bun-version literals are forbidden; use bun-version-file pointing to a package.json with the shared Bun pin");
|
|
138
|
-
const reference = original.match(/(?:^\s*(?:-\s*)?|[{,]\s*)["']?bun-version-file["']?\s*:\s*(?:"([^"]*)"|'([^']*)'|([^,}]*))/);
|
|
139
|
-
if (reference !== null) {
|
|
140
|
-
const target = scalar(reference[1] ?? reference[2] ?? reference[3] ?? "");
|
|
141
|
-
const normalized = node_path.default.posix.normalize(target);
|
|
142
|
-
const pkg = packages.get(normalized);
|
|
143
|
-
if (target === "" || node_path.default.posix.isAbsolute(target) || normalized.startsWith("../") || !tracked.has(normalized) || node_path.default.basename(normalized) !== "package.json" || pkg?.manager !== `bun@${bunVersion}`) add(file, line, `bun-version-file must reference a tracked package.json with packageManager bun@${bunVersion}, found ${target}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (dockerfile) {
|
|
147
|
-
const image = original.match(/^\s*FROM\s+(?:--\S+\s+)*oven\/bun(?::([^\s@]+))?(?:@\S+)?(?:\s|$)/i);
|
|
148
|
-
if (image !== null) {
|
|
149
|
-
const tag = image[1];
|
|
150
|
-
if (tag !== void 0) {
|
|
151
|
-
const version = tag.match(/^(\d+\.\d+\.\d+)(?:-|$)/)?.[1];
|
|
152
|
-
checkVersion(file, line, version ?? tag, "oven/bun version");
|
|
153
|
-
} else if (!/oven\/bun@/i.test(original)) add(file, line, `oven/bun must use a ${bunVersion} version tag`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (name === ".tool-versions") {
|
|
157
|
-
const entry = original.match(/^\s*bun\s+(.+)$/);
|
|
158
|
-
if (entry !== null) checkVersion(file, line, scalar(entry[1] ?? ""), ".tool-versions Bun version");
|
|
159
|
-
}
|
|
160
|
-
if (mise) {
|
|
161
|
-
const heading = original.match(/^\s*\[([^\]]+)\]/);
|
|
162
|
-
if (heading !== null) section = heading[1] ?? "";
|
|
163
|
-
const checkMiseVersion = (value) => {
|
|
164
|
-
const inlineVersion = [...value.matchAll(/[,{]\s*([\w-]+|"[^"]+"|'[^']+')\s*=\s*("(?:\\.|[^"\\])*"|'[^']*'|[^,{}]+)\s*(?=[,}])/g)].find((field) => scalar(field[1] ?? "") === "version");
|
|
165
|
-
checkVersion(file, line, scalar(inlineVersion?.[2] ?? value), "mise Bun version");
|
|
166
|
-
};
|
|
167
|
-
const entry = original.match(/^\s*["']?bun["']?\s*=\s*(.+)$/);
|
|
168
|
-
if (entry !== null && section === "tools") checkMiseVersion(entry[1] ?? "");
|
|
169
|
-
const dotted = original.match(/^\s*tools\.bun(?:\.version)?\s*=\s*(.+)$/);
|
|
170
|
-
if (dotted !== null && section === "") checkMiseVersion(dotted[1] ?? "");
|
|
171
|
-
const tableVersion = original.match(/^\s*version\s*=\s*(.+)$/);
|
|
172
|
-
if (tableVersion !== null && section === "tools.bun") checkMiseVersion(tableVersion[1] ?? "");
|
|
173
|
-
const inlineTools = original.match(/^\s*tools\s*=\s*\{(.*)\}/);
|
|
174
|
-
if (inlineTools !== null && section === "") {
|
|
175
|
-
const inlineBun = inlineTools[1]?.match(/(?:^|,)\s*["']?bun["']?\s*=\s*(\{[^}]*\}|[^,}]+)/);
|
|
176
|
-
if (inlineBun !== null && inlineBun !== void 0) checkMiseVersion(inlineBun[1] ?? "");
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return diagnostics;
|
|
182
|
-
};
|
|
183
|
-
//#endregion
|
|
184
5
|
//#region src/toolchain-check-cli.ts
|
|
185
6
|
try {
|
|
186
|
-
const allowNonBunPackageManagers = [];
|
|
187
7
|
const args = process.argv.slice(2);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
process.stdout.write("Usage: stll-toolchain-check [--allow-non-bun-package-manager <tracked package.json path>]\n");
|
|
192
|
-
process.exit(0);
|
|
193
|
-
}
|
|
194
|
-
const value = args.at(index + 1);
|
|
195
|
-
if (argument !== "--allow-non-bun-package-manager" || value === void 0 || value.startsWith("--")) throw new Error(`unexpected or incomplete argument: ${argument}`);
|
|
196
|
-
allowNonBunPackageManagers.push(value);
|
|
197
|
-
index += 1;
|
|
8
|
+
if (args.length === 1 && args[0] === "--help") {
|
|
9
|
+
process.stdout.write("Usage: stll-toolchain-check (opt-outs: tracked stll-toolchain.json)\n");
|
|
10
|
+
process.exit(0);
|
|
198
11
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const diagnostics = checkToolchain({
|
|
12
|
+
if (args.length !== 0) throw new Error(`unexpected argument: ${args.at(0)}`);
|
|
13
|
+
const policy = require_toolchain_guard.parseToolchainPolicy(JSON.parse((0, node_fs.readFileSync)(new URL("../toolchain.json", require("url").pathToFileURL(__filename).href), "utf8")));
|
|
14
|
+
const diagnostics = require_toolchain_guard.checkToolchain({
|
|
202
15
|
root: process.cwd(),
|
|
203
|
-
|
|
204
|
-
|
|
16
|
+
policy,
|
|
17
|
+
onDelegated: ({ path, line, tool, selector, ref }) => process.stdout.write(`${path}:${line}: [runtime-delegated] setup-${tool} ${selector} delegates its version to checkout ref ${ref}\n`)
|
|
205
18
|
});
|
|
206
|
-
for (const diagnostic of diagnostics) process.stderr.write(`${diagnostic.path}:${diagnostic.line}: ${diagnostic.message}\n`);
|
|
19
|
+
for (const diagnostic of diagnostics) process.stderr.write(`${diagnostic.path}:${diagnostic.line}: [${diagnostic.rule}] ${diagnostic.message}\n`);
|
|
207
20
|
if (diagnostics.length > 0) process.exitCode = 1;
|
|
208
21
|
} catch (error) {
|
|
209
22
|
process.stderr.write(`toolchain-check:1: ${error instanceof Error ? error.message : String(error)}\n`);
|