@rehearsal-db/core 0.1.0-beta.6 → 0.1.0-beta.7
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 +26 -1
- package/README.md +10 -3
- package/docs/commands.md +29 -0
- package/docs/configuration.md +65 -7
- package/docs/releasing.md +10 -1
- package/docs/roadmap.md +39 -0
- package/docs/troubleshooting.md +23 -0
- package/package.json +1 -1
- package/scripts/lib/environment/local_supabase.mjs +8 -8
- package/scripts/lib/rehearsal/baseline_artifact.mjs +32 -3
- package/scripts/lib/rehearsal/cleanup.mjs +381 -0
- package/scripts/lib/rehearsal/configuration.d.mts +6 -0
- package/scripts/lib/rehearsal/configuration.mjs +113 -6
- package/scripts/lib/rehearsal/diagnostics.mjs +1 -1
- package/scripts/lib/rehearsal/plan.mjs +4 -4
- package/scripts/lib/runtime/postgresql_runtime.mjs +5 -5
- package/scripts/lib/runtime/supabase_runtime.mjs +2 -0
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +192 -1
|
@@ -468,6 +468,7 @@ const resetRuntime = async () => {
|
|
|
468
468
|
workdir: runtimeWorkdir,
|
|
469
469
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
470
470
|
environment: serviceEnvironment,
|
|
471
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
471
472
|
});
|
|
472
473
|
const structuralSchemaSha256 = await restoreProductionSchema(active);
|
|
473
474
|
await restoreBaselineRows(active);
|
|
@@ -532,6 +533,7 @@ const startRuntime = async () => {
|
|
|
532
533
|
workdir: runtimeWorkdir,
|
|
533
534
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
534
535
|
environment: serviceEnvironment,
|
|
536
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
535
537
|
});
|
|
536
538
|
const projectRuntime = configureProjectRuntime({ environment, baseline });
|
|
537
539
|
await writeRuntimeEnvironment({
|
|
@@ -56,6 +56,10 @@ import {
|
|
|
56
56
|
suggestPolicyExceptionColumns,
|
|
57
57
|
} from "../../lib/rehearsal/policy_review.mjs";
|
|
58
58
|
import { collectRehearsalSupportReport } from "../../lib/rehearsal/support_report.mjs";
|
|
59
|
+
import {
|
|
60
|
+
applyRehearsalCleanup,
|
|
61
|
+
planRehearsalCleanup,
|
|
62
|
+
} from "../../lib/rehearsal/cleanup.mjs";
|
|
59
63
|
|
|
60
64
|
const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
61
65
|
const projectRoot = process.cwd();
|
|
@@ -81,6 +85,9 @@ const parseArguments = (arguments_) => {
|
|
|
81
85
|
ledgerPath: undefined,
|
|
82
86
|
assetsPath: undefined,
|
|
83
87
|
target: undefined,
|
|
88
|
+
includeRuntime: false,
|
|
89
|
+
includeImages: false,
|
|
90
|
+
cleanupConfirmation: undefined,
|
|
84
91
|
};
|
|
85
92
|
const positionals = [];
|
|
86
93
|
for (const argument of arguments_) {
|
|
@@ -95,6 +102,8 @@ const parseArguments = (arguments_) => {
|
|
|
95
102
|
flags.configPath = argument.slice("--config=".length);
|
|
96
103
|
} else if (argument.startsWith("--confirm-candidates=")) {
|
|
97
104
|
flags.confirmation = argument.slice("--confirm-candidates=".length);
|
|
105
|
+
} else if (argument.startsWith("--confirm-cleanup=")) {
|
|
106
|
+
flags.cleanupConfirmation = argument.slice("--confirm-cleanup=".length);
|
|
98
107
|
} else if (argument.startsWith("--records=")) {
|
|
99
108
|
flags.recordsPath = argument.slice("--records=".length);
|
|
100
109
|
} else if (argument.startsWith("--ledger=")) {
|
|
@@ -103,6 +112,10 @@ const parseArguments = (arguments_) => {
|
|
|
103
112
|
flags.assetsPath = argument.slice("--assets=".length);
|
|
104
113
|
} else if (argument.startsWith("--target=")) {
|
|
105
114
|
flags.target = argument.slice("--target=".length);
|
|
115
|
+
} else if (argument === "--include-runtime") {
|
|
116
|
+
flags.includeRuntime = true;
|
|
117
|
+
} else if (argument === "--include-images") {
|
|
118
|
+
flags.includeImages = true;
|
|
106
119
|
} else if (argument.startsWith("--")) {
|
|
107
120
|
throw new Error(`Unknown Rehearsal option: ${argument}.`);
|
|
108
121
|
} else positionals.push(argument);
|
|
@@ -131,7 +144,7 @@ const renderPlan = (plan, verbosity) => {
|
|
|
131
144
|
"Environment",
|
|
132
145
|
` ✓ ${plan.environment.kind}`,
|
|
133
146
|
` ✓ Hosted access ${plan.environment.hostedAccess}`,
|
|
134
|
-
` ✓
|
|
147
|
+
` ✓ Hosted target configuration ${plan.environment.outboundNetwork}`,
|
|
135
148
|
...(plan.environment.authenticationProviders.length
|
|
136
149
|
? [
|
|
137
150
|
` ✓ External identity providers ${plan.environment.authenticationProviders.join(", ")}`,
|
|
@@ -834,6 +847,13 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
834
847
|
command: "manage-runtime",
|
|
835
848
|
});
|
|
836
849
|
}
|
|
850
|
+
if (state.node.supported && state.config === "valid") {
|
|
851
|
+
options.push({
|
|
852
|
+
label: "Clean up disk space",
|
|
853
|
+
hint: "Preview old baselines, the runtime, and unused images",
|
|
854
|
+
command: "cleanup-guide",
|
|
855
|
+
});
|
|
856
|
+
}
|
|
837
857
|
if (lastGuidedDetails) {
|
|
838
858
|
options.push({
|
|
839
859
|
label: "Show details from the last action",
|
|
@@ -982,6 +1002,60 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
982
1002
|
}
|
|
983
1003
|
return runtimeAction.command;
|
|
984
1004
|
}
|
|
1005
|
+
if (selected.command === "cleanup-guide") {
|
|
1006
|
+
const cleanupScope = await promptForChoice({
|
|
1007
|
+
message: "What may Rehearsal include in the cleanup preview?",
|
|
1008
|
+
flags,
|
|
1009
|
+
options: [
|
|
1010
|
+
{
|
|
1011
|
+
label: "Old baselines only",
|
|
1012
|
+
hint: "Keep the configured number of recent generations",
|
|
1013
|
+
command: "baselines",
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
label: "Old baselines and this project's runtime",
|
|
1017
|
+
hint: "Also remove the disposable database and its volumes",
|
|
1018
|
+
command: "runtime",
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
label: "Old baselines and older unused Supabase images",
|
|
1022
|
+
hint: "Images may be shared and can be downloaded again",
|
|
1023
|
+
command: "images",
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
label: "Everything above",
|
|
1027
|
+
command: "all",
|
|
1028
|
+
},
|
|
1029
|
+
{ label: "Back", command: "exit" },
|
|
1030
|
+
],
|
|
1031
|
+
});
|
|
1032
|
+
if (!cleanupScope || cleanupScope.command === "exit") return "refresh";
|
|
1033
|
+
flags.includeRuntime = ["runtime", "all"].includes(cleanupScope.command);
|
|
1034
|
+
flags.includeImages = ["images", "all"].includes(cleanupScope.command);
|
|
1035
|
+
flags.cleanupPlan = await planRehearsalCleanup({
|
|
1036
|
+
...planOptions,
|
|
1037
|
+
includeRuntime: flags.includeRuntime,
|
|
1038
|
+
includeImages: flags.includeImages,
|
|
1039
|
+
});
|
|
1040
|
+
console.log(
|
|
1041
|
+
`\n${renderCleanup({ mode: "preview", plan: flags.cleanupPlan })}`,
|
|
1042
|
+
);
|
|
1043
|
+
const selectedCount =
|
|
1044
|
+
flags.cleanupPlan.baselines.removed.length +
|
|
1045
|
+
(flags.cleanupPlan.runtime.included && flags.cleanupPlan.runtime.detected
|
|
1046
|
+
? 1
|
|
1047
|
+
: 0) +
|
|
1048
|
+
flags.cleanupPlan.images.candidates.length;
|
|
1049
|
+
if (selectedCount === 0) return "refresh";
|
|
1050
|
+
const accepted = await promptForConfirmation(
|
|
1051
|
+
"Remove exactly the resources shown above?",
|
|
1052
|
+
flags,
|
|
1053
|
+
);
|
|
1054
|
+
if (!accepted) return "refresh";
|
|
1055
|
+
flags.write = true;
|
|
1056
|
+
flags.cleanupConfirmation = flags.cleanupPlan.digest;
|
|
1057
|
+
return "cleanup";
|
|
1058
|
+
}
|
|
985
1059
|
if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
|
|
986
1060
|
console.log(
|
|
987
1061
|
[
|
|
@@ -1641,6 +1715,112 @@ const renderSupportReport = (report) => {
|
|
|
1641
1715
|
].join("\n");
|
|
1642
1716
|
};
|
|
1643
1717
|
|
|
1718
|
+
const formatBytes = (bytes) => {
|
|
1719
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
1720
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
1721
|
+
const unit = Math.min(
|
|
1722
|
+
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
1723
|
+
units.length - 1,
|
|
1724
|
+
);
|
|
1725
|
+
const value = bytes / 1024 ** unit;
|
|
1726
|
+
return `${value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unit]}`;
|
|
1727
|
+
};
|
|
1728
|
+
|
|
1729
|
+
const renderCleanup = (result) => {
|
|
1730
|
+
const plan = result.plan;
|
|
1731
|
+
const imageBytes = plan.images.candidates.reduce(
|
|
1732
|
+
(total, image) => total + image.bytes,
|
|
1733
|
+
0,
|
|
1734
|
+
);
|
|
1735
|
+
const dockerImages = plan.docker.resources.find(
|
|
1736
|
+
(resource) => resource.Type === "Images",
|
|
1737
|
+
);
|
|
1738
|
+
const selectedCount =
|
|
1739
|
+
plan.baselines.removed.length +
|
|
1740
|
+
(plan.runtime.included && plan.runtime.detected ? 1 : 0) +
|
|
1741
|
+
plan.images.candidates.length;
|
|
1742
|
+
return [
|
|
1743
|
+
`REHEARSAL CLEANUP — ${result.mode.toUpperCase()}`,
|
|
1744
|
+
`Project: ${plan.project}`,
|
|
1745
|
+
...(dockerImages
|
|
1746
|
+
? [
|
|
1747
|
+
`Docker images: ${dockerImages.Size}; ${dockerImages.Reclaimable} reclaimable across Docker`,
|
|
1748
|
+
]
|
|
1749
|
+
: ["Docker usage: unavailable"]),
|
|
1750
|
+
...(plan.docker.disk
|
|
1751
|
+
? [
|
|
1752
|
+
`Container disk: ${formatBytes(plan.docker.disk.usedBytes)} used of ${formatBytes(plan.docker.disk.totalBytes)}; ${formatBytes(plan.docker.disk.availableBytes)} free (${plan.docker.disk.usedPercent}% used)`,
|
|
1753
|
+
]
|
|
1754
|
+
: []),
|
|
1755
|
+
"",
|
|
1756
|
+
"Project-owned files",
|
|
1757
|
+
plan.baselines.available
|
|
1758
|
+
? ` ${plan.baselines.removed.length ? "→" : "✓"} ${formatCount(plan.baselines.removed.length, "old baseline generation")} selected; keeping ${plan.baselines.retained.length}`
|
|
1759
|
+
: " ○ No active baseline found",
|
|
1760
|
+
"",
|
|
1761
|
+
"Disposable runtime",
|
|
1762
|
+
!plan.runtime.detected
|
|
1763
|
+
? " ✓ No runtime found"
|
|
1764
|
+
: plan.runtime.included
|
|
1765
|
+
? " → Current project runtime selected"
|
|
1766
|
+
: " ○ Current project runtime preserved (add --include-runtime to select it)",
|
|
1767
|
+
"",
|
|
1768
|
+
"Shared Docker images",
|
|
1769
|
+
!plan.images.requested
|
|
1770
|
+
? " ○ Not inspected (add --include-images to inspect older unused Supabase images)"
|
|
1771
|
+
: !plan.images.inspected
|
|
1772
|
+
? " ! Docker is unavailable; images could not be inspected"
|
|
1773
|
+
: plan.images.candidates.length
|
|
1774
|
+
? ` → ${formatCount(plan.images.candidates.length, "older unused Supabase image")} selected; approximately ${formatBytes(imageBytes)}`
|
|
1775
|
+
: " ✓ No older unused Supabase images found",
|
|
1776
|
+
...(plan.images.candidates.length
|
|
1777
|
+
? plan.images.candidates.flatMap((image) =>
|
|
1778
|
+
image.tags.map((tag) => ` ${tag}`),
|
|
1779
|
+
)
|
|
1780
|
+
: []),
|
|
1781
|
+
"",
|
|
1782
|
+
...(result.mode === "preview"
|
|
1783
|
+
? selectedCount
|
|
1784
|
+
? [
|
|
1785
|
+
"Nothing has been removed.",
|
|
1786
|
+
`Cleanup set: ${plan.digest.slice(0, 12)}`,
|
|
1787
|
+
`Exact confirmation: --confirm-cleanup=${plan.digest}`,
|
|
1788
|
+
"Review this list, then rerun the same command with --write and the exact confirmation above.",
|
|
1789
|
+
]
|
|
1790
|
+
: [
|
|
1791
|
+
"Nothing has been removed, and nothing is currently selected.",
|
|
1792
|
+
"Use --include-runtime or --include-images only when you want those shared resources considered.",
|
|
1793
|
+
]
|
|
1794
|
+
: [
|
|
1795
|
+
`Removed ${formatCount(result.applied.baselines.removed.length, "old baseline generation")}.`,
|
|
1796
|
+
result.applied.runtimeRemoved
|
|
1797
|
+
? "Removed this project's disposable runtime and volumes."
|
|
1798
|
+
: "Preserved this project's disposable runtime.",
|
|
1799
|
+
`Removed ${formatCount(result.applied.imagesRemoved.length, "unused image tag")}.`,
|
|
1800
|
+
]),
|
|
1801
|
+
].join("\n");
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
const runCleanup = async ({ flags, planOptions }) => {
|
|
1805
|
+
const plan =
|
|
1806
|
+
flags.cleanupPlan ??
|
|
1807
|
+
(await planRehearsalCleanup({
|
|
1808
|
+
...planOptions,
|
|
1809
|
+
includeRuntime: flags.includeRuntime,
|
|
1810
|
+
includeImages: flags.includeImages,
|
|
1811
|
+
}));
|
|
1812
|
+
if (!flags.write) return { mode: "preview", plan };
|
|
1813
|
+
const applied = await applyRehearsalCleanup({
|
|
1814
|
+
plan,
|
|
1815
|
+
confirmation: flags.cleanupConfirmation,
|
|
1816
|
+
...planOptions,
|
|
1817
|
+
removeRuntime: async () => {
|
|
1818
|
+
runManager({ action: "discard", flags });
|
|
1819
|
+
},
|
|
1820
|
+
});
|
|
1821
|
+
return { mode: "written", plan, applied };
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1644
1824
|
const usage = () => `Usage: rehearsal <command> [options]
|
|
1645
1825
|
|
|
1646
1826
|
Commands:
|
|
@@ -1663,6 +1843,8 @@ Commands:
|
|
|
1663
1843
|
status Report the disposable local runtime state
|
|
1664
1844
|
stop Stop only this project's local runtime
|
|
1665
1845
|
discard Remove only this project's disposable runtime
|
|
1846
|
+
cleanup [--include-runtime] [--include-images] [--write] [--confirm-cleanup=]
|
|
1847
|
+
Preview or apply conservative disk cleanup
|
|
1666
1848
|
verify Verify the current local Rehearsal runtime
|
|
1667
1849
|
|
|
1668
1850
|
Options: --json --verbose --debug --plain --config=<path> --target=supabase|postgresql`;
|
|
@@ -1737,6 +1919,11 @@ const executeCommand = async ({ command, flags, planOptions, guided }) => {
|
|
|
1737
1919
|
emit({ command, data, flags, render: renderSupportReport });
|
|
1738
1920
|
return;
|
|
1739
1921
|
}
|
|
1922
|
+
if (command === "cleanup") {
|
|
1923
|
+
const data = await runCleanup({ flags, planOptions });
|
|
1924
|
+
emit({ command, data, flags, render: renderCleanup });
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1740
1927
|
if (command === "explain" || (command === "run" && flags.dryRun)) {
|
|
1741
1928
|
const data = await buildRehearsalPlan(planOptions);
|
|
1742
1929
|
emit({ command, data, flags, render: renderPlan });
|
|
@@ -1872,8 +2059,12 @@ const resetGuidedFlags = (flags) => {
|
|
|
1872
2059
|
flags.ledgerPath = undefined;
|
|
1873
2060
|
flags.assetsPath = undefined;
|
|
1874
2061
|
flags.target = undefined;
|
|
2062
|
+
flags.includeRuntime = false;
|
|
2063
|
+
flags.includeImages = false;
|
|
2064
|
+
flags.cleanupConfirmation = undefined;
|
|
1875
2065
|
flags.setupPlan = undefined;
|
|
1876
2066
|
flags.preparationPlan = undefined;
|
|
2067
|
+
flags.cleanupPlan = undefined;
|
|
1877
2068
|
};
|
|
1878
2069
|
|
|
1879
2070
|
const installGuidedExitShortcut = (flags) => {
|