@davidvornholt/standards 0.29.0 → 0.29.1
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 +3 -3
- package/package.json +2 -1
- package/src/cli.ts +3 -3
- package/src/structure-check.ts +10 -5
- package/src/structure-turbo.ts +89 -0
package/README.md
CHANGED
|
@@ -97,14 +97,14 @@ bun standards structure --profile source
|
|
|
97
97
|
|
|
98
98
|
`doctor` checks project-owned integration points such as `biome.jsonc`, `AGENTS.local.md`, package scripts, sync policy, settings overlays, and Dependabot inputs.
|
|
99
99
|
|
|
100
|
-
`structure` checks the exact Bun declaration, root and workspace scripts, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring, and CI secret structure. The `source` profile records the few intentional differences in this repository itself.
|
|
100
|
+
`structure` checks the exact Bun declaration, root and workspace scripts, the root lint tasks in `turbo.json`, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring, and CI secret structure. The `source` profile records the few intentional differences in this repository itself.
|
|
101
101
|
|
|
102
102
|
Starting with 0.29.0, Biome runs once from the repository root instead of once per workspace. The root `package.json` must declare `lint` running `biome check --error-on-warnings .` and `lint:fix` running `biome check --write --error-on-warnings .`, and any workspace that declares `lint` or `lint:fix` fails. To upgrade an existing repository:
|
|
103
103
|
|
|
104
|
-
1. Install the new CLI with `bun add --dev --exact @davidvornholt/standards@0.29.
|
|
104
|
+
1. Install the new CLI with `bun add --dev --exact @davidvornholt/standards@0.29.1`.
|
|
105
105
|
2. Run `bun standards sync` so the canonical `packages/typescript-config` and `packages/a11y-testing` drop their lint scripts. A repository that pins `ref` in `sync-standards.local.json` first moves it to `v0.29.0` or later.
|
|
106
106
|
3. Add the root `lint` and `lint:fix` scripts and remove both scripts from every workspace `package.json`.
|
|
107
|
-
4. In `turbo.json`, replace the `lint` and `lint:fix` tasks with `//#lint` (`"outputs": []`) and `//#lint:fix` (`"cache": false`, `"outputs": []`), and delete any `<package>#lint` overrides. Keep the default inputs for `//#lint`.
|
|
107
|
+
4. In `turbo.json`, replace the `lint` and `lint:fix` tasks with `//#lint` (`"outputs": []`) and `//#lint:fix` (`"cache": false`, `"outputs": []`), and delete any `<package>#lint` overrides. Keep the default inputs for `//#lint`. Starting with 0.29.1, `structure` fails until this step is done: Turbo runs the root lint scripts only through the `//#` tasks, so a leftover `lint` task makes `turbo run lint` pass without running Biome.
|
|
108
108
|
5. Run `bun run lint:fix` and fix anything it still reports. The root run also checks root files that per-workspace runs never covered.
|
|
109
109
|
|
|
110
110
|
## dependabot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davidvornholt/standards",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "Bootstrap, synchronize, and validate davidvornholt/standards consumers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -173,6 +173,7 @@
|
|
|
173
173
|
"src/structure-script.ts",
|
|
174
174
|
"src/structure-secrets.ts",
|
|
175
175
|
"src/structure-sops-envelope.ts",
|
|
176
|
+
"src/structure-turbo.ts",
|
|
176
177
|
"src/structure-workspace.ts",
|
|
177
178
|
"src/sync-policy.ts",
|
|
178
179
|
"src/yaml-emit.ts",
|
package/src/cli.ts
CHANGED
|
@@ -1326,9 +1326,9 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
|
|
|
1326
1326
|
};
|
|
1327
1327
|
};
|
|
1328
1328
|
|
|
1329
|
-
// Canonical monorepo structure gate: workspace and root script shapes,
|
|
1330
|
-
// internal versioning, `exports`, tsconfig
|
|
1331
|
-
// and the undecrypted CI secrets contract
|
|
1329
|
+
// Canonical monorepo structure gate: workspace and root script shapes, the
|
|
1330
|
+
// root lint tasks in turbo.json, internal versioning, `exports`, tsconfig
|
|
1331
|
+
// inheritance, a11y wiring, and the undecrypted CI secrets contract
|
|
1332
1332
|
// (SOPS shape mirroring plus the keys the synced workflows require).
|
|
1333
1333
|
// The `source` profile instead pins the standards template repository's own
|
|
1334
1334
|
// deliberate exceptions to the consumer contract.
|
package/src/structure-check.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './structure-profile';
|
|
14
14
|
import { filteredTurboAliasProblem, hasSafeCommands } from './structure-script';
|
|
15
15
|
import { collectCiSecretsProblems } from './structure-secrets';
|
|
16
|
+
import { collectTurboLintProblems } from './structure-turbo';
|
|
16
17
|
import { inspectWorkspace, type Workspace } from './structure-workspace';
|
|
17
18
|
|
|
18
19
|
const ROOT_FIXED_SCRIPTS = new Set(
|
|
@@ -155,14 +156,17 @@ export const collectStructureProblems = async (
|
|
|
155
156
|
consumer: string,
|
|
156
157
|
profile: StructureProfile,
|
|
157
158
|
): Promise<ReadonlyArray<string>> => {
|
|
158
|
-
const [root, secretsProblems, callerProblems] =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
const [root, turboProblems, secretsProblems, callerProblems] =
|
|
160
|
+
await Promise.all([
|
|
161
|
+
readJsonFile(join(consumer, 'package.json')),
|
|
162
|
+
collectTurboLintProblems(consumer),
|
|
163
|
+
collectCiSecretsProblems(consumer),
|
|
164
|
+
collectSopsActionCallerProblems(consumer),
|
|
165
|
+
]);
|
|
163
166
|
if (root === null) {
|
|
164
167
|
return [
|
|
165
168
|
'package.json must exist and contain a JSON object',
|
|
169
|
+
...turboProblems,
|
|
166
170
|
...secretsProblems,
|
|
167
171
|
...callerProblems,
|
|
168
172
|
];
|
|
@@ -194,6 +198,7 @@ export const collectStructureProblems = async (
|
|
|
194
198
|
...missingPublishedCliProblems(profile, workspaces),
|
|
195
199
|
...collectBunVersionProblems(root),
|
|
196
200
|
...inspectRootScripts(root, profile, requireA11y),
|
|
201
|
+
...turboProblems,
|
|
197
202
|
...inspections.flatMap((i) => [...i.problems]),
|
|
198
203
|
...secretsProblems,
|
|
199
204
|
...callerProblems,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isRecord } from './github-settings-parse';
|
|
4
|
+
|
|
5
|
+
// The root gate runs `turbo run lint` and `turbo run lint:fix`, and Turbo runs
|
|
6
|
+
// a root package.json script only through a task registered as `//#<name>`.
|
|
7
|
+
// A plain `lint` task targets workspace scripts, which no longer exist, so
|
|
8
|
+
// Turbo runs nothing and the gate passes without linting. A cache hit also
|
|
9
|
+
// skips Biome: narrowed `//#lint` inputs let CI replay main's cached pass after
|
|
10
|
+
// a linted file changes, and a cached `//#lint:fix` replays instead of fixing.
|
|
11
|
+
const ROOT_LINT_TASK = '//#lint';
|
|
12
|
+
const ROOT_LINT_FIX_TASK = '//#lint:fix';
|
|
13
|
+
const LINT_TASK_NAMES: ReadonlyArray<string> = ['lint', 'lint:fix'];
|
|
14
|
+
const ROOT_PACKAGE = '//';
|
|
15
|
+
|
|
16
|
+
const readTurboConfig = async (
|
|
17
|
+
consumer: string,
|
|
18
|
+
): Promise<Record<string, unknown> | null> => {
|
|
19
|
+
const raw = await readFile(join(consumer, 'turbo.json'), 'utf8').catch(
|
|
20
|
+
() => null,
|
|
21
|
+
);
|
|
22
|
+
if (raw === null) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// Turbo accepts comments and trailing commas in turbo.json.
|
|
27
|
+
const parsed: unknown = globalThis.Bun.JSONC.parse(raw);
|
|
28
|
+
return isRecord(parsed) ? parsed : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Matches `lint`, `lint:fix`, and `<package>#lint(:fix)` overrides, but not
|
|
35
|
+
// the root `//#` tasks.
|
|
36
|
+
const isWorkspaceLintTask = (key: string): boolean => {
|
|
37
|
+
const separator = key.lastIndexOf('#');
|
|
38
|
+
const owner = separator === -1 ? null : key.slice(0, separator);
|
|
39
|
+
return (
|
|
40
|
+
owner !== ROOT_PACKAGE && LINT_TASK_NAMES.includes(key.slice(separator + 1))
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const rootLintProblems = (
|
|
45
|
+
tasks: Record<string, unknown>,
|
|
46
|
+
): ReadonlyArray<string> => {
|
|
47
|
+
const { [ROOT_LINT_TASK]: lint, [ROOT_LINT_FIX_TASK]: lintFix } = tasks;
|
|
48
|
+
return [
|
|
49
|
+
...(isRecord(lint)
|
|
50
|
+
? []
|
|
51
|
+
: [
|
|
52
|
+
`turbo.json: define the root task "${ROOT_LINT_TASK}" as { "outputs": [] }; Turbo runs the root "lint" script only through that task, so without it turbo run lint never runs Biome`,
|
|
53
|
+
]),
|
|
54
|
+
...(isRecord(lint) && Object.hasOwn(lint, 'inputs')
|
|
55
|
+
? [
|
|
56
|
+
`turbo.json: remove "inputs" from the root task "${ROOT_LINT_TASK}"; the default inputs cover every file Biome checks, and a narrower set replays a cached pass after a linted file changes`,
|
|
57
|
+
]
|
|
58
|
+
: []),
|
|
59
|
+
...(isRecord(lintFix)
|
|
60
|
+
? []
|
|
61
|
+
: [
|
|
62
|
+
`turbo.json: define the root task "${ROOT_LINT_FIX_TASK}" as { "cache": false, "outputs": [] }; Turbo runs the root "lint:fix" script only through that task, so without it turbo run lint:fix never runs Biome`,
|
|
63
|
+
]),
|
|
64
|
+
...(isRecord(lintFix) && lintFix.cache !== false
|
|
65
|
+
? [
|
|
66
|
+
`turbo.json: set "cache": false on the root task "${ROOT_LINT_FIX_TASK}"; a cache hit replays the previous output instead of running Biome's fixes`,
|
|
67
|
+
]
|
|
68
|
+
: []),
|
|
69
|
+
];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const collectTurboLintProblems = async (
|
|
73
|
+
consumer: string,
|
|
74
|
+
): Promise<ReadonlyArray<string>> => {
|
|
75
|
+
const config = await readTurboConfig(consumer);
|
|
76
|
+
if (config === null) {
|
|
77
|
+
return ['turbo.json must exist and contain a JSON object'];
|
|
78
|
+
}
|
|
79
|
+
const tasks = isRecord(config.tasks) ? config.tasks : {};
|
|
80
|
+
return [
|
|
81
|
+
...rootLintProblems(tasks),
|
|
82
|
+
...Object.keys(tasks)
|
|
83
|
+
.filter(isWorkspaceLintTask)
|
|
84
|
+
.map(
|
|
85
|
+
(key) =>
|
|
86
|
+
`turbo.json: remove task "${key}"; linting runs once at the repository root through the root tasks "${ROOT_LINT_TASK}" and "${ROOT_LINT_FIX_TASK}"`,
|
|
87
|
+
),
|
|
88
|
+
];
|
|
89
|
+
};
|