@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
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 doubledigital
3
+ Copyright (c) 2026 doubledigit
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @doubledigit/cli
2
+
3
+ CLI for Double Digit local setup, project bootstrapping, extension management, and database workflows.
4
+
5
+ Double Digit is a pluggable Next.js/Payload developer control plane. The CLI is the supported command surface for creating a new Double Digit project and managing an existing local workspace.
6
+
7
+ ## Usage
8
+
9
+ Run the published package directly:
10
+
11
+ ```bash
12
+ npx @doubledigit/cli init my-project
13
+ pnpm dlx @doubledigit/cli init my-project
14
+ ```
15
+
16
+ Inside a Double Digit project, use the repo-local shortcut:
17
+
18
+ ```bash
19
+ pnpm dd doctor
20
+ pnpm dd onboard
21
+ pnpm dd run
22
+ ```
23
+
24
+ If installed globally, the binary is available as `dd`:
25
+
26
+ ```bash
27
+ dd doctor
28
+ dd onboard
29
+ dd run
30
+ ```
31
+
32
+ ## Requirements
33
+
34
+ - Node.js 20 or newer
35
+ - pnpm 9 or newer
36
+
37
+ Enable pnpm through Corepack when needed:
38
+
39
+ ```bash
40
+ corepack enable
41
+ ```
42
+
43
+ ## Core Commands
44
+
45
+ ```bash
46
+ dd init <project-name> # scaffold a new Double Digit project
47
+ dd doctor # check local prerequisites and project health
48
+ dd onboard # prepare env, dependencies, DB, migrations, and types
49
+ dd run # bootstrap the runtime DB and start the app
50
+ dd db status # inspect migration state
51
+ dd db migrate # run migrations
52
+ dd db create <target> <name> # create a migration for shared or micro-app schema
53
+ dd add <source> # install an extension
54
+ dd sync # regenerate workspace registries
55
+ dd list # list discovered micro-apps
56
+ dd enable <name> # enable a micro-app
57
+ dd disable <name> # disable a micro-app
58
+ dd remove <name> # remove a micro-app package and wiring
59
+ dd reconcile # detect registry, lock file, and package drift
60
+ dd marketplace <subcommand> # manage marketplace registrations
61
+ dd browse # browse marketplace extensions
62
+ ```
63
+
64
+ `add` is also available as `install`, and `remove` is also available as `uninstall`.
65
+
66
+ ## New Project Bootstrap
67
+
68
+ ```bash
69
+ npx @doubledigit/cli init my-project --yes
70
+ cd my-project
71
+ pnpm dd run
72
+ ```
73
+
74
+ Useful `init` options:
75
+
76
+ - `--yes` skips prompts and uses defaults.
77
+ - `--run` bootstraps and starts the app after scaffolding.
78
+ - `--skip-install` skips dependency installation.
79
+ - `--no-git` removes the cloned `.git` directory from the generated project.
80
+
81
+ ## Existing Project Setup
82
+
83
+ Use `onboard` for setup-only work:
84
+
85
+ ```bash
86
+ pnpm dd onboard
87
+ ```
88
+
89
+ Use `run` when you want the CLI to prepare the runtime database and then start the app:
90
+
91
+ ```bash
92
+ pnpm dd run
93
+ ```
94
+
95
+ Leave `DATABASE_URL` empty in `apps/main-app/.env` to use the default CLI-managed embedded PostgreSQL flow. Provide `DATABASE_URL` through the environment or `.env` when using your own PostgreSQL instance.
96
+
97
+ ## Links
98
+
99
+ - Repository: https://github.com/crystalphantom/double-digit
100
+ - Issues: https://github.com/crystalphantom/double-digit/issues
101
+ - Getting started: https://github.com/crystalphantom/double-digit/blob/main/docs/getting-started.md
102
+ - Architecture: https://github.com/crystalphantom/double-digit/blob/main/docs/architecture.md
package/dist/codegen.d.ts CHANGED
@@ -5,8 +5,12 @@
5
5
  * that the Next.js bundler can tree-shake and analyze.
6
6
  */
7
7
  import type { DiscoveredApp, DiscoveredPayloadPlugin } from './scanner.js';
8
- export declare function generateMicroAppsModule(enabledApps: DiscoveredApp[]): string;
8
+ export declare function generateMicroAppsModule(outputPath: string, enabledApps: DiscoveredApp[]): string;
9
9
  export declare function writeMicroAppsModule(outputPath: string, enabledApps: DiscoveredApp[]): void;
10
- export declare function generatePayloadPluginsModule(plugins: DiscoveredPayloadPlugin[]): string;
10
+ export declare function generateMicroAppTranspilePackagesModule(apps: DiscoveredApp[]): string;
11
+ export declare function writeMicroAppTranspilePackagesModule(outputPath: string, apps: DiscoveredApp[]): void;
12
+ export declare function generatePayloadPluginsModule(outputPath: string, plugins: DiscoveredPayloadPlugin[]): string;
11
13
  export declare function writePayloadPluginsModule(outputPath: string, plugins: DiscoveredPayloadPlugin[]): void;
14
+ export declare function generateScopedPayloadConfigModule(app: DiscoveredApp): string;
15
+ export declare function writeScopedPayloadConfigs(configsDir: string, apps: DiscoveredApp[]): void;
12
16
  //# sourceMappingURL=codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAQ3E,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,aAAa,EAAE,GAC3B,MAAM,CAoER;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,aAAa,EAAE,GAC3B,IAAI,CAIN;AAID,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,uBAAuB,EAAE,GACjC,MAAM,CAwBR;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EAAE,GACjC,IAAI,CAIN"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAe3E,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,aAAa,EAAE,GAC3B,MAAM,CAmER;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,aAAa,EAAE,GAC3B,IAAI,CAIN;AAED,wBAAgB,uCAAuC,CACrD,IAAI,EAAE,aAAa,EAAE,GACpB,MAAM,CAcR;AAED,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,EAAE,GACpB,IAAI,CAIN;AAID,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EAAE,GACjC,MAAM,CAuBR;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EAAE,GACjC,IAAI,CAIN;AAID,wBAAgB,iCAAiC,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA6B5E;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,EAAE,GACpB,IAAI,CAuBN"}
package/dist/codegen.js CHANGED
@@ -5,16 +5,22 @@
5
5
  * that the Next.js bundler can tree-shake and analyze.
6
6
  */
7
7
  import fs from 'node:fs';
8
+ import path from 'node:path';
8
9
  const HEADER = `// ⚠️ AUTO-GENERATED by @doubledigit/cli sync
9
10
  // Do not edit manually. Run \`dd sync\` or \`npx @doubledigit/cli sync\` to regenerate.
10
- //
11
- // Generated at: {{TIMESTAMP}}
12
11
  `;
13
- export function generateMicroAppsModule(enabledApps) {
14
- const timestamp = new Date().toISOString();
12
+ function toImportSpecifier(outputPath, entryFile) {
13
+ const relativePath = path.relative(path.dirname(outputPath), entryFile);
14
+ const normalized = relativePath
15
+ .split(path.sep)
16
+ .join('/')
17
+ .replace(/\.(cts|mts|ts|tsx|js|jsx)$/, '');
18
+ return normalized.startsWith('.') ? normalized : `./${normalized}`;
19
+ }
20
+ export function generateMicroAppsModule(outputPath, enabledApps) {
15
21
  const lines = [];
16
22
  // Header
17
- lines.push(HEADER.replace('{{TIMESTAMP}}', timestamp));
23
+ lines.push(HEADER);
18
24
  // Type + config imports
19
25
  lines.push("import type { DDApp } from '@doubledigit/micro-app-sdk';");
20
26
  lines.push("import { isAppEnabled } from '@doubledigit/micro-app-sdk';");
@@ -74,20 +80,37 @@ export function generateMicroAppsModule(enabledApps) {
74
80
  return lines.join('\n');
75
81
  }
76
82
  export function writeMicroAppsModule(outputPath, enabledApps) {
77
- const content = generateMicroAppsModule(enabledApps);
78
- fs.mkdirSync(fs.realpathSync(outputPath + '/..'), { recursive: true });
83
+ const content = generateMicroAppsModule(outputPath, enabledApps);
84
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
85
+ fs.writeFileSync(outputPath, content, 'utf-8');
86
+ }
87
+ export function generateMicroAppTranspilePackagesModule(apps) {
88
+ const lines = [];
89
+ lines.push(HEADER);
90
+ lines.push('// Packages statically imported by the generated micro-app registry.');
91
+ lines.push('// next.config.ts includes these in transpilePackages for source-first workspace dev.');
92
+ lines.push('export const microAppTranspilePackages = [');
93
+ for (const app of apps) {
94
+ lines.push(` '${app.npmName}',`);
95
+ }
96
+ lines.push('] as const;');
97
+ lines.push('');
98
+ return lines.join('\n');
99
+ }
100
+ export function writeMicroAppTranspilePackagesModule(outputPath, apps) {
101
+ const content = generateMicroAppTranspilePackagesModule(apps);
102
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
79
103
  fs.writeFileSync(outputPath, content, 'utf-8');
80
104
  }
81
105
  // ─── Payload Plugins codegen ───────────────────────────────────────────
82
- export function generatePayloadPluginsModule(plugins) {
83
- const timestamp = new Date().toISOString();
106
+ export function generatePayloadPluginsModule(outputPath, plugins) {
84
107
  const lines = [];
85
- lines.push(HEADER.replace('{{TIMESTAMP}}', timestamp));
108
+ lines.push(HEADER);
86
109
  lines.push("import type { Plugin } from 'payload';");
87
110
  lines.push('');
88
111
  if (plugins.length > 0) {
89
112
  for (const plugin of plugins) {
90
- lines.push(`import ${plugin.importName} from '${plugin.npmName}';`);
113
+ lines.push(`import ${plugin.importName} from '${toImportSpecifier(outputPath, plugin.entryFile)}';`);
91
114
  }
92
115
  }
93
116
  lines.push('');
@@ -101,7 +124,52 @@ export function generatePayloadPluginsModule(plugins) {
101
124
  return lines.join('\n');
102
125
  }
103
126
  export function writePayloadPluginsModule(outputPath, plugins) {
104
- const content = generatePayloadPluginsModule(plugins);
105
- fs.mkdirSync(fs.realpathSync(outputPath + '/..'), { recursive: true });
127
+ const content = generatePayloadPluginsModule(outputPath, plugins);
128
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
106
129
  fs.writeFileSync(outputPath, content, 'utf-8');
107
130
  }
131
+ // ─── Scoped Payload configs codegen ─────────────────────────────────────
132
+ export function generateScopedPayloadConfigModule(app) {
133
+ const lines = [];
134
+ lines.push(HEADER);
135
+ lines.push("import path from 'node:path';");
136
+ lines.push('');
137
+ lines.push(`import ${app.importName}MicroApp from '${app.npmName}';`);
138
+ lines.push('');
139
+ lines.push("import { createScopedPayloadConfig } from './_factory';");
140
+ lines.push("import { getScopedMigrationTables } from './scoped-migration-tables';");
141
+ lines.push('');
142
+ lines.push(`export const scopedMigrationTables = getScopedMigrationTables(${app.importName}MicroApp);`);
143
+ lines.push('');
144
+ lines.push('export default createScopedPayloadConfig(');
145
+ lines.push(` [${app.importName}MicroApp],`);
146
+ lines.push(' {');
147
+ lines.push(' migrationDir: path.resolve(');
148
+ lines.push(' process.cwd(),');
149
+ lines.push(` '../../extensions/micro-apps/${app.key}/src/migrations',`);
150
+ lines.push(' ),');
151
+ lines.push(' mcp: false,');
152
+ lines.push(' tablesFilter: scopedMigrationTables,');
153
+ lines.push(' },');
154
+ lines.push(');');
155
+ lines.push('');
156
+ return lines.join('\n');
157
+ }
158
+ export function writeScopedPayloadConfigs(configsDir, apps) {
159
+ fs.mkdirSync(configsDir, { recursive: true });
160
+ const expectedConfigFiles = new Set(apps.map((app) => `${app.key}.config.ts`));
161
+ for (const app of apps) {
162
+ fs.writeFileSync(path.join(configsDir, `${app.key}.config.ts`), generateScopedPayloadConfigModule(app), 'utf-8');
163
+ }
164
+ for (const entry of fs.readdirSync(configsDir, { withFileTypes: true })) {
165
+ if (!entry.isFile())
166
+ continue;
167
+ if (!entry.name.endsWith('.config.ts'))
168
+ continue;
169
+ if (entry.name.startsWith('_') || entry.name === 'shared.config.ts')
170
+ continue;
171
+ if (expectedConfigFiles.has(entry.name))
172
+ continue;
173
+ fs.rmSync(path.join(configsDir, entry.name));
174
+ }
175
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAymBH,wBAAsB,GAAG,CACvB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CA4Ff"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAopBH,wBAAsB,GAAG,CACvB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CA4Ff"}
@@ -107,6 +107,32 @@ function restoreSnapshots(snapshots) {
107
107
  }
108
108
  }
109
109
  }
110
+ function snapshotDirectory(dirPath) {
111
+ if (!fs.existsSync(dirPath)) {
112
+ return { path: dirPath, existed: false };
113
+ }
114
+ const backupRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-add-configs-'));
115
+ const backupDir = path.join(backupRoot, 'payload-configs');
116
+ fs.cpSync(dirPath, backupDir, { recursive: true });
117
+ return { path: dirPath, existed: true, backupDir };
118
+ }
119
+ function restoreDirectorySnapshot(snapshot) {
120
+ if (fs.existsSync(snapshot.path)) {
121
+ fs.rmSync(snapshot.path, { recursive: true, force: true });
122
+ }
123
+ if (snapshot.existed && snapshot.backupDir) {
124
+ fs.mkdirSync(path.dirname(snapshot.path), { recursive: true });
125
+ fs.cpSync(snapshot.backupDir, snapshot.path, { recursive: true });
126
+ }
127
+ }
128
+ function cleanupDirectorySnapshot(snapshot) {
129
+ if (snapshot.backupDir) {
130
+ const backupRoot = path.dirname(snapshot.backupDir);
131
+ if (fs.existsSync(backupRoot)) {
132
+ fs.rmSync(backupRoot, { recursive: true, force: true });
133
+ }
134
+ }
135
+ }
110
136
  // ---------------------------------------------------------------------------
111
137
  // npm source handler
112
138
  // ---------------------------------------------------------------------------
@@ -300,8 +326,10 @@ async function addFromGitHub(raw, options, marketplaceCtx) {
300
326
  paths.configPath,
301
327
  paths.lockFilePath,
302
328
  paths.microAppsOutputPath,
329
+ paths.microAppTranspilePackagesOutputPath,
303
330
  paths.payloadPluginsOutputPath,
304
331
  ]);
332
+ const payloadConfigsSnapshot = snapshotDirectory(paths.payloadConfigsDir);
305
333
  try {
306
334
  // Ensure parent directory exists (extensions/<kind>/ may not yet exist)
307
335
  fs.mkdirSync(path.dirname(targetDir), { recursive: true });
@@ -421,6 +449,7 @@ async function addFromGitHub(raw, options, marketplaceCtx) {
421
449
  console.error(' ✘ Install failed — rolling back workspace changes');
422
450
  try {
423
451
  restoreSnapshots(rollbackSnapshots);
452
+ restoreDirectorySnapshot(payloadConfigsSnapshot);
424
453
  if (fs.existsSync(targetDir)) {
425
454
  fs.rmSync(targetDir, { recursive: true, force: true });
426
455
  }
@@ -437,6 +466,9 @@ async function addFromGitHub(raw, options, marketplaceCtx) {
437
466
  }
438
467
  throw installErr;
439
468
  }
469
+ finally {
470
+ cleanupDirectorySnapshot(payloadConfigsSnapshot);
471
+ }
440
472
  for (const backup of backups) {
441
473
  if (fs.existsSync(backup.backupDir)) {
442
474
  fs.rmSync(backup.backupDir, { recursive: true, force: true });
@@ -455,9 +487,10 @@ async function addFromGitHub(raw, options, marketplaceCtx) {
455
487
 
456
488
  Next steps:
457
489
  1. Review the installed code: ${targetRelPath}/
458
- 2. Run: pnpm db:migrate:create
459
- 3. Run: pnpm db:migrate
460
- 4. Run: pnpm dev
490
+ 2. ${isPayloadPlugin ? 'Run: pnpm db:migrate:create:shared <migration-name>' : `Run: pnpm db:migrate:create:app ${appName} <migration-name>`}
491
+ 3. ${isPayloadPlugin ? 'Run: pnpm db:migrate:shared' : `Run: pnpm db:migrate:app ${appName}`}
492
+ 4. Run: pnpm payload:generate-types:main
493
+ 5. Run: pnpm dev
461
494
  `);
462
495
  }
463
496
  finally {
@@ -210,8 +210,8 @@ export async function create(name) {
210
210
  Next steps:
211
211
  1. Edit ${targetRelPath}/src/micro-app/index.tsx to define your routes, widgets, and API entrypoints
212
212
  2. Edit ${targetRelPath}/src/collections to define your collections
213
- 3. Run: pnpm db:migrate:create
214
- 4. Run: pnpm db:migrate
213
+ 3. Run: pnpm db:migrate:create:app ${name} <migration-name>
214
+ 4. Run: pnpm db:migrate:app ${name}
215
215
  5. Run: pnpm payload:generate-types:main
216
216
  6. Run: pnpm dev
217
217
  `);
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"AAyEA,wBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtD"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"AA8EA,wBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBtD"}
@@ -1,6 +1,7 @@
1
1
  import { resolveWorkspacePaths } from '../paths.js';
2
- import { applyRuntimeDatabaseUrl, captureCommand, checkDatabaseReachability, inspectLocalEnv, parseDatabaseUrl, resolveDatabasePreference, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, runChecked, } from '../lib/onboarding.js';
3
- async function dbStatus() {
2
+ import { applyRuntimeDatabaseUrl, checkDatabaseReachability, inspectLocalEnv, parseDatabaseUrl, resolveDatabasePreference, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, } from '../lib/onboarding.js';
3
+ import { runScopedMigrations } from '../lib/scoped-migrations.js';
4
+ async function dbStatus(args) {
4
5
  const paths = resolveWorkspacePaths();
5
6
  const envInspection = inspectLocalEnv(paths);
6
7
  const databasePreference = resolveDatabasePreference(envInspection.env);
@@ -18,38 +19,50 @@ async function dbStatus() {
18
19
  console.log(`Port: ${info.port}`);
19
20
  console.log(`Database: ${info.database}`);
20
21
  console.log(`Reachable: ${reachable ? 'yes' : 'no'}`);
21
- const migrationStatus = captureCommand('pnpm', ['db:migrate:status'], paths.root);
22
+ process.env.DATABASE_URL = databaseUrl;
22
23
  console.log('\nMigration status:\n');
23
- console.log(migrationStatus.output || 'Unavailable');
24
- if (!reachable || !migrationStatus.ok) {
24
+ runScopedMigrations(paths, 'status', args);
25
+ if (!reachable) {
25
26
  process.exitCode = 1;
26
27
  }
27
28
  }
28
- async function dbMigrate() {
29
+ async function dbMigrate(args) {
29
30
  const paths = resolveWorkspacePaths();
30
31
  const runtime = await ensureDatabaseReady(paths, {
31
32
  yes: true,
32
33
  allowDockerFallback: true,
33
34
  });
34
35
  applyRuntimeDatabaseUrl(ensureLocalEnv(paths, databaseRuntimeToEnvOptions(runtime)).env, runtime);
35
- runChecked('pnpm', ['db:migrate'], paths.root, 'Migration run');
36
+ runScopedMigrations(paths, 'migrate', args);
36
37
  console.log('✔ Migrations applied');
37
38
  }
39
+ function dbCreate(args) {
40
+ runScopedMigrations(resolveWorkspacePaths(), 'create', args);
41
+ }
38
42
  const HELP = `
39
43
  dd db — Database helpers
40
44
 
41
45
  Subcommands:
42
- status Show database connectivity and migration status
43
- migrate Apply committed migrations
46
+ status [target] Show database connectivity and migration status
47
+ migrate [target] Apply committed migrations
48
+ create [target] [name] Create a scoped migration
49
+
50
+ Targets:
51
+ --all Shared migrations, then every micro-app config (default)
52
+ --shared Shared migrations only
53
+ <micro-app> One micro-app, for example meal-planner
44
54
  `;
45
55
  export async function db(args) {
46
56
  const subcommand = args[0];
47
57
  switch (subcommand) {
48
58
  case 'status':
49
- await dbStatus();
59
+ await dbStatus(args.slice(1));
50
60
  break;
51
61
  case 'migrate':
52
- await dbMigrate();
62
+ await dbMigrate(args.slice(1));
63
+ break;
64
+ case 'create':
65
+ dbCreate(args.slice(1));
53
66
  break;
54
67
  case '--help':
55
68
  case '-h':
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAsBA,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAmG5C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAyBA,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAqI5C"}
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import { resolveWorkspacePaths } from '../paths.js';
3
- import { captureCommand, checkDatabaseReachability, commandExists, dockerAvailable, getGeneratedTypesPath, probeEmbeddedPostgresSupport, inspectLocalEnv, resolveDatabasePreference, requiredEnvKeysMissing, trimOutput, } from '../lib/onboarding.js';
4
+ import { captureCommand, checkDatabaseReachability, commandExists, DEFAULT_APP_URL, DEFAULT_EMBEDDED_POSTGRES_DATA_DIR, dockerAvailable, getGeneratedTypesPath, probeEmbeddedPostgresSupport, inspectLocalEnv, resolveDatabasePreference, requiredEnvKeysMissing, trimOutput, } from '../lib/onboarding.js';
4
5
  export async function doctor() {
5
6
  const paths = resolveWorkspacePaths();
6
7
  const checks = [];
@@ -29,10 +30,24 @@ export async function doctor() {
29
30
  : 'Missing apps/main-app/.env.'
30
31
  : `Missing keys: ${missingKeys.join(', ')}`,
31
32
  });
33
+ const dbReasonLabels = {
34
+ 'DATABASE_URL': 'via DATABASE_URL env var',
35
+ 'apps/main-app/.env': 'via .env',
36
+ 'DD_DATABASE_MODE': 'via DD_DATABASE_MODE',
37
+ 'embedded-managed-url': 'auto-detected from database URL',
38
+ 'no-database-url': 'default — no DATABASE_URL set',
39
+ 'legacy-default': 'migrated from legacy default',
40
+ };
41
+ let dbModeDetail = `${databaseMode} (${dbReasonLabels[databasePreference.reason] || databasePreference.reason})`;
42
+ if (databaseMode === 'embedded') {
43
+ const dataDir = (envInspection.env.DD_EMBEDDED_POSTGRES_DATA_DIR?.trim() || DEFAULT_EMBEDDED_POSTGRES_DATA_DIR)
44
+ .replace(os.homedir(), '~');
45
+ dbModeDetail += `\n Data dir: ${dataDir}`;
46
+ }
32
47
  checks.push({
33
48
  label: 'Database mode',
34
49
  ok: true,
35
- detail: databaseMode,
50
+ detail: dbModeDetail,
36
51
  });
37
52
  const dbReachable = databasePreference.databaseUrl
38
53
  ? await checkDatabaseReachability(databasePreference.databaseUrl)
@@ -55,14 +70,23 @@ export async function doctor() {
55
70
  ? 'Database is not reachable yet. Run `pnpm dd onboard` or start Docker/Postgres manually.'
56
71
  : 'Database is not reachable. Start PostgreSQL or update DATABASE_URL.',
57
72
  });
58
- const migrationStatus = captureCommand('pnpm', ['db:migrate:status'], paths.root);
59
- checks.push({
60
- label: 'Migration status',
61
- ok: migrationStatus.ok,
62
- detail: migrationStatus.ok
63
- ? trimOutput(migrationStatus.output)
64
- : trimOutput(migrationStatus.output) || 'Could not read migration status.',
65
- });
73
+ if (databaseMode === 'embedded' && embeddedSupport.supported && !dbReachable) {
74
+ checks.push({
75
+ label: 'Migration status',
76
+ ok: true,
77
+ detail: 'Skipped until embedded PostgreSQL starts. `pnpm dev` or `dd run` will start it automatically.',
78
+ });
79
+ }
80
+ else {
81
+ const migrationStatus = captureCommand('pnpm', ['db:migrate:status'], paths.root, databasePreference.databaseUrl ? { DATABASE_URL: databasePreference.databaseUrl } : {});
82
+ checks.push({
83
+ label: 'Migration status',
84
+ ok: migrationStatus.ok,
85
+ detail: migrationStatus.ok
86
+ ? trimOutput(migrationStatus.output)
87
+ : trimOutput(migrationStatus.output) || 'Could not read migration status.',
88
+ });
89
+ }
66
90
  const generatedTypesPath = getGeneratedTypesPath(paths);
67
91
  const generatedTypesExists = fs.existsSync(generatedTypesPath);
68
92
  checks.push({
@@ -72,6 +96,15 @@ export async function doctor() {
72
96
  ? 'packages/shared/src/types/payload-types.ts exists.'
73
97
  : 'Run `pnpm payload:generate-types:main`.',
74
98
  });
99
+ const appPort = envInspection.env.APP_PORT?.trim() || process.env.APP_PORT?.trim();
100
+ const appUrl = appPort ? `http://localhost:${appPort}` : DEFAULT_APP_URL;
101
+ checks.push({
102
+ label: 'App URL',
103
+ ok: true,
104
+ detail: appPort
105
+ ? `${appUrl} (APP_PORT override)`
106
+ : `${appUrl} (default — override with APP_PORT)`,
107
+ });
75
108
  console.log('\n🩺 Double Digit doctor\n');
76
109
  for (const check of checks) {
77
110
  console.log(`${check.ok ? '✔' : '✘'} ${check.label}`);
@@ -79,8 +112,9 @@ export async function doctor() {
79
112
  }
80
113
  const failed = checks.filter((check) => !check.ok);
81
114
  if (failed.length > 0) {
82
- console.log('\nDoctor found issues. Recommended next step:\n');
83
- console.log(' pnpm dd onboard\n');
115
+ console.log('\nDoctor found issues. Recommended next steps:\n');
116
+ console.log(' pnpm dd onboard # fix setup issues');
117
+ console.log(' pnpm dev # then start the app\n');
84
118
  process.exitCode = 1;
85
119
  return;
86
120
  }
@@ -0,0 +1,2 @@
1
+ export declare function init(args: string[]): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA0JA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ExD"}