@produck/agent-toolkit 0.5.0 → 0.7.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.
Files changed (34) hide show
  1. package/README.md +74 -43
  2. package/bin/agent-toolkit.mjs +33 -32
  3. package/bin/build-publish-assets.mjs +28 -0
  4. package/bin/command/enforce-node-baseline/help.txt +12 -10
  5. package/bin/command/enforce-node-baseline/index.mjs +31 -15
  6. package/bin/command/main/help.txt +7 -5
  7. package/bin/command/preflight/help.txt +1 -1
  8. package/bin/command/preflight/index.mjs +1 -1
  9. package/bin/command/{sync-coverage-script → sync-coverage}/help.txt +2 -1
  10. package/bin/command/{sync-coverage-script → sync-coverage}/index.mjs +116 -19
  11. package/bin/command/sync-editorconfig/editorconfig.template +15 -0
  12. package/bin/command/sync-editorconfig/help.txt +13 -0
  13. package/bin/command/sync-editorconfig/index.mjs +90 -0
  14. package/bin/command/{sync-prettier-config → sync-format}/help.txt +2 -2
  15. package/bin/command/{sync-prettier-config → sync-format}/index.mjs +63 -9
  16. package/bin/command/{sync-workspace-config → sync-git}/help.txt +10 -6
  17. package/bin/command/sync-git/index.mjs +424 -0
  18. package/bin/command/sync-install/help.txt +14 -0
  19. package/bin/command/sync-install/index.mjs +106 -0
  20. package/bin/command/{sync-eslint-config → sync-lint}/help.txt +2 -2
  21. package/bin/command/{sync-eslint-config → sync-lint}/index.mjs +3 -4
  22. package/bin/command/sync-publish/help.txt +18 -0
  23. package/bin/command/sync-publish/index.mjs +157 -0
  24. package/bin/command/validate-commit-msg/index.mjs +30 -2
  25. package/package.json +3 -5
  26. package/publish-assets/gitattributes +5 -0
  27. package/publish-assets/gitignore +137 -0
  28. package/publish-assets/instructions/produck/10-produck-node.instructions.md +53 -40
  29. package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +13 -16
  30. package/publish-assets/instructions/produck/20-produck-commit.instructions.md +2 -2
  31. package/publish-assets/instructions/produck/tooling-version-baseline.json +8 -1
  32. package/bin/command/sync-husky-hooks/help.txt +0 -14
  33. package/bin/command/sync-husky-hooks/index.mjs +0 -89
  34. package/bin/command/sync-workspace-config/index.mjs +0 -290
@@ -16,6 +16,11 @@
16
16
  "version": "9.0.7",
17
17
  "policy": "pinned",
18
18
  "allowLatest": false
19
+ },
20
+ "prettier": {
21
+ "version": "3.8.3",
22
+ "policy": "pinned",
23
+ "allowLatest": false
19
24
  }
20
25
  },
21
26
  "coverage": {
@@ -26,7 +31,9 @@
26
31
  "npx c8",
27
32
  "c8@latest",
28
33
  "npx lerna",
29
- "lerna@latest"
34
+ "lerna@latest",
35
+ "npx prettier",
36
+ "prettier@latest"
30
37
  ]
31
38
  }
32
39
  }
@@ -1,14 +0,0 @@
1
- Usage:
2
- agent-toolkit sync-husky-hooks [--cwd <dir>] [--check] [--dry-run]
3
- [--json <file>]
4
-
5
- Behavior:
6
- - Applies organization-required hook files only:
7
- - .husky/pre-commit
8
- - .husky/commit-msg
9
- - commit-msg hook validates message via local node_modules toolkit path
10
-
11
- Rules:
12
- - --check validates without writing and exits non-zero on mismatch
13
- - --dry-run prints planned changes without writing
14
- - --check takes precedence over --dry-run
@@ -1,89 +0,0 @@
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
-
11
- const REQUIRED_PRE_COMMIT_HOOK = '#!/usr/bin/env sh\nnpm run produck:precommit-check\n';
12
- const REQUIRED_COMMIT_MSG_HOOK =
13
- '#!/usr/bin/env sh\nnode ./node_modules/@produck/agent-toolkit/bin/agent-toolkit.mjs validate-commit-msg --file "$1"\n';
14
-
15
- export function printSyncHuskyHooksHelp() {
16
- printTextResource(HELP_FILE);
17
- }
18
-
19
- function readFileIfExists(filePath) {
20
- if (!fs.existsSync(filePath)) {
21
- return null;
22
- }
23
-
24
- return fs.readFileSync(filePath, 'utf8');
25
- }
26
-
27
- export function runSyncHuskyHooks(options) {
28
- const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
29
- const check = hasFlag(options, '--check');
30
- const dryRun = hasFlag(options, '--dry-run') && !check;
31
- const jsonFile = getSingle(options, '--json', '');
32
- const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
33
-
34
- if (!fs.existsSync(cwd)) {
35
- console.error(`CWD does not exist: ${cwd}`);
36
- process.exit(2);
37
- }
38
-
39
- const huskyDir = path.resolve(cwd, '.husky');
40
- const preCommitHookPath = path.resolve(huskyDir, 'pre-commit');
41
- const commitMsgHookPath = path.resolve(huskyDir, 'commit-msg');
42
-
43
- const previousPreCommitHook = readFileIfExists(preCommitHookPath);
44
- const previousCommitMsgHook = readFileIfExists(commitMsgHookPath);
45
-
46
- const matchesRequiredPreCommitHook = previousPreCommitHook === REQUIRED_PRE_COMMIT_HOOK;
47
- const matchesRequiredCommitMsgHook = previousCommitMsgHook === REQUIRED_COMMIT_MSG_HOOK;
48
- const requiresUpdate = !matchesRequiredPreCommitHook || !matchesRequiredCommitMsgHook;
49
-
50
- if (mode === 'sync' && requiresUpdate) {
51
- fs.mkdirSync(huskyDir, { recursive: true });
52
- fs.writeFileSync(preCommitHookPath, REQUIRED_PRE_COMMIT_HOOK, 'utf8');
53
- fs.writeFileSync(commitMsgHookPath, REQUIRED_COMMIT_MSG_HOOK, 'utf8');
54
- }
55
-
56
- const report = {
57
- cwd,
58
- mode,
59
- ok: true,
60
- required: {
61
- preCommitHookPath: path.relative(cwd, preCommitHookPath),
62
- commitMsgHookPath: path.relative(cwd, commitMsgHookPath),
63
- },
64
- status: {
65
- matchesRequiredPreCommitHookBefore: matchesRequiredPreCommitHook,
66
- matchesRequiredCommitMsgHookBefore: matchesRequiredCommitMsgHook,
67
- matchesRequiredPreCommitHookAfter:
68
- requiresUpdate && mode === 'sync' ? true : matchesRequiredPreCommitHook,
69
- matchesRequiredCommitMsgHookAfter:
70
- requiresUpdate && mode === 'sync' ? true : matchesRequiredCommitMsgHook,
71
- updated: requiresUpdate && mode === 'sync',
72
- },
73
- };
74
-
75
- if (mode === 'check' && requiresUpdate) {
76
- report.ok = false;
77
- }
78
-
79
- if (jsonFile) {
80
- const outPath = path.resolve(cwd, jsonFile);
81
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
82
- fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
83
- }
84
-
85
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
86
- if (!report.ok) {
87
- process.exit(2);
88
- }
89
- }
@@ -1,290 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { spawnSync } from 'node:child_process';
4
- import { fileURLToPath } from 'node:url';
5
-
6
- import { getSingle, hasFlag } from '../shared/args.mjs';
7
- import { printTextResource } from '../shared/text-resource.mjs';
8
- import { validateRequiredExactEntries } from '../shared/workspace-validation.mjs';
9
-
10
- const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
11
- const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
12
- const PACKAGE_ROOT = path.resolve(COMMAND_DIR, '../../..');
13
- const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
14
- const TOOLKIT_PACKAGE_JSON = path.resolve(PACKAGE_ROOT, 'package.json');
15
- const TOOLING_BASELINE_CANDIDATE_PATHS = [
16
- path.resolve(REPO_ROOT, '.github/distribution/produck/tooling-version-baseline.json'),
17
- path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck/tooling-version-baseline.json'),
18
- ];
19
-
20
- const REQUIRED_BASELINE_SCRIPT_KEY = 'produck:baseline';
21
- const REQUIRED_BASELINE_SCRIPT_VALUE =
22
- 'npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd .';
23
- const REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY = 'produck:coverage';
24
- const REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE =
25
- 'c8 --config .c8rc.json npm run test --workspaces --if-present';
26
- const REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY = 'produck:precommit-check';
27
- const REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE = 'npm run produck:format && npm run produck:lint';
28
- const REQUIRED_C8_CONFIG_FILE = '.c8rc.json';
29
- const REQUIRED_C8_CONFIG_CONTENT = `${JSON.stringify(
30
- {
31
- reporter: ['lcov', 'html', 'text-summary'],
32
- },
33
- null,
34
- 2,
35
- )}\n`;
36
-
37
- export function printSyncWorkspaceConfigHelp() {
38
- printTextResource(HELP_FILE);
39
- }
40
-
41
- function parseJsonFile(filePath, label) {
42
- try {
43
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
44
- } catch {
45
- console.error(`${label} is not valid JSON: ${filePath}`);
46
- process.exit(2);
47
- }
48
- }
49
-
50
- function getRequiredToolkitDevDependency() {
51
- const overrideVersion = String(process.env.PRODUCK_TOOLKIT_VERSION_OVERRIDE || '').trim();
52
- if (overrideVersion) {
53
- return overrideVersion;
54
- }
55
-
56
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
57
- const latestResult = spawnSync(npmCommand, ['view', '@produck/agent-toolkit', 'version'], {
58
- encoding: 'utf8',
59
- });
60
-
61
- const latestVersion = String(latestResult.stdout || '').trim();
62
- if (latestResult.status === 0 && latestVersion) {
63
- return latestVersion;
64
- }
65
-
66
- const pkg = parseJsonFile(TOOLKIT_PACKAGE_JSON, 'Toolkit package.json');
67
- const version = typeof pkg.version === 'string' ? pkg.version.trim() : '';
68
-
69
- if (!version) {
70
- console.error(`Toolkit package version is missing: ${TOOLKIT_PACKAGE_JSON}`);
71
- process.exit(2);
72
- }
73
-
74
- return version;
75
- }
76
-
77
- function loadToolingBaseline() {
78
- const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find((candidatePath) => {
79
- return fs.existsSync(candidatePath);
80
- });
81
-
82
- if (!toolingBaselinePath) {
83
- console.error('Tooling baseline file does not exist in expected locations:');
84
- for (const candidatePath of TOOLING_BASELINE_CANDIDATE_PATHS) {
85
- console.error(`- ${candidatePath}`);
86
- }
87
- process.exit(2);
88
- }
89
-
90
- const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
91
- const c8Version = String(baseline?.tools?.c8?.version || '').trim();
92
- const huskyVersion = String(baseline?.tools?.husky?.version || '').trim();
93
- const lernaVersion = String(baseline?.tools?.lerna?.version || '').trim();
94
-
95
- if (!c8Version || !huskyVersion || !lernaVersion) {
96
- console.error(
97
- `Tooling baseline must define fixed tools.c8/husky/lerna.version: ${toolingBaselinePath}`,
98
- );
99
- process.exit(2);
100
- }
101
-
102
- return {
103
- toolingBaselinePath,
104
- c8Version,
105
- huskyVersion,
106
- lernaVersion,
107
- };
108
- }
109
-
110
- function buildScriptState(pkg) {
111
- const scripts =
112
- pkg.scripts && typeof pkg.scripts === 'object' && !Array.isArray(pkg.scripts)
113
- ? { ...pkg.scripts }
114
- : {};
115
-
116
- return {
117
- scripts,
118
- previousBaseline:
119
- typeof scripts[REQUIRED_BASELINE_SCRIPT_KEY] === 'string'
120
- ? scripts[REQUIRED_BASELINE_SCRIPT_KEY]
121
- : null,
122
- previousCoverage:
123
- typeof scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY] === 'string'
124
- ? scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY]
125
- : null,
126
- previousPrecommitCheck:
127
- typeof scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY] === 'string'
128
- ? scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY]
129
- : null,
130
- };
131
- }
132
-
133
- function readFileIfExists(filePath) {
134
- if (!fs.existsSync(filePath)) {
135
- return null;
136
- }
137
-
138
- return fs.readFileSync(filePath, 'utf8');
139
- }
140
-
141
- function buildDevDependencyState(pkg) {
142
- const devDependencies =
143
- pkg.devDependencies &&
144
- typeof pkg.devDependencies === 'object' &&
145
- !Array.isArray(pkg.devDependencies)
146
- ? { ...pkg.devDependencies }
147
- : {};
148
-
149
- return {
150
- devDependencies,
151
- previousManaged: {
152
- c8: typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null,
153
- husky: typeof devDependencies.husky === 'string' ? devDependencies.husky : null,
154
- lerna: typeof devDependencies.lerna === 'string' ? devDependencies.lerna : null,
155
- '@produck/agent-toolkit':
156
- typeof devDependencies['@produck/agent-toolkit'] === 'string'
157
- ? devDependencies['@produck/agent-toolkit']
158
- : null,
159
- },
160
- };
161
- }
162
-
163
- export function runSyncWorkspaceConfig(options) {
164
- const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
165
- const check = hasFlag(options, '--check');
166
- const dryRun = hasFlag(options, '--dry-run') && !check;
167
- const jsonFile = getSingle(options, '--json', '');
168
- const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
169
-
170
- if (!fs.existsSync(cwd)) {
171
- console.error(`CWD does not exist: ${cwd}`);
172
- process.exit(2);
173
- }
174
-
175
- const rootPackageJsonPath = path.resolve(cwd, 'package.json');
176
- if (!fs.existsSync(rootPackageJsonPath)) {
177
- console.error(`Root package.json does not exist: ${rootPackageJsonPath}`);
178
- process.exit(2);
179
- }
180
-
181
- const pkg = parseJsonFile(rootPackageJsonPath, 'Root package.json');
182
- const toolingBaseline = loadToolingBaseline();
183
- const requiredToolkitDependency = getRequiredToolkitDevDependency();
184
- const requiredDevDependencies = {
185
- c8: toolingBaseline.c8Version,
186
- husky: toolingBaseline.huskyVersion,
187
- lerna: toolingBaseline.lernaVersion,
188
- '@produck/agent-toolkit': requiredToolkitDependency,
189
- };
190
-
191
- const scriptState = buildScriptState(pkg);
192
- const dependencyState = buildDevDependencyState(pkg);
193
- const c8ConfigPath = path.resolve(cwd, REQUIRED_C8_CONFIG_FILE);
194
- const currentC8ConfigContent = readFileIfExists(c8ConfigPath);
195
-
196
- const scriptValidation = validateRequiredExactEntries(scriptState.scripts, {
197
- [REQUIRED_BASELINE_SCRIPT_KEY]: REQUIRED_BASELINE_SCRIPT_VALUE,
198
- [REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY]: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE,
199
- [REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY]: REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE,
200
- });
201
- const dependencyValidation = validateRequiredExactEntries(
202
- dependencyState.devDependencies,
203
- requiredDevDependencies,
204
- );
205
-
206
- const matchesRequiredBaseline = !(REQUIRED_BASELINE_SCRIPT_KEY in scriptValidation.mismatches);
207
- const matchesRequiredWorkspaceCoverage = !(
208
- REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY in scriptValidation.mismatches
209
- );
210
- const matchesRequiredPrecommitCheck = !(
211
- REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY in scriptValidation.mismatches
212
- );
213
- const matchesRequiredManagedDevDependencies = dependencyValidation.ok;
214
- const matchesRequiredC8Config = currentC8ConfigContent === REQUIRED_C8_CONFIG_CONTENT;
215
-
216
- const requiresUpdate =
217
- !matchesRequiredBaseline ||
218
- !matchesRequiredWorkspaceCoverage ||
219
- !matchesRequiredPrecommitCheck ||
220
- !matchesRequiredManagedDevDependencies ||
221
- !matchesRequiredC8Config;
222
-
223
- if (mode === 'sync' && requiresUpdate) {
224
- scriptState.scripts[REQUIRED_BASELINE_SCRIPT_KEY] = REQUIRED_BASELINE_SCRIPT_VALUE;
225
- scriptState.scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY] =
226
- REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE;
227
- scriptState.scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY] =
228
- REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE;
229
- pkg.scripts = scriptState.scripts;
230
-
231
- for (const [name, version] of Object.entries(requiredDevDependencies)) {
232
- dependencyState.devDependencies[name] = version;
233
- }
234
- pkg.devDependencies = dependencyState.devDependencies;
235
-
236
- fs.writeFileSync(rootPackageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
237
- fs.writeFileSync(c8ConfigPath, REQUIRED_C8_CONFIG_CONTENT, 'utf8');
238
- }
239
-
240
- const report = {
241
- cwd,
242
- mode,
243
- ok: true,
244
- rootPackageJsonPath,
245
- toolingBaselinePath: toolingBaseline.toolingBaselinePath,
246
- required: {
247
- baselineScriptKey: REQUIRED_BASELINE_SCRIPT_KEY,
248
- baselineScriptValue: REQUIRED_BASELINE_SCRIPT_VALUE,
249
- workspaceCoverageScriptKey: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY,
250
- workspaceCoverageScriptValue: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE,
251
- precommitCheckScriptKey: REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY,
252
- precommitCheckScriptValue: REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE,
253
- c8ConfigFile: REQUIRED_C8_CONFIG_FILE,
254
- managedDevDependencies: requiredDevDependencies,
255
- },
256
- status: {
257
- matchesRequiredBaselineBefore: matchesRequiredBaseline,
258
- matchesRequiredWorkspaceCoverageBefore: matchesRequiredWorkspaceCoverage,
259
- matchesRequiredPrecommitCheckBefore: matchesRequiredPrecommitCheck,
260
- matchesRequiredManagedDevDependenciesBefore: matchesRequiredManagedDevDependencies,
261
- matchesRequiredC8ConfigBefore: matchesRequiredC8Config,
262
- matchesRequiredBaselineAfter:
263
- requiresUpdate && mode === 'sync' ? true : matchesRequiredBaseline,
264
- matchesRequiredWorkspaceCoverageAfter:
265
- requiresUpdate && mode === 'sync' ? true : matchesRequiredWorkspaceCoverage,
266
- matchesRequiredPrecommitCheckAfter:
267
- requiresUpdate && mode === 'sync' ? true : matchesRequiredPrecommitCheck,
268
- matchesRequiredManagedDevDependenciesAfter:
269
- requiresUpdate && mode === 'sync' ? true : matchesRequiredManagedDevDependencies,
270
- matchesRequiredC8ConfigAfter:
271
- requiresUpdate && mode === 'sync' ? true : matchesRequiredC8Config,
272
- updated: requiresUpdate && mode === 'sync',
273
- },
274
- };
275
-
276
- if (mode === 'check' && requiresUpdate) {
277
- report.ok = false;
278
- }
279
-
280
- if (jsonFile) {
281
- const outPath = path.resolve(cwd, jsonFile);
282
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
283
- fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
284
- }
285
-
286
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
287
- if (!report.ok) {
288
- process.exit(2);
289
- }
290
- }