@produck/agent-toolkit 0.10.0 → 0.11.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 +9 -6
- package/bin/agent-toolkit.mjs +8 -0
- package/bin/command/enforce-node-baseline/help.txt +6 -5
- package/bin/command/enforce-node-baseline/index.mjs +11 -0
- package/bin/command/main/help.txt +4 -3
- package/bin/command/sync-coverage/help.txt +6 -21
- package/bin/command/sync-coverage/index.mjs +1 -199
- package/bin/command/sync-publish/help.txt +1 -1
- package/bin/command/sync-publish/index.mjs +38 -15
- package/bin/command/sync-workspace/help.txt +22 -0
- package/bin/command/sync-workspace/index.mjs +340 -0
- package/package.json +2 -2
- package/publish-assets/gitignore +3 -0
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +43 -6
- package/publish-assets/instructions/produck/tooling-version-baseline.json +6 -6
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ What it does (in order):
|
|
|
26
26
|
6. Deploys root `.c8rc.json` and root `c8` devDependency
|
|
27
27
|
**Note:** The `produck:coverage` script in subpackages is for local and AI development use only. It is NOT enforced by organization CI or `.c8rc.json`. Only the root workspace (monorepo root) is subject to org-level coverage enforcement and `.c8rc.json`.
|
|
28
28
|
7. Deploys root `.gitattributes`
|
|
29
|
-
8. Deploys the pinned `produck:coverage` script
|
|
29
|
+
8. Deploys the pinned `produck:coverage` script to each workspace package and enforces `scripts.test` (generates a default `test` script when missing). Workspace packages rely on root-level c8 devDependency (hoisted by npm workspaces).
|
|
30
30
|
9. Deploys `.husky/pre-commit` and `.husky/commit-msg`
|
|
31
31
|
|
|
32
32
|
After running, add the persistent enforcement entry to the repository
|
|
@@ -90,11 +90,14 @@ failure:
|
|
|
90
90
|
(`produck:baseline`, `produck:commit:check`) plus shared pinned root
|
|
91
91
|
devDependencies (`husky`, `lerna`, `@produck/agent-toolkit`)
|
|
92
92
|
8. `sync-coverage` — deploy root `scripts.produck:coverage`, `.c8rc.json`, and
|
|
93
|
-
root `c8` devDependency
|
|
94
|
-
|
|
95
|
-
package
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
root `c8` devDependency (root-only; does not touch workspace packages)
|
|
94
|
+
9. `sync-workspace` — deploy pinned `produck:coverage` script to each
|
|
95
|
+
workspace package and enforce `scripts.test` (auto-generate a default value
|
|
96
|
+
when missing). Workspace packages rely on root-level c8 devDependency
|
|
97
|
+
(hoisted by npm workspaces) and must not duplicate c8 in their own
|
|
98
|
+
`devDependencies`
|
|
99
|
+
10. `sync-publish` — create default `lerna.json` when missing and deploy root
|
|
100
|
+
`scripts.produck:publish:check` plus `scripts.produck:publish`
|
|
98
101
|
|
|
99
102
|
Add to downstream repository root `package.json` for one-command enforcement:
|
|
100
103
|
|
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -46,6 +46,10 @@ import {
|
|
|
46
46
|
printSyncTypescriptHelp,
|
|
47
47
|
runSyncTypescript,
|
|
48
48
|
} from './command/sync-typescript/index.mjs';
|
|
49
|
+
import {
|
|
50
|
+
printSyncWorkspaceHelp,
|
|
51
|
+
runSyncWorkspace,
|
|
52
|
+
} from './command/sync-workspace/index.mjs';
|
|
49
53
|
import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
|
|
50
54
|
import {
|
|
51
55
|
printValidateCommitMsgHelp,
|
|
@@ -109,6 +113,10 @@ const COMMANDS = {
|
|
|
109
113
|
printHelp: printSyncTypescriptHelp,
|
|
110
114
|
run: runSyncTypescript,
|
|
111
115
|
},
|
|
116
|
+
'sync-workspace': {
|
|
117
|
+
printHelp: printSyncWorkspaceHelp,
|
|
118
|
+
run: runSyncWorkspace,
|
|
119
|
+
},
|
|
112
120
|
};
|
|
113
121
|
|
|
114
122
|
const DEFAULT_COMMAND = 'enforce-node-baseline';
|
|
@@ -12,13 +12,14 @@ Behavior:
|
|
|
12
12
|
5) sync-lint
|
|
13
13
|
6) sync-install
|
|
14
14
|
7) sync-git
|
|
15
|
-
8) sync-coverage
|
|
16
|
-
9) sync-
|
|
15
|
+
8) sync-coverage (root-level only)
|
|
16
|
+
9) sync-workspace (workspace packages, scripts only)
|
|
17
|
+
10) sync-publish
|
|
17
18
|
- Stops at first failed step and exits non-zero
|
|
18
19
|
- Prints one combined JSON report for all executed steps
|
|
19
20
|
|
|
20
21
|
Rules:
|
|
21
|
-
- --check runs non-mutating validation mode for step 2
|
|
22
|
-
- --dry-run runs non-mutating preview mode for step 2
|
|
22
|
+
- --check runs non-mutating validation mode for step 2 through step 10
|
|
23
|
+
- --dry-run runs non-mutating preview mode for step 2 through step 10
|
|
23
24
|
- --check takes precedence over --dry-run
|
|
24
|
-
- --workspace filters
|
|
25
|
+
- --workspace filters workspace package targets in step 9
|
|
@@ -168,6 +168,16 @@ export function runEnforceNodeBaseline(options) {
|
|
|
168
168
|
syncEslintConfigArgs.push('--dry-run');
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
const syncWorkspaceArgs = ['sync-workspace', '--cwd', cwd];
|
|
172
|
+
for (const workspacePath of workspaces) {
|
|
173
|
+
syncWorkspaceArgs.push('--workspace', workspacePath);
|
|
174
|
+
}
|
|
175
|
+
if (check) {
|
|
176
|
+
syncWorkspaceArgs.push('--check');
|
|
177
|
+
} else if (dryRun) {
|
|
178
|
+
syncWorkspaceArgs.push('--dry-run');
|
|
179
|
+
}
|
|
180
|
+
|
|
171
181
|
const plan = [
|
|
172
182
|
{ name: 'preflight', args: preflightArgs },
|
|
173
183
|
{ name: 'sync-instructions', args: syncInstructionsArgs },
|
|
@@ -177,6 +187,7 @@ export function runEnforceNodeBaseline(options) {
|
|
|
177
187
|
{ name: 'sync-install', args: syncInstallArgs },
|
|
178
188
|
{ name: 'sync-git', args: syncGitArgs },
|
|
179
189
|
{ name: 'sync-coverage', args: syncCoverageArgs },
|
|
190
|
+
{ name: 'sync-workspace', args: syncWorkspaceArgs },
|
|
180
191
|
{ name: 'sync-publish', args: syncPublishArgs },
|
|
181
192
|
];
|
|
182
193
|
|
|
@@ -5,14 +5,15 @@ agent-toolkit commands:
|
|
|
5
5
|
summarize-log
|
|
6
6
|
sync-coverage
|
|
7
7
|
sync-editorconfig
|
|
8
|
-
sync-git
|
|
9
8
|
sync-format
|
|
10
|
-
sync-
|
|
9
|
+
sync-git
|
|
11
10
|
sync-install
|
|
11
|
+
sync-instructions
|
|
12
12
|
sync-lint
|
|
13
13
|
sync-publish
|
|
14
|
+
sync-typescript
|
|
15
|
+
sync-workspace
|
|
14
16
|
validate-commit-msg
|
|
15
|
-
sync-instructions
|
|
16
17
|
|
|
17
18
|
Default:
|
|
18
19
|
agent-toolkit
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
Usage:
|
|
2
2
|
agent-toolkit sync-coverage [--cwd <dir>]
|
|
3
|
-
[--
|
|
3
|
+
[--check] [--dry-run] [--json <file>]
|
|
4
4
|
|
|
5
5
|
Behavior:
|
|
6
|
-
- Adds or updates root scripts.produck:coverage, root .c8rc.json,
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
node -e "console.log('No tests configured')"
|
|
11
|
-
- The produck:coverage script in subpackages is for local and AI development use only.
|
|
12
|
-
- Subpackage produck:coverage is NOT enforced by organization CI or .c8rc.json.
|
|
13
|
-
- Only the root workspace (monorepo root) is subject to org-level coverage enforcement and .c8rc.json.
|
|
6
|
+
- Adds or updates root scripts.produck:coverage, root .c8rc.json,
|
|
7
|
+
and root devDependencies.c8.
|
|
8
|
+
- Only the root workspace (monorepo root) is subject to org-level
|
|
9
|
+
coverage enforcement and .c8rc.json.
|
|
14
10
|
- Organization-reserved script key is scripts.produck:coverage
|
|
15
|
-
-
|
|
16
|
-
(lookup order):
|
|
17
|
-
1) .github/distribution/produck/tooling-version-baseline.json
|
|
18
|
-
2) publish-assets/instructions/produck/tooling-version-baseline.json
|
|
19
|
-
- Baseline template:
|
|
20
|
-
c8 --reporter=lcov --reporter=html --reporter=text-summary npm test
|
|
11
|
+
- c8 version is resolved from organization tooling baseline file
|
|
21
12
|
|
|
22
13
|
Rules:
|
|
23
|
-
- When --workspace is omitted, root package.json workspaces are used
|
|
24
|
-
- Root workspaces must be explicit paths (no glob tokens)
|
|
25
|
-
- Workspace package.json files must include scripts.test
|
|
26
|
-
- Workspace package.json files must pin devDependencies.c8 to baseline version
|
|
27
|
-
- Subpackage produck:coverage is not a CI or org gate; it is for local/dev use only
|
|
28
|
-
- Only the root workspace is enforced by org CI and .c8rc.json
|
|
29
14
|
- --check validates without writing and exits non-zero on mismatch
|
|
30
15
|
- --dry-run prints planned changes without writing
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { getSingle, hasFlag } from '../shared/args.mjs';
|
|
6
6
|
import { printTextResource } from '../shared/text-resource.mjs';
|
|
7
7
|
|
|
8
8
|
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,7 +19,6 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
|
19
19
|
'publish-assets/instructions/produck/tooling-version-baseline.json',
|
|
20
20
|
),
|
|
21
21
|
];
|
|
22
|
-
const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
|
|
23
22
|
const REQUIRED_ROOT_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
24
23
|
const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE = [
|
|
25
24
|
'c8',
|
|
@@ -28,10 +27,6 @@ const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE = [
|
|
|
28
27
|
'--workspaces',
|
|
29
28
|
'--if-present',
|
|
30
29
|
].join(' ');
|
|
31
|
-
const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
32
|
-
const REQUIRED_TEST_SCRIPT_KEY = 'test';
|
|
33
|
-
const DEFAULT_TEST_SCRIPT_VALUE =
|
|
34
|
-
'node -e "console.log(\'No tests configured\')"';
|
|
35
30
|
const REQUIRED_C8_CONFIG_FILE = '.c8rc.json';
|
|
36
31
|
const REQUIRED_C8_CONFIG_TEMPLATE_FILE = path.resolve(
|
|
37
32
|
COMMAND_DIR,
|
|
@@ -127,12 +122,6 @@ function loadToolingBaseline() {
|
|
|
127
122
|
};
|
|
128
123
|
}
|
|
129
124
|
|
|
130
|
-
function buildRequiredCoverageScript(baseline) {
|
|
131
|
-
const c8Version = String(baseline.tools.c8.version);
|
|
132
|
-
const coverageTemplate = String(baseline.coverage.scriptTemplate);
|
|
133
|
-
return coverageTemplate.replace(/\{c8\.version\}/g, c8Version);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
125
|
function buildRequiredC8DevDependency(baseline) {
|
|
137
126
|
return String(baseline.tools.c8.version);
|
|
138
127
|
}
|
|
@@ -160,37 +149,6 @@ function loadRequiredC8ConfigContent() {
|
|
|
160
149
|
return `${JSON.stringify(template, null, 2)}\n`;
|
|
161
150
|
}
|
|
162
151
|
|
|
163
|
-
function resolveWorkspacePaths(cwd, options) {
|
|
164
|
-
const manual = getMulti(options, '--workspace');
|
|
165
|
-
if (manual.length > 0) {
|
|
166
|
-
return manual;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const rootPackageJsonPath = path.resolve(cwd, 'package.json');
|
|
170
|
-
const rootPackageJson = parseJsonFile(
|
|
171
|
-
rootPackageJsonPath,
|
|
172
|
-
'Root package.json',
|
|
173
|
-
);
|
|
174
|
-
if (!Array.isArray(rootPackageJson.workspaces)) {
|
|
175
|
-
return [];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const workspaces = rootPackageJson.workspaces.map((entry) => String(entry));
|
|
179
|
-
if (workspaces.length === 0) {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const hasGlob = workspaces.some((entry) => GLOB_TOKEN_PATTERN.test(entry));
|
|
184
|
-
if (hasGlob) {
|
|
185
|
-
console.error(
|
|
186
|
-
'Root package.json `workspaces` must use explicit paths without glob tokens',
|
|
187
|
-
);
|
|
188
|
-
process.exit(2);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return workspaces;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
152
|
function syncRootCoverage(
|
|
195
153
|
cwd,
|
|
196
154
|
mode,
|
|
@@ -274,130 +232,6 @@ function syncRootCoverage(
|
|
|
274
232
|
};
|
|
275
233
|
}
|
|
276
234
|
|
|
277
|
-
function reconcileCoverageScript(
|
|
278
|
-
cwd,
|
|
279
|
-
workspacePath,
|
|
280
|
-
mode,
|
|
281
|
-
requiredCoverageScript,
|
|
282
|
-
requiredC8Version,
|
|
283
|
-
) {
|
|
284
|
-
const packageDir = path.resolve(cwd, workspacePath);
|
|
285
|
-
const packageJsonPath = path.resolve(packageDir, 'package.json');
|
|
286
|
-
|
|
287
|
-
const result = {
|
|
288
|
-
workspacePath,
|
|
289
|
-
packageDir,
|
|
290
|
-
packageJsonPath,
|
|
291
|
-
exists: false,
|
|
292
|
-
validJson: false,
|
|
293
|
-
previousCoverage: null,
|
|
294
|
-
coverageScript: null,
|
|
295
|
-
previousTestScript: null,
|
|
296
|
-
testScript: null,
|
|
297
|
-
previousC8DevDependency: null,
|
|
298
|
-
c8DevDependency: null,
|
|
299
|
-
matchesRequiredCoverageBefore: false,
|
|
300
|
-
matchesRequiredCoverageAfter: false,
|
|
301
|
-
hasRequiredTestScriptBefore: false,
|
|
302
|
-
hasRequiredTestScriptAfter: false,
|
|
303
|
-
matchesRequiredC8DevDependencyBefore: false,
|
|
304
|
-
matchesRequiredC8DevDependencyAfter: false,
|
|
305
|
-
updated: false,
|
|
306
|
-
error: '',
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
310
|
-
result.error = `Workspace package.json does not exist: ${workspacePath}`;
|
|
311
|
-
return result;
|
|
312
|
-
}
|
|
313
|
-
result.exists = true;
|
|
314
|
-
|
|
315
|
-
let pkg;
|
|
316
|
-
try {
|
|
317
|
-
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
318
|
-
result.validJson = true;
|
|
319
|
-
} catch {
|
|
320
|
-
result.error = `Workspace package.json is not valid JSON: ${workspacePath}`;
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const scripts =
|
|
325
|
-
pkg.scripts &&
|
|
326
|
-
typeof pkg.scripts === 'object' &&
|
|
327
|
-
!Array.isArray(pkg.scripts)
|
|
328
|
-
? { ...pkg.scripts }
|
|
329
|
-
: {};
|
|
330
|
-
const devDependencies =
|
|
331
|
-
pkg.devDependencies &&
|
|
332
|
-
typeof pkg.devDependencies === 'object' &&
|
|
333
|
-
!Array.isArray(pkg.devDependencies)
|
|
334
|
-
? { ...pkg.devDependencies }
|
|
335
|
-
: {};
|
|
336
|
-
|
|
337
|
-
const previousCoverage =
|
|
338
|
-
typeof scripts[REQUIRED_COVERAGE_SCRIPT_KEY] === 'string'
|
|
339
|
-
? scripts[REQUIRED_COVERAGE_SCRIPT_KEY]
|
|
340
|
-
: null;
|
|
341
|
-
const previousTestScript =
|
|
342
|
-
typeof scripts[REQUIRED_TEST_SCRIPT_KEY] === 'string' &&
|
|
343
|
-
scripts[REQUIRED_TEST_SCRIPT_KEY].trim() !== ''
|
|
344
|
-
? scripts[REQUIRED_TEST_SCRIPT_KEY]
|
|
345
|
-
: null;
|
|
346
|
-
const previousC8DevDependency =
|
|
347
|
-
typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null;
|
|
348
|
-
result.previousCoverage = previousCoverage;
|
|
349
|
-
result.previousTestScript = previousTestScript;
|
|
350
|
-
result.previousC8DevDependency = previousC8DevDependency;
|
|
351
|
-
result.matchesRequiredCoverageBefore =
|
|
352
|
-
previousCoverage === requiredCoverageScript;
|
|
353
|
-
result.hasRequiredTestScriptBefore = previousTestScript !== null;
|
|
354
|
-
result.matchesRequiredC8DevDependencyBefore =
|
|
355
|
-
previousC8DevDependency === requiredC8Version;
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
(!result.matchesRequiredCoverageBefore ||
|
|
359
|
-
!result.hasRequiredTestScriptBefore ||
|
|
360
|
-
!result.matchesRequiredC8DevDependencyBefore) &&
|
|
361
|
-
mode === 'sync'
|
|
362
|
-
) {
|
|
363
|
-
scripts[REQUIRED_COVERAGE_SCRIPT_KEY] = requiredCoverageScript;
|
|
364
|
-
if (!result.hasRequiredTestScriptBefore) {
|
|
365
|
-
scripts[REQUIRED_TEST_SCRIPT_KEY] = DEFAULT_TEST_SCRIPT_VALUE;
|
|
366
|
-
}
|
|
367
|
-
devDependencies.c8 = requiredC8Version;
|
|
368
|
-
pkg.scripts = scripts;
|
|
369
|
-
pkg.devDependencies = devDependencies;
|
|
370
|
-
fs.writeFileSync(
|
|
371
|
-
packageJsonPath,
|
|
372
|
-
`${JSON.stringify(pkg, null, 2)}\n`,
|
|
373
|
-
'utf8',
|
|
374
|
-
);
|
|
375
|
-
result.updated = true;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
result.coverageScript =
|
|
379
|
-
mode === 'sync' && !result.matchesRequiredCoverageBefore
|
|
380
|
-
? requiredCoverageScript
|
|
381
|
-
: previousCoverage;
|
|
382
|
-
result.testScript =
|
|
383
|
-
mode === 'sync' && !result.hasRequiredTestScriptBefore
|
|
384
|
-
? DEFAULT_TEST_SCRIPT_VALUE
|
|
385
|
-
: previousTestScript;
|
|
386
|
-
result.c8DevDependency =
|
|
387
|
-
mode === 'sync' && !result.matchesRequiredC8DevDependencyBefore
|
|
388
|
-
? requiredC8Version
|
|
389
|
-
: previousC8DevDependency;
|
|
390
|
-
|
|
391
|
-
result.matchesRequiredCoverageAfter =
|
|
392
|
-
result.updated || result.matchesRequiredCoverageBefore;
|
|
393
|
-
result.hasRequiredTestScriptAfter =
|
|
394
|
-
(mode === 'sync' && !result.hasRequiredTestScriptBefore) ||
|
|
395
|
-
result.hasRequiredTestScriptBefore;
|
|
396
|
-
result.matchesRequiredC8DevDependencyAfter =
|
|
397
|
-
result.updated || result.matchesRequiredC8DevDependencyBefore;
|
|
398
|
-
return result;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
235
|
export function runSyncCoverage(options) {
|
|
402
236
|
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
403
237
|
const check = hasFlag(options, '--check');
|
|
@@ -405,7 +239,6 @@ export function runSyncCoverage(options) {
|
|
|
405
239
|
const jsonFile = getSingle(options, '--json', '');
|
|
406
240
|
const { baseline: toolingBaseline, toolingBaselinePath } =
|
|
407
241
|
loadToolingBaseline();
|
|
408
|
-
const requiredCoverageScript = buildRequiredCoverageScript(toolingBaseline);
|
|
409
242
|
const requiredC8Version = buildRequiredC8DevDependency(toolingBaseline);
|
|
410
243
|
const requiredC8ConfigContent = loadRequiredC8ConfigContent();
|
|
411
244
|
|
|
@@ -421,7 +254,6 @@ export function runSyncCoverage(options) {
|
|
|
421
254
|
requiredC8Version,
|
|
422
255
|
requiredC8ConfigContent,
|
|
423
256
|
);
|
|
424
|
-
const workspacePaths = resolveWorkspacePaths(cwd, options);
|
|
425
257
|
|
|
426
258
|
const report = {
|
|
427
259
|
cwd,
|
|
@@ -431,12 +263,8 @@ export function runSyncCoverage(options) {
|
|
|
431
263
|
schemaVersion: toolingBaseline.schemaVersion,
|
|
432
264
|
c8Version: toolingBaseline.tools.c8.version,
|
|
433
265
|
},
|
|
434
|
-
requiredCoverageScript,
|
|
435
|
-
requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
|
|
436
266
|
requiredC8DevDependency: requiredC8Version,
|
|
437
267
|
root,
|
|
438
|
-
workspaces: workspacePaths,
|
|
439
|
-
results: [],
|
|
440
268
|
ok: true,
|
|
441
269
|
};
|
|
442
270
|
|
|
@@ -449,32 +277,6 @@ export function runSyncCoverage(options) {
|
|
|
449
277
|
report.ok = false;
|
|
450
278
|
}
|
|
451
279
|
|
|
452
|
-
for (const workspacePath of workspacePaths) {
|
|
453
|
-
const effectiveMode = mode === 'sync' ? 'sync' : 'check';
|
|
454
|
-
const item = reconcileCoverageScript(
|
|
455
|
-
cwd,
|
|
456
|
-
workspacePath,
|
|
457
|
-
effectiveMode,
|
|
458
|
-
requiredCoverageScript,
|
|
459
|
-
requiredC8Version,
|
|
460
|
-
);
|
|
461
|
-
report.results.push(item);
|
|
462
|
-
|
|
463
|
-
if (item.error) {
|
|
464
|
-
report.ok = false;
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (
|
|
469
|
-
mode === 'check' &&
|
|
470
|
-
(!item.matchesRequiredCoverageAfter ||
|
|
471
|
-
!item.hasRequiredTestScriptAfter ||
|
|
472
|
-
!item.matchesRequiredC8DevDependencyAfter)
|
|
473
|
-
) {
|
|
474
|
-
report.ok = false;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
280
|
if (jsonFile) {
|
|
479
281
|
const outPath = path.resolve(cwd, jsonFile);
|
|
480
282
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
@@ -10,7 +10,7 @@ Behavior:
|
|
|
10
10
|
- Sync mode applies organization-required root publish scripts:
|
|
11
11
|
- scripts.produck:publish:check = npm run produck:install && npm run produck:format && npm run produck:lint && npm run produck:coverage
|
|
12
12
|
- scripts.produck:publish = npm run produck:publish:check && npm run publish --
|
|
13
|
-
|
|
13
|
+
- If scripts.publish is missing, defaults to "lerna publish"
|
|
14
14
|
- Enforces lerna.json command.version.commitHooks = false so publish/version commits skip git commit hooks
|
|
15
15
|
|
|
16
16
|
Rules:
|
|
@@ -26,6 +26,8 @@ const REQUIRED_PUBLISH_SCRIPT_VALUE = [
|
|
|
26
26
|
'npm run produck:publish:check',
|
|
27
27
|
'npm run publish --',
|
|
28
28
|
].join(' && ');
|
|
29
|
+
const REQUIRED_ROOT_PUBLISH_SCRIPT_KEY = 'publish';
|
|
30
|
+
const REQUIRED_ROOT_PUBLISH_SCRIPT_VALUE = 'lerna publish';
|
|
29
31
|
const REQUIRED_LERNA_VERSION_COMMIT_HOOKS = false;
|
|
30
32
|
|
|
31
33
|
export function printSyncPublishHelp() {
|
|
@@ -175,31 +177,49 @@ export function runSyncPublish(options) {
|
|
|
175
177
|
typeof scripts[REQUIRED_PUBLISH_SCRIPT_KEY] === 'string'
|
|
176
178
|
? scripts[REQUIRED_PUBLISH_SCRIPT_KEY]
|
|
177
179
|
: null;
|
|
180
|
+
const previousRootPublish =
|
|
181
|
+
typeof scripts[REQUIRED_ROOT_PUBLISH_SCRIPT_KEY] === 'string' &&
|
|
182
|
+
scripts[REQUIRED_ROOT_PUBLISH_SCRIPT_KEY].trim() !== ''
|
|
183
|
+
? scripts[REQUIRED_ROOT_PUBLISH_SCRIPT_KEY]
|
|
184
|
+
: null;
|
|
178
185
|
|
|
179
186
|
const matchesRequiredPublishCheck =
|
|
180
187
|
previousPublishCheck === REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE;
|
|
181
188
|
const matchesRequiredPublish =
|
|
182
189
|
previousPublish === REQUIRED_PUBLISH_SCRIPT_VALUE;
|
|
190
|
+
const hasRequiredRootPublishBefore = previousRootPublish !== null;
|
|
183
191
|
const lernaRequiresCreation = !lernaExistedBefore && !lernaDefaultCreated;
|
|
184
192
|
const requiresUpdate =
|
|
185
193
|
!matchesRequiredPublishCheck ||
|
|
186
194
|
!matchesRequiredPublish ||
|
|
187
195
|
lernaRequiresCreation ||
|
|
188
|
-
!matchesRequiredLernaCommitHooks
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
!matchesRequiredLernaCommitHooks ||
|
|
197
|
+
!hasRequiredRootPublishBefore;
|
|
198
|
+
|
|
199
|
+
if (mode === 'sync') {
|
|
200
|
+
let needsWrite = false;
|
|
201
|
+
|
|
202
|
+
if (!matchesRequiredPublishCheck || !matchesRequiredPublish) {
|
|
203
|
+
scripts[REQUIRED_PUBLISH_CHECK_SCRIPT_KEY] =
|
|
204
|
+
REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE;
|
|
205
|
+
scripts[REQUIRED_PUBLISH_SCRIPT_KEY] = REQUIRED_PUBLISH_SCRIPT_VALUE;
|
|
206
|
+
needsWrite = true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!hasRequiredRootPublishBefore) {
|
|
210
|
+
scripts[REQUIRED_ROOT_PUBLISH_SCRIPT_KEY] =
|
|
211
|
+
REQUIRED_ROOT_PUBLISH_SCRIPT_VALUE;
|
|
212
|
+
needsWrite = true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (needsWrite) {
|
|
216
|
+
pkg.scripts = scripts;
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
rootPackageJsonPath,
|
|
219
|
+
`${JSON.stringify(pkg, null, 2)}\n`,
|
|
220
|
+
'utf8',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
const report = {
|
|
@@ -232,6 +252,9 @@ export function runSyncPublish(options) {
|
|
|
232
252
|
!matchesRequiredPublish && mode === 'sync'
|
|
233
253
|
? true
|
|
234
254
|
: matchesRequiredPublish,
|
|
255
|
+
hasRequiredRootPublishBefore,
|
|
256
|
+
hasRequiredRootPublishAfter:
|
|
257
|
+
mode === 'sync' ? true : hasRequiredRootPublishBefore,
|
|
235
258
|
updated: requiresUpdate && mode === 'sync',
|
|
236
259
|
},
|
|
237
260
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit sync-workspace [--cwd <dir>]
|
|
3
|
+
[--workspace <path>] ... [--check] [--dry-run] [--json <file>]
|
|
4
|
+
|
|
5
|
+
Behavior:
|
|
6
|
+
- Adds or updates scripts.produck:coverage in each workspace
|
|
7
|
+
package.json.
|
|
8
|
+
- Enforces scripts.test in each workspace package.json.
|
|
9
|
+
- If scripts.test is missing, generates:
|
|
10
|
+
node -e "console.log('No tests configured')"
|
|
11
|
+
- The produck:coverage script in workspaces relies on root-level
|
|
12
|
+
c8 devDependency (hoisted by npm workspaces).
|
|
13
|
+
- Organization-reserved script key is scripts.produck:coverage
|
|
14
|
+
|
|
15
|
+
Rules:
|
|
16
|
+
- When --workspace is omitted, root package.json workspaces are used
|
|
17
|
+
- Single-level glob patterns (e.g. packages/*) are expanded automatically
|
|
18
|
+
- Recursive glob patterns (e.g. packages/**) are rejected
|
|
19
|
+
- Workspace packages must not duplicate root devDependencies;
|
|
20
|
+
c8 belongs in root package.json only
|
|
21
|
+
- --check validates without writing and exits non-zero on mismatch
|
|
22
|
+
- --dry-run prints planned changes without writing
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getMulti, getSingle, hasFlag } from '../shared/args.mjs';
|
|
6
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
7
|
+
|
|
8
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
10
|
+
const PACKAGE_ROOT = path.resolve(COMMAND_DIR, '../../..');
|
|
11
|
+
const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
|
|
12
|
+
const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
13
|
+
path.resolve(
|
|
14
|
+
REPO_ROOT,
|
|
15
|
+
'.github/distribution/produck/tooling-version-baseline.json',
|
|
16
|
+
),
|
|
17
|
+
path.resolve(
|
|
18
|
+
PACKAGE_ROOT,
|
|
19
|
+
'publish-assets/instructions/produck/tooling-version-baseline.json',
|
|
20
|
+
),
|
|
21
|
+
];
|
|
22
|
+
const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
|
|
23
|
+
const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
24
|
+
const REQUIRED_TEST_SCRIPT_KEY = 'test';
|
|
25
|
+
const DEFAULT_TEST_SCRIPT_VALUE =
|
|
26
|
+
'node -e "console.log(\'No tests configured\')"';
|
|
27
|
+
|
|
28
|
+
export function printSyncWorkspaceHelp() {
|
|
29
|
+
printTextResource(HELP_FILE);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseJsonFile(filePath, label) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
35
|
+
} catch {
|
|
36
|
+
console.error(`${label} is not valid JSON: ${filePath}`);
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveSemverExact(text) {
|
|
42
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveToolVersionFromDevDeps(baseline, toolName) {
|
|
46
|
+
const baselineVersion = String(
|
|
47
|
+
baseline?.tools?.[toolName]?.version || '',
|
|
48
|
+
).trim();
|
|
49
|
+
if (baselineVersion && baselineVersion !== 'auto') {
|
|
50
|
+
return baselineVersion;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
54
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
55
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
56
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
57
|
+
const dep = pkg?.devDependencies?.[toolName];
|
|
58
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
59
|
+
return resolveSemverExact(dep);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function loadToolingBaseline() {
|
|
67
|
+
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
|
|
68
|
+
(candidatePath) => fs.existsSync(candidatePath),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!toolingBaselinePath) {
|
|
72
|
+
console.error(
|
|
73
|
+
'Tooling baseline file does not exist in expected locations:',
|
|
74
|
+
);
|
|
75
|
+
for (const candidatePath of TOOLING_BASELINE_CANDIDATE_PATHS) {
|
|
76
|
+
console.error(`- ${candidatePath}`);
|
|
77
|
+
}
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
82
|
+
if (typeof baseline.schemaVersion !== 'number') {
|
|
83
|
+
console.error(
|
|
84
|
+
`Tooling baseline schemaVersion must be a number: ${toolingBaselinePath}`,
|
|
85
|
+
);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const c8Version = resolveToolVersionFromDevDeps(baseline, 'c8');
|
|
90
|
+
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
91
|
+
console.error(
|
|
92
|
+
`Tooling baseline tools.c8.version must be a non-empty string: ${toolingBaselinePath}`,
|
|
93
|
+
);
|
|
94
|
+
process.exit(2);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const coverageTemplate = baseline?.coverage?.scriptTemplate;
|
|
98
|
+
if (typeof coverageTemplate !== 'string' || coverageTemplate.trim() === '') {
|
|
99
|
+
console.error(
|
|
100
|
+
`Tooling baseline coverage.scriptTemplate must be a non-empty string: ${toolingBaselinePath}`,
|
|
101
|
+
);
|
|
102
|
+
process.exit(2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { baseline, toolingBaselinePath };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildRequiredCoverageScript(baseline) {
|
|
109
|
+
const c8Version = String(baseline.tools.c8.version);
|
|
110
|
+
const coverageTemplate = String(baseline.coverage.scriptTemplate);
|
|
111
|
+
return coverageTemplate.replace(/\{c8\.version\}/g, c8Version);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function expandGlobPatterns(cwd, entries) {
|
|
115
|
+
const result = [];
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
// Reject recursive glob patterns (**) as too dangerous to auto-expand
|
|
118
|
+
if (entry.includes('**')) {
|
|
119
|
+
console.error(
|
|
120
|
+
`Recursive glob pattern '${entry}' is not allowed in workspaces. ` +
|
|
121
|
+
'Use explicit paths or single-level globs (e.g. packages/*).',
|
|
122
|
+
);
|
|
123
|
+
process.exit(2);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const starIndex = entry.indexOf('*');
|
|
127
|
+
if (starIndex === -1) {
|
|
128
|
+
result.push(entry);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// entry is like "packages/*" → split at first *
|
|
133
|
+
const baseDir = entry.slice(0, starIndex);
|
|
134
|
+
const basePath = path.resolve(cwd, baseDir);
|
|
135
|
+
|
|
136
|
+
if (!fs.existsSync(basePath)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const dirEntries = fs.readdirSync(basePath, { withFileTypes: true });
|
|
141
|
+
const remainder = entry.slice(starIndex + 1);
|
|
142
|
+
|
|
143
|
+
for (const dirent of dirEntries) {
|
|
144
|
+
if (dirent.isDirectory()) {
|
|
145
|
+
const subpath = `${baseDir}${dirent.name}${remainder}`;
|
|
146
|
+
result.push(subpath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result.sort();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function resolveWorkspacePaths(cwd, options) {
|
|
154
|
+
const manual = getMulti(options, '--workspace');
|
|
155
|
+
if (manual.length > 0) {
|
|
156
|
+
return manual;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const rootPackageJsonPath = path.resolve(cwd, 'package.json');
|
|
160
|
+
const rootPackageJson = parseJsonFile(
|
|
161
|
+
rootPackageJsonPath,
|
|
162
|
+
'Root package.json',
|
|
163
|
+
);
|
|
164
|
+
if (!Array.isArray(rootPackageJson.workspaces)) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const raw = rootPackageJson.workspaces.map((entry) => String(entry));
|
|
169
|
+
if (raw.length === 0) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hasGlob = raw.some((entry) => GLOB_TOKEN_PATTERN.test(entry));
|
|
174
|
+
if (hasGlob) {
|
|
175
|
+
return expandGlobPatterns(cwd, raw);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return raw;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function reconcileWorkspace(cwd, workspacePath, mode, requiredCoverageScript) {
|
|
182
|
+
const packageDir = path.resolve(cwd, workspacePath);
|
|
183
|
+
const packageJsonPath = path.resolve(packageDir, 'package.json');
|
|
184
|
+
|
|
185
|
+
const result = {
|
|
186
|
+
workspacePath,
|
|
187
|
+
packageDir,
|
|
188
|
+
packageJsonPath,
|
|
189
|
+
exists: false,
|
|
190
|
+
validJson: false,
|
|
191
|
+
previousCoverage: null,
|
|
192
|
+
coverageScript: null,
|
|
193
|
+
previousTestScript: null,
|
|
194
|
+
testScript: null,
|
|
195
|
+
matchesRequiredCoverageBefore: false,
|
|
196
|
+
matchesRequiredCoverageAfter: false,
|
|
197
|
+
hasRequiredTestScriptBefore: false,
|
|
198
|
+
hasRequiredTestScriptAfter: false,
|
|
199
|
+
updated: false,
|
|
200
|
+
error: '',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
204
|
+
result.error = `Workspace package.json does not exist: ${workspacePath}`;
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
result.exists = true;
|
|
208
|
+
|
|
209
|
+
let pkg;
|
|
210
|
+
try {
|
|
211
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
212
|
+
result.validJson = true;
|
|
213
|
+
} catch {
|
|
214
|
+
result.error = `Workspace package.json is not valid JSON: ${workspacePath}`;
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const scripts =
|
|
219
|
+
pkg.scripts &&
|
|
220
|
+
typeof pkg.scripts === 'object' &&
|
|
221
|
+
!Array.isArray(pkg.scripts)
|
|
222
|
+
? { ...pkg.scripts }
|
|
223
|
+
: {};
|
|
224
|
+
|
|
225
|
+
const previousCoverage =
|
|
226
|
+
typeof scripts[REQUIRED_COVERAGE_SCRIPT_KEY] === 'string'
|
|
227
|
+
? scripts[REQUIRED_COVERAGE_SCRIPT_KEY]
|
|
228
|
+
: null;
|
|
229
|
+
const previousTestScript =
|
|
230
|
+
typeof scripts[REQUIRED_TEST_SCRIPT_KEY] === 'string' &&
|
|
231
|
+
scripts[REQUIRED_TEST_SCRIPT_KEY].trim() !== ''
|
|
232
|
+
? scripts[REQUIRED_TEST_SCRIPT_KEY]
|
|
233
|
+
: null;
|
|
234
|
+
result.previousCoverage = previousCoverage;
|
|
235
|
+
result.previousTestScript = previousTestScript;
|
|
236
|
+
result.matchesRequiredCoverageBefore =
|
|
237
|
+
previousCoverage === requiredCoverageScript;
|
|
238
|
+
result.hasRequiredTestScriptBefore = previousTestScript !== null;
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
(!result.matchesRequiredCoverageBefore ||
|
|
242
|
+
!result.hasRequiredTestScriptBefore) &&
|
|
243
|
+
mode === 'sync'
|
|
244
|
+
) {
|
|
245
|
+
scripts[REQUIRED_COVERAGE_SCRIPT_KEY] = requiredCoverageScript;
|
|
246
|
+
if (!result.hasRequiredTestScriptBefore) {
|
|
247
|
+
scripts[REQUIRED_TEST_SCRIPT_KEY] = DEFAULT_TEST_SCRIPT_VALUE;
|
|
248
|
+
}
|
|
249
|
+
pkg.scripts = scripts;
|
|
250
|
+
fs.writeFileSync(
|
|
251
|
+
packageJsonPath,
|
|
252
|
+
`${JSON.stringify(pkg, null, 2)}\n`,
|
|
253
|
+
'utf8',
|
|
254
|
+
);
|
|
255
|
+
result.updated = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
result.coverageScript =
|
|
259
|
+
mode === 'sync' && !result.matchesRequiredCoverageBefore
|
|
260
|
+
? requiredCoverageScript
|
|
261
|
+
: previousCoverage;
|
|
262
|
+
result.testScript =
|
|
263
|
+
mode === 'sync' && !result.hasRequiredTestScriptBefore
|
|
264
|
+
? DEFAULT_TEST_SCRIPT_VALUE
|
|
265
|
+
: previousTestScript;
|
|
266
|
+
|
|
267
|
+
result.matchesRequiredCoverageAfter =
|
|
268
|
+
result.updated || result.matchesRequiredCoverageBefore;
|
|
269
|
+
result.hasRequiredTestScriptAfter =
|
|
270
|
+
(mode === 'sync' && !result.hasRequiredTestScriptBefore) ||
|
|
271
|
+
result.hasRequiredTestScriptBefore;
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function runSyncWorkspace(options) {
|
|
276
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
277
|
+
const check = hasFlag(options, '--check');
|
|
278
|
+
const dryRun = hasFlag(options, '--dry-run');
|
|
279
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
280
|
+
const { baseline: toolingBaseline, toolingBaselinePath } =
|
|
281
|
+
loadToolingBaseline();
|
|
282
|
+
const requiredCoverageScript = buildRequiredCoverageScript(toolingBaseline);
|
|
283
|
+
|
|
284
|
+
if (!fs.existsSync(cwd)) {
|
|
285
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
286
|
+
process.exit(2);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const mode = dryRun ? 'dry-run' : check ? 'check' : 'sync';
|
|
290
|
+
const workspacePaths = resolveWorkspacePaths(cwd, options);
|
|
291
|
+
|
|
292
|
+
const report = {
|
|
293
|
+
cwd,
|
|
294
|
+
mode,
|
|
295
|
+
toolingBaselinePath,
|
|
296
|
+
toolingBaseline: {
|
|
297
|
+
schemaVersion: toolingBaseline.schemaVersion,
|
|
298
|
+
c8Version: toolingBaseline.tools.c8.version,
|
|
299
|
+
},
|
|
300
|
+
requiredCoverageScript,
|
|
301
|
+
requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
|
|
302
|
+
workspaces: workspacePaths,
|
|
303
|
+
results: [],
|
|
304
|
+
ok: true,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
for (const workspacePath of workspacePaths) {
|
|
308
|
+
const effectiveMode = mode === 'sync' ? 'sync' : 'check';
|
|
309
|
+
const item = reconcileWorkspace(
|
|
310
|
+
cwd,
|
|
311
|
+
workspacePath,
|
|
312
|
+
effectiveMode,
|
|
313
|
+
requiredCoverageScript,
|
|
314
|
+
);
|
|
315
|
+
report.results.push(item);
|
|
316
|
+
|
|
317
|
+
if (item.error) {
|
|
318
|
+
report.ok = false;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
mode === 'check' &&
|
|
324
|
+
(!item.matchesRequiredCoverageAfter || !item.hasRequiredTestScriptAfter)
|
|
325
|
+
) {
|
|
326
|
+
report.ok = false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (jsonFile) {
|
|
331
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
332
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
333
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
337
|
+
if (!report.ok) {
|
|
338
|
+
process.exit(2);
|
|
339
|
+
}
|
|
340
|
+
}
|
package/package.json
CHANGED
package/publish-assets/gitignore
CHANGED
|
@@ -51,18 +51,28 @@ The Produck monorepo provides unified configuration across all packages for cons
|
|
|
51
51
|
|
|
52
52
|
**Decision rule:**
|
|
53
53
|
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
Root-level `tsconfig.json` is **not** recommended for downstream repositories.
|
|
55
|
+
Prefer per-package `tsconfig.json` that stands alone.
|
|
56
|
+
|
|
57
|
+
- Do **not** create/deploy root `tsconfig.json` unless there is a clear,
|
|
58
|
+
unavoidable need for centralized TypeScript configuration (e.g., a shared
|
|
59
|
+
path alias map or compound project references).
|
|
60
|
+
- Each TypeScript package should manage its own `tsconfig.json` independently.
|
|
61
|
+
- Only introduce root `tsconfig.json` when per-package duplication becomes a
|
|
62
|
+
proven maintenance burden — not as a proactive measure.
|
|
58
63
|
|
|
59
64
|
**Governance (enforced by `agent-toolkit sync-typescript`):**
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
When root `tsconfig.json` exists, run
|
|
67
|
+
`agent-toolkit sync-typescript --package-root packages/<name> --cwd .`
|
|
62
68
|
to ensure a sub-package has a `tsconfig.json` that extends the root with the
|
|
63
69
|
correct relative path and uses standard compiler options. If the file already
|
|
64
70
|
exists, it is skipped without modification.
|
|
65
71
|
|
|
72
|
+
If no root `tsconfig.json` exists (the recommended default), each TypeScript
|
|
73
|
+
package maintains its own standalone `tsconfig.json` with its own compiler
|
|
74
|
+
options — no `extends` chain is needed.
|
|
75
|
+
|
|
66
76
|
**Enforced settings:**
|
|
67
77
|
|
|
68
78
|
- Target: ES2022
|
|
@@ -95,7 +105,7 @@ exists, it is skipped without modification.
|
|
|
95
105
|
|
|
96
106
|
**Rules:**
|
|
97
107
|
|
|
98
|
-
- Print width:
|
|
108
|
+
- Print width: 80 characters
|
|
99
109
|
- Tab width: 2 spaces
|
|
100
110
|
- Single quotes: true
|
|
101
111
|
- Trailing commas: es5
|
|
@@ -185,6 +195,33 @@ For TypeScript configuration, packages may extend root `tsconfig.json` (see
|
|
|
185
195
|
[TypeScript Configuration](#2-typescript-configuration-tsconfigjson-conditional)
|
|
186
196
|
above).
|
|
187
197
|
|
|
198
|
+
### Dependency Management in Downstream Repositories
|
|
199
|
+
|
|
200
|
+
When a monorepo policy is distributed to a downstream repository, sub-package
|
|
201
|
+
`devDependencies` must **not** duplicate workspace-level (root) devDependencies.
|
|
202
|
+
|
|
203
|
+
**Rationale:**
|
|
204
|
+
|
|
205
|
+
- Avoids version conflicts between root and sub-package declarations
|
|
206
|
+
- Eliminates ambiguity about which declaration is the source of truth
|
|
207
|
+
- Keeps sub-packages version-stable — root-level upgrades apply uniformly,
|
|
208
|
+
preventing drift across packages
|
|
209
|
+
- Reduces `npm install` deduplication overhead and `lockfile` churn
|
|
210
|
+
|
|
211
|
+
**Rule:**
|
|
212
|
+
|
|
213
|
+
- Shared tooling (ESLint, Prettier, TypeScript, c8, test runners, build tools)
|
|
214
|
+
belongs in root `devDependencies` only.
|
|
215
|
+
- Sub-packages may list only **package-specific** devDependencies that are not
|
|
216
|
+
already declared at root level.
|
|
217
|
+
- In the downstream repository, sub-packages that extend root configs (e.g.,
|
|
218
|
+
`extends` in `tsconfig.json` or `eslint.config.mjs`) inherit their tooling
|
|
219
|
+
from root and must **not** redeclare those tools in their own
|
|
220
|
+
`devDependencies`.
|
|
221
|
+
- When a downstream sub-package needs a different version of a root-level tool,
|
|
222
|
+
use root-level overrides (e.g., `overrides` or `resolutions` in root
|
|
223
|
+
`package.json`) rather than redeclaring in the sub-package.
|
|
224
|
+
|
|
188
225
|
## .editorconfig
|
|
189
226
|
|
|
190
227
|
**Location:** Root `.editorconfig`
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@eslint/json": {
|
|
28
28
|
"allowLatest": false,
|
|
29
29
|
"policy": "pinned",
|
|
30
|
-
"version": "
|
|
30
|
+
"version": "2.0.0"
|
|
31
31
|
},
|
|
32
32
|
"@eslint/markdown": {
|
|
33
33
|
"allowLatest": false,
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@types/node": {
|
|
38
38
|
"allowLatest": false,
|
|
39
39
|
"policy": "pinned",
|
|
40
|
-
"version": "
|
|
40
|
+
"version": "24.13.2"
|
|
41
41
|
},
|
|
42
42
|
"c8": {
|
|
43
43
|
"allowLatest": false,
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"eslint": {
|
|
48
48
|
"allowLatest": false,
|
|
49
49
|
"policy": "pinned",
|
|
50
|
-
"version": "10.
|
|
50
|
+
"version": "10.6.0"
|
|
51
51
|
},
|
|
52
52
|
"globals": {
|
|
53
53
|
"allowLatest": false,
|
|
54
54
|
"policy": "pinned",
|
|
55
|
-
"version": "17.
|
|
55
|
+
"version": "17.7.0"
|
|
56
56
|
},
|
|
57
57
|
"husky": {
|
|
58
58
|
"allowLatest": false,
|
|
@@ -67,12 +67,12 @@
|
|
|
67
67
|
"prettier": {
|
|
68
68
|
"allowLatest": false,
|
|
69
69
|
"policy": "pinned",
|
|
70
|
-
"version": "3.
|
|
70
|
+
"version": "3.9.1"
|
|
71
71
|
},
|
|
72
72
|
"typescript-eslint": {
|
|
73
73
|
"allowLatest": false,
|
|
74
74
|
"policy": "pinned",
|
|
75
|
-
"version": "8.
|
|
75
|
+
"version": "8.62.0"
|
|
76
76
|
},
|
|
77
77
|
"@produck/eslint-rules": {
|
|
78
78
|
"version": "0.4.1",
|