@kb-labs/devkit 2.94.0 → 2.98.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.
@@ -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 clear-cache
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 clear-cache --deep
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
- ```bash
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 clear-cache --deep
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 clear-cache --deep
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 clear-cache
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
@@ -93,6 +93,11 @@ const CHECKS = {
93
93
  command: 'devkit-check-types.mjs',
94
94
  emoji: '📝',
95
95
  },
96
+ plugins: {
97
+ name: 'Plugin Entry Structure',
98
+ command: 'devkit-check-plugins.mjs',
99
+ emoji: '🔌',
100
+ },
96
101
  };
97
102
 
98
103
  /**
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.94.0",
4
+ "version": "2.98.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-sync": "./bin/devkit-sync.mjs",
60
- "kb-devkit-paths": "./bin/devkit-paths.mjs",
61
- "kb-devkit-tsup-external": "./bin/devkit-tsup-external.mjs",
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-migrate-configs": "./bin/devkit-migrate-configs.mjs",
72
- "kb-devkit-types-audit": "./bin/devkit-types-audit.mjs",
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-core-gate": "./bin/devkit-core-gate.mjs"
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.4",
131
- "@kb-labs/devkit": "2.94.0"
133
+ "vitest": "^3.2.6",
134
+ "@kb-labs/devkit": "2.98.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.4"
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
- statements: 90,
38
- branches: 85,
39
- functions: 90,
40
- lines: 90
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
  })