@rehearsal-db/core 0.1.0-beta.6 → 0.1.0-beta.8
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 +35 -1
- package/README.md +10 -3
- package/docs/commands.md +35 -1
- 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 +10 -9
- package/scripts/lib/runtime/runtime_target.mjs +25 -0
- package/scripts/lib/runtime/supabase_runtime.mjs +7 -4
- package/scripts/operations/database/manage_rehearsal_database.mjs +9 -2
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +199 -5
|
@@ -27,10 +27,14 @@ import {
|
|
|
27
27
|
summarizeRestoreError,
|
|
28
28
|
} from "../rehearsal/runtime_restore.mjs";
|
|
29
29
|
import { readBoundRuntimeSanitizationPolicy } from "../rehearsal/sanitization_policy.mjs";
|
|
30
|
+
import { ensureLocalContainerRuntime } from "../environment/local_supabase.mjs";
|
|
31
|
+
import { parseRuntimeInvocation } from "./runtime_target.mjs";
|
|
30
32
|
|
|
31
33
|
const repositoryRoot = process.cwd();
|
|
34
|
+
const invocation = parseRuntimeInvocation();
|
|
32
35
|
const loadedConfiguration = await loadRehearsalConfig({
|
|
33
36
|
projectRoot: repositoryRoot,
|
|
37
|
+
configPath: invocation.configPath,
|
|
34
38
|
});
|
|
35
39
|
const { config, paths: configuredPaths } = loadedConfiguration;
|
|
36
40
|
const artifactRoot = configuredPaths.artifactDirectory;
|
|
@@ -50,7 +54,7 @@ const image = config.postgresql.image;
|
|
|
50
54
|
const database = config.postgresql.database;
|
|
51
55
|
const databaseUser = config.postgresql.user;
|
|
52
56
|
const databasePort = config.runtime.ports.database;
|
|
53
|
-
const action =
|
|
57
|
+
const action = invocation.action;
|
|
54
58
|
const applicationMigrations = new URL(
|
|
55
59
|
"./",
|
|
56
60
|
pathToFileURL(`${configuredPaths.migrationDirectory}/`),
|
|
@@ -105,11 +109,10 @@ const assertRuntimePath = () => {
|
|
|
105
109
|
};
|
|
106
110
|
|
|
107
111
|
const assertDocker = () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
112
|
+
ensureLocalContainerRuntime({
|
|
113
|
+
cwd: repositoryRoot,
|
|
114
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
115
|
+
});
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
const inspectResourceLabel = (kind, name) =>
|
|
@@ -650,9 +653,7 @@ const migrateRuntime = async () => {
|
|
|
650
653
|
const { baseline } = await readActive();
|
|
651
654
|
await startRuntime();
|
|
652
655
|
const receipt = await readCandidateReceipt(baseline);
|
|
653
|
-
const confirmation =
|
|
654
|
-
.find((argument) => argument.startsWith("--confirm-candidates="))
|
|
655
|
-
?.slice("--confirm-candidates=".length);
|
|
656
|
+
const confirmation = invocation.confirmation;
|
|
656
657
|
if (confirmation !== receipt.candidateSha256) {
|
|
657
658
|
throw new Error(
|
|
658
659
|
"Candidate migration confirmation is missing or does not match the exact SHA-256.",
|
|
@@ -17,6 +17,31 @@ const TARGETS = Object.freeze({
|
|
|
17
17
|
|
|
18
18
|
export const REHEARSAL_RUNTIME_TARGETS = Object.freeze(Object.keys(TARGETS));
|
|
19
19
|
|
|
20
|
+
export const parseRuntimeInvocation = (arguments_ = process.argv.slice(2)) => {
|
|
21
|
+
const action = arguments_.find((argument) => !argument.startsWith("--"));
|
|
22
|
+
const valueAfter = (prefix) =>
|
|
23
|
+
arguments_
|
|
24
|
+
.find((argument) => argument.startsWith(prefix))
|
|
25
|
+
?.slice(prefix.length);
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
action: action ?? "status",
|
|
28
|
+
configPath: valueAfter("--config="),
|
|
29
|
+
confirmation: valueAfter("--confirm-candidates="),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const buildRuntimeManagerArguments = ({
|
|
34
|
+
managerPath,
|
|
35
|
+
action,
|
|
36
|
+
configPath,
|
|
37
|
+
confirmation,
|
|
38
|
+
}) => [
|
|
39
|
+
managerPath,
|
|
40
|
+
action,
|
|
41
|
+
...(configPath ? [`--config=${configPath}`] : []),
|
|
42
|
+
...(confirmation ? [`--confirm-candidates=${confirmation}`] : []),
|
|
43
|
+
];
|
|
44
|
+
|
|
20
45
|
export const resolveRuntimeTarget = (value = "supabase") => {
|
|
21
46
|
if (typeof value !== "string" || !Object.hasOwn(TARGETS, value)) {
|
|
22
47
|
throw new Error(
|
|
@@ -50,10 +50,13 @@ import {
|
|
|
50
50
|
compareProductionSchemaDumps,
|
|
51
51
|
} from "../rehearsal/schema_snapshot.mjs";
|
|
52
52
|
import { formatCount } from "../rehearsal/human_output.mjs";
|
|
53
|
+
import { parseRuntimeInvocation } from "./runtime_target.mjs";
|
|
53
54
|
|
|
54
55
|
const repositoryRoot = process.cwd();
|
|
56
|
+
const invocation = parseRuntimeInvocation();
|
|
55
57
|
const loadedConfiguration = await loadRehearsalConfig({
|
|
56
58
|
projectRoot: repositoryRoot,
|
|
59
|
+
configPath: invocation.configPath,
|
|
57
60
|
});
|
|
58
61
|
const { config, paths: configuredPaths } = loadedConfiguration;
|
|
59
62
|
const artifactRoot = configuredPaths.artifactDirectory;
|
|
@@ -76,7 +79,7 @@ const runtimeAdapter = configuredPaths.runtimeAdapter
|
|
|
76
79
|
? await import(pathToFileURL(configuredPaths.runtimeAdapter).href)
|
|
77
80
|
: null;
|
|
78
81
|
const environmentKeyPattern = /^[A-Z][A-Z0-9_]*$/u;
|
|
79
|
-
const action =
|
|
82
|
+
const action = invocation.action;
|
|
80
83
|
|
|
81
84
|
const readServiceEnvironment = () =>
|
|
82
85
|
readRehearsalServiceEnvironment({
|
|
@@ -468,6 +471,7 @@ const resetRuntime = async () => {
|
|
|
468
471
|
workdir: runtimeWorkdir,
|
|
469
472
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
470
473
|
environment: serviceEnvironment,
|
|
474
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
471
475
|
});
|
|
472
476
|
const structuralSchemaSha256 = await restoreProductionSchema(active);
|
|
473
477
|
await restoreBaselineRows(active);
|
|
@@ -532,6 +536,7 @@ const startRuntime = async () => {
|
|
|
532
536
|
workdir: runtimeWorkdir,
|
|
533
537
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
534
538
|
environment: serviceEnvironment,
|
|
539
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
535
540
|
});
|
|
536
541
|
const projectRuntime = configureProjectRuntime({ environment, baseline });
|
|
537
542
|
await writeRuntimeEnvironment({
|
|
@@ -660,9 +665,7 @@ const migrateRuntime = async () => {
|
|
|
660
665
|
console.log(
|
|
661
666
|
`${receipt.candidates.length === 1 ? "Candidate migration" : "Candidate migrations"} (${receipt.candidateSha256}): ${receipt.candidates.map((candidate) => candidate.filename).join(", ")}`,
|
|
662
667
|
);
|
|
663
|
-
const confirmation =
|
|
664
|
-
.find((argument) => argument.startsWith("--confirm-candidates="))
|
|
665
|
-
?.slice("--confirm-candidates=".length);
|
|
668
|
+
const confirmation = invocation.confirmation;
|
|
666
669
|
if (confirmation !== receipt.candidateSha256) {
|
|
667
670
|
throw new Error(
|
|
668
671
|
"Candidate migration confirmation is missing or does not match the exact SHA-256.",
|
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { loadRehearsalConfig } from "../../lib/rehearsal/configuration.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
parseRuntimeInvocation,
|
|
10
|
+
runRuntimeTarget,
|
|
11
|
+
} from "../../lib/runtime/runtime_target.mjs";
|
|
9
12
|
|
|
10
|
-
const
|
|
13
|
+
const invocation = parseRuntimeInvocation();
|
|
14
|
+
const { config } = await loadRehearsalConfig({
|
|
15
|
+
projectRoot: process.cwd(),
|
|
16
|
+
configPath: invocation.configPath,
|
|
17
|
+
});
|
|
11
18
|
await runRuntimeTarget(config.runtime.target);
|
|
@@ -56,6 +56,11 @@ 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";
|
|
63
|
+
import { buildRuntimeManagerArguments } from "../../lib/runtime/runtime_target.mjs";
|
|
59
64
|
|
|
60
65
|
const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
61
66
|
const projectRoot = process.cwd();
|
|
@@ -81,6 +86,9 @@ const parseArguments = (arguments_) => {
|
|
|
81
86
|
ledgerPath: undefined,
|
|
82
87
|
assetsPath: undefined,
|
|
83
88
|
target: undefined,
|
|
89
|
+
includeRuntime: false,
|
|
90
|
+
includeImages: false,
|
|
91
|
+
cleanupConfirmation: undefined,
|
|
84
92
|
};
|
|
85
93
|
const positionals = [];
|
|
86
94
|
for (const argument of arguments_) {
|
|
@@ -95,6 +103,8 @@ const parseArguments = (arguments_) => {
|
|
|
95
103
|
flags.configPath = argument.slice("--config=".length);
|
|
96
104
|
} else if (argument.startsWith("--confirm-candidates=")) {
|
|
97
105
|
flags.confirmation = argument.slice("--confirm-candidates=".length);
|
|
106
|
+
} else if (argument.startsWith("--confirm-cleanup=")) {
|
|
107
|
+
flags.cleanupConfirmation = argument.slice("--confirm-cleanup=".length);
|
|
98
108
|
} else if (argument.startsWith("--records=")) {
|
|
99
109
|
flags.recordsPath = argument.slice("--records=".length);
|
|
100
110
|
} else if (argument.startsWith("--ledger=")) {
|
|
@@ -103,6 +113,10 @@ const parseArguments = (arguments_) => {
|
|
|
103
113
|
flags.assetsPath = argument.slice("--assets=".length);
|
|
104
114
|
} else if (argument.startsWith("--target=")) {
|
|
105
115
|
flags.target = argument.slice("--target=".length);
|
|
116
|
+
} else if (argument === "--include-runtime") {
|
|
117
|
+
flags.includeRuntime = true;
|
|
118
|
+
} else if (argument === "--include-images") {
|
|
119
|
+
flags.includeImages = true;
|
|
106
120
|
} else if (argument.startsWith("--")) {
|
|
107
121
|
throw new Error(`Unknown Rehearsal option: ${argument}.`);
|
|
108
122
|
} else positionals.push(argument);
|
|
@@ -131,7 +145,7 @@ const renderPlan = (plan, verbosity) => {
|
|
|
131
145
|
"Environment",
|
|
132
146
|
` ✓ ${plan.environment.kind}`,
|
|
133
147
|
` ✓ Hosted access ${plan.environment.hostedAccess}`,
|
|
134
|
-
` ✓
|
|
148
|
+
` ✓ Hosted target configuration ${plan.environment.outboundNetwork}`,
|
|
135
149
|
...(plan.environment.authenticationProviders.length
|
|
136
150
|
? [
|
|
137
151
|
` ✓ External identity providers ${plan.environment.authenticationProviders.join(", ")}`,
|
|
@@ -834,6 +848,13 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
834
848
|
command: "manage-runtime",
|
|
835
849
|
});
|
|
836
850
|
}
|
|
851
|
+
if (state.node.supported && state.config === "valid") {
|
|
852
|
+
options.push({
|
|
853
|
+
label: "Clean up disk space",
|
|
854
|
+
hint: "Preview old baselines, the runtime, and unused images",
|
|
855
|
+
command: "cleanup-guide",
|
|
856
|
+
});
|
|
857
|
+
}
|
|
837
858
|
if (lastGuidedDetails) {
|
|
838
859
|
options.push({
|
|
839
860
|
label: "Show details from the last action",
|
|
@@ -982,6 +1003,60 @@ const runGuidedHome = async ({ flags, planOptions }) => {
|
|
|
982
1003
|
}
|
|
983
1004
|
return runtimeAction.command;
|
|
984
1005
|
}
|
|
1006
|
+
if (selected.command === "cleanup-guide") {
|
|
1007
|
+
const cleanupScope = await promptForChoice({
|
|
1008
|
+
message: "What may Rehearsal include in the cleanup preview?",
|
|
1009
|
+
flags,
|
|
1010
|
+
options: [
|
|
1011
|
+
{
|
|
1012
|
+
label: "Old baselines only",
|
|
1013
|
+
hint: "Keep the configured number of recent generations",
|
|
1014
|
+
command: "baselines",
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
label: "Old baselines and this project's runtime",
|
|
1018
|
+
hint: "Also remove the disposable database and its volumes",
|
|
1019
|
+
command: "runtime",
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
label: "Old baselines and older unused Supabase images",
|
|
1023
|
+
hint: "Images may be shared and can be downloaded again",
|
|
1024
|
+
command: "images",
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
label: "Everything above",
|
|
1028
|
+
command: "all",
|
|
1029
|
+
},
|
|
1030
|
+
{ label: "Back", command: "exit" },
|
|
1031
|
+
],
|
|
1032
|
+
});
|
|
1033
|
+
if (!cleanupScope || cleanupScope.command === "exit") return "refresh";
|
|
1034
|
+
flags.includeRuntime = ["runtime", "all"].includes(cleanupScope.command);
|
|
1035
|
+
flags.includeImages = ["images", "all"].includes(cleanupScope.command);
|
|
1036
|
+
flags.cleanupPlan = await planRehearsalCleanup({
|
|
1037
|
+
...planOptions,
|
|
1038
|
+
includeRuntime: flags.includeRuntime,
|
|
1039
|
+
includeImages: flags.includeImages,
|
|
1040
|
+
});
|
|
1041
|
+
console.log(
|
|
1042
|
+
`\n${renderCleanup({ mode: "preview", plan: flags.cleanupPlan })}`,
|
|
1043
|
+
);
|
|
1044
|
+
const selectedCount =
|
|
1045
|
+
flags.cleanupPlan.baselines.removed.length +
|
|
1046
|
+
(flags.cleanupPlan.runtime.included && flags.cleanupPlan.runtime.detected
|
|
1047
|
+
? 1
|
|
1048
|
+
: 0) +
|
|
1049
|
+
flags.cleanupPlan.images.candidates.length;
|
|
1050
|
+
if (selectedCount === 0) return "refresh";
|
|
1051
|
+
const accepted = await promptForConfirmation(
|
|
1052
|
+
"Remove exactly the resources shown above?",
|
|
1053
|
+
flags,
|
|
1054
|
+
);
|
|
1055
|
+
if (!accepted) return "refresh";
|
|
1056
|
+
flags.write = true;
|
|
1057
|
+
flags.cleanupConfirmation = flags.cleanupPlan.digest;
|
|
1058
|
+
return "cleanup";
|
|
1059
|
+
}
|
|
985
1060
|
if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
|
|
986
1061
|
console.log(
|
|
987
1062
|
[
|
|
@@ -1209,10 +1284,12 @@ const emit = ({ command, data, flags, render, status = "success" }) => {
|
|
|
1209
1284
|
};
|
|
1210
1285
|
|
|
1211
1286
|
const runManager = ({ action, flags }) => {
|
|
1212
|
-
const args =
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1287
|
+
const args = buildRuntimeManagerArguments({
|
|
1288
|
+
managerPath,
|
|
1289
|
+
action,
|
|
1290
|
+
configPath: flags.configPath,
|
|
1291
|
+
confirmation: flags.confirmation,
|
|
1292
|
+
});
|
|
1216
1293
|
const environment = Object.fromEntries(
|
|
1217
1294
|
[
|
|
1218
1295
|
"CI",
|
|
@@ -1641,6 +1718,112 @@ const renderSupportReport = (report) => {
|
|
|
1641
1718
|
].join("\n");
|
|
1642
1719
|
};
|
|
1643
1720
|
|
|
1721
|
+
const formatBytes = (bytes) => {
|
|
1722
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
1723
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
1724
|
+
const unit = Math.min(
|
|
1725
|
+
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
1726
|
+
units.length - 1,
|
|
1727
|
+
);
|
|
1728
|
+
const value = bytes / 1024 ** unit;
|
|
1729
|
+
return `${value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unit]}`;
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
const renderCleanup = (result) => {
|
|
1733
|
+
const plan = result.plan;
|
|
1734
|
+
const imageBytes = plan.images.candidates.reduce(
|
|
1735
|
+
(total, image) => total + image.bytes,
|
|
1736
|
+
0,
|
|
1737
|
+
);
|
|
1738
|
+
const dockerImages = plan.docker.resources.find(
|
|
1739
|
+
(resource) => resource.Type === "Images",
|
|
1740
|
+
);
|
|
1741
|
+
const selectedCount =
|
|
1742
|
+
plan.baselines.removed.length +
|
|
1743
|
+
(plan.runtime.included && plan.runtime.detected ? 1 : 0) +
|
|
1744
|
+
plan.images.candidates.length;
|
|
1745
|
+
return [
|
|
1746
|
+
`REHEARSAL CLEANUP — ${result.mode.toUpperCase()}`,
|
|
1747
|
+
`Project: ${plan.project}`,
|
|
1748
|
+
...(dockerImages
|
|
1749
|
+
? [
|
|
1750
|
+
`Docker images: ${dockerImages.Size}; ${dockerImages.Reclaimable} reclaimable across Docker`,
|
|
1751
|
+
]
|
|
1752
|
+
: ["Docker usage: unavailable"]),
|
|
1753
|
+
...(plan.docker.disk
|
|
1754
|
+
? [
|
|
1755
|
+
`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)`,
|
|
1756
|
+
]
|
|
1757
|
+
: []),
|
|
1758
|
+
"",
|
|
1759
|
+
"Project-owned files",
|
|
1760
|
+
plan.baselines.available
|
|
1761
|
+
? ` ${plan.baselines.removed.length ? "→" : "✓"} ${formatCount(plan.baselines.removed.length, "old baseline generation")} selected; keeping ${plan.baselines.retained.length}`
|
|
1762
|
+
: " ○ No active baseline found",
|
|
1763
|
+
"",
|
|
1764
|
+
"Disposable runtime",
|
|
1765
|
+
!plan.runtime.detected
|
|
1766
|
+
? " ✓ No runtime found"
|
|
1767
|
+
: plan.runtime.included
|
|
1768
|
+
? " → Current project runtime selected"
|
|
1769
|
+
: " ○ Current project runtime preserved (add --include-runtime to select it)",
|
|
1770
|
+
"",
|
|
1771
|
+
"Shared Docker images",
|
|
1772
|
+
!plan.images.requested
|
|
1773
|
+
? " ○ Not inspected (add --include-images to inspect older unused Supabase images)"
|
|
1774
|
+
: !plan.images.inspected
|
|
1775
|
+
? " ! Docker is unavailable; images could not be inspected"
|
|
1776
|
+
: plan.images.candidates.length
|
|
1777
|
+
? ` → ${formatCount(plan.images.candidates.length, "older unused Supabase image")} selected; approximately ${formatBytes(imageBytes)}`
|
|
1778
|
+
: " ✓ No older unused Supabase images found",
|
|
1779
|
+
...(plan.images.candidates.length
|
|
1780
|
+
? plan.images.candidates.flatMap((image) =>
|
|
1781
|
+
image.tags.map((tag) => ` ${tag}`),
|
|
1782
|
+
)
|
|
1783
|
+
: []),
|
|
1784
|
+
"",
|
|
1785
|
+
...(result.mode === "preview"
|
|
1786
|
+
? selectedCount
|
|
1787
|
+
? [
|
|
1788
|
+
"Nothing has been removed.",
|
|
1789
|
+
`Cleanup set: ${plan.digest.slice(0, 12)}`,
|
|
1790
|
+
`Exact confirmation: --confirm-cleanup=${plan.digest}`,
|
|
1791
|
+
"Review this list, then rerun the same command with --write and the exact confirmation above.",
|
|
1792
|
+
]
|
|
1793
|
+
: [
|
|
1794
|
+
"Nothing has been removed, and nothing is currently selected.",
|
|
1795
|
+
"Use --include-runtime or --include-images only when you want those shared resources considered.",
|
|
1796
|
+
]
|
|
1797
|
+
: [
|
|
1798
|
+
`Removed ${formatCount(result.applied.baselines.removed.length, "old baseline generation")}.`,
|
|
1799
|
+
result.applied.runtimeRemoved
|
|
1800
|
+
? "Removed this project's disposable runtime and volumes."
|
|
1801
|
+
: "Preserved this project's disposable runtime.",
|
|
1802
|
+
`Removed ${formatCount(result.applied.imagesRemoved.length, "unused image tag")}.`,
|
|
1803
|
+
]),
|
|
1804
|
+
].join("\n");
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
const runCleanup = async ({ flags, planOptions }) => {
|
|
1808
|
+
const plan =
|
|
1809
|
+
flags.cleanupPlan ??
|
|
1810
|
+
(await planRehearsalCleanup({
|
|
1811
|
+
...planOptions,
|
|
1812
|
+
includeRuntime: flags.includeRuntime,
|
|
1813
|
+
includeImages: flags.includeImages,
|
|
1814
|
+
}));
|
|
1815
|
+
if (!flags.write) return { mode: "preview", plan };
|
|
1816
|
+
const applied = await applyRehearsalCleanup({
|
|
1817
|
+
plan,
|
|
1818
|
+
confirmation: flags.cleanupConfirmation,
|
|
1819
|
+
...planOptions,
|
|
1820
|
+
removeRuntime: async () => {
|
|
1821
|
+
runManager({ action: "discard", flags });
|
|
1822
|
+
},
|
|
1823
|
+
});
|
|
1824
|
+
return { mode: "written", plan, applied };
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1644
1827
|
const usage = () => `Usage: rehearsal <command> [options]
|
|
1645
1828
|
|
|
1646
1829
|
Commands:
|
|
@@ -1663,6 +1846,8 @@ Commands:
|
|
|
1663
1846
|
status Report the disposable local runtime state
|
|
1664
1847
|
stop Stop only this project's local runtime
|
|
1665
1848
|
discard Remove only this project's disposable runtime
|
|
1849
|
+
cleanup [--include-runtime] [--include-images] [--write] [--confirm-cleanup=]
|
|
1850
|
+
Preview or apply conservative disk cleanup
|
|
1666
1851
|
verify Verify the current local Rehearsal runtime
|
|
1667
1852
|
|
|
1668
1853
|
Options: --json --verbose --debug --plain --config=<path> --target=supabase|postgresql`;
|
|
@@ -1737,6 +1922,11 @@ const executeCommand = async ({ command, flags, planOptions, guided }) => {
|
|
|
1737
1922
|
emit({ command, data, flags, render: renderSupportReport });
|
|
1738
1923
|
return;
|
|
1739
1924
|
}
|
|
1925
|
+
if (command === "cleanup") {
|
|
1926
|
+
const data = await runCleanup({ flags, planOptions });
|
|
1927
|
+
emit({ command, data, flags, render: renderCleanup });
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1740
1930
|
if (command === "explain" || (command === "run" && flags.dryRun)) {
|
|
1741
1931
|
const data = await buildRehearsalPlan(planOptions);
|
|
1742
1932
|
emit({ command, data, flags, render: renderPlan });
|
|
@@ -1872,8 +2062,12 @@ const resetGuidedFlags = (flags) => {
|
|
|
1872
2062
|
flags.ledgerPath = undefined;
|
|
1873
2063
|
flags.assetsPath = undefined;
|
|
1874
2064
|
flags.target = undefined;
|
|
2065
|
+
flags.includeRuntime = false;
|
|
2066
|
+
flags.includeImages = false;
|
|
2067
|
+
flags.cleanupConfirmation = undefined;
|
|
1875
2068
|
flags.setupPlan = undefined;
|
|
1876
2069
|
flags.preparationPlan = undefined;
|
|
2070
|
+
flags.cleanupPlan = undefined;
|
|
1877
2071
|
};
|
|
1878
2072
|
|
|
1879
2073
|
const installGuidedExitShortcut = (flags) => {
|