@rehearsal-db/core 0.1.0-rc.2 → 0.1.0-rc.3
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 +32 -1
- package/README.md +3 -1
- package/dist/package.json +1 -1
- package/dist/scripts/runtime/manage_database.mjs +17 -8
- package/dist/src/cli/application_command_handlers.mjs +9 -4
- package/dist/src/cli/basic_command_handlers.mjs +1 -0
- package/dist/src/cli/inspection_command_handlers.mjs +27 -0
- package/dist/src/cli/project_commands.d.mts +9 -1
- package/dist/src/cli/project_commands.mjs +2 -2
- package/dist/src/cli/rehearsal.mjs +77 -65
- package/dist/src/cli/runtime_action_handlers.mjs +36 -9
- package/dist/src/cli/runtime_commands.d.mts +26 -7
- package/dist/src/cli/runtime_commands.mjs +94 -36
- package/dist/src/cli/source_commands.d.mts +12 -2
- package/dist/src/cli/source_commands.mjs +24 -15
- package/dist/src/project/configuration_contract.d.mts +10 -0
- package/dist/src/project/configuration_contract.mjs +4 -0
- package/dist/src/project/configuration_normalization.d.mts +4 -0
- package/dist/src/project/configuration_normalization.mjs +11 -1
- package/dist/src/project/configuration_rendering.mjs +14 -0
- package/dist/src/project/configuration_resolution.d.mts +4 -0
- package/dist/src/project/configuration_schema.d.mts +1 -0
- package/dist/src/project/configuration_schema.mjs +4 -0
- package/dist/src/runtime/lifecycle_engine.mjs +10 -2
- package/dist/src/runtime/persistence.d.mts +19 -0
- package/dist/src/runtime/persistence.mjs +40 -0
- package/dist/src/runtime/plan.d.mts +5 -0
- package/dist/src/runtime/plan.mjs +8 -0
- package/dist/src/runtime/status.d.mts +30 -0
- package/dist/src/runtime/status.mjs +141 -0
- package/dist/src/shared/cancellation.d.mts +18 -0
- package/dist/src/shared/cancellation.mjs +80 -0
- package/dist/src/shared/diagnostics.d.mts +1 -0
- package/dist/src/shared/diagnostics.mjs +1 -0
- package/dist/src/shared/operation_guard.d.mts +12 -0
- package/dist/src/shared/operation_guard.mjs +91 -2
- package/dist/src/shared/owned_process.d.mts +30 -0
- package/dist/src/shared/owned_process.mjs +117 -0
- package/dist/src/targets/postgresql.d.mts +0 -4
- package/dist/src/targets/postgresql.mjs +37 -12
- package/dist/src/targets/postgresql_process.d.mts +8 -0
- package/dist/src/targets/postgresql_process.mjs +34 -0
- package/dist/src/targets/postgresql_status.d.mts +21 -0
- package/dist/src/targets/postgresql_status.mjs +51 -0
- package/dist/src/targets/supabase.mjs +31 -16
- package/dist/src/targets/supabase_environment.mjs +2 -0
- package/dist/src/targets/supabase_process.d.mts +7 -0
- package/dist/src/targets/supabase_process.mjs +21 -0
- package/dist/src/targets/supabase_status.d.mts +12 -0
- package/dist/src/targets/supabase_status.mjs +56 -0
- package/dist/src/targets/target.d.mts +3 -1
- package/dist/src/targets/target.mjs +3 -1
- package/docs/commands.md +127 -2
- package/docs/configuration.md +12 -0
- package/docs/runtime-lifecycle.md +31 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## [0.1.0-rc.3] - 2026-10-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `status --json` now exposes bounded, target-scoped runtime state without requiring
|
|
13
|
+
editor clients to parse human-formatted process output.
|
|
14
|
+
- `activity --json` reports idle, busy, stale, invalid, and unavailable project-operation
|
|
15
|
+
state without exposing lock tokens, process arguments, credentials, or private paths.
|
|
16
|
+
- Supported mutating operations now have an explicit cancellation contract: owned child
|
|
17
|
+
groups receive one bounded shutdown request, rollback and lock cleanup complete before
|
|
18
|
+
exit `130`, and unrelated processes and Docker resources remain untouched.
|
|
19
|
+
- Root configuration can now choose `stop-after-run` for bounded automated checks and
|
|
20
|
+
`stop-on-application-exit` for bounded hands-on sessions. Both modes stop only the
|
|
21
|
+
configured Rehearsal targets while preserving database and Storage volumes, applied
|
|
22
|
+
migrations, and sandbox edits for the next `start` or `open`.
|
|
23
|
+
- Plans, JSON results, terminal receipts, generated configuration comments, and the
|
|
24
|
+
lifecycle guide now report the selected persistence mode and resulting runtime state.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Transient application and helper processes are cleaned up before an opted-in runtime
|
|
29
|
+
stop after success, proof failure, readiness timeout, or supported interruption. The
|
|
30
|
+
compatibility default remains `keep-until-stop` for both `run` and `open`.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Read-only runtime inspection now works when a VS Code extension launches the CLI
|
|
35
|
+
through Electron's executable in Node mode. That mode is preserved only for
|
|
36
|
+
Rehearsal's nested runtime manager and is not exposed to project-owned commands.
|
|
37
|
+
|
|
8
38
|
## [0.1.0-rc.2] - 2026-10-09
|
|
9
39
|
|
|
10
40
|
### Changed
|
|
@@ -575,7 +605,8 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
|
|
|
575
605
|
publication uses short-lived trusted OIDC, and every release tag must already exist on
|
|
576
606
|
protected `main`.
|
|
577
607
|
|
|
578
|
-
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.
|
|
608
|
+
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.3...HEAD
|
|
609
|
+
[0.1.0-rc.3]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.2...v0.1.0-rc.3
|
|
579
610
|
[0.1.0-rc.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.1...v0.1.0-rc.2
|
|
580
611
|
[0.1.0-rc.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.18...v0.1.0-rc.1
|
|
581
612
|
[0.1.0-beta.18]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.17...v0.1.0-beta.18
|
package/README.md
CHANGED
|
@@ -116,7 +116,9 @@ source-owner approval; see [Standalone workflow](docs/standalone-workflow.md).
|
|
|
116
116
|
|
|
117
117
|
`npx rehearsal open` starts and verifies the existing local runtime, keeps the configured
|
|
118
118
|
app available until you press `Ctrl+C`, and preserves database and Storage changes for
|
|
119
|
-
the next session.
|
|
119
|
+
the next session. Set `lifecycle.open` to `stop-on-application-exit` when you also want
|
|
120
|
+
its database targets stopped afterward; `lifecycle.run: "stop-after-run"` gives automated
|
|
121
|
+
checks the same bounded, non-destructive behavior.
|
|
120
122
|
|
|
121
123
|
For an approved production-shaped copy, the optional sequence is `source plan`, `source
|
|
122
124
|
apply`, `refresh`, and `source retire`. `refresh` verifies the replacement before it
|
package/dist/package.json
CHANGED
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
import { loadRehearsalConfig } from "../../src/project/configuration.mjs";
|
|
7
7
|
import { parseRuntimeInvocation } from "../../src/targets/target.mjs";
|
|
8
8
|
import { runRuntimeTarget } from "../../src/targets/driver.mjs";
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
9
|
+
import { runWithOperationCancellation } from "../../src/shared/cancellation.mjs";
|
|
10
|
+
const run = async () => {
|
|
11
|
+
const invocation = parseRuntimeInvocation();
|
|
12
|
+
const { config } = await loadRehearsalConfig({
|
|
13
|
+
projectRoot: process.cwd(),
|
|
14
|
+
...(invocation.configPath === undefined
|
|
15
|
+
? {}
|
|
16
|
+
: { configPath: invocation.configPath }),
|
|
17
|
+
});
|
|
18
|
+
await runRuntimeTarget(config.runtime.target);
|
|
19
|
+
};
|
|
20
|
+
if (process.env.REHEARSAL_CANCELLABLE_OPERATION === "1") {
|
|
21
|
+
await runWithOperationCancellation({ task: run });
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
await run();
|
|
25
|
+
}
|
|
@@ -14,10 +14,10 @@ export const createApplicationCommandHandlers = ({ openRuntimeApplication, }) =>
|
|
|
14
14
|
onReady: (ready) => {
|
|
15
15
|
const lines = [
|
|
16
16
|
`Application: ${ready.url}`,
|
|
17
|
-
"The
|
|
17
|
+
"The configured lifecycle policy will preserve your local database and Storage changes.",
|
|
18
18
|
guided
|
|
19
19
|
? "Press Ctrl+C to close the app and return here, or Ctrl+Z to exit Rehearsal."
|
|
20
|
-
: "Press Ctrl+C or Ctrl+Z to close the app. Rehearsal will
|
|
20
|
+
: "Press Ctrl+C or Ctrl+Z to close the app. Rehearsal will then apply the configured lifecycle policy.",
|
|
21
21
|
];
|
|
22
22
|
if (useStyledPrompts(flags)) {
|
|
23
23
|
prompts.note(lines.join("\n"), "SANDBOX READY");
|
|
@@ -35,6 +35,7 @@ export const createApplicationCommandHandlers = ({ openRuntimeApplication, }) =>
|
|
|
35
35
|
data.started.output,
|
|
36
36
|
data.verified.output,
|
|
37
37
|
...data.preparations.map((preparation) => preparation.output),
|
|
38
|
+
data.lifecycle.stop?.output,
|
|
38
39
|
]
|
|
39
40
|
.filter(Boolean)
|
|
40
41
|
.join("\n");
|
|
@@ -44,8 +45,12 @@ export const createApplicationCommandHandlers = ({ openRuntimeApplication, }) =>
|
|
|
44
45
|
render: (result) => [
|
|
45
46
|
"SANDBOX APP CLOSED",
|
|
46
47
|
`Application stopped after ${result.application.stoppedBy}.`,
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
result.lifecycle.runtimeState === "stopped"
|
|
49
|
+
? `Lifecycle ${result.lifecycle.mode} stopped the database runtime; its local database and Storage changes were preserved.`
|
|
50
|
+
: `Lifecycle ${result.lifecycle.mode} left the database runtime running; its local database and Storage changes were preserved.`,
|
|
51
|
+
result.lifecycle.runtimeState === "stopped"
|
|
52
|
+
? "Run rehearsal start or rehearsal open to resume."
|
|
53
|
+
: "Run rehearsal open to return, rehearsal verify to check it, or rehearsal stop to stop the databases.",
|
|
49
54
|
].join("\n"),
|
|
50
55
|
});
|
|
51
56
|
}),
|
|
@@ -21,6 +21,7 @@ Commands:
|
|
|
21
21
|
identity connect [--identity=] Keep the app open, detect sign-in, and connect once
|
|
22
22
|
doctor Check whether Rehearsal is safe and ready
|
|
23
23
|
support Print a safe, copy-ready support report
|
|
24
|
+
activity Report idle, busy, stale, or invalid operation state
|
|
24
25
|
explain Show the immutable execution plan
|
|
25
26
|
run --dry-run Alias the exact explain plan without mutations
|
|
26
27
|
run --confirm-candidates= Execute reset, migration, and verification locally
|
|
@@ -4,7 +4,34 @@ import { inspectRehearsalBaseline, inspectRehearsalMigrations, } from "../runtim
|
|
|
4
4
|
import { buildRuntimeStackPlan, runRuntimeStackDoctor, topologyCandidateSummary, } from "./runtime_commands.mjs";
|
|
5
5
|
import { renderBaseline, renderCandidates, renderDoctor, renderMigrations, renderPlan, renderSupportReport, } from "./renderers.mjs";
|
|
6
6
|
import { exactCommands, renderedCommand } from "./command_contract.mjs";
|
|
7
|
+
import { inspectProjectOperationState } from "../shared/operation_guard.mjs";
|
|
7
8
|
export const createInspectionCommandHandlers = () => [
|
|
9
|
+
exactCommands("activity", ["activity"], async ({ planOptions }) => {
|
|
10
|
+
const data = await inspectProjectOperationState({
|
|
11
|
+
projectRoot: planOptions.projectRoot ?? process.cwd(),
|
|
12
|
+
});
|
|
13
|
+
return renderedCommand({
|
|
14
|
+
data,
|
|
15
|
+
render: (result) => {
|
|
16
|
+
if (result.state === "idle")
|
|
17
|
+
return "Rehearsal activity: idle.";
|
|
18
|
+
if (result.state === "invalid") {
|
|
19
|
+
return "Rehearsal activity: invalid lock state. No lock was changed; finish or diagnose the owning operation before retrying a mutation.";
|
|
20
|
+
}
|
|
21
|
+
const label = result.state === "busy" ? "busy" : "stale";
|
|
22
|
+
return [
|
|
23
|
+
`Rehearsal activity: ${label}.`,
|
|
24
|
+
`Operation: ${result.operation.kind}`,
|
|
25
|
+
`Started: ${result.operation.startedAt}`,
|
|
26
|
+
result.operation.rehearsalVersion
|
|
27
|
+
? `Rehearsal: ${result.operation.rehearsalVersion}`
|
|
28
|
+
: null,
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join("\n");
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}),
|
|
8
35
|
exactCommands("doctor", ["doctor"], async ({ guided, flags, planOptions }) => {
|
|
9
36
|
const data = await runRuntimeStackDoctor(planOptions);
|
|
10
37
|
return renderedCommand({
|
|
@@ -17,7 +17,7 @@ type RunManager = (input: {
|
|
|
17
17
|
flags: RehearsalCliFlags;
|
|
18
18
|
configPath?: string;
|
|
19
19
|
targetName?: string;
|
|
20
|
-
}) => unknown
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
21
|
interface ProjectCommandInput {
|
|
22
22
|
readonly flags: RehearsalCliFlags;
|
|
23
23
|
readonly planOptions: RehearsalConfigPathOptions;
|
|
@@ -67,6 +67,10 @@ export declare const createProjectCommands: ({ projectRoot, runManager, }: {
|
|
|
67
67
|
containerRuntime: Readonly<{
|
|
68
68
|
autoStartColima: boolean;
|
|
69
69
|
}>;
|
|
70
|
+
lifecycle: Readonly<{
|
|
71
|
+
run: "keep-until-stop" | "stop-after-run";
|
|
72
|
+
open: "keep-until-stop" | "stop-on-application-exit";
|
|
73
|
+
}>;
|
|
70
74
|
cleanup: Readonly<{
|
|
71
75
|
retainBaselineGenerations: number;
|
|
72
76
|
}>;
|
|
@@ -191,6 +195,10 @@ export declare const createProjectCommands: ({ projectRoot, runManager, }: {
|
|
|
191
195
|
containerRuntime: Readonly<{
|
|
192
196
|
autoStartColima: boolean;
|
|
193
197
|
}>;
|
|
198
|
+
lifecycle: Readonly<{
|
|
199
|
+
run: "keep-until-stop" | "stop-after-run";
|
|
200
|
+
open: "keep-until-stop" | "stop-on-application-exit";
|
|
201
|
+
}>;
|
|
194
202
|
cleanup: Readonly<{
|
|
195
203
|
retainBaselineGenerations: number;
|
|
196
204
|
}>;
|
|
@@ -128,7 +128,7 @@ export const createProjectCommands = ({ projectRoot, runManager, }) => {
|
|
|
128
128
|
projectRoot,
|
|
129
129
|
configPath: target.configPath,
|
|
130
130
|
removeRuntime: async () => {
|
|
131
|
-
runManager({
|
|
131
|
+
await runManager({
|
|
132
132
|
action: "discard",
|
|
133
133
|
flags,
|
|
134
134
|
configPath: target.configPath,
|
|
@@ -151,7 +151,7 @@ export const createProjectCommands = ({ projectRoot, runManager, }) => {
|
|
|
151
151
|
: { confirmation: flags.cleanupConfirmation }),
|
|
152
152
|
...planOptions,
|
|
153
153
|
removeRuntime: async () => {
|
|
154
|
-
runManager({ action: "discard", flags });
|
|
154
|
+
await runManager({ action: "discard", flags });
|
|
155
155
|
},
|
|
156
156
|
});
|
|
157
157
|
return { mode: "written", plan, applied };
|
|
@@ -20,6 +20,7 @@ import { createProjectCommands } from "./project_commands.mjs";
|
|
|
20
20
|
import { buildRuntimeStackPlan, createRuntimeCommands, runRuntimeStackDoctor, topologyCandidateSummary, topologyCommandEnvironment, } from "./runtime_commands.mjs";
|
|
21
21
|
import { buildRefreshWorkflowPlan, createRefreshWorkflow, } from "./source_commands.mjs";
|
|
22
22
|
import { installGuidedExitShortcut, useStyledPrompts } from "./terminal.mjs";
|
|
23
|
+
import { runWithOperationCancellation, supportsOperationCancellation, } from "../shared/cancellation.mjs";
|
|
23
24
|
const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
24
25
|
const projectRoot = process.cwd();
|
|
25
26
|
const managerPath = join(packageRoot, "dist/scripts/runtime/manage_database.mjs");
|
|
@@ -89,83 +90,94 @@ const main = async () => {
|
|
|
89
90
|
const guided = command === "guide" || wantsAutomaticGuide;
|
|
90
91
|
const mutating = commandMutatesProject({ command, flags, guided });
|
|
91
92
|
const operation = mutating
|
|
92
|
-
? await acquireProjectOperation({
|
|
93
|
+
? await acquireProjectOperation({
|
|
94
|
+
projectRoot,
|
|
95
|
+
command: guided ? "guide" : command,
|
|
96
|
+
})
|
|
93
97
|
: null;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
if (!guided) {
|
|
101
|
-
await executeCommand({ command, flags, planOptions, guided: false });
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
105
|
-
throw new Error("The guided home screen requires an interactive terminal. Run rehearsal --help to list scriptable commands.");
|
|
106
|
-
}
|
|
107
|
-
flags.guided = true;
|
|
108
|
-
const installExitShortcut = () => installGuidedExitShortcut(flags, {
|
|
109
|
-
beforeExit: () => operation?.releaseSync(),
|
|
110
|
-
});
|
|
111
|
-
let removeGuidedExitShortcut = installExitShortcut();
|
|
112
|
-
if (useStyledPrompts(flags)) {
|
|
113
|
-
prompts.intro("REHEARSAL · Safe local migration testing");
|
|
98
|
+
const run = async () => {
|
|
99
|
+
if (operation) {
|
|
100
|
+
activePackageFingerprint = createInstalledPackageFingerprint({
|
|
101
|
+
packageRoot,
|
|
102
|
+
});
|
|
114
103
|
}
|
|
115
104
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
if (!guided) {
|
|
106
|
+
await executeCommand({ command, flags, planOptions, guided: false });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
110
|
+
throw new Error("The guided home screen requires an interactive terminal. Run rehearsal --help to list scriptable commands.");
|
|
111
|
+
}
|
|
112
|
+
flags.guided = true;
|
|
113
|
+
const installExitShortcut = () => installGuidedExitShortcut(flags, {
|
|
114
|
+
beforeExit: () => operation?.releaseSync(),
|
|
115
|
+
});
|
|
116
|
+
let removeGuidedExitShortcut = installExitShortcut();
|
|
117
|
+
if (useStyledPrompts(flags)) {
|
|
118
|
+
prompts.intro("REHEARSAL · Safe local migration testing");
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
while (true) {
|
|
122
|
+
resetGuidedFlags(flags);
|
|
123
|
+
resetCommandSessionState(state);
|
|
124
|
+
const selected = await runGuidedHome({ flags, planOptions });
|
|
125
|
+
if (!selected)
|
|
126
|
+
break;
|
|
127
|
+
if (selected === "home")
|
|
128
|
+
continue;
|
|
129
|
+
const opensApplication = selected === "open";
|
|
130
|
+
if (opensApplication) {
|
|
131
|
+
removeGuidedExitShortcut?.();
|
|
132
|
+
removeGuidedExitShortcut = null;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await executeCommand({
|
|
136
|
+
command: selected,
|
|
137
|
+
flags,
|
|
138
|
+
planOptions,
|
|
139
|
+
guided: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (opensApplication) {
|
|
144
|
+
removeGuidedExitShortcut = installExitShortcut();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (flags.exitGuidedSession)
|
|
148
|
+
break;
|
|
128
149
|
}
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
removeGuidedExitShortcut?.();
|
|
153
|
+
}
|
|
154
|
+
if (useStyledPrompts(flags)) {
|
|
155
|
+
prompts.outro("See you at the next rehearsal.");
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log("See you at the next rehearsal.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
if (operation) {
|
|
129
163
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
planOptions,
|
|
134
|
-
guided: true,
|
|
164
|
+
assertInstalledPackageFingerprint({
|
|
165
|
+
packageRoot,
|
|
166
|
+
expected: activePackageFingerprint,
|
|
135
167
|
});
|
|
136
168
|
}
|
|
137
169
|
finally {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
170
|
+
activePackageFingerprint = null;
|
|
171
|
+
await operation.release();
|
|
141
172
|
}
|
|
142
|
-
if (flags.exitGuidedSession)
|
|
143
|
-
break;
|
|
144
173
|
}
|
|
145
174
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (useStyledPrompts(flags)) {
|
|
150
|
-
prompts.outro("See you at the next rehearsal.");
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
console.log("See you at the next rehearsal.");
|
|
154
|
-
}
|
|
175
|
+
};
|
|
176
|
+
if (operation && !guided && supportsOperationCancellation(command)) {
|
|
177
|
+
await runWithOperationCancellation({ task: run });
|
|
155
178
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
assertInstalledPackageFingerprint({
|
|
160
|
-
packageRoot,
|
|
161
|
-
expected: activePackageFingerprint,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
activePackageFingerprint = null;
|
|
166
|
-
await operation.release();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
179
|
+
else {
|
|
180
|
+
await run();
|
|
169
181
|
}
|
|
170
182
|
};
|
|
171
183
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Database runtime action handlers shared by guided and scripted CLI flows. */
|
|
2
2
|
import * as prompts from "@clack/prompts";
|
|
3
3
|
import { buildApplicationEnvironment, runHttpProofs, startApplicationSession, } from "../application/session.mjs";
|
|
4
|
+
import { applyRuntimePersistence } from "../runtime/persistence.mjs";
|
|
4
5
|
import { exactCommands, renderedCommand, silentCommand, } from "./command_contract.mjs";
|
|
5
6
|
import { topologyCommandEnvironment, topologyEnvironmentFiles, } from "./runtime_commands.mjs";
|
|
6
7
|
import { formatDuration, isHumanTerminal, terminalStyle, useStyledPrompts, } from "./terminal.mjs";
|
|
@@ -42,17 +43,20 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
42
43
|
});
|
|
43
44
|
const { runtime, topology } = stack;
|
|
44
45
|
const stackEnvironment = topologyCommandEnvironment(topology);
|
|
45
|
-
|
|
46
|
-
? prepareDependentTargets({
|
|
47
|
-
topology,
|
|
48
|
-
environment: stackEnvironment,
|
|
49
|
-
})
|
|
50
|
-
: [];
|
|
46
|
+
let preparations = [];
|
|
51
47
|
let applicationProof;
|
|
52
48
|
let dependentProofs = [];
|
|
53
49
|
let httpProofResults = [];
|
|
54
50
|
let applicationReadiness;
|
|
55
|
-
|
|
51
|
+
const completeFollowups = async () => {
|
|
52
|
+
preparations = ["run", "reset", "migrate"].includes(command)
|
|
53
|
+
? await prepareDependentTargets({
|
|
54
|
+
topology,
|
|
55
|
+
environment: stackEnvironment,
|
|
56
|
+
})
|
|
57
|
+
: [];
|
|
58
|
+
if (command !== "run")
|
|
59
|
+
return;
|
|
56
60
|
const applicationConfig = topology.primary.config.application;
|
|
57
61
|
const mappedApplicationEnvironment = Object.keys(applicationConfig.environmentVariables).length
|
|
58
62
|
? await buildApplicationEnvironment({
|
|
@@ -111,7 +115,19 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
111
115
|
else if (isHumanTerminal(flags)) {
|
|
112
116
|
console.log(`${terminalStyle(flags, "32", "✓")} Project proofs passed.`);
|
|
113
117
|
}
|
|
114
|
-
}
|
|
118
|
+
};
|
|
119
|
+
const persistence = command === "run"
|
|
120
|
+
? await applyRuntimePersistence({
|
|
121
|
+
mode: topology.primary.config.lifecycle.run,
|
|
122
|
+
operation: completeFollowups,
|
|
123
|
+
stop: async () => (await runRuntimeStack({
|
|
124
|
+
command: "stop",
|
|
125
|
+
flags,
|
|
126
|
+
planOptions,
|
|
127
|
+
session: state,
|
|
128
|
+
})).runtime,
|
|
129
|
+
})
|
|
130
|
+
: (await completeFollowups(), null);
|
|
115
131
|
const data = command === "run"
|
|
116
132
|
? {
|
|
117
133
|
runtime,
|
|
@@ -120,6 +136,7 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
120
136
|
applicationProof,
|
|
121
137
|
applicationReadiness,
|
|
122
138
|
httpProofResults,
|
|
139
|
+
lifecycle: persistence.lifecycle,
|
|
123
140
|
}
|
|
124
141
|
: preparations.length
|
|
125
142
|
? { ...runtime, preparations }
|
|
@@ -130,6 +147,7 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
130
147
|
...preparations.map((preparation) => preparation.output),
|
|
131
148
|
...dependentProofs.map((proof) => proof.output),
|
|
132
149
|
applicationProof?.output,
|
|
150
|
+
persistence?.lifecycle.stop?.output,
|
|
133
151
|
]
|
|
134
152
|
.filter(Boolean)
|
|
135
153
|
.join("\n");
|
|
@@ -145,8 +163,11 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
145
163
|
? value.applicationReadiness
|
|
146
164
|
: undefined;
|
|
147
165
|
const renderedHttpProofResults = "httpProofResults" in value ? value.httpProofResults : [];
|
|
166
|
+
const renderedLifecycle = "lifecycle" in value ? value.lifecycle : undefined;
|
|
148
167
|
const nextAction = {
|
|
149
|
-
run:
|
|
168
|
+
run: renderedLifecycle?.runtimeState === "stopped"
|
|
169
|
+
? "The runtime was stopped with its database and Storage changes preserved. Run rehearsal start or rehearsal open to resume."
|
|
170
|
+
: "Next: run rehearsal open for hands-on testing, then rehearsal verify.",
|
|
150
171
|
start: "Next: run rehearsal open for hands-on testing or rehearsal status.",
|
|
151
172
|
migrate: "Next: run rehearsal verify to prove the current runtime.",
|
|
152
173
|
reset: "Next: run rehearsal open or rehearsal verify.",
|
|
@@ -171,6 +192,9 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
171
192
|
? `✓ Application became ready at ${renderedApplicationReadiness.url}`
|
|
172
193
|
: null,
|
|
173
194
|
...renderedHttpProofResults.map((proof) => `✓ ${proof.kind} HTTP proof passed: ${proof.name}`),
|
|
195
|
+
renderedLifecycle
|
|
196
|
+
? `✓ Lifecycle ${renderedLifecycle.mode}: runtime ${renderedLifecycle.runtimeState}; data preserved`
|
|
197
|
+
: null,
|
|
174
198
|
"",
|
|
175
199
|
"Technical output is available from Show details in the guide.",
|
|
176
200
|
]
|
|
@@ -189,6 +213,9 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
189
213
|
? `Application readiness passed: ${renderedApplicationReadiness.url}`
|
|
190
214
|
: null,
|
|
191
215
|
...renderedHttpProofResults.map((proof) => `${proof.kind} HTTP proof passed: ${proof.name}`),
|
|
216
|
+
renderedLifecycle
|
|
217
|
+
? `Lifecycle ${renderedLifecycle.mode}: runtime ${renderedLifecycle.runtimeState}; data preserved.`
|
|
218
|
+
: null,
|
|
192
219
|
nextAction ? "" : null,
|
|
193
220
|
nextAction,
|
|
194
221
|
]
|
|
@@ -8,6 +8,7 @@ import { buildRehearsalPlan } from "../runtime/plan.mjs";
|
|
|
8
8
|
import { loadRuntimeTopology } from "../runtime/topology.mjs";
|
|
9
9
|
import type { RuntimeTopologyTarget } from "../runtime/topology.mjs";
|
|
10
10
|
import type { InstalledPackageFingerprint } from "../shared/operation_guard.mjs";
|
|
11
|
+
import type { RuntimeStatusSnapshot } from "../runtime/status.mjs";
|
|
11
12
|
import type { RehearsalCliFlags } from "./arguments.mjs";
|
|
12
13
|
import type { RehearsalErrorCategory } from "../shared/diagnostics.mjs";
|
|
13
14
|
import type { CliSessionState } from "./command_contract.mjs";
|
|
@@ -18,6 +19,7 @@ interface ManagerReceipt {
|
|
|
18
19
|
readonly target: string;
|
|
19
20
|
readonly durationMs: number;
|
|
20
21
|
readonly output: string;
|
|
22
|
+
readonly runtimeStatus?: RuntimeStatusSnapshot;
|
|
21
23
|
}
|
|
22
24
|
interface RuntimeStackResult {
|
|
23
25
|
readonly topology: RuntimeTopology;
|
|
@@ -73,6 +75,11 @@ export declare const buildRuntimeStackPlan: (planOptions: RehearsalConfigPathOpt
|
|
|
73
75
|
sha256: string;
|
|
74
76
|
}[];
|
|
75
77
|
};
|
|
78
|
+
lifecycle: {
|
|
79
|
+
run: "keep-until-stop" | "stop-after-run";
|
|
80
|
+
open: "keep-until-stop" | "stop-on-application-exit";
|
|
81
|
+
dataOnStop: string;
|
|
82
|
+
};
|
|
76
83
|
execution: string[];
|
|
77
84
|
guarantee: string;
|
|
78
85
|
paths: {
|
|
@@ -90,6 +97,11 @@ export declare const buildRuntimeStackPlan: (planOptions: RehearsalConfigPathOpt
|
|
|
90
97
|
candidateCount: number;
|
|
91
98
|
candidateSha256: string;
|
|
92
99
|
};
|
|
100
|
+
lifecycle: {
|
|
101
|
+
run: "keep-until-stop" | "stop-after-run";
|
|
102
|
+
open: "keep-until-stop" | "stop-on-application-exit";
|
|
103
|
+
dataOnStop: string;
|
|
104
|
+
};
|
|
93
105
|
execution: string[];
|
|
94
106
|
guarantee: string;
|
|
95
107
|
}>;
|
|
@@ -209,9 +221,9 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
209
221
|
targets: readonly ManagerReceipt[];
|
|
210
222
|
}>;
|
|
211
223
|
preparations: {
|
|
224
|
+
target: string;
|
|
212
225
|
command: string;
|
|
213
226
|
output: string;
|
|
214
|
-
target: string;
|
|
215
227
|
}[];
|
|
216
228
|
application: {
|
|
217
229
|
command: string;
|
|
@@ -222,11 +234,18 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
222
234
|
stoppedBy: string;
|
|
223
235
|
diagnostics: import("../application/session.mjs").ApplicationDiagnostics;
|
|
224
236
|
};
|
|
237
|
+
lifecycle: import("../runtime/persistence.mjs").RuntimePersistenceOutcome<ManagerReceipt | Readonly<{
|
|
238
|
+
action: RuntimeAction;
|
|
239
|
+
durationMs: number;
|
|
240
|
+
output: string;
|
|
241
|
+
targets: readonly ManagerReceipt[];
|
|
242
|
+
}>>;
|
|
225
243
|
}>;
|
|
226
|
-
startRuntimeApplication: ({ flags, planOptions, session: commandSession, }: {
|
|
244
|
+
startRuntimeApplication: ({ flags, planOptions, session: commandSession, onRuntimeStarted, }: {
|
|
227
245
|
flags: RehearsalCliFlags;
|
|
228
246
|
planOptions: RehearsalConfigPathOptions;
|
|
229
247
|
session: CliSessionState;
|
|
248
|
+
onRuntimeStarted?: () => void;
|
|
230
249
|
}) => Promise<{
|
|
231
250
|
started: ManagerReceipt | Readonly<{
|
|
232
251
|
action: RuntimeAction;
|
|
@@ -241,9 +260,9 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
241
260
|
targets: readonly ManagerReceipt[];
|
|
242
261
|
}>;
|
|
243
262
|
preparations: {
|
|
263
|
+
target: string;
|
|
244
264
|
command: string;
|
|
245
265
|
output: string;
|
|
246
|
-
target: string;
|
|
247
266
|
}[];
|
|
248
267
|
command: string;
|
|
249
268
|
session: import("../application/session.mjs").ApplicationSession;
|
|
@@ -257,11 +276,11 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
257
276
|
prepareDependentTargets: ({ topology, environment, }: {
|
|
258
277
|
topology: RuntimeTopology;
|
|
259
278
|
environment: NodeJS.ProcessEnv;
|
|
260
|
-
}) => {
|
|
279
|
+
}) => Promise<{
|
|
280
|
+
target: string;
|
|
261
281
|
command: string;
|
|
262
282
|
output: string;
|
|
263
|
-
|
|
264
|
-
}[];
|
|
283
|
+
}[]>;
|
|
265
284
|
proveRuntimeStack: ({ topology, planOptions, environment, }: {
|
|
266
285
|
topology: RuntimeTopology;
|
|
267
286
|
planOptions: RehearsalConfigPathOptions;
|
|
@@ -283,7 +302,7 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
283
302
|
configPath?: string;
|
|
284
303
|
confirmation?: string;
|
|
285
304
|
targetName?: string;
|
|
286
|
-
}) => ManagerReceipt
|
|
305
|
+
}) => Promise<ManagerReceipt>;
|
|
287
306
|
runRuntimeStack: ({ command, flags, planOptions, session, }: {
|
|
288
307
|
command: RuntimeAction;
|
|
289
308
|
flags: RehearsalCliFlags;
|