@produck/agent-toolkit 0.9.1 → 0.10.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/bin/agent-toolkit.mjs +8 -0
- package/bin/build-publish-assets.mjs +28 -6
- package/bin/command/main/help.txt +1 -0
- package/bin/command/sync-coverage/index.mjs +37 -3
- package/bin/command/sync-coverage/required-c8-config.json +10 -5
- package/bin/command/sync-format/index.mjs +30 -3
- package/bin/command/sync-git/help.txt +1 -1
- package/bin/command/sync-git/index.mjs +46 -6
- package/bin/command/sync-install/index.mjs +1 -1
- package/bin/command/sync-lint/eslint.config.template.mjs +3 -16
- package/bin/command/sync-lint/index.mjs +43 -9
- package/bin/command/sync-publish/index.mjs +9 -4
- package/bin/command/sync-typescript/help.txt +14 -0
- package/bin/command/sync-typescript/index.mjs +154 -0
- package/package.json +21 -21
- package/publish-assets/eslint.config.template.mjs +3 -16
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +3 -3
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +151 -118
- package/publish-assets/instructions/produck/12-produck-test.instructions.md +92 -0
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +33 -36
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +12 -0
- package/publish-assets/instructions/produck/tooling-version-baseline.json +53 -48
- package/publish-assets/lerna.json +3 -3
- package/publish-assets/prettierrc +9 -1
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -42,6 +42,10 @@ import {
|
|
|
42
42
|
printSyncPublishHelp,
|
|
43
43
|
runSyncPublish,
|
|
44
44
|
} from './command/sync-publish/index.mjs';
|
|
45
|
+
import {
|
|
46
|
+
printSyncTypescriptHelp,
|
|
47
|
+
runSyncTypescript,
|
|
48
|
+
} from './command/sync-typescript/index.mjs';
|
|
45
49
|
import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
|
|
46
50
|
import {
|
|
47
51
|
printValidateCommitMsgHelp,
|
|
@@ -101,6 +105,10 @@ const COMMANDS = {
|
|
|
101
105
|
printHelp: printSyncPublishHelp,
|
|
102
106
|
run: runSyncPublish,
|
|
103
107
|
},
|
|
108
|
+
'sync-typescript': {
|
|
109
|
+
printHelp: printSyncTypescriptHelp,
|
|
110
|
+
run: runSyncTypescript,
|
|
111
|
+
},
|
|
104
112
|
};
|
|
105
113
|
|
|
106
114
|
const DEFAULT_COMMAND = 'enforce-node-baseline';
|
|
@@ -49,6 +49,7 @@ const OUTPUT_LERNA_PATH = path.resolve(
|
|
|
49
49
|
PACKAGE_ROOT,
|
|
50
50
|
'publish-assets/lerna.json',
|
|
51
51
|
);
|
|
52
|
+
const ROOT_PACKAGE_JSON_PATH = path.resolve(REPO_ROOT, 'package.json');
|
|
52
53
|
const LEGACY_OUTPUT_PATH = path.resolve(
|
|
53
54
|
PACKAGE_ROOT,
|
|
54
55
|
'publish-assets/instructions/org.instructions.md',
|
|
@@ -80,6 +81,10 @@ function validateSourceFile(fileName, text) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
function resolveSemverExact(text) {
|
|
85
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
function readAndValidateToolingBaseline() {
|
|
84
89
|
if (!fs.existsSync(SOURCE_TOOLING_BASELINE_PATH)) {
|
|
85
90
|
throw new Error(
|
|
@@ -98,25 +103,40 @@ function readAndValidateToolingBaseline() {
|
|
|
98
103
|
);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
const c8Version = baseline?.tools?.c8?.version;
|
|
102
|
-
const lernaVersion = baseline?.tools?.lerna?.version;
|
|
103
|
-
const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
|
|
104
|
-
|
|
105
106
|
if (typeof baseline.schemaVersion !== 'number') {
|
|
106
107
|
throw new Error(
|
|
107
108
|
`Invalid tooling baseline schemaVersion in: ${SOURCE_TOOLING_BASELINE_PATH}`,
|
|
108
109
|
);
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// Auto-resolve tool versions: only override version when set to "auto"
|
|
113
|
+
const rootPkg = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON_PATH, 'utf8'));
|
|
114
|
+
const devDeps = rootPkg.devDependencies || {};
|
|
115
|
+
|
|
116
|
+
for (const [toolName, entry] of Object.entries(baseline.tools)) {
|
|
117
|
+
if (entry.version === 'auto') {
|
|
118
|
+
const depVersion = devDeps[toolName];
|
|
119
|
+
if (typeof depVersion === 'string' && depVersion.trim()) {
|
|
120
|
+
entry.version = resolveSemverExact(depVersion);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const c8Version = baseline?.tools?.c8?.version;
|
|
126
|
+
const lernaVersion = baseline?.tools?.lerna?.version;
|
|
127
|
+
const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
|
|
128
|
+
|
|
111
129
|
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
112
130
|
throw new Error(
|
|
113
|
-
`Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH}
|
|
131
|
+
`Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH} ` +
|
|
132
|
+
'(c8 must be listed in root package.json devDependencies)',
|
|
114
133
|
);
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
if (typeof lernaVersion !== 'string' || lernaVersion.trim() === '') {
|
|
118
137
|
throw new Error(
|
|
119
|
-
`Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH}
|
|
138
|
+
`Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH} ` +
|
|
139
|
+
'(lerna must be listed in root package.json devDependencies)',
|
|
120
140
|
);
|
|
121
141
|
}
|
|
122
142
|
|
|
@@ -129,6 +149,7 @@ function readAndValidateToolingBaseline() {
|
|
|
129
149
|
);
|
|
130
150
|
}
|
|
131
151
|
|
|
152
|
+
// Dynamically inject @produck/eslint-rules version from its own package.json
|
|
132
153
|
const eslintRulesPkgPath = path.resolve(
|
|
133
154
|
PACKAGE_ROOT,
|
|
134
155
|
'../eslint-rules/package.json',
|
|
@@ -146,6 +167,7 @@ function readAndValidateToolingBaseline() {
|
|
|
146
167
|
};
|
|
147
168
|
}
|
|
148
169
|
}
|
|
170
|
+
|
|
149
171
|
return `${JSON.stringify(baseline, null, 2)}\n`;
|
|
150
172
|
}
|
|
151
173
|
|
|
@@ -21,8 +21,13 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
|
21
21
|
];
|
|
22
22
|
const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
|
|
23
23
|
const REQUIRED_ROOT_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
24
|
-
const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE =
|
|
25
|
-
'c8
|
|
24
|
+
const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE = [
|
|
25
|
+
'c8',
|
|
26
|
+
'--config .c8rc.json',
|
|
27
|
+
'npm run test',
|
|
28
|
+
'--workspaces',
|
|
29
|
+
'--if-present',
|
|
30
|
+
].join(' ');
|
|
26
31
|
const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
27
32
|
const REQUIRED_TEST_SCRIPT_KEY = 'test';
|
|
28
33
|
const DEFAULT_TEST_SCRIPT_VALUE =
|
|
@@ -46,6 +51,35 @@ function parseJsonFile(filePath, label) {
|
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
function resolveSemverExact(text) {
|
|
55
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveToolVersionFromDevDeps(baseline, toolName) {
|
|
59
|
+
// If baseline has a concrete version (not "auto"), use it directly.
|
|
60
|
+
// This is the case when reading the published publish-assets baseline.
|
|
61
|
+
const baselineVersion = String(
|
|
62
|
+
baseline?.tools?.[toolName]?.version || '',
|
|
63
|
+
).trim();
|
|
64
|
+
if (baselineVersion && baselineVersion !== 'auto') {
|
|
65
|
+
return baselineVersion;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fall back to resolving from local root package.json devDependencies.
|
|
69
|
+
// This covers source baseline with version="auto" during local dev.
|
|
70
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
71
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
72
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
73
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
74
|
+
const dep = pkg?.devDependencies?.[toolName];
|
|
75
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
76
|
+
return resolveSemverExact(dep);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
49
83
|
function loadToolingBaseline() {
|
|
50
84
|
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
|
|
51
85
|
(candidatePath) => {
|
|
@@ -71,7 +105,7 @@ function loadToolingBaseline() {
|
|
|
71
105
|
process.exit(2);
|
|
72
106
|
}
|
|
73
107
|
|
|
74
|
-
const c8Version = baseline
|
|
108
|
+
const c8Version = resolveToolVersionFromDevDeps(baseline, 'c8');
|
|
75
109
|
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
76
110
|
console.error(
|
|
77
111
|
`Tooling baseline tools.c8.version must be a non-empty string: ${toolingBaselinePath}`,
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
+
"branches": 99.5,
|
|
2
3
|
"check-coverage": true,
|
|
3
4
|
"exclude": [
|
|
4
5
|
"**/node_modules/**",
|
|
5
6
|
"**/coverage/**",
|
|
6
7
|
"**/dist/**",
|
|
7
8
|
"**/build/**",
|
|
8
|
-
"**/out/**"
|
|
9
|
+
"**/out/**",
|
|
10
|
+
"**/test/**"
|
|
9
11
|
],
|
|
10
|
-
"reporter": ["lcov", "html", "text-summary"],
|
|
11
|
-
"branches": 99.5,
|
|
12
12
|
"functions": 99.5,
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"lines": 99.5,
|
|
14
|
+
"reporter": [
|
|
15
|
+
"lcov",
|
|
16
|
+
"html",
|
|
17
|
+
"text-summary"
|
|
18
|
+
],
|
|
19
|
+
"statements": 99.5
|
|
15
20
|
}
|
|
@@ -99,6 +99,35 @@ function parseJsonFile(filePath, label) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function resolveSemverExact(text) {
|
|
103
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolveToolVersionFromDevDeps(baseline, toolName) {
|
|
107
|
+
// If baseline has a concrete version (not "auto"), use it directly.
|
|
108
|
+
// This is the case when reading the published publish-assets baseline.
|
|
109
|
+
const baselineVersion = String(
|
|
110
|
+
baseline?.tools?.[toolName]?.version || '',
|
|
111
|
+
).trim();
|
|
112
|
+
if (baselineVersion && baselineVersion !== 'auto') {
|
|
113
|
+
return baselineVersion;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Fall back to resolving from local root package.json devDependencies.
|
|
117
|
+
// This covers source baseline with version="auto" during local dev.
|
|
118
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
119
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
120
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
121
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
122
|
+
const dep = pkg?.devDependencies?.[toolName];
|
|
123
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
124
|
+
return resolveSemverExact(dep);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return '';
|
|
129
|
+
}
|
|
130
|
+
|
|
102
131
|
function loadToolingBaseline() {
|
|
103
132
|
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
|
|
104
133
|
(candidatePath) => {
|
|
@@ -117,9 +146,7 @@ function loadToolingBaseline() {
|
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
120
|
-
const prettierVersion =
|
|
121
|
-
baseline?.tools?.prettier?.version || '',
|
|
122
|
-
).trim();
|
|
149
|
+
const prettierVersion = resolveToolVersionFromDevDeps(baseline, 'prettier');
|
|
123
150
|
|
|
124
151
|
if (!prettierVersion) {
|
|
125
152
|
console.error(
|
|
@@ -4,7 +4,7 @@ Usage:
|
|
|
4
4
|
|
|
5
5
|
Behavior:
|
|
6
6
|
- Applies organization-required root shared scripts:
|
|
7
|
-
- scripts.produck:baseline = npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd .
|
|
7
|
+
- scripts.produck:baseline = npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd . && npm run produck:install
|
|
8
8
|
- scripts.produck:commit:check = npm run produck:format && npm run produck:lint
|
|
9
9
|
- scripts.prepare = husky
|
|
10
10
|
- Applies organization-required root shared managed devDependencies:
|
|
@@ -29,11 +29,22 @@ const HUSKY_DIR = '.husky';
|
|
|
29
29
|
const PRE_COMMIT_HOOK_FILE = 'pre-commit';
|
|
30
30
|
const COMMIT_MSG_HOOK_FILE = 'commit-msg';
|
|
31
31
|
const REQUIRED_BASELINE_SCRIPT_KEY = 'produck:baseline';
|
|
32
|
-
const REQUIRED_BASELINE_SCRIPT_VALUE =
|
|
33
|
-
|
|
32
|
+
const REQUIRED_BASELINE_SCRIPT_VALUE = [
|
|
33
|
+
[
|
|
34
|
+
'npm exec',
|
|
35
|
+
'--package=@produck/agent-toolkit@latest',
|
|
36
|
+
'--',
|
|
37
|
+
'agent-toolkit',
|
|
38
|
+
'enforce-node-baseline',
|
|
39
|
+
'--cwd .',
|
|
40
|
+
].join(' '),
|
|
41
|
+
'npm run produck:install',
|
|
42
|
+
].join(' && ');
|
|
34
43
|
const REQUIRED_COMMIT_CHECK_SCRIPT_KEY = 'produck:commit:check';
|
|
35
|
-
const REQUIRED_COMMIT_CHECK_SCRIPT_VALUE =
|
|
36
|
-
'npm run produck:format
|
|
44
|
+
const REQUIRED_COMMIT_CHECK_SCRIPT_VALUE = [
|
|
45
|
+
'npm run produck:format',
|
|
46
|
+
'npm run produck:lint',
|
|
47
|
+
].join(' && ');
|
|
37
48
|
const REQUIRED_PREPARE_SCRIPT_KEY = 'prepare';
|
|
38
49
|
const REQUIRED_PREPARE_SCRIPT_VALUE = 'husky';
|
|
39
50
|
|
|
@@ -105,6 +116,35 @@ function getRequiredToolkitDevDependency() {
|
|
|
105
116
|
return version;
|
|
106
117
|
}
|
|
107
118
|
|
|
119
|
+
function resolveSemverExact(text) {
|
|
120
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveToolVersionFromDevDeps(baseline, toolName) {
|
|
124
|
+
// If baseline has a concrete version (not "auto"), use it directly.
|
|
125
|
+
// This is the case when reading the published publish-assets baseline.
|
|
126
|
+
const baselineVersion = String(
|
|
127
|
+
baseline?.tools?.[toolName]?.version || '',
|
|
128
|
+
).trim();
|
|
129
|
+
if (baselineVersion && baselineVersion !== 'auto') {
|
|
130
|
+
return baselineVersion;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fall back to resolving from local root package.json devDependencies.
|
|
134
|
+
// This covers source baseline with version="auto" during local dev.
|
|
135
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
136
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
137
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
138
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
139
|
+
const dep = pkg?.devDependencies?.[toolName];
|
|
140
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
141
|
+
return resolveSemverExact(dep);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
|
|
108
148
|
function loadToolingBaseline() {
|
|
109
149
|
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
|
|
110
150
|
(candidatePath) => {
|
|
@@ -123,8 +163,8 @@ function loadToolingBaseline() {
|
|
|
123
163
|
}
|
|
124
164
|
|
|
125
165
|
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
126
|
-
const huskyVersion =
|
|
127
|
-
const lernaVersion =
|
|
166
|
+
const huskyVersion = resolveToolVersionFromDevDeps(baseline, 'husky');
|
|
167
|
+
const lernaVersion = resolveToolVersionFromDevDeps(baseline, 'lerna');
|
|
128
168
|
|
|
129
169
|
if (!huskyVersion || !lernaVersion) {
|
|
130
170
|
console.error(
|
|
@@ -9,7 +9,7 @@ const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
10
10
|
const LEGACY_INSTALL_SCRIPT_KEY = 'deps:install';
|
|
11
11
|
const REQUIRED_INSTALL_SCRIPT_KEY = 'produck:install';
|
|
12
|
-
const REQUIRED_INSTALL_SCRIPT_VALUE = 'npm -v
|
|
12
|
+
const REQUIRED_INSTALL_SCRIPT_VALUE = ['npm -v', 'npm install'].join(' && ');
|
|
13
13
|
|
|
14
14
|
export function printSyncInstallHelp() {
|
|
15
15
|
printTextResource(HELP_FILE);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import js from '@eslint/js';
|
|
2
2
|
import globals from 'globals';
|
|
3
3
|
import tseslint from 'typescript-eslint';
|
|
4
|
-
import json from '@eslint/json';
|
|
5
|
-
import markdown from '@eslint/markdown';
|
|
6
4
|
import { defineConfig } from 'eslint/config';
|
|
7
5
|
import * as ProduckRule from '@produck/eslint-rules';
|
|
8
6
|
|
|
@@ -14,19 +12,8 @@ export default defineConfig([
|
|
|
14
12
|
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
|
15
13
|
},
|
|
16
14
|
tseslint.configs.recommended,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
plugins: { json },
|
|
21
|
-
language: 'json/json',
|
|
22
|
-
extends: ['json/recommended'],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
files: ['**/*.md'],
|
|
26
|
-
plugins: { markdown },
|
|
27
|
-
language: 'markdown/gfm',
|
|
28
|
-
extends: ['markdown/recommended'],
|
|
29
|
-
},
|
|
30
|
-
ProduckRule.config,
|
|
15
|
+
ProduckRule.config.ecma,
|
|
16
|
+
ProduckRule.config.json,
|
|
17
|
+
ProduckRule.config.markdown,
|
|
31
18
|
ProduckRule.excludeGitIgnore(import.meta.url),
|
|
32
19
|
]);
|
|
@@ -21,6 +21,12 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
|
21
21
|
];
|
|
22
22
|
const ESLINT_RULES_PACKAGE_NAME = '@produck/eslint-rules';
|
|
23
23
|
const ESLINT_CONFIG_FILE = 'eslint.config.mjs';
|
|
24
|
+
const REQUIRED_ESLINT_ENTRIES = [
|
|
25
|
+
'ProduckRule.config.ecma',
|
|
26
|
+
'ProduckRule.config.json',
|
|
27
|
+
'ProduckRule.config.markdown',
|
|
28
|
+
'ProduckRule.excludeGitIgnore(import.meta.url)',
|
|
29
|
+
];
|
|
24
30
|
|
|
25
31
|
const REQUIRED_LINT_SCRIPT_KEY = 'produck:lint';
|
|
26
32
|
const REQUIRED_LINT_SCRIPT_VALUE = 'eslint --fix . --max-warnings=0';
|
|
@@ -122,16 +128,38 @@ function getRequiredEslintDevDependencies() {
|
|
|
122
128
|
eslintRulesVersion = v;
|
|
123
129
|
}
|
|
124
130
|
|
|
131
|
+
function resolveSemverExact(text) {
|
|
132
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveToolVersionFromDevDeps(name) {
|
|
136
|
+
// If baseline has a concrete version (not "auto"), use it directly.
|
|
137
|
+
// This is the case when reading the published publish-assets baseline.
|
|
138
|
+
const entry = baseline?.tools?.[name];
|
|
139
|
+
const v = typeof entry?.version === 'string' ? entry.version.trim() : '';
|
|
140
|
+
if (v && v !== 'auto') {
|
|
141
|
+
return v;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Fall back to resolving from local root package.json devDependencies.
|
|
145
|
+
// This covers source baseline with version="auto" during local dev.
|
|
146
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
147
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
148
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
149
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
150
|
+
const dep = pkg?.devDependencies?.[name];
|
|
151
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
152
|
+
return resolveSemverExact(dep);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
158
|
+
|
|
125
159
|
const deps = { [ESLINT_RULES_PACKAGE_NAME]: eslintRulesVersion };
|
|
126
160
|
|
|
127
161
|
for (const name of ESLINT_TOOLING_PACKAGE_NAMES) {
|
|
128
|
-
const
|
|
129
|
-
const v =
|
|
130
|
-
typeof entry?.version === 'string'
|
|
131
|
-
? entry.version.trim()
|
|
132
|
-
: /* c8 ignore next */
|
|
133
|
-
'';
|
|
134
|
-
/* c8 ignore next 6 */
|
|
162
|
+
const v = resolveToolVersionFromDevDeps(name);
|
|
135
163
|
if (!v) {
|
|
136
164
|
console.error(
|
|
137
165
|
`Tooling baseline tools["${name}"].version must be a non-empty string: ${toolingBaselinePath}`,
|
|
@@ -169,7 +197,7 @@ function patchEslintConfig(existing) {
|
|
|
169
197
|
}
|
|
170
198
|
|
|
171
199
|
output =
|
|
172
|
-
`${output.slice(0, exportEnd)} ProduckRule.config,\n ProduckRule.excludeGitIgnore(import.meta.url),\n` +
|
|
200
|
+
`${output.slice(0, exportEnd)} ProduckRule.config.ecma,\n ProduckRule.config.json,\n ProduckRule.config.markdown,\n ProduckRule.excludeGitIgnore(import.meta.url),\n` +
|
|
173
201
|
output.slice(exportEnd);
|
|
174
202
|
|
|
175
203
|
if (!output.endsWith('\n')) {
|
|
@@ -235,8 +263,14 @@ export function runSyncLint(options) {
|
|
|
235
263
|
nextEslintConfigText = REQUIRED_ESLINT_CONFIG;
|
|
236
264
|
} else if (previousEslintConfig === REQUIRED_ESLINT_CONFIG) {
|
|
237
265
|
matchesRequiredEslintConfig = true;
|
|
238
|
-
} else if (
|
|
266
|
+
} else if (
|
|
267
|
+
previousEslintConfig.includes(ESLINT_RULES_PACKAGE_NAME) &&
|
|
268
|
+
REQUIRED_ESLINT_ENTRIES.every((e) => previousEslintConfig.includes(e))
|
|
269
|
+
) {
|
|
239
270
|
matchesRequiredEslintConfig = true;
|
|
271
|
+
} else if (previousEslintConfig.includes(ESLINT_RULES_PACKAGE_NAME)) {
|
|
272
|
+
eslintConfigAction = 'replaced';
|
|
273
|
+
nextEslintConfigText = REQUIRED_ESLINT_CONFIG;
|
|
240
274
|
} else {
|
|
241
275
|
const patched = patchEslintConfig(previousEslintConfig);
|
|
242
276
|
if (patched.ok) {
|
|
@@ -16,11 +16,16 @@ const LERNA_TEMPLATE_CANDIDATE_PATHS = [
|
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
const REQUIRED_PUBLISH_CHECK_SCRIPT_KEY = 'produck:publish:check';
|
|
19
|
-
const REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE =
|
|
20
|
-
'npm run produck:install
|
|
19
|
+
const REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE = [
|
|
20
|
+
'npm run produck:install',
|
|
21
|
+
'npm run produck:coverage',
|
|
22
|
+
'npm run produck:commit:check',
|
|
23
|
+
].join(' && ');
|
|
21
24
|
const REQUIRED_PUBLISH_SCRIPT_KEY = 'produck:publish';
|
|
22
|
-
const REQUIRED_PUBLISH_SCRIPT_VALUE =
|
|
23
|
-
'npm run produck:publish:check
|
|
25
|
+
const REQUIRED_PUBLISH_SCRIPT_VALUE = [
|
|
26
|
+
'npm run produck:publish:check',
|
|
27
|
+
'npm run publish --',
|
|
28
|
+
].join(' && ');
|
|
24
29
|
const REQUIRED_LERNA_VERSION_COMMIT_HOOKS = false;
|
|
25
30
|
|
|
26
31
|
export function printSyncPublishHelp() {
|
|
@@ -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
|
+
}
|