@proappstore/cli 2.6.57 → 2.6.59

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.
@@ -0,0 +1,49 @@
1
+ import { Command } from 'commander';
2
+ export interface BillingProductRow {
3
+ key: string;
4
+ name: string;
5
+ amount: number;
6
+ currency: string;
7
+ modes: string[];
8
+ interval: {
9
+ unit: string;
10
+ count: number | null;
11
+ } | null;
12
+ active: boolean;
13
+ syncError: string | null;
14
+ }
15
+ export interface LedgerRow {
16
+ purchaseId: string;
17
+ product: string;
18
+ buyerUserId: string;
19
+ subjects: string[];
20
+ status: string;
21
+ createdAt: number;
22
+ currency: string | null;
23
+ gross: number;
24
+ refunded: number;
25
+ fee: number | null;
26
+ net: number;
27
+ }
28
+ export interface EntitlementRow {
29
+ product: string;
30
+ subject: string;
31
+ status: string;
32
+ periodEnd: number | null;
33
+ graceUntil: number | null;
34
+ renews: boolean;
35
+ buyerUserId: string | null;
36
+ }
37
+ /** 1234 -> "12.34 AUD". Amounts are in the smallest unit; a zero-decimal currency would need its own table, so the code is always shown. */
38
+ export declare function money(minor: number, currency: string | null): string;
39
+ export declare function formatProducts(enabled: boolean, products: BillingProductRow[]): string;
40
+ export declare function formatPurchases(rows: LedgerRow[], totals: {
41
+ currency: string;
42
+ gross: number;
43
+ refunded: number;
44
+ fee: number;
45
+ net: number;
46
+ }[]): string;
47
+ export declare function formatEntitlements(rows: EntitlementRow[]): string;
48
+ export declare const billingCommand: Command;
49
+ //# sourceMappingURL=billing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACxD,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,4IAA4I;AAC5I,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAEpE;AAID,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAQtF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,MAAM,CAMpJ;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,MAAM,CAIjE;AAID,eAAO,MAAM,cAAc,SAuCxB,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { Command } from 'commander';
2
+ import { requireSession, resolveAppIdOrExit, ownerApi } from './secret.js';
3
+ /** 1234 -> "12.34 AUD". Amounts are in the smallest unit; a zero-decimal currency would need its own table, so the code is always shown. */
4
+ export function money(minor, currency) {
5
+ return `${(minor / 100).toFixed(2)}${currency ? ` ${currency.toUpperCase()}` : ''}`;
6
+ }
7
+ const day = (ms) => (ms ? new Date(ms).toISOString().slice(0, 10) : '-');
8
+ export function formatProducts(enabled, products) {
9
+ const lines = [`Billing is ${enabled ? 'ENABLED' : 'not enabled'} for this app${enabled ? '' : ' (a platform admin turns it on)'}.`];
10
+ if (products.length === 0)
11
+ return `${lines[0]}\nNo products declared.\n`;
12
+ for (const p of products) {
13
+ const term = p.interval ? `every ${p.interval.count ?? 1} ${p.interval.unit}` : 'one-off';
14
+ lines.push(`${p.key.padEnd(24)} ${money(p.amount, p.currency).padEnd(14)} ${p.modes.join('/').padEnd(18)} ${term}${p.active ? '' : ' (inactive)'}${p.syncError ? ` sync error: ${p.syncError}` : ''}`);
15
+ }
16
+ return `${lines.join('\n')}\n`;
17
+ }
18
+ export function formatPurchases(rows, totals) {
19
+ if (rows.length === 0)
20
+ return 'No paid purchases.\n';
21
+ const lines = rows.map((r) => `${day(r.createdAt)} ${r.purchaseId.slice(0, 8)} ${r.product.padEnd(20)} ${r.buyerUserId.padEnd(14)} gross ${money(r.gross, r.currency)} refunded ${money(r.refunded, r.currency)} fee ${r.fee === null ? 'pending' : money(r.fee, r.currency)} net ${money(r.net, r.currency)} ${r.status}${r.subjects.length ? ` [${r.subjects.join(', ')}]` : ''}`);
22
+ for (const t of totals)
23
+ lines.push(`TOTAL ${t.currency.toUpperCase() || '?'}: gross ${money(t.gross, t.currency)} refunded ${money(t.refunded, t.currency)} fees ${money(t.fee, t.currency)} net ${money(t.net, t.currency)}`);
24
+ return `${lines.join('\n')}\n`;
25
+ }
26
+ export function formatEntitlements(rows) {
27
+ if (rows.length === 0)
28
+ return 'No entitlements.\n';
29
+ return `${rows.map((e) => `${e.product.padEnd(24)} ${(e.subject || '(none)').padEnd(20)} ${e.status.padEnd(9)} until ${day(e.periodEnd)}${e.status === 'grace' ? ` (grace to ${day(e.graceUntil)})` : ''}${e.renews ? ' renews' : ''} ${e.buyerUserId ?? ''}`).join('\n')}\n`;
30
+ }
31
+ const appOption = ['--app <id>', 'app id (defaults to package.json name in cwd)'];
32
+ export const billingCommand = new Command('billing')
33
+ .description("What your app sells: products, purchases with refunds and Stripe fees, and entitlements. Owner only.")
34
+ .addCommand(new Command('products')
35
+ .description('The products the app declares, and whether billing is enabled.')
36
+ .option(...appOption)
37
+ .option('--json', 'Output JSON.')
38
+ .action(async (opts) => {
39
+ const cfg = await requireSession();
40
+ const appId = await resolveAppIdOrExit(opts.app);
41
+ const data = await ownerApi(cfg, 'GET', `/v1/apps/${appId}/billing/owner/products`, 'list billing products');
42
+ process.stdout.write(opts.json ? `${JSON.stringify(data, null, 2)}\n` : formatProducts(data.enabled, data.products));
43
+ }))
44
+ .addCommand(new Command('purchases')
45
+ .description('Paid purchases with gross, refunds and Stripe fees (the revenue ledger).')
46
+ .option(...appOption)
47
+ .option('--json', 'Output JSON.')
48
+ .action(async (opts) => {
49
+ const cfg = await requireSession();
50
+ const appId = await resolveAppIdOrExit(opts.app);
51
+ const data = await ownerApi(cfg, 'GET', `/v1/apps/${appId}/billing/owner/purchases`, 'list billing purchases');
52
+ process.stdout.write(opts.json ? `${JSON.stringify(data, null, 2)}\n` : formatPurchases(data.purchases, data.totals));
53
+ }))
54
+ .addCommand(new Command('entitlements')
55
+ .description('Every entitlement of the app: who has paid access to what, until when.')
56
+ .option(...appOption)
57
+ .option('--product <key>', 'only this product')
58
+ .option('--json', 'Output JSON.')
59
+ .action(async (opts) => {
60
+ const cfg = await requireSession();
61
+ const appId = await resolveAppIdOrExit(opts.app);
62
+ const query = opts.product ? `?product=${encodeURIComponent(opts.product)}` : '';
63
+ const data = await ownerApi(cfg, 'GET', `/v1/apps/${appId}/billing/owner/entitlements${query}`, 'list entitlements');
64
+ process.stdout.write(opts.json ? `${JSON.stringify(data, null, 2)}\n` : formatEntitlements(data.entitlements));
65
+ }));
66
+ //# sourceMappingURL=billing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=billing.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.test.d.ts","sourceRoot":"","sources":["../src/billing.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { billingCommand, formatEntitlements, formatProducts, formatPurchases, money } from './billing.js';
3
+ // #509: the owner's CLI. The server decides who may see this; these pin what is shown and that every subcommand is there.
4
+ describe('pas billing', () => {
5
+ it('has products, purchases and entitlements, each with --app', () => {
6
+ expect(billingCommand.commands.map((c) => c.name()).sort()).toEqual(['entitlements', 'products', 'purchases']);
7
+ for (const c of billingCommand.commands)
8
+ expect(c.options.map((o) => o.long)).toContain('--app');
9
+ });
10
+ it('formats money from the smallest unit', () => {
11
+ expect(money(1234, 'aud')).toBe('12.34 AUD');
12
+ expect(money(5, null)).toBe('0.05');
13
+ });
14
+ it('says plainly when billing is off, and shows a sync error', () => {
15
+ const out = formatProducts(false, [{ key: 'term', name: 'Term', amount: 1000, currency: 'aud', modes: ['one_time', 'recurring'], interval: { unit: 'month', count: 3 }, active: true, syncError: 'stripe down' }]);
16
+ expect(out).toContain('not enabled');
17
+ expect(out).toContain('10.00 AUD');
18
+ expect(out).toContain('every 3 month');
19
+ expect(out).toContain('sync error: stripe down');
20
+ });
21
+ it('shows refunds, a pending fee, net and totals per purchase', () => {
22
+ const row = { purchaseId: 'abcdef123456', product: 'term', buyerUserId: 'gh:2', subjects: ['kid-a', 'kid-b'], status: 'paid', createdAt: Date.UTC(2026, 9, 1), currency: 'aud', gross: 2000, refunded: 500, fee: null, net: 1500 };
23
+ const out = formatPurchases([row, { ...row, purchaseId: 'ffffffff0000', fee: 88, net: 1412 }], [{ currency: 'aud', gross: 4000, refunded: 1000, fee: 88, net: 2912 }]);
24
+ expect(out).toContain('2026-10-01 abcdef12');
25
+ expect(out).toContain('refunded 5.00 AUD');
26
+ expect(out).toContain('fee pending');
27
+ expect(out).toContain('fee 0.88 AUD');
28
+ expect(out).toContain('TOTAL AUD: gross 40.00 AUD refunded 10.00 AUD fees 0.88 AUD net 29.12 AUD');
29
+ expect(formatPurchases([], [])).toBe('No paid purchases.\n');
30
+ });
31
+ it('shows entitlements with grace and renewal', () => {
32
+ const out = formatEntitlements([
33
+ { product: 'term', subject: 'kid-a', status: 'grace', periodEnd: Date.UTC(2026, 9, 1), graceUntil: Date.UTC(2026, 9, 6), renews: true, buyerUserId: 'gh:2' },
34
+ { product: 'donation', subject: '', status: 'active', periodEnd: null, graceUntil: null, renews: false, buyerUserId: null },
35
+ ]);
36
+ expect(out).toContain('grace to 2026-10-06');
37
+ expect(out).toContain('renews');
38
+ expect(out).toContain('(none)');
39
+ expect(formatEntitlements([])).toBe('No entitlements.\n');
40
+ });
41
+ });
42
+ //# sourceMappingURL=billing.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.test.js","sourceRoot":"","sources":["../src/billing.test.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  import { Command } from 'commander';
6
6
  import { checkCommand } from './check.js';
7
7
  import { createApp } from './create.js';
8
+ import { billingCommand } from './billing.js';
8
9
  import { domainCommand } from './domain.js';
9
10
  import { loginCommand } from './login.js';
10
11
  import { logoutCommand } from './logout.js';
@@ -22,6 +23,7 @@ import { marketingCommand } from './marketing.js';
22
23
  import { permissionsCommand } from './permissions.js';
23
24
  import { brandCommand } from './brand.js';
24
25
  import { migrateCommand } from './migrate-tailwind-tokens.js';
26
+ import { migrationsCommand } from './migrations-test.js';
25
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
28
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
27
29
  const program = new Command();
@@ -60,6 +62,7 @@ program
60
62
  });
61
63
  program.addCommand(checkCommand);
62
64
  program.addCommand(domainCommand);
65
+ program.addCommand(billingCommand);
63
66
  program.addCommand(secretCommand);
64
67
  program.addCommand(proxyCommand);
65
68
  program.addCommand(integrateCommand);
@@ -71,6 +74,7 @@ program.addCommand(sourcemapsCommand);
71
74
  program.addCommand(marketingCommand);
72
75
  program.addCommand(permissionsCommand);
73
76
  program.addCommand(migrateCommand);
77
+ program.addCommand(migrationsCommand);
74
78
  program.addCommand(brandCommand);
75
79
  program.parseAsync().catch((err) => {
76
80
  const msg = err instanceof Error ? err.message : String(err);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SQLite statement tokenizer and splitter (#455), shared by the data worker's
3
+ * `/migrate` executor and the backend's migration lint, so they always agree on
4
+ * where a statement starts and ends. Dependency-free: it runs inside every app's
5
+ * data worker.
6
+ *
7
+ * It follows SQLite's own rules (lang_keywords, tokenize.c, and sqlite3_complete
8
+ * for statement ends):
9
+ * - strings '…', quoted identifiers "…" `…` […], -- and block comments are
10
+ * single tokens or skipped, whatever they contain;
11
+ * - a `;` ends a statement, except inside CREATE TRIGGER. A trigger body is
12
+ * ended by the `;` after an `END` that itself follows a `;`, exactly as
13
+ * sqlite3_complete does. A `CASE … END` expression is therefore not mistaken
14
+ * for the end of the body (the bug that rejected valid triggers).
15
+ *
16
+ * The tokenizer is lenient: an unterminated string, identifier or comment becomes
17
+ * a token with `error` set and runs to the end of the input, so the executor still
18
+ * splits (and the database rejects) what it always did, while the lint reports it.
19
+ */
20
+ type TokenKind = 'word' | 'ident' | 'string' | 'number' | 'punct';
21
+ export interface SqlToken {
22
+ kind: TokenKind;
23
+ /** The source text of the token. */
24
+ text: string;
25
+ /** Upper-cased `text` for a bare word (a keyword or an unquoted name); '' otherwise. */
26
+ upper: string;
27
+ /** Offset of the first character in the input. */
28
+ start: number;
29
+ /** Offset after the last character. */
30
+ end: number;
31
+ /** Set on an unterminated string, quoted identifier or block comment. */
32
+ error?: string;
33
+ }
34
+ /** Tokens of `sql`, comments dropped. Never throws. */
35
+ export declare function tokenize(sql: string): SqlToken[];
36
+ export interface SqlStatement {
37
+ /** The statement's source, comments included, trimmed. */
38
+ text: string;
39
+ /** Its tokens (empty for a comment-only fragment). */
40
+ tokens: SqlToken[];
41
+ /** Offset of `text` in the input. */
42
+ start: number;
43
+ }
44
+ /**
45
+ * Split a migration body into statements. The one tokenizer decides every
46
+ * boundary. A `;` ends a statement unless it is inside a CREATE TRIGGER body, where
47
+ * only a `;` after `END` after `;` does (sqlite3_complete's rule).
48
+ */
49
+ export declare function parseStatements(sql: string): SqlStatement[];
50
+ /**
51
+ * The statements' source text, for the executor. Identical, by construction, to
52
+ * what the migration lint sees.
53
+ */
54
+ export declare function splitSqlStatements(sql: string): string[];
55
+ export {};
56
+ //# sourceMappingURL=sql-statements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-statements.d.ts","sourceRoot":"","sources":["../../src/lib/sql-statements.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,KAAK,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,CAoEhD;AAED,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AAeD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAwB3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAExD"}
@@ -0,0 +1,157 @@
1
+ // VENDORED from packages/data-worker/src/sql-statements.ts (#503): the data worker's /migrate splits a migration
2
+ // with exactly this code, so `pas migrations test` must split it the same way. Do not edit here: change the data
3
+ // worker's copy and copy it over. migrations-test.test.ts fails if the two differ.
4
+ /**
5
+ * SQLite statement tokenizer and splitter (#455), shared by the data worker's
6
+ * `/migrate` executor and the backend's migration lint, so they always agree on
7
+ * where a statement starts and ends. Dependency-free: it runs inside every app's
8
+ * data worker.
9
+ *
10
+ * It follows SQLite's own rules (lang_keywords, tokenize.c, and sqlite3_complete
11
+ * for statement ends):
12
+ * - strings '…', quoted identifiers "…" `…` […], -- and block comments are
13
+ * single tokens or skipped, whatever they contain;
14
+ * - a `;` ends a statement, except inside CREATE TRIGGER. A trigger body is
15
+ * ended by the `;` after an `END` that itself follows a `;`, exactly as
16
+ * sqlite3_complete does. A `CASE … END` expression is therefore not mistaken
17
+ * for the end of the body (the bug that rejected valid triggers).
18
+ *
19
+ * The tokenizer is lenient: an unterminated string, identifier or comment becomes
20
+ * a token with `error` set and runs to the end of the input, so the executor still
21
+ * splits (and the database rejects) what it always did, while the lint reports it.
22
+ */
23
+ const WORD_START = /[A-Za-z_\u0080-￿]/;
24
+ const WORD_PART = /[A-Za-z0-9_$\u0080-￿]/;
25
+ const NUMBER = /^(?:0[xX][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?|\.\d+(?:[eE][+-]?\d+)?)/;
26
+ /** Tokens of `sql`, comments dropped. Never throws. */
27
+ export function tokenize(sql) {
28
+ const out = [];
29
+ const n = sql.length;
30
+ let i = 0;
31
+ const push = (kind, start, end, error) => {
32
+ const text = sql.slice(start, end);
33
+ out.push({ kind, text, upper: kind === 'word' ? text.toUpperCase() : '', start, end, ...(error ? { error } : {}) });
34
+ };
35
+ while (i < n) {
36
+ const ch = sql[i];
37
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f') {
38
+ i++;
39
+ continue;
40
+ }
41
+ if (ch === '-' && sql[i + 1] === '-') {
42
+ const nl = sql.indexOf('\n', i);
43
+ i = nl === -1 ? n : nl;
44
+ continue;
45
+ }
46
+ if (ch === '/' && sql[i + 1] === '*') {
47
+ const close = sql.indexOf('*/', i + 2);
48
+ if (close === -1) {
49
+ // An unterminated block comment swallows the rest, as SQLite reads it; flag it.
50
+ push('punct', i, n, 'unterminated block comment');
51
+ i = n;
52
+ }
53
+ else {
54
+ i = close + 2;
55
+ }
56
+ continue;
57
+ }
58
+ if (ch === "'" || ch === '"' || ch === '`') {
59
+ let j = i + 1;
60
+ let closed = false;
61
+ while (j < n) {
62
+ if (sql[j] === ch) {
63
+ if (sql[j + 1] === ch) {
64
+ j += 2;
65
+ continue;
66
+ }
67
+ closed = true;
68
+ j++;
69
+ break;
70
+ }
71
+ j++;
72
+ }
73
+ push(ch === "'" ? 'string' : 'ident', i, j, closed ? undefined : `unterminated ${ch === "'" ? 'string literal' : 'quoted identifier'}`);
74
+ i = j;
75
+ continue;
76
+ }
77
+ if (ch === '[') {
78
+ const close = sql.indexOf(']', i + 1);
79
+ const end = close === -1 ? n : close + 1;
80
+ push('ident', i, end, close === -1 ? 'unterminated [identifier]' : undefined);
81
+ i = end;
82
+ continue;
83
+ }
84
+ if (WORD_START.test(ch)) {
85
+ let j = i + 1;
86
+ while (j < n && WORD_PART.test(sql[j]))
87
+ j++;
88
+ push('word', i, j);
89
+ i = j;
90
+ continue;
91
+ }
92
+ if ((ch >= '0' && ch <= '9') || (ch === '.' && sql[i + 1] !== undefined && sql[i + 1] >= '0' && sql[i + 1] <= '9')) {
93
+ const m = NUMBER.exec(sql.slice(i, i + 64));
94
+ const len = m ? m[0].length : 1;
95
+ push('number', i, i + len);
96
+ i += len;
97
+ continue;
98
+ }
99
+ push('punct', i, i + 1);
100
+ i++;
101
+ }
102
+ return out;
103
+ }
104
+ /** Is `tokens[i]` the bare word `w`? */
105
+ function isWord(t, w) {
106
+ return t !== undefined && t.kind === 'word' && t.upper === w;
107
+ }
108
+ /** Does this statement begin CREATE [TEMP|TEMPORARY] TRIGGER? */
109
+ function startsTrigger(tokens, from) {
110
+ if (!isWord(tokens[from], 'CREATE'))
111
+ return false;
112
+ let j = from + 1;
113
+ if (isWord(tokens[j], 'TEMP') || isWord(tokens[j], 'TEMPORARY'))
114
+ j++;
115
+ return isWord(tokens[j], 'TRIGGER');
116
+ }
117
+ /**
118
+ * Split a migration body into statements. The one tokenizer decides every
119
+ * boundary. A `;` ends a statement unless it is inside a CREATE TRIGGER body, where
120
+ * only a `;` after `END` after `;` does (sqlite3_complete's rule).
121
+ */
122
+ export function parseStatements(sql) {
123
+ const tokens = tokenize(sql);
124
+ const out = [];
125
+ let first = 0; // index of the first token of the current statement
126
+ let from = 0; // offset where the current statement's text begins
127
+ let inTrigger = false;
128
+ const flush = (endOffset, lastToken) => {
129
+ const text = sql.slice(from, endOffset).trim();
130
+ if (text)
131
+ out.push({ text, tokens: tokens.slice(first, lastToken), start: from + (sql.slice(from, endOffset).length - sql.slice(from, endOffset).trimStart().length) });
132
+ };
133
+ for (let i = 0; i < tokens.length; i++) {
134
+ const t = tokens[i];
135
+ if (i === first)
136
+ inTrigger = startsTrigger(tokens, first);
137
+ if (t.kind === 'punct' && t.text === ';' && !t.error) {
138
+ const endsTrigger = isWord(tokens[i - 1], 'END') && i - 2 >= first && tokens[i - 2].text === ';' && tokens[i - 2].kind === 'punct';
139
+ if (inTrigger && !endsTrigger)
140
+ continue;
141
+ flush(t.start, i);
142
+ first = i + 1;
143
+ from = t.end;
144
+ inTrigger = false;
145
+ }
146
+ }
147
+ flush(sql.length, tokens.length);
148
+ return out;
149
+ }
150
+ /**
151
+ * The statements' source text, for the executor. Identical, by construction, to
152
+ * what the migration lint sees.
153
+ */
154
+ export function splitSqlStatements(sql) {
155
+ return parseStatements(sql).map((s) => s.text);
156
+ }
157
+ //# sourceMappingURL=sql-statements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-statements.js","sourceRoot":"","sources":["../../src/lib/sql-statements.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * `pas migrations test` (#503): before a deploy applies migrations.json to production, apply the whole chain to
4
+ * a FRESH D1 and run every action once with schema-valid sample params.
5
+ *
6
+ * Why a real D1 engine: bookkeeper #62 shipped a migration whose triggers broke every workspace insert on D1 although
7
+ * its tests passed on node:sqlite, and it ran in production on deploy. Miniflare runs workerd's own D1, the same
8
+ * SQLite the platform uses, so what fails here fails there.
9
+ *
10
+ * What fails the run: any migration statement that errors (the data worker's /migrate refuses the same), and any
11
+ * action whose SQL errors for a reason other than an expected constraint rejection (SQLITE_CONSTRAINT: a
12
+ * foreign key, a unique index, a NOT NULL or a trigger's RAISE with sample values is the schema doing its job).
13
+ * Rejections are reported, not failed: a trigger that rejects EVERY call looks the same, so read the list.
14
+ */
15
+ export interface Migration {
16
+ name: string;
17
+ sql: string;
18
+ }
19
+ export interface ActionManifest {
20
+ name: string;
21
+ operation?: string;
22
+ sql?: string;
23
+ statements?: string[];
24
+ params?: Record<string, {
25
+ type?: string;
26
+ optional?: boolean;
27
+ default?: unknown;
28
+ max?: number;
29
+ enum?: unknown[];
30
+ }>;
31
+ }
32
+ /** The slice of the D1 binding this uses, so tests can hand in any implementation. */
33
+ export interface D1Like {
34
+ prepare(sql: string): {
35
+ bind(...values: unknown[]): {
36
+ run(): Promise<unknown>;
37
+ all(): Promise<unknown>;
38
+ };
39
+ run(): Promise<unknown>;
40
+ };
41
+ batch(statements: unknown[]): Promise<unknown>;
42
+ }
43
+ type ActionOutcome = 'ok' | 'rejected' | 'failed' | 'skipped';
44
+ interface ActionResult {
45
+ name: string;
46
+ operation: string;
47
+ outcome: ActionOutcome;
48
+ detail?: string;
49
+ }
50
+ interface MigrationFailure {
51
+ migration: string;
52
+ statementIndex: number;
53
+ statement: string;
54
+ detail: string;
55
+ }
56
+ export interface ChainReport {
57
+ migrations: number;
58
+ statements: number;
59
+ migrationFailure: MigrationFailure | null;
60
+ actions: ActionResult[];
61
+ ok: boolean;
62
+ }
63
+ /** A schema rejecting sample values: workerd names it SQLITE_CONSTRAINT, with or without an extended code. */
64
+ export declare const isConstraintRejection: (message: string) => boolean;
65
+ /** A value for a declared param: its default, else something of the right type that the executor's own typing accepts. */
66
+ export declare function sampleParam(schema: NonNullable<ActionManifest['params']>[string]): unknown;
67
+ /**
68
+ * The executor's own binding (backend `bindStatement`): `:name` becomes `?` and the values follow in order, with the
69
+ * three magic names resolved. Mirrored here; a parity test holds it equal to the backend's.
70
+ */
71
+ export declare function bindAction(sql: string, resolved: Record<string, unknown>, userId?: string, now?: number): {
72
+ sql: string;
73
+ params: unknown[];
74
+ };
75
+ /** Apply the chain, then run each action once. Never throws: every failure is in the report. */
76
+ export declare function testMigrationChain(db: D1Like, input: {
77
+ migrations: Migration[];
78
+ actions: ActionManifest[];
79
+ }): Promise<ChainReport>;
80
+ type MiniflareModule = {
81
+ Miniflare: new (opts: Record<string, unknown>) => {
82
+ getD1Database(name: string): Promise<D1Like>;
83
+ dispose(): Promise<void>;
84
+ };
85
+ };
86
+ /** A fresh in-memory D1 on workerd's own engine, via miniflare. `load` is for tests, which resolve miniflare from their own package. */
87
+ export declare function withFreshD1<T>(run: (db: D1Like) => Promise<T>, load?: () => Promise<MiniflareModule>): Promise<T>;
88
+ export declare class MiniflareUnavailable extends Error {
89
+ }
90
+ export declare const migrationsCommand: Command;
91
+ export {};
92
+ //# sourceMappingURL=migrations-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations-test.d.ts","sourceRoot":"","sources":["../src/migrations-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Command } from 'commander';
4
+ import { bold, dim, green, red, yellow } from './lib/ansi.js';
5
+ import { splitSqlStatements } from './lib/sql-statements.js';
6
+ const errorText = (e) => (e instanceof Error ? e.message : String(e)).slice(0, 400);
7
+ /** A schema rejecting sample values: workerd names it SQLITE_CONSTRAINT, with or without an extended code. */
8
+ export const isConstraintRejection = (message) => /\bSQLITE_CONSTRAINT/.test(message);
9
+ /** A value for a declared param: its default, else something of the right type that the executor's own typing accepts. */
10
+ export function sampleParam(schema) {
11
+ if (schema.default !== undefined)
12
+ return schema.default;
13
+ if (Array.isArray(schema.enum) && schema.enum.length > 0)
14
+ return schema.enum[0];
15
+ switch (schema.type) {
16
+ case 'integer':
17
+ case 'number':
18
+ return schema.max !== undefined ? Math.min(1, schema.max) : 1;
19
+ case 'boolean':
20
+ return true;
21
+ default:
22
+ return 'sample';
23
+ }
24
+ }
25
+ /**
26
+ * The executor's own binding (backend `bindStatement`): `:name` becomes `?` and the values follow in order, with the
27
+ * three magic names resolved. Mirrored here; a parity test holds it equal to the backend's.
28
+ */
29
+ export function bindAction(sql, resolved, userId = 'sample-user', now = Date.now()) {
30
+ const names = [];
31
+ const bound = sql.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_m, name) => {
32
+ names.push(name);
33
+ return '?';
34
+ });
35
+ const magic = { __user_id: () => userId, __now: () => now, __uuid: () => crypto.randomUUID() };
36
+ const params = names.map((name) => {
37
+ if (name in magic)
38
+ return magic[name]();
39
+ if (name in resolved)
40
+ return resolved[name];
41
+ throw new Error(`Unresolved parameter: ${name}`);
42
+ });
43
+ return { sql: bound, params };
44
+ }
45
+ function actionStatements(a) {
46
+ if (a.operation === 'batch')
47
+ return Array.isArray(a.statements) ? a.statements : [];
48
+ return typeof a.sql === 'string' ? [a.sql] : [];
49
+ }
50
+ /** Apply the chain, then run each action once. Never throws: every failure is in the report. */
51
+ export async function testMigrationChain(db, input) {
52
+ const report = { migrations: 0, statements: 0, migrationFailure: null, actions: [], ok: true };
53
+ for (const m of input.migrations) {
54
+ const statements = splitSqlStatements(m.sql);
55
+ for (let i = 0; i < statements.length; i++) {
56
+ try {
57
+ await db.prepare(statements[i]).run();
58
+ report.statements++;
59
+ }
60
+ catch (e) {
61
+ report.migrationFailure = { migration: m.name, statementIndex: i, statement: statements[i].slice(0, 300), detail: errorText(e) };
62
+ report.ok = false;
63
+ return report; // nothing after a broken migration means anything
64
+ }
65
+ }
66
+ report.migrations++;
67
+ }
68
+ for (const a of input.actions) {
69
+ const operation = a.operation ?? 'query';
70
+ // A verify action runs platform code the app cannot supply; its SQL is exercised through the platform, not here.
71
+ if (operation === 'verify') {
72
+ report.actions.push({ name: a.name, operation, outcome: 'skipped', detail: 'verify actions run platform verifiers' });
73
+ continue;
74
+ }
75
+ const statements = actionStatements(a);
76
+ if (statements.length === 0) {
77
+ report.actions.push({ name: a.name, operation, outcome: 'skipped', detail: 'no sql' });
78
+ continue;
79
+ }
80
+ try {
81
+ const resolved = {};
82
+ for (const [name, schema] of Object.entries(a.params ?? {}))
83
+ resolved[name] = sampleParam(schema ?? {});
84
+ const now = Date.now();
85
+ const prepared = statements.map((sql) => bindAction(sql, resolved, 'sample-user', now));
86
+ if (operation === 'batch') {
87
+ await db.batch(prepared.map((p) => db.prepare(p.sql).bind(...p.params)));
88
+ }
89
+ else if (operation === 'execute') {
90
+ await db.prepare(prepared[0].sql).bind(...prepared[0].params).run();
91
+ }
92
+ else {
93
+ await db.prepare(prepared[0].sql).bind(...prepared[0].params).all();
94
+ }
95
+ report.actions.push({ name: a.name, operation, outcome: 'ok' });
96
+ }
97
+ catch (e) {
98
+ const detail = errorText(e);
99
+ if (isConstraintRejection(detail))
100
+ report.actions.push({ name: a.name, operation, outcome: 'rejected', detail });
101
+ else {
102
+ report.actions.push({ name: a.name, operation, outcome: 'failed', detail });
103
+ report.ok = false;
104
+ }
105
+ }
106
+ }
107
+ return report;
108
+ }
109
+ /** miniflare is not a dependency of this CLI (apps do not vendor it): `npx -p @proappstore/cli -p miniflare` puts it beside the CLI. */
110
+ const loadMiniflare = async () => {
111
+ const moduleName = 'miniflare';
112
+ try {
113
+ return (await import(moduleName));
114
+ }
115
+ catch {
116
+ throw new MiniflareUnavailable("miniflare is not installed. Run through npx so it comes with the command: `npx -p @proappstore/cli@latest -p miniflare pas migrations test`.");
117
+ }
118
+ };
119
+ /** A fresh in-memory D1 on workerd's own engine, via miniflare. `load` is for tests, which resolve miniflare from their own package. */
120
+ export async function withFreshD1(run, load = loadMiniflare) {
121
+ const { Miniflare } = await load();
122
+ const mf = new Miniflare({
123
+ modules: true,
124
+ script: 'export default { fetch() { return new Response("ok") } }',
125
+ compatibilityDate: '2024-12-01',
126
+ d1Databases: { DB: 'migration-chain-test' },
127
+ });
128
+ try {
129
+ return await run(await mf.getD1Database('DB'));
130
+ }
131
+ finally {
132
+ await mf.dispose().catch(() => undefined);
133
+ }
134
+ }
135
+ export class MiniflareUnavailable extends Error {
136
+ }
137
+ function readJson(path) {
138
+ if (!existsSync(path))
139
+ return null;
140
+ return JSON.parse(readFileSync(path, 'utf8'));
141
+ }
142
+ export const migrationsCommand = new Command('migrations').description('Migration tooling');
143
+ migrationsCommand
144
+ .command('test')
145
+ .description("Apply migrations.json to a fresh D1 and run every action in mcp.json once, before production does (#503)")
146
+ .option('--dir <path>', 'App root', '.')
147
+ .option('--json', 'Print the report as JSON')
148
+ .action(async (opts) => {
149
+ const root = resolve(opts.dir);
150
+ const manifest = readJson(resolve(root, 'migrations.json'));
151
+ const migrations = Array.isArray(manifest?.migrations) ? manifest.migrations : [];
152
+ const actions = readJson(resolve(root, 'mcp.json'))?.tools ?? [];
153
+ if (migrations.length === 0) {
154
+ process.stdout.write(`${dim('No migrations.json: nothing to test.')}\n`);
155
+ return;
156
+ }
157
+ let report;
158
+ try {
159
+ report = await withFreshD1((db) => testMigrationChain(db, { migrations, actions }));
160
+ }
161
+ catch (e) {
162
+ // 2: the check itself could not run (no miniflare, workerd would not start). Distinct from a failing app (1).
163
+ process.stderr.write(`${red('Could not start a D1 to test against:')} ${errorText(e)}\n`);
164
+ process.exit(2);
165
+ }
166
+ if (opts.json) {
167
+ process.stdout.write(`${JSON.stringify(report)}\n`);
168
+ }
169
+ else {
170
+ process.stdout.write(`${bold('Migration chain')} ${report.migrations}/${migrations.length} migration(s), ${report.statements} statement(s) on a fresh D1\n`);
171
+ if (report.migrationFailure) {
172
+ const f = report.migrationFailure;
173
+ process.stdout.write(`${red('✗')} migration ${f.migration}, statement ${f.statementIndex}: ${f.detail}\n ${dim(f.statement)}\n`);
174
+ }
175
+ for (const a of report.actions) {
176
+ const mark = a.outcome === 'ok' ? green('✓') : a.outcome === 'failed' ? red('✗') : yellow(a.outcome === 'rejected' ? '!' : '-');
177
+ process.stdout.write(`${mark} ${a.name.padEnd(32)} ${dim(a.outcome + (a.detail ? `: ${a.detail}` : ''))}\n`);
178
+ }
179
+ const rejected = report.actions.filter((a) => a.outcome === 'rejected').length;
180
+ if (rejected > 0)
181
+ process.stdout.write(`${yellow('!')} ${rejected} action(s) were rejected by a constraint with sample values: expected for a foreign key or unique index, but a trigger that rejects every call looks the same.\n`);
182
+ }
183
+ process.exit(report.ok ? 0 : 1);
184
+ });
185
+ //# sourceMappingURL=migrations-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations-test.js","sourceRoot":"","sources":["../src/migrations-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrations-test.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations-test.test.d.ts","sourceRoot":"","sources":["../src/migrations-test.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseStatements, tokenize } from './lib/sql-statements.js';
3
+ import { bindAction, isConstraintRejection, MiniflareUnavailable, sampleParam, testMigrationChain, withFreshD1 } from './migrations-test.js';
4
+ describe('the vendored statement splitter (#503)', () => {
5
+ it('tokenizes without comments, and reports where each statement starts', () => {
6
+ const sql = "-- header\nCREATE TABLE a (x TEXT DEFAULT 'a;b'); /* gap */ CREATE TABLE b (y INTEGER);";
7
+ const tokens = tokenize(sql);
8
+ expect(tokens.some((t) => t.text.includes('header') || t.text.includes('gap'))).toBe(false);
9
+ expect(tokens.find((t) => t.kind === 'string')?.text).toBe("'a;b'");
10
+ const statements = parseStatements(sql);
11
+ expect(statements).toHaveLength(2);
12
+ expect(statements[0].text).toContain('CREATE TABLE a');
13
+ expect(statements[1].text).toContain('CREATE TABLE b');
14
+ expect(statements[1].start).toBe(sql.indexOf('/* gap */')); // comments stay in the statement's text, as the executor sends them
15
+ });
16
+ it('keeps a trigger body, CASE ... END included, as one statement', () => {
17
+ const sql = "CREATE TRIGGER t AFTER INSERT ON a BEGIN UPDATE a SET x = CASE WHEN 1 THEN 'p' ELSE 'q' END; DELETE FROM a WHERE 0; END; CREATE INDEX i ON a (x);";
18
+ expect(parseStatements(sql)).toHaveLength(2);
19
+ });
20
+ });
21
+ describe('binding', () => {
22
+ it('refuses an unresolved parameter, as the executor does', () => {
23
+ expect(() => bindAction('SELECT :nope', {})).toThrow(/Unresolved parameter: nope/);
24
+ });
25
+ it('draws __uuid fresh for each occurrence', () => {
26
+ const { params } = bindAction('INSERT INTO t VALUES (:__uuid, :__uuid)', {});
27
+ expect(params[0]).not.toEqual(params[1]);
28
+ });
29
+ });
30
+ describe('sample params', () => {
31
+ it('prefers a default, then an enum value, then something of the right type within its cap', () => {
32
+ expect(sampleParam({ type: 'string', default: 'fixed' })).toBe('fixed');
33
+ expect(sampleParam({ type: 'string', enum: ['a', 'b'] })).toBe('a');
34
+ expect(sampleParam({ type: 'integer' })).toBe(1);
35
+ expect(sampleParam({ type: 'integer', max: 0 })).toBe(0);
36
+ expect(sampleParam({ type: 'number' })).toBe(1);
37
+ expect(sampleParam({ type: 'boolean' })).toBe(true);
38
+ expect(sampleParam({ type: 'string' })).toBe('sample');
39
+ expect(sampleParam({})).toBe('sample');
40
+ });
41
+ });
42
+ describe('the report, on a scripted D1', () => {
43
+ /** A D1 whose statements fail by what they contain. */
44
+ function scripted(fail) {
45
+ const ran = [];
46
+ const attempt = async (sql) => {
47
+ ran.push(sql);
48
+ for (const [needle, message] of Object.entries(fail))
49
+ if (sql.includes(needle))
50
+ throw new Error(message);
51
+ return {};
52
+ };
53
+ const db = {
54
+ prepare: (sql) => ({ run: () => attempt(sql), bind: () => ({ run: () => attempt(sql), all: () => attempt(sql) }) }),
55
+ batch: async (stmts) => { for (const s of stmts)
56
+ void s; return {}; },
57
+ };
58
+ return { db, ran };
59
+ }
60
+ it('classifies a constraint rejection as rejected and any other SQLite error as failed', async () => {
61
+ const { db } = scripted({ reject_me: 'D1_ERROR: UNIQUE constraint failed: t.a: SQLITE_CONSTRAINT', break_me: 'D1_ERROR: no such column: x: SQLITE_ERROR' });
62
+ const r = await testMigrationChain(db, { migrations: [{ name: '1', sql: 'CREATE TABLE t (a)' }], actions: [
63
+ { name: 'ok', operation: 'query', sql: 'SELECT 1' },
64
+ { name: 'rej', operation: 'execute', sql: 'INSERT reject_me' },
65
+ { name: 'bad', operation: 'execute', sql: 'INSERT break_me' },
66
+ ] });
67
+ expect(r.actions.map((a) => [a.name, a.outcome])).toEqual([['ok', 'ok'], ['rej', 'rejected'], ['bad', 'failed']]);
68
+ expect(r.ok).toBe(false);
69
+ });
70
+ it('stops at the first failing migration statement, names it, and runs no action', async () => {
71
+ const { db, ran } = scripted({ boom: 'D1_ERROR: near "boom": syntax error: SQLITE_ERROR' });
72
+ const r = await testMigrationChain(db, { migrations: [{ name: '0001', sql: 'CREATE TABLE a (x); boom; CREATE TABLE c (z)' }, { name: '0002', sql: 'CREATE TABLE later (y)' }], actions: [{ name: 'q', operation: 'query', sql: 'SELECT 1' }] });
73
+ expect(r.migrationFailure).toMatchObject({ migration: '0001', statementIndex: 1, statement: 'boom' });
74
+ expect(r.ok).toBe(false);
75
+ expect(r.actions).toEqual([]);
76
+ expect(ran).toEqual(['CREATE TABLE a (x)', 'boom']);
77
+ });
78
+ it('is ok when everything runs, counting migrations and statements', async () => {
79
+ const { db } = scripted({});
80
+ const r = await testMigrationChain(db, { migrations: [{ name: '1', sql: 'CREATE TABLE a (x); CREATE TABLE b (y)' }], actions: [] });
81
+ expect(r).toMatchObject({ migrations: 1, statements: 2, ok: true, migrationFailure: null });
82
+ });
83
+ it('recognises only SQLITE_CONSTRAINT (with or without an extended code) as an expected rejection', () => {
84
+ for (const m of ['x: SQLITE_CONSTRAINT', 'x: SQLITE_CONSTRAINT_TRIGGER', 'x: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_FOREIGNKEY)'])
85
+ expect(isConstraintRejection(m)).toBe(true);
86
+ for (const m of ['x: SQLITE_ERROR', 'no such column: a', 'D1_ERROR: Network connection lost.'])
87
+ expect(isConstraintRejection(m)).toBe(false);
88
+ });
89
+ it('says how to get miniflare when it cannot be loaded', async () => {
90
+ await expect(withFreshD1(async () => 1, async () => { throw new MiniflareUnavailable('miniflare is not installed.'); })).rejects.toBeInstanceOf(MiniflareUnavailable);
91
+ });
92
+ });
93
+ //# sourceMappingURL=migrations-test.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations-test.test.js","sourceRoot":"","sources":["../src/migrations-test.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proappstore/cli",
3
- "version": "2.6.57",
3
+ "version": "2.6.59",
4
4
  "description": "pas — CLI for publishing paid apps to proappstore.online",
5
5
  "license": "MIT",
6
6
  "type": "module",