@produck/agent-toolkit 0.2.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -9
- package/bin/agent-toolkit.mjs +76 -654
- package/bin/build-publish-assets.mjs +49 -0
- package/bin/command/enforce-node-baseline/help.txt +19 -0
- package/bin/command/enforce-node-baseline/index.mjs +155 -0
- package/{templates/help/main.txt → bin/command/main/help.txt} +7 -0
- package/bin/command/main/index.mjs +11 -0
- package/bin/command/preflight/help.txt +9 -0
- package/bin/command/preflight/index.mjs +147 -0
- package/bin/command/run-capture/index.mjs +100 -0
- package/bin/command/shared/args.mjs +45 -0
- package/bin/command/shared/text-resource.mjs +19 -0
- package/bin/command/summarize-log/index.mjs +64 -0
- package/bin/command/sync-coverage-script/help.txt +22 -0
- package/bin/command/sync-coverage-script/index.mjs +275 -0
- package/bin/command/sync-husky-hooks/help.txt +22 -0
- package/bin/command/sync-husky-hooks/index.mjs +267 -0
- package/bin/command/sync-instructions/index.mjs +272 -0
- package/bin/command/validate-commit-msg/help.txt +9 -0
- package/bin/command/validate-commit-msg/index.mjs +183 -0
- package/package.json +5 -3
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +37 -39
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +130 -26
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +59 -27
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +24 -8
- package/publish-assets/instructions/produck/tooling-version-baseline.json +32 -0
- package/templates/help/preflight.txt +0 -3
- package/templates/help/validate-commit-msg.txt +0 -7
- /package/{templates/help/run-capture.txt → bin/command/run-capture/help.txt} +0 -0
- /package/{templates/help/summarize-log.txt → bin/command/summarize-log/help.txt} +0 -0
- /package/{templates/help/sync-instructions.txt → bin/command/sync-instructions/help.txt} +0 -0
- /package/{templates → bin/command/sync-instructions}/user-space-bootstrap.md +0 -0
|
@@ -8,6 +8,8 @@ const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '..');
|
|
|
8
8
|
const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
|
|
9
9
|
const SOURCE_DIR = path.resolve(REPO_ROOT, '.github/distribution/produck');
|
|
10
10
|
const OUTPUT_DIR = path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck');
|
|
11
|
+
const SOURCE_TOOLING_BASELINE_PATH = path.resolve(SOURCE_DIR, 'tooling-version-baseline.json');
|
|
12
|
+
const OUTPUT_TOOLING_BASELINE_PATH = path.resolve(OUTPUT_DIR, 'tooling-version-baseline.json');
|
|
11
13
|
const LEGACY_OUTPUT_PATH = path.resolve(
|
|
12
14
|
PACKAGE_ROOT,
|
|
13
15
|
'publish-assets/instructions/org.instructions.md',
|
|
@@ -39,6 +41,47 @@ function validateSourceFile(fileName, text) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
function readAndValidateToolingBaseline() {
|
|
45
|
+
if (!fs.existsSync(SOURCE_TOOLING_BASELINE_PATH)) {
|
|
46
|
+
throw new Error(`Missing tooling baseline source file: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let baseline;
|
|
50
|
+
try {
|
|
51
|
+
baseline = JSON.parse(fs.readFileSync(SOURCE_TOOLING_BASELINE_PATH, 'utf8'));
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(`Invalid tooling baseline JSON: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const c8Version = baseline?.tools?.c8?.version;
|
|
57
|
+
const lernaVersion = baseline?.tools?.lerna?.version;
|
|
58
|
+
const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
|
|
59
|
+
|
|
60
|
+
if (typeof baseline.schemaVersion !== 'number') {
|
|
61
|
+
throw new Error(`Invalid tooling baseline schemaVersion in: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
65
|
+
throw new Error(`Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof lernaVersion !== 'string' || lernaVersion.trim() === '') {
|
|
69
|
+
throw new Error(`Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof coverageScriptTemplate !== 'string' || coverageScriptTemplate.trim() === '') {
|
|
73
|
+
throw new Error(`Invalid coverage.scriptTemplate in: ${SOURCE_TOOLING_BASELINE_PATH}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!coverageScriptTemplate.includes('{c8.version}')) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`coverage.scriptTemplate must include {c8.version} in: ${SOURCE_TOOLING_BASELINE_PATH}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `${JSON.stringify(baseline, null, 2)}\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
42
85
|
function readSourceEntries() {
|
|
43
86
|
if (!fs.existsSync(SOURCE_DIR)) {
|
|
44
87
|
throw new Error(`Missing source directory: ${SOURCE_DIR}`);
|
|
@@ -94,6 +137,12 @@ function run() {
|
|
|
94
137
|
process.stdout.write(`Generated ${outPath} from ${entry.sourcePath}\n`);
|
|
95
138
|
}
|
|
96
139
|
|
|
140
|
+
const toolingBaselineText = readAndValidateToolingBaseline();
|
|
141
|
+
fs.writeFileSync(OUTPUT_TOOLING_BASELINE_PATH, toolingBaselineText, 'utf8');
|
|
142
|
+
process.stdout.write(
|
|
143
|
+
`Generated ${OUTPUT_TOOLING_BASELINE_PATH} from ${SOURCE_TOOLING_BASELINE_PATH}\n`,
|
|
144
|
+
);
|
|
145
|
+
|
|
97
146
|
cleanStaleManagedFiles(expectedNames);
|
|
98
147
|
|
|
99
148
|
if (fs.existsSync(LEGACY_OUTPUT_PATH)) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit enforce-node-baseline [--cwd <dir>] [--source <file-or-dir>]
|
|
3
|
+
[--force] [--prune] [--workspace <path>] ... [--check] [--dry-run]
|
|
4
|
+
[--json <file>]
|
|
5
|
+
|
|
6
|
+
Behavior:
|
|
7
|
+
- Runs mandatory baseline flow in fixed order:
|
|
8
|
+
1) sync-instructions
|
|
9
|
+
2) preflight
|
|
10
|
+
3) sync-coverage-script
|
|
11
|
+
4) sync-husky-hooks
|
|
12
|
+
- Stops at first failed step and exits non-zero
|
|
13
|
+
- Prints one combined JSON report for all executed steps
|
|
14
|
+
|
|
15
|
+
Rules:
|
|
16
|
+
- --check runs non-mutating validation mode for step 1, step 3, and step 4
|
|
17
|
+
- --dry-run runs non-mutating preview mode for step 1, step 3, and step 4
|
|
18
|
+
- --check takes precedence over --dry-run
|
|
19
|
+
- --workspace filters coverage sync targets in step 3
|
|
@@ -0,0 +1,155 @@
|
|
|
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 { getMulti, getSingle, hasFlag } from '../shared/args.mjs';
|
|
7
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
8
|
+
|
|
9
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
11
|
+
const TOOLKIT_BIN = path.resolve(COMMAND_DIR, '../../agent-toolkit.mjs');
|
|
12
|
+
|
|
13
|
+
export function printEnforceNodeBaselineHelp() {
|
|
14
|
+
printTextResource(HELP_FILE);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseJsonOrNull(text) {
|
|
18
|
+
const trimmed = text.trim();
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(trimmed);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runToolkitSubcommand(cwd, args) {
|
|
31
|
+
const result = spawnSync(process.execPath, [TOOLKIT_BIN, ...args], {
|
|
32
|
+
cwd,
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const stdout = String(result.stdout || '');
|
|
37
|
+
const stderr = String(result.stderr || '');
|
|
38
|
+
const status = typeof result.status === 'number' ? result.status : 1;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
args,
|
|
42
|
+
status,
|
|
43
|
+
ok: status === 0,
|
|
44
|
+
report: parseJsonOrNull(stdout),
|
|
45
|
+
stdout,
|
|
46
|
+
stderr,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildStepReport(name, stepResult) {
|
|
51
|
+
const hasParsedReport = Boolean(stepResult.report);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
name,
|
|
55
|
+
args: stepResult.args,
|
|
56
|
+
status: stepResult.status,
|
|
57
|
+
ok: stepResult.ok,
|
|
58
|
+
report: stepResult.report,
|
|
59
|
+
stdout: hasParsedReport ? '' : stepResult.stdout,
|
|
60
|
+
stderr: stepResult.stderr,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function runEnforceNodeBaseline(options) {
|
|
65
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
66
|
+
const source = getSingle(options, '--source', '');
|
|
67
|
+
const force = hasFlag(options, '--force');
|
|
68
|
+
const prune = hasFlag(options, '--prune');
|
|
69
|
+
const check = hasFlag(options, '--check');
|
|
70
|
+
const dryRun = hasFlag(options, '--dry-run') && !check;
|
|
71
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
72
|
+
const workspaces = getMulti(options, '--workspace');
|
|
73
|
+
|
|
74
|
+
if (!fs.existsSync(cwd)) {
|
|
75
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
76
|
+
process.exit(2);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
|
|
80
|
+
const report = {
|
|
81
|
+
cwd,
|
|
82
|
+
mode,
|
|
83
|
+
ok: true,
|
|
84
|
+
steps: [],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const syncInstructionsArgs = ['sync-instructions', '--cwd', cwd];
|
|
88
|
+
if (source) {
|
|
89
|
+
syncInstructionsArgs.push('--source', source);
|
|
90
|
+
}
|
|
91
|
+
if (force) {
|
|
92
|
+
syncInstructionsArgs.push('--force');
|
|
93
|
+
}
|
|
94
|
+
if (prune) {
|
|
95
|
+
syncInstructionsArgs.push('--prune');
|
|
96
|
+
}
|
|
97
|
+
if (mode !== 'sync') {
|
|
98
|
+
syncInstructionsArgs.push('--dry-run');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const preflightArgs = [
|
|
102
|
+
'preflight',
|
|
103
|
+
'--cwd',
|
|
104
|
+
cwd,
|
|
105
|
+
'--require',
|
|
106
|
+
'package.json',
|
|
107
|
+
'--check-workspace-package-json',
|
|
108
|
+
'package.json',
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const syncCoverageArgs = ['sync-coverage-script', '--cwd', cwd];
|
|
112
|
+
for (const workspacePath of workspaces) {
|
|
113
|
+
syncCoverageArgs.push('--workspace', workspacePath);
|
|
114
|
+
}
|
|
115
|
+
if (check) {
|
|
116
|
+
syncCoverageArgs.push('--check');
|
|
117
|
+
} else if (dryRun) {
|
|
118
|
+
syncCoverageArgs.push('--dry-run');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const syncHuskyArgs = ['sync-husky-hooks', '--cwd', cwd];
|
|
122
|
+
if (check) {
|
|
123
|
+
syncHuskyArgs.push('--check');
|
|
124
|
+
} else if (dryRun) {
|
|
125
|
+
syncHuskyArgs.push('--dry-run');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const plan = [
|
|
129
|
+
{ name: 'sync-instructions', args: syncInstructionsArgs },
|
|
130
|
+
{ name: 'preflight', args: preflightArgs },
|
|
131
|
+
{ name: 'sync-coverage-script', args: syncCoverageArgs },
|
|
132
|
+
{ name: 'sync-husky-hooks', args: syncHuskyArgs },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
for (const step of plan) {
|
|
136
|
+
const stepResult = runToolkitSubcommand(cwd, step.args);
|
|
137
|
+
report.steps.push(buildStepReport(step.name, stepResult));
|
|
138
|
+
|
|
139
|
+
if (!stepResult.ok) {
|
|
140
|
+
report.ok = false;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (jsonFile) {
|
|
146
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
147
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
148
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
152
|
+
if (!report.ok) {
|
|
153
|
+
process.exit(2);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
agent-toolkit commands:
|
|
2
|
+
enforce-node-baseline
|
|
2
3
|
preflight
|
|
3
4
|
run-capture
|
|
4
5
|
summarize-log
|
|
6
|
+
sync-coverage-script
|
|
7
|
+
sync-husky-hooks
|
|
5
8
|
validate-commit-msg
|
|
6
9
|
sync-instructions
|
|
7
10
|
|
|
11
|
+
Default:
|
|
12
|
+
agent-toolkit
|
|
13
|
+
# equivalent to: agent-toolkit enforce-node-baseline
|
|
14
|
+
|
|
8
15
|
Use:
|
|
9
16
|
agent-toolkit <command> --help
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
5
|
+
|
|
6
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
8
|
+
|
|
9
|
+
export function printMainHelp() {
|
|
10
|
+
printTextResource(HELP_FILE);
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit preflight [--cwd <dir>] [--require <path>] ...
|
|
3
|
+
[--ensure-dir <path>] ... [--json <file>]
|
|
4
|
+
[--check-workspace-package-json <path>]
|
|
5
|
+
|
|
6
|
+
Rules for --check-workspace-package-json:
|
|
7
|
+
- `private` must be true
|
|
8
|
+
- `workspaces` must be a non-empty array of explicit paths (no glob tokens)
|
|
9
|
+
- `scripts` must include deps:install, test, produck:coverage, lint
|
|
@@ -0,0 +1,147 @@
|
|
|
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 GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
|
|
11
|
+
const REQUIRED_WORKSPACE_FIELDS = ['private', 'workspaces', 'scripts'];
|
|
12
|
+
const REQUIRED_WORKSPACE_SCRIPTS = ['deps:install', 'test', 'produck:coverage', 'lint'];
|
|
13
|
+
|
|
14
|
+
export function printPreflightHelp() {
|
|
15
|
+
printTextResource(HELP_FILE);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validateWorkspacePackageJson(cwd, checkPath) {
|
|
19
|
+
const packagePath = path.resolve(cwd, checkPath);
|
|
20
|
+
const check = {
|
|
21
|
+
file: checkPath,
|
|
22
|
+
ok: true,
|
|
23
|
+
exists: true,
|
|
24
|
+
validJson: true,
|
|
25
|
+
missingFields: [],
|
|
26
|
+
missingScripts: [],
|
|
27
|
+
wildcardWorkspaces: [],
|
|
28
|
+
scriptTypeValid: true,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(packagePath)) {
|
|
32
|
+
check.ok = false;
|
|
33
|
+
check.exists = false;
|
|
34
|
+
return check;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let json;
|
|
38
|
+
try {
|
|
39
|
+
json = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
check.ok = false;
|
|
42
|
+
check.validJson = false;
|
|
43
|
+
return check;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check.missingFields = REQUIRED_WORKSPACE_FIELDS.filter((field) => {
|
|
47
|
+
return !(field in json);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (typeof json.scripts !== 'object' || json.scripts === null || Array.isArray(json.scripts)) {
|
|
51
|
+
check.scriptTypeValid = false;
|
|
52
|
+
check.ok = false;
|
|
53
|
+
} else {
|
|
54
|
+
check.missingScripts = REQUIRED_WORKSPACE_SCRIPTS.filter((scriptName) => {
|
|
55
|
+
return !(scriptName in json.scripts);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const workspaceList = Array.isArray(json.workspaces)
|
|
60
|
+
? json.workspaces.map((entry) => String(entry))
|
|
61
|
+
: [];
|
|
62
|
+
if (!Array.isArray(json.workspaces)) {
|
|
63
|
+
check.wildcardWorkspaces = ['<non-array-workspaces>'];
|
|
64
|
+
check.ok = false;
|
|
65
|
+
} else {
|
|
66
|
+
check.wildcardWorkspaces = workspaceList.filter((entry) => GLOB_TOKEN_PATTERN.test(entry));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (json.private !== true) {
|
|
70
|
+
check.ok = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (check.missingFields.length > 0 || check.missingScripts.length > 0) {
|
|
74
|
+
check.ok = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (check.wildcardWorkspaces.length > 0) {
|
|
78
|
+
check.ok = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return check;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function runPreflight(options) {
|
|
85
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
86
|
+
const requireTargets = options['--require'] || [];
|
|
87
|
+
const ensureDirs = options['--ensure-dir'] || [];
|
|
88
|
+
const checkWorkspacePackageJson = getSingle(options, '--check-workspace-package-json', '');
|
|
89
|
+
const dryRun = hasFlag(options, '--dry-run');
|
|
90
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
91
|
+
|
|
92
|
+
const report = {
|
|
93
|
+
cwd,
|
|
94
|
+
dryRun,
|
|
95
|
+
ok: true,
|
|
96
|
+
required: [],
|
|
97
|
+
ensuredDirs: [],
|
|
98
|
+
workspacePackageJson: null,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(cwd)) {
|
|
102
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const target of requireTargets) {
|
|
107
|
+
const absolute = path.resolve(cwd, String(target));
|
|
108
|
+
const exists = fs.existsSync(absolute);
|
|
109
|
+
report.required.push({ target: String(target), absolute, exists });
|
|
110
|
+
if (!exists) {
|
|
111
|
+
report.ok = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const dir of ensureDirs) {
|
|
116
|
+
const absolute = path.resolve(cwd, String(dir));
|
|
117
|
+
const existedBefore = fs.existsSync(absolute);
|
|
118
|
+
if (!dryRun && !existedBefore) {
|
|
119
|
+
fs.mkdirSync(absolute, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
report.ensuredDirs.push({
|
|
122
|
+
target: String(dir),
|
|
123
|
+
absolute,
|
|
124
|
+
existedBefore,
|
|
125
|
+
existsAfter: fs.existsSync(absolute),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (checkWorkspacePackageJson) {
|
|
130
|
+
const workspaceCheck = validateWorkspacePackageJson(cwd, checkWorkspacePackageJson);
|
|
131
|
+
report.workspacePackageJson = workspaceCheck;
|
|
132
|
+
if (!workspaceCheck.ok) {
|
|
133
|
+
report.ok = false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (jsonFile) {
|
|
138
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
139
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
140
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
144
|
+
if (!report.ok) {
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawn } 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
|
+
|
|
9
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
11
|
+
|
|
12
|
+
export function printRunCaptureHelp() {
|
|
13
|
+
printTextResource(HELP_FILE);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function runCapture(options) {
|
|
17
|
+
const out = getSingle(options, '--out', '');
|
|
18
|
+
const cmd = getSingle(options, '--cmd', '');
|
|
19
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
20
|
+
const meta = getSingle(options, '--meta', '');
|
|
21
|
+
const allowPipe = hasFlag(options, '--allow-pipe');
|
|
22
|
+
|
|
23
|
+
if (!out || !cmd) {
|
|
24
|
+
printRunCaptureHelp();
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!allowPipe && cmd.includes('|')) {
|
|
29
|
+
console.error('Blocked command containing pipe. Use --allow-pipe if needed.');
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const outPath = path.resolve(out);
|
|
34
|
+
const metaPath = meta ? path.resolve(meta) : `${outPath}.meta.json`;
|
|
35
|
+
|
|
36
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
37
|
+
fs.mkdirSync(path.dirname(metaPath), { recursive: true });
|
|
38
|
+
|
|
39
|
+
const startAt = Date.now();
|
|
40
|
+
const outStream = fs.createWriteStream(outPath, { encoding: 'utf8' });
|
|
41
|
+
|
|
42
|
+
outStream.write(`# command: ${cmd}\n`);
|
|
43
|
+
outStream.write(`# cwd: ${cwd}\n`);
|
|
44
|
+
outStream.write(`# startedAt: ${new Date(startAt).toISOString()}\n\n`);
|
|
45
|
+
|
|
46
|
+
const child = spawn(cmd, {
|
|
47
|
+
cwd,
|
|
48
|
+
shell: true,
|
|
49
|
+
env: process.env,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let stdoutBytes = 0;
|
|
53
|
+
let stderrBytes = 0;
|
|
54
|
+
|
|
55
|
+
child.stdout.on('data', (chunk) => {
|
|
56
|
+
stdoutBytes += chunk.length;
|
|
57
|
+
outStream.write(chunk);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
child.stderr.on('data', (chunk) => {
|
|
61
|
+
stderrBytes += chunk.length;
|
|
62
|
+
outStream.write(chunk);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
child.on('error', (error) => {
|
|
66
|
+
outStream.write(`\n[agent-toolkit] spawn error: ${error.message}\n`);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.on('close', (code, signal) => {
|
|
70
|
+
const endAt = Date.now();
|
|
71
|
+
const durationMs = endAt - startAt;
|
|
72
|
+
const signalLabel = String(signal).replace(/^null$/, 'none');
|
|
73
|
+
const numberCode = Number(code);
|
|
74
|
+
const isNumberCode = Number(typeof code === 'number');
|
|
75
|
+
const normalizedExitCode = isNumberCode * numberCode + (1 - isNumberCode);
|
|
76
|
+
|
|
77
|
+
outStream.write('\n');
|
|
78
|
+
outStream.write(`# finishedAt: ${new Date(endAt).toISOString()}\n`);
|
|
79
|
+
outStream.write(`# exitCode: ${String(code)}\n`);
|
|
80
|
+
outStream.write(`# signal: ${signalLabel}\n`);
|
|
81
|
+
outStream.end();
|
|
82
|
+
|
|
83
|
+
const report = {
|
|
84
|
+
command: cmd,
|
|
85
|
+
cwd,
|
|
86
|
+
startedAt: new Date(startAt).toISOString(),
|
|
87
|
+
finishedAt: new Date(endAt).toISOString(),
|
|
88
|
+
durationMs,
|
|
89
|
+
exitCode: code,
|
|
90
|
+
signal,
|
|
91
|
+
stdoutBytes,
|
|
92
|
+
stderrBytes,
|
|
93
|
+
outputFile: outPath,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync(metaPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
97
|
+
|
|
98
|
+
process.exit(normalizedExitCode);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function parseCommonArgs(argv) {
|
|
2
|
+
const positional = [];
|
|
3
|
+
const options = {};
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6
|
+
const token = argv[i];
|
|
7
|
+
if (token.startsWith('--')) {
|
|
8
|
+
const next = argv[i + 1];
|
|
9
|
+
if (!next || next.startsWith('--')) {
|
|
10
|
+
if (!options[token]) {
|
|
11
|
+
options[token] = [];
|
|
12
|
+
}
|
|
13
|
+
options[token].push(true);
|
|
14
|
+
} else {
|
|
15
|
+
if (!options[token]) {
|
|
16
|
+
options[token] = [];
|
|
17
|
+
}
|
|
18
|
+
options[token].push(next);
|
|
19
|
+
i += 1;
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
positional.push(token);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { positional, options };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getSingle(options, key, fallback = '') {
|
|
30
|
+
if (!options[key] || options[key].length === 0) {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
return String(options[key][options[key].length - 1]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getMulti(options, key) {
|
|
37
|
+
if (!options[key]) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return options[key].map((v) => String(v));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasFlag(options, key) {
|
|
44
|
+
return Boolean(options[key]);
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export function loadTextResource(filePath) {
|
|
4
|
+
if (!fs.existsSync(filePath)) {
|
|
5
|
+
console.error(`Resource file not found: ${filePath}`);
|
|
6
|
+
process.exit(2);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function printTextResource(filePath) {
|
|
13
|
+
let content = loadTextResource(filePath);
|
|
14
|
+
if (!content.endsWith('\n')) {
|
|
15
|
+
content = `${content}\n`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
process.stdout.write(content);
|
|
19
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getSingle } 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
|
+
export function printSummarizeHelp() {
|
|
12
|
+
printTextResource(HELP_FILE);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function runSummarize(options) {
|
|
16
|
+
const file = getSingle(options, '--file', '');
|
|
17
|
+
const last = Number(getSingle(options, '--last', '0')) || 0;
|
|
18
|
+
const match = getSingle(options, '--match', '');
|
|
19
|
+
const max = Number(getSingle(options, '--max', '200')) || 200;
|
|
20
|
+
|
|
21
|
+
if (!file) {
|
|
22
|
+
printSummarizeHelp();
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const filePath = path.resolve(file);
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
console.error(`Log file does not exist: ${filePath}`);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
33
|
+
const allLines = raw.split(/\r?\n/);
|
|
34
|
+
|
|
35
|
+
let lines = allLines;
|
|
36
|
+
let mode = 'all';
|
|
37
|
+
|
|
38
|
+
if (match) {
|
|
39
|
+
const pattern = new RegExp(match, 'i');
|
|
40
|
+
lines = allLines.filter((line) => pattern.test(line));
|
|
41
|
+
mode = 'match';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (last > 0) {
|
|
45
|
+
lines = lines.slice(-last);
|
|
46
|
+
mode = mode === 'match' ? 'match+last' : 'last';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (max > 0 && lines.length > max) {
|
|
50
|
+
lines = lines.slice(0, max);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const header = [
|
|
54
|
+
`# file: ${filePath}`,
|
|
55
|
+
`# totalLines: ${allLines.length}`,
|
|
56
|
+
`# selectedLines: ${lines.length}`,
|
|
57
|
+
`# mode: ${mode}`,
|
|
58
|
+
'',
|
|
59
|
+
].join('\n');
|
|
60
|
+
|
|
61
|
+
process.stdout.write(header);
|
|
62
|
+
process.stdout.write(lines.join('\n'));
|
|
63
|
+
process.stdout.write('\n');
|
|
64
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit sync-coverage-script [--cwd <dir>]
|
|
3
|
+
[--workspace <path>] ... [--check] [--dry-run] [--json <file>]
|
|
4
|
+
|
|
5
|
+
Behavior:
|
|
6
|
+
- Applies organization-required coverage script to workspace package.json files
|
|
7
|
+
- Organization-reserved script key is scripts.produck:coverage
|
|
8
|
+
- Applies organization-required pinned local c8 devDependency to workspace
|
|
9
|
+
package.json files
|
|
10
|
+
- Target script is rendered from organization tooling baseline file
|
|
11
|
+
(lookup order):
|
|
12
|
+
1) .github/distribution/produck/tooling-version-baseline.json
|
|
13
|
+
2) publish-assets/instructions/produck/tooling-version-baseline.json
|
|
14
|
+
- Baseline template:
|
|
15
|
+
c8 --reporter=lcov --reporter=html --reporter=text-summary npm test
|
|
16
|
+
|
|
17
|
+
Rules:
|
|
18
|
+
- When --workspace is omitted, root package.json workspaces are used
|
|
19
|
+
- Root workspaces must be explicit paths (no glob tokens)
|
|
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
|