@rehearsal-db/core 1.0.0 → 1.0.2
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 +46 -1
- package/COMPATIBILITY.md +43 -0
- package/README.md +31 -12
- package/dist/package.json +2 -2
- package/dist/scripts/install/create_root_config.mjs +9 -2
- package/dist/src/cli/failure_output.d.mts +3 -0
- package/dist/src/cli/failure_output.mjs +48 -0
- package/dist/src/cli/rehearsal.mjs +15 -4
- package/dist/src/cli/runtime_action_handlers.mjs +11 -4
- package/dist/src/cli/runtime_commands.mjs +23 -24
- package/dist/src/cli/runtime_failure_summary.d.mts +1 -0
- package/dist/src/cli/runtime_failure_summary.mjs +30 -4
- package/dist/src/cli/runtime_planning.mjs +6 -0
- package/dist/src/project/install_scaffold.mjs +7 -0
- package/dist/src/project/project_discovery_metadata.mjs +9 -11
- package/dist/src/runtime/migration_failure.d.mts +28 -0
- package/dist/src/runtime/migration_failure.mjs +197 -0
- package/dist/src/runtime/migration_failure_source.d.mts +16 -0
- package/dist/src/runtime/migration_failure_source.mjs +68 -0
- package/dist/src/runtime/plan.mjs +26 -15
- package/dist/src/shared/diagnostics.mjs +5 -0
- package/dist/src/shared/host_platform.d.mts +16 -0
- package/dist/src/shared/host_platform.mjs +109 -0
- package/dist/src/shared/operation_guard.d.mts +4 -1
- package/dist/src/shared/operation_guard.mjs +18 -13
- package/dist/src/shared/operation_progress.mjs +35 -1
- package/dist/src/shared/operation_progress_storage.d.mts +27 -6
- package/dist/src/shared/operation_progress_storage.mjs +65 -9
- package/dist/src/shared/process_environment.d.mts +2 -1
- package/dist/src/shared/process_environment.mjs +47 -5
- package/dist/src/targets/port_start_failure.d.mts +15 -0
- package/dist/src/targets/port_start_failure.mjs +46 -0
- package/dist/src/targets/postgresql.mjs +8 -7
- package/dist/src/targets/postgresql_process.d.mts +8 -1
- package/dist/src/targets/postgresql_process.mjs +46 -2
- package/dist/src/targets/supabase_process.mjs +2 -1
- package/dist/src/targets/supabase_start_failure.mjs +5 -1
- package/docs/architecture.md +32 -2
- package/docs/commands.md +35 -0
- package/docs/getting-started.md +48 -0
- package/docs/index.md +15 -9
- package/docs/releasing.md +28 -24
- package/docs/roadmap.md +8 -3
- package/docs/stability.md +8 -4
- package/docs/troubleshooting.md +54 -2
- package/docs/tutorial.md +93 -8
- package/docs/vscode.md +220 -37
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,49 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.0.2] - 2026-10-20
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Explain recognized PostgreSQL/Supabase migration failures with bounded native
|
|
13
|
+
SQLSTATE evidence and safe syntax, schema, constraint, permission, connectivity
|
|
14
|
+
and timeout guidance. Unsupported or ambiguous evidence remains unknown; raw
|
|
15
|
+
SQL, provider details, credentials and private rows are not exposed.
|
|
16
|
+
- Preserve the original diagnosis across cleanup and bounded operation history so
|
|
17
|
+
the CLI and editor can show the same failure after refresh. Failed operations
|
|
18
|
+
never claim verification or unconditional rollback success.
|
|
19
|
+
- Optionally identify the exact approved PostgreSQL input file by hash, without
|
|
20
|
+
inventing a failing SQL origin, line or column. Reject invalid UTF-8 before
|
|
21
|
+
execution rather than submitting replacement-decoded bytes.
|
|
22
|
+
- Point recognized bind failures at the configured local ports, including safe
|
|
23
|
+
Windows/WSL exclusion-inspection guidance. Do not infer a reservation from every
|
|
24
|
+
denial or suggest terminating unknown listeners.
|
|
25
|
+
- Keep database-only command guidance separate from optional application launch.
|
|
26
|
+
|
|
27
|
+
### Documentation and distribution
|
|
28
|
+
|
|
29
|
+
- Add a main-tutorial existing-row constraint failure, reviewed backfill/fix and
|
|
30
|
+
verification sequence, plus a same-project VS Code handoff and safe local-copy
|
|
31
|
+
recovery for unsupported external npm links.
|
|
32
|
+
- Pair with Starter 0.1.22 for actionable persistent failures, safe exact-byte
|
|
33
|
+
source opening, distinct command/database states and truthful recorded proof.
|
|
34
|
+
- Refresh the separate macOS, Linux and Windows/WSL 2 setup bundles to exact core
|
|
35
|
+
1.0.2. Native Windows database execution remains unsupported.
|
|
36
|
+
|
|
37
|
+
## [1.0.1] - 2026-10-19
|
|
38
|
+
|
|
39
|
+
### Platform compatibility
|
|
40
|
+
|
|
41
|
+
- Keep macOS Intel/Apple Silicon and Linux execution distinct from Windows desktop
|
|
42
|
+
access through WSL 2. Require the WSL project's Linux filesystem; document the
|
|
43
|
+
preview's actual component and runtime limits rather than claiming every OS works.
|
|
44
|
+
- Verify installed package version/discovery on real desktop OS runners and exercise
|
|
45
|
+
PostgreSQL/dependent targets in an owned Windows 2025 WSL2 Ubuntu distribution.
|
|
46
|
+
Provision checksum-verified official Node and signed Docker Engine packages without
|
|
47
|
+
Docker Desktop, a daemon TCP endpoint or new branch-protection requirements.
|
|
48
|
+
- Document native Windows runtime refusal and the Linux/Remote-WSL setup path; version,
|
|
49
|
+
help and project discovery remain distinct from database-runtime support.
|
|
50
|
+
|
|
8
51
|
## [1.0.0] - 2026-10-17
|
|
9
52
|
|
|
10
53
|
### Stable compatibility
|
|
@@ -772,7 +815,9 @@ not unattended cleanup, and container age is not proof of user inactivity.
|
|
|
772
815
|
publication uses short-lived trusted OIDC, and every release tag must already exist on
|
|
773
816
|
protected `main`.
|
|
774
817
|
|
|
775
|
-
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.
|
|
818
|
+
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...HEAD
|
|
819
|
+
[1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
|
|
820
|
+
[1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
|
|
776
821
|
[1.0.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.10...v1.0.0
|
|
777
822
|
[0.1.0-rc.10]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.9...v0.1.0-rc.10
|
|
778
823
|
[0.1.0-rc.9]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.8...v0.1.0-rc.9
|
package/COMPATIBILITY.md
CHANGED
|
@@ -32,6 +32,49 @@ default and the release-build runtime. End-of-life and odd-numbered Node.js rele
|
|
|
32
32
|
not supported. A future release may remove a Node.js line after its upstream end-of-life
|
|
33
33
|
date, with that change called out in the changelog.
|
|
34
34
|
|
|
35
|
+
## Operating systems and execution location
|
|
36
|
+
|
|
37
|
+
Core 1.0.1's platform boundary distinguishes the computer running VS Code from the
|
|
38
|
+
operating system actually executing Node, Docker and Rehearsal. The corresponding editor
|
|
39
|
+
changes are delivered with extension 0.1.21. A Windows desktop connected to WSL is not native
|
|
40
|
+
Windows execution.
|
|
41
|
+
|
|
42
|
+
| Execution environment | Runtime boundary |
|
|
43
|
+
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| macOS, Intel or Apple Silicon | POSIX runtime; supported Node and a local Docker-compatible engine required |
|
|
45
|
+
| Linux | POSIX runtime; Ubuntu 24.04 is the primary PostgreSQL/Supabase fixture environment |
|
|
46
|
+
| Windows with WSL 2 | Run Linux Node and Docker inside the distribution, with the project on its Linux filesystem; compatibility preview |
|
|
47
|
+
| Native Windows Node, PowerShell or Command Prompt | Version, help and read-only project discovery remain available; runtime/setup/database operations refuse with WSL guidance |
|
|
48
|
+
| WSL 1, a WSL project on `/mnt/c` or another Windows-mounted filesystem | Not a supported database-runtime environment |
|
|
49
|
+
| SSH, Dev Containers, Codespaces or browser-only VS Code | No end-to-end support claim; different host, filesystem and port-forwarding boundaries need their own verification |
|
|
50
|
+
| iOS, iPadOS, Android, BSD and other unlisted systems | Not supported; installing a JavaScript package does not establish a compatible database engine or desktop extension host |
|
|
51
|
+
|
|
52
|
+
Do not bypass native Windows refusal with permission bits or a shell wrapper. Native
|
|
53
|
+
Windows ACLs, process-tree retirement and baseline filesystem behavior are not certified
|
|
54
|
+
by Linux tests. Keep keys, baselines and working files in the WSL Linux home filesystem,
|
|
55
|
+
not a Windows share. The package does not move an existing project automatically.
|
|
56
|
+
See [Windows and WSL setup](docs/getting-started.md#windows-use-a-wsl-2-linux-workspace).
|
|
57
|
+
|
|
58
|
+
### What platform verification proves
|
|
59
|
+
|
|
60
|
+
The separate `Platform compatibility` workflow preserves existing release/check names.
|
|
61
|
+
Desktop jobs install the packed package and exercise version/discovery from a path with
|
|
62
|
+
spaces and Unicode on real Linux, macOS and Windows runners. Windows also checks the
|
|
63
|
+
intentional runtime refusal; a passing Windows job is not a working Windows database.
|
|
64
|
+
The Windows 2025 WSL job provisions its own Ubuntu 24.04 distribution, verifies Linux
|
|
65
|
+
execution and filesystem location, then runs the existing real PostgreSQL/dependent-target
|
|
66
|
+
fixtures against a local Unix-socket Docker Engine. It does not install Docker Desktop
|
|
67
|
+
or expose the daemon over TCP. Node uses the official release checksum; Docker packages
|
|
68
|
+
use its signed apt repository. These integrity checks are not independent publisher
|
|
69
|
+
authentication.
|
|
70
|
+
|
|
71
|
+
A configured workflow is not passing evidence. Reports identify actual OS, architecture,
|
|
72
|
+
installed artifact and exercised boundary. The WSL fixture does not certify native
|
|
73
|
+
Windows runtime, WSL Supabase/Auth/Storage, a native Remote-WSL VS Code session, all Linux
|
|
74
|
+
distributions, assistive technology or every architecture. Editor Node/controller tests
|
|
75
|
+
are separate from real extension-host and browser walkthroughs. Intel/ARM labels and
|
|
76
|
+
Windows/WSL are never interchangeable evidence.
|
|
77
|
+
|
|
35
78
|
The npm install hook may create project scaffolding only when it proves one application
|
|
36
79
|
root. Ordinary npm application installs are covered. A workspace is automatic only when
|
|
37
80
|
one declaring consumer is already visible in its manifest or lockfile; npm can save a
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<p align="center">
|
|
17
17
|
<a href="https://ddupasquier.github.io/rehearsal-db/">Read the documentation</a> ·
|
|
18
18
|
<a href="https://www.npmjs.com/package/@rehearsal-db/core">View on npm</a> ·
|
|
19
|
+
<a href="https://ddupasquier.github.io/rehearsal-db/vscode/">Free VS Code Starter</a> ·
|
|
19
20
|
<a href="https://github.com/Ddupasquier/rehearsal-db">Source on GitHub</a> ·
|
|
20
21
|
<a href="https://github.com/Ddupasquier/rehearsal-db/issues">Report a problem</a>
|
|
21
22
|
</p>
|
|
@@ -45,8 +46,14 @@ it through a reviewed privacy policy, and retire the access again. It is never p
|
|
|
45
46
|
`run`, `reset`, `migrate`, or application launch. Rehearsal is not a backup system or a
|
|
46
47
|
production deployment tool.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
The CLI is free and MIT-licensed, with stable releases on
|
|
50
|
+
[npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.0.2**;
|
|
51
|
+
install only a version actually published there. Prefer a visual workflow?
|
|
52
|
+
[Install the free VS Code Starter preview](docs/vscode.md). Its included features are
|
|
53
|
+
free indefinitely for personal or commercial development—no account, card, or trial.
|
|
54
|
+
|
|
55
|
+
Work on a branch and keep a working backup of your project. Rehearsal's local safety
|
|
56
|
+
boundaries do not replace version control or backups.
|
|
50
57
|
|
|
51
58
|
## What you need
|
|
52
59
|
|
|
@@ -62,6 +69,12 @@ your migration is intended to change.
|
|
|
62
69
|
|
|
63
70
|
## Quick start
|
|
64
71
|
|
|
72
|
+
Prefer OS-specific files? Use the [Windows / WSL 2](installers/windows/README.md),
|
|
73
|
+
[macOS](installers/macos/README.md) or [Linux](installers/linux/README.md) setup folder.
|
|
74
|
+
These readable helpers require existing Node.js 24 or 26 and check prerequisites by
|
|
75
|
+
default; installation is an explicit option. They do not install OS tools or start a
|
|
76
|
+
database. See [their exact behavior and release availability](installers/README.md).
|
|
77
|
+
|
|
65
78
|
From your project directory:
|
|
66
79
|
|
|
67
80
|
```bash
|
|
@@ -157,18 +170,24 @@ loads.
|
|
|
157
170
|
|
|
158
171
|
## Supported today
|
|
159
172
|
|
|
160
|
-
| Environment | Support
|
|
161
|
-
| ------------------------------------------------ |
|
|
162
|
-
| Supabase CLI projects | Supported
|
|
163
|
-
| Ordinary PostgreSQL in local Docker | Supported
|
|
164
|
-
| macOS and Linux | Supported
|
|
165
|
-
| WSL
|
|
166
|
-
| Native Windows
|
|
167
|
-
| Hosted database URLs | Intentionally rejected
|
|
168
|
-
| Optional read-only PostgreSQL source preparation | Explicit opt-in beta
|
|
169
|
-
| MySQL, MongoDB, and other database families | Not yet supported
|
|
173
|
+
| Environment | Support |
|
|
174
|
+
| ------------------------------------------------ | ---------------------------- |
|
|
175
|
+
| Supabase CLI projects | Supported |
|
|
176
|
+
| Ordinary PostgreSQL in local Docker | Supported |
|
|
177
|
+
| macOS and Linux | Supported |
|
|
178
|
+
| Windows via WSL 2 on its Linux filesystem | Compatibility preview |
|
|
179
|
+
| Native Windows runtime | Not yet supported; use WSL 2 |
|
|
180
|
+
| Hosted database URLs | Intentionally rejected |
|
|
181
|
+
| Optional read-only PostgreSQL source preparation | Explicit opt-in beta |
|
|
182
|
+
| MySQL, MongoDB, and other database families | Not yet supported |
|
|
170
183
|
|
|
171
184
|
See [COMPATIBILITY.md](COMPATIBILITY.md) for the exact support contract.
|
|
185
|
+
Native Windows metadata commands are not database-runtime support. For Windows, follow
|
|
186
|
+
the [WSL 2 setup path](docs/getting-started.md#windows-use-a-wsl-2-linux-workspace):
|
|
187
|
+
Linux Node, project files and Docker must live in the Linux execution environment.
|
|
188
|
+
Core 1.0.1 and editor 0.1.21 add explicit platform guidance/refusal; older immutable
|
|
189
|
+
releases retain their original behavior. Mobile, browser-only and unlisted systems are
|
|
190
|
+
not implied by desktop or Linux compatibility.
|
|
172
191
|
|
|
173
192
|
## Documentation
|
|
174
193
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehearsal-db/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
|
|
6
6
|
"repository": {
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"test:fixture:resources": "npm run build && node scripts/verification/run_scenario.mjs resources",
|
|
79
79
|
"release:verify": "npm run build && node scripts/verification/release_gate.mjs",
|
|
80
80
|
"check": "npm run check:syntax && npm run typecheck && npm run test && npm run docs:build && npm run format:check && npm run package:audit",
|
|
81
|
-
"check:syntax": "
|
|
81
|
+
"check:syntax": "node scripts/verification/check_syntax.mjs",
|
|
82
82
|
"typecheck": "npm run build && tsc --project tsconfig.json",
|
|
83
83
|
"format": "prettier --write .",
|
|
84
84
|
"format:check": "prettier --check .",
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
/** Create safe project-root Rehearsal scaffolding during a local install. */
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { createInstalledRehearsalScaffold, renderInstalledRehearsalScaffold, } from "../../src/project/install_scaffold.mjs";
|
|
5
|
+
import { inspectHostPlatform } from "../../src/shared/host_platform.mjs";
|
|
5
6
|
const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
6
7
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
8
|
+
const host = await inspectHostPlatform();
|
|
9
|
+
if (!host.supported) {
|
|
10
|
+
console.log(`Rehearsal installed without creating project files. ${host.remediation}`);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const result = await createInstalledRehearsalScaffold({ packageRoot });
|
|
14
|
+
console.log(renderInstalledRehearsalScaffold(result));
|
|
15
|
+
}
|
|
9
16
|
}
|
|
10
17
|
catch (error) {
|
|
11
18
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Preserve fixed original migration guidance without masking aggregate cleanup failure. */
|
|
2
|
+
import { RehearsalError, type RehearsalErrorOverrides } from "../shared/diagnostics.mjs";
|
|
3
|
+
export declare const normalizeCliFailure: (error: unknown, overrides?: RehearsalErrorOverrides) => RehearsalError;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Preserve fixed original migration guidance without masking aggregate cleanup failure. */
|
|
2
|
+
import { RehearsalError, normalizeRehearsalError, } from "../shared/diagnostics.mjs";
|
|
3
|
+
import { parseMigrationFailureDiagnostic, } from "../runtime/migration_failure.mjs";
|
|
4
|
+
const aggregateMigrationFailure = (error) => {
|
|
5
|
+
try {
|
|
6
|
+
let original = error;
|
|
7
|
+
for (let depth = 0; depth <= 4; depth += 1) {
|
|
8
|
+
if (original instanceof AggregateError) {
|
|
9
|
+
if (depth === 4 || !Array.isArray(original.errors))
|
|
10
|
+
return undefined;
|
|
11
|
+
original = original.errors[0];
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (original instanceof RehearsalError &&
|
|
15
|
+
original.category !== "operation_cancelled" &&
|
|
16
|
+
original.context !== null &&
|
|
17
|
+
typeof original.context === "object" &&
|
|
18
|
+
!Array.isArray(original.context) &&
|
|
19
|
+
"migrationFailure" in original.context)
|
|
20
|
+
return parseMigrationFailureDiagnostic(original.context.migrationFailure);
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* Untrusted metadata is not evidence; retain the aggregate's existing failure. */
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
export const normalizeCliFailure = (error, overrides = {}) => {
|
|
30
|
+
const failure = normalizeRehearsalError(error, overrides);
|
|
31
|
+
if (!(error instanceof AggregateError))
|
|
32
|
+
return failure;
|
|
33
|
+
const migrationFailure = aggregateMigrationFailure(error);
|
|
34
|
+
if (migrationFailure === undefined)
|
|
35
|
+
return failure;
|
|
36
|
+
// Keep outer category/exit and cleanup warning. Project no original/sibling text.
|
|
37
|
+
return new RehearsalError({
|
|
38
|
+
category: failure.category,
|
|
39
|
+
code: failure.code,
|
|
40
|
+
message: failure.message,
|
|
41
|
+
expected: failure.expected,
|
|
42
|
+
actual: failure.actual,
|
|
43
|
+
context: { migrationFailure },
|
|
44
|
+
...(failure.refused === undefined ? {} : { refused: failure.refused }),
|
|
45
|
+
suggestions: [...migrationFailure.suggestions, ...failure.suggestions],
|
|
46
|
+
cause: failure.cause,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
@@ -8,13 +8,14 @@ import { join } from "node:path";
|
|
|
8
8
|
import { performance } from "node:perf_hooks";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import * as prompts from "@clack/prompts";
|
|
11
|
-
import { RehearsalError,
|
|
11
|
+
import { RehearsalError, renderHumanError, serializeRehearsalError, } from "../shared/diagnostics.mjs";
|
|
12
12
|
import { acquireProjectOperation, assertInstalledPackageFingerprint, createInstalledPackageFingerprint, } from "../shared/operation_guard.mjs";
|
|
13
13
|
import { commandMutatesProject, parseArguments, resetGuidedFlags, } from "./arguments.mjs";
|
|
14
14
|
import { usage } from "./basic_command_handlers.mjs";
|
|
15
15
|
import { createCliCommandRegistry } from "./cli_commands.mjs";
|
|
16
16
|
import { createCliSessionState, resetCommandSessionState, } from "./command_contract.mjs";
|
|
17
17
|
import { createCommandEmitter } from "./command_output.mjs";
|
|
18
|
+
import { normalizeCliFailure } from "./failure_output.mjs";
|
|
18
19
|
import { createGuidedHome } from "./guided.mjs";
|
|
19
20
|
import { createProjectCommands } from "./project_commands.mjs";
|
|
20
21
|
import { buildRuntimeStackPlan, createRuntimeCommands, runRuntimeStackDoctor, topologyCandidateSummary, topologyCommandEnvironment, } from "./runtime_commands.mjs";
|
|
@@ -22,6 +23,7 @@ import { buildRefreshWorkflowPlan, createRefreshWorkflow, } from "./source_comma
|
|
|
22
23
|
import { installGuidedExitShortcut, useStyledPrompts } from "./terminal.mjs";
|
|
23
24
|
import { runWithOperationCancellation, supportsOperationCancellation, } from "../shared/cancellation.mjs";
|
|
24
25
|
import { withOperationProgress } from "../shared/operation_progress.mjs";
|
|
26
|
+
import { assertSupportedHostPlatform } from "../shared/host_platform.mjs";
|
|
25
27
|
const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
26
28
|
const projectRoot = process.cwd();
|
|
27
29
|
const managerPath = join(packageRoot, "dist/scripts/runtime/manage_database.mjs");
|
|
@@ -94,8 +96,17 @@ const main = async () => {
|
|
|
94
96
|
!flags.json &&
|
|
95
97
|
process.stdin.isTTY &&
|
|
96
98
|
process.stdout.isTTY;
|
|
97
|
-
const guided =
|
|
98
|
-
|
|
99
|
+
const guided = !flags.help &&
|
|
100
|
+
!flags.version &&
|
|
101
|
+
(command === "guide" || wantsAutomaticGuide);
|
|
102
|
+
if (!flags.help &&
|
|
103
|
+
!flags.version &&
|
|
104
|
+
(guided || !["help", "version", "discover", "doctor"].includes(command))) {
|
|
105
|
+
await assertSupportedHostPlatform({ projectRoot });
|
|
106
|
+
}
|
|
107
|
+
const mutating = !flags.help &&
|
|
108
|
+
!flags.version &&
|
|
109
|
+
commandMutatesProject({ command, flags, guided });
|
|
99
110
|
const operation = mutating
|
|
100
111
|
? await acquireProjectOperation({
|
|
101
112
|
projectRoot,
|
|
@@ -207,7 +218,7 @@ try {
|
|
|
207
218
|
await main();
|
|
208
219
|
}
|
|
209
220
|
catch (error) {
|
|
210
|
-
const failure =
|
|
221
|
+
const failure = normalizeCliFailure(error, {
|
|
211
222
|
expected: "a safe, versioned, local-only Rehearsal operation",
|
|
212
223
|
actual: errorMessage(error),
|
|
213
224
|
refused: "No further Rehearsal action was performed.",
|
|
@@ -168,13 +168,20 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
|
|
|
168
168
|
: undefined;
|
|
169
169
|
const renderedHttpProofResults = "httpProofResults" in value ? value.httpProofResults : [];
|
|
170
170
|
const renderedLifecycle = "lifecycle" in value ? value.lifecycle : undefined;
|
|
171
|
+
const hasApplicationLaunch = topology.primary.config.application.readiness !== null;
|
|
171
172
|
const nextAction = {
|
|
172
173
|
run: renderedLifecycle?.runtimeState === "stopped"
|
|
173
|
-
?
|
|
174
|
-
:
|
|
175
|
-
|
|
174
|
+
? `The runtime was stopped with its database and Storage changes preserved. Run rehearsal start${hasApplicationLaunch ? " or rehearsal open" : ""} to resume.`
|
|
175
|
+
: hasApplicationLaunch
|
|
176
|
+
? "Next: run rehearsal open for hands-on testing, then rehearsal verify."
|
|
177
|
+
: "The rehearsal command is complete; the database remains running. Next: run rehearsal verify or inspect the local database. This database-only project has no application to open.",
|
|
178
|
+
start: hasApplicationLaunch
|
|
179
|
+
? "Next: run rehearsal open for hands-on testing or rehearsal status."
|
|
180
|
+
: "The database is running. Next: run rehearsal status or rehearsal verify; no application launch is configured.",
|
|
176
181
|
migrate: "Next: run rehearsal verify to prove the current runtime.",
|
|
177
|
-
reset:
|
|
182
|
+
reset: hasApplicationLaunch
|
|
183
|
+
? "Next: run rehearsal open or rehearsal verify."
|
|
184
|
+
: "Next: run rehearsal verify or inspect the local database; no application launch is configured.",
|
|
178
185
|
status: "Next: run rehearsal verify, reset, stop, or discard.",
|
|
179
186
|
stop: "Next: run rehearsal start when you want to resume.",
|
|
180
187
|
discard: "The disposable runtime was removed; the immutable baseline remains.",
|
|
@@ -14,12 +14,14 @@ import { RehearsalError, normalizeRehearsalError, redactDiagnosticValue, } from
|
|
|
14
14
|
import { assertInstalledPackageFingerprint } from "../shared/operation_guard.mjs";
|
|
15
15
|
import { getOperationCancellationSignal, throwIfOperationCancelled, } from "../shared/cancellation.mjs";
|
|
16
16
|
import { runOwnedProcess } from "../shared/owned_process.mjs";
|
|
17
|
+
import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
|
|
17
18
|
import { getOwnedOperationProgressId, trackOperationStage, } from "../shared/operation_progress.mjs";
|
|
18
19
|
import { buildRuntimeManagerArguments } from "../targets/target.mjs";
|
|
19
20
|
import { parseRuntimeStatusSnapshot, renderRuntimeStatusSnapshot, } from "../runtime/status.mjs";
|
|
20
21
|
import { applyRuntimePersistence, shouldStopRuntime, } from "../runtime/persistence.mjs";
|
|
21
22
|
import { executeRuntimeTargetStack } from "./runtime_stack_execution.mjs";
|
|
22
|
-
import { summarizeRuntimeManagerFailure } from "./runtime_failure_summary.mjs";
|
|
23
|
+
import { summarizeRuntimeManagerFailure, runtimePortFailureGuidance, } from "./runtime_failure_summary.mjs";
|
|
24
|
+
import { readManagerMigrationFailure } from "../runtime/migration_failure.mjs";
|
|
23
25
|
import { releaseOwnedColimaWhenIdle } from "../runtime/container_engine_ownership.mjs";
|
|
24
26
|
import { inspectRehearsalResources } from "../runtime/resource_inventory.mjs";
|
|
25
27
|
import { assertLocalDockerContext } from "../runtime/local_docker_context.mjs";
|
|
@@ -55,27 +57,15 @@ const parseCommand = (source) => {
|
|
|
55
57
|
throw new Error("Application proof command is empty.");
|
|
56
58
|
return values;
|
|
57
59
|
};
|
|
58
|
-
const projectCommandEnvironment = (additional = {}) => ({
|
|
59
|
-
...Object.fromEntries([
|
|
60
|
-
"CI",
|
|
61
|
-
"COLORTERM",
|
|
62
|
-
"FORCE_COLOR",
|
|
63
|
-
"HOME",
|
|
64
|
-
"LANG",
|
|
65
|
-
"LC_ALL",
|
|
66
|
-
"NO_COLOR",
|
|
67
|
-
"PATH",
|
|
68
|
-
"SHELL",
|
|
69
|
-
"TERM",
|
|
70
|
-
"TMPDIR",
|
|
71
|
-
"USER",
|
|
72
|
-
].flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]])),
|
|
73
|
-
...additional,
|
|
74
|
-
});
|
|
60
|
+
const projectCommandEnvironment = (additional = {}) => createCleanProcessEnvironment({ overrides: additional });
|
|
75
61
|
const RUNTIME_STARTUP_FAILURE_PATTERN = /container\b.*\b(?:is not ready|starting|unhealthy)|health(?:check)?\b.*\b(?:failed|starting|unhealthy)|out of memory|\boom\b|no space left|resource temporarily unavailable/iu;
|
|
76
62
|
export const classifyManagerFailure = ({ action, output, inferredCategory, }) => {
|
|
63
|
+
if (/^(?:Error:\s*)?The baseline (?:PostgreSQL|Supabase) migration failed \(/mu.test(output))
|
|
64
|
+
return "baseline_invalid";
|
|
65
|
+
if (/^(?:Error:\s*)?The confirmed candidate (?:PostgreSQL|Supabase) migration failed \(/mu.test(output))
|
|
66
|
+
return "migration_candidate_failure";
|
|
77
67
|
if (RUNTIME_STARTUP_FAILURE_PATTERN.test(output) ||
|
|
78
|
-
/Supabase startup failed \(/u.test(output) ||
|
|
68
|
+
/(?:PostgreSQL|Supabase) startup failed \(/u.test(output) ||
|
|
79
69
|
/^Error:\s*supabase start(?:\s|$)/imu.test(output)) {
|
|
80
70
|
return "runtime_dependency_failure";
|
|
81
71
|
}
|
|
@@ -287,22 +277,31 @@ export const createRuntimeCommands = ({ packageRoot, projectRoot, managerPath, g
|
|
|
287
277
|
inferredCategory: inferred.category,
|
|
288
278
|
});
|
|
289
279
|
const startupFailure = category === "runtime_dependency_failure";
|
|
290
|
-
const
|
|
280
|
+
const migrationFailure = readManagerMigrationFailure(completeOutput);
|
|
281
|
+
const portGuidance = runtimePortFailureGuidance(conciseOutput);
|
|
282
|
+
const engineCapacity = startupFailure && portGuidance.length === 0
|
|
291
283
|
? inspectContainerCapacity()
|
|
292
284
|
: undefined;
|
|
293
285
|
throw new RehearsalError({
|
|
294
286
|
category,
|
|
295
287
|
code: `MANAGER_${action.toUpperCase()}_FAILED`,
|
|
296
|
-
message: `The local Rehearsal ${action} operation did not complete
|
|
288
|
+
message: `The local Rehearsal ${action} operation did not complete.${migrationFailure ? ` ${migrationFailure.summary}` : ""}`,
|
|
297
289
|
expected: "the isolated runtime operation to finish and verify",
|
|
298
290
|
actual: conciseOutput || `exit ${result.status}`,
|
|
299
|
-
context: {
|
|
291
|
+
context: {
|
|
292
|
+
action,
|
|
293
|
+
target: targetName ?? "primary",
|
|
294
|
+
...(migrationFailure ? { migrationFailure } : {}),
|
|
295
|
+
...(engineCapacity ? { engineCapacity } : {}),
|
|
296
|
+
},
|
|
300
297
|
refused: "The runtime was not reported as trusted.",
|
|
301
298
|
suggestions: [
|
|
299
|
+
...(migrationFailure?.suggestions ?? []),
|
|
300
|
+
...portGuidance,
|
|
302
301
|
"Review the concise failure above, then rerun rehearsal doctor before retrying.",
|
|
303
|
-
...(startupFailure
|
|
302
|
+
...(startupFailure && portGuidance.length === 0
|
|
304
303
|
? [
|
|
305
|
-
"
|
|
304
|
+
"If startup evidence confirms a resource shortage, stop only stacks you own or increase Docker/Colima capacity, then retry. Rehearsal will not change engine settings or bypass service health checks.",
|
|
306
305
|
"Run docker stats --no-stream to identify active containers using the shared engine.",
|
|
307
306
|
]
|
|
308
307
|
: []),
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
/** Keep useful manager failures rather than debug advice and trailing Node stacks. */
|
|
2
2
|
import { redactDiagnosticValue } from "../shared/diagnostics.mjs";
|
|
3
|
+
import { readManagerMigrationFailure, renderMigrationFailureDiagnostic, } from "../runtime/migration_failure.mjs";
|
|
4
|
+
import { portStartupGuidance } from "../targets/port_start_failure.mjs";
|
|
5
|
+
export const runtimePortFailureGuidance = (output) => {
|
|
6
|
+
const provider = output.match(/^(?:Error:\s*)?(PostgreSQL|Supabase) startup failed \((?:exit -?\d+|process failure)\)\.$/mu)?.[1];
|
|
7
|
+
const evidence = output
|
|
8
|
+
.match(/^Reported startup evidence: ([a-z_, ]+)\.$/mu)?.[1]
|
|
9
|
+
?.split(/,\s*/u);
|
|
10
|
+
const port = evidence?.find((value) => value === "port_in_use" || value === "port_unavailable");
|
|
11
|
+
return provider && (port === "port_in_use" || port === "port_unavailable")
|
|
12
|
+
? portStartupGuidance(port, provider === "PostgreSQL" ? "postgresql" : "supabase").split("\n")
|
|
13
|
+
: [];
|
|
14
|
+
};
|
|
3
15
|
export const summarizeRuntimeManagerFailure = (output) => {
|
|
4
16
|
let nodeSourceContext = false;
|
|
5
17
|
const lines = output
|
|
@@ -27,16 +39,22 @@ export const summarizeRuntimeManagerFailure = (output) => {
|
|
|
27
39
|
.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"']+/giu, "[REDACTED_URL]")
|
|
28
40
|
.replace(/\b[A-Z][A-Z0-9_]*\s*=\s*[^\s]+/gu, "[REDACTED_ENV]")
|
|
29
41
|
.replace(/(?:\/[A-Za-z0-9_.-]+){2,}(?::\d+(?::\d+)?)?/gu, "[LOCAL_PATH]"));
|
|
30
|
-
const startup = lines.findIndex((line) => /Supabase startup failed \(/u.test(line));
|
|
42
|
+
const startup = lines.findIndex((line) => /(?:PostgreSQL|Supabase) startup failed \(/u.test(line));
|
|
31
43
|
const failure = lines.reduce((last, line, index) => (/^[A-Za-z]*Error:/u.test(line) ? index : last), -1);
|
|
32
44
|
const migration = lines.findIndex((line) => /^Error: The (?:confirmed candidate|baseline) (?:PostgreSQL|Supabase) migration failed \(exit (?:-?\d+|unknown); stderr \d+ bytes\)\. SQL and provider output are withheld\.$/u.test(line));
|
|
33
45
|
const rollbackFailed = /The runtime operation failed and its owned runtime could not be removed automatically\./u.test(output);
|
|
46
|
+
const migrationDiagnostic = migration >= 0 ? readManagerMigrationFailure(lines.join("\n")) : undefined;
|
|
34
47
|
const selected = startup >= 0
|
|
35
48
|
? lines
|
|
36
|
-
.slice(startup, startup +
|
|
37
|
-
.filter((line) => /^(?:Error:\s*)?Supabase startup failed \(|^Reported startup evidence:|^Reported service states:|^Provider logs, paths, environment values and connection URLs are omitted\./u.test(line))
|
|
49
|
+
.slice(startup, startup + 12)
|
|
50
|
+
.filter((line) => /^(?:Error:\s*)?(?:PostgreSQL|Supabase) startup failed \(|^Reported startup evidence:|^Reported service states:|^Provider logs, (?:command arguments, )?paths, environment values and connection URLs are omitted\./u.test(line))
|
|
38
51
|
: migration >= 0
|
|
39
|
-
? [
|
|
52
|
+
? [
|
|
53
|
+
lines[migration],
|
|
54
|
+
...(migrationDiagnostic
|
|
55
|
+
? [renderMigrationFailureDiagnostic(migrationDiagnostic)]
|
|
56
|
+
: []),
|
|
57
|
+
]
|
|
40
58
|
: rollbackFailed
|
|
41
59
|
? [
|
|
42
60
|
"Error: The operation failed; the original cause could not be safely summarized.",
|
|
@@ -44,10 +62,18 @@ export const summarizeRuntimeManagerFailure = (output) => {
|
|
|
44
62
|
: failure >= 0
|
|
45
63
|
? lines.slice(failure, failure + 8)
|
|
46
64
|
: lines.slice(-8);
|
|
65
|
+
if (startup >= 0)
|
|
66
|
+
selected.push(...runtimePortFailureGuidance(selected.join("\n")));
|
|
47
67
|
if (rollbackFailed)
|
|
48
68
|
selected.push("Owned rollback cleanup could not be confirmed. Resources may remain; inspect activity and status before retrying. No cleanup success is claimed.");
|
|
49
69
|
if (startup >= 0 &&
|
|
50
70
|
/owned cleanup could not be proved|owned resources were retained|Resources and data were retained|retained resources need review/iu.test(output))
|
|
51
71
|
selected.push("Owned startup cleanup could not be proved; resources and data were retained. Inspect activity and status before retrying.");
|
|
72
|
+
if (selected.join("\n").length > 2_000 && migrationDiagnostic?.source) {
|
|
73
|
+
// Human summary stays bounded and retains cleanup warnings; structured output
|
|
74
|
+
// independently reads the complete, bounded manager frame.
|
|
75
|
+
const { source: _source, ...diagnostic } = migrationDiagnostic;
|
|
76
|
+
selected[1] = renderMigrationFailureDiagnostic(diagnostic);
|
|
77
|
+
}
|
|
52
78
|
return selected.join("\n").slice(0, 2_000);
|
|
53
79
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildRehearsalPlan, runRehearsalDoctor } from "../runtime/plan.mjs";
|
|
2
2
|
import { inspectRuntimeTopologyCandidates, loadRuntimeTopology, } from "../runtime/topology.mjs";
|
|
3
3
|
import { redactDiagnosticValue } from "../shared/diagnostics.mjs";
|
|
4
|
+
import { inspectHostPlatform } from "../shared/host_platform.mjs";
|
|
4
5
|
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
5
6
|
export const buildRuntimeStackPlan = async (planOptions) => {
|
|
6
7
|
const topology = await loadRuntimeTopology(planOptions);
|
|
@@ -52,6 +53,11 @@ export const buildRuntimeStackPlan = async (planOptions) => {
|
|
|
52
53
|
};
|
|
53
54
|
};
|
|
54
55
|
export const runRuntimeStackDoctor = async (planOptions) => {
|
|
56
|
+
const host = await inspectHostPlatform({
|
|
57
|
+
projectRoot: planOptions.projectRoot ?? process.cwd(),
|
|
58
|
+
});
|
|
59
|
+
if (!host.supported)
|
|
60
|
+
return runRehearsalDoctor(planOptions);
|
|
55
61
|
let topology;
|
|
56
62
|
try {
|
|
57
63
|
topology = await loadRuntimeTopology(planOptions);
|
|
@@ -6,6 +6,7 @@ import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
|
6
6
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { applyRehearsalSetup, planRehearsalSetup } from "./setup.mjs";
|
|
8
8
|
import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, } from "./configuration.mjs";
|
|
9
|
+
import { inspectHostPlatform } from "../shared/host_platform.mjs";
|
|
9
10
|
const PACKAGE_NAME = "@rehearsal-db/core";
|
|
10
11
|
const DEPENDENCY_GROUPS = Object.freeze([
|
|
11
12
|
"dependencies",
|
|
@@ -224,6 +225,12 @@ export const createInstalledRehearsalScaffold = async ({ packageRoot, environmen
|
|
|
224
225
|
});
|
|
225
226
|
if (resolution.status !== "resolved")
|
|
226
227
|
return resolution;
|
|
228
|
+
const host = await inspectHostPlatform({
|
|
229
|
+
projectRoot: resolution.projectRoot,
|
|
230
|
+
});
|
|
231
|
+
if (!host.supported) {
|
|
232
|
+
return skip(`${host.detail} ${host.remediation}`.replace(/\.$/u, ""));
|
|
233
|
+
}
|
|
227
234
|
const detected = await inspectDetectedProject({
|
|
228
235
|
projectRoot: resolution.projectRoot,
|
|
229
236
|
});
|
|
@@ -7,6 +7,7 @@ import { lstat, readFile, readdir } from "node:fs/promises";
|
|
|
7
7
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { CONFIG_FILENAMES } from "./configuration_discovery.mjs";
|
|
10
|
+
import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
|
|
10
11
|
const MAX_METADATA_BYTES = 1024 * 1024;
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
const inside = (root, path) => {
|
|
@@ -112,17 +113,14 @@ export const probeContainerEngineWithoutStarting = async () => {
|
|
|
112
113
|
await execFileAsync("docker", ["info", "--format", "{{.ServerVersion}}"], {
|
|
113
114
|
timeout: 2_000,
|
|
114
115
|
maxBuffer: 64 * 1024,
|
|
115
|
-
env:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
]
|
|
124
|
-
.map((key) => [key, process.env[key]])
|
|
125
|
-
.filter((entry) => Boolean(entry[1]))),
|
|
116
|
+
env: createCleanProcessEnvironment({
|
|
117
|
+
passthroughKeys: [
|
|
118
|
+
"COLIMA_HOME",
|
|
119
|
+
"DOCKER_CONFIG",
|
|
120
|
+
"DOCKER_CONTEXT",
|
|
121
|
+
"DOCKER_HOST",
|
|
122
|
+
],
|
|
123
|
+
}),
|
|
126
124
|
});
|
|
127
125
|
return "running";
|
|
128
126
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Translate provider-native SQLSTATE evidence into fixed, privacy-safe guidance. */
|
|
2
|
+
import { type MigrationFailureSource } from "./migration_failure_source.mjs";
|
|
3
|
+
export type MigrationFailureClassification = "syntax" | "schema" | "constraint" | "permission" | "connectivity" | "timeout_or_cancel" | "unknown";
|
|
4
|
+
export interface MigrationFailureDiagnostic {
|
|
5
|
+
readonly schemaVersion: 1;
|
|
6
|
+
readonly provider: "postgresql" | "supabase";
|
|
7
|
+
readonly classification: MigrationFailureClassification;
|
|
8
|
+
readonly sqlState: string | null;
|
|
9
|
+
readonly evidence: "provider_sqlstate" | "unavailable" | "ambiguous";
|
|
10
|
+
readonly summary: string;
|
|
11
|
+
readonly suggestions: readonly string[];
|
|
12
|
+
readonly source?: MigrationFailureSource;
|
|
13
|
+
}
|
|
14
|
+
export declare const migrationFailureDiagnostic: ({ provider, sqlState, evidence, }: {
|
|
15
|
+
provider: MigrationFailureDiagnostic["provider"];
|
|
16
|
+
sqlState?: string | null;
|
|
17
|
+
evidence?: MigrationFailureDiagnostic["evidence"];
|
|
18
|
+
}) => MigrationFailureDiagnostic;
|
|
19
|
+
/** Read only known provider error envelopes, never arbitrary mentions of SQLSTATE. */
|
|
20
|
+
export declare const diagnoseMigrationFailure: ({ provider, stderr, }: {
|
|
21
|
+
provider: MigrationFailureDiagnostic["provider"];
|
|
22
|
+
stderr: string;
|
|
23
|
+
}) => MigrationFailureDiagnostic;
|
|
24
|
+
export declare const renderMigrationFailureDiagnostic: (diagnostic: MigrationFailureDiagnostic) => string;
|
|
25
|
+
/** Closed public projection: reject free text or metadata not generated by this owner. */
|
|
26
|
+
export declare const parseMigrationFailureDiagnostic: (value: unknown) => MigrationFailureDiagnostic;
|
|
27
|
+
/** Parse only Rehearsal's fixed migration header and adjacent fixed code line. */
|
|
28
|
+
export declare const readManagerMigrationFailure: (output: string) => MigrationFailureDiagnostic | undefined;
|