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/README.md +202 -12
- package/dist/{chunk-L7JOGWP3.js → chunk-GFCG5C7D.js} +3678 -263
- package/dist/deploy.js +1 -1
- package/dist/index.js +302 -39
- package/package.json +5 -5
package/dist/deploy.js
CHANGED
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-
|
|
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
|
|
454
|
-
import { dirname as dirname3, join as
|
|
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: {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
{
|
|
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(
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
|
2483
|
+
import { join as join4 } from "path";
|
|
2230
2484
|
|
|
2231
2485
|
// src/commands/policy.ts
|
|
2232
|
-
import { readFileSync as
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
4605
|
-
[--dry-run] [--yes]
|
|
4606
|
-
|
|
4607
|
-
(
|
|
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
|
-
|
|
5039
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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",
|