@rehearsal-db/core 0.1.0-beta.4 → 0.1.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -1
- package/COMPATIBILITY.md +5 -0
- package/README.md +105 -368
- package/SECURITY.md +2 -1
- package/SUPPORT.md +5 -0
- package/docs/adapters.md +5 -0
- package/docs/baselines.md +81 -22
- package/docs/commands.md +76 -64
- package/docs/configuration.md +47 -4
- package/docs/getting-started.md +87 -218
- package/docs/glossary.md +7 -8
- package/docs/sanitization.md +8 -5
- package/docs/security-model.md +5 -1
- package/docs/troubleshooting.md +46 -1
- package/docs/tutorial.md +70 -59
- package/package.json +5 -3
- package/scripts/lib/rehearsal/baseline_preparation.mjs +138 -23
- package/scripts/lib/rehearsal/configuration.d.mts +18 -3
- package/scripts/lib/rehearsal/configuration.mjs +227 -56
- package/scripts/lib/rehearsal/diagnostics.mjs +4 -2
- package/scripts/lib/rehearsal/input_discovery.mjs +155 -0
- package/scripts/lib/rehearsal/plan.mjs +85 -10
- package/scripts/lib/rehearsal/policy_review.mjs +45 -5
- package/scripts/lib/rehearsal/runtime_restore.mjs +6 -2
- package/scripts/lib/rehearsal/setup.mjs +60 -28
- package/scripts/lib/rehearsal/support_report.mjs +102 -0
- package/scripts/lib/runtime/postgresql_runtime.mjs +772 -0
- package/scripts/lib/runtime/runtime_target.mjs +41 -0
- package/scripts/lib/runtime/supabase_runtime.mjs +790 -0
- package/scripts/operations/database/manage_rehearsal_database.mjs +6 -785
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +522 -149
package/docs/tutorial.md
CHANGED
|
@@ -1,96 +1,107 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Safe hands-on tutorial
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
3
|
+
This tutorial runs a complete PostgreSQL rehearsal in a temporary fictional project. It
|
|
4
|
+
does not use your application, production data, or a hosted database.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Allow about five minutes. You need Node.js 24 and a running Docker-compatible engine.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## 1. Prepare Rehearsal
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
If you do not already have this repository, clone it:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone https://github.com/Ddupasquier/rehearsal-db.git
|
|
14
|
+
cd rehearsal-db
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
candidate migration:
|
|
17
|
+
Then run:
|
|
19
18
|
|
|
20
|
-
```
|
|
21
|
-
|
|
19
|
+
```bash
|
|
20
|
+
npm ci --ignore-scripts
|
|
21
|
+
docker pull postgres:17-alpine
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
If you already have this repository open, use that checkout.
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## 2. Make a temporary project
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
ports and a unique project ID. Its `rehearsalConfig` must point at a dedicated, unlinked
|
|
30
|
-
Supabase config. Never reuse a hosted project reference or production environment file.
|
|
28
|
+
From the Rehearsal repository root, run this block exactly:
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
```bash
|
|
31
|
+
rehearsal_repo=$PWD
|
|
32
|
+
tutorial_dir=$(mktemp -d)
|
|
33
|
+
cp -R tests/fixtures/postgresql-project "$tutorial_dir/app"
|
|
34
|
+
cd "$tutorial_dir/app"
|
|
35
|
+
npm install --ignore-scripts --no-save "$rehearsal_repo"
|
|
36
|
+
```
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
The temporary project contains:
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
- one historical migration that creates a `widgets` table;
|
|
41
|
+
- one candidate migration that adds a `description` column;
|
|
42
|
+
- one synthetic row;
|
|
43
|
+
- a reviewed sanitization policy;
|
|
44
|
+
- an application proof that checks the migrated database.
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
## 3. Create the baseline
|
|
47
|
+
|
|
48
|
+
Open the guide:
|
|
44
49
|
|
|
45
50
|
```bash
|
|
46
|
-
npx rehearsal
|
|
47
|
-
--records=rehearsal/synthetic-data.ndjson \
|
|
48
|
-
--ledger=rehearsal/migration-ledger.json
|
|
51
|
+
npx rehearsal
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
and never contacts a hosted service.
|
|
54
|
+
The project begins at Stage 3 of 4. Choose **Create the baseline**. Accept the detected
|
|
55
|
+
record and ledger files, review the summary, then confirm creation.
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
The project should advance to Stage 4 of 4 with every item checked.
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
## 4. Run the migration
|
|
60
|
+
|
|
61
|
+
Choose **Run a rehearsal**. The guide should show exactly one candidate:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
20260101000100_add_widget_description.sql
|
|
62
65
|
```
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
Confirm it. Rehearsal creates a loopback-only PostgreSQL container, restores the baseline,
|
|
68
|
+
applies the candidate, and runs the fixture proof.
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
Success ends with an application-proof message and suggests exercising the local
|
|
71
|
+
application before verification.
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
## 5. Try the runtime commands
|
|
74
|
+
|
|
75
|
+
Exit the guide, then run:
|
|
70
76
|
|
|
71
77
|
```bash
|
|
72
|
-
npx rehearsal run --confirm-candidates=<sha256>
|
|
73
|
-
npx rehearsal inspect migrations
|
|
74
78
|
npx rehearsal status
|
|
79
|
+
npx rehearsal verify
|
|
80
|
+
npx rehearsal reset
|
|
81
|
+
npx rehearsal stop
|
|
82
|
+
npx rehearsal discard
|
|
75
83
|
```
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
`discard` removes only the labeled tutorial container and volume. The temporary project
|
|
86
|
+
directory remains on disk and can be deleted when you no longer need it.
|
|
87
|
+
|
|
88
|
+
## What you proved
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
You used the same packaged CLI a normal project installs. Rehearsal verified the baseline,
|
|
91
|
+
approved an exact migration, ran it in a disposable database, tested the result, and
|
|
92
|
+
cleaned up only its own runtime.
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
runtime, and never produce a successful receipt.
|
|
94
|
+
Next, follow [Getting started](getting-started.md) in your own project. Start with synthetic
|
|
95
|
+
rows until the workflow and application proof are reliable.
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
## Supabase check
|
|
98
|
+
|
|
99
|
+
If Supabase CLI 2.117.0 is installed, the repository also has a fully automated Supabase
|
|
100
|
+
proof:
|
|
88
101
|
|
|
89
102
|
```bash
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
npx rehearsal stop
|
|
103
|
+
cd "$rehearsal_repo"
|
|
104
|
+
npm run test:fixture
|
|
93
105
|
```
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
workflow.
|
|
107
|
+
The PostgreSQL equivalent is `npm run test:fixture:postgresql`.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehearsal-db/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Safely rehearse Supabase migrations against sanitized, production-shaped
|
|
5
|
+
"description": "Safely rehearse PostgreSQL and Supabase migrations against sanitized, production-shaped data.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/Ddupasquier/rehearsal-db.git"
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"scripts/lib/environment/local_supabase.mjs",
|
|
41
41
|
"scripts/lib/rehearsal",
|
|
42
|
+
"scripts/lib/runtime",
|
|
42
43
|
"scripts/operations/database/manage_rehearsal_database.mjs",
|
|
43
44
|
"scripts/operations/rehearsal/rehearsal_cli.mjs",
|
|
44
45
|
"docs/*.md",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"test:unit": "vitest run tests/scripts",
|
|
56
57
|
"test:docs": "vitest run tests/contracts/documentationCommands.test.mjs",
|
|
57
58
|
"test:fixture": "node scripts/operations/rehearsal/prove_independent_fixture.mjs",
|
|
59
|
+
"test:fixture:postgresql": "node scripts/operations/rehearsal/prove_postgresql_fixture.mjs",
|
|
58
60
|
"check": "npm run check:syntax && npm run test && npm run format:check && npm run package:audit",
|
|
59
61
|
"check:syntax": "find scripts tests -type f -name '*.mjs' -exec node --check {} +",
|
|
60
62
|
"format": "prettier --write .",
|
|
@@ -75,6 +77,6 @@
|
|
|
75
77
|
"devDependencies": {
|
|
76
78
|
"@lydell/node-pty": "^1.2.0-beta.15",
|
|
77
79
|
"prettier": "^3.9.6",
|
|
78
|
-
"vitest": "^
|
|
80
|
+
"vitest": "^5.0.2"
|
|
79
81
|
}
|
|
80
82
|
}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { createReadStream } from "node:fs";
|
|
7
|
-
import { access, mkdir, open, readFile } from "node:fs/promises";
|
|
7
|
+
import { access, lstat, mkdir, open, readFile } from "node:fs/promises";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { loadRehearsalConfig } from "./configuration.mjs";
|
|
11
11
|
import { buildMigrationLedgerInventory } from "./migration_history.mjs";
|
|
12
12
|
|
|
13
13
|
const identifierPattern = /^[a-z][a-z0-9_]{0,62}$/u;
|
|
14
|
+
const storageBucketPattern = /^[a-z0-9][a-z0-9.-]{0,99}$/u;
|
|
14
15
|
|
|
15
16
|
const resolveProjectInput = (projectRoot, value, label) => {
|
|
16
17
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -37,6 +38,19 @@ const pathExists = (path) =>
|
|
|
37
38
|
throw error;
|
|
38
39
|
});
|
|
39
40
|
|
|
41
|
+
const assertRegularInput = async (path, label) => {
|
|
42
|
+
let stats;
|
|
43
|
+
try {
|
|
44
|
+
stats = await lstat(path);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error?.code === "ENOENT") throw new Error(`${label} does not exist.`);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
50
|
+
throw new Error(`${label} must be a regular project-local file.`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
40
54
|
const inspectSyntheticRecords = async (path) => {
|
|
41
55
|
const tables = new Map();
|
|
42
56
|
let rowCount = 0;
|
|
@@ -97,6 +111,120 @@ const inspectSyntheticRecords = async (path) => {
|
|
|
97
111
|
};
|
|
98
112
|
};
|
|
99
113
|
|
|
114
|
+
const inspectAssetManifest = async (path, projectRoot) => {
|
|
115
|
+
let manifest;
|
|
116
|
+
try {
|
|
117
|
+
manifest = JSON.parse(await readFile(path, "utf8"));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new Error("Storage asset manifest contains invalid JSON.", {
|
|
120
|
+
cause: error,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (!Array.isArray(manifest)) {
|
|
124
|
+
throw new Error("Storage asset manifest must be a JSON array.");
|
|
125
|
+
}
|
|
126
|
+
const destinations = new Set();
|
|
127
|
+
for (const [index, asset] of manifest.entries()) {
|
|
128
|
+
if (
|
|
129
|
+
!asset ||
|
|
130
|
+
typeof asset !== "object" ||
|
|
131
|
+
Array.isArray(asset) ||
|
|
132
|
+
!storageBucketPattern.test(asset.bucket ?? "") ||
|
|
133
|
+
typeof asset.objectPath !== "string" ||
|
|
134
|
+
!asset.objectPath ||
|
|
135
|
+
asset.objectPath
|
|
136
|
+
.split("/")
|
|
137
|
+
.some((segment) => !segment || segment === "." || segment === "..") ||
|
|
138
|
+
typeof asset.file !== "string" ||
|
|
139
|
+
!asset.file.trim() ||
|
|
140
|
+
(asset.contentType !== undefined && typeof asset.contentType !== "string")
|
|
141
|
+
) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Storage asset manifest entry ${index + 1} has an invalid shape.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const destination = `${asset.bucket}/${asset.objectPath}`;
|
|
147
|
+
if (destinations.has(destination)) {
|
|
148
|
+
throw new Error(`Duplicate Storage asset destination: ${destination}.`);
|
|
149
|
+
}
|
|
150
|
+
destinations.add(destination);
|
|
151
|
+
const assetFile = resolveProjectInput(
|
|
152
|
+
projectRoot,
|
|
153
|
+
asset.file,
|
|
154
|
+
`Storage asset file in entry ${index + 1}`,
|
|
155
|
+
);
|
|
156
|
+
await assertRegularInput(
|
|
157
|
+
assetFile,
|
|
158
|
+
`Storage asset file in entry ${index + 1}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return { assetCount: manifest.length };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const inspectBaselineInputFiles = async ({
|
|
165
|
+
projectRoot = process.cwd(),
|
|
166
|
+
configPath,
|
|
167
|
+
recordsPath,
|
|
168
|
+
ledgerPath,
|
|
169
|
+
assetsPath,
|
|
170
|
+
}) => {
|
|
171
|
+
const loaded = await loadRehearsalConfig({ projectRoot, configPath });
|
|
172
|
+
const records = resolveProjectInput(
|
|
173
|
+
loaded.projectRoot,
|
|
174
|
+
recordsPath,
|
|
175
|
+
"Synthetic records path",
|
|
176
|
+
);
|
|
177
|
+
const ledger = resolveProjectInput(
|
|
178
|
+
loaded.projectRoot,
|
|
179
|
+
ledgerPath,
|
|
180
|
+
"Migration ledger path",
|
|
181
|
+
);
|
|
182
|
+
const assets = assetsPath
|
|
183
|
+
? resolveProjectInput(
|
|
184
|
+
loaded.projectRoot,
|
|
185
|
+
assetsPath,
|
|
186
|
+
"Storage asset manifest path",
|
|
187
|
+
)
|
|
188
|
+
: undefined;
|
|
189
|
+
await Promise.all([
|
|
190
|
+
assertRegularInput(records, "Synthetic records path"),
|
|
191
|
+
assertRegularInput(ledger, "Migration ledger path"),
|
|
192
|
+
...(assets
|
|
193
|
+
? [assertRegularInput(assets, "Storage asset manifest path")]
|
|
194
|
+
: []),
|
|
195
|
+
]);
|
|
196
|
+
let ledgerRows;
|
|
197
|
+
try {
|
|
198
|
+
ledgerRows = JSON.parse(await readFile(ledger, "utf8"));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new Error("Migration ledger contains invalid JSON.", {
|
|
201
|
+
cause: error,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const [inspection, assetInspection] = await Promise.all([
|
|
205
|
+
inspectSyntheticRecords(records),
|
|
206
|
+
assets
|
|
207
|
+
? inspectAssetManifest(assets, loaded.projectRoot)
|
|
208
|
+
: Promise.resolve({ assetCount: 0 }),
|
|
209
|
+
]);
|
|
210
|
+
const migrationHistory = buildMigrationLedgerInventory(
|
|
211
|
+
ledgerRows,
|
|
212
|
+
"Synthetic migration ledger",
|
|
213
|
+
);
|
|
214
|
+
return {
|
|
215
|
+
projectRoot: loaded.projectRoot,
|
|
216
|
+
recordsPath: relative(loaded.projectRoot, records),
|
|
217
|
+
ledgerPath: relative(loaded.projectRoot, ledger),
|
|
218
|
+
assetsPath: assets ? relative(loaded.projectRoot, assets) : undefined,
|
|
219
|
+
migrationHistory,
|
|
220
|
+
migrationCutoff: migrationHistory.at(-1).version,
|
|
221
|
+
migrationCount: migrationHistory.length,
|
|
222
|
+
rowCount: inspection.rowCount,
|
|
223
|
+
tables: inspection.tables,
|
|
224
|
+
assetCount: assetInspection.assetCount,
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
100
228
|
const renderPolicyDraft = ({ migrationCutoff, tables }) =>
|
|
101
229
|
`${JSON.stringify(
|
|
102
230
|
{
|
|
@@ -127,43 +255,30 @@ export const planBaselinePreparation = async ({
|
|
|
127
255
|
ledgerPath,
|
|
128
256
|
}) => {
|
|
129
257
|
const loaded = await loadRehearsalConfig({ projectRoot, configPath });
|
|
130
|
-
const
|
|
131
|
-
loaded.projectRoot,
|
|
258
|
+
const inspection = await inspectBaselineInputFiles({
|
|
259
|
+
projectRoot: loaded.projectRoot,
|
|
260
|
+
configPath,
|
|
132
261
|
recordsPath,
|
|
133
|
-
"Synthetic records path",
|
|
134
|
-
);
|
|
135
|
-
const ledger = resolveProjectInput(
|
|
136
|
-
loaded.projectRoot,
|
|
137
262
|
ledgerPath,
|
|
138
|
-
|
|
139
|
-
);
|
|
140
|
-
const [inspection, ledgerRows] = await Promise.all([
|
|
141
|
-
inspectSyntheticRecords(records),
|
|
142
|
-
readFile(ledger, "utf8").then(JSON.parse),
|
|
143
|
-
]);
|
|
144
|
-
const migrationHistory = buildMigrationLedgerInventory(
|
|
145
|
-
ledgerRows,
|
|
146
|
-
"Synthetic migration ledger",
|
|
147
|
-
);
|
|
263
|
+
});
|
|
148
264
|
const destination = loaded.paths.sanitizationPolicy;
|
|
149
265
|
if (await pathExists(destination)) {
|
|
150
266
|
throw new Error(
|
|
151
267
|
`Rehearsal baseline preparation will not overwrite ${relative(loaded.projectRoot, destination)}.`,
|
|
152
268
|
);
|
|
153
269
|
}
|
|
154
|
-
const migrationCutoff = migrationHistory.at(-1).version;
|
|
155
270
|
return {
|
|
156
271
|
projectRoot: loaded.projectRoot,
|
|
157
272
|
destination,
|
|
158
273
|
destinationRelative: relative(loaded.projectRoot, destination),
|
|
159
|
-
recordsPath:
|
|
160
|
-
ledgerPath:
|
|
161
|
-
migrationCutoff,
|
|
162
|
-
migrationCount:
|
|
274
|
+
recordsPath: inspection.recordsPath,
|
|
275
|
+
ledgerPath: inspection.ledgerPath,
|
|
276
|
+
migrationCutoff: inspection.migrationCutoff,
|
|
277
|
+
migrationCount: inspection.migrationCount,
|
|
163
278
|
rowCount: inspection.rowCount,
|
|
164
279
|
tables: inspection.tables,
|
|
165
280
|
content: renderPolicyDraft({
|
|
166
|
-
migrationCutoff,
|
|
281
|
+
migrationCutoff: inspection.migrationCutoff,
|
|
167
282
|
tables: inspection.tables,
|
|
168
283
|
}),
|
|
169
284
|
};
|
|
@@ -3,7 +3,7 @@ export type RehearsalConfigVersion = 1;
|
|
|
3
3
|
export interface RehearsalConfig {
|
|
4
4
|
schemaVersion: RehearsalConfigVersion;
|
|
5
5
|
project: { name: string };
|
|
6
|
-
supabase
|
|
6
|
+
supabase?: {
|
|
7
7
|
workdir: string;
|
|
8
8
|
migrationDirectory: string;
|
|
9
9
|
rehearsalConfig: string;
|
|
@@ -11,6 +11,13 @@ export interface RehearsalConfig {
|
|
|
11
11
|
serviceEnvironmentFile?: string;
|
|
12
12
|
serviceEnvironmentVariables?: string[];
|
|
13
13
|
};
|
|
14
|
+
postgresql?: {
|
|
15
|
+
migrationDirectory: string;
|
|
16
|
+
runtimeWorkdir?: string;
|
|
17
|
+
image?: string;
|
|
18
|
+
database?: string;
|
|
19
|
+
user?: string;
|
|
20
|
+
};
|
|
14
21
|
baseline: {
|
|
15
22
|
artifactDirectory?: string;
|
|
16
23
|
sanitizationPolicy: string;
|
|
@@ -22,11 +29,12 @@ export interface RehearsalConfig {
|
|
|
22
29
|
runtimeAdapter?: string;
|
|
23
30
|
};
|
|
24
31
|
runtime: {
|
|
32
|
+
target?: "supabase" | "postgresql";
|
|
25
33
|
applicationUrl?: string;
|
|
26
34
|
projectId?: string;
|
|
27
|
-
apiPort
|
|
35
|
+
apiPort?: number;
|
|
28
36
|
databasePort: number;
|
|
29
|
-
studioPort
|
|
37
|
+
studioPort?: number;
|
|
30
38
|
};
|
|
31
39
|
safety?: {
|
|
32
40
|
allowedHosts?: string[];
|
|
@@ -58,6 +66,13 @@ export declare const renderDetectedConfig: (
|
|
|
58
66
|
ports?: { api: number; database: number; studio: number };
|
|
59
67
|
},
|
|
60
68
|
) => string;
|
|
69
|
+
export declare const renderDetectedPostgresqlConfig: (
|
|
70
|
+
detected: unknown,
|
|
71
|
+
options?: {
|
|
72
|
+
applicationUrl?: string;
|
|
73
|
+
databasePort?: number;
|
|
74
|
+
},
|
|
75
|
+
) => string;
|
|
61
76
|
export declare const SANITIZATION_ACTIONS: Readonly<{
|
|
62
77
|
KEEP: "KEEP";
|
|
63
78
|
PSEUDONYMIZE: "PSEUDONYMIZE";
|