@boopkit/cli 0.6.10 → 0.6.12
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/dist/capability-catalog.json +11 -0
- package/dist/capability-versions.json +2 -1
- package/dist/commands/status.js +2 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/db-status.d.ts +17 -0
- package/dist/lib/db-status.js +54 -4
- package/dist/lib/execute.js +1 -1
- package/dist/lib/scaffold.d.ts +8 -2
- package/dist/lib/scaffold.js +29 -4
- package/package.json +1 -1
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
"web"
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
"name": "oauth-server",
|
|
80
|
+
"description": "Be an OAuth 2.1 authorization server and resource server, so Claude, Claude Code or any MCP client can act on behalf of one of your users",
|
|
81
|
+
"type": "application",
|
|
82
|
+
"package": "@boopkit/cap-oauth-server",
|
|
83
|
+
"requires": [
|
|
84
|
+
"users-and-accounts",
|
|
85
|
+
"web",
|
|
86
|
+
"database"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
78
89
|
{
|
|
79
90
|
"name": "product-design",
|
|
80
91
|
"description": "Product design skills",
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
"@boopkit/cap-hello": "0.4.1",
|
|
9
9
|
"@boopkit/cap-noticeboard": "0.4.1",
|
|
10
10
|
"@boopkit/cap-oauth-client": "0.1.1",
|
|
11
|
+
"@boopkit/cap-oauth-server": "0.1.0",
|
|
11
12
|
"@boopkit/cap-product-design": "0.6.7",
|
|
12
13
|
"@boopkit/cap-project-mgmt": "0.4.9",
|
|
13
14
|
"@boopkit/cap-services": "0.4.2",
|
|
14
15
|
"@boopkit/cap-skills": "0.5.4",
|
|
15
16
|
"@boopkit/cap-testing": "0.5.6",
|
|
16
|
-
"@boopkit/cap-users": "0.
|
|
17
|
+
"@boopkit/cap-users": "0.8.0",
|
|
17
18
|
"@boopkit/cap-ux": "0.2.2",
|
|
18
19
|
"@boopkit/cap-web": "0.5.4",
|
|
19
20
|
"@boopkit/cap-workflows": "0.2.0",
|
package/dist/commands/status.js
CHANGED
|
@@ -180,6 +180,8 @@ async function enrichWithSetupChecks(data, envValues, projectDir, projectName) {
|
|
|
180
180
|
type: c.type,
|
|
181
181
|
label: (0, interpolate_1.interpolate)(c.label, vars),
|
|
182
182
|
hint: c.hint ? (0, interpolate_1.interpolate)(c.hint, vars) : undefined,
|
|
183
|
+
// Probe queries are literal SQL — pass them through untouched.
|
|
184
|
+
query: c.query,
|
|
183
185
|
}));
|
|
184
186
|
// Extract this capability's .prisma files for scoped prisma-migrate checks
|
|
185
187
|
const prismaFiles = capDef?.files?.filter(f => f.endsWith('.prisma'));
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -39,9 +39,16 @@ export interface CapabilityEnvVar {
|
|
|
39
39
|
required?: 'boot' | 'runtime';
|
|
40
40
|
}
|
|
41
41
|
export interface CapabilitySetupCheck {
|
|
42
|
-
type: 'env-set' | 'service' | 'database' | 'prisma-migrate' | 'prisma-generate';
|
|
42
|
+
type: 'env-set' | 'service' | 'database' | 'prisma-migrate' | 'prisma-generate' | 'probe';
|
|
43
43
|
label: string;
|
|
44
44
|
hint?: string;
|
|
45
|
+
/**
|
|
46
|
+
* For `type: 'probe'` only — a read-only SQL query the capability asserts
|
|
47
|
+
* about its own data. The check passes when the query returns no rows. The
|
|
48
|
+
* query runs in a read-only transaction, so it cannot write. Ignored by
|
|
49
|
+
* every other check type.
|
|
50
|
+
*/
|
|
51
|
+
query?: string;
|
|
45
52
|
}
|
|
46
53
|
export interface CapabilityPostInstall {
|
|
47
54
|
prerequisites?: string[];
|
package/dist/lib/db-status.d.ts
CHANGED
|
@@ -51,6 +51,22 @@ export declare function isPrismaClientGenerated(projectDir: string): boolean;
|
|
|
51
51
|
* Returns true if in sync, false if not, null if prisma not available.
|
|
52
52
|
*/
|
|
53
53
|
export declare function checkPrismaMigrateStatus(projectDir: string): Promise<boolean | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Run a capability-supplied read-only data probe.
|
|
56
|
+
*
|
|
57
|
+
* The query is executed in a read-only transaction (via PGOPTIONS), so a probe
|
|
58
|
+
* that accidentally contains a write raises "cannot execute … in a read-only
|
|
59
|
+
* transaction" rather than mutating anything. The probe is a positive detector:
|
|
60
|
+
* it reports a violation ONLY when it observes one.
|
|
61
|
+
*
|
|
62
|
+
* Returns:
|
|
63
|
+
* true — clean: the query returned no rows.
|
|
64
|
+
* false — violation: the query returned at least one row.
|
|
65
|
+
* null — could not run (psql missing, DB unreachable, table absent, or the
|
|
66
|
+
* query errored). The caller treats this as "no violation observed"
|
|
67
|
+
* so an un-runnable probe never raises a false alarm.
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkDataProbe(dbUrl: string, query: string): Promise<boolean | null>;
|
|
54
70
|
export interface SetupStep {
|
|
55
71
|
label: string;
|
|
56
72
|
done: boolean;
|
|
@@ -73,4 +89,5 @@ export declare function runSetupChecks(checks: {
|
|
|
73
89
|
type: string;
|
|
74
90
|
label: string;
|
|
75
91
|
hint?: string;
|
|
92
|
+
query?: string;
|
|
76
93
|
}[], envValues: Map<string, string>, projectDir: string, projectName: string, prismaFiles?: string[]): Promise<SetupStep[]>;
|
package/dist/lib/db-status.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.checkTablesExist = checkTablesExist;
|
|
|
43
43
|
exports.hasPrismaModels = hasPrismaModels;
|
|
44
44
|
exports.isPrismaClientGenerated = isPrismaClientGenerated;
|
|
45
45
|
exports.checkPrismaMigrateStatus = checkPrismaMigrateStatus;
|
|
46
|
+
exports.checkDataProbe = checkDataProbe;
|
|
46
47
|
exports.runSetupChecks = runSetupChecks;
|
|
47
48
|
const child_process_1 = require("child_process");
|
|
48
49
|
const fs = __importStar(require("fs"));
|
|
@@ -258,11 +259,46 @@ function checkPrismaMigrateStatus(projectDir) {
|
|
|
258
259
|
});
|
|
259
260
|
}
|
|
260
261
|
/**
|
|
261
|
-
* Run a
|
|
262
|
+
* Run a capability-supplied read-only data probe.
|
|
263
|
+
*
|
|
264
|
+
* The query is executed in a read-only transaction (via PGOPTIONS), so a probe
|
|
265
|
+
* that accidentally contains a write raises "cannot execute … in a read-only
|
|
266
|
+
* transaction" rather than mutating anything. The probe is a positive detector:
|
|
267
|
+
* it reports a violation ONLY when it observes one.
|
|
268
|
+
*
|
|
269
|
+
* Returns:
|
|
270
|
+
* true — clean: the query returned no rows.
|
|
271
|
+
* false — violation: the query returned at least one row.
|
|
272
|
+
* null — could not run (psql missing, DB unreachable, table absent, or the
|
|
273
|
+
* query errored). The caller treats this as "no violation observed"
|
|
274
|
+
* so an un-runnable probe never raises a false alarm.
|
|
275
|
+
*/
|
|
276
|
+
function checkDataProbe(dbUrl, query) {
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
(0, child_process_1.execFile)('psql', [dbUrl, '-tAc', query], {
|
|
279
|
+
timeout: 3000,
|
|
280
|
+
// Force a read-only session: any write in the query fails at the server.
|
|
281
|
+
env: { ...process.env, PGOPTIONS: '-c default_transaction_read_only=on' },
|
|
282
|
+
}, (error, stdout) => {
|
|
283
|
+
if (error) {
|
|
284
|
+
// psql not installed, DB unreachable, missing table, or a query error.
|
|
285
|
+
// None of these is an observed violation, so don't raise a false alarm.
|
|
286
|
+
resolve(null);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// With -tA a zero-row result is empty output; any output means the
|
|
290
|
+
// query found a violating row.
|
|
291
|
+
resolve(stdout.trim() === '' ? true : false);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Run a single setup check. Returns true/false for pass/fail.
|
|
262
298
|
* Checks can depend on earlier results via ctx.results.
|
|
263
299
|
*/
|
|
264
|
-
async function runCheck(
|
|
265
|
-
switch (type) {
|
|
300
|
+
async function runCheck(check, ctx) {
|
|
301
|
+
switch (check.type) {
|
|
266
302
|
case 'env-set': {
|
|
267
303
|
const dbUrl = ctx.envValues.get('DATABASE_URL');
|
|
268
304
|
return !!dbUrl;
|
|
@@ -319,6 +355,20 @@ async function runCheck(type, ctx) {
|
|
|
319
355
|
case 'prisma-generate': {
|
|
320
356
|
return isPrismaClientGenerated(ctx.projectDir);
|
|
321
357
|
}
|
|
358
|
+
case 'probe': {
|
|
359
|
+
// A read-only data probe: passes when the capability's query finds no
|
|
360
|
+
// violating rows. Needs a query and a database URL; without either there
|
|
361
|
+
// is nothing to observe, so it passes rather than raising a false alarm.
|
|
362
|
+
if (!check.query)
|
|
363
|
+
return true;
|
|
364
|
+
const dbUrl = ctx.envValues.get('DATABASE_URL');
|
|
365
|
+
if (!dbUrl)
|
|
366
|
+
return true;
|
|
367
|
+
const result = await checkDataProbe(dbUrl, check.query);
|
|
368
|
+
// Only an observed violation (result === false) fails the check; a probe
|
|
369
|
+
// that could not run (null) is treated as clean.
|
|
370
|
+
return result !== false;
|
|
371
|
+
}
|
|
322
372
|
default:
|
|
323
373
|
return false;
|
|
324
374
|
}
|
|
@@ -331,7 +381,7 @@ async function runSetupChecks(checks, envValues, projectDir, projectName, prisma
|
|
|
331
381
|
const ctx = { envValues, projectDir, projectName, results: new Map(), prismaFiles };
|
|
332
382
|
const steps = [];
|
|
333
383
|
for (const check of checks) {
|
|
334
|
-
const done = await runCheck(check
|
|
384
|
+
const done = await runCheck(check, ctx);
|
|
335
385
|
ctx.results.set(check.type, done);
|
|
336
386
|
let hint = check.hint;
|
|
337
387
|
// When prisma-migrate fails and no migrations exist yet, the first
|
package/dist/lib/execute.js
CHANGED
|
@@ -127,7 +127,7 @@ function executeTask(task, manifest) {
|
|
|
127
127
|
case 'link-package': {
|
|
128
128
|
const data = task.data;
|
|
129
129
|
(0, scaffold_1.mergePackageJson)(targetDir, undefined, undefined, {
|
|
130
|
-
[data.packageName]: (0, scaffold_1.getBoopkitVersion)(data.packageName),
|
|
130
|
+
[data.packageName]: (0, scaffold_1.getBoopkitVersion)(data.packageName, [targetDir]),
|
|
131
131
|
}, { skipBackup: true });
|
|
132
132
|
break;
|
|
133
133
|
}
|
package/dist/lib/scaffold.d.ts
CHANGED
|
@@ -64,11 +64,17 @@ export declare function linkSkillsToClaude(targetDir: string, only?: Iterable<st
|
|
|
64
64
|
* Read the version from a @boopkit/ package's package.json and return ^version.
|
|
65
65
|
* Fallback chain:
|
|
66
66
|
* 1. Monorepo packages/ dir (development)
|
|
67
|
-
* 2. require.resolve from CLI's node_modules
|
|
67
|
+
* 2. require.resolve from `searchPaths`, then the CLI's node_modules
|
|
68
68
|
* 3. dist/capability-versions.json (baked at build time for npm installs)
|
|
69
69
|
* 4. '*' (last resort)
|
|
70
|
+
*
|
|
71
|
+
* `searchPaths` is where the *project* is, and it comes before the CLI root for
|
|
72
|
+
* the same reason `planAdd` passes it: the package a project is being given may
|
|
73
|
+
* only exist under that project — npm-linked from a worktree, or installed by
|
|
74
|
+
* `boop add`'s own phase 0 — and reading only the CLI root then falls all the
|
|
75
|
+
* way through to `'*'`, which is not a version any install can resolve.
|
|
70
76
|
*/
|
|
71
|
-
export declare function getBoopkitVersion(packageName: string): string;
|
|
77
|
+
export declare function getBoopkitVersion(packageName: string, searchPaths?: string[]): string;
|
|
72
78
|
/**
|
|
73
79
|
* The warning shown when a dependency lands in a project's package.json with no
|
|
74
80
|
* version range. Written to be actionable by an agent reading it in a
|
package/dist/lib/scaffold.js
CHANGED
|
@@ -128,7 +128,11 @@ function hashFile(filePath) {
|
|
|
128
128
|
* Returns list of copied file paths relative to targetDir, and file hashes.
|
|
129
129
|
*/
|
|
130
130
|
function copyCapabilityFiles(targetDir, capabilityName, searchPaths) {
|
|
131
|
-
|
|
131
|
+
// Default to the project itself, so this resolves the capability to the same
|
|
132
|
+
// package `planAdd` did. Without it a capability that lives only under the
|
|
133
|
+
// project resolved here to nothing, and `boop add` reported every file copied
|
|
134
|
+
// while copying none (boop-2vdy.8.5).
|
|
135
|
+
const pkgDir = (0, capability_1.getPackageDir)(capabilityName, searchPaths ?? [targetDir]);
|
|
132
136
|
if (pkgDir) {
|
|
133
137
|
// Read package.json for boopkit config
|
|
134
138
|
const pkgJsonPath = path.join(pkgDir, 'package.json');
|
|
@@ -266,11 +270,17 @@ function updateCapabilityHashes(targetDir, capabilityName, fileHashes, version)
|
|
|
266
270
|
* Read the version from a @boopkit/ package's package.json and return ^version.
|
|
267
271
|
* Fallback chain:
|
|
268
272
|
* 1. Monorepo packages/ dir (development)
|
|
269
|
-
* 2. require.resolve from CLI's node_modules
|
|
273
|
+
* 2. require.resolve from `searchPaths`, then the CLI's node_modules
|
|
270
274
|
* 3. dist/capability-versions.json (baked at build time for npm installs)
|
|
271
275
|
* 4. '*' (last resort)
|
|
276
|
+
*
|
|
277
|
+
* `searchPaths` is where the *project* is, and it comes before the CLI root for
|
|
278
|
+
* the same reason `planAdd` passes it: the package a project is being given may
|
|
279
|
+
* only exist under that project — npm-linked from a worktree, or installed by
|
|
280
|
+
* `boop add`'s own phase 0 — and reading only the CLI root then falls all the
|
|
281
|
+
* way through to `'*'`, which is not a version any install can resolve.
|
|
272
282
|
*/
|
|
273
|
-
function getBoopkitVersion(packageName) {
|
|
283
|
+
function getBoopkitVersion(packageName, searchPaths) {
|
|
274
284
|
const pkgName = packageName.replace(/^@boopkit\//, '');
|
|
275
285
|
// Try monorepo packages/ directory first (development)
|
|
276
286
|
const monorepoPkgJsonPath = path.join((0, config_1.getBoopRoot)(), 'packages', pkgName, 'package.json');
|
|
@@ -281,10 +291,25 @@ function getBoopkitVersion(packageName) {
|
|
|
281
291
|
catch {
|
|
282
292
|
// Not in monorepo — try require.resolve (published CLI install)
|
|
283
293
|
}
|
|
294
|
+
// Then the node_modules of each search path, read straight off disk rather
|
|
295
|
+
// than through require.resolve — the package being read may have no build
|
|
296
|
+
// output yet, and a resolver is entitled to refuse a package whose `main`
|
|
297
|
+
// does not exist.
|
|
298
|
+
for (const searchPath of [...(searchPaths ?? []), (0, config_1.getBoopRoot)()]) {
|
|
299
|
+
try {
|
|
300
|
+
const installedPath = path.join(searchPath, 'node_modules', packageName, 'package.json');
|
|
301
|
+
const pkg = JSON.parse(fs.readFileSync(installedPath, 'utf-8'));
|
|
302
|
+
if (pkg.version)
|
|
303
|
+
return `^${pkg.version}`;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Not installed under this path — try the next
|
|
307
|
+
}
|
|
308
|
+
}
|
|
284
309
|
// Try resolving the installed package
|
|
285
310
|
try {
|
|
286
311
|
const resolvedPath = require.resolve(`${packageName}/package.json`, {
|
|
287
|
-
paths: [(0, config_1.getBoopRoot)()],
|
|
312
|
+
paths: [...(searchPaths ?? []), (0, config_1.getBoopRoot)()],
|
|
288
313
|
});
|
|
289
314
|
const pkg = JSON.parse(fs.readFileSync(resolvedPath, 'utf-8'));
|
|
290
315
|
return `^${pkg.version}`;
|
package/package.json
CHANGED