@produck/agent-toolkit 0.9.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- if (
191
- mode === 'sync' &&
192
- (!matchesRequiredPublishCheck || !matchesRequiredPublish)
193
- ) {
194
- scripts[REQUIRED_PUBLISH_CHECK_SCRIPT_KEY] =
195
- REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE;
196
- scripts[REQUIRED_PUBLISH_SCRIPT_KEY] = REQUIRED_PUBLISH_SCRIPT_VALUE;
197
- pkg.scripts = scripts;
198
- fs.writeFileSync(
199
- rootPackageJsonPath,
200
- `${JSON.stringify(pkg, null, 2)}\n`,
201
- 'utf8',
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,14 @@
1
+ Usage:
2
+ agent-toolkit sync-typescript --package-root <pkg> [--cwd <dir>]
3
+ [--check] [--dry-run] [--json <file>]
4
+
5
+ Behavior:
6
+ - Creates a tsconfig.json for a sub-package that extends the root
7
+ tsconfig.json with the correct relative path
8
+ - If tsconfig.json already exists, skips without modifying
9
+
10
+ Rules:
11
+ - --package-root is required
12
+ - --check validates without writing and exits non-zero if file is missing
13
+ - --dry-run prints planned changes without writing
14
+ - --check takes precedence over --dry-run
@@ -0,0 +1,154 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { 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 TSCONFIG_FILE = 'tsconfig.json';
11
+
12
+ const PACKAGE_TSCONFIG_TEMPLATE = {
13
+ compilerOptions: {
14
+ lib: ['ESNext'],
15
+ types: ['node'],
16
+ strictNullChecks: true,
17
+ allowJs: true,
18
+ noEmit: true,
19
+ module: 'NodeNext',
20
+ },
21
+ };
22
+
23
+ export function printSyncTypescriptHelp() {
24
+ printTextResource(HELP_FILE);
25
+ }
26
+
27
+ function readJsonIfExists(filePath) {
28
+ if (!fs.existsSync(filePath)) {
29
+ return null;
30
+ }
31
+ try {
32
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ function computeExtendsPath(packageRoot, repoRoot) {
39
+ const relative = path.relative(packageRoot, repoRoot);
40
+ const normalized = relative.replace(/\\/g, '/');
41
+ if (!normalized || normalized === '.') {
42
+ return './tsconfig.json';
43
+ }
44
+ return normalized.startsWith('..')
45
+ ? `${normalized}/tsconfig.json`
46
+ : `./${normalized}/tsconfig.json`;
47
+ }
48
+
49
+ export function runSyncTypescript(options) {
50
+ const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
51
+ const check = hasFlag(options, '--check');
52
+ const dryRun = hasFlag(options, '--dry-run') && !check;
53
+ const jsonFile = getSingle(options, '--json', '');
54
+ const packageRoot = getSingle(options, '--package-root', '');
55
+ const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
56
+
57
+ if (!packageRoot) {
58
+ console.error('--package-root is required');
59
+ process.exit(2);
60
+ }
61
+
62
+ if (!fs.existsSync(cwd)) {
63
+ console.error(`CWD does not exist: ${cwd}`);
64
+ process.exit(2);
65
+ }
66
+
67
+ const pkgDir = path.resolve(cwd, packageRoot);
68
+ if (!fs.existsSync(pkgDir)) {
69
+ console.error(`Package root does not exist: ${pkgDir}`);
70
+ process.exit(2);
71
+ }
72
+
73
+ const tsconfigPath = path.resolve(pkgDir, TSCONFIG_FILE);
74
+ const current = readJsonIfExists(tsconfigPath);
75
+ const fileExists = current !== null;
76
+
77
+ // If tsconfig.json already exists, skip without checking content
78
+ if (fileExists) {
79
+ const report = {
80
+ cwd,
81
+ mode,
82
+ ok: true,
83
+ tsconfigPath,
84
+ required: {
85
+ file: path.join(packageRoot, TSCONFIG_FILE),
86
+ },
87
+ status: {
88
+ fileExistsBefore: true,
89
+ mismatchesBefore: [],
90
+ fileExistsAfter: true,
91
+ mismatchesAfter: [],
92
+ updated: false,
93
+ skipped: true,
94
+ },
95
+ };
96
+
97
+ if (jsonFile) {
98
+ const outPath = path.resolve(cwd, jsonFile);
99
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
100
+ fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
101
+ }
102
+
103
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
104
+ return;
105
+ }
106
+
107
+ const expectedExtends = computeExtendsPath(pkgDir, cwd);
108
+ const expectedPkgConfig = {
109
+ extends: expectedExtends,
110
+ ...PACKAGE_TSCONFIG_TEMPLATE,
111
+ };
112
+
113
+ const plannedContent = `${JSON.stringify(expectedPkgConfig, null, 2)}\n`;
114
+
115
+ if (mode === 'sync') {
116
+ fs.writeFileSync(tsconfigPath, plannedContent, 'utf8');
117
+ }
118
+
119
+ const mismatches = [
120
+ {
121
+ file: path.join(packageRoot, TSCONFIG_FILE),
122
+ expected: JSON.stringify(expectedPkgConfig, null, 2),
123
+ actual: 'missing',
124
+ },
125
+ ];
126
+
127
+ const report = {
128
+ cwd,
129
+ mode,
130
+ ok: mode !== 'check',
131
+ tsconfigPath,
132
+ required: {
133
+ file: path.join(packageRoot, TSCONFIG_FILE),
134
+ },
135
+ status: {
136
+ fileExistsBefore: false,
137
+ mismatchesBefore: mismatches,
138
+ fileExistsAfter: mode === 'sync',
139
+ mismatchesAfter: mode === 'sync' ? [] : mismatches,
140
+ updated: mode === 'sync',
141
+ },
142
+ };
143
+
144
+ if (jsonFile) {
145
+ const outPath = path.resolve(cwd, jsonFile);
146
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
147
+ fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
148
+ }
149
+
150
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
151
+ if (!report.ok) {
152
+ process.exit(2);
153
+ }
154
+ }
@@ -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 and devDependencies.c8
7
+ in each workspace 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 is for local and AI
12
+ development use only.
13
+ - Organization-reserved script key is scripts.produck:coverage
14
+ - c8 version is resolved from organization tooling baseline file
15
+
16
+ Rules:
17
+ - When --workspace is omitted, root package.json workspaces are used
18
+ - Single-level glob patterns (e.g. packages/*) are expanded automatically
19
+ - Recursive glob patterns (e.g. packages/**) are rejected
20
+ - Workspace package.json files must pin devDependencies.c8 to baseline version
21
+ - --check validates without writing and exits non-zero on mismatch
22
+ - --dry-run prints planned changes without writing
@@ -0,0 +1,379 @@
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 buildRequiredC8DevDependency(baseline) {
115
+ return String(baseline.tools.c8.version);
116
+ }
117
+
118
+ function expandGlobPatterns(cwd, entries) {
119
+ const result = [];
120
+ for (const entry of entries) {
121
+ // Reject recursive glob patterns (**) as too dangerous to auto-expand
122
+ if (entry.includes('**')) {
123
+ console.error(
124
+ `Recursive glob pattern '${entry}' is not allowed in workspaces. ` +
125
+ 'Use explicit paths or single-level globs (e.g. packages/*).',
126
+ );
127
+ process.exit(2);
128
+ }
129
+
130
+ const starIndex = entry.indexOf('*');
131
+ if (starIndex === -1) {
132
+ result.push(entry);
133
+ continue;
134
+ }
135
+
136
+ // entry is like "packages/*" → split at first *
137
+ const baseDir = entry.slice(0, starIndex);
138
+ const basePath = path.resolve(cwd, baseDir);
139
+
140
+ if (!fs.existsSync(basePath)) {
141
+ continue;
142
+ }
143
+
144
+ const dirEntries = fs.readdirSync(basePath, { withFileTypes: true });
145
+ const remainder = entry.slice(starIndex + 1);
146
+
147
+ for (const dirent of dirEntries) {
148
+ if (dirent.isDirectory()) {
149
+ const subpath = `${baseDir}${dirent.name}${remainder}`;
150
+ result.push(subpath);
151
+ }
152
+ }
153
+ }
154
+ return result.sort();
155
+ }
156
+
157
+ function resolveWorkspacePaths(cwd, options) {
158
+ const manual = getMulti(options, '--workspace');
159
+ if (manual.length > 0) {
160
+ return manual;
161
+ }
162
+
163
+ const rootPackageJsonPath = path.resolve(cwd, 'package.json');
164
+ const rootPackageJson = parseJsonFile(
165
+ rootPackageJsonPath,
166
+ 'Root package.json',
167
+ );
168
+ if (!Array.isArray(rootPackageJson.workspaces)) {
169
+ return [];
170
+ }
171
+
172
+ const raw = rootPackageJson.workspaces.map((entry) => String(entry));
173
+ if (raw.length === 0) {
174
+ return [];
175
+ }
176
+
177
+ const hasGlob = raw.some((entry) => GLOB_TOKEN_PATTERN.test(entry));
178
+ if (hasGlob) {
179
+ return expandGlobPatterns(cwd, raw);
180
+ }
181
+
182
+ return raw;
183
+ }
184
+
185
+ function reconcileWorkspace(
186
+ cwd,
187
+ workspacePath,
188
+ mode,
189
+ requiredCoverageScript,
190
+ requiredC8Version,
191
+ ) {
192
+ const packageDir = path.resolve(cwd, workspacePath);
193
+ const packageJsonPath = path.resolve(packageDir, 'package.json');
194
+
195
+ const result = {
196
+ workspacePath,
197
+ packageDir,
198
+ packageJsonPath,
199
+ exists: false,
200
+ validJson: false,
201
+ previousCoverage: null,
202
+ coverageScript: null,
203
+ previousTestScript: null,
204
+ testScript: null,
205
+ previousC8DevDependency: null,
206
+ c8DevDependency: null,
207
+ matchesRequiredCoverageBefore: false,
208
+ matchesRequiredCoverageAfter: false,
209
+ hasRequiredTestScriptBefore: false,
210
+ hasRequiredTestScriptAfter: false,
211
+ matchesRequiredC8DevDependencyBefore: false,
212
+ matchesRequiredC8DevDependencyAfter: false,
213
+ updated: false,
214
+ error: '',
215
+ };
216
+
217
+ if (!fs.existsSync(packageJsonPath)) {
218
+ result.error = `Workspace package.json does not exist: ${workspacePath}`;
219
+ return result;
220
+ }
221
+ result.exists = true;
222
+
223
+ let pkg;
224
+ try {
225
+ pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
226
+ result.validJson = true;
227
+ } catch {
228
+ result.error = `Workspace package.json is not valid JSON: ${workspacePath}`;
229
+ return result;
230
+ }
231
+
232
+ const scripts =
233
+ pkg.scripts &&
234
+ typeof pkg.scripts === 'object' &&
235
+ !Array.isArray(pkg.scripts)
236
+ ? { ...pkg.scripts }
237
+ : {};
238
+ const devDependencies =
239
+ pkg.devDependencies &&
240
+ typeof pkg.devDependencies === 'object' &&
241
+ !Array.isArray(pkg.devDependencies)
242
+ ? { ...pkg.devDependencies }
243
+ : {};
244
+
245
+ const previousCoverage =
246
+ typeof scripts[REQUIRED_COVERAGE_SCRIPT_KEY] === 'string'
247
+ ? scripts[REQUIRED_COVERAGE_SCRIPT_KEY]
248
+ : null;
249
+ const previousTestScript =
250
+ typeof scripts[REQUIRED_TEST_SCRIPT_KEY] === 'string' &&
251
+ scripts[REQUIRED_TEST_SCRIPT_KEY].trim() !== ''
252
+ ? scripts[REQUIRED_TEST_SCRIPT_KEY]
253
+ : null;
254
+ const previousC8DevDependency =
255
+ typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null;
256
+ result.previousCoverage = previousCoverage;
257
+ result.previousTestScript = previousTestScript;
258
+ result.previousC8DevDependency = previousC8DevDependency;
259
+ result.matchesRequiredCoverageBefore =
260
+ previousCoverage === requiredCoverageScript;
261
+ result.hasRequiredTestScriptBefore = previousTestScript !== null;
262
+ result.matchesRequiredC8DevDependencyBefore =
263
+ previousC8DevDependency === requiredC8Version;
264
+
265
+ if (
266
+ (!result.matchesRequiredCoverageBefore ||
267
+ !result.hasRequiredTestScriptBefore ||
268
+ !result.matchesRequiredC8DevDependencyBefore) &&
269
+ mode === 'sync'
270
+ ) {
271
+ scripts[REQUIRED_COVERAGE_SCRIPT_KEY] = requiredCoverageScript;
272
+ if (!result.hasRequiredTestScriptBefore) {
273
+ scripts[REQUIRED_TEST_SCRIPT_KEY] = DEFAULT_TEST_SCRIPT_VALUE;
274
+ }
275
+ devDependencies.c8 = requiredC8Version;
276
+ pkg.scripts = scripts;
277
+ pkg.devDependencies = devDependencies;
278
+ fs.writeFileSync(
279
+ packageJsonPath,
280
+ `${JSON.stringify(pkg, null, 2)}\n`,
281
+ 'utf8',
282
+ );
283
+ result.updated = true;
284
+ }
285
+
286
+ result.coverageScript =
287
+ mode === 'sync' && !result.matchesRequiredCoverageBefore
288
+ ? requiredCoverageScript
289
+ : previousCoverage;
290
+ result.testScript =
291
+ mode === 'sync' && !result.hasRequiredTestScriptBefore
292
+ ? DEFAULT_TEST_SCRIPT_VALUE
293
+ : previousTestScript;
294
+ result.c8DevDependency =
295
+ mode === 'sync' && !result.matchesRequiredC8DevDependencyBefore
296
+ ? requiredC8Version
297
+ : previousC8DevDependency;
298
+
299
+ result.matchesRequiredCoverageAfter =
300
+ result.updated || result.matchesRequiredCoverageBefore;
301
+ result.hasRequiredTestScriptAfter =
302
+ (mode === 'sync' && !result.hasRequiredTestScriptBefore) ||
303
+ result.hasRequiredTestScriptBefore;
304
+ result.matchesRequiredC8DevDependencyAfter =
305
+ result.updated || result.matchesRequiredC8DevDependencyBefore;
306
+ return result;
307
+ }
308
+
309
+ export function runSyncWorkspace(options) {
310
+ const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
311
+ const check = hasFlag(options, '--check');
312
+ const dryRun = hasFlag(options, '--dry-run');
313
+ const jsonFile = getSingle(options, '--json', '');
314
+ const { baseline: toolingBaseline, toolingBaselinePath } =
315
+ loadToolingBaseline();
316
+ const requiredCoverageScript = buildRequiredCoverageScript(toolingBaseline);
317
+ const requiredC8Version = buildRequiredC8DevDependency(toolingBaseline);
318
+
319
+ if (!fs.existsSync(cwd)) {
320
+ console.error(`CWD does not exist: ${cwd}`);
321
+ process.exit(2);
322
+ }
323
+
324
+ const mode = dryRun ? 'dry-run' : check ? 'check' : 'sync';
325
+ const workspacePaths = resolveWorkspacePaths(cwd, options);
326
+
327
+ const report = {
328
+ cwd,
329
+ mode,
330
+ toolingBaselinePath,
331
+ toolingBaseline: {
332
+ schemaVersion: toolingBaseline.schemaVersion,
333
+ c8Version: toolingBaseline.tools.c8.version,
334
+ },
335
+ requiredCoverageScript,
336
+ requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
337
+ requiredC8DevDependency: requiredC8Version,
338
+ workspaces: workspacePaths,
339
+ results: [],
340
+ ok: true,
341
+ };
342
+
343
+ for (const workspacePath of workspacePaths) {
344
+ const effectiveMode = mode === 'sync' ? 'sync' : 'check';
345
+ const item = reconcileWorkspace(
346
+ cwd,
347
+ workspacePath,
348
+ effectiveMode,
349
+ requiredCoverageScript,
350
+ requiredC8Version,
351
+ );
352
+ report.results.push(item);
353
+
354
+ if (item.error) {
355
+ report.ok = false;
356
+ continue;
357
+ }
358
+
359
+ if (
360
+ mode === 'check' &&
361
+ (!item.matchesRequiredCoverageAfter ||
362
+ !item.hasRequiredTestScriptAfter ||
363
+ !item.matchesRequiredC8DevDependencyAfter)
364
+ ) {
365
+ report.ok = false;
366
+ }
367
+ }
368
+
369
+ if (jsonFile) {
370
+ const outPath = path.resolve(cwd, jsonFile);
371
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
372
+ fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
373
+ }
374
+
375
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
376
+ if (!report.ok) {
377
+ process.exit(2);
378
+ }
379
+ }