@raypx-kit/devkit 0.0.0-alpha.1 → 0.0.0-alpha.3
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/CHANGELOG.md +14 -0
- package/README.md +68 -0
- package/dist/lint.d.mts +68 -0
- package/dist/lint.mjs +71 -0
- package/dist/lint.mjs.map +1 -0
- package/dist/testing.d.mts +13 -0
- package/dist/testing.mjs +18 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/toolchain.d.mts +9 -1
- package/dist/toolchain.mjs +47 -22
- package/dist/toolchain.mjs.map +1 -1
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @raypx-kit/devkit
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0b904b6: Republish through the CI release pipeline with npm provenance. No functional changes.
|
|
8
|
+
|
|
9
|
+
## 0.0.0-alpha.2
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 5515e5f: Support Bun-only repository toolchain checks and active runtime validation without
|
|
14
|
+
requiring Node pins. Preserve the existing Node/Bun defaults. Export independent
|
|
15
|
+
Vite+ lint presets and plain Bun test defaults for consumer-owned configuration.
|
|
16
|
+
|
|
3
17
|
## 0.0.0-alpha.1
|
|
4
18
|
|
|
5
19
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Maintenance checks and task helpers for Bun monorepos: workspace boundaries,
|
|
4
4
|
toolchain pins, static message catalogs, documentation links and relocated build
|
|
5
5
|
artifacts.
|
|
6
|
+
The package also exports plain Vite+ lint presets and Bun test defaults.
|
|
6
7
|
|
|
7
8
|
```sh
|
|
8
9
|
bun add -d @raypx-kit/devkit
|
|
@@ -21,6 +22,8 @@ contains no product rules, so repositories compose their own policy from it.
|
|
|
21
22
|
| `./docs` | Report broken relative Markdown links |
|
|
22
23
|
| `./process` | Run commands, load dotenv files and run Turbo with a guarded cache |
|
|
23
24
|
| `./artifact` | Stage a relocated output and probe a running Node server |
|
|
25
|
+
| `./lint` | Base and opt-in strict Vite+ lint presets without repository paths |
|
|
26
|
+
| `./testing` | Plain Vitest defaults for Bun workers |
|
|
24
27
|
|
|
25
28
|
## Workspace architecture
|
|
26
29
|
|
|
@@ -147,6 +150,71 @@ documented `Node x.y.z` and `Bun x.y.z` versions and Turbo global dependencies.
|
|
|
147
150
|
`pinnedNode` returns the Node executable after checking its version; the binary
|
|
148
151
|
variable selects an executable but never relaxes the version.
|
|
149
152
|
|
|
153
|
+
### Bun-only repositories
|
|
154
|
+
|
|
155
|
+
The default policy still requires Node and Bun. Repositories that run JavaScript
|
|
156
|
+
only with Bun can select that policy explicitly:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { assertBunRuntime, checkToolchain, readBunPin } from "@raypx-kit/devkit/toolchain";
|
|
160
|
+
|
|
161
|
+
const findings = checkToolchain(root, { runtime: "bun", documents: ["README.md"] });
|
|
162
|
+
const version = readBunPin(root);
|
|
163
|
+
const { bun } = assertBunRuntime(root);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This mode reads only `package.json#packageManager`. It checks `devEngines`, Bun
|
|
167
|
+
types, mise, Dockerfiles, GitHub workflows/composite actions and selected docs.
|
|
168
|
+
It rejects Node engine declarations, Node version files, Node setup actions and
|
|
169
|
+
Node runtime images. Node API types remain allowed without a Node runtime pin.
|
|
170
|
+
`readBunPin` and `assertBunRuntime` never read `.nvmrc` or execute Node.
|
|
171
|
+
|
|
172
|
+
### Lint and test presets
|
|
173
|
+
|
|
174
|
+
With Vite+ 1.3 or later, import the independent configuration entries. They contain
|
|
175
|
+
plain data and do not load Vite+, application plugins, test setup or repository
|
|
176
|
+
configuration. Their declarations do not require Vite+ in unrelated consumers.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// vite.config.ts
|
|
180
|
+
import { defineConfig } from "vite-plus";
|
|
181
|
+
import { strictLint } from "@raypx-kit/devkit/lint";
|
|
182
|
+
|
|
183
|
+
export default defineConfig({
|
|
184
|
+
lint: {
|
|
185
|
+
...strictLint,
|
|
186
|
+
ignorePatterns: ["**/dist/**", "**/*.gen.ts"],
|
|
187
|
+
rules: { ...strictLint.rules, "react/no-danger": "off" },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`baseLint` provides type-aware checking and the Vite+ import rule. `strictLint`
|
|
193
|
+
adds TypeScript, React, accessibility, promise and import correctness checks;
|
|
194
|
+
adopt it explicitly because stricter rules can introduce findings. Neither
|
|
195
|
+
preset chooses generated files, workspace paths or project exceptions.
|
|
196
|
+
Consumers own formatting and staged Git commands as well.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
// vitest.config.ts
|
|
200
|
+
import { defineConfig, mergeConfig } from "vite-plus/test/config";
|
|
201
|
+
import { bunTestDefaults } from "@raypx-kit/devkit/testing";
|
|
202
|
+
|
|
203
|
+
export default mergeConfig(
|
|
204
|
+
bunTestDefaults,
|
|
205
|
+
defineConfig({
|
|
206
|
+
test: { include: ["tests/**/*.test.ts"], setupFiles: ["./tests/setup.ts"] },
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`bunTestDefaults` selects the Node environment, one worker, Bun's `--no-env-file`
|
|
212
|
+
and `--no-install` flags, and V8 coverage with text/JSON/LCOV reporters. Execute
|
|
213
|
+
tests with `bun --bun vp test --run`; install the matching coverage provider when
|
|
214
|
+
requesting coverage. Suites, DOM environments, setup files, React deduplication
|
|
215
|
+
and application fixtures remain consumer-owned. Use native `mergeConfig` for
|
|
216
|
+
overrides; no configuration factory or custom option schema is required.
|
|
217
|
+
|
|
150
218
|
## Catalogs and documentation
|
|
151
219
|
|
|
152
220
|
```ts
|
package/dist/lint.d.mts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Vite+ checks shared by libraries and applications; paths belong to the consumer. */
|
|
2
|
+
export declare const baseLint: {
|
|
3
|
+
jsPlugins: {
|
|
4
|
+
name: string;
|
|
5
|
+
specifier: string;
|
|
6
|
+
}[];
|
|
7
|
+
options: {
|
|
8
|
+
typeAware: true;
|
|
9
|
+
typeCheck: true;
|
|
10
|
+
};
|
|
11
|
+
rules: {
|
|
12
|
+
"vite-plus/prefer-vite-plus-imports": "error";
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/** Opt-in correctness rules for TypeScript and React repositories. */
|
|
16
|
+
export declare const strictLint: {
|
|
17
|
+
jsPlugins: {
|
|
18
|
+
name: string;
|
|
19
|
+
specifier: string;
|
|
20
|
+
}[];
|
|
21
|
+
plugins: ("import" | "jsx-a11y" | "node" | "oxc" | "promise" | "react" | "typescript" | "unicorn" | "vitest")[];
|
|
22
|
+
categories: {
|
|
23
|
+
correctness: "error";
|
|
24
|
+
suspicious: "error";
|
|
25
|
+
};
|
|
26
|
+
options: {
|
|
27
|
+
typeAware: true;
|
|
28
|
+
typeCheck: true;
|
|
29
|
+
reportUnusedDisableDirectives: "error";
|
|
30
|
+
};
|
|
31
|
+
rules: {
|
|
32
|
+
"vite-plus/prefer-vite-plus-imports": "error";
|
|
33
|
+
"array-callback-return": "error";
|
|
34
|
+
eqeqeq: "error";
|
|
35
|
+
"prefer-const": "error";
|
|
36
|
+
"no-duplicate-imports": ["error", {
|
|
37
|
+
allowSeparateTypeImports: true;
|
|
38
|
+
}];
|
|
39
|
+
"import/no-cycle": "error";
|
|
40
|
+
"oxc/no-accumulating-spread": "error";
|
|
41
|
+
"react/rules-of-hooks": "error";
|
|
42
|
+
"react/no-danger": "error";
|
|
43
|
+
"typescript/consistent-type-imports": ["error", {
|
|
44
|
+
disallowTypeAnnotations: false;
|
|
45
|
+
}];
|
|
46
|
+
"typescript/switch-exhaustiveness-check": "error";
|
|
47
|
+
"import/no-unassigned-import": "off";
|
|
48
|
+
"import/no-named-as-default": "off";
|
|
49
|
+
"import/no-named-as-default-member": "off";
|
|
50
|
+
"promise/always-return": "off";
|
|
51
|
+
"promise/no-callback-in-promise": "off";
|
|
52
|
+
"promise/no-promise-in-callback": "off";
|
|
53
|
+
"react/exhaustive-effect-dependencies": "off";
|
|
54
|
+
"react/react-in-jsx-scope": "off";
|
|
55
|
+
"vitest/require-mock-type-parameters": "off";
|
|
56
|
+
"vitest/warn-todo": "off";
|
|
57
|
+
"vitest/prefer-snapshot-hint": "off";
|
|
58
|
+
"typescript/consistent-return": "off";
|
|
59
|
+
"typescript/no-unsafe-type-assertion": "off";
|
|
60
|
+
"unicorn/no-array-sort": "off";
|
|
61
|
+
"unicorn/no-array-reverse": "off";
|
|
62
|
+
"no-underscore-dangle": "off";
|
|
63
|
+
"unicorn/consistent-function-scoping": "off";
|
|
64
|
+
"jsx-a11y/prefer-tag-over-role": "off";
|
|
65
|
+
"jsx-a11y/no-autofocus": "off";
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=lint.d.mts.map
|
package/dist/lint.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/lint.ts
|
|
2
|
+
/** Vite+ checks shared by libraries and applications; paths belong to the consumer. */
|
|
3
|
+
const baseLint = {
|
|
4
|
+
jsPlugins: [{
|
|
5
|
+
name: "vite-plus",
|
|
6
|
+
specifier: "vite-plus/oxlint-plugin"
|
|
7
|
+
}],
|
|
8
|
+
options: {
|
|
9
|
+
typeAware: true,
|
|
10
|
+
typeCheck: true
|
|
11
|
+
},
|
|
12
|
+
rules: { "vite-plus/prefer-vite-plus-imports": "error" }
|
|
13
|
+
};
|
|
14
|
+
/** Opt-in correctness rules for TypeScript and React repositories. */
|
|
15
|
+
const strictLint = {
|
|
16
|
+
...baseLint,
|
|
17
|
+
plugins: [
|
|
18
|
+
"typescript",
|
|
19
|
+
"unicorn",
|
|
20
|
+
"oxc",
|
|
21
|
+
"import",
|
|
22
|
+
"react",
|
|
23
|
+
"vitest",
|
|
24
|
+
"jsx-a11y",
|
|
25
|
+
"promise",
|
|
26
|
+
"node"
|
|
27
|
+
],
|
|
28
|
+
categories: {
|
|
29
|
+
correctness: "error",
|
|
30
|
+
suspicious: "error"
|
|
31
|
+
},
|
|
32
|
+
options: {
|
|
33
|
+
...baseLint.options,
|
|
34
|
+
reportUnusedDisableDirectives: "error"
|
|
35
|
+
},
|
|
36
|
+
rules: {
|
|
37
|
+
...baseLint.rules,
|
|
38
|
+
"array-callback-return": "error",
|
|
39
|
+
eqeqeq: "error",
|
|
40
|
+
"prefer-const": "error",
|
|
41
|
+
"no-duplicate-imports": ["error", { allowSeparateTypeImports: true }],
|
|
42
|
+
"import/no-cycle": "error",
|
|
43
|
+
"oxc/no-accumulating-spread": "error",
|
|
44
|
+
"react/rules-of-hooks": "error",
|
|
45
|
+
"react/no-danger": "error",
|
|
46
|
+
"typescript/consistent-type-imports": ["error", { disallowTypeAnnotations: false }],
|
|
47
|
+
"typescript/switch-exhaustiveness-check": "error",
|
|
48
|
+
"import/no-unassigned-import": "off",
|
|
49
|
+
"import/no-named-as-default": "off",
|
|
50
|
+
"import/no-named-as-default-member": "off",
|
|
51
|
+
"promise/always-return": "off",
|
|
52
|
+
"promise/no-callback-in-promise": "off",
|
|
53
|
+
"promise/no-promise-in-callback": "off",
|
|
54
|
+
"react/exhaustive-effect-dependencies": "off",
|
|
55
|
+
"react/react-in-jsx-scope": "off",
|
|
56
|
+
"vitest/require-mock-type-parameters": "off",
|
|
57
|
+
"vitest/warn-todo": "off",
|
|
58
|
+
"vitest/prefer-snapshot-hint": "off",
|
|
59
|
+
"typescript/consistent-return": "off",
|
|
60
|
+
"typescript/no-unsafe-type-assertion": "off",
|
|
61
|
+
"unicorn/no-array-sort": "off",
|
|
62
|
+
"unicorn/no-array-reverse": "off",
|
|
63
|
+
"no-underscore-dangle": "off",
|
|
64
|
+
"unicorn/consistent-function-scoping": "off",
|
|
65
|
+
"jsx-a11y/prefer-tag-over-role": "off",
|
|
66
|
+
"jsx-a11y/no-autofocus": "off"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export { baseLint, strictLint };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=lint.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.mjs","names":[],"sources":["../src/lint.ts"],"sourcesContent":["import type { ViteUserConfig } from \"vite-plus\";\n\n/** Vite+ checks shared by libraries and applications; paths belong to the consumer. */\nexport const baseLint = {\n jsPlugins: [{ name: \"vite-plus\", specifier: \"vite-plus/oxlint-plugin\" }],\n options: { typeAware: true, typeCheck: true },\n rules: { \"vite-plus/prefer-vite-plus-imports\": \"error\" },\n} satisfies NonNullable<ViteUserConfig[\"lint\"]>;\n\n/** Opt-in correctness rules for TypeScript and React repositories. */\nexport const strictLint = {\n ...baseLint,\n plugins: [\n \"typescript\",\n \"unicorn\",\n \"oxc\",\n \"import\",\n \"react\",\n \"vitest\",\n \"jsx-a11y\",\n \"promise\",\n \"node\",\n ],\n categories: { correctness: \"error\", suspicious: \"error\" },\n options: { ...baseLint.options, reportUnusedDisableDirectives: \"error\" },\n rules: {\n ...baseLint.rules,\n \"array-callback-return\": \"error\",\n eqeqeq: \"error\",\n \"prefer-const\": \"error\",\n \"no-duplicate-imports\": [\"error\", { allowSeparateTypeImports: true }],\n \"import/no-cycle\": \"error\",\n \"oxc/no-accumulating-spread\": \"error\",\n \"react/rules-of-hooks\": \"error\",\n \"react/no-danger\": \"error\",\n \"typescript/consistent-type-imports\": [\"error\", { disallowTypeAnnotations: false }],\n \"typescript/switch-exhaustiveness-check\": \"error\",\n // Bootstrap imports, third-party defaults and callback adapters are valid.\n \"import/no-unassigned-import\": \"off\",\n \"import/no-named-as-default\": \"off\",\n \"import/no-named-as-default-member\": \"off\",\n \"promise/always-return\": \"off\",\n \"promise/no-callback-in-promise\": \"off\",\n \"promise/no-promise-in-callback\": \"off\",\n \"react/exhaustive-effect-dependencies\": \"off\",\n \"react/react-in-jsx-scope\": \"off\",\n \"vitest/require-mock-type-parameters\": \"off\",\n \"vitest/warn-todo\": \"off\",\n \"vitest/prefer-snapshot-hint\": \"off\",\n \"typescript/consistent-return\": \"off\",\n \"typescript/no-unsafe-type-assertion\": \"off\",\n // ES2022 consumers do not have to polyfill the copying array methods.\n \"unicorn/no-array-sort\": \"off\",\n \"unicorn/no-array-reverse\": \"off\",\n // Keep naming, helper placement and deliberate focus under local control.\n \"no-underscore-dangle\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"jsx-a11y/prefer-tag-over-role\": \"off\",\n \"jsx-a11y/no-autofocus\": \"off\",\n },\n} satisfies NonNullable<ViteUserConfig[\"lint\"]>;\n"],"mappings":";;AAGA,MAAa,WAAW;CACtB,WAAW,CAAC;EAAE,MAAM;EAAa,WAAW;CAA0B,CAAC;CACvE,SAAS;EAAE,WAAW;EAAM,WAAW;CAAK;CAC5C,OAAO,EAAE,sCAAsC,QAAQ;AACzD;;AAGA,MAAa,aAAa;CACxB,GAAG;CACH,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,YAAY;EAAE,aAAa;EAAS,YAAY;CAAQ;CACxD,SAAS;EAAE,GAAG,SAAS;EAAS,+BAA+B;CAAQ;CACvE,OAAO;EACL,GAAG,SAAS;EACZ,yBAAyB;EACzB,QAAQ;EACR,gBAAgB;EAChB,wBAAwB,CAAC,SAAS,EAAE,0BAA0B,KAAK,CAAC;EACpE,mBAAmB;EACnB,8BAA8B;EAC9B,wBAAwB;EACxB,mBAAmB;EACnB,sCAAsC,CAAC,SAAS,EAAE,yBAAyB,MAAM,CAAC;EAClF,0CAA0C;EAE1C,+BAA+B;EAC/B,8BAA8B;EAC9B,qCAAqC;EACrC,yBAAyB;EACzB,kCAAkC;EAClC,kCAAkC;EAClC,wCAAwC;EACxC,4BAA4B;EAC5B,uCAAuC;EACvC,oBAAoB;EACpB,+BAA+B;EAC/B,gCAAgC;EAChC,uCAAuC;EAEvC,yBAAyB;EACzB,4BAA4B;EAE5B,wBAAwB;EACxB,uCAAuC;EACvC,iCAAiC;EACjC,yBAAyB;CAC3B;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Plain Vitest defaults for Bun workers; consumers own suites, setup and environments. */
|
|
2
|
+
export declare const bunTestDefaults: {
|
|
3
|
+
test: {
|
|
4
|
+
environment: "node";
|
|
5
|
+
maxWorkers: number;
|
|
6
|
+
execArgv: string[];
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: "v8";
|
|
9
|
+
reporter: ("json" | "lcov" | "text")[];
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=testing.d.mts.map
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/testing.ts
|
|
2
|
+
/** Plain Vitest defaults for Bun workers; consumers own suites, setup and environments. */
|
|
3
|
+
const bunTestDefaults = { test: {
|
|
4
|
+
environment: "node",
|
|
5
|
+
maxWorkers: 1,
|
|
6
|
+
execArgv: ["--no-env-file", "--no-install"],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: "v8",
|
|
9
|
+
reporter: [
|
|
10
|
+
"text",
|
|
11
|
+
"json",
|
|
12
|
+
"lcov"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
} };
|
|
16
|
+
export { bunTestDefaults };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type { ViteUserConfig } from \"vite-plus\";\n\n/** Plain Vitest defaults for Bun workers; consumers own suites, setup and environments. */\nexport const bunTestDefaults = {\n test: {\n environment: \"node\",\n maxWorkers: 1,\n execArgv: [\"--no-env-file\", \"--no-install\"],\n coverage: { provider: \"v8\", reporter: [\"text\", \"json\", \"lcov\"] },\n },\n} satisfies ViteUserConfig;\n"],"mappings":";;AAGA,MAAa,kBAAkB,EAC7B,MAAM;CACJ,aAAa;CACb,YAAY;CACZ,UAAU,CAAC,iBAAiB,cAAc;CAC1C,UAAU;EAAE,UAAU;EAAM,UAAU;GAAC;GAAQ;GAAQ;EAAM;CAAE;AACjE,EACF"}
|
package/dist/toolchain.d.mts
CHANGED
|
@@ -3,6 +3,8 @@ export interface Toolchain {
|
|
|
3
3
|
bun: string;
|
|
4
4
|
}
|
|
5
5
|
export interface ToolchainOptions {
|
|
6
|
+
/** Bun-only repositories forbid Node runtime pins; the default checks both runtimes. */
|
|
7
|
+
runtime?: "node-bun" | "bun";
|
|
6
8
|
/** Repository-relative documents whose `Node x.y.z` and `Bun x.y.z` mentions must match. */
|
|
7
9
|
documents?: readonly string[];
|
|
8
10
|
/** Files every Turbo cache key must include when `turbo.json` exists. */
|
|
@@ -12,6 +14,12 @@ export interface RuntimeOptions {
|
|
|
12
14
|
/** Environment variable that may select a Node executable outside PATH. */
|
|
13
15
|
nodeBinaryEnv?: string;
|
|
14
16
|
}
|
|
17
|
+
/** Read the exact Bun pin without requiring a Node development runtime. */
|
|
18
|
+
export declare function readBunPin(root: string): string;
|
|
19
|
+
/** Check the active Bun runtime without executing Node or inspected repository code. */
|
|
20
|
+
export declare function assertBunRuntime(root: string): {
|
|
21
|
+
bun: string;
|
|
22
|
+
};
|
|
15
23
|
/** `.nvmrc` pins Node and the root `packageManager` pins Bun; every other pin follows them. */
|
|
16
24
|
export declare function readToolchain(root: string): Toolchain;
|
|
17
25
|
/** Returns the Node executable after confirming it provides the pinned release. */
|
|
@@ -21,5 +29,5 @@ export declare function assertToolchainRuntime(root: string, options?: RuntimeOp
|
|
|
21
29
|
nodeBinary: string;
|
|
22
30
|
};
|
|
23
31
|
/** Check duplicated declarations without installing tools or running application code. */
|
|
24
|
-
export declare function checkToolchain(root: string, { documents, turboGlobalDependencies }?: ToolchainOptions): string[];
|
|
32
|
+
export declare function checkToolchain(root: string, { runtime, documents, turboGlobalDependencies }?: ToolchainOptions): string[];
|
|
25
33
|
//# sourceMappingURL=toolchain.d.mts.map
|
package/dist/toolchain.mjs
CHANGED
|
@@ -13,10 +13,26 @@ const catalogsOf = (manifest) => {
|
|
|
13
13
|
const { catalog, catalogs } = workspaces;
|
|
14
14
|
return [...catalog ? [catalog] : [], ...Object.values(catalogs ?? {})];
|
|
15
15
|
};
|
|
16
|
+
const bunVersion = (root) => /^bun@(\d+\.\d+\.\d+)$/.exec(manifestAt(root).packageManager ?? "")?.[1];
|
|
17
|
+
/** Read the exact Bun pin without requiring a Node development runtime. */
|
|
18
|
+
function readBunPin(root) {
|
|
19
|
+
const bun = bunVersion(root);
|
|
20
|
+
if (!bun) throw new Error("Pin an exact Bun version in package.json packageManager.");
|
|
21
|
+
return bun;
|
|
22
|
+
}
|
|
23
|
+
function assertBunVersion(bun) {
|
|
24
|
+
if (process.versions.bun !== bun) throw new Error(`Expected Bun ${bun}, got ${process.versions.bun ?? "none"}.`);
|
|
25
|
+
}
|
|
26
|
+
/** Check the active Bun runtime without executing Node or inspected repository code. */
|
|
27
|
+
function assertBunRuntime(root) {
|
|
28
|
+
const bun = readBunPin(root);
|
|
29
|
+
assertBunVersion(bun);
|
|
30
|
+
return { bun };
|
|
31
|
+
}
|
|
16
32
|
/** `.nvmrc` pins Node and the root `packageManager` pins Bun; every other pin follows them. */
|
|
17
33
|
function readToolchain(root) {
|
|
18
34
|
const node = readFileSync(join(root, ".nvmrc"), "utf8").trim().replace(/^v/, "");
|
|
19
|
-
const bun =
|
|
35
|
+
const bun = bunVersion(root) ?? "";
|
|
20
36
|
if (!exactVersion.test(node) || !exactVersion.test(bun)) throw new Error("Pin exact Node and Bun versions in .nvmrc and package.json packageManager.");
|
|
21
37
|
return {
|
|
22
38
|
node,
|
|
@@ -42,7 +58,7 @@ function pinnedNode(root, { nodeBinaryEnv = "DEVKIT_NODE_BINARY" } = {}) {
|
|
|
42
58
|
/** The binary override changes the consumer executable, never the required version. */
|
|
43
59
|
function assertToolchainRuntime(root, options = {}) {
|
|
44
60
|
const pins = readToolchain(root);
|
|
45
|
-
|
|
61
|
+
assertBunVersion(pins.bun);
|
|
46
62
|
return {
|
|
47
63
|
...pins,
|
|
48
64
|
nodeBinary: pinnedNode(root, options)
|
|
@@ -51,7 +67,7 @@ function assertToolchainRuntime(root, options = {}) {
|
|
|
51
67
|
function isRecord(value) {
|
|
52
68
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
69
|
}
|
|
54
|
-
function checkMise(root) {
|
|
70
|
+
function checkMise(root, bunOnly) {
|
|
55
71
|
const file = "mise.toml";
|
|
56
72
|
const path = join(root, file);
|
|
57
73
|
if (!existsSync(path)) return [`${file}: missing runtime manager configuration`];
|
|
@@ -65,10 +81,11 @@ function checkMise(root) {
|
|
|
65
81
|
const findings = [];
|
|
66
82
|
if (Object.hasOwn(mise, "tools")) findings.push(`${file}: read existing pins instead of declaring [tools] versions`);
|
|
67
83
|
const tools = isRecord(mise.settings) ? mise.settings.idiomatic_version_file_enable_tools : void 0;
|
|
68
|
-
|
|
84
|
+
const expected = bunOnly ? ["bun"] : ["node", "bun"];
|
|
85
|
+
if (!Array.isArray(tools) || tools.length !== expected.length || !expected.every((tool) => tools.includes(tool))) findings.push(`${file}: enable idiomatic version files for ${bunOnly ? "bun only" : "node and bun"}`);
|
|
69
86
|
return findings;
|
|
70
87
|
}
|
|
71
|
-
function checkSetupStep(file, location, step) {
|
|
88
|
+
function checkSetupStep(file, location, step, bunOnly) {
|
|
72
89
|
if (typeof step.uses !== "string") return [];
|
|
73
90
|
const refSeparator = step.uses.indexOf("@");
|
|
74
91
|
if (refSeparator < 0) return [];
|
|
@@ -84,6 +101,7 @@ function checkSetupStep(file, location, step) {
|
|
|
84
101
|
]].find(([, action]) => repository === action);
|
|
85
102
|
if (!setup) return [];
|
|
86
103
|
const [tool, , source] = setup;
|
|
104
|
+
if (bunOnly && tool === "node") return [`${file}: remove actions/setup-node; Bun is the only runtime`];
|
|
87
105
|
const findings = [];
|
|
88
106
|
if (step.with !== void 0 && !isRecord(step.with)) findings.push(`${file}: ${location}.with must be a mapping`);
|
|
89
107
|
const inputs = isRecord(step.with) ? step.with : {};
|
|
@@ -93,7 +111,7 @@ function checkSetupStep(file, location, step) {
|
|
|
93
111
|
else if (versionFile !== source) findings.push(`${file}: ${tool}-version-file must be ${source}`);
|
|
94
112
|
return findings;
|
|
95
113
|
}
|
|
96
|
-
function checkSetupSteps(file, location, steps) {
|
|
114
|
+
function checkSetupSteps(file, location, steps, bunOnly) {
|
|
97
115
|
if (!Array.isArray(steps)) return [`${file}: ${location} must be an array`];
|
|
98
116
|
const findings = [];
|
|
99
117
|
for (const [index, step] of steps.entries()) {
|
|
@@ -102,11 +120,11 @@ function checkSetupSteps(file, location, steps) {
|
|
|
102
120
|
findings.push(`${file}: ${stepLocation} must be a mapping`);
|
|
103
121
|
continue;
|
|
104
122
|
}
|
|
105
|
-
findings.push(...checkSetupStep(file, stepLocation, step));
|
|
123
|
+
findings.push(...checkSetupStep(file, stepLocation, step, bunOnly));
|
|
106
124
|
}
|
|
107
125
|
return findings;
|
|
108
126
|
}
|
|
109
|
-
function checkWorkflows(root) {
|
|
127
|
+
function checkWorkflows(root, bunOnly) {
|
|
110
128
|
const findings = [];
|
|
111
129
|
for (const file of globSync(".github/workflows/*.{yml,yaml}", { cwd: root })) {
|
|
112
130
|
let workflow;
|
|
@@ -127,12 +145,12 @@ function checkWorkflows(root) {
|
|
|
127
145
|
continue;
|
|
128
146
|
}
|
|
129
147
|
if (job.steps === void 0) continue;
|
|
130
|
-
findings.push(...checkSetupSteps(file, `${location}.steps`, job.steps));
|
|
148
|
+
findings.push(...checkSetupSteps(file, `${location}.steps`, job.steps, bunOnly));
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
return findings;
|
|
134
152
|
}
|
|
135
|
-
function checkCompositeActions(root) {
|
|
153
|
+
function checkCompositeActions(root, bunOnly) {
|
|
136
154
|
const findings = [];
|
|
137
155
|
for (const file of globSync(".github/actions/**/action.{yml,yaml}", { cwd: root })) {
|
|
138
156
|
let action;
|
|
@@ -147,15 +165,16 @@ function checkCompositeActions(root) {
|
|
|
147
165
|
continue;
|
|
148
166
|
}
|
|
149
167
|
if (action.runs.using !== "composite") continue;
|
|
150
|
-
findings.push(...checkSetupSteps(file, "runs.steps", action.runs.steps));
|
|
168
|
+
findings.push(...checkSetupSteps(file, "runs.steps", action.runs.steps, bunOnly));
|
|
151
169
|
}
|
|
152
170
|
return findings;
|
|
153
171
|
}
|
|
154
172
|
/** Check duplicated declarations without installing tools or running application code. */
|
|
155
|
-
function checkToolchain(root, { documents = ["README.md", "AGENTS.md"], turboGlobalDependencies = [".nvmrc", "mise.toml"] } = {}) {
|
|
173
|
+
function checkToolchain(root, { runtime = "node-bun", documents = ["README.md", "AGENTS.md"], turboGlobalDependencies = runtime === "bun" ? ["mise.toml"] : [".nvmrc", "mise.toml"] } = {}) {
|
|
174
|
+
const bunOnly = runtime === "bun";
|
|
156
175
|
let pins;
|
|
157
176
|
try {
|
|
158
|
-
pins = readToolchain(root);
|
|
177
|
+
pins = bunOnly ? { bun: readBunPin(root) } : readToolchain(root);
|
|
159
178
|
} catch (error) {
|
|
160
179
|
return [error instanceof Error ? error.message : String(error)];
|
|
161
180
|
}
|
|
@@ -164,23 +183,26 @@ function checkToolchain(root, { documents = ["README.md", "AGENTS.md"], turboGlo
|
|
|
164
183
|
const expect = (file, label, actual, expected) => {
|
|
165
184
|
if (actual !== expected) findings.push(`${file}: ${label} is ${actual ?? "missing"}, expected ${expected}`);
|
|
166
185
|
};
|
|
167
|
-
|
|
186
|
+
if (bunOnly) {
|
|
187
|
+
if (manifest.engines?.node !== void 0) findings.push("package.json: remove engines.node; Bun is the only runtime");
|
|
188
|
+
} else expect("package.json", "engines.node", manifest.engines?.node, pins.node);
|
|
168
189
|
for (const key of ["packageManager", "runtime"]) {
|
|
169
190
|
const engine = manifest.devEngines?.[key];
|
|
170
191
|
expect("package.json", `devEngines.${key}`, `${engine?.name}@${engine?.version}`, `bun@${pins.bun}`);
|
|
171
192
|
expect("package.json", `devEngines.${key}.onFail`, engine?.onFail, "error");
|
|
172
193
|
}
|
|
173
194
|
for (const file of [
|
|
195
|
+
...bunOnly ? [".nvmrc"] : [],
|
|
174
196
|
".node-version",
|
|
175
197
|
".bun-version",
|
|
176
198
|
".tool-versions"
|
|
177
|
-
]) if (existsSync(join(root, file))) findings.push(`${file}: use .nvmrc and package.json instead of a second version source`);
|
|
178
|
-
findings.push(...checkMise(root));
|
|
179
|
-
const nodeMajor = pins.node
|
|
199
|
+
]) if (existsSync(join(root, file))) findings.push(bunOnly ? `${file}: package.json packageManager is the only runtime pin` : `${file}: use .nvmrc and package.json instead of a second version source`);
|
|
200
|
+
findings.push(...checkMise(root, bunOnly));
|
|
201
|
+
const nodeMajor = pins.node?.split(".")[0];
|
|
180
202
|
const version = (range) => range && !/^[a-z]+:/.test(range) ? range.replace(/^[\^~]/, "") : void 0;
|
|
181
203
|
const checkTypes = (file, dependencies) => {
|
|
182
204
|
const node = version(dependencies?.["@types/node"]);
|
|
183
|
-
if (node && node.split(".")[0] !== nodeMajor) findings.push(`${file}: @types/node must match the Node ${nodeMajor} major`);
|
|
205
|
+
if (nodeMajor && node && node.split(".")[0] !== nodeMajor) findings.push(`${file}: @types/node must match the Node ${nodeMajor} major`);
|
|
184
206
|
const bun = version(dependencies?.["@types/bun"]);
|
|
185
207
|
if (bun) expect(file, "@types/bun", bun, pins.bun);
|
|
186
208
|
};
|
|
@@ -191,14 +213,17 @@ function checkToolchain(root, { documents = ["README.md", "AGENTS.md"], turboGlo
|
|
|
191
213
|
exclude: ["**/node_modules/**"]
|
|
192
214
|
})) {
|
|
193
215
|
const source = readFileSync(join(root, file), "utf8");
|
|
194
|
-
|
|
216
|
+
if (bunOnly) {
|
|
217
|
+
if (/^\s*FROM\s+(?:--platform=\S+\s+)?node:/im.test(source)) findings.push(`${file}: build and run on oven/bun images`);
|
|
218
|
+
} else for (const [, version] of source.matchAll(/^FROM\s+node:(\d+\.\d+\.\d+)/gm)) expect(file, "node image", version, pins.node);
|
|
195
219
|
for (const [, version] of source.matchAll(/^FROM\s+oven\/bun:(\d+\.\d+\.\d+)/gm)) expect(file, "bun image", version, pins.bun);
|
|
196
220
|
}
|
|
197
|
-
findings.push(...checkWorkflows(root), ...checkCompositeActions(root));
|
|
221
|
+
findings.push(...checkWorkflows(root, bunOnly), ...checkCompositeActions(root, bunOnly));
|
|
198
222
|
for (const file of documents) {
|
|
199
223
|
if (!existsSync(join(root, file))) continue;
|
|
200
224
|
const document = readFileSync(join(root, file), "utf8").replaceAll("**", "");
|
|
201
|
-
for (const [, tool, version] of document.matchAll(/\b(Node|Bun) v?(\d+\.\d+\.\d+)\b/g))
|
|
225
|
+
for (const [, tool, version] of document.matchAll(/\b(Node|Bun) v?(\d+\.\d+\.\d+)\b/g)) if (bunOnly && tool === "Node") findings.push(`${file}: documents a Node release; Bun is the only runtime`);
|
|
226
|
+
else expect(file, `documented ${tool}`, version, tool === "Node" ? pins.node : pins.bun);
|
|
202
227
|
}
|
|
203
228
|
const turboFile = join(root, "turbo.json");
|
|
204
229
|
if (existsSync(turboFile)) {
|
|
@@ -207,6 +232,6 @@ function checkToolchain(root, { documents = ["README.md", "AGENTS.md"], turboGlo
|
|
|
207
232
|
}
|
|
208
233
|
return [...new Set(findings)];
|
|
209
234
|
}
|
|
210
|
-
export { assertToolchainRuntime, checkToolchain, pinnedNode, readToolchain };
|
|
235
|
+
export { assertBunRuntime, assertToolchainRuntime, checkToolchain, pinnedNode, readBunPin, readToolchain };
|
|
211
236
|
|
|
212
237
|
//# sourceMappingURL=toolchain.mjs.map
|
package/dist/toolchain.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolchain.mjs","names":[],"sources":["../src/toolchain.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, globSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parseTOML } from \"confbox/toml\";\nimport { parseYAML } from \"confbox/yaml\";\nimport { workspacePatterns } from \"./workspaces.js\";\n\nexport interface Toolchain {\n node: string;\n bun: string;\n}\n\nexport interface ToolchainOptions {\n /** Repository-relative documents whose `Node x.y.z` and `Bun x.y.z` mentions must match. */\n documents?: readonly string[];\n /** Files every Turbo cache key must include when `turbo.json` exists. */\n turboGlobalDependencies?: readonly string[];\n}\n\nexport interface RuntimeOptions {\n /** Environment variable that may select a Node executable outside PATH. */\n nodeBinaryEnv?: string;\n}\n\ninterface Manifest {\n packageManager?: string;\n engines?: { node?: string };\n devEngines?: Record<string, { name?: string; version?: string; onFail?: string }>;\n devDependencies?: Record<string, string>;\n workspaces?: unknown;\n}\n\nconst exactVersion = /^\\d+\\.\\d+\\.\\d+$/;\nconst manifestAt = (root: string, file = \"package.json\"): Manifest =>\n JSON.parse(readFileSync(join(root, file), \"utf8\")) as Manifest;\nconst catalogsOf = (manifest: Manifest): Record<string, string>[] => {\n const workspaces = manifest.workspaces;\n if (!workspaces || typeof workspaces !== \"object\" || Array.isArray(workspaces)) return [];\n const { catalog, catalogs } = workspaces as {\n catalog?: Record<string, string>;\n catalogs?: Record<string, Record<string, string>>;\n };\n return [...(catalog ? [catalog] : []), ...Object.values(catalogs ?? {})];\n};\n\n/** `.nvmrc` pins Node and the root `packageManager` pins Bun; every other pin follows them. */\nexport function readToolchain(root: string): Toolchain {\n const node = readFileSync(join(root, \".nvmrc\"), \"utf8\").trim().replace(/^v/, \"\");\n const bun = /^bun@(.+)$/.exec(manifestAt(root).packageManager ?? \"\")?.[1] ?? \"\";\n if (!exactVersion.test(node) || !exactVersion.test(bun))\n throw new Error(\"Pin exact Node and Bun versions in .nvmrc and package.json packageManager.\");\n return { node, bun };\n}\n\n/** Returns the Node executable after confirming it provides the pinned release. */\nexport function pinnedNode(\n root: string,\n { nodeBinaryEnv = \"DEVKIT_NODE_BINARY\" }: RuntimeOptions = {},\n) {\n const pins = readToolchain(root);\n const nodeBinary = process.env[nodeBinaryEnv] || \"node\";\n let node: string;\n try {\n node = execFileSync(nodeBinary, [\"--version\"], { encoding: \"utf8\", stdio: \"pipe\" }).trim();\n } catch {\n throw new Error(\n `Node is unavailable; select Node ${pins.node} from .nvmrc or set ${nodeBinaryEnv}.`,\n );\n }\n if (node !== `v${pins.node}`)\n throw new Error(\n `Expected Node ${pins.node}, got ${node}; select the pinned Node from .nvmrc or set ${nodeBinaryEnv}.`,\n );\n return nodeBinary;\n}\n\n/** The binary override changes the consumer executable, never the required version. */\nexport function assertToolchainRuntime(\n root: string,\n options: RuntimeOptions = {},\n): Toolchain & { nodeBinary: string } {\n const pins = readToolchain(root);\n if (process.versions.bun !== pins.bun)\n throw new Error(`Expected Bun ${pins.bun}, got ${process.versions.bun ?? \"none\"}.`);\n return { ...pins, nodeBinary: pinnedNode(root, options) };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction checkMise(root: string): string[] {\n const file = \"mise.toml\";\n const path = join(root, file);\n if (!existsSync(path)) return [`${file}: missing runtime manager configuration`];\n let mise: unknown;\n try {\n mise = parseTOML(readFileSync(path, \"utf8\"));\n } catch {\n return [`${file}: invalid TOML configuration`];\n }\n if (!isRecord(mise)) return [`${file}: configuration must be a table`];\n const findings: string[] = [];\n if (Object.hasOwn(mise, \"tools\"))\n findings.push(`${file}: read existing pins instead of declaring [tools] versions`);\n const tools = isRecord(mise.settings)\n ? mise.settings.idiomatic_version_file_enable_tools\n : undefined;\n if (\n !Array.isArray(tools) ||\n tools.length !== 2 ||\n ![\"node\", \"bun\"].every((tool) => tools.includes(tool))\n )\n findings.push(`${file}: enable idiomatic version files for node and bun`);\n return findings;\n}\n\nfunction checkSetupStep(file: string, location: string, step: Record<string, unknown>): string[] {\n if (typeof step.uses !== \"string\") return [];\n const refSeparator = step.uses.indexOf(\"@\");\n if (refSeparator < 0) return [];\n const repository = step.uses.slice(0, refSeparator).toLowerCase();\n const setup = (\n [\n [\"node\", \"actions/setup-node\", \".nvmrc\"],\n [\"bun\", \"oven-sh/setup-bun\", \"package.json\"],\n ] as const\n ).find(([, action]) => repository === action);\n if (!setup) return [];\n const [tool, , source] = setup;\n const findings: string[] = [];\n if (step.with !== undefined && !isRecord(step.with))\n findings.push(`${file}: ${location}.with must be a mapping`);\n const inputs = isRecord(step.with) ? step.with : {};\n if (Object.hasOwn(inputs, `${tool}-version`))\n findings.push(`${file}: read ${tool}-version-file: ${source} instead of a literal version`);\n const versionFile = inputs[`${tool}-version-file`];\n if (versionFile === undefined) findings.push(`${file}: every ${tool} setup must read ${source}`);\n else if (versionFile !== source) findings.push(`${file}: ${tool}-version-file must be ${source}`);\n return findings;\n}\n\nfunction checkSetupSteps(file: string, location: string, steps: unknown): string[] {\n if (!Array.isArray(steps)) return [`${file}: ${location} must be an array`];\n const findings: string[] = [];\n for (const [index, step] of steps.entries()) {\n const stepLocation = `${location}[${index}]`;\n if (!isRecord(step)) {\n findings.push(`${file}: ${stepLocation} must be a mapping`);\n continue;\n }\n findings.push(...checkSetupStep(file, stepLocation, step));\n }\n return findings;\n}\n\nfunction checkWorkflows(root: string): string[] {\n const findings: string[] = [];\n for (const file of globSync(\".github/workflows/*.{yml,yaml}\", { cwd: root })) {\n let workflow: unknown;\n try {\n workflow = parseYAML(readFileSync(join(root, file), \"utf8\"), { filename: file });\n } catch {\n findings.push(`${file}: invalid YAML configuration`);\n continue;\n }\n if (!isRecord(workflow) || !isRecord(workflow.jobs)) {\n findings.push(`${file}: workflow jobs must be a mapping`);\n continue;\n }\n for (const [name, job] of Object.entries(workflow.jobs)) {\n const location = `jobs.${name}`;\n if (!isRecord(job)) {\n findings.push(`${file}: ${location} must be a mapping`);\n continue;\n }\n if (job.steps === undefined) continue;\n findings.push(...checkSetupSteps(file, `${location}.steps`, job.steps));\n }\n }\n return findings;\n}\n\nfunction checkCompositeActions(root: string): string[] {\n const findings: string[] = [];\n for (const file of globSync(\".github/actions/**/action.{yml,yaml}\", { cwd: root })) {\n let action: unknown;\n try {\n action = parseYAML(readFileSync(join(root, file), \"utf8\"), { filename: file });\n } catch {\n findings.push(`${file}: invalid YAML configuration`);\n continue;\n }\n if (!isRecord(action) || !isRecord(action.runs)) {\n findings.push(`${file}: action runs must be a mapping`);\n continue;\n }\n if (action.runs.using !== \"composite\") continue;\n findings.push(...checkSetupSteps(file, \"runs.steps\", action.runs.steps));\n }\n return findings;\n}\n\n/** Check duplicated declarations without installing tools or running application code. */\nexport function checkToolchain(\n root: string,\n {\n documents = [\"README.md\", \"AGENTS.md\"],\n turboGlobalDependencies = [\".nvmrc\", \"mise.toml\"],\n }: ToolchainOptions = {},\n): string[] {\n let pins: Toolchain;\n try {\n pins = readToolchain(root);\n } catch (error) {\n return [error instanceof Error ? error.message : String(error)];\n }\n const findings: string[] = [];\n const manifest = manifestAt(root);\n const expect = (file: string, label: string, actual: string | undefined, expected: string) => {\n if (actual !== expected)\n findings.push(`${file}: ${label} is ${actual ?? \"missing\"}, expected ${expected}`);\n };\n expect(\"package.json\", \"engines.node\", manifest.engines?.node, pins.node);\n for (const key of [\"packageManager\", \"runtime\"]) {\n const engine = manifest.devEngines?.[key];\n expect(\n \"package.json\",\n `devEngines.${key}`,\n `${engine?.name}@${engine?.version}`,\n `bun@${pins.bun}`,\n );\n expect(\"package.json\", `devEngines.${key}.onFail`, engine?.onFail, \"error\");\n }\n for (const file of [\".node-version\", \".bun-version\", \".tool-versions\"])\n if (existsSync(join(root, file)))\n findings.push(`${file}: use .nvmrc and package.json instead of a second version source`);\n findings.push(...checkMise(root));\n\n // Runtime type packages follow the pins: @types/node by major, @types/bun exactly.\n const nodeMajor = pins.node.split(\".\")[0]!;\n // Protocol ranges such as catalog: and workspace: are checked where they resolve.\n const version = (range: string | undefined) =>\n range && !/^[a-z]+:/.test(range) ? range.replace(/^[\\^~]/, \"\") : undefined;\n const checkTypes = (file: string, dependencies: Record<string, string> | undefined) => {\n const node = version(dependencies?.[\"@types/node\"]);\n if (node && node.split(\".\")[0] !== nodeMajor)\n findings.push(`${file}: @types/node must match the Node ${nodeMajor} major`);\n const bun = version(dependencies?.[\"@types/bun\"]);\n if (bun) expect(file, \"@types/bun\", bun, pins.bun);\n };\n for (const catalog of catalogsOf(manifest)) checkTypes(\"package.json catalog\", catalog);\n for (const file of [\n \"package.json\",\n ...globSync(\n workspacePatterns(manifest).map((path) => `${path}/package.json`),\n { cwd: root },\n ),\n ])\n checkTypes(file, manifestAt(root, file).devDependencies);\n\n for (const file of globSync(\"**/Dockerfile\", { cwd: root, exclude: [\"**/node_modules/**\"] })) {\n const source = readFileSync(join(root, file), \"utf8\");\n for (const [, version] of source.matchAll(/^FROM\\s+node:(\\d+\\.\\d+\\.\\d+)/gm))\n expect(file, \"node image\", version, pins.node);\n for (const [, version] of source.matchAll(/^FROM\\s+oven\\/bun:(\\d+\\.\\d+\\.\\d+)/gm))\n expect(file, \"bun image\", version, pins.bun);\n }\n findings.push(...checkWorkflows(root), ...checkCompositeActions(root));\n for (const file of documents) {\n if (!existsSync(join(root, file))) continue;\n const document = readFileSync(join(root, file), \"utf8\").replaceAll(\"**\", \"\");\n for (const [, tool, version] of document.matchAll(/\\b(Node|Bun) v?(\\d+\\.\\d+\\.\\d+)\\b/g))\n expect(file, `documented ${tool}`, version, tool === \"Node\" ? pins.node : pins.bun);\n }\n const turboFile = join(root, \"turbo.json\");\n if (existsSync(turboFile)) {\n const turbo = JSON.parse(readFileSync(turboFile, \"utf8\")) as { globalDependencies?: string[] };\n for (const file of turboGlobalDependencies)\n if (!turbo.globalDependencies?.includes(file))\n findings.push(`turbo.json: include ${file} in globalDependencies`);\n }\n return [...new Set(findings)];\n}\n"],"mappings":";;;;;;;AAgCA,MAAM,eAAe;AACrB,MAAM,cAAc,MAAc,OAAO,mBACvC,KAAK,MAAM,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM,CAAC;AACnD,MAAM,cAAc,aAAiD;CACnE,MAAM,aAAa,SAAS;CAC5B,IAAI,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAAG,OAAO,CAAC;CACxF,MAAM,EAAE,SAAS,aAAa;CAI9B,OAAO,CAAC,GAAI,UAAU,CAAC,OAAO,IAAI,CAAC,GAAI,GAAG,OAAO,OAAO,YAAY,CAAC,CAAC,CAAC;AACzE;;AAGA,SAAgB,cAAc,MAAyB;CACrD,MAAM,OAAO,aAAa,KAAK,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,MAAM,EAAE;CAC/E,MAAM,MAAM,aAAa,KAAK,WAAW,IAAI,CAAC,CAAC,kBAAkB,EAAE,CAAC,GAAG,MAAM;CAC7E,IAAI,CAAC,aAAa,KAAK,IAAI,KAAK,CAAC,aAAa,KAAK,GAAG,GACpD,MAAM,IAAI,MAAM,4EAA4E;CAC9F,OAAO;EAAE;EAAM;CAAI;AACrB;;AAGA,SAAgB,WACd,MACA,EAAE,gBAAgB,yBAAyC,CAAC,GAC5D;CACA,MAAM,OAAO,cAAc,IAAI;CAC/B,MAAM,aAAa,QAAQ,IAAI,kBAAkB;CACjD,IAAI;CACJ,IAAI;EACF,OAAO,aAAa,YAAY,CAAC,WAAW,GAAG;GAAE,UAAU;GAAQ,OAAO;EAAO,CAAC,CAAC,CAAC,KAAK;CAC3F,QAAQ;EACN,MAAM,IAAI,MACR,oCAAoC,KAAK,KAAK,sBAAsB,cAAc,EACpF;CACF;CACA,IAAI,SAAS,IAAI,KAAK,QACpB,MAAM,IAAI,MACR,iBAAiB,KAAK,KAAK,QAAQ,KAAK,8CAA8C,cAAc,EACtG;CACF,OAAO;AACT;;AAGA,SAAgB,uBACd,MACA,UAA0B,CAAC,GACS;CACpC,MAAM,OAAO,cAAc,IAAI;CAC/B,IAAI,QAAQ,SAAS,QAAQ,KAAK,KAChC,MAAM,IAAI,MAAM,gBAAgB,KAAK,IAAI,QAAQ,QAAQ,SAAS,OAAO,OAAO,EAAE;CACpF,OAAO;EAAE,GAAG;EAAM,YAAY,WAAW,MAAM,OAAO;CAAE;AAC1D;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,UAAU,MAAwB;CACzC,MAAM,OAAO;CACb,MAAM,OAAO,KAAK,MAAM,IAAI;CAC5B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,CAAC,GAAG,KAAK,wCAAwC;CAC/E,IAAI;CACJ,IAAI;EACF,OAAO,UAAU,aAAa,MAAM,MAAM,CAAC;CAC7C,QAAQ;EACN,OAAO,CAAC,GAAG,KAAK,6BAA6B;CAC/C;CACA,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,CAAC,GAAG,KAAK,gCAAgC;CACrE,MAAM,WAAqB,CAAC;CAC5B,IAAI,OAAO,OAAO,MAAM,OAAO,GAC7B,SAAS,KAAK,GAAG,KAAK,2DAA2D;CACnF,MAAM,QAAQ,SAAS,KAAK,QAAQ,IAChC,KAAK,SAAS,sCACd,KAAA;CACJ,IACE,CAAC,MAAM,QAAQ,KAAK,KACpB,MAAM,WAAW,KACjB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,SAAS,MAAM,SAAS,IAAI,CAAC,GAErD,SAAS,KAAK,GAAG,KAAK,kDAAkD;CAC1E,OAAO;AACT;AAEA,SAAS,eAAe,MAAc,UAAkB,MAAyC;CAC/F,IAAI,OAAO,KAAK,SAAS,UAAU,OAAO,CAAC;CAC3C,MAAM,eAAe,KAAK,KAAK,QAAQ,GAAG;CAC1C,IAAI,eAAe,GAAG,OAAO,CAAC;CAC9B,MAAM,aAAa,KAAK,KAAK,MAAM,GAAG,YAAY,CAAC,CAAC,YAAY;CAChE,MAAM,QACJ,CACE;EAAC;EAAQ;EAAsB;CAAQ,GACvC;EAAC;EAAO;EAAqB;CAAc,CAC7C,CAAC,CACD,MAAM,GAAG,YAAY,eAAe,MAAM;CAC5C,IAAI,CAAC,OAAO,OAAO,CAAC;CACpB,MAAM,CAAC,QAAQ,UAAU;CACzB,MAAM,WAAqB,CAAC;CAC5B,IAAI,KAAK,SAAS,KAAA,KAAa,CAAC,SAAS,KAAK,IAAI,GAChD,SAAS,KAAK,GAAG,KAAK,IAAI,SAAS,wBAAwB;CAC7D,MAAM,SAAS,SAAS,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC;CAClD,IAAI,OAAO,OAAO,QAAQ,GAAG,KAAK,SAAS,GACzC,SAAS,KAAK,GAAG,KAAK,SAAS,KAAK,iBAAiB,OAAO,8BAA8B;CAC5F,MAAM,cAAc,OAAO,GAAG,KAAK;CACnC,IAAI,gBAAgB,KAAA,GAAW,SAAS,KAAK,GAAG,KAAK,UAAU,KAAK,mBAAmB,QAAQ;MAC1F,IAAI,gBAAgB,QAAQ,SAAS,KAAK,GAAG,KAAK,IAAI,KAAK,wBAAwB,QAAQ;CAChG,OAAO;AACT;AAEA,SAAS,gBAAgB,MAAc,UAAkB,OAA0B;CACjF,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC,GAAG,KAAK,IAAI,SAAS,kBAAkB;CAC1E,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;EAC3C,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM;EAC1C,IAAI,CAAC,SAAS,IAAI,GAAG;GACnB,SAAS,KAAK,GAAG,KAAK,IAAI,aAAa,mBAAmB;GAC1D;EACF;EACA,SAAS,KAAK,GAAG,eAAe,MAAM,cAAc,IAAI,CAAC;CAC3D;CACA,OAAO;AACT;AAEA,SAAS,eAAe,MAAwB;CAC9C,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,SAAS,kCAAkC,EAAE,KAAK,KAAK,CAAC,GAAG;EAC5E,IAAI;EACJ,IAAI;GACF,WAAW,UAAU,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM,GAAG,EAAE,UAAU,KAAK,CAAC;EACjF,QAAQ;GACN,SAAS,KAAK,GAAG,KAAK,6BAA6B;GACnD;EACF;EACA,IAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS,SAAS,IAAI,GAAG;GACnD,SAAS,KAAK,GAAG,KAAK,kCAAkC;GACxD;EACF;EACA,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,SAAS,IAAI,GAAG;GACvD,MAAM,WAAW,QAAQ;GACzB,IAAI,CAAC,SAAS,GAAG,GAAG;IAClB,SAAS,KAAK,GAAG,KAAK,IAAI,SAAS,mBAAmB;IACtD;GACF;GACA,IAAI,IAAI,UAAU,KAAA,GAAW;GAC7B,SAAS,KAAK,GAAG,gBAAgB,MAAM,GAAG,SAAS,SAAS,IAAI,KAAK,CAAC;EACxE;CACF;CACA,OAAO;AACT;AAEA,SAAS,sBAAsB,MAAwB;CACrD,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,SAAS,wCAAwC,EAAE,KAAK,KAAK,CAAC,GAAG;EAClF,IAAI;EACJ,IAAI;GACF,SAAS,UAAU,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM,GAAG,EAAE,UAAU,KAAK,CAAC;EAC/E,QAAQ;GACN,SAAS,KAAK,GAAG,KAAK,6BAA6B;GACnD;EACF;EACA,IAAI,CAAC,SAAS,MAAM,KAAK,CAAC,SAAS,OAAO,IAAI,GAAG;GAC/C,SAAS,KAAK,GAAG,KAAK,gCAAgC;GACtD;EACF;EACA,IAAI,OAAO,KAAK,UAAU,aAAa;EACvC,SAAS,KAAK,GAAG,gBAAgB,MAAM,cAAc,OAAO,KAAK,KAAK,CAAC;CACzE;CACA,OAAO;AACT;;AAGA,SAAgB,eACd,MACA,EACE,YAAY,CAAC,aAAa,WAAW,GACrC,0BAA0B,CAAC,UAAU,WAAW,MAC5B,CAAC,GACb;CACV,IAAI;CACJ,IAAI;EACF,OAAO,cAAc,IAAI;CAC3B,SAAS,OAAO;EACd,OAAO,CAAC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;CAChE;CACA,MAAM,WAAqB,CAAC;CAC5B,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,UAAU,MAAc,OAAe,QAA4B,aAAqB;EAC5F,IAAI,WAAW,UACb,SAAS,KAAK,GAAG,KAAK,IAAI,MAAM,MAAM,UAAU,UAAU,aAAa,UAAU;CACrF;CACA,OAAO,gBAAgB,gBAAgB,SAAS,SAAS,MAAM,KAAK,IAAI;CACxE,KAAK,MAAM,OAAO,CAAC,kBAAkB,SAAS,GAAG;EAC/C,MAAM,SAAS,SAAS,aAAa;EACrC,OACE,gBACA,cAAc,OACd,GAAG,QAAQ,KAAK,GAAG,QAAQ,WAC3B,OAAO,KAAK,KACd;EACA,OAAO,gBAAgB,cAAc,IAAI,UAAU,QAAQ,QAAQ,OAAO;CAC5E;CACA,KAAK,MAAM,QAAQ;EAAC;EAAiB;EAAgB;CAAgB,GACnE,IAAI,WAAW,KAAK,MAAM,IAAI,CAAC,GAC7B,SAAS,KAAK,GAAG,KAAK,iEAAiE;CAC3F,SAAS,KAAK,GAAG,UAAU,IAAI,CAAC;CAGhC,MAAM,YAAY,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC;CAEvC,MAAM,WAAW,UACf,SAAS,CAAC,WAAW,KAAK,KAAK,IAAI,MAAM,QAAQ,UAAU,EAAE,IAAI,KAAA;CACnE,MAAM,cAAc,MAAc,iBAAqD;EACrF,MAAM,OAAO,QAAQ,eAAe,cAAc;EAClD,IAAI,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,OAAO,WACjC,SAAS,KAAK,GAAG,KAAK,oCAAoC,UAAU,OAAO;EAC7E,MAAM,MAAM,QAAQ,eAAe,aAAa;EAChD,IAAI,KAAK,OAAO,MAAM,cAAc,KAAK,KAAK,GAAG;CACnD;CACA,KAAK,MAAM,WAAW,WAAW,QAAQ,GAAG,WAAW,wBAAwB,OAAO;CACtF,KAAK,MAAM,QAAQ,CACjB,gBACA,GAAG,SACD,kBAAkB,QAAQ,CAAC,CAAC,KAAK,SAAS,GAAG,KAAK,cAAc,GAChE,EAAE,KAAK,KAAK,CACd,CACF,GACE,WAAW,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC,eAAe;CAEzD,KAAK,MAAM,QAAQ,SAAS,iBAAiB;EAAE,KAAK;EAAM,SAAS,CAAC,oBAAoB;CAAE,CAAC,GAAG;EAC5F,MAAM,SAAS,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM;EACpD,KAAK,MAAM,GAAG,YAAY,OAAO,SAAS,gCAAgC,GACxE,OAAO,MAAM,cAAc,SAAS,KAAK,IAAI;EAC/C,KAAK,MAAM,GAAG,YAAY,OAAO,SAAS,qCAAqC,GAC7E,OAAO,MAAM,aAAa,SAAS,KAAK,GAAG;CAC/C;CACA,SAAS,KAAK,GAAG,eAAe,IAAI,GAAG,GAAG,sBAAsB,IAAI,CAAC;CACrE,KAAK,MAAM,QAAQ,WAAW;EAC5B,IAAI,CAAC,WAAW,KAAK,MAAM,IAAI,CAAC,GAAG;EACnC,MAAM,WAAW,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,WAAW,MAAM,EAAE;EAC3E,KAAK,MAAM,GAAG,MAAM,YAAY,SAAS,SAAS,mCAAmC,GACnF,OAAO,MAAM,cAAc,QAAQ,SAAS,SAAS,SAAS,KAAK,OAAO,KAAK,GAAG;CACtF;CACA,MAAM,YAAY,KAAK,MAAM,YAAY;CACzC,IAAI,WAAW,SAAS,GAAG;EACzB,MAAM,QAAQ,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC;EACxD,KAAK,MAAM,QAAQ,yBACjB,IAAI,CAAC,MAAM,oBAAoB,SAAS,IAAI,GAC1C,SAAS,KAAK,uBAAuB,KAAK,uBAAuB;CACvE;CACA,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AAC9B"}
|
|
1
|
+
{"version":3,"file":"toolchain.mjs","names":[],"sources":["../src/toolchain.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, globSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parseTOML } from \"confbox/toml\";\nimport { parseYAML } from \"confbox/yaml\";\nimport { workspacePatterns } from \"./workspaces.js\";\n\nexport interface Toolchain {\n node: string;\n bun: string;\n}\n\nexport interface ToolchainOptions {\n /** Bun-only repositories forbid Node runtime pins; the default checks both runtimes. */\n runtime?: \"node-bun\" | \"bun\";\n /** Repository-relative documents whose `Node x.y.z` and `Bun x.y.z` mentions must match. */\n documents?: readonly string[];\n /** Files every Turbo cache key must include when `turbo.json` exists. */\n turboGlobalDependencies?: readonly string[];\n}\n\nexport interface RuntimeOptions {\n /** Environment variable that may select a Node executable outside PATH. */\n nodeBinaryEnv?: string;\n}\n\ninterface Manifest {\n packageManager?: string;\n engines?: { node?: string };\n devEngines?: Record<string, { name?: string; version?: string; onFail?: string }>;\n devDependencies?: Record<string, string>;\n workspaces?: unknown;\n}\n\nconst exactVersion = /^\\d+\\.\\d+\\.\\d+$/;\nconst manifestAt = (root: string, file = \"package.json\"): Manifest =>\n JSON.parse(readFileSync(join(root, file), \"utf8\")) as Manifest;\nconst catalogsOf = (manifest: Manifest): Record<string, string>[] => {\n const workspaces = manifest.workspaces;\n if (!workspaces || typeof workspaces !== \"object\" || Array.isArray(workspaces)) return [];\n const { catalog, catalogs } = workspaces as {\n catalog?: Record<string, string>;\n catalogs?: Record<string, Record<string, string>>;\n };\n return [...(catalog ? [catalog] : []), ...Object.values(catalogs ?? {})];\n};\n\nconst bunVersion = (root: string) =>\n /^bun@(\\d+\\.\\d+\\.\\d+)$/.exec(manifestAt(root).packageManager ?? \"\")?.[1];\n\n/** Read the exact Bun pin without requiring a Node development runtime. */\nexport function readBunPin(root: string): string {\n const bun = bunVersion(root);\n if (!bun) throw new Error(\"Pin an exact Bun version in package.json packageManager.\");\n return bun;\n}\n\nfunction assertBunVersion(bun: string) {\n if (process.versions.bun !== bun)\n throw new Error(`Expected Bun ${bun}, got ${process.versions.bun ?? \"none\"}.`);\n}\n\n/** Check the active Bun runtime without executing Node or inspected repository code. */\nexport function assertBunRuntime(root: string): { bun: string } {\n const bun = readBunPin(root);\n assertBunVersion(bun);\n return { bun };\n}\n\n/** `.nvmrc` pins Node and the root `packageManager` pins Bun; every other pin follows them. */\nexport function readToolchain(root: string): Toolchain {\n const node = readFileSync(join(root, \".nvmrc\"), \"utf8\").trim().replace(/^v/, \"\");\n const bun = bunVersion(root) ?? \"\";\n if (!exactVersion.test(node) || !exactVersion.test(bun))\n throw new Error(\"Pin exact Node and Bun versions in .nvmrc and package.json packageManager.\");\n return { node, bun };\n}\n\n/** Returns the Node executable after confirming it provides the pinned release. */\nexport function pinnedNode(\n root: string,\n { nodeBinaryEnv = \"DEVKIT_NODE_BINARY\" }: RuntimeOptions = {},\n) {\n const pins = readToolchain(root);\n const nodeBinary = process.env[nodeBinaryEnv] || \"node\";\n let node: string;\n try {\n node = execFileSync(nodeBinary, [\"--version\"], { encoding: \"utf8\", stdio: \"pipe\" }).trim();\n } catch {\n throw new Error(\n `Node is unavailable; select Node ${pins.node} from .nvmrc or set ${nodeBinaryEnv}.`,\n );\n }\n if (node !== `v${pins.node}`)\n throw new Error(\n `Expected Node ${pins.node}, got ${node}; select the pinned Node from .nvmrc or set ${nodeBinaryEnv}.`,\n );\n return nodeBinary;\n}\n\n/** The binary override changes the consumer executable, never the required version. */\nexport function assertToolchainRuntime(\n root: string,\n options: RuntimeOptions = {},\n): Toolchain & { nodeBinary: string } {\n const pins = readToolchain(root);\n assertBunVersion(pins.bun);\n return { ...pins, nodeBinary: pinnedNode(root, options) };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction checkMise(root: string, bunOnly: boolean): string[] {\n const file = \"mise.toml\";\n const path = join(root, file);\n if (!existsSync(path)) return [`${file}: missing runtime manager configuration`];\n let mise: unknown;\n try {\n mise = parseTOML(readFileSync(path, \"utf8\"));\n } catch {\n return [`${file}: invalid TOML configuration`];\n }\n if (!isRecord(mise)) return [`${file}: configuration must be a table`];\n const findings: string[] = [];\n if (Object.hasOwn(mise, \"tools\"))\n findings.push(`${file}: read existing pins instead of declaring [tools] versions`);\n const tools = isRecord(mise.settings)\n ? mise.settings.idiomatic_version_file_enable_tools\n : undefined;\n const expected = bunOnly ? [\"bun\"] : [\"node\", \"bun\"];\n if (\n !Array.isArray(tools) ||\n tools.length !== expected.length ||\n !expected.every((tool) => tools.includes(tool))\n )\n findings.push(\n `${file}: enable idiomatic version files for ${bunOnly ? \"bun only\" : \"node and bun\"}`,\n );\n return findings;\n}\n\nfunction checkSetupStep(\n file: string,\n location: string,\n step: Record<string, unknown>,\n bunOnly: boolean,\n): string[] {\n if (typeof step.uses !== \"string\") return [];\n const refSeparator = step.uses.indexOf(\"@\");\n if (refSeparator < 0) return [];\n const repository = step.uses.slice(0, refSeparator).toLowerCase();\n const setup = (\n [\n [\"node\", \"actions/setup-node\", \".nvmrc\"],\n [\"bun\", \"oven-sh/setup-bun\", \"package.json\"],\n ] as const\n ).find(([, action]) => repository === action);\n if (!setup) return [];\n const [tool, , source] = setup;\n if (bunOnly && tool === \"node\")\n return [`${file}: remove actions/setup-node; Bun is the only runtime`];\n const findings: string[] = [];\n if (step.with !== undefined && !isRecord(step.with))\n findings.push(`${file}: ${location}.with must be a mapping`);\n const inputs = isRecord(step.with) ? step.with : {};\n if (Object.hasOwn(inputs, `${tool}-version`))\n findings.push(`${file}: read ${tool}-version-file: ${source} instead of a literal version`);\n const versionFile = inputs[`${tool}-version-file`];\n if (versionFile === undefined) findings.push(`${file}: every ${tool} setup must read ${source}`);\n else if (versionFile !== source) findings.push(`${file}: ${tool}-version-file must be ${source}`);\n return findings;\n}\n\nfunction checkSetupSteps(\n file: string,\n location: string,\n steps: unknown,\n bunOnly: boolean,\n): string[] {\n if (!Array.isArray(steps)) return [`${file}: ${location} must be an array`];\n const findings: string[] = [];\n for (const [index, step] of steps.entries()) {\n const stepLocation = `${location}[${index}]`;\n if (!isRecord(step)) {\n findings.push(`${file}: ${stepLocation} must be a mapping`);\n continue;\n }\n findings.push(...checkSetupStep(file, stepLocation, step, bunOnly));\n }\n return findings;\n}\n\nfunction checkWorkflows(root: string, bunOnly: boolean): string[] {\n const findings: string[] = [];\n for (const file of globSync(\".github/workflows/*.{yml,yaml}\", { cwd: root })) {\n let workflow: unknown;\n try {\n workflow = parseYAML(readFileSync(join(root, file), \"utf8\"), { filename: file });\n } catch {\n findings.push(`${file}: invalid YAML configuration`);\n continue;\n }\n if (!isRecord(workflow) || !isRecord(workflow.jobs)) {\n findings.push(`${file}: workflow jobs must be a mapping`);\n continue;\n }\n for (const [name, job] of Object.entries(workflow.jobs)) {\n const location = `jobs.${name}`;\n if (!isRecord(job)) {\n findings.push(`${file}: ${location} must be a mapping`);\n continue;\n }\n if (job.steps === undefined) continue;\n findings.push(...checkSetupSteps(file, `${location}.steps`, job.steps, bunOnly));\n }\n }\n return findings;\n}\n\nfunction checkCompositeActions(root: string, bunOnly: boolean): string[] {\n const findings: string[] = [];\n for (const file of globSync(\".github/actions/**/action.{yml,yaml}\", { cwd: root })) {\n let action: unknown;\n try {\n action = parseYAML(readFileSync(join(root, file), \"utf8\"), { filename: file });\n } catch {\n findings.push(`${file}: invalid YAML configuration`);\n continue;\n }\n if (!isRecord(action) || !isRecord(action.runs)) {\n findings.push(`${file}: action runs must be a mapping`);\n continue;\n }\n if (action.runs.using !== \"composite\") continue;\n findings.push(...checkSetupSteps(file, \"runs.steps\", action.runs.steps, bunOnly));\n }\n return findings;\n}\n\n/** Check duplicated declarations without installing tools or running application code. */\nexport function checkToolchain(\n root: string,\n {\n runtime = \"node-bun\",\n documents = [\"README.md\", \"AGENTS.md\"],\n turboGlobalDependencies = runtime === \"bun\" ? [\"mise.toml\"] : [\".nvmrc\", \"mise.toml\"],\n }: ToolchainOptions = {},\n): string[] {\n const bunOnly = runtime === \"bun\";\n let pins: { bun: string; node?: string };\n try {\n pins = bunOnly ? { bun: readBunPin(root) } : readToolchain(root);\n } catch (error) {\n return [error instanceof Error ? error.message : String(error)];\n }\n const findings: string[] = [];\n const manifest = manifestAt(root);\n const expect = (file: string, label: string, actual: string | undefined, expected: string) => {\n if (actual !== expected)\n findings.push(`${file}: ${label} is ${actual ?? \"missing\"}, expected ${expected}`);\n };\n if (bunOnly) {\n if (manifest.engines?.node !== undefined)\n findings.push(\"package.json: remove engines.node; Bun is the only runtime\");\n } else expect(\"package.json\", \"engines.node\", manifest.engines?.node, pins.node!);\n for (const key of [\"packageManager\", \"runtime\"]) {\n const engine = manifest.devEngines?.[key];\n expect(\n \"package.json\",\n `devEngines.${key}`,\n `${engine?.name}@${engine?.version}`,\n `bun@${pins.bun}`,\n );\n expect(\"package.json\", `devEngines.${key}.onFail`, engine?.onFail, \"error\");\n }\n for (const file of [\n ...(bunOnly ? [\".nvmrc\"] : []),\n \".node-version\",\n \".bun-version\",\n \".tool-versions\",\n ])\n if (existsSync(join(root, file)))\n findings.push(\n bunOnly\n ? `${file}: package.json packageManager is the only runtime pin`\n : `${file}: use .nvmrc and package.json instead of a second version source`,\n );\n findings.push(...checkMise(root, bunOnly));\n\n // Runtime type packages follow the pins: @types/node by major, @types/bun exactly.\n const nodeMajor = pins.node?.split(\".\")[0];\n // Protocol ranges such as catalog: and workspace: are checked where they resolve.\n const version = (range: string | undefined) =>\n range && !/^[a-z]+:/.test(range) ? range.replace(/^[\\^~]/, \"\") : undefined;\n const checkTypes = (file: string, dependencies: Record<string, string> | undefined) => {\n const node = version(dependencies?.[\"@types/node\"]);\n if (nodeMajor && node && node.split(\".\")[0] !== nodeMajor)\n findings.push(`${file}: @types/node must match the Node ${nodeMajor} major`);\n const bun = version(dependencies?.[\"@types/bun\"]);\n if (bun) expect(file, \"@types/bun\", bun, pins.bun);\n };\n for (const catalog of catalogsOf(manifest)) checkTypes(\"package.json catalog\", catalog);\n for (const file of [\n \"package.json\",\n ...globSync(\n workspacePatterns(manifest).map((path) => `${path}/package.json`),\n { cwd: root },\n ),\n ])\n checkTypes(file, manifestAt(root, file).devDependencies);\n\n for (const file of globSync(\"**/Dockerfile\", { cwd: root, exclude: [\"**/node_modules/**\"] })) {\n const source = readFileSync(join(root, file), \"utf8\");\n if (bunOnly) {\n if (/^\\s*FROM\\s+(?:--platform=\\S+\\s+)?node:/im.test(source))\n findings.push(`${file}: build and run on oven/bun images`);\n } else {\n for (const [, version] of source.matchAll(/^FROM\\s+node:(\\d+\\.\\d+\\.\\d+)/gm))\n expect(file, \"node image\", version, pins.node!);\n }\n for (const [, version] of source.matchAll(/^FROM\\s+oven\\/bun:(\\d+\\.\\d+\\.\\d+)/gm))\n expect(file, \"bun image\", version, pins.bun);\n }\n findings.push(...checkWorkflows(root, bunOnly), ...checkCompositeActions(root, bunOnly));\n for (const file of documents) {\n if (!existsSync(join(root, file))) continue;\n const document = readFileSync(join(root, file), \"utf8\").replaceAll(\"**\", \"\");\n for (const [, tool, version] of document.matchAll(/\\b(Node|Bun) v?(\\d+\\.\\d+\\.\\d+)\\b/g)) {\n if (bunOnly && tool === \"Node\")\n findings.push(`${file}: documents a Node release; Bun is the only runtime`);\n else expect(file, `documented ${tool}`, version, tool === \"Node\" ? pins.node! : pins.bun);\n }\n }\n const turboFile = join(root, \"turbo.json\");\n if (existsSync(turboFile)) {\n const turbo = JSON.parse(readFileSync(turboFile, \"utf8\")) as { globalDependencies?: string[] };\n for (const file of turboGlobalDependencies)\n if (!turbo.globalDependencies?.includes(file))\n findings.push(`turbo.json: include ${file} in globalDependencies`);\n }\n return [...new Set(findings)];\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raypx-kit/devkit",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.3",
|
|
4
4
|
"description": "Monorepo maintenance checks: workspace boundaries, toolchain pins, static catalogs, artifacts and task helpers.",
|
|
5
5
|
"homepage": "https://github.com/raypx/raypx-kit/tree/canary/packages/devkit",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,6 +49,14 @@
|
|
|
49
49
|
"./docs": {
|
|
50
50
|
"types": "./dist/docs.d.mts",
|
|
51
51
|
"import": "./dist/docs.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./lint": {
|
|
54
|
+
"types": "./dist/lint.d.mts",
|
|
55
|
+
"import": "./dist/lint.mjs"
|
|
56
|
+
},
|
|
57
|
+
"./testing": {
|
|
58
|
+
"types": "./dist/testing.d.mts",
|
|
59
|
+
"import": "./dist/testing.mjs"
|
|
52
60
|
}
|
|
53
61
|
},
|
|
54
62
|
"publishConfig": {
|
|
@@ -71,7 +79,8 @@
|
|
|
71
79
|
"oxc-parser": "^0.155.0"
|
|
72
80
|
},
|
|
73
81
|
"devDependencies": {
|
|
74
|
-
"@vitest/coverage-v8": "^5.0.5"
|
|
82
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
83
|
+
"vite-plus": "^1.3.0"
|
|
75
84
|
},
|
|
76
85
|
"engines": {
|
|
77
86
|
"node": ">=22.21"
|