@kb-labs/devkit 2.94.0 → 2.96.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/assets/claude/skills/kb-labs-create-plugin/SKILL.md +2 -2
- package/assets/claude/skills/kb-labs-troubleshoot/SKILL.md +3 -7
- package/assets/claude/skills/kb-labs-update/SKILL.md +3 -2
- package/bin/devkit-check-plugins.mjs +293 -0
- package/bin/devkit-ci.mjs +5 -0
- package/package.json +27 -24
- package/tsup/node.js +59 -1
- package/vitest/cli.js +18 -0
- package/vitest/node.js +21 -7
- package/vitest/react.js +5 -6
|
@@ -111,14 +111,14 @@ The CLI caches plugin manifests. After building a new plugin, always clear the
|
|
|
111
111
|
cache — otherwise the new commands will not be discovered.
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
pnpm kb marketplace
|
|
114
|
+
pnpm kb marketplace plugins refresh
|
|
115
115
|
pnpm kb <name>:hello --help
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
If the command is not found, clear the cache deeply and retry:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
pnpm kb marketplace
|
|
121
|
+
pnpm kb marketplace plugins refresh
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
## Step 8: Register in the workspace (only if needed)
|
|
@@ -22,14 +22,10 @@ git status of key files. Most problems surface here.
|
|
|
22
22
|
|
|
23
23
|
Most common cause: **stale plugin registry cache after building a plugin**.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
pnpm kb marketplace clear-cache
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
If that does not help, deep clear:
|
|
25
|
+
The cache auto-invalidates on rebuild. If commands are still missing, force-reset:
|
|
30
26
|
|
|
31
27
|
```bash
|
|
32
|
-
pnpm kb marketplace
|
|
28
|
+
pnpm kb marketplace plugins refresh
|
|
33
29
|
```
|
|
34
30
|
|
|
35
31
|
Then retry the command. If it is still missing:
|
|
@@ -53,7 +49,7 @@ Fix:
|
|
|
53
49
|
```bash
|
|
54
50
|
pkill -9 -f "host-agent-app/dist/index.js"
|
|
55
51
|
ps aux | grep host-agent | grep -v grep # must be empty
|
|
56
|
-
pnpm kb marketplace
|
|
52
|
+
pnpm kb marketplace plugins refresh
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
Then retry the command.
|
|
@@ -61,10 +61,11 @@ pnpm kb-dev status
|
|
|
61
61
|
|
|
62
62
|
## Step 5: Clear stale caches
|
|
63
63
|
|
|
64
|
-
After an update, plugin manifests may have changed
|
|
64
|
+
After an update, plugin manifests may have changed. The cache auto-invalidates,
|
|
65
|
+
but you can force-reset if needed:
|
|
65
66
|
|
|
66
67
|
```bash
|
|
67
|
-
pnpm kb marketplace
|
|
68
|
+
pnpm kb marketplace plugins refresh
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
## If the update fails partway
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* KB Labs DevKit - Plugin Entry Checker
|
|
5
|
+
*
|
|
6
|
+
* Validates plugin entry packages against the reference structure (devlink-entry).
|
|
7
|
+
* A package is considered a plugin entry if it has a "kb.manifest" field.
|
|
8
|
+
*
|
|
9
|
+
* Checks:
|
|
10
|
+
* 1. sideEffects: false
|
|
11
|
+
* 2. description field
|
|
12
|
+
* 3. main + types top-level fields
|
|
13
|
+
* 4. ./plugin-manifest in exports
|
|
14
|
+
* 5. kb.manifest field
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* kb-devkit-check-plugins # Check all plugin entries
|
|
18
|
+
* kb-devkit-check-plugins --fix # Auto-fix what's possible
|
|
19
|
+
* kb-devkit-check-plugins --package @kb-labs/review-entry
|
|
20
|
+
* kb-devkit-check-plugins --verbose
|
|
21
|
+
* kb-devkit-check-plugins --json
|
|
22
|
+
* kb-devkit-check-plugins --ci
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { writeFile, readFile } from 'node:fs/promises';
|
|
26
|
+
import { join, dirname, relative } from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
29
|
+
|
|
30
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
|
|
32
|
+
// ─── discovery ───────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
function findWorkspaceRoot(cwd = process.cwd()) {
|
|
35
|
+
let current = cwd;
|
|
36
|
+
while (current !== '/') {
|
|
37
|
+
if (existsSync(join(current, 'pnpm-workspace.yaml'))) return current;
|
|
38
|
+
current = dirname(current);
|
|
39
|
+
}
|
|
40
|
+
return cwd;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectPackages(root) {
|
|
44
|
+
const packages = [];
|
|
45
|
+
|
|
46
|
+
const walk = (dir) => {
|
|
47
|
+
if (dir.includes('node_modules') || dir.includes('/dist')) return;
|
|
48
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (!entry.isDirectory()) continue;
|
|
51
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
52
|
+
const fullPath = join(dir, entry.name);
|
|
53
|
+
const pkgJsonPath = join(fullPath, 'package.json');
|
|
54
|
+
if (existsSync(pkgJsonPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
57
|
+
if (pkgJson.name?.startsWith('@kb-labs/')) {
|
|
58
|
+
packages.push({ name: pkgJson.name, path: fullPath, pkgJson, pkgJsonPath });
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
walk(fullPath);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
walk(root);
|
|
67
|
+
return packages;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Plugin entries live in plugins/*/entry/ (not adapters, not app-packages, not daemons).
|
|
71
|
+
// Adapter packages also carry kb.manifest but are a separate category.
|
|
72
|
+
function isPluginEntry(pkg) {
|
|
73
|
+
if (!pkg.pkgJson.kb?.manifest) return false;
|
|
74
|
+
const normalised = pkg.path.replace(/\\/g, '/');
|
|
75
|
+
return /\/plugins\/[^/]+\/entry$/.test(normalised);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── checker ─────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function checkPlugin(pkg) {
|
|
81
|
+
const issues = [];
|
|
82
|
+
const warnings = [];
|
|
83
|
+
const { pkgJson } = pkg;
|
|
84
|
+
|
|
85
|
+
// 1. sideEffects: false
|
|
86
|
+
if (!('sideEffects' in pkgJson)) {
|
|
87
|
+
issues.push({
|
|
88
|
+
type: 'missing-sideEffects',
|
|
89
|
+
severity: 'error',
|
|
90
|
+
message: 'Missing "sideEffects": false — required for tree-shaking',
|
|
91
|
+
fix: () => { pkgJson.sideEffects = false; },
|
|
92
|
+
});
|
|
93
|
+
} else if (pkgJson.sideEffects !== false) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
type: 'wrong-sideEffects',
|
|
96
|
+
severity: 'warning',
|
|
97
|
+
message: `"sideEffects" is ${JSON.stringify(pkgJson.sideEffects)}, expected false`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 2. description
|
|
102
|
+
if (!pkgJson.description) {
|
|
103
|
+
issues.push({
|
|
104
|
+
type: 'missing-description',
|
|
105
|
+
severity: 'error',
|
|
106
|
+
message: 'Missing "description" field',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. main + types (top-level, for older tooling compatibility)
|
|
111
|
+
if (!pkgJson.main) {
|
|
112
|
+
issues.push({
|
|
113
|
+
type: 'missing-main',
|
|
114
|
+
severity: 'error',
|
|
115
|
+
message: 'Missing top-level "main" field',
|
|
116
|
+
fix: () => { pkgJson.main = './dist/index.js'; },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (!pkgJson.types) {
|
|
120
|
+
issues.push({
|
|
121
|
+
type: 'missing-types',
|
|
122
|
+
severity: 'error',
|
|
123
|
+
message: 'Missing top-level "types" field',
|
|
124
|
+
fix: () => { pkgJson.types = './dist/index.d.ts'; },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 4. ./plugin-manifest in exports
|
|
129
|
+
const exports_ = pkgJson.exports || {};
|
|
130
|
+
if (!('./plugin-manifest' in exports_)) {
|
|
131
|
+
issues.push({
|
|
132
|
+
type: 'missing-plugin-manifest-export',
|
|
133
|
+
severity: 'error',
|
|
134
|
+
message: 'Missing "./plugin-manifest" in exports — required for plugin discovery',
|
|
135
|
+
fix: () => {
|
|
136
|
+
if (!pkgJson.exports) pkgJson.exports = {};
|
|
137
|
+
pkgJson.exports['./plugin-manifest'] = {
|
|
138
|
+
types: './dist/manifest.d.ts',
|
|
139
|
+
import: './dist/manifest.js',
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 5. kb.manifest
|
|
146
|
+
if (!pkgJson.kb?.manifest) {
|
|
147
|
+
issues.push({
|
|
148
|
+
type: 'missing-kb-manifest',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
message: 'Missing "kb.manifest" field — required for plugin registration',
|
|
151
|
+
fix: () => {
|
|
152
|
+
pkgJson.kb = { ...(pkgJson.kb || {}), manifest: './dist/manifest.js' };
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { issues, warnings };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── fixer ───────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
async function fixPlugin(pkg, issues) {
|
|
163
|
+
const fixable = issues.filter((i) => typeof i.fix === 'function');
|
|
164
|
+
if (fixable.length === 0) return false;
|
|
165
|
+
|
|
166
|
+
for (const issue of fixable) issue.fix();
|
|
167
|
+
|
|
168
|
+
await writeFile(pkg.pkgJsonPath, JSON.stringify(pkg.pkgJson, null, 2) + '\n', 'utf-8');
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─── main ────────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
async function main() {
|
|
175
|
+
const args = process.argv.slice(2);
|
|
176
|
+
const flags = {
|
|
177
|
+
fix: args.includes('--fix'),
|
|
178
|
+
verbose: args.includes('--verbose'),
|
|
179
|
+
json: args.includes('--json'),
|
|
180
|
+
ci: args.includes('--ci'),
|
|
181
|
+
package: args.find((a) => a.startsWith('--package='))?.split('=')[1],
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const root = findWorkspaceRoot();
|
|
185
|
+
let packages = collectPackages(root).filter(isPluginEntry);
|
|
186
|
+
|
|
187
|
+
if (flags.package) {
|
|
188
|
+
packages = packages.filter((p) => p.name === flags.package);
|
|
189
|
+
if (packages.length === 0) {
|
|
190
|
+
console.error(`\n❌ Plugin entry package "${flags.package}" not found\n`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!flags.json && !flags.ci) {
|
|
196
|
+
console.log('\n🔌 KB Labs Plugin Entry Checker\n');
|
|
197
|
+
console.log(`Found ${packages.length} plugin entry package(s)\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const results = [];
|
|
201
|
+
let totalIssues = 0;
|
|
202
|
+
let totalWarnings = 0;
|
|
203
|
+
|
|
204
|
+
for (const pkg of packages) {
|
|
205
|
+
const { issues, warnings } = checkPlugin(pkg);
|
|
206
|
+
totalIssues += issues.length;
|
|
207
|
+
totalWarnings += warnings.length;
|
|
208
|
+
|
|
209
|
+
let fixed = false;
|
|
210
|
+
if (flags.fix && issues.length > 0) {
|
|
211
|
+
fixed = await fixPlugin(pkg, issues);
|
|
212
|
+
}
|
|
213
|
+
results.push({ pkg, issues, warnings, fixed });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── json output ────────────────────────────────────────────────────────────
|
|
217
|
+
if (flags.json) {
|
|
218
|
+
console.log(JSON.stringify({
|
|
219
|
+
packages: results.map((r) => ({
|
|
220
|
+
name: r.pkg.name,
|
|
221
|
+
path: relative(root, r.pkg.path),
|
|
222
|
+
issues: r.issues.length,
|
|
223
|
+
warnings: r.warnings.length,
|
|
224
|
+
fixed: r.fixed,
|
|
225
|
+
details: {
|
|
226
|
+
issues: r.issues.map(({ fix: _fix, ...rest }) => rest),
|
|
227
|
+
warnings: r.warnings,
|
|
228
|
+
},
|
|
229
|
+
})),
|
|
230
|
+
summary: {
|
|
231
|
+
total: packages.length,
|
|
232
|
+
withIssues: results.filter((r) => r.issues.length > 0).length,
|
|
233
|
+
withWarnings: results.filter((r) => r.warnings.length > 0).length,
|
|
234
|
+
fixed: results.filter((r) => r.fixed).length,
|
|
235
|
+
},
|
|
236
|
+
}, null, 2));
|
|
237
|
+
if (flags.ci && totalIssues > 0) process.exit(1);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── human output ───────────────────────────────────────────────────────────
|
|
242
|
+
if (totalIssues > 0) {
|
|
243
|
+
const affected = results.filter((r) => r.issues.length > 0);
|
|
244
|
+
console.log(`🔴 Issues: ${totalIssues} error(s) in ${affected.length} package(s)\n`);
|
|
245
|
+
for (const { pkg, issues } of affected) {
|
|
246
|
+
console.log(` ${pkg.name}`);
|
|
247
|
+
for (const issue of issues) {
|
|
248
|
+
const fixable = typeof issue.fix === 'function' ? ' [auto-fixable]' : ' [manual]';
|
|
249
|
+
console.log(` ${issue.message}${flags.verbose ? fixable : ''}`);
|
|
250
|
+
}
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (totalWarnings > 0 && flags.verbose) {
|
|
256
|
+
console.log(`⚠️ Warnings: ${totalWarnings} non-critical issue(s)\n`);
|
|
257
|
+
for (const { pkg, warnings } of results.filter((r) => r.warnings.length > 0)) {
|
|
258
|
+
console.log(` ${pkg.name}`);
|
|
259
|
+
for (const w of warnings) console.log(` ${w.message}`);
|
|
260
|
+
console.log();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (totalIssues === 0 && totalWarnings === 0) {
|
|
265
|
+
console.log('✅ All plugin entry packages are compliant!\n');
|
|
266
|
+
} else if (flags.fix) {
|
|
267
|
+
const fixedCount = results.filter((r) => r.fixed).length;
|
|
268
|
+
const unfixable = results.filter((r) =>
|
|
269
|
+
r.issues.some((i) => typeof i.fix !== 'function')
|
|
270
|
+
);
|
|
271
|
+
console.log(`✅ Fixed ${fixedCount} package(s)\n`);
|
|
272
|
+
if (unfixable.length > 0) {
|
|
273
|
+
console.log('⚠️ Manual fixes still required:\n');
|
|
274
|
+
for (const { pkg, issues } of unfixable) {
|
|
275
|
+
const manual = issues.filter((i) => typeof i.fix !== 'function');
|
|
276
|
+
if (manual.length === 0) continue;
|
|
277
|
+
console.log(` ${pkg.name}`);
|
|
278
|
+
for (const issue of manual) console.log(` ${issue.message}`);
|
|
279
|
+
console.log();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
console.log('\n💡 Run with --fix to automatically fix auto-fixable issues\n');
|
|
284
|
+
console.log(' Note: "description" requires a manual fix\n');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (flags.ci && totalIssues > 0) process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
main().catch((err) => {
|
|
291
|
+
console.error('Error:', err.message);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
});
|
package/bin/devkit-ci.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kb-labs/devkit",
|
|
3
3
|
"description": "Shared developer toolkit for KB Labs projects: TS/ESLint/Prettier/Vitest/Tsup presets and reusable GitHub Actions.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.96.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
"./vitest/": "./vitest/",
|
|
27
27
|
"./vitest/node.js": "./vitest/node.js",
|
|
28
28
|
"./vitest/react.js": "./vitest/react.js",
|
|
29
|
+
"./vitest/cli.js": "./vitest/cli.js",
|
|
29
30
|
"./vitest/node": "./vitest/node.js",
|
|
30
31
|
"./vitest/react": "./vitest/react.js",
|
|
32
|
+
"./vitest/cli": "./vitest/cli.js",
|
|
31
33
|
"./vite/": "./vite/",
|
|
32
34
|
"./vite/react-app.js": "./vite/react-app.js",
|
|
33
35
|
"./vite/react-app": "./vite/react-app.js",
|
|
@@ -56,35 +58,36 @@
|
|
|
56
58
|
"./sync": "./sync/index.mjs"
|
|
57
59
|
},
|
|
58
60
|
"bin": {
|
|
59
|
-
"kb-devkit-
|
|
60
|
-
"kb-devkit-
|
|
61
|
-
"kb-devkit-
|
|
62
|
-
"kb-devkit-validate-naming": "./bin/devkit-validate-naming.mjs",
|
|
63
|
-
"kb-devkit-check-imports": "./bin/devkit-check-imports.mjs",
|
|
64
|
-
"kb-devkit-check-exports": "./bin/devkit-check-exports.mjs",
|
|
65
|
-
"kb-devkit-check-duplicates": "./bin/devkit-check-duplicates.mjs",
|
|
66
|
-
"kb-devkit-check-structure": "./bin/devkit-check-structure.mjs",
|
|
61
|
+
"kb-devkit-architecture": "./bin/devkit-architecture.mjs",
|
|
62
|
+
"kb-devkit-build-order": "./bin/devkit-build-order.mjs",
|
|
63
|
+
"kb-devkit-check-build-readiness": "./bin/devkit-check-build-readiness.mjs",
|
|
67
64
|
"kb-devkit-check-commands": "./bin/devkit-check-commands.mjs",
|
|
68
|
-
"kb-devkit-check-types": "./bin/devkit-check-types.mjs",
|
|
69
65
|
"kb-devkit-check-configs": "./bin/devkit-check-configs.mjs",
|
|
66
|
+
"kb-devkit-check-deprecated": "./bin/devkit-check-deprecated.mjs",
|
|
67
|
+
"kb-devkit-check-duplicates": "./bin/devkit-check-duplicates.mjs",
|
|
68
|
+
"kb-devkit-check-exports": "./bin/devkit-check-exports.mjs",
|
|
69
|
+
"kb-devkit-check-imports": "./bin/devkit-check-imports.mjs",
|
|
70
|
+
"kb-devkit-check-paths": "./bin/devkit-check-paths.mjs",
|
|
71
|
+
"kb-devkit-check-plugins": "./bin/devkit-check-plugins.mjs",
|
|
70
72
|
"kb-devkit-check-scripts": "./bin/devkit-check-scripts.mjs",
|
|
71
|
-
"kb-devkit-
|
|
72
|
-
"kb-devkit-types
|
|
73
|
-
"kb-devkit-build-order": "./bin/devkit-build-order.mjs",
|
|
74
|
-
"kb-devkit-types-order": "./bin/devkit-types-order.mjs",
|
|
75
|
-
"kb-devkit-visualize": "./bin/devkit-visualize.mjs",
|
|
73
|
+
"kb-devkit-check-structure": "./bin/devkit-check-structure.mjs",
|
|
74
|
+
"kb-devkit-check-types": "./bin/devkit-check-types.mjs",
|
|
76
75
|
"kb-devkit-ci": "./bin/devkit-ci.mjs",
|
|
76
|
+
"kb-devkit-core-gate": "./bin/devkit-core-gate.mjs",
|
|
77
77
|
"kb-devkit-fix-deps": "./bin/devkit-fix-deps.mjs",
|
|
78
|
-
"kb-devkit-stats": "./bin/devkit-stats.mjs",
|
|
79
|
-
"kb-devkit-check-paths": "./bin/devkit-check-paths.mjs",
|
|
80
|
-
"kb-devkit-architecture": "./bin/devkit-architecture.mjs",
|
|
81
78
|
"kb-devkit-freshness": "./bin/devkit-freshness.mjs",
|
|
82
|
-
"kb-devkit-check-deprecated": "./bin/devkit-check-deprecated.mjs",
|
|
83
|
-
"kb-devkit-check-build-readiness": "./bin/devkit-check-build-readiness.mjs",
|
|
84
79
|
"kb-devkit-health": "./bin/devkit-health.mjs",
|
|
80
|
+
"kb-devkit-migrate-configs": "./bin/devkit-migrate-configs.mjs",
|
|
81
|
+
"kb-devkit-paths": "./bin/devkit-paths.mjs",
|
|
85
82
|
"kb-devkit-qa": "./bin/kb-devkit-qa.mjs",
|
|
86
83
|
"kb-devkit-qa-history": "./bin/kb-devkit-qa-history.mjs",
|
|
87
|
-
"kb-devkit-
|
|
84
|
+
"kb-devkit-stats": "./bin/devkit-stats.mjs",
|
|
85
|
+
"kb-devkit-sync": "./bin/devkit-sync.mjs",
|
|
86
|
+
"kb-devkit-tsup-external": "./bin/devkit-tsup-external.mjs",
|
|
87
|
+
"kb-devkit-types-audit": "./bin/devkit-types-audit.mjs",
|
|
88
|
+
"kb-devkit-types-order": "./bin/devkit-types-order.mjs",
|
|
89
|
+
"kb-devkit-validate-naming": "./bin/devkit-validate-naming.mjs",
|
|
90
|
+
"kb-devkit-visualize": "./bin/devkit-visualize.mjs"
|
|
88
91
|
},
|
|
89
92
|
"files": [
|
|
90
93
|
"agents",
|
|
@@ -127,8 +130,8 @@
|
|
|
127
130
|
"tsup": "^8.5.0",
|
|
128
131
|
"typescript": "^5.9.2",
|
|
129
132
|
"typescript-eslint": "^8.44.0",
|
|
130
|
-
"vitest": "^3.2.
|
|
131
|
-
"@kb-labs/devkit": "2.
|
|
133
|
+
"vitest": "^3.2.6",
|
|
134
|
+
"@kb-labs/devkit": "2.96.0"
|
|
132
135
|
},
|
|
133
136
|
"engines": {
|
|
134
137
|
"node": ">=20.0.0",
|
|
@@ -140,7 +143,7 @@
|
|
|
140
143
|
"tsup": "^8.5.0",
|
|
141
144
|
"typescript": "^5.9.2",
|
|
142
145
|
"typescript-eslint": "^8.44.0",
|
|
143
|
-
"vitest": "^3.2.
|
|
146
|
+
"vitest": "^3.2.6"
|
|
144
147
|
},
|
|
145
148
|
"peerDependenciesMeta": {
|
|
146
149
|
"eslint-plugin-import": {
|
package/tsup/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineConfig } from 'tsup'
|
|
2
2
|
import { readTsupExternalSync } from './external-sync.mjs'
|
|
3
|
-
import { readFileSync, existsSync } from 'node:fs'
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -40,6 +40,61 @@ function resolveEntryFromExports() {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the built manifest JS path from `pkg.kb.manifest`.
|
|
45
|
+
* e.g. pkg.kb.manifest = "./dist/manifest.js" → "<cwd>/dist/manifest.js"
|
|
46
|
+
*/
|
|
47
|
+
function resolveManifestDistPath() {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf8'))
|
|
50
|
+
if (typeof pkg.kb?.manifest === 'string') {
|
|
51
|
+
return join(process.cwd(), pkg.kb.manifest.replace(/^\.\//, ''))
|
|
52
|
+
}
|
|
53
|
+
} catch { /* nothing to emit */ }
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* tsup `onSuccess`: emit dist/manifest.json by reading the compiled
|
|
59
|
+
* dist/manifest.js as TEXT and evaluating the exported object.
|
|
60
|
+
*
|
|
61
|
+
* Why not dynamic `import()`?
|
|
62
|
+
* In multi-entry tsup builds `onSuccess` fires while Node's module cache may
|
|
63
|
+
* still hold a stale (empty) version of dist/manifest.js from an earlier
|
|
64
|
+
* watch-mode iteration, or the file descriptors may not be fully flushed.
|
|
65
|
+
* Using `import()` with a cache-bust URL is unreliable in those cases —
|
|
66
|
+
* it returned an empty module in CI, producing a 0-byte manifest.json that
|
|
67
|
+
* kb-create rejected with "unexpected end of JSON input".
|
|
68
|
+
*
|
|
69
|
+
* Reading the COMPILED JS as text is safe: tsup has already written the file
|
|
70
|
+
* before invoking `onSuccess`, and we're parsing plain JavaScript (no type
|
|
71
|
+
* annotations) with a small Function() eval — controlled, no user input.
|
|
72
|
+
*/
|
|
73
|
+
function emitManifestJson() {
|
|
74
|
+
const distPath = resolveManifestDistPath()
|
|
75
|
+
if (!distPath || !existsSync(distPath)) return
|
|
76
|
+
try {
|
|
77
|
+
const js = readFileSync(distPath, 'utf8')
|
|
78
|
+
// tsup ESM output pattern (from inspecting actual built files):
|
|
79
|
+
// var manifest = { schema: "kb.service/1", id: "...", ... };
|
|
80
|
+
// var manifest_default = manifest;
|
|
81
|
+
// export { manifest_default as default, manifest };
|
|
82
|
+
// Capture everything between `var manifest =` and the next `var manifest_default`.
|
|
83
|
+
const match = js.match(/var\s+manifest\s*=\s*(\{[\s\S]*?\n\});\s*\nvar\s+manifest_default/)
|
|
84
|
+
if (!match) return
|
|
85
|
+
// eslint-disable-next-line no-new-func
|
|
86
|
+
const obj = new Function(`"use strict"; return (${match[1]})`)()
|
|
87
|
+
if (!obj || typeof obj !== 'object') return
|
|
88
|
+
// Scope to SERVICE manifests only — plugin/adapter manifests are loaded as
|
|
89
|
+
// JS by the runtime and don't need a sibling .json.
|
|
90
|
+
if (typeof obj.schema !== 'string' || !obj.schema.startsWith('kb.service/')) return
|
|
91
|
+
const jsonPath = distPath.replace(/\.js$/, '.json')
|
|
92
|
+
writeFileSync(jsonPath, JSON.stringify(obj, null, 2) + '\n')
|
|
93
|
+
} catch {
|
|
94
|
+
// Never fail the build over manifest emission.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
43
98
|
function resolveExternalDependencies() {
|
|
44
99
|
try {
|
|
45
100
|
const pkgPath = join(process.cwd(), 'package.json')
|
|
@@ -86,6 +141,9 @@ export default defineConfig({
|
|
|
86
141
|
splitting: false,
|
|
87
142
|
skipNodeModulesBundle: true,
|
|
88
143
|
shims: false,
|
|
144
|
+
// Emit dist/manifest.json from the built manifest module so Go installers
|
|
145
|
+
// (kb-create) can register the service. No-op for packages without a manifest.
|
|
146
|
+
onSuccess: emitManifestJson,
|
|
89
147
|
ignoreWatch: [
|
|
90
148
|
'**/node_modules/**',
|
|
91
149
|
'**/dist/**',
|
package/vitest/cli.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
environment: 'node',
|
|
6
|
+
globals: false,
|
|
7
|
+
reporters: ['default'],
|
|
8
|
+
include: ['src/__tests__/cli/**/*.cli.test.ts'],
|
|
9
|
+
exclude: [
|
|
10
|
+
'**/node_modules/**',
|
|
11
|
+
'**/dist/**',
|
|
12
|
+
],
|
|
13
|
+
testTimeout: 5_000,
|
|
14
|
+
coverage: {
|
|
15
|
+
enabled: false,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
package/vitest/node.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import { cpus } from 'os'
|
|
3
|
+
|
|
4
|
+
const defaultForks = Math.max(1, Math.ceil(cpus().length / 2))
|
|
5
|
+
const maxForks = process.env.VITEST_MAX_FORKS
|
|
6
|
+
? parseInt(process.env.VITEST_MAX_FORKS, 10)
|
|
7
|
+
: defaultForks
|
|
2
8
|
|
|
3
9
|
export default defineConfig({
|
|
4
10
|
test: {
|
|
5
11
|
environment: 'node',
|
|
6
12
|
globals: false,
|
|
7
13
|
reporters: ['default'],
|
|
14
|
+
pool: 'forks',
|
|
15
|
+
poolOptions: {
|
|
16
|
+
forks: { maxForks, minForks: 1 },
|
|
17
|
+
},
|
|
8
18
|
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
|
9
19
|
exclude: [
|
|
10
20
|
'**/node_modules/**',
|
|
@@ -12,6 +22,13 @@ export default defineConfig({
|
|
|
12
22
|
'**/cypress/**',
|
|
13
23
|
'**/.{idea,git,cache,output,temp}/**'
|
|
14
24
|
],
|
|
25
|
+
// The first test in an import-heavy suite pays the cold ESM transform +
|
|
26
|
+
// module-graph load. Under the forks pool on a CPU-constrained CI runner
|
|
27
|
+
// that occasionally spikes past Vitest's 5s default and flakes a healthy
|
|
28
|
+
// test (e.g. one whose body does `await import('../heavy-module.js')`).
|
|
29
|
+
// 30s gives generous headroom for cold-start latency while still catching a
|
|
30
|
+
// genuine hang; logic-level slowness is not a concern for these unit suites.
|
|
31
|
+
testTimeout: 30_000,
|
|
15
32
|
coverage: {
|
|
16
33
|
enabled: false,
|
|
17
34
|
provider: 'v8',
|
|
@@ -30,14 +47,11 @@ export default defineConfig({
|
|
|
30
47
|
'**/constants.ts',
|
|
31
48
|
'**/constants/**',
|
|
32
49
|
'**/*.config.ts',
|
|
33
|
-
'**/*.config.js'
|
|
50
|
+
'**/*.config.js',
|
|
51
|
+
'**/bootstrap.ts',
|
|
52
|
+
'**/bin.ts'
|
|
34
53
|
],
|
|
35
|
-
thresholds: {
|
|
36
|
-
statements: 90,
|
|
37
|
-
branches: 85,
|
|
38
|
-
functions: 90,
|
|
39
|
-
lines: 90
|
|
40
|
-
}
|
|
54
|
+
thresholds: {}
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
})
|
package/vitest/react.js
CHANGED
|
@@ -33,12 +33,11 @@ export default defineConfig({
|
|
|
33
33
|
'**/*.config.ts',
|
|
34
34
|
'**/*.config.js'
|
|
35
35
|
],
|
|
36
|
-
thresholds
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
36
|
+
// No global thresholds in the preset — enforce per-package via
|
|
37
|
+
// devkit.yaml coverage.categories/thresholds (current floors,
|
|
38
|
+
// raised quarterly). The previous 90/85/90/90 values broke every
|
|
39
|
+
// consumer that wasn't already at target, with no migration path.
|
|
40
|
+
thresholds: {}
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
})
|