@boopkit/cli 0.6.10 → 0.6.11

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.
@@ -13,7 +13,7 @@
13
13
  "@boopkit/cap-services": "0.4.2",
14
14
  "@boopkit/cap-skills": "0.5.4",
15
15
  "@boopkit/cap-testing": "0.5.6",
16
- "@boopkit/cap-users": "0.6.0",
16
+ "@boopkit/cap-users": "0.7.0",
17
17
  "@boopkit/cap-ux": "0.2.2",
18
18
  "@boopkit/cap-web": "0.5.4",
19
19
  "@boopkit/cap-workflows": "0.2.0",
@@ -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'));
@@ -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[];
@@ -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[]>;
@@ -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 single setup check by type. Returns true/false for pass/fail.
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(type, ctx) {
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.type, ctx);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cli",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "boop: the opinionated bootstrap for founder friendly, AI-first web apps - code plus agent skills, composable from day one.",
5
5
  "main": "dist/cli.js",
6
6
  "workspaces": [