@doubledigit/cli 0.1.0 → 0.2.1

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.
Files changed (45) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +102 -0
  3. package/dist/codegen.d.ts +6 -2
  4. package/dist/codegen.d.ts.map +1 -1
  5. package/dist/codegen.js +81 -13
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +36 -3
  8. package/dist/commands/create.js +2 -2
  9. package/dist/commands/db.d.ts.map +1 -1
  10. package/dist/commands/db.js +24 -11
  11. package/dist/commands/doctor.d.ts.map +1 -1
  12. package/dist/commands/doctor.js +46 -12
  13. package/dist/commands/init.d.ts +2 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +163 -0
  16. package/dist/commands/onboard.d.ts.map +1 -1
  17. package/dist/commands/onboard.js +6 -1
  18. package/dist/commands/run.d.ts.map +1 -1
  19. package/dist/commands/run.js +1 -0
  20. package/dist/commands/sync.d.ts.map +1 -1
  21. package/dist/commands/sync.js +5 -1
  22. package/dist/commands/uninstall.d.ts.map +1 -1
  23. package/dist/commands/uninstall.js +25 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +15 -5
  27. package/dist/lib/marketplace-schema.d.ts +24 -24
  28. package/dist/lib/onboarding.d.ts +3 -3
  29. package/dist/lib/onboarding.d.ts.map +1 -1
  30. package/dist/lib/onboarding.js +282 -237
  31. package/dist/lib/package-entry.d.ts +6 -0
  32. package/dist/lib/package-entry.d.ts.map +1 -0
  33. package/dist/lib/package-entry.js +63 -0
  34. package/dist/lib/scoped-migrations.d.ts +11 -0
  35. package/dist/lib/scoped-migrations.d.ts.map +1 -0
  36. package/dist/lib/scoped-migrations.js +156 -0
  37. package/dist/lib/validators.d.ts.map +1 -1
  38. package/dist/lib/validators.js +12 -49
  39. package/dist/paths.d.ts +4 -0
  40. package/dist/paths.d.ts.map +1 -1
  41. package/dist/paths.js +2 -0
  42. package/dist/scanner.d.ts +4 -0
  43. package/dist/scanner.d.ts.map +1 -1
  44. package/dist/scanner.js +21 -0
  45. package/package.json +12 -5
@@ -1,28 +1,29 @@
1
- import fs from 'node:fs';
2
- import net from 'node:net';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { randomBytes } from 'node:crypto';
6
- import { execFileSync, spawn } from 'node:child_process';
7
- import { createRequire } from 'node:module';
8
- import { createInterface } from 'node:readline/promises';
9
- import { pathToFileURL } from 'node:url';
10
- export const DEFAULT_DATABASE_URL = 'postgresql://doubledigit:doubledigit@localhost:5432/doubledigit';
11
- export const DEFAULT_APP_URL = 'http://localhost:3000';
1
+ import fs from "node:fs";
2
+ import net from "node:net";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { randomBytes } from "node:crypto";
6
+ import { execFileSync, spawn } from "node:child_process";
7
+ import { createRequire } from "node:module";
8
+ import { createInterface } from "node:readline/promises";
9
+ import { pathToFileURL } from "node:url";
10
+ export const DEFAULT_DATABASE_URL = "postgresql://doubledigit:doubledigit@localhost:5432/doubledigit";
12
11
  export const DEFAULT_EMBEDDED_POSTGRES_PORT = 54329;
13
- export const DEFAULT_EMBEDDED_POSTGRES_INSTANCE_ID = 'default';
14
- export const DEFAULT_EMBEDDED_POSTGRES_HOME = path.join(os.homedir(), '.doubledigit');
15
- export const DEFAULT_EMBEDDED_POSTGRES_DATA_DIR = path.join(DEFAULT_EMBEDDED_POSTGRES_HOME, 'instances', DEFAULT_EMBEDDED_POSTGRES_INSTANCE_ID, 'db');
12
+ export const DEFAULT_EMBEDDED_POSTGRES_INSTANCE_ID = "default";
13
+ export const DEFAULT_EMBEDDED_POSTGRES_HOME = path.join(os.homedir(), ".doubledigit");
14
+ export const DEFAULT_EMBEDDED_POSTGRES_DATA_DIR = path.join(DEFAULT_EMBEDDED_POSTGRES_HOME, "instances", DEFAULT_EMBEDDED_POSTGRES_INSTANCE_ID, "db");
16
15
  const DEFAULT_DOCKER_POSTGRES_PORT = 5432;
17
16
  const EMBEDDED_POSTGRES_LOOKAHEAD = 20;
18
- const EMBEDDED_POSTGRES_USER = 'doubledigit';
19
- const EMBEDDED_POSTGRES_PASSWORD = 'doubledigit';
20
- const EMBEDDED_POSTGRES_DATABASE = 'doubledigit';
17
+ const EMBEDDED_POSTGRES_USER = "doubledigit";
18
+ const EMBEDDED_POSTGRES_PASSWORD = "doubledigit";
19
+ const EMBEDDED_POSTGRES_DATABASE = "doubledigit";
21
20
  const EMBEDDED_LOG_BUFFER_LIMIT = 40;
22
- const DEFAULT_APP_PORT = 3000;
21
+ const DEFAULT_APP_PORT = 3111;
23
22
  const APP_PORT_LOOKAHEAD = 20;
24
23
  const INITIAL_DATABASE_URL = process.env.DATABASE_URL?.trim();
25
24
  const INITIAL_DATABASE_MODE = process.env.DD_DATABASE_MODE?.trim();
25
+ const buildAppUrl = (port) => `http://localhost:${port}`;
26
+ export const DEFAULT_APP_URL = buildAppUrl(DEFAULT_APP_PORT);
26
27
  /**
27
28
  * Snapshot of shell-originated environment at module load time.
28
29
  * Values present here must never be overwritten by .env file loading —
@@ -35,24 +36,24 @@ for (const key of Object.keys(process.env)) {
35
36
  }
36
37
  }
37
38
  const REQUIRED_ENV_KEYS = [
38
- 'DATABASE_URL',
39
- 'PAYLOAD_SECRET',
40
- 'BETTER_AUTH_SECRET',
41
- 'BETTER_AUTH_URL',
39
+ "DATABASE_URL",
40
+ "PAYLOAD_SECRET",
41
+ "BETTER_AUTH_SECRET",
42
+ "BETTER_AUTH_URL",
42
43
  ];
43
44
  export function getEnvPath(paths) {
44
- return path.join(paths.mainAppDir, '.env');
45
+ return path.join(paths.mainAppDir, ".env");
45
46
  }
46
47
  export function getEnvExamplePath(paths) {
47
- return path.join(paths.root, '.env.example');
48
+ return path.join(paths.root, ".env.example");
48
49
  }
49
50
  export function getGeneratedTypesPath(paths) {
50
- return path.join(paths.root, 'packages', 'shared', 'src', 'types', 'payload-types.ts');
51
+ return path.join(paths.root, "packages", "shared", "src", "types", "payload-types.ts");
51
52
  }
52
53
  export function commandExists(command) {
53
- const lookup = process.platform === 'win32' ? 'where' : 'which';
54
+ const lookup = process.platform === "win32" ? "where" : "which";
54
55
  try {
55
- execFileSync(lookup, [command], { stdio: 'pipe' });
56
+ execFileSync(lookup, [command], { stdio: "pipe" });
56
57
  return true;
57
58
  }
58
59
  catch {
@@ -60,11 +61,11 @@ export function commandExists(command) {
60
61
  }
61
62
  }
62
63
  export function dockerAvailable() {
63
- if (!commandExists('docker')) {
64
+ if (!commandExists("docker")) {
64
65
  return false;
65
66
  }
66
67
  try {
67
- execFileSync('docker', ['info'], { stdio: 'pipe' });
68
+ execFileSync("docker", ["info"], { stdio: "pipe" });
68
69
  return true;
69
70
  }
70
71
  catch {
@@ -75,7 +76,7 @@ export function runChecked(command, args, cwd, label, envOverrides = {}) {
75
76
  try {
76
77
  execFileSync(command, args, {
77
78
  cwd,
78
- stdio: 'inherit',
79
+ stdio: "inherit",
79
80
  env: {
80
81
  ...process.env,
81
82
  ...envOverrides,
@@ -90,8 +91,8 @@ export function captureCommand(command, args, cwd, envOverrides = {}) {
90
91
  try {
91
92
  const output = execFileSync(command, args, {
92
93
  cwd,
93
- stdio: 'pipe',
94
- encoding: 'utf8',
94
+ stdio: "pipe",
95
+ encoding: "utf8",
95
96
  env: {
96
97
  ...process.env,
97
98
  ...envOverrides,
@@ -100,15 +101,15 @@ export function captureCommand(command, args, cwd, envOverrides = {}) {
100
101
  return { ok: true, output };
101
102
  }
102
103
  catch (error) {
103
- const stdout = typeof error.stdout === 'string'
104
+ const stdout = typeof error.stdout === "string"
104
105
  ? error.stdout
105
- : '';
106
- const stderr = typeof error.stderr === 'string'
106
+ : "";
107
+ const stderr = typeof error.stderr === "string"
107
108
  ? error.stderr
108
- : '';
109
+ : "";
109
110
  return {
110
111
  ok: false,
111
- output: [stdout, stderr].filter(Boolean).join('\n').trim(),
112
+ output: [stdout, stderr].filter(Boolean).join("\n").trim(),
112
113
  };
113
114
  }
114
115
  }
@@ -117,13 +118,13 @@ export function readEnvFile(filePath) {
117
118
  return {};
118
119
  }
119
120
  const result = {};
120
- const content = fs.readFileSync(filePath, 'utf-8');
121
+ const content = fs.readFileSync(filePath, "utf-8");
121
122
  for (const line of content.split(/\r?\n/)) {
122
123
  const trimmed = line.trim();
123
- if (!trimmed || trimmed.startsWith('#')) {
124
+ if (!trimmed || trimmed.startsWith("#")) {
124
125
  continue;
125
126
  }
126
- const separator = trimmed.indexOf('=');
127
+ const separator = trimmed.indexOf("=");
127
128
  if (separator === -1) {
128
129
  continue;
129
130
  }
@@ -134,10 +135,10 @@ export function readEnvFile(filePath) {
134
135
  return result;
135
136
  }
136
137
  function escapeRegExp(value) {
137
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
138
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
138
139
  }
139
140
  function upsertEnvValue(content, key, value) {
140
- const pattern = new RegExp(`^${escapeRegExp(key)}=.*$`, 'm');
141
+ const pattern = new RegExp(`^${escapeRegExp(key)}=.*$`, "m");
141
142
  if (pattern.test(content)) {
142
143
  return content.replace(pattern, `${key}=${value}`);
143
144
  }
@@ -145,11 +146,11 @@ function upsertEnvValue(content, key, value) {
145
146
  return `${trimmed}\n${key}=${value}\n`;
146
147
  }
147
148
  function removeEnvValue(content, key) {
148
- const pattern = new RegExp(`^${escapeRegExp(key)}=.*(?:\\r?\\n)?`, 'm');
149
- return content.replace(pattern, '');
149
+ const pattern = new RegExp(`^${escapeRegExp(key)}=.*(?:\\r?\\n)?`, "m");
150
+ return content.replace(pattern, "");
150
151
  }
151
152
  function generateSecret() {
152
- return randomBytes(32).toString('base64');
153
+ return randomBytes(32).toString("base64");
153
154
  }
154
155
  function buildManagedDatabaseUrl(port, database = EMBEDDED_POSTGRES_DATABASE) {
155
156
  return `postgresql://${EMBEDDED_POSTGRES_USER}:${EMBEDDED_POSTGRES_PASSWORD}@127.0.0.1:${port}/${database}`;
@@ -165,22 +166,25 @@ function parsePositiveInt(value) {
165
166
  return parsed;
166
167
  }
167
168
  function expandHomePrefix(value) {
168
- if (value === '~') {
169
+ if (value === "~") {
169
170
  return os.homedir();
170
171
  }
171
- if (value.startsWith('~/')) {
172
+ if (value.startsWith("~/")) {
172
173
  return path.resolve(os.homedir(), value.slice(2));
173
174
  }
174
175
  return value;
175
176
  }
176
177
  function resolveEmbeddedDataDir(env) {
177
- return path.resolve(expandHomePrefix(env.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim() || DEFAULT_EMBEDDED_POSTGRES_DATA_DIR));
178
+ return path.resolve(expandHomePrefix(env.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim() ||
179
+ DEFAULT_EMBEDDED_POSTGRES_DATA_DIR));
178
180
  }
179
181
  function resolveEmbeddedPort(env) {
180
- return parsePositiveInt(env.DD_EMBEDDED_POSTGRES_PORT) || DEFAULT_EMBEDDED_POSTGRES_PORT;
182
+ return (parsePositiveInt(env.DD_EMBEDDED_POSTGRES_PORT) ||
183
+ DEFAULT_EMBEDDED_POSTGRES_PORT);
181
184
  }
182
185
  function resolveDockerPort(env) {
183
- return parsePositiveInt(env.DD_DOCKER_POSTGRES_PORT) || DEFAULT_DOCKER_POSTGRES_PORT;
186
+ return (parsePositiveInt(env.DD_DOCKER_POSTGRES_PORT) ||
187
+ DEFAULT_DOCKER_POSTGRES_PORT);
184
188
  }
185
189
  function applyEnvToProcess(env) {
186
190
  for (const [key, value] of Object.entries(env)) {
@@ -197,32 +201,32 @@ function isLegacyLocalDatabaseUrl(value) {
197
201
  return value?.trim() === DEFAULT_DATABASE_URL;
198
202
  }
199
203
  function isLoopbackHost(host) {
200
- return host === '127.0.0.1' || host === 'localhost' || host === '::1';
204
+ return host === "127.0.0.1" || host === "localhost" || host === "::1";
201
205
  }
202
206
  function hasEmbeddedRuntimeHints(env) {
203
- return Boolean(env.DD_EMBEDDED_POSTGRES_PORT?.trim()
204
- || env.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim());
207
+ return Boolean(env.DD_EMBEDDED_POSTGRES_PORT?.trim() ||
208
+ env.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim());
205
209
  }
206
210
  function hasDockerRuntimeHints(env) {
207
- return Boolean(env.DD_DATABASE_MODE?.trim() === 'docker'
208
- || env.DD_DOCKER_POSTGRES_PORT?.trim());
211
+ return Boolean(env.DD_DATABASE_MODE?.trim() === "docker" ||
212
+ env.DD_DOCKER_POSTGRES_PORT?.trim());
209
213
  }
210
214
  function canonicalManagedDatabaseUrl(databaseUrl) {
211
215
  try {
212
216
  const url = new URL(databaseUrl);
213
- if (url.protocol !== 'postgresql:' && url.protocol !== 'postgres:') {
217
+ if (url.protocol !== "postgresql:" && url.protocol !== "postgres:") {
214
218
  return undefined;
215
219
  }
216
220
  if (!isLoopbackHost(url.hostname) || url.search || url.hash) {
217
221
  return undefined;
218
222
  }
219
223
  const port = parsePositiveInt(url.port) || DEFAULT_DOCKER_POSTGRES_PORT;
220
- const database = url.pathname.replace(/^\/+/, '');
224
+ const database = url.pathname.replace(/^\/+/, "");
221
225
  const username = decodeURIComponent(url.username);
222
226
  const password = decodeURIComponent(url.password);
223
- if (username !== EMBEDDED_POSTGRES_USER
224
- || password !== EMBEDDED_POSTGRES_PASSWORD
225
- || database !== EMBEDDED_POSTGRES_DATABASE) {
227
+ if (username !== EMBEDDED_POSTGRES_USER ||
228
+ password !== EMBEDDED_POSTGRES_PASSWORD ||
229
+ database !== EMBEDDED_POSTGRES_DATABASE) {
226
230
  return undefined;
227
231
  }
228
232
  return buildManagedDatabaseUrl(port, database);
@@ -235,26 +239,28 @@ function isEmbeddedManagedDatabaseUrl(databaseUrl, env) {
235
239
  if (!databaseUrl || !hasEmbeddedRuntimeHints(env)) {
236
240
  return false;
237
241
  }
238
- return canonicalManagedDatabaseUrl(databaseUrl) === buildManagedDatabaseUrl(resolveEmbeddedPort(env));
242
+ return (canonicalManagedDatabaseUrl(databaseUrl) ===
243
+ buildManagedDatabaseUrl(resolveEmbeddedPort(env)));
239
244
  }
240
245
  function isDockerManagedDatabaseUrl(databaseUrl, env) {
241
246
  if (!databaseUrl || !hasDockerRuntimeHints(env)) {
242
247
  return false;
243
248
  }
244
- return canonicalManagedDatabaseUrl(databaseUrl) === buildManagedDatabaseUrl(resolveDockerPort(env));
249
+ return (canonicalManagedDatabaseUrl(databaseUrl) ===
250
+ buildManagedDatabaseUrl(resolveDockerPort(env)));
245
251
  }
246
252
  function hasExplicitExternalDatabaseUrl(databaseUrl, env) {
247
253
  const trimmed = databaseUrl?.trim();
248
- return Boolean(trimmed
249
- && !isLegacyLocalDatabaseUrl(trimmed)
250
- && !isEmbeddedManagedDatabaseUrl(trimmed, env)
251
- && !isDockerManagedDatabaseUrl(trimmed, env));
254
+ return Boolean(trimmed &&
255
+ !isLegacyLocalDatabaseUrl(trimmed) &&
256
+ !isEmbeddedManagedDatabaseUrl(trimmed, env) &&
257
+ !isDockerManagedDatabaseUrl(trimmed, env));
252
258
  }
253
259
  export function isPlaceholderValue(key, value) {
254
260
  if (!value) {
255
261
  return true;
256
262
  }
257
- if (value.includes('change-me')) {
263
+ if (value.includes("change-me")) {
258
264
  return true;
259
265
  }
260
266
  return false;
@@ -271,52 +277,56 @@ export function ensureLocalEnv(paths, options = {}) {
271
277
  fs.copyFileSync(envExamplePath, envPath);
272
278
  created = true;
273
279
  }
274
- let content = fs.readFileSync(envPath, 'utf-8');
280
+ let content = fs.readFileSync(envPath, "utf-8");
275
281
  const current = readEnvFile(envPath);
276
282
  const updates = {};
277
283
  const removals = new Set();
278
284
  const currentDatabaseMode = current.DD_DATABASE_MODE?.trim();
279
285
  const currentDatabaseUrl = current.DATABASE_URL?.trim();
280
- if (!options.databaseMode && currentDatabaseMode === 'external') {
281
- removals.add('DD_DATABASE_MODE');
282
- }
283
- if (!options.databaseUrl
284
- && (isEmbeddedManagedDatabaseUrl(currentDatabaseUrl, current)
285
- || isDockerManagedDatabaseUrl(currentDatabaseUrl, current))) {
286
- removals.add('DATABASE_URL');
287
- }
288
- if (!options.databaseMode
289
- && isEmbeddedManagedDatabaseUrl(current.DATABASE_URL, current)
290
- && currentDatabaseMode !== 'embedded') {
291
- updates.DD_DATABASE_MODE = 'embedded';
292
- }
293
- if (!options.databaseMode
294
- && currentDatabaseMode
295
- && currentDatabaseMode !== 'external'
296
- && hasExplicitExternalDatabaseUrl(current.DATABASE_URL, current)) {
297
- removals.add('DD_DATABASE_MODE');
298
- }
299
- const normalizedCurrentDatabaseMode = removals.has('DD_DATABASE_MODE')
286
+ if (!options.databaseMode && currentDatabaseMode === "external") {
287
+ removals.add("DD_DATABASE_MODE");
288
+ }
289
+ if (!options.databaseUrl &&
290
+ (isEmbeddedManagedDatabaseUrl(currentDatabaseUrl, current) ||
291
+ isDockerManagedDatabaseUrl(currentDatabaseUrl, current))) {
292
+ removals.add("DATABASE_URL");
293
+ }
294
+ if (!options.databaseMode &&
295
+ isEmbeddedManagedDatabaseUrl(current.DATABASE_URL, current) &&
296
+ currentDatabaseMode !== "embedded") {
297
+ updates.DD_DATABASE_MODE = "embedded";
298
+ }
299
+ if (!options.databaseMode &&
300
+ currentDatabaseMode &&
301
+ currentDatabaseMode !== "external" &&
302
+ hasExplicitExternalDatabaseUrl(current.DATABASE_URL, current)) {
303
+ removals.add("DD_DATABASE_MODE");
304
+ }
305
+ const normalizedCurrentDatabaseMode = removals.has("DD_DATABASE_MODE")
300
306
  ? undefined
301
307
  : currentDatabaseMode;
302
- const effectiveDatabaseMode = options.databaseMode || updates.DD_DATABASE_MODE || normalizedCurrentDatabaseMode;
303
- if (isPlaceholderValue('PAYLOAD_SECRET', current.PAYLOAD_SECRET)) {
308
+ const effectiveDatabaseMode = options.databaseMode ||
309
+ updates.DD_DATABASE_MODE ||
310
+ normalizedCurrentDatabaseMode;
311
+ if (isPlaceholderValue("PAYLOAD_SECRET", current.PAYLOAD_SECRET)) {
304
312
  updates.PAYLOAD_SECRET = generateSecret();
305
313
  }
306
- if (isPlaceholderValue('BETTER_AUTH_SECRET', current.BETTER_AUTH_SECRET)) {
314
+ if (isPlaceholderValue("BETTER_AUTH_SECRET", current.BETTER_AUTH_SECRET)) {
307
315
  updates.BETTER_AUTH_SECRET = generateSecret();
308
316
  }
309
- if (isPlaceholderValue('BETTER_AUTH_URL', current.BETTER_AUTH_URL)) {
317
+ if (isPlaceholderValue("BETTER_AUTH_URL", current.BETTER_AUTH_URL)) {
310
318
  updates.BETTER_AUTH_URL = DEFAULT_APP_URL;
311
319
  }
312
- if (!effectiveDatabaseMode && (!current.DATABASE_URL?.trim() || isLegacyLocalDatabaseUrl(current.DATABASE_URL))) {
313
- updates.DD_DATABASE_MODE = 'embedded';
320
+ if (!effectiveDatabaseMode &&
321
+ (!current.DATABASE_URL?.trim() ||
322
+ isLegacyLocalDatabaseUrl(current.DATABASE_URL))) {
323
+ updates.DD_DATABASE_MODE = "embedded";
314
324
  }
315
325
  if (options.databaseMode) {
316
326
  updates.DD_DATABASE_MODE = options.databaseMode;
317
327
  }
318
328
  const resolvedDatabaseMode = options.databaseMode || updates.DD_DATABASE_MODE || currentDatabaseMode;
319
- if (resolvedDatabaseMode === 'embedded') {
329
+ if (resolvedDatabaseMode === "embedded") {
320
330
  if (options.embeddedPort) {
321
331
  updates.DD_EMBEDDED_POSTGRES_PORT = String(options.embeddedPort);
322
332
  }
@@ -327,10 +337,11 @@ export function ensureLocalEnv(paths, options = {}) {
327
337
  updates.DD_EMBEDDED_POSTGRES_DATA_DIR = options.embeddedDataDir;
328
338
  }
329
339
  else if (!current.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim()) {
330
- updates.DD_EMBEDDED_POSTGRES_DATA_DIR = DEFAULT_EMBEDDED_POSTGRES_DATA_DIR;
340
+ updates.DD_EMBEDDED_POSTGRES_DATA_DIR =
341
+ DEFAULT_EMBEDDED_POSTGRES_DATA_DIR;
331
342
  }
332
343
  }
333
- if (resolvedDatabaseMode === 'docker') {
344
+ if (resolvedDatabaseMode === "docker") {
334
345
  if (options.dockerPort) {
335
346
  updates.DD_DOCKER_POSTGRES_PORT = String(options.dockerPort);
336
347
  }
@@ -347,8 +358,8 @@ export function ensureLocalEnv(paths, options = {}) {
347
358
  for (const [key, value] of Object.entries(updates)) {
348
359
  content = upsertEnvValue(content, key, value);
349
360
  }
350
- if (Object.keys(updates).length > 0) {
351
- fs.writeFileSync(envPath, content, 'utf-8');
361
+ if (Object.keys(updates).length > 0 || removals.size > 0) {
362
+ fs.writeFileSync(envPath, content, "utf-8");
352
363
  }
353
364
  const env = readEnvFile(envPath);
354
365
  applyEnvToProcess(env);
@@ -368,12 +379,12 @@ export function inspectLocalEnv(paths) {
368
379
  };
369
380
  }
370
381
  export function parseDatabaseUrl(databaseUrl) {
371
- const normalized = databaseUrl.replace(/^postgres:\/\//, 'postgresql://');
382
+ const normalized = databaseUrl.replace(/^postgres:\/\//, "postgresql://");
372
383
  const parsed = new URL(normalized);
373
384
  return {
374
- host: parsed.hostname || 'localhost',
385
+ host: parsed.hostname || "localhost",
375
386
  port: parsed.port ? Number(parsed.port) : 5432,
376
- database: parsed.pathname.replace(/^\//, ''),
387
+ database: parsed.pathname.replace(/^\//, ""),
377
388
  username: parsed.username || undefined,
378
389
  };
379
390
  }
@@ -391,15 +402,15 @@ export async function checkDatabaseReachability(databaseUrl) {
391
402
  resolve(true);
392
403
  });
393
404
  socket.setTimeout(1500);
394
- socket.on('error', () => resolve(false));
395
- socket.on('timeout', () => {
405
+ socket.on("error", () => resolve(false));
406
+ socket.on("timeout", () => {
396
407
  socket.destroy();
397
408
  resolve(false);
398
409
  });
399
410
  });
400
411
  }
401
412
  export async function confirmYesNo(message) {
402
- if (!process.stdin.isTTY || process.env.CI === 'true') {
413
+ if (!process.stdin.isTTY || process.env.CI === "true") {
403
414
  return true;
404
415
  }
405
416
  const rl = createInterface({
@@ -409,7 +420,7 @@ export async function confirmYesNo(message) {
409
420
  try {
410
421
  const answer = await rl.question(`${message} [Y/n] `);
411
422
  const normalized = answer.trim().toLowerCase();
412
- return normalized === '' || normalized === 'y' || normalized === 'yes';
423
+ return normalized === "" || normalized === "y" || normalized === "yes";
413
424
  }
414
425
  finally {
415
426
  rl.close();
@@ -417,7 +428,17 @@ export async function confirmYesNo(message) {
417
428
  }
418
429
  export async function waitForDockerDatabase(paths, envOverrides = {}) {
419
430
  for (let attempt = 0; attempt < 30; attempt++) {
420
- const result = captureCommand('docker', ['compose', 'exec', 'db', 'pg_isready', '-U', 'doubledigit', '-d', 'doubledigit', '-q'], paths.root, envOverrides);
431
+ const result = captureCommand("docker", [
432
+ "compose",
433
+ "exec",
434
+ "db",
435
+ "pg_isready",
436
+ "-U",
437
+ "doubledigit",
438
+ "-d",
439
+ "doubledigit",
440
+ "-q",
441
+ ], paths.root, envOverrides);
421
442
  if (result.ok) {
422
443
  return true;
423
444
  }
@@ -426,7 +447,8 @@ export async function waitForDockerDatabase(paths, envOverrides = {}) {
426
447
  return false;
427
448
  }
428
449
  export async function startDevServer(paths, envOverrides = {}) {
429
- const preferredPort = parsePositiveInt(process.env.APP_PORT || envOverrides.APP_PORT) || DEFAULT_APP_PORT;
450
+ const preferredPort = parsePositiveInt(process.env.APP_PORT || envOverrides.APP_PORT) ||
451
+ DEFAULT_APP_PORT;
430
452
  let port;
431
453
  try {
432
454
  port = await findAvailablePort(preferredPort, APP_PORT_LOOKAHEAD);
@@ -437,44 +459,54 @@ export async function startDevServer(paths, envOverrides = {}) {
437
459
  if (port !== preferredPort) {
438
460
  console.log(`⚠ Port ${preferredPort} is busy — using port ${port} instead.`);
439
461
  }
440
- const betterAuthUrl = `http://localhost:${port}`;
441
- const child = spawn('pnpm', ['--dir', 'apps/main-app', 'exec', 'next', 'dev', '--turbo', '--port', String(port)], {
462
+ const betterAuthUrl = buildAppUrl(port);
463
+ const child = spawn("pnpm", [
464
+ "--dir",
465
+ "apps/main-app",
466
+ "exec",
467
+ "next",
468
+ "dev",
469
+ "--turbo",
470
+ "--port",
471
+ String(port),
472
+ ], {
442
473
  cwd: paths.root,
443
- stdio: 'inherit',
474
+ stdio: "inherit",
444
475
  env: {
445
476
  ...process.env,
446
477
  ...envOverrides,
447
478
  PORT: String(port),
479
+ APP_PORT: String(port),
448
480
  BETTER_AUTH_URL: betterAuthUrl,
449
481
  },
450
- shell: process.platform === 'win32',
482
+ shell: process.platform === "win32",
451
483
  });
452
- child.on('exit', (code) => {
484
+ child.on("exit", (code) => {
453
485
  process.exit(code ?? 0);
454
486
  });
455
- process.on('SIGINT', () => {
456
- child.kill('SIGINT');
487
+ process.on("SIGINT", () => {
488
+ child.kill("SIGINT");
457
489
  });
458
- process.on('SIGTERM', () => {
459
- child.kill('SIGTERM');
490
+ process.on("SIGTERM", () => {
491
+ child.kill("SIGTERM");
460
492
  });
461
493
  }
462
494
  export function printNextSteps() {
463
- console.log('\nNext steps:\n');
464
- console.log(' pnpm dev');
465
- console.log(' open http://localhost:3000');
466
- console.log(' open http://localhost:3000/admin');
467
- console.log('');
495
+ console.log("\n✅ Setup complete! Next steps:\n");
496
+ console.log(" pnpm dev # start the development server");
497
+ console.log(` open ${DEFAULT_APP_URL}`);
498
+ console.log(` open ${buildAppUrl(DEFAULT_APP_PORT)}/admin`);
499
+ console.log("");
468
500
  }
469
501
  function createEmbeddedPostgresLogBuffer(limit = EMBEDDED_LOG_BUFFER_LIMIT) {
470
502
  const recentLogs = [];
471
503
  return {
472
504
  append(message) {
473
- const text = typeof message === 'string'
505
+ const text = typeof message === "string"
474
506
  ? message
475
507
  : message instanceof Error
476
508
  ? message.message
477
- : String(message ?? '');
509
+ : String(message ?? "");
478
510
  for (const rawLine of text.split(/\r?\n/)) {
479
511
  const line = rawLine.trim();
480
512
  if (!line) {
@@ -499,29 +531,29 @@ function summarizeEmbeddedPostgresLogs(recentLogs) {
499
531
  .slice(-8)
500
532
  .map((line) => line.trim())
501
533
  .filter(Boolean)
502
- .join(' | ');
534
+ .join(" | ");
503
535
  }
504
536
  function formatEmbeddedPostgresError(error, fallbackMessage, recentLogs) {
505
537
  const base = error instanceof Error
506
538
  ? error.message
507
539
  : `${fallbackMessage}: ${String(error ?? fallbackMessage)}`;
508
540
  const parts = [base];
509
- const haystack = recentLogs.join('\n').toLowerCase();
510
- if (haystack.includes('could not create shared memory segment')) {
511
- parts.push('Embedded PostgreSQL could not allocate shared memory. Stop other local PostgreSQL servers or raise the host shared-memory limits, then retry.');
541
+ const haystack = recentLogs.join("\n").toLowerCase();
542
+ if (haystack.includes("could not create shared memory segment")) {
543
+ parts.push("Embedded PostgreSQL could not allocate shared memory. Stop other local PostgreSQL servers or raise the host shared-memory limits, then retry.");
512
544
  }
513
545
  const recentSummary = summarizeEmbeddedPostgresLogs(recentLogs);
514
546
  if (recentSummary) {
515
547
  parts.push(`Recent embedded Postgres logs: ${recentSummary}`);
516
548
  }
517
- return new Error(parts.join(' '));
549
+ return new Error(parts.join(" "));
518
550
  }
519
551
  export function resolveDatabasePreference(env) {
520
552
  const shellDatabaseUrl = INITIAL_DATABASE_URL;
521
553
  if (shellDatabaseUrl) {
522
554
  return {
523
- mode: 'external',
524
- reason: 'DATABASE_URL',
555
+ mode: "external",
556
+ reason: "DATABASE_URL",
525
557
  databaseUrl: shellDatabaseUrl,
526
558
  };
527
559
  }
@@ -531,89 +563,94 @@ export function resolveDatabasePreference(env) {
531
563
  const configuredExternalDatabaseUrl = hasExplicitExternalDatabaseUrl(configuredDatabaseUrl, env);
532
564
  if (configuredExternalDatabaseUrl) {
533
565
  return {
534
- mode: 'external',
535
- reason: 'apps/main-app/.env',
566
+ mode: "external",
567
+ reason: "apps/main-app/.env",
536
568
  databaseUrl: configuredDatabaseUrl,
537
569
  };
538
570
  }
539
- if (modeHint === 'external' && (configuredDatabaseUrl || INITIAL_DATABASE_MODE)) {
571
+ if (modeHint === "external" &&
572
+ (configuredDatabaseUrl || INITIAL_DATABASE_MODE)) {
540
573
  if (!INITIAL_DATABASE_MODE && embeddedManagedDatabaseUrl) {
541
574
  return {
542
- mode: 'embedded',
543
- reason: 'embedded-managed-url',
575
+ mode: "embedded",
576
+ reason: "embedded-managed-url",
544
577
  databaseUrl: configuredDatabaseUrl,
545
578
  };
546
579
  }
547
580
  return {
548
- mode: 'external',
549
- reason: 'DD_DATABASE_MODE',
581
+ mode: "external",
582
+ reason: "DD_DATABASE_MODE",
550
583
  databaseUrl: configuredDatabaseUrl,
551
584
  };
552
585
  }
553
- if (modeHint === 'docker') {
586
+ if (modeHint === "docker") {
554
587
  return {
555
- mode: 'docker',
556
- reason: 'DD_DATABASE_MODE',
557
- databaseUrl: configuredDatabaseUrl || buildManagedDatabaseUrl(resolveDockerPort(env)),
588
+ mode: "docker",
589
+ reason: "DD_DATABASE_MODE",
590
+ databaseUrl: configuredDatabaseUrl ||
591
+ buildManagedDatabaseUrl(resolveDockerPort(env)),
558
592
  };
559
593
  }
560
- if (modeHint === 'embedded') {
594
+ if (modeHint === "embedded") {
561
595
  return {
562
- mode: 'embedded',
563
- reason: 'DD_DATABASE_MODE',
564
- databaseUrl: configuredDatabaseUrl || buildManagedDatabaseUrl(resolveEmbeddedPort(env)),
596
+ mode: "embedded",
597
+ reason: "DD_DATABASE_MODE",
598
+ databaseUrl: configuredDatabaseUrl ||
599
+ buildManagedDatabaseUrl(resolveEmbeddedPort(env)),
565
600
  };
566
601
  }
567
602
  if (embeddedManagedDatabaseUrl) {
568
603
  return {
569
- mode: 'embedded',
570
- reason: 'embedded-managed-url',
604
+ mode: "embedded",
605
+ reason: "embedded-managed-url",
571
606
  databaseUrl: configuredDatabaseUrl,
572
607
  };
573
608
  }
574
- if (configuredDatabaseUrl && !isLegacyLocalDatabaseUrl(configuredDatabaseUrl)) {
609
+ if (configuredDatabaseUrl &&
610
+ !isLegacyLocalDatabaseUrl(configuredDatabaseUrl)) {
575
611
  return {
576
- mode: 'external',
577
- reason: 'apps/main-app/.env',
612
+ mode: "external",
613
+ reason: "apps/main-app/.env",
578
614
  databaseUrl: configuredDatabaseUrl,
579
615
  };
580
616
  }
581
617
  return {
582
- mode: 'embedded',
583
- reason: configuredDatabaseUrl ? 'legacy-default' : 'no-database-url',
584
- databaseUrl: configuredDatabaseUrl || buildManagedDatabaseUrl(resolveEmbeddedPort(env)),
618
+ mode: "embedded",
619
+ reason: configuredDatabaseUrl ? "legacy-default" : "no-database-url",
620
+ databaseUrl: configuredDatabaseUrl ||
621
+ buildManagedDatabaseUrl(resolveEmbeddedPort(env)),
585
622
  };
586
623
  }
587
624
  function getEmbeddedPostgresPlatformPackageName() {
588
625
  switch (process.platform) {
589
- case 'darwin':
590
- if (process.arch === 'arm64') {
591
- return '@embedded-postgres/darwin-arm64';
626
+ case "darwin":
627
+ if (process.arch === "arm64") {
628
+ return "@embedded-postgres/darwin-arm64";
592
629
  }
593
- if (process.arch === 'x64') {
594
- return '@embedded-postgres/darwin-x64';
630
+ if (process.arch === "x64") {
631
+ return "@embedded-postgres/darwin-x64";
595
632
  }
596
633
  break;
597
- case 'linux':
598
- if (process.arch === 'arm') {
599
- return '@embedded-postgres/linux-arm';
634
+ case "linux":
635
+ if (process.arch === "arm") {
636
+ return "@embedded-postgres/linux-arm";
600
637
  }
601
- if (process.arch === 'arm64') {
602
- return '@embedded-postgres/linux-arm64';
638
+ if (process.arch === "arm64") {
639
+ return "@embedded-postgres/linux-arm64";
603
640
  }
604
- if (process.arch === 'ia32') {
605
- return '@embedded-postgres/linux-ia32';
641
+ if (process.arch === "ia32") {
642
+ return "@embedded-postgres/linux-ia32";
606
643
  }
607
- if (process.arch === 'ppc64') {
608
- return '@embedded-postgres/linux-ppc64';
644
+ if (process.arch === "ppc64") {
645
+ return "@embedded-postgres/linux-ppc64";
609
646
  }
610
- if (process.arch === 'x64') {
611
- return '@embedded-postgres/linux-x64';
647
+ if (process.arch === "x64") {
648
+ return "@embedded-postgres/linux-x64";
612
649
  }
613
650
  break;
614
- case 'win32':
615
- if (process.arch === 'x64') {
616
- return '@embedded-postgres/windows-x64';
651
+ case "win32":
652
+ if (process.arch === "x64") {
653
+ return "@embedded-postgres/windows-x64";
617
654
  }
618
655
  break;
619
656
  default:
@@ -623,15 +660,15 @@ function getEmbeddedPostgresPlatformPackageName() {
623
660
  }
624
661
  function hydrateEmbeddedPostgresSymlinks() {
625
662
  const resolver = createRequire(import.meta.url);
626
- const embeddedEntry = resolver.resolve('embedded-postgres');
663
+ const embeddedEntry = resolver.resolve("embedded-postgres");
627
664
  const embeddedRequire = createRequire(embeddedEntry);
628
665
  const packageEntryPath = embeddedRequire.resolve(getEmbeddedPostgresPlatformPackageName());
629
666
  const packageRoot = path.dirname(path.dirname(packageEntryPath));
630
- const symlinkFile = path.join(packageRoot, 'native', 'pg-symlinks.json');
667
+ const symlinkFile = path.join(packageRoot, "native", "pg-symlinks.json");
631
668
  if (!fs.existsSync(symlinkFile)) {
632
669
  return;
633
670
  }
634
- const symlinks = JSON.parse(fs.readFileSync(symlinkFile, 'utf8'));
671
+ const symlinks = JSON.parse(fs.readFileSync(symlinkFile, "utf8"));
635
672
  for (const { source, target } of symlinks) {
636
673
  const sourcePath = path.resolve(packageRoot, source);
637
674
  const targetPath = path.resolve(packageRoot, target);
@@ -646,11 +683,11 @@ function hydrateEmbeddedPostgresSymlinks() {
646
683
  async function loadEmbeddedPostgresBinaries() {
647
684
  hydrateEmbeddedPostgresSymlinks();
648
685
  const resolver = createRequire(import.meta.url);
649
- const embeddedEntry = resolver.resolve('embedded-postgres');
686
+ const embeddedEntry = resolver.resolve("embedded-postgres");
650
687
  const embeddedRequire = createRequire(embeddedEntry);
651
688
  const packageEntryPath = embeddedRequire.resolve(getEmbeddedPostgresPlatformPackageName());
652
689
  const packageRoot = path.dirname(path.dirname(packageEntryPath));
653
- const mod = await import(pathToFileURL(packageEntryPath).href);
690
+ const mod = (await import(pathToFileURL(packageEntryPath).href));
654
691
  return {
655
692
  packageRoot,
656
693
  initdb: mod.initdb,
@@ -659,8 +696,8 @@ async function loadEmbeddedPostgresBinaries() {
659
696
  };
660
697
  }
661
698
  async function createPgClient(connectionString) {
662
- const pgMod = await import('pg');
663
- const pg = 'default' in pgMod ? pgMod.default : pgMod;
699
+ const pgMod = await import("pg");
700
+ const pg = "default" in pgMod ? pgMod.default : pgMod;
664
701
  return new pg.Client({ connectionString });
665
702
  }
666
703
  function ensureExecutable(filePath) {
@@ -671,8 +708,10 @@ function ensureExecutable(filePath) {
671
708
  }
672
709
  }
673
710
  function createEmbeddedPasswordFile() {
674
- const passwordFile = path.join(os.tmpdir(), `dd-embedded-postgres-${randomBytes(6).toString('hex')}.txt`);
675
- fs.writeFileSync(passwordFile, `${EMBEDDED_POSTGRES_PASSWORD}\n`, { mode: 0o600 });
711
+ const passwordFile = path.join(os.tmpdir(), `dd-embedded-postgres-${randomBytes(6).toString("hex")}.txt`);
712
+ fs.writeFileSync(passwordFile, `${EMBEDDED_POSTGRES_PASSWORD}\n`, {
713
+ mode: 0o600,
714
+ });
676
715
  return passwordFile;
677
716
  }
678
717
  function appendEmbeddedOutput(logBuffer, output) {
@@ -681,9 +720,9 @@ function appendEmbeddedOutput(logBuffer, output) {
681
720
  }
682
721
  }
683
722
  function getEmbeddedPostgresLogPath(dataDir) {
684
- const logsDir = path.join(path.dirname(dataDir), 'logs');
723
+ const logsDir = path.join(path.dirname(dataDir), "logs");
685
724
  fs.mkdirSync(logsDir, { recursive: true });
686
- return path.join(logsDir, 'postgres.log');
725
+ return path.join(logsDir, "postgres.log");
687
726
  }
688
727
  async function getPostgresDataDirectory(connectionString) {
689
728
  const client = await createPgClient(connectionString);
@@ -691,7 +730,7 @@ async function getPostgresDataDirectory(connectionString) {
691
730
  try {
692
731
  const result = await client.query(`SELECT current_setting('data_directory') AS data_directory`);
693
732
  const dataDirectory = result.rows[0]?.data_directory;
694
- return typeof dataDirectory === 'string' ? dataDirectory : null;
733
+ return typeof dataDirectory === "string" ? dataDirectory : null;
695
734
  }
696
735
  finally {
697
736
  await client.end().catch(() => undefined);
@@ -701,13 +740,13 @@ async function ensurePostgresDatabase(adminConnectionString, databaseName) {
701
740
  const client = await createPgClient(adminConnectionString);
702
741
  await client.connect();
703
742
  try {
704
- const lookup = await client.query('SELECT 1 FROM pg_database WHERE datname = $1 LIMIT 1', [databaseName]);
743
+ const lookup = await client.query("SELECT 1 FROM pg_database WHERE datname = $1 LIMIT 1", [databaseName]);
705
744
  if (lookup.rowCount > 0) {
706
- return 'existing';
745
+ return "existing";
707
746
  }
708
747
  const safeDatabaseName = databaseName.replace(/"/g, '""');
709
748
  await client.query(`CREATE DATABASE "${safeDatabaseName}"`);
710
- return 'created';
749
+ return "created";
711
750
  }
712
751
  finally {
713
752
  await client.end().catch(() => undefined);
@@ -718,7 +757,7 @@ function readRunningPostmasterPid(postmasterPidFile) {
718
757
  return null;
719
758
  }
720
759
  try {
721
- const pid = Number(fs.readFileSync(postmasterPidFile, 'utf8').split('\n')[0]?.trim());
760
+ const pid = Number(fs.readFileSync(postmasterPidFile, "utf8").split("\n")[0]?.trim());
722
761
  if (!Number.isInteger(pid) || pid <= 0) {
723
762
  return null;
724
763
  }
@@ -734,7 +773,7 @@ function readPostmasterPort(postmasterPidFile) {
734
773
  return null;
735
774
  }
736
775
  try {
737
- const port = Number(fs.readFileSync(postmasterPidFile, 'utf8').split('\n')[3]?.trim());
776
+ const port = Number(fs.readFileSync(postmasterPidFile, "utf8").split("\n")[3]?.trim());
738
777
  return Number.isInteger(port) && port > 0 ? port : null;
739
778
  }
740
779
  catch {
@@ -745,10 +784,10 @@ async function isPortInUse(port) {
745
784
  return await new Promise((resolve) => {
746
785
  const server = net.createServer();
747
786
  server.unref();
748
- server.once('error', (error) => {
749
- resolve(error.code === 'EADDRINUSE');
787
+ server.once("error", (error) => {
788
+ resolve(error.code === "EADDRINUSE");
750
789
  });
751
- server.listen(port, '127.0.0.1', () => {
790
+ server.listen(port, "127.0.0.1", () => {
752
791
  server.close();
753
792
  resolve(false);
754
793
  });
@@ -767,20 +806,21 @@ async function ensureEmbeddedDatabaseRuntime(env) {
767
806
  const binaries = await loadEmbeddedPostgresBinaries();
768
807
  const dataDir = resolveEmbeddedDataDir(env);
769
808
  const preferredPort = resolveEmbeddedPort(env);
770
- const postmasterPidFile = path.join(dataDir, 'postmaster.pid');
771
- const pgVersionFile = path.join(dataDir, 'PG_VERSION');
809
+ const postmasterPidFile = path.join(dataDir, "postmaster.pid");
810
+ const pgVersionFile = path.join(dataDir, "PG_VERSION");
772
811
  const runningPid = readRunningPostmasterPid(postmasterPidFile);
773
812
  const runningPort = readPostmasterPort(postmasterPidFile);
774
- const adminUrl = buildManagedDatabaseUrl(preferredPort, 'postgres');
813
+ const adminUrl = buildManagedDatabaseUrl(preferredPort, "postgres");
775
814
  const logBuffer = createEmbeddedPostgresLogBuffer();
776
815
  fs.mkdirSync(path.dirname(dataDir), { recursive: true });
777
816
  if (!runningPid && fs.existsSync(pgVersionFile)) {
778
817
  try {
779
818
  const actualDataDir = await getPostgresDataDirectory(adminUrl);
780
- if (actualDataDir && path.resolve(actualDataDir) === path.resolve(dataDir)) {
819
+ if (actualDataDir &&
820
+ path.resolve(actualDataDir) === path.resolve(dataDir)) {
781
821
  await ensurePostgresDatabase(adminUrl, EMBEDDED_POSTGRES_DATABASE);
782
822
  return {
783
- mode: 'embedded',
823
+ mode: "embedded",
784
824
  databaseUrl: buildManagedDatabaseUrl(preferredPort),
785
825
  source: `embedded-postgres@${preferredPort}`,
786
826
  reachable: true,
@@ -795,10 +835,10 @@ async function ensureEmbeddedDatabaseRuntime(env) {
795
835
  }
796
836
  if (runningPid) {
797
837
  const port = runningPort || preferredPort;
798
- const runningAdminUrl = buildManagedDatabaseUrl(port, 'postgres');
838
+ const runningAdminUrl = buildManagedDatabaseUrl(port, "postgres");
799
839
  await ensurePostgresDatabase(runningAdminUrl, EMBEDDED_POSTGRES_DATABASE);
800
840
  return {
801
- mode: 'embedded',
841
+ mode: "embedded",
802
842
  databaseUrl: buildManagedDatabaseUrl(port),
803
843
  source: `embedded-postgres@${port}`,
804
844
  reachable: true,
@@ -814,18 +854,18 @@ async function ensureEmbeddedDatabaseRuntime(env) {
814
854
  try {
815
855
  const initResult = captureCommand(binaries.initdb, [
816
856
  `--pgdata=${dataDir}`,
817
- '--auth=password',
857
+ "--auth=password",
818
858
  `--username=${EMBEDDED_POSTGRES_USER}`,
819
859
  `--pwfile=${passwordFile}`,
820
- '--lc-messages=C',
821
- '--encoding=UTF8',
822
- '--locale=C',
860
+ "--lc-messages=C",
861
+ "--encoding=UTF8",
862
+ "--locale=C",
823
863
  ], binaries.packageRoot, {
824
- LC_MESSAGES: 'C',
864
+ LC_MESSAGES: "C",
825
865
  });
826
866
  appendEmbeddedOutput(logBuffer, initResult.output);
827
867
  if (!initResult.ok) {
828
- throw new Error(initResult.output || 'initdb failed');
868
+ throw new Error(initResult.output || "initdb failed");
829
869
  }
830
870
  }
831
871
  catch (error) {
@@ -840,23 +880,23 @@ async function ensureEmbeddedDatabaseRuntime(env) {
840
880
  }
841
881
  const logPath = getEmbeddedPostgresLogPath(dataDir);
842
882
  try {
843
- const startResult = captureCommand(binaries.pgCtl, ['-D', dataDir, '-l', logPath, '-w', 'start', '-o', `-p ${selectedPort}`], binaries.packageRoot, {
844
- LC_MESSAGES: 'C',
883
+ const startResult = captureCommand(binaries.pgCtl, ["-D", dataDir, "-l", logPath, "-w", "start", "-o", `-p ${selectedPort}`], binaries.packageRoot, {
884
+ LC_MESSAGES: "C",
845
885
  });
846
886
  appendEmbeddedOutput(logBuffer, startResult.output);
847
887
  if (fs.existsSync(logPath)) {
848
- appendEmbeddedOutput(logBuffer, fs.readFileSync(logPath, 'utf8'));
888
+ appendEmbeddedOutput(logBuffer, fs.readFileSync(logPath, "utf8"));
849
889
  }
850
890
  if (!startResult.ok) {
851
- throw new Error(startResult.output || 'pg_ctl start failed');
891
+ throw new Error(startResult.output || "pg_ctl start failed");
852
892
  }
853
893
  }
854
894
  catch (error) {
855
895
  throw formatEmbeddedPostgresError(error, `Failed to start embedded PostgreSQL on port ${selectedPort}`, logBuffer.getRecentLogs());
856
896
  }
857
- await ensurePostgresDatabase(buildManagedDatabaseUrl(selectedPort, 'postgres'), EMBEDDED_POSTGRES_DATABASE);
897
+ await ensurePostgresDatabase(buildManagedDatabaseUrl(selectedPort, "postgres"), EMBEDDED_POSTGRES_DATABASE);
858
898
  return {
859
- mode: 'embedded',
899
+ mode: "embedded",
860
900
  databaseUrl: buildManagedDatabaseUrl(selectedPort),
861
901
  source: `embedded-postgres@${selectedPort}`,
862
902
  reachable: true,
@@ -870,15 +910,15 @@ async function ensureDockerDatabaseRuntime(paths, env) {
870
910
  const envOverrides = {
871
911
  DB_PORT: String(selectedPort),
872
912
  };
873
- runChecked('docker', ['compose', 'up', '-d', 'db'], paths.root, 'Docker db startup', envOverrides);
913
+ runChecked("docker", ["compose", "up", "-d", "db"], paths.root, "Docker db startup", envOverrides);
874
914
  const ready = await waitForDockerDatabase(paths, envOverrides);
875
915
  if (!ready) {
876
- throw new Error('Docker PostgreSQL did not become ready in time.');
916
+ throw new Error("Docker PostgreSQL did not become ready in time.");
877
917
  }
878
918
  return {
879
- mode: 'docker',
919
+ mode: "docker",
880
920
  databaseUrl: buildManagedDatabaseUrl(selectedPort),
881
- source: 'docker-compose',
921
+ source: "docker-compose",
882
922
  reachable: true,
883
923
  dockerPort: selectedPort,
884
924
  };
@@ -898,25 +938,26 @@ export async function probeEmbeddedPostgresSupport() {
898
938
  export async function ensureDatabaseReady(paths, options = {}) {
899
939
  const env = readEnvFile(getEnvPath(paths));
900
940
  const preference = resolveDatabasePreference(env);
901
- if (preference.mode === 'external') {
941
+ if (preference.mode === "external") {
902
942
  if (!preference.databaseUrl) {
903
- throw new Error('DD_DATABASE_MODE=external requires DATABASE_URL to be set.');
943
+ throw new Error("DD_DATABASE_MODE=external requires DATABASE_URL to be set.");
904
944
  }
905
945
  const reachable = await checkDatabaseReachability(preference.databaseUrl);
906
946
  if (!reachable) {
907
- throw new Error('Database is not reachable. Start PostgreSQL or update apps/main-app/.env first.');
947
+ throw new Error("Database is not reachable. Start PostgreSQL or update apps/main-app/.env first.");
908
948
  }
909
949
  return {
910
- mode: 'external',
950
+ mode: "external",
911
951
  databaseUrl: preference.databaseUrl,
912
952
  source: preference.reason,
913
953
  reachable,
914
954
  };
915
955
  }
916
- if (preference.mode === 'docker') {
917
- if (preference.databaseUrl && await checkDatabaseReachability(preference.databaseUrl)) {
956
+ if (preference.mode === "docker") {
957
+ if (preference.databaseUrl &&
958
+ (await checkDatabaseReachability(preference.databaseUrl))) {
918
959
  return {
919
- mode: 'docker',
960
+ mode: "docker",
920
961
  databaseUrl: preference.databaseUrl,
921
962
  source: preference.reason,
922
963
  reachable: true,
@@ -924,17 +965,18 @@ export async function ensureDatabaseReady(paths, options = {}) {
924
965
  };
925
966
  }
926
967
  if (!dockerAvailable()) {
927
- throw new Error('Docker is not available, so the configured Docker database cannot be started.');
968
+ throw new Error("Docker is not available, so the configured Docker database cannot be started.");
928
969
  }
929
970
  return ensureDockerDatabaseRuntime(paths, env);
930
971
  }
931
- if (preference.databaseUrl && isLegacyLocalDatabaseUrl(preference.databaseUrl)) {
972
+ if (preference.databaseUrl &&
973
+ isLegacyLocalDatabaseUrl(preference.databaseUrl)) {
932
974
  const reachable = await checkDatabaseReachability(preference.databaseUrl);
933
975
  if (reachable) {
934
976
  return {
935
- mode: 'external',
977
+ mode: "external",
936
978
  databaseUrl: preference.databaseUrl,
937
- source: 'legacy-localhost',
979
+ source: "legacy-localhost",
938
980
  reachable: true,
939
981
  };
940
982
  }
@@ -946,7 +988,7 @@ export async function ensureDatabaseReady(paths, options = {}) {
946
988
  if (options.allowDockerFallback && dockerAvailable()) {
947
989
  const useDocker = options.yes
948
990
  ? true
949
- : await confirmYesNo('Embedded PostgreSQL failed. Start PostgreSQL via Docker instead?');
991
+ : await confirmYesNo("Embedded PostgreSQL failed. Start PostgreSQL via Docker instead?");
950
992
  if (useDocker) {
951
993
  return ensureDockerDatabaseRuntime(paths, env);
952
994
  }
@@ -955,23 +997,23 @@ export async function ensureDatabaseReady(paths, options = {}) {
955
997
  }
956
998
  }
957
999
  export function localDependenciesInstalled(paths) {
958
- return fs.existsSync(path.join(paths.root, 'packages', 'cli', 'node_modules', 'embedded-postgres'))
959
- && fs.existsSync(path.join(paths.root, 'packages', 'cli', 'node_modules', 'pg'));
1000
+ return (fs.existsSync(path.join(paths.root, "packages", "cli", "node_modules", "embedded-postgres")) &&
1001
+ fs.existsSync(path.join(paths.root, "packages", "cli", "node_modules", "pg")));
960
1002
  }
961
1003
  export function databaseRuntimeToEnvOptions(runtime) {
962
1004
  const options = {};
963
- if (runtime.mode === 'embedded') {
1005
+ if (runtime.mode === "embedded") {
964
1006
  options.databaseMode = runtime.mode;
965
1007
  options.embeddedDataDir = runtime.embeddedDataDir;
966
1008
  options.embeddedPort = runtime.embeddedPort;
967
1009
  return options;
968
1010
  }
969
- if (runtime.mode === 'docker') {
1011
+ if (runtime.mode === "docker") {
970
1012
  options.databaseMode = runtime.mode;
971
1013
  options.dockerPort = runtime.dockerPort;
972
1014
  return options;
973
1015
  }
974
- if (runtime.source !== 'DATABASE_URL') {
1016
+ if (runtime.source !== "DATABASE_URL") {
975
1017
  options.databaseUrl = runtime.databaseUrl;
976
1018
  }
977
1019
  return options;
@@ -989,15 +1031,18 @@ export function trimOutput(output, maxLines = 12) {
989
1031
  .map((line) => line.trimEnd())
990
1032
  .filter(Boolean);
991
1033
  if (lines.length <= maxLines) {
992
- return lines.join('\n');
1034
+ return lines.join("\n");
993
1035
  }
994
- return lines.slice(-maxLines).join('\n');
1036
+ return lines.slice(-maxLines).join("\n");
995
1037
  }
996
1038
  export function requiredEnvKeysMissing(env) {
997
1039
  const databasePreference = resolveDatabasePreference(env);
998
1040
  return REQUIRED_ENV_KEYS.filter((key) => {
999
- if (key === 'DATABASE_URL') {
1000
- return !env[key] && databasePreference.mode !== 'embedded' && databasePreference.mode !== 'docker';
1041
+ if (key === "DATABASE_URL") {
1042
+ return (!env[key] &&
1043
+ databasePreference.mode !== "embedded" &&
1044
+ databasePreference.mode !== "docker" &&
1045
+ databasePreference.reason !== "DATABASE_URL");
1001
1046
  }
1002
1047
  return !env[key];
1003
1048
  });