@rehearsal-db/core 0.1.0-beta.5 → 0.1.0-beta.6
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/CHANGELOG.md +31 -1
- package/COMPATIBILITY.md +5 -0
- package/README.md +105 -377
- package/SECURITY.md +2 -1
- package/docs/adapters.md +5 -0
- package/docs/baselines.md +81 -22
- package/docs/commands.md +76 -75
- package/docs/configuration.md +47 -4
- package/docs/getting-started.md +88 -227
- package/docs/glossary.md +7 -8
- package/docs/security-model.md +5 -1
- package/docs/troubleshooting.md +46 -1
- package/docs/tutorial.md +70 -59
- package/package.json +4 -2
- package/scripts/lib/rehearsal/configuration.d.mts +18 -3
- package/scripts/lib/rehearsal/configuration.mjs +227 -56
- package/scripts/lib/rehearsal/diagnostics.mjs +4 -2
- package/scripts/lib/rehearsal/plan.mjs +85 -10
- package/scripts/lib/rehearsal/runtime_restore.mjs +6 -2
- package/scripts/lib/rehearsal/setup.mjs +60 -28
- package/scripts/lib/runtime/postgresql_runtime.mjs +772 -0
- package/scripts/lib/runtime/runtime_target.mjs +41 -0
- package/scripts/lib/runtime/supabase_runtime.mjs +790 -0
- package/scripts/operations/database/manage_rehearsal_database.mjs +6 -785
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +138 -30
|
@@ -80,6 +80,7 @@ const parseArguments = (arguments_) => {
|
|
|
80
80
|
recordsPath: undefined,
|
|
81
81
|
ledgerPath: undefined,
|
|
82
82
|
assetsPath: undefined,
|
|
83
|
+
target: undefined,
|
|
83
84
|
};
|
|
84
85
|
const positionals = [];
|
|
85
86
|
for (const argument of arguments_) {
|
|
@@ -100,6 +101,8 @@ const parseArguments = (arguments_) => {
|
|
|
100
101
|
flags.ledgerPath = argument.slice("--ledger=".length);
|
|
101
102
|
} else if (argument.startsWith("--assets=")) {
|
|
102
103
|
flags.assetsPath = argument.slice("--assets=".length);
|
|
104
|
+
} else if (argument.startsWith("--target=")) {
|
|
105
|
+
flags.target = argument.slice("--target=".length);
|
|
103
106
|
} else if (argument.startsWith("--")) {
|
|
104
107
|
throw new Error(`Unknown Rehearsal option: ${argument}.`);
|
|
105
108
|
} else positionals.push(argument);
|
|
@@ -274,7 +277,12 @@ const formatDuration = (durationMs) =>
|
|
|
274
277
|
? `${Math.round(durationMs)}ms`
|
|
275
278
|
: `${(durationMs / 1_000).toFixed(1)}s`;
|
|
276
279
|
|
|
277
|
-
const promptForChoice = async ({
|
|
280
|
+
const promptForChoice = async ({
|
|
281
|
+
message,
|
|
282
|
+
options,
|
|
283
|
+
flags = {},
|
|
284
|
+
optionsAlreadyShown = false,
|
|
285
|
+
}) => {
|
|
278
286
|
if (useStyledPrompts(flags)) {
|
|
279
287
|
const selected = await prompts.select({
|
|
280
288
|
message,
|
|
@@ -289,6 +297,18 @@ const promptForChoice = async ({ message, options, flags = {} }) => {
|
|
|
289
297
|
? options.find((option) => option.command === "exit")
|
|
290
298
|
: selected;
|
|
291
299
|
}
|
|
300
|
+
if (!optionsAlreadyShown) {
|
|
301
|
+
console.log(
|
|
302
|
+
[
|
|
303
|
+
"",
|
|
304
|
+
...options.map(
|
|
305
|
+
(option, index) =>
|
|
306
|
+
` ${index === 0 ? "›" : " "} ${index + 1}. ${option.label}${option.hint ? ` — ${option.hint}` : ""}`,
|
|
307
|
+
),
|
|
308
|
+
"",
|
|
309
|
+
].join("\n"),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
292
312
|
const prompt = createInterface({
|
|
293
313
|
input: process.stdin,
|
|
294
314
|
output: process.stdout,
|
|
@@ -502,6 +522,7 @@ const inspectGuidedProject = async (planOptions) => {
|
|
|
502
522
|
detected,
|
|
503
523
|
node,
|
|
504
524
|
config: "missing",
|
|
525
|
+
target: detected.hasSupabaseConfig ? "supabase" : "postgresql",
|
|
505
526
|
baseline: false,
|
|
506
527
|
runtime: false,
|
|
507
528
|
};
|
|
@@ -514,6 +535,7 @@ const inspectGuidedProject = async (planOptions) => {
|
|
|
514
535
|
detected,
|
|
515
536
|
node,
|
|
516
537
|
config: "invalid",
|
|
538
|
+
target: undefined,
|
|
517
539
|
configPath,
|
|
518
540
|
configError: redactDiagnosticValue(String(error?.message ?? error)),
|
|
519
541
|
baseline: false,
|
|
@@ -544,6 +566,7 @@ const inspectGuidedProject = async (planOptions) => {
|
|
|
544
566
|
detected,
|
|
545
567
|
node,
|
|
546
568
|
config: "valid",
|
|
569
|
+
target: loaded.config.runtime.target,
|
|
547
570
|
configPath,
|
|
548
571
|
baseline: Boolean(migrationSummary),
|
|
549
572
|
policy,
|
|
@@ -829,8 +852,16 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
829
852
|
|
|
830
853
|
const statusLines = [
|
|
831
854
|
`${marker(state.node.supported)} Node.js ${state.node.version}${state.node.supported ? "" : " (Node.js 24 required)"}`,
|
|
832
|
-
`${marker(
|
|
833
|
-
|
|
855
|
+
`${marker(
|
|
856
|
+
state.target === "supabase"
|
|
857
|
+
? state.detected.hasSupabaseConfig
|
|
858
|
+
: state.detected.hasPostgresqlMigrations,
|
|
859
|
+
)} ${state.target === "supabase" ? "Supabase" : "PostgreSQL"} project detected`,
|
|
860
|
+
`${marker(
|
|
861
|
+
state.target === "supabase"
|
|
862
|
+
? state.detected.hasMigrations
|
|
863
|
+
: state.detected.hasPostgresqlMigrations,
|
|
864
|
+
)} Migration history detected`,
|
|
834
865
|
`${marker(state.config === "valid")} Rehearsal configuration${state.config === "invalid" ? " needs attention" : ""}`,
|
|
835
866
|
...(state.config === "valid"
|
|
836
867
|
? [
|
|
@@ -882,6 +913,7 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
882
913
|
message: "What would you like to do?",
|
|
883
914
|
options,
|
|
884
915
|
flags,
|
|
916
|
+
optionsAlreadyShown: !useStyledPrompts(flags),
|
|
885
917
|
});
|
|
886
918
|
if (selected.command === "exit") {
|
|
887
919
|
return null;
|
|
@@ -956,7 +988,7 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
956
988
|
"",
|
|
957
989
|
terminalStyle(flags, "1", "CREATE A SAFE BASELINE"),
|
|
958
990
|
"Rehearsal only reads the local files you name here.",
|
|
959
|
-
"It does not extract data or contact a hosted
|
|
991
|
+
"It does not extract data or contact a hosted database.",
|
|
960
992
|
"",
|
|
961
993
|
].join("\n"),
|
|
962
994
|
);
|
|
@@ -968,9 +1000,13 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
968
1000
|
discovered.ledgers.length
|
|
969
1001
|
? `Migration ledgers: ${discovered.ledgers.join(", ")}`
|
|
970
1002
|
: "Migration ledgers: none detected",
|
|
971
|
-
|
|
972
|
-
?
|
|
973
|
-
|
|
1003
|
+
...(state.target === "supabase"
|
|
1004
|
+
? [
|
|
1005
|
+
discovered.assetManifests.length
|
|
1006
|
+
? `Storage manifests: ${discovered.assetManifests.join(", ")}`
|
|
1007
|
+
: "Storage manifests: none detected",
|
|
1008
|
+
]
|
|
1009
|
+
: []),
|
|
974
1010
|
"Only paths and file structure were inspected; row values stay hidden.",
|
|
975
1011
|
];
|
|
976
1012
|
if (useStyledPrompts(flags)) {
|
|
@@ -1017,12 +1053,15 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
1017
1053
|
flags.write = true;
|
|
1018
1054
|
return "baseline prepare";
|
|
1019
1055
|
}
|
|
1020
|
-
flags.assetsPath =
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1056
|
+
flags.assetsPath =
|
|
1057
|
+
state.target === "supabase"
|
|
1058
|
+
? await promptForDiscoveredPath({
|
|
1059
|
+
message: "Storage assets",
|
|
1060
|
+
candidates: discovered.assetManifests,
|
|
1061
|
+
optional: true,
|
|
1062
|
+
flags,
|
|
1063
|
+
})
|
|
1064
|
+
: undefined;
|
|
1026
1065
|
const inspection = await inspectBaselineInputFiles({
|
|
1027
1066
|
...planOptions,
|
|
1028
1067
|
recordsPath: flags.recordsPath,
|
|
@@ -1052,7 +1091,26 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
1052
1091
|
}
|
|
1053
1092
|
if (selected.command !== "setup-write") return selected.command;
|
|
1054
1093
|
|
|
1055
|
-
const
|
|
1094
|
+
const targetChoice = await promptForChoice({
|
|
1095
|
+
message: "Which database should Rehearsal use?",
|
|
1096
|
+
flags,
|
|
1097
|
+
options:
|
|
1098
|
+
state.target === "supabase"
|
|
1099
|
+
? [
|
|
1100
|
+
{ label: "Supabase", command: "supabase" },
|
|
1101
|
+
{ label: "PostgreSQL", command: "postgresql" },
|
|
1102
|
+
]
|
|
1103
|
+
: [
|
|
1104
|
+
{ label: "PostgreSQL", command: "postgresql" },
|
|
1105
|
+
{ label: "Supabase", command: "supabase" },
|
|
1106
|
+
],
|
|
1107
|
+
});
|
|
1108
|
+
if (!targetChoice) return "refresh";
|
|
1109
|
+
flags.target = targetChoice.command;
|
|
1110
|
+
const setupPlan = await planRehearsalSetup({
|
|
1111
|
+
projectRoot,
|
|
1112
|
+
target: flags.target,
|
|
1113
|
+
});
|
|
1056
1114
|
const preview = {
|
|
1057
1115
|
...summarizeRehearsalSetup(setupPlan, { mode: "preview" }),
|
|
1058
1116
|
nextAction:
|
|
@@ -1215,6 +1273,14 @@ const runManager = ({ action, flags }) => {
|
|
|
1215
1273
|
const inferred = normalizeRehearsalError(
|
|
1216
1274
|
new Error(completeOutput || `Rehearsal ${action} failed.`),
|
|
1217
1275
|
);
|
|
1276
|
+
const category =
|
|
1277
|
+
action === "migrate" ||
|
|
1278
|
+
(action === "run" &&
|
|
1279
|
+
/migrateRuntime|candidate migration|migration up/iu.test(
|
|
1280
|
+
completeOutput,
|
|
1281
|
+
))
|
|
1282
|
+
? "migration_candidate_failure"
|
|
1283
|
+
: inferred.category;
|
|
1218
1284
|
const conciseOutput = completeOutput
|
|
1219
1285
|
.replaceAll(String.fromCodePoint(27), "")
|
|
1220
1286
|
.split("\n")
|
|
@@ -1224,7 +1290,7 @@ const runManager = ({ action, flags }) => {
|
|
|
1224
1290
|
.join("\n")
|
|
1225
1291
|
.slice(-2_000);
|
|
1226
1292
|
throw new RehearsalError({
|
|
1227
|
-
category
|
|
1293
|
+
category,
|
|
1228
1294
|
code: `MANAGER_${action.toUpperCase()}_FAILED`,
|
|
1229
1295
|
message: `The local Rehearsal ${action} operation did not complete.`,
|
|
1230
1296
|
expected: "the isolated runtime operation to finish and verify",
|
|
@@ -1382,7 +1448,12 @@ const renderInit = (result) =>
|
|
|
1382
1448
|
].join("\n");
|
|
1383
1449
|
|
|
1384
1450
|
const runSetup = async ({ flags, planOptions }) => {
|
|
1385
|
-
const plan =
|
|
1451
|
+
const plan =
|
|
1452
|
+
flags.setupPlan ??
|
|
1453
|
+
(await planRehearsalSetup({
|
|
1454
|
+
projectRoot,
|
|
1455
|
+
target: flags.target ?? "supabase",
|
|
1456
|
+
}));
|
|
1386
1457
|
if (flags.write) await applyRehearsalSetup(plan);
|
|
1387
1458
|
const result = summarizeRehearsalSetup(plan, {
|
|
1388
1459
|
mode: flags.write ? "written" : "preview",
|
|
@@ -1454,9 +1525,12 @@ const renderSetup = (result) => {
|
|
|
1454
1525
|
return [
|
|
1455
1526
|
`REHEARSAL SETUP — ${result.mode.toUpperCase()}`,
|
|
1456
1527
|
`Project: ${result.project}`,
|
|
1528
|
+
`Database: ${result.target === "postgresql" ? "PostgreSQL" : "Supabase"}`,
|
|
1457
1529
|
`Runtime ID: ${result.projectId}`,
|
|
1458
1530
|
`Application: ${result.applicationUrl}`,
|
|
1459
|
-
|
|
1531
|
+
result.target === "postgresql"
|
|
1532
|
+
? `Port: database ${result.ports.database}`
|
|
1533
|
+
: `Ports: API ${result.ports.api}, database ${result.ports.database}, Studio ${result.ports.studio}`,
|
|
1460
1534
|
"",
|
|
1461
1535
|
"Files",
|
|
1462
1536
|
...result.files.map(
|
|
@@ -1571,7 +1645,7 @@ const usage = () => `Usage: rehearsal <command> [options]
|
|
|
1571
1645
|
|
|
1572
1646
|
Commands:
|
|
1573
1647
|
guide Open the interactive, state-aware home screen
|
|
1574
|
-
setup [--write]
|
|
1648
|
+
setup [--target=] [--write] Preview or create safe first-run scaffolding
|
|
1575
1649
|
init [--write] Preview or explicitly write safe starter config
|
|
1576
1650
|
baseline prepare --records= --ledger= [--write] Create a fail-closed policy draft
|
|
1577
1651
|
baseline create --records= --ledger= [--assets=] Create a baseline from safe local inputs
|
|
@@ -1591,7 +1665,7 @@ Commands:
|
|
|
1591
1665
|
discard Remove only this project's disposable runtime
|
|
1592
1666
|
verify Verify the current local Rehearsal runtime
|
|
1593
1667
|
|
|
1594
|
-
Options: --json --verbose --debug --plain --config=<path
|
|
1668
|
+
Options: --json --verbose --debug --plain --config=<path> --target=supabase|postgresql`;
|
|
1595
1669
|
|
|
1596
1670
|
const executeCommand = async ({ command, flags, planOptions, guided }) => {
|
|
1597
1671
|
if (command === "help" || flags.help) {
|
|
@@ -1797,10 +1871,39 @@ const resetGuidedFlags = (flags) => {
|
|
|
1797
1871
|
flags.recordsPath = undefined;
|
|
1798
1872
|
flags.ledgerPath = undefined;
|
|
1799
1873
|
flags.assetsPath = undefined;
|
|
1874
|
+
flags.target = undefined;
|
|
1800
1875
|
flags.setupPlan = undefined;
|
|
1801
1876
|
flags.preparationPlan = undefined;
|
|
1802
1877
|
};
|
|
1803
1878
|
|
|
1879
|
+
const installGuidedExitShortcut = (flags) => {
|
|
1880
|
+
let exiting = false;
|
|
1881
|
+
const exit = () => {
|
|
1882
|
+
if (exiting) return;
|
|
1883
|
+
exiting = true;
|
|
1884
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
1885
|
+
process.stdin.setRawMode(false);
|
|
1886
|
+
}
|
|
1887
|
+
process.stdout.write("\u001B[?25h");
|
|
1888
|
+
if (useStyledPrompts(flags)) {
|
|
1889
|
+
prompts.outro("Rehearsal exited.");
|
|
1890
|
+
} else {
|
|
1891
|
+
console.log("\nRehearsal exited.");
|
|
1892
|
+
}
|
|
1893
|
+
process.exit(0);
|
|
1894
|
+
};
|
|
1895
|
+
const onInput = (chunk) => {
|
|
1896
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1897
|
+
if (bytes.includes(0x1a)) exit();
|
|
1898
|
+
};
|
|
1899
|
+
process.stdin.prependListener("data", onInput);
|
|
1900
|
+
process.prependListener("SIGTSTP", exit);
|
|
1901
|
+
return () => {
|
|
1902
|
+
process.stdin.removeListener("data", onInput);
|
|
1903
|
+
process.removeListener("SIGTSTP", exit);
|
|
1904
|
+
};
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1804
1907
|
const main = async () => {
|
|
1805
1908
|
const { flags, positionals } = parseArguments(process.argv.slice(2));
|
|
1806
1909
|
const command = positionals.join(" ") || "help";
|
|
@@ -1825,20 +1928,25 @@ const main = async () => {
|
|
|
1825
1928
|
);
|
|
1826
1929
|
}
|
|
1827
1930
|
flags.guided = true;
|
|
1931
|
+
const removeGuidedExitShortcut = installGuidedExitShortcut(flags);
|
|
1828
1932
|
if (useStyledPrompts(flags)) {
|
|
1829
1933
|
prompts.intro("REHEARSAL · Safe local migration testing");
|
|
1830
1934
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1935
|
+
try {
|
|
1936
|
+
while (true) {
|
|
1937
|
+
resetGuidedFlags(flags);
|
|
1938
|
+
const selected = await runGuidedHome({ flags, planOptions });
|
|
1939
|
+
if (!selected) break;
|
|
1940
|
+
if (selected === "refresh") continue;
|
|
1941
|
+
await executeCommand({
|
|
1942
|
+
command: selected,
|
|
1943
|
+
flags,
|
|
1944
|
+
planOptions,
|
|
1945
|
+
guided: true,
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
} finally {
|
|
1949
|
+
removeGuidedExitShortcut();
|
|
1842
1950
|
}
|
|
1843
1951
|
if (useStyledPrompts(flags)) {
|
|
1844
1952
|
prompts.outro("See you at the next rehearsal.");
|