naro-cli 0.11.0 → 0.12.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/dist/deploy.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  parseDotenv,
6
6
  readProjectEnv,
7
7
  slugify
8
- } from "./chunk-L7JOGWP3.js";
8
+ } from "./chunk-GFCG5C7D.js";
9
9
  import "./chunk-K3NQKI34.js";
10
10
  export {
11
11
  deployDirectory,
package/dist/index.js CHANGED
@@ -2,11 +2,16 @@
2
2
  import {
3
3
  BACKEND_STEPS,
4
4
  BOOKMARK_FORMAT_MESSAGE,
5
+ INDEX_SQL,
6
+ NAMED_POLICY_SQL,
7
+ NAMED_POLICY_TABLE,
5
8
  NaroClient,
6
9
  NaroError,
7
10
  POLICY_ACTIONS,
8
11
  POLICY_RULES,
12
+ RESET_BACKEND_OUTDATED,
9
13
  RESTORE_OUTCOME_UNKNOWN_CODES,
14
+ SCHEMA_SQL,
10
15
  buildResetPlan,
11
16
  cellText,
12
17
  clearSession,
@@ -20,6 +25,7 @@ import {
20
25
  ensureGitignoreHasNaro,
21
26
  formatRedirectEntry,
22
27
  generateTypes,
28
+ indexesFromRows,
23
29
  isBookmark,
24
30
  isExpired,
25
31
  isNaroError,
@@ -27,6 +33,7 @@ import {
27
33
  isTerminalStatus,
28
34
  lintSchema,
29
35
  listMigrationFiles,
36
+ loadPolicy,
30
37
  loadStoredSession,
31
38
  login,
32
39
  migrationNameMismatch,
@@ -38,7 +45,9 @@ import {
38
45
  parsePolicyStatement,
39
46
  parseRedirectEntry,
40
47
  pendingMigrationNames,
48
+ planPolicyMigration,
41
49
  policiesForTypes,
50
+ policiesLostByReset,
42
51
  projectPath,
43
52
  promptForMissingEnv,
44
53
  quoteIdent,
@@ -47,18 +56,26 @@ import {
47
56
  refreshStoredSession,
48
57
  removeProjectLink,
49
58
  renderRows,
59
+ resetFileRefusedMessage,
60
+ resetPoliciesLostMessage,
61
+ resetPoliciesWithTablesMessage,
50
62
  resolveAuth,
51
63
  resolveMigrationsDir,
52
64
  saveSession,
53
65
  schemaMigrationTitle,
54
66
  serializeDotenv,
55
67
  slugify,
68
+ snapshotFromRows,
56
69
  splitPolicyStatements,
70
+ splitStatements,
71
+ storedPolicySql,
57
72
  summarizeStatement,
58
73
  systemTablePredicate,
74
+ tableNames,
59
75
  toNaroError,
76
+ unreadablePolicyComment,
60
77
  writeProjectLink
61
- } from "./chunk-L7JOGWP3.js";
78
+ } from "./chunk-GFCG5C7D.js";
62
79
  import "./chunk-K3NQKI34.js";
63
80
 
64
81
  // src/args.ts
@@ -450,8 +467,8 @@ function createContext(flags, positionals = []) {
450
467
  }
451
468
 
452
469
  // src/router.ts
453
- import { readFileSync as readFileSync4 } from "fs";
454
- import { dirname as dirname3, join as join5 } from "path";
470
+ import { readFileSync as readFileSync5 } from "fs";
471
+ import { dirname as dirname3, join as join6 } from "path";
455
472
  import { fileURLToPath } from "url";
456
473
 
457
474
  // src/commands/auth.ts
@@ -586,6 +603,22 @@ function printable(text) {
586
603
  function isPrintable(text) {
587
604
  return printable(text) === text;
588
605
  }
606
+ var UNSHOWN_WARNING_LINE = "(a line of this warning has characters the terminal cannot show; run with --json to see it exactly)";
607
+ function warningLines(message) {
608
+ return message.split("\n").map(
609
+ (line, index) => index === 0 ? `\u26A0 ${printable(line)}` : ` ${isPrintable(line) ? line : UNSHOWN_WARNING_LINE}`
610
+ );
611
+ }
612
+ function policyProblems(dir, names) {
613
+ return names.flatMap((name) => {
614
+ const split = splitStatements(readFileSync(join(dir, name), "utf8"));
615
+ if (!split.ok) return [];
616
+ const planned = planPolicyMigration(
617
+ split.statements.map((statement) => statement.text)
618
+ );
619
+ return planned.ok ? [] : [{ migration: name, message: planned.message }];
620
+ });
621
+ }
589
622
  function warningLine(warning) {
590
623
  if (warning === "out-of-order")
591
624
  return " \u26A0 older than an applied migration \u2014 db push will still apply it";
@@ -636,23 +669,42 @@ async function runMigrationList(context) {
636
669
  }
637
670
  async function applyMigrationFiles(context, client, projectId, dir, names) {
638
671
  const applied = [];
672
+ const warnings = [];
639
673
  for (const name of names) {
640
674
  const sql = readFileSync(join(dir, name), "utf8");
675
+ let answer;
641
676
  try {
642
- await client.db.applyMigration(projectId, { name, sql });
677
+ answer = await client.db.applyMigration(projectId, { name, sql });
643
678
  } catch (error) {
644
679
  if (!isNaroError(error)) throw error;
680
+ for (const message of serverWarnings(error.details))
681
+ for (const line of warningLines(message)) context.output.warn(line);
645
682
  const shown = context.output.json ? name : printable(name);
646
683
  throw new NaroError(error.code, `${shown}: ${error.message}`, {
647
684
  ...error.status === void 0 ? {} : { status: error.status },
648
- details: { ...error.details, migration: name, applied },
685
+ details: {
686
+ ...error.details,
687
+ migration: name,
688
+ applied,
689
+ appliedWarnings: warnings
690
+ },
649
691
  cause: error
650
692
  });
651
693
  }
652
694
  context.output.log(`\u2713 ${printable(name)}`);
653
695
  applied.push(name);
696
+ for (const message of answer.warnings) {
697
+ warnings.push({ migration: name, message });
698
+ for (const line of warningLines(message)) context.output.warn(line);
699
+ }
654
700
  }
655
- return applied;
701
+ return { applied, warnings };
702
+ }
703
+ function serverWarnings(details) {
704
+ const warnings = details?.warnings;
705
+ return Array.isArray(warnings) ? warnings.filter(
706
+ (warning) => typeof warning === "string"
707
+ ) : [];
656
708
  }
657
709
  var MIGRATION_NAME_RE = /^[A-Za-z0-9_.-]{1,200}\.sql$/;
658
710
  var LEDGER_DDL = "CREATE TABLE IF NOT EXISTS d1_migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)";
@@ -724,10 +776,15 @@ async function runMigrationUp(context) {
724
776
  return;
725
777
  }
726
778
  if (context.flags.dryRun) {
727
- context.output.result({ projectId, dryRun: true, pending }, [
779
+ const problems = policyProblems(dir, pending);
780
+ context.output.result({ projectId, dryRun: true, pending, problems }, [
728
781
  `Pending migrations (${String(pending.length)}):`,
729
- ...pending.map((name) => ` ${printable(name)}`)
782
+ ...pending.map((name) => ` ${printable(name)}`),
783
+ ...problems.map(
784
+ (problem) => `${printable(problem.migration)}: ${printable(problem.message)}`
785
+ )
730
786
  ]);
787
+ if (problems.length > 0) process.exitCode = 1;
731
788
  return;
732
789
  }
733
790
  context.output.log(`Pending migrations (${String(pending.length)}):`);
@@ -736,14 +793,14 @@ async function runMigrationUp(context) {
736
793
  context,
737
794
  `Apply ${String(pending.length)} migration(s) to the project database? Each file is applied all-or-nothing; a failure stops the run.`
738
795
  );
739
- const applied = await applyMigrationFiles(
796
+ const { applied, warnings } = await applyMigrationFiles(
740
797
  context,
741
798
  client,
742
799
  projectId,
743
800
  dir,
744
801
  pending
745
802
  );
746
- context.output.result({ projectId, applied, pending: [] }, [
803
+ context.output.result({ projectId, applied, pending: [], warnings }, [
747
804
  `\u2705 Applied ${String(applied.length)} migration(s).`
748
805
  ]);
749
806
  }
@@ -1658,6 +1715,8 @@ async function runDbQuery(context, deps = processStdin4) {
1658
1715
  });
1659
1716
  if (result.truncated) lines.push(ROW_CAP_NOTICE);
1660
1717
  context.output.result({ projectId, result }, lines);
1718
+ for (const warning of result.warnings ?? [])
1719
+ for (const line of warningLines(warning)) context.output.warn(line);
1661
1720
  }
1662
1721
 
1663
1722
  // src/commands/db-backup.ts
@@ -1998,6 +2057,77 @@ var DIAGNOSTICS_SQL = [
1998
2057
  "SELECT * FROM pragma_foreign_key_check;"
1999
2058
  ].join("\n");
2000
2059
  var TRUNCATED_NOTICE = "\u26A0 The diagnostics were cut at 10,000 rows \u2014 there may be more broken rows than these.";
2060
+ var POLICIES_TRUNCATED_NOTICE = "\u26A0 The policies were cut at 10,000 rows, so they were not checked.";
2061
+ var POLICY_TABLE_PROBE = `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '${NAMED_POLICY_TABLE}'`;
2062
+ var POLICY_READ_SQL = [NAMED_POLICY_SQL.list, INDEX_SQL, ...SCHEMA_SQL].map((sql) => `${sql};`).join("\n");
2063
+ function objectsOf(statement) {
2064
+ return statement.rows.map(
2065
+ (row) => Object.fromEntries(
2066
+ statement.columns.map((column, index) => [column, row[index]])
2067
+ )
2068
+ );
2069
+ }
2070
+ async function readPolicies(client, projectId, warn) {
2071
+ let backend;
2072
+ try {
2073
+ backend = await client.backend.info(projectId);
2074
+ } catch (error) {
2075
+ if (error instanceof NaroError && error.code === "BACKEND_DISABLED")
2076
+ return void 0;
2077
+ throw error;
2078
+ }
2079
+ if (backend.status === "none") return void 0;
2080
+ const probe = await client.db.query(projectId, {
2081
+ sql: POLICY_TABLE_PROBE,
2082
+ readOnly: true
2083
+ });
2084
+ if ((probe.statements[0]?.rows.length ?? 0) === 0) return void 0;
2085
+ const answer = await client.db.query(projectId, { sql: POLICY_READ_SQL });
2086
+ const [stored, indexes, columns, definitions] = answer.statements;
2087
+ if (stored === void 0 || indexes === void 0 || columns === void 0 || definitions === void 0)
2088
+ throw new NaroError(
2089
+ "DATABASE_QUERY_FAILED",
2090
+ `The policies could not be read: the database answered with ${String(answer.statements.length)} result sets instead of 4.`
2091
+ );
2092
+ if (answer.truncated) {
2093
+ warn(POLICIES_TRUNCATED_NOTICE);
2094
+ return void 0;
2095
+ }
2096
+ const policies = [];
2097
+ const unreadable = [];
2098
+ for (const row of objectsOf(stored)) {
2099
+ const policy = loadPolicy(row);
2100
+ if (policy === void 0) continue;
2101
+ if (policy.invalid !== null)
2102
+ unreadable.push({
2103
+ table: policy.table,
2104
+ name: policy.name,
2105
+ reason: policy.invalid
2106
+ });
2107
+ else
2108
+ policies.push({
2109
+ table: policy.table,
2110
+ name: policy.name,
2111
+ permissive: policy.permissive,
2112
+ command: policy.command,
2113
+ roles: policy.roles,
2114
+ using: policy.using,
2115
+ check: policy.check,
2116
+ owner: policy.owner
2117
+ });
2118
+ }
2119
+ const snapshot = snapshotFromRows([
2120
+ ...objectsOf(columns),
2121
+ ...objectsOf(definitions)
2122
+ ]);
2123
+ return {
2124
+ tables: tableNames(snapshot),
2125
+ policies,
2126
+ unreadable,
2127
+ snapshot,
2128
+ indexes: indexesFromRows(objectsOf(indexes))
2129
+ };
2130
+ }
2001
2131
  function firstCell(row) {
2002
2132
  const value = row[0];
2003
2133
  return value === null || value === void 0 ? "" : String(value);
@@ -2043,12 +2173,18 @@ async function runDbLint(context) {
2043
2173
  rowid: rowidOf(cellText(broken, row, "rowid")),
2044
2174
  parent: cellText(broken, row, "parent")
2045
2175
  }));
2176
+ const policies = await readPolicies(
2177
+ client,
2178
+ projectId,
2179
+ (message) => context.output.warn(message)
2180
+ );
2046
2181
  const input = {
2047
2182
  tables: tables.filter((table) => !shadow.has(table.name)),
2048
2183
  foreignKeyCheck,
2049
2184
  quickCheck: quick.rows.map(firstCell),
2050
2185
  triggers: triggers.rows.map((row) => cellText(triggers, row, "name")),
2051
- virtualTables
2186
+ virtualTables,
2187
+ ...policies === void 0 ? {} : { policies }
2052
2188
  };
2053
2189
  const issues = lintSchema(input);
2054
2190
  const counted = countLintedTables(input);
@@ -2064,12 +2200,14 @@ async function runDbLint(context) {
2064
2200
  }
2065
2201
 
2066
2202
  // src/commands/db-reset.ts
2203
+ import { readFileSync as readFileSync3 } from "fs";
2204
+ import { join as join3 } from "path";
2067
2205
  var MAX_STATEMENTS = 50;
2068
2206
  var SCHEMA_FILTER = `${systemTablePredicate("name")} AND ${systemTablePredicate("tbl_name")}`;
2069
- var SCHEMA_SQL = [
2207
+ var SCHEMA_SQL2 = [
2070
2208
  `SELECT type, name FROM sqlite_master WHERE type IN ('table', 'view', 'trigger') AND ${SCHEMA_FILTER} ORDER BY name;`,
2071
2209
  "SELECT name FROM pragma_table_list WHERE schema = 'main' AND type = 'virtual';",
2072
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('sqlite_sequence', 'd1_migrations');"
2210
+ `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('sqlite_sequence', 'd1_migrations', '${NAMED_POLICY_TABLE}');`
2073
2211
  ].join("\n");
2074
2212
  function literal(value) {
2075
2213
  return `'${value.replace(/'/g, "''")}'`;
@@ -2081,7 +2219,7 @@ function chunk(items2, size) {
2081
2219
  return pieces;
2082
2220
  }
2083
2221
  async function readSchema(client, projectId) {
2084
- const schema = await client.db.query(projectId, { sql: SCHEMA_SQL });
2222
+ const schema = await client.db.query(projectId, { sql: SCHEMA_SQL2 });
2085
2223
  const [listed, virtual, internal] = schema.statements;
2086
2224
  if (listed === void 0 || virtual === void 0 || internal === void 0)
2087
2225
  throw new NaroError(
@@ -2127,7 +2265,54 @@ async function readSchema(client, projectId) {
2127
2265
  internal: {
2128
2266
  sqliteSequence: internalNames.has("sqlite_sequence"),
2129
2267
  ledger: internalNames.has("d1_migrations")
2130
- }
2268
+ },
2269
+ policyTable: internalNames.has(NAMED_POLICY_TABLE)
2270
+ };
2271
+ }
2272
+ async function hasBackend(client, projectId) {
2273
+ try {
2274
+ return (await client.backend.info(projectId)).status !== "none";
2275
+ } catch (error) {
2276
+ if (isNaroError(error) && error.code === "BACKEND_DISABLED") return false;
2277
+ throw error;
2278
+ }
2279
+ }
2280
+ async function policyPlan(client, projectId, schema, dir, files) {
2281
+ if (!schema.policyTable) {
2282
+ if (await hasBackend(client, projectId))
2283
+ throw new NaroError("BACKEND_OUTDATED", RESET_BACKEND_OUTDATED);
2284
+ return { clearPolicies: false, lost: [], withTables: [] };
2285
+ }
2286
+ const answer = await client.db.query(projectId, {
2287
+ sql: NAMED_POLICY_SQL.list
2288
+ });
2289
+ const stored = answer.statements[0];
2290
+ if (stored === void 0)
2291
+ throw new NaroError(
2292
+ "DATABASE_QUERY_FAILED",
2293
+ "The policies could not be read: the database answered with no result set."
2294
+ );
2295
+ if (answer.truncated)
2296
+ throw new NaroError(
2297
+ "DATABASE_QUERY_FAILED",
2298
+ "The policies are too many to read in one pass: the list was cut at 10,000 rows, so the reset cannot tell which ones your migration files do not make."
2299
+ );
2300
+ const current = stored.rows.map((row) => ({
2301
+ table: cellText(stored, row, "table_name"),
2302
+ name: cellText(stored, row, "name"),
2303
+ ...storedPolicySql(
2304
+ Object.fromEntries(
2305
+ stored.columns.map((column, index) => [column, row[index]])
2306
+ )
2307
+ )
2308
+ }));
2309
+ return {
2310
+ clearPolicies: true,
2311
+ ...policiesLostByReset({
2312
+ current,
2313
+ tables: schema.tables,
2314
+ files: files.map((name) => readFileSync3(join3(dir, name), "utf8"))
2315
+ })
2131
2316
  };
2132
2317
  }
2133
2318
  function resetStopped(context, error, bookmark, lead, message) {
@@ -2149,16 +2334,72 @@ async function runDbReset(context) {
2149
2334
  const dir = migrationsDir(context);
2150
2335
  const shownDir = relativeToCwd(context, dir);
2151
2336
  const files = listMigrationFiles(dir);
2337
+ const problems = policyProblems(dir, files);
2338
+ const problemLines = problems.map(
2339
+ (problem) => `${context.output.json ? problem.migration : printable(problem.migration)}: ${context.output.json ? problem.message : printable(problem.message)}`
2340
+ );
2341
+ if (problems.length > 0 && !context.flags.dryRun)
2342
+ throw new NaroError(
2343
+ "VALIDATION_FAILED",
2344
+ `The control plane would refuse these migration files, so the reset would stop at the first of them with the schema already dropped. Nothing was changed:
2345
+ ${problemLines.join("\n")}`,
2346
+ { details: { problems } }
2347
+ );
2152
2348
  const client = await context.client();
2153
2349
  const projectId = await context.resolveProjectId();
2154
2350
  const bookmark = context.flags.dryRun ? null : await bookmarkNow(context, client, projectId, "resetting");
2155
- const plan = buildResetPlan(await readSchema(client, projectId));
2351
+ const { policyTable: policyTable2, ...input } = await readSchema(client, projectId);
2352
+ const plan = buildResetPlan(input, { clearPolicies: policyTable2 });
2156
2353
  const { tables, views, triggers } = plan.dropped;
2157
2354
  const counts = `${String(tables.length)} table(s), ${String(views.length)} view(s) and ${String(triggers.length)} trigger(s)`;
2158
2355
  const dropped = tables.length + views.length + triggers.length;
2356
+ const policies = await policyPlan(
2357
+ client,
2358
+ projectId,
2359
+ { policyTable: policyTable2, tables },
2360
+ dir,
2361
+ files
2362
+ );
2363
+ const policiesNotInFiles = policies.lost.map((policy) => ({
2364
+ name: `${policy.table}.${policy.name}`,
2365
+ sql: policy.sql
2366
+ }));
2367
+ const policiesOfTablesNotInFiles = policies.withTables.map(
2368
+ (policy) => `${policy.table}.${policy.name}`
2369
+ );
2370
+ const refused = policies.refused && {
2371
+ migration: files[policies.refused.file] ?? "",
2372
+ message: policies.refused.reason
2373
+ };
2374
+ if (refused && !problems.some((one) => one.migration === refused.migration))
2375
+ context.output.warn(
2376
+ `\u26A0 ${printable(resetFileRefusedMessage(refused.migration, refused.message))}`
2377
+ );
2378
+ if (policies.lost.length > 0) {
2379
+ context.output.warn(
2380
+ `\u26A0 ${printable(resetPoliciesLostMessage(policies.lost))}`
2381
+ );
2382
+ for (const policy of policies.lost)
2383
+ for (const line of policy.statements.every(isPrintable) ? policy.statements : [UNSHOWN_WARNING_LINE])
2384
+ context.output.warn(` ${line}`);
2385
+ }
2386
+ if (policies.withTables.length > 0)
2387
+ context.output.warn(
2388
+ `\u26A0 ${printable(resetPoliciesWithTablesMessage(policies.withTables))}`
2389
+ );
2159
2390
  if (context.flags.dryRun) {
2160
2391
  context.output.result(
2161
- { dryRun: true, tables, views, triggers, migrations: files },
2392
+ {
2393
+ dryRun: true,
2394
+ tables,
2395
+ views,
2396
+ triggers,
2397
+ migrations: files,
2398
+ policiesNotInFiles,
2399
+ policiesOfTablesNotInFiles,
2400
+ problems,
2401
+ ...refused ? { wouldBeRefused: refused } : {}
2402
+ },
2162
2403
  [
2163
2404
  `Would drop ${counts}:`,
2164
2405
  ...views.map((name) => ` ${"view".padEnd(9)}${printable(name)}`),
@@ -2166,9 +2407,11 @@ async function runDbReset(context) {
2166
2407
  ...tables.map((name) => ` ${"table".padEnd(9)}${printable(name)}`),
2167
2408
  `Would re-apply ${String(files.length)} migration file(s) from ${shownDir}:`,
2168
2409
  ...files.map((name) => ` ${printable(name)}`),
2169
- "Nothing was changed."
2410
+ "Nothing was changed.",
2411
+ ...problemLines
2170
2412
  ]
2171
2413
  );
2414
+ if (problems.length > 0 || refused) process.exitCode = 1;
2172
2415
  return;
2173
2416
  }
2174
2417
  if (bookmark !== null)
@@ -2200,15 +2443,16 @@ async function runDbReset(context) {
2200
2443
  `No migration files in ${shownDir} \u2014 the database is now empty.`
2201
2444
  );
2202
2445
  let reapplied = [];
2446
+ let warnings = [];
2203
2447
  try {
2204
2448
  if (files.length > 0)
2205
- reapplied = await applyMigrationFiles(
2449
+ ({ applied: reapplied, warnings } = await applyMigrationFiles(
2206
2450
  context,
2207
2451
  client,
2208
2452
  projectId,
2209
2453
  dir,
2210
2454
  files
2211
- );
2455
+ ));
2212
2456
  } catch (error) {
2213
2457
  throw resetStopped(
2214
2458
  context,
@@ -2218,18 +2462,28 @@ async function runDbReset(context) {
2218
2462
  (underlying) => underlying
2219
2463
  );
2220
2464
  }
2221
- context.output.result({ dropped, reapplied: reapplied.length, bookmark }, [
2222
- `\u2713 Reset the project database: dropped ${String(dropped)} object(s), re-applied ${String(reapplied.length)} migration(s).`,
2223
- ...bookmark === null ? [] : [` To undo: naro db restore --bookmark ${bookmark}`]
2224
- ]);
2465
+ context.output.result(
2466
+ {
2467
+ dropped,
2468
+ reapplied: reapplied.length,
2469
+ bookmark,
2470
+ policiesNotInFiles,
2471
+ policiesOfTablesNotInFiles,
2472
+ warnings
2473
+ },
2474
+ [
2475
+ `\u2713 Reset the project database: dropped ${String(dropped)} object(s), re-applied ${String(reapplied.length)} migration(s).`,
2476
+ ...bookmark === null ? [] : [` To undo: naro db restore --bookmark ${bookmark}`]
2477
+ ]
2478
+ );
2225
2479
  }
2226
2480
 
2227
2481
  // src/commands/db-schema.ts
2228
2482
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
2229
- import { join as join3 } from "path";
2483
+ import { join as join4 } from "path";
2230
2484
 
2231
2485
  // src/commands/policy.ts
2232
- import { readFileSync as readFileSync3 } from "fs";
2486
+ import { readFileSync as readFileSync4 } from "fs";
2233
2487
  import { resolve as resolve3 } from "path";
2234
2488
 
2235
2489
  // ../../packages/naro-sdk/src/query/filters.ts
@@ -2647,7 +2901,7 @@ function conditionsFrom(flags) {
2647
2901
  function patchFromFile(context, path, table) {
2648
2902
  let text;
2649
2903
  try {
2650
- text = readFileSync3(resolve3(context.cwd, path), "utf8");
2904
+ text = readFileSync4(resolve3(context.cwd, path), "utf8");
2651
2905
  } catch {
2652
2906
  refuse2(`Could not read ${path}.`);
2653
2907
  }
@@ -2858,7 +3112,11 @@ var UNSHOWN = "has characters that cannot be shown here";
2858
3112
  function statementLine(policy) {
2859
3113
  if (policy.invalid !== null)
2860
3114
  return printable(
2861
- `-- ${policyNamed(policy.name, policy.table)} cannot be read (${commentText(policy.invalid)}); the requests it applies to are refused until it is dropped or created again.`
3115
+ unreadablePolicyComment({
3116
+ name: policy.name,
3117
+ table: policy.table,
3118
+ invalid: policy.invalid
3119
+ })
2862
3120
  );
2863
3121
  return shownStatement(policy, policy.table, "naro policy list --json");
2864
3122
  }
@@ -2911,7 +3169,7 @@ function statementText(context) {
2911
3169
  let text = positional;
2912
3170
  if (file !== void 0) {
2913
3171
  try {
2914
- text = readFileSync3(resolve3(context.cwd, file), "utf8");
3172
+ text = readFileSync4(resolve3(context.cwd, file), "utf8");
2915
3173
  } catch {
2916
3174
  refuse2(`Could not read ${file}.`);
2917
3175
  }
@@ -3273,13 +3531,13 @@ async function runSchemaChange(context, operations, question) {
3273
3531
  let file = null;
3274
3532
  let fileWarning;
3275
3533
  const relDir = relativeToCwd(context, dir);
3276
- const rel = join3(relDir === "." ? "" : relDir, name);
3534
+ const rel = join4(relDir === "." ? "" : relDir, name);
3277
3535
  if (context.flags.noWrite) {
3278
3536
  fileWarning = `Not written to ${relDir} (--no-write): naro db reset will not re-apply ${result.name}, and naro migration list shows it as remote-only.`;
3279
3537
  } else {
3280
3538
  try {
3281
3539
  mkdirSync3(dir, { recursive: true });
3282
- writeFileSync3(join3(dir, name), result.sql, { flag: "wx" });
3540
+ writeFileSync3(join4(dir, name), result.sql, { flag: "wx" });
3283
3541
  file = rel;
3284
3542
  } catch (error) {
3285
3543
  const reason = (error instanceof Error ? error.message : String(error)).replace(/[.\s]+$/, "");
@@ -4130,7 +4388,7 @@ async function runDomainsRemove(context) {
4130
4388
 
4131
4389
  // src/commands/env.ts
4132
4390
  import { existsSync, writeFileSync as writeFileSync4 } from "fs";
4133
- import { join as join4 } from "path";
4391
+ import { join as join5 } from "path";
4134
4392
  var TARGETS = /* @__PURE__ */ new Set(["production", "preview", "all"]);
4135
4393
  var KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
4136
4394
  function parseTarget(value) {
@@ -4233,7 +4491,7 @@ async function runEnvPull(context) {
4233
4491
  const vars = Object.fromEntries(
4234
4492
  selected.map((row) => [row.key, row.value ?? ""])
4235
4493
  );
4236
- const path = join4(context.cwd, file);
4494
+ const path = join5(context.cwd, file);
4237
4495
  if (existsSync(path) && isInteractive() && !context.flags.yes && !context.flags.json) {
4238
4496
  const confirmed = await context.prompter.confirm(`Overwrite ${file}?`);
4239
4497
  if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
@@ -4595,16 +4853,21 @@ Usage:
4595
4853
  and today's writes against the daily write limit (UTC)
4596
4854
  naro db query <sql> | --file <f> Run SQL against it (reads stdin when piped)
4597
4855
  naro db push [--dry-run] Apply pending migrations from ./migrations (asks first; --yes to skip)
4856
+ --dry-run checks their policy statements on this machine and
4857
+ exits 1 on one the server would refuse
4598
4858
  naro db dump [--output <f>] Download a SQL dump of the database (stdout by default)
4599
4859
  [--schema-only|--data-only] [--table a,b] Cloudflare holds other queries until it finishes
4600
4860
  naro db restore --bookmark <id> Roll the database back (Time Travel, last 30 days; asks first)
4601
4861
  | --timestamp <time> [--dry-run] prints the command that undoes it
4602
4862
  naro db pull [--dir <d>] Write the remote schema as migrations/0001_remote_schema.sql
4603
4863
  and record it as applied - a baseline (asks first)
4604
- naro db reset [--dir <d>] Drop everything, clear the ledger, re-apply ./migrations
4605
- [--dry-run] [--yes] (asks first; db restore --bookmark undoes it)
4606
- naro db lint [--strict] Check the schema for what D1 actually trips on
4607
- (exits 1 on an error, --strict on a warning too)
4864
+ naro db reset [--dir <d>] Drop everything and clear the policies and the ledger, then re-apply
4865
+ [--dry-run] [--yes] ./migrations, which create the policies again. Refuses files the server
4866
+ would refuse; lists first the policies the files do not make
4867
+ (asks first; db restore --bookmark undoes it)
4868
+ naro db lint [--strict] Check the schema for what D1 actually trips on, and the RLS policies
4869
+ (rls_no_policy, rls_policy_invalid, rls_policy_text_default,
4870
+ rls_policy_unindexed; exits 1 on an error, --strict on a warning too)
4608
4871
  naro db table create <name> --column <spec> [--column <spec>\u2026]
4609
4872
  [--owner <column>] [--if-not-exists] Create a table without SQL. <spec> is name:type[:modifier\u2026]:
4610
4873
  types integer, real, text, blob, numeric, boolean, datetime, json;
@@ -5035,8 +5298,8 @@ function route(parsed) {
5035
5298
  function version() {
5036
5299
  try {
5037
5300
  const pkg = JSON.parse(
5038
- readFileSync4(
5039
- join5(dirname3(fileURLToPath(import.meta.url)), "..", "package.json"),
5301
+ readFileSync5(
5302
+ join6(dirname3(fileURLToPath(import.meta.url)), "..", "package.json"),
5040
5303
  "utf8"
5041
5304
  )
5042
5305
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naro-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naro": "./dist/index.js"
@@ -34,11 +34,11 @@
34
34
  "typescript": "7.0.2",
35
35
  "vitest": "3.2.7",
36
36
  "naro-js": "0.2.0",
37
- "@next-deploy/build": "0.1.0",
38
- "@next-deploy/config": "0.1.0",
39
- "@next-deploy/sdk": "0.1.0",
37
+ "@next-deploy/core": "0.1.0",
40
38
  "@next-deploy/data-core": "0.1.0",
41
- "@next-deploy/core": "0.1.0"
39
+ "@next-deploy/sdk": "0.1.0",
40
+ "@next-deploy/config": "0.1.0",
41
+ "@next-deploy/build": "0.1.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup && pnpm build:types",