@produck/agent-toolkit 0.6.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.
- package/README.md +74 -43
- package/bin/agent-toolkit.mjs +26 -33
- package/bin/build-publish-assets.mjs +28 -0
- package/bin/command/enforce-node-baseline/help.txt +12 -10
- package/bin/command/enforce-node-baseline/index.mjs +23 -15
- package/bin/command/main/help.txt +6 -5
- package/bin/command/preflight/help.txt +1 -1
- package/bin/command/preflight/index.mjs +1 -1
- package/bin/command/{sync-coverage-script → sync-coverage}/help.txt +2 -1
- package/bin/command/{sync-coverage-script → sync-coverage}/index.mjs +116 -19
- package/bin/command/sync-editorconfig/index.mjs +10 -153
- package/bin/command/{sync-prettier-config → sync-format}/help.txt +2 -2
- package/bin/command/{sync-prettier-config → sync-format}/index.mjs +63 -9
- package/bin/command/{sync-workspace-config → sync-git}/help.txt +10 -6
- package/bin/command/sync-git/index.mjs +424 -0
- package/bin/command/sync-install/help.txt +14 -0
- package/bin/command/sync-install/index.mjs +106 -0
- package/bin/command/{sync-eslint-config → sync-lint}/help.txt +2 -2
- package/bin/command/{sync-eslint-config → sync-lint}/index.mjs +3 -4
- package/bin/command/sync-publish/help.txt +18 -0
- package/bin/command/sync-publish/index.mjs +157 -0
- package/bin/command/validate-commit-msg/index.mjs +30 -2
- package/package.json +3 -5
- package/publish-assets/gitattributes +5 -0
- package/publish-assets/gitignore +137 -0
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +53 -40
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +13 -16
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +2 -2
- package/publish-assets/instructions/produck/tooling-version-baseline.json +8 -1
- package/bin/command/sync-husky-hooks/help.txt +0 -14
- package/bin/command/sync-husky-hooks/index.mjs +0 -89
- package/bin/command/sync-workspace-config/index.mjs +0 -290
|
@@ -14,14 +14,42 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
|
14
14
|
path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck/tooling-version-baseline.json'),
|
|
15
15
|
];
|
|
16
16
|
const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
|
|
17
|
+
const REQUIRED_ROOT_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
18
|
+
const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE =
|
|
19
|
+
'c8 --config .c8rc.json npm run test --workspaces --if-present';
|
|
17
20
|
const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
18
21
|
const REQUIRED_TEST_SCRIPT_KEY = 'test';
|
|
19
22
|
const DEFAULT_TEST_SCRIPT_VALUE = 'node -e "console.log(\'No tests configured\')"';
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const REQUIRED_C8_CONFIG_FILE = '.c8rc.json';
|
|
24
|
+
const REQUIRED_C8_CONFIG_CONTENT = `${JSON.stringify(
|
|
25
|
+
{
|
|
26
|
+
'check-coverage': true,
|
|
27
|
+
all: true,
|
|
28
|
+
branches: 99.5,
|
|
29
|
+
exclude: ['**/node_modules/**', '**/coverage/**', '**/dist/**', '**/build/**', '**/out/**'],
|
|
30
|
+
functions: 99.5,
|
|
31
|
+
include: ['src/**', 'extension/**'],
|
|
32
|
+
reporter: ['lcov', 'html', 'text-summary'],
|
|
33
|
+
statements: 99.5,
|
|
34
|
+
lines: 99.5,
|
|
35
|
+
},
|
|
36
|
+
null,
|
|
37
|
+
2,
|
|
38
|
+
)}\n`;
|
|
39
|
+
|
|
40
|
+
export function printSyncCoverageHelp() {
|
|
22
41
|
printTextResource(HELP_FILE);
|
|
23
42
|
}
|
|
24
43
|
|
|
44
|
+
function parseJsonFile(filePath, label) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
console.error(`${label} is not valid JSON: ${filePath}`);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
function loadToolingBaseline() {
|
|
26
54
|
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find((candidatePath) => {
|
|
27
55
|
return fs.existsSync(candidatePath);
|
|
@@ -36,14 +64,12 @@ function loadToolingBaseline() {
|
|
|
36
64
|
}
|
|
37
65
|
|
|
38
66
|
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
39
|
-
const c8Version = baseline?.tools?.c8?.version;
|
|
40
|
-
const coverageTemplate = baseline?.coverage?.scriptTemplate;
|
|
41
|
-
|
|
42
67
|
if (typeof baseline.schemaVersion !== 'number') {
|
|
43
68
|
console.error(`Tooling baseline schemaVersion must be a number: ${toolingBaselinePath}`);
|
|
44
69
|
process.exit(2);
|
|
45
70
|
}
|
|
46
71
|
|
|
72
|
+
const c8Version = baseline?.tools?.c8?.version;
|
|
47
73
|
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
48
74
|
console.error(
|
|
49
75
|
`Tooling baseline tools.c8.version must be a non-empty string: ${toolingBaselinePath}`,
|
|
@@ -51,6 +77,7 @@ function loadToolingBaseline() {
|
|
|
51
77
|
process.exit(2);
|
|
52
78
|
}
|
|
53
79
|
|
|
80
|
+
const coverageTemplate = baseline?.coverage?.scriptTemplate;
|
|
54
81
|
if (typeof coverageTemplate !== 'string' || coverageTemplate.trim() === '') {
|
|
55
82
|
console.error(
|
|
56
83
|
`Tooling baseline coverage.scriptTemplate must be a non-empty string: ${toolingBaselinePath}`,
|
|
@@ -74,13 +101,12 @@ function buildRequiredC8DevDependency(baseline) {
|
|
|
74
101
|
return String(baseline.tools.c8.version);
|
|
75
102
|
}
|
|
76
103
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
return
|
|
80
|
-
} catch {
|
|
81
|
-
console.error(`${label} is not valid JSON: ${filePath}`);
|
|
82
|
-
process.exit(2);
|
|
104
|
+
function readFileIfExists(filePath) {
|
|
105
|
+
if (!fs.existsSync(filePath)) {
|
|
106
|
+
return null;
|
|
83
107
|
}
|
|
108
|
+
|
|
109
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
function resolveWorkspacePaths(cwd, options) {
|
|
@@ -91,20 +117,17 @@ function resolveWorkspacePaths(cwd, options) {
|
|
|
91
117
|
|
|
92
118
|
const rootPackageJsonPath = path.resolve(cwd, 'package.json');
|
|
93
119
|
if (!fs.existsSync(rootPackageJsonPath)) {
|
|
94
|
-
|
|
95
|
-
process.exit(2);
|
|
120
|
+
return [];
|
|
96
121
|
}
|
|
97
122
|
|
|
98
123
|
const rootPackageJson = parseJsonFile(rootPackageJsonPath, 'Root package.json');
|
|
99
124
|
if (!Array.isArray(rootPackageJson.workspaces)) {
|
|
100
|
-
|
|
101
|
-
process.exit(2);
|
|
125
|
+
return [];
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
const workspaces = rootPackageJson.workspaces.map((entry) => String(entry));
|
|
105
129
|
if (workspaces.length === 0) {
|
|
106
|
-
|
|
107
|
-
process.exit(2);
|
|
130
|
+
return [];
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
const hasGlob = workspaces.some((entry) => GLOB_TOKEN_PATTERN.test(entry));
|
|
@@ -116,6 +139,69 @@ function resolveWorkspacePaths(cwd, options) {
|
|
|
116
139
|
return workspaces;
|
|
117
140
|
}
|
|
118
141
|
|
|
142
|
+
function syncRootCoverage(cwd, mode, requiredC8Version) {
|
|
143
|
+
const rootPackageJsonPath = path.resolve(cwd, 'package.json');
|
|
144
|
+
const c8ConfigPath = path.resolve(cwd, REQUIRED_C8_CONFIG_FILE);
|
|
145
|
+
const currentC8ConfigContent = readFileIfExists(c8ConfigPath);
|
|
146
|
+
const pkg = parseJsonFile(rootPackageJsonPath, 'Root package.json');
|
|
147
|
+
const scripts =
|
|
148
|
+
pkg.scripts && typeof pkg.scripts === 'object' && !Array.isArray(pkg.scripts)
|
|
149
|
+
? { ...pkg.scripts }
|
|
150
|
+
: {};
|
|
151
|
+
const devDependencies =
|
|
152
|
+
pkg.devDependencies &&
|
|
153
|
+
typeof pkg.devDependencies === 'object' &&
|
|
154
|
+
!Array.isArray(pkg.devDependencies)
|
|
155
|
+
? { ...pkg.devDependencies }
|
|
156
|
+
: {};
|
|
157
|
+
const previousRootCoverageScript =
|
|
158
|
+
typeof scripts[REQUIRED_ROOT_COVERAGE_SCRIPT_KEY] === 'string'
|
|
159
|
+
? scripts[REQUIRED_ROOT_COVERAGE_SCRIPT_KEY]
|
|
160
|
+
: null;
|
|
161
|
+
const previousC8DevDependency =
|
|
162
|
+
typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null;
|
|
163
|
+
const matchesRequiredRootCoverageBefore =
|
|
164
|
+
previousRootCoverageScript === REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE;
|
|
165
|
+
const matchesRequiredC8DevDependencyBefore = previousC8DevDependency === requiredC8Version;
|
|
166
|
+
const matchesRequiredC8ConfigBefore = currentC8ConfigContent === REQUIRED_C8_CONFIG_CONTENT;
|
|
167
|
+
const requiresUpdate =
|
|
168
|
+
!matchesRequiredRootCoverageBefore ||
|
|
169
|
+
!matchesRequiredC8DevDependencyBefore ||
|
|
170
|
+
!matchesRequiredC8ConfigBefore;
|
|
171
|
+
|
|
172
|
+
if (mode === 'sync' && requiresUpdate) {
|
|
173
|
+
scripts[REQUIRED_ROOT_COVERAGE_SCRIPT_KEY] = REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE;
|
|
174
|
+
pkg.scripts = scripts;
|
|
175
|
+
devDependencies.c8 = requiredC8Version;
|
|
176
|
+
pkg.devDependencies = devDependencies;
|
|
177
|
+
fs.writeFileSync(rootPackageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
178
|
+
fs.writeFileSync(c8ConfigPath, REQUIRED_C8_CONFIG_CONTENT, 'utf8');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
rootPackageJsonPath,
|
|
183
|
+
required: {
|
|
184
|
+
rootCoverageScriptKey: REQUIRED_ROOT_COVERAGE_SCRIPT_KEY,
|
|
185
|
+
rootCoverageScriptValue: REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE,
|
|
186
|
+
c8ConfigFile: REQUIRED_C8_CONFIG_FILE,
|
|
187
|
+
c8ConfigContent: REQUIRED_C8_CONFIG_CONTENT,
|
|
188
|
+
c8DevDependency: requiredC8Version,
|
|
189
|
+
},
|
|
190
|
+
status: {
|
|
191
|
+
matchesRequiredRootCoverageBefore,
|
|
192
|
+
matchesRequiredC8DevDependencyBefore,
|
|
193
|
+
matchesRequiredC8ConfigBefore,
|
|
194
|
+
matchesRequiredRootCoverageAfter:
|
|
195
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredRootCoverageBefore,
|
|
196
|
+
matchesRequiredC8DevDependencyAfter:
|
|
197
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredC8DevDependencyBefore,
|
|
198
|
+
matchesRequiredC8ConfigAfter:
|
|
199
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredC8ConfigBefore,
|
|
200
|
+
updated: requiresUpdate && mode === 'sync',
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
119
205
|
function reconcileCoverageScript(
|
|
120
206
|
cwd,
|
|
121
207
|
workspacePath,
|
|
@@ -230,7 +316,7 @@ function reconcileCoverageScript(
|
|
|
230
316
|
return result;
|
|
231
317
|
}
|
|
232
318
|
|
|
233
|
-
export function
|
|
319
|
+
export function runSyncCoverage(options) {
|
|
234
320
|
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
235
321
|
const check = hasFlag(options, '--check');
|
|
236
322
|
const dryRun = hasFlag(options, '--dry-run');
|
|
@@ -244,8 +330,9 @@ export function runSyncCoverageScript(options) {
|
|
|
244
330
|
process.exit(2);
|
|
245
331
|
}
|
|
246
332
|
|
|
247
|
-
const workspacePaths = resolveWorkspacePaths(cwd, options);
|
|
248
333
|
const mode = dryRun ? 'dry-run' : check ? 'check' : 'sync';
|
|
334
|
+
const root = syncRootCoverage(cwd, mode, requiredC8Version);
|
|
335
|
+
const workspacePaths = resolveWorkspacePaths(cwd, options);
|
|
249
336
|
|
|
250
337
|
const report = {
|
|
251
338
|
cwd,
|
|
@@ -258,11 +345,21 @@ export function runSyncCoverageScript(options) {
|
|
|
258
345
|
requiredCoverageScript,
|
|
259
346
|
requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
|
|
260
347
|
requiredC8DevDependency: requiredC8Version,
|
|
348
|
+
root,
|
|
261
349
|
workspaces: workspacePaths,
|
|
262
350
|
results: [],
|
|
263
351
|
ok: true,
|
|
264
352
|
};
|
|
265
353
|
|
|
354
|
+
if (
|
|
355
|
+
mode === 'check' &&
|
|
356
|
+
(!root.status.matchesRequiredRootCoverageAfter ||
|
|
357
|
+
!root.status.matchesRequiredC8DevDependencyAfter ||
|
|
358
|
+
!root.status.matchesRequiredC8ConfigAfter)
|
|
359
|
+
) {
|
|
360
|
+
report.ok = false;
|
|
361
|
+
}
|
|
362
|
+
|
|
266
363
|
for (const workspacePath of workspacePaths) {
|
|
267
364
|
const effectiveMode = mode === 'sync' ? 'sync' : 'check';
|
|
268
365
|
const item = reconcileCoverageScript(
|
|
@@ -12,154 +12,10 @@ const TEMPLATE_FILE = path.resolve(COMMAND_DIR, 'editorconfig.template');
|
|
|
12
12
|
|
|
13
13
|
const REQUIRED_EDITORCONFIG_CONTENT = fs.readFileSync(TEMPLATE_FILE, 'utf8');
|
|
14
14
|
|
|
15
|
-
// Required key-value pairs for validation
|
|
16
|
-
const REQUIRED_SECTIONS = {
|
|
17
|
-
root: {
|
|
18
|
-
line: 'root = true',
|
|
19
|
-
},
|
|
20
|
-
'*': {
|
|
21
|
-
keys: {
|
|
22
|
-
charset: 'utf-8',
|
|
23
|
-
indent_style: 'space',
|
|
24
|
-
indent_size: '2',
|
|
25
|
-
trim_trailing_whitespace: 'true',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
'*.{yml,yaml}': {
|
|
29
|
-
keys: {
|
|
30
|
-
indent_style: 'space',
|
|
31
|
-
indent_size: '2',
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
'*.md': {
|
|
35
|
-
keys: {
|
|
36
|
-
trim_trailing_whitespace: 'false',
|
|
37
|
-
max_line_length: '80',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
15
|
export function printSyncEditorconfigHelp() {
|
|
43
16
|
printTextResource(HELP_FILE);
|
|
44
17
|
}
|
|
45
18
|
|
|
46
|
-
function parseEditorconfig(content) {
|
|
47
|
-
const sections = {};
|
|
48
|
-
let currentSection = null;
|
|
49
|
-
|
|
50
|
-
for (const line of content.split('\n')) {
|
|
51
|
-
const trimmed = line.trim();
|
|
52
|
-
|
|
53
|
-
// Skip empty lines and comments
|
|
54
|
-
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith(';')) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check for section header
|
|
59
|
-
const sectionMatch = trimmed.match(/^\[(.+)\]$/);
|
|
60
|
-
if (sectionMatch) {
|
|
61
|
-
currentSection = sectionMatch[1];
|
|
62
|
-
sections[currentSection] = {};
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check for root = true
|
|
67
|
-
const rootMatch = trimmed.match(/^root\s*=\s*(.+)$/i);
|
|
68
|
-
if (rootMatch && !currentSection) {
|
|
69
|
-
sections._root = rootMatch[1].trim().toLowerCase();
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Parse key-value pair
|
|
74
|
-
if (currentSection) {
|
|
75
|
-
const kvMatch = trimmed.match(/^([^=]+)\s*=\s*(.+)$/);
|
|
76
|
-
if (kvMatch) {
|
|
77
|
-
sections[currentSection][kvMatch[1].trim().toLowerCase()] = kvMatch[2].trim().toLowerCase();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return sections;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function validateEditorconfig(sections) {
|
|
86
|
-
const mismatches = [];
|
|
87
|
-
|
|
88
|
-
// Check root
|
|
89
|
-
if (sections._root !== 'true') {
|
|
90
|
-
mismatches.push({ section: '_root', expected: 'true', actual: sections._root || 'missing' });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Check each required section
|
|
94
|
-
for (const [sectionName, config] of Object.entries(REQUIRED_SECTIONS)) {
|
|
95
|
-
if (sectionName === 'root') continue;
|
|
96
|
-
|
|
97
|
-
if (!sections[sectionName]) {
|
|
98
|
-
mismatches.push({ section: `[${sectionName}]`, expected: 'present', actual: 'missing' });
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (config.keys) {
|
|
103
|
-
for (const [key, expectedValue] of Object.entries(config.keys)) {
|
|
104
|
-
const actualValue = sections[sectionName][key];
|
|
105
|
-
if (actualValue !== expectedValue) {
|
|
106
|
-
mismatches.push({
|
|
107
|
-
section: `[${sectionName}]`,
|
|
108
|
-
key,
|
|
109
|
-
expected: expectedValue,
|
|
110
|
-
actual: actualValue || 'missing',
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return mismatches;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function buildUpdatedContent(existingContent) {
|
|
121
|
-
const existingSections = parseEditorconfig(existingContent);
|
|
122
|
-
const lines = [];
|
|
123
|
-
|
|
124
|
-
// Add root if missing
|
|
125
|
-
if (existingSections._root !== 'true') {
|
|
126
|
-
lines.push('root = true');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Process each required section
|
|
130
|
-
for (const [sectionName, config] of Object.entries(REQUIRED_SECTIONS)) {
|
|
131
|
-
if (sectionName === 'root') continue;
|
|
132
|
-
|
|
133
|
-
const existingSection = existingSections[sectionName] || {};
|
|
134
|
-
const missingKeys = [];
|
|
135
|
-
|
|
136
|
-
if (config.keys) {
|
|
137
|
-
for (const [key, expectedValue] of Object.entries(config.keys)) {
|
|
138
|
-
if (existingSection[key] !== expectedValue) {
|
|
139
|
-
missingKeys.push({ key, value: expectedValue });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (missingKeys.length > 0 || !existingSections[sectionName]) {
|
|
145
|
-
lines.push('');
|
|
146
|
-
lines.push(`[${sectionName}]`);
|
|
147
|
-
for (const { key, value } of missingKeys) {
|
|
148
|
-
lines.push(`${key} = ${value}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// If no updates needed, return original
|
|
154
|
-
// c8 ignore next 3
|
|
155
|
-
if (lines.length === 0) {
|
|
156
|
-
return existingContent;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Append missing entries to existing content
|
|
160
|
-
return existingContent.trimEnd() + lines.join('\n') + '\n';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
19
|
function readFileIfExists(filePath) {
|
|
164
20
|
if (!fs.existsSync(filePath)) {
|
|
165
21
|
return null;
|
|
@@ -183,17 +39,18 @@ export function runSyncEditorconfig(options) {
|
|
|
183
39
|
const editorconfigPath = path.resolve(cwd, EDITORCONFIG_FILE);
|
|
184
40
|
const currentContent = readFileIfExists(editorconfigPath);
|
|
185
41
|
const fileExists = currentContent !== null;
|
|
42
|
+
const upToDate = fileExists && currentContent === REQUIRED_EDITORCONFIG_CONTENT;
|
|
186
43
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
? buildUpdatedContent(currentContent)
|
|
195
|
-
: REQUIRED_EDITORCONFIG_CONTENT;
|
|
44
|
+
const mismatches = [];
|
|
45
|
+
if (!upToDate) {
|
|
46
|
+
mismatches.push({
|
|
47
|
+
file: EDITORCONFIG_FILE,
|
|
48
|
+
expected: 'exact required content',
|
|
49
|
+
actual: fileExists ? 'different content' : 'missing',
|
|
50
|
+
});
|
|
196
51
|
}
|
|
52
|
+
const requiresUpdate = mismatches.length > 0;
|
|
53
|
+
const plannedContent = requiresUpdate ? REQUIRED_EDITORCONFIG_CONTENT : null;
|
|
197
54
|
|
|
198
55
|
if (mode === 'sync' && requiresUpdate && plannedContent) {
|
|
199
56
|
fs.writeFileSync(editorconfigPath, plannedContent, 'utf8');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Usage:
|
|
2
|
-
agent-toolkit sync-
|
|
2
|
+
agent-toolkit sync-format [--cwd <dir>] [--check] [--dry-run]
|
|
3
3
|
[--json <file>]
|
|
4
4
|
|
|
5
5
|
Behavior:
|
|
6
6
|
- Applies organization-required root format script:
|
|
7
|
-
- scripts.produck:format =
|
|
7
|
+
- scripts.produck:format = prettier --write .
|
|
8
8
|
- Applies organization-required root Prettier config file:
|
|
9
9
|
- .prettierrc
|
|
10
10
|
|
|
@@ -7,11 +7,17 @@ import { printTextResource } from '../shared/text-resource.mjs';
|
|
|
7
7
|
|
|
8
8
|
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
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(REPO_ROOT, '.github/distribution/produck/tooling-version-baseline.json'),
|
|
14
|
+
path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck/tooling-version-baseline.json'),
|
|
15
|
+
];
|
|
10
16
|
const PRETTIER_CONFIG_FILE = '.prettierrc';
|
|
17
|
+
const REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY = 'prettier';
|
|
11
18
|
|
|
12
19
|
const REQUIRED_FORMAT_SCRIPT_KEY = 'produck:format';
|
|
13
|
-
const REQUIRED_FORMAT_SCRIPT_VALUE =
|
|
14
|
-
'npm exec -- prettier --check . && npm run format --if-present';
|
|
20
|
+
const REQUIRED_FORMAT_SCRIPT_VALUE = 'prettier --write .';
|
|
15
21
|
const REQUIRED_PRETTIER_CONFIG = `${JSON.stringify(
|
|
16
22
|
{
|
|
17
23
|
semi: true,
|
|
@@ -27,10 +33,18 @@ const REQUIRED_PRETTIER_CONFIG = `${JSON.stringify(
|
|
|
27
33
|
2,
|
|
28
34
|
)}\n`;
|
|
29
35
|
|
|
30
|
-
export function
|
|
36
|
+
export function printSyncFormatHelp() {
|
|
31
37
|
printTextResource(HELP_FILE);
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
function readFileIfExists(filePath) {
|
|
41
|
+
if (!fs.existsSync(filePath)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
function parseJsonFile(filePath, label) {
|
|
35
49
|
try {
|
|
36
50
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -40,15 +54,33 @@ function parseJsonFile(filePath, label) {
|
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
return
|
|
57
|
+
function loadToolingBaseline() {
|
|
58
|
+
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find((candidatePath) => {
|
|
59
|
+
return fs.existsSync(candidatePath);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!toolingBaselinePath) {
|
|
63
|
+
console.error('Tooling baseline file does not exist in expected locations:');
|
|
64
|
+
for (const candidatePath of TOOLING_BASELINE_CANDIDATE_PATHS) {
|
|
65
|
+
console.error(`- ${candidatePath}`);
|
|
66
|
+
}
|
|
67
|
+
process.exit(2);
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
|
|
70
|
+
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
71
|
+
const prettierVersion = String(baseline?.tools?.prettier?.version || '').trim();
|
|
72
|
+
|
|
73
|
+
if (!prettierVersion) {
|
|
74
|
+
console.error(
|
|
75
|
+
`Tooling baseline must define fixed tools.prettier.version: ${toolingBaselinePath}`,
|
|
76
|
+
);
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { toolingBaselinePath, prettierVersion };
|
|
49
81
|
}
|
|
50
82
|
|
|
51
|
-
export function
|
|
83
|
+
export function runSyncFormat(options) {
|
|
52
84
|
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
53
85
|
const check = hasFlag(options, '--check');
|
|
54
86
|
const dryRun = hasFlag(options, '--dry-run') && !check;
|
|
@@ -67,28 +99,45 @@ export function runSyncPrettierConfig(options) {
|
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
const pkg = parseJsonFile(rootPackageJsonPath, 'Root package.json');
|
|
102
|
+
const toolingBaseline = loadToolingBaseline();
|
|
103
|
+
const requiredPrettierVersion = toolingBaseline.prettierVersion;
|
|
70
104
|
const scripts =
|
|
71
105
|
pkg.scripts && typeof pkg.scripts === 'object' && !Array.isArray(pkg.scripts)
|
|
72
106
|
? { ...pkg.scripts }
|
|
73
107
|
: {};
|
|
108
|
+
const devDependencies =
|
|
109
|
+
pkg.devDependencies &&
|
|
110
|
+
typeof pkg.devDependencies === 'object' &&
|
|
111
|
+
!Array.isArray(pkg.devDependencies)
|
|
112
|
+
? { ...pkg.devDependencies }
|
|
113
|
+
: {};
|
|
74
114
|
|
|
75
115
|
const previousFormat =
|
|
76
116
|
typeof scripts[REQUIRED_FORMAT_SCRIPT_KEY] === 'string'
|
|
77
117
|
? scripts[REQUIRED_FORMAT_SCRIPT_KEY]
|
|
78
118
|
: null;
|
|
119
|
+
const previousPrettierDep =
|
|
120
|
+
typeof devDependencies[REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY] === 'string'
|
|
121
|
+
? devDependencies[REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY]
|
|
122
|
+
: null;
|
|
79
123
|
|
|
80
124
|
const prettierConfigPath = path.resolve(cwd, PRETTIER_CONFIG_FILE);
|
|
81
125
|
const previousPrettierConfig = readFileIfExists(prettierConfigPath);
|
|
82
126
|
|
|
83
127
|
const matchesRequiredFormat = previousFormat === REQUIRED_FORMAT_SCRIPT_VALUE;
|
|
84
128
|
const matchesRequiredPrettierConfig = previousPrettierConfig === REQUIRED_PRETTIER_CONFIG;
|
|
129
|
+
const matchesRequiredPrettierDep = previousPrettierDep === requiredPrettierVersion;
|
|
85
130
|
|
|
86
|
-
const requiresUpdate =
|
|
131
|
+
const requiresUpdate =
|
|
132
|
+
!matchesRequiredFormat || !matchesRequiredPrettierConfig || !matchesRequiredPrettierDep;
|
|
87
133
|
|
|
88
134
|
if (mode === 'sync' && requiresUpdate) {
|
|
89
135
|
scripts[REQUIRED_FORMAT_SCRIPT_KEY] = REQUIRED_FORMAT_SCRIPT_VALUE;
|
|
90
136
|
pkg.scripts = scripts;
|
|
91
137
|
|
|
138
|
+
devDependencies[REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY] = requiredPrettierVersion;
|
|
139
|
+
pkg.devDependencies = devDependencies;
|
|
140
|
+
|
|
92
141
|
fs.writeFileSync(rootPackageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
93
142
|
fs.writeFileSync(prettierConfigPath, REQUIRED_PRETTIER_CONFIG, 'utf8');
|
|
94
143
|
}
|
|
@@ -98,17 +147,22 @@ export function runSyncPrettierConfig(options) {
|
|
|
98
147
|
mode,
|
|
99
148
|
ok: true,
|
|
100
149
|
rootPackageJsonPath,
|
|
150
|
+
toolingBaselinePath: toolingBaseline.toolingBaselinePath,
|
|
101
151
|
required: {
|
|
102
152
|
formatScriptKey: REQUIRED_FORMAT_SCRIPT_KEY,
|
|
103
153
|
formatScriptValue: REQUIRED_FORMAT_SCRIPT_VALUE,
|
|
104
154
|
prettierConfigPath: path.relative(cwd, prettierConfigPath),
|
|
155
|
+
managedDevDependencies: { [REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY]: requiredPrettierVersion },
|
|
105
156
|
},
|
|
106
157
|
status: {
|
|
107
158
|
matchesRequiredFormatBefore: matchesRequiredFormat,
|
|
108
159
|
matchesRequiredPrettierConfigBefore: matchesRequiredPrettierConfig,
|
|
160
|
+
matchesRequiredPrettierDepBefore: matchesRequiredPrettierDep,
|
|
109
161
|
matchesRequiredFormatAfter: requiresUpdate && mode === 'sync' ? true : matchesRequiredFormat,
|
|
110
162
|
matchesRequiredPrettierConfigAfter:
|
|
111
163
|
requiresUpdate && mode === 'sync' ? true : matchesRequiredPrettierConfig,
|
|
164
|
+
matchesRequiredPrettierDepAfter:
|
|
165
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredPrettierDep,
|
|
112
166
|
updated: requiresUpdate && mode === 'sync',
|
|
113
167
|
},
|
|
114
168
|
};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
Usage:
|
|
2
|
-
agent-toolkit sync-
|
|
2
|
+
agent-toolkit sync-git [--cwd <dir>] [--check] [--dry-run]
|
|
3
3
|
[--json <file>]
|
|
4
4
|
|
|
5
5
|
Behavior:
|
|
6
6
|
- Applies organization-required root shared scripts:
|
|
7
7
|
- scripts.produck:baseline = npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd .
|
|
8
|
-
- scripts.produck:
|
|
9
|
-
- scripts.produck:precommit-check = npm run produck:format && npm run produck:lint
|
|
10
|
-
- Applies organization-required root c8 config file:
|
|
11
|
-
- .c8rc.json
|
|
8
|
+
- scripts.produck:commit:check = npm run produck:format && npm run produck:lint
|
|
12
9
|
- Applies organization-required root shared managed devDependencies:
|
|
13
|
-
- devDependencies.c8 = <fixed-version-from-baseline>
|
|
14
10
|
- devDependencies.husky = <fixed-version-from-baseline>
|
|
15
11
|
- devDependencies.lerna = <fixed-version-from-baseline>
|
|
16
12
|
- devDependencies.@produck/agent-toolkit = <latest-version-resolved-at-runtime>
|
|
13
|
+
- Applies organization-required root .gitattributes file (exact replacement)
|
|
14
|
+
- Normalizes line endings for text files
|
|
15
|
+
- Keeps Windows script entrypoints on CRLF
|
|
16
|
+
- Ensures root .gitignore contains all org-baseline required entries
|
|
17
|
+
(downstream can extend; missing entries are appended)
|
|
18
|
+
- Applies organization-required hook files:
|
|
19
|
+
- .husky/pre-commit
|
|
20
|
+
- .husky/commit-msg
|
|
17
21
|
|
|
18
22
|
Rules:
|
|
19
23
|
- --check validates without writing and exits non-zero on mismatch
|