@doubledigit/cli 0.1.0 → 0.2.0

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 (44) hide show
  1. package/LICENSE +1 -1
  2. package/dist/codegen.d.ts +6 -2
  3. package/dist/codegen.d.ts.map +1 -1
  4. package/dist/codegen.js +81 -13
  5. package/dist/commands/add.d.ts.map +1 -1
  6. package/dist/commands/add.js +36 -3
  7. package/dist/commands/create.js +2 -2
  8. package/dist/commands/db.d.ts.map +1 -1
  9. package/dist/commands/db.js +24 -11
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +46 -12
  12. package/dist/commands/init.d.ts +2 -0
  13. package/dist/commands/init.d.ts.map +1 -0
  14. package/dist/commands/init.js +163 -0
  15. package/dist/commands/onboard.d.ts.map +1 -1
  16. package/dist/commands/onboard.js +6 -1
  17. package/dist/commands/run.d.ts.map +1 -1
  18. package/dist/commands/run.js +1 -0
  19. package/dist/commands/sync.d.ts.map +1 -1
  20. package/dist/commands/sync.js +5 -1
  21. package/dist/commands/uninstall.d.ts.map +1 -1
  22. package/dist/commands/uninstall.js +25 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +15 -5
  26. package/dist/lib/marketplace-schema.d.ts +24 -24
  27. package/dist/lib/onboarding.d.ts +3 -3
  28. package/dist/lib/onboarding.d.ts.map +1 -1
  29. package/dist/lib/onboarding.js +282 -237
  30. package/dist/lib/package-entry.d.ts +6 -0
  31. package/dist/lib/package-entry.d.ts.map +1 -0
  32. package/dist/lib/package-entry.js +63 -0
  33. package/dist/lib/scoped-migrations.d.ts +11 -0
  34. package/dist/lib/scoped-migrations.d.ts.map +1 -0
  35. package/dist/lib/scoped-migrations.js +156 -0
  36. package/dist/lib/validators.d.ts.map +1 -1
  37. package/dist/lib/validators.js +12 -49
  38. package/dist/paths.d.ts +4 -0
  39. package/dist/paths.d.ts.map +1 -1
  40. package/dist/paths.js +2 -0
  41. package/dist/scanner.d.ts +4 -0
  42. package/dist/scanner.d.ts.map +1 -1
  43. package/dist/scanner.js +21 -0
  44. package/package.json +10 -4
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/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"}
@@ -0,0 +1,163 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { applyRuntimeDatabaseUrl, commandExists, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, localDependenciesInstalled, printNextSteps, runChecked, startDevServer, } from '../lib/onboarding.js';
5
+ import { resolveWorkspacePaths } from '../paths.js';
6
+ const REPO_URL = 'https://github.com/crystalphantom/double-digit.git';
7
+ const USAGE = 'Usage: dd init <project-name> [--yes] [--run] [--skip-install] [--no-git]';
8
+ const INIT_HELP = `${USAGE}
9
+
10
+ Options:
11
+ --yes Skip interactive prompts and use defaults.
12
+ --run Run onboarding, generate types, and start the app.
13
+ --skip-install Skip pnpm install step.
14
+ --no-git Remove .git from the generated project.
15
+
16
+ Examples:
17
+ dd init my-project
18
+ dd init my-project --run
19
+ dd init my-project --skip-install --no-git`;
20
+ function parseInitArgs(args) {
21
+ let projectName;
22
+ const options = {
23
+ yes: false,
24
+ run: false,
25
+ skipInstall: false,
26
+ noGit: false,
27
+ };
28
+ for (const arg of args) {
29
+ if (arg === '--help' || arg === '-h') {
30
+ console.log(INIT_HELP);
31
+ process.exit(0);
32
+ }
33
+ if (arg === '--yes' || arg === '-y') {
34
+ options.yes = true;
35
+ continue;
36
+ }
37
+ if (arg === '--run') {
38
+ options.run = true;
39
+ continue;
40
+ }
41
+ if (arg === '--skip-install') {
42
+ options.skipInstall = true;
43
+ continue;
44
+ }
45
+ if (arg === '--no-git') {
46
+ options.noGit = true;
47
+ continue;
48
+ }
49
+ if (arg.startsWith('-')) {
50
+ console.error(`Unknown option: ${arg}`);
51
+ process.exit(1);
52
+ }
53
+ if (!projectName) {
54
+ projectName = arg;
55
+ continue;
56
+ }
57
+ console.error('Unexpected extra argument: ' + arg);
58
+ process.exit(1);
59
+ }
60
+ if (!projectName) {
61
+ console.error(`Error: missing required argument. ${USAGE}`);
62
+ process.exit(1);
63
+ }
64
+ return { projectName, options };
65
+ }
66
+ function cloneRepo(projectName, cwd) {
67
+ const projectDir = path.join(cwd, projectName);
68
+ if (fs.existsSync(projectDir)) {
69
+ console.error(`Directory "${projectName}" already exists.`);
70
+ process.exit(1);
71
+ }
72
+ if (!commandExists('git')) {
73
+ console.error('git is required. Install it from https://git-scm.com');
74
+ process.exit(1);
75
+ }
76
+ if (!commandExists('pnpm')) {
77
+ console.error('pnpm is required. Enable it with: corepack enable && corepack prepare pnpm@latest --activate');
78
+ process.exit(1);
79
+ }
80
+ console.log('🚀 Cloning Double Digit template...');
81
+ try {
82
+ execSync(`git clone --depth 1 ${REPO_URL} ${projectName}`, {
83
+ cwd,
84
+ stdio: 'inherit',
85
+ });
86
+ }
87
+ catch {
88
+ console.error('Failed to clone template repository from GitHub.');
89
+ console.error('Check internet connection, branch name, and repo access.');
90
+ process.exit(1);
91
+ }
92
+ return projectDir;
93
+ }
94
+ function printInitNextSteps(projectName, skipInstall) {
95
+ console.log('\n✅ Project scaffolding complete!\n');
96
+ console.log(`cd ${projectName}`);
97
+ if (skipInstall) {
98
+ console.log('pnpm install');
99
+ console.log('pnpm dd onboard');
100
+ console.log('pnpm dd run');
101
+ return;
102
+ }
103
+ printNextSteps();
104
+ }
105
+ export async function init(args) {
106
+ const { projectName, options } = parseInitArgs(args);
107
+ const cwd = process.cwd();
108
+ const projectDir = cloneRepo(projectName, cwd);
109
+ const targetName = path.basename(projectDir);
110
+ process.chdir(projectDir);
111
+ if (options.noGit) {
112
+ fs.rmSync(path.join(projectDir, '.git'), {
113
+ recursive: true,
114
+ force: true,
115
+ });
116
+ console.log('Removed .git directory as requested by --no-git.');
117
+ }
118
+ const paths = resolveWorkspacePaths();
119
+ const envSetup = ensureLocalEnv(paths);
120
+ console.log(`✔ ${envSetup.created ? 'Created' : 'Found'} ${envSetup.envPath.replace(`${paths.root}/`, '')}`);
121
+ if (envSetup.updatedKeys.length > 0) {
122
+ console.log(` Updated: ${envSetup.updatedKeys.join(', ')}`);
123
+ }
124
+ if (options.skipInstall && options.run && !localDependenciesInstalled(paths)) {
125
+ console.error('Cannot run without project dependencies. Install first, or remove --skip-install.');
126
+ console.error('Run: pnpm install');
127
+ process.exit(1);
128
+ }
129
+ const shouldBootstrap = !options.skipInstall || options.run;
130
+ if (!options.skipInstall) {
131
+ try {
132
+ runChecked('pnpm', ['install'], paths.root, 'Dependency install');
133
+ console.log('✔ Dependencies installed');
134
+ }
135
+ catch {
136
+ console.error('Dependency install failed. Run `pnpm install` manually.');
137
+ process.exit(1);
138
+ }
139
+ }
140
+ if (shouldBootstrap) {
141
+ ensureLocalEnv(paths); // re-read updated env before DB bootstrap.
142
+ const databaseRuntime = await ensureDatabaseReady(paths, {
143
+ yes: options.yes,
144
+ allowDockerFallback: true,
145
+ });
146
+ const runtimeEnv = applyRuntimeDatabaseUrl(ensureLocalEnv(paths, databaseRuntimeToEnvOptions(databaseRuntime)).env, databaseRuntime);
147
+ console.log(`✔ ${databaseRuntime.mode === 'embedded'
148
+ ? 'Embedded'
149
+ : databaseRuntime.mode === 'docker'
150
+ ? 'Docker'
151
+ : 'External'} PostgreSQL is ready`);
152
+ runChecked('pnpm', ['db:migrate'], paths.root, 'Migration run');
153
+ console.log('✔ Migrations applied');
154
+ runChecked('pnpm', ['payload:generate-types:main'], paths.root, 'Type generation');
155
+ console.log('✔ Payload types generated');
156
+ if (options.run) {
157
+ console.log('\nStarting development server...');
158
+ await startDevServer(paths, runtimeEnv);
159
+ return;
160
+ }
161
+ }
162
+ printInitNextSteps(targetName, options.skipInstall);
163
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAuCA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD3D"}
1
+ {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AA6CA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD3D"}
@@ -2,11 +2,16 @@ import { resolveWorkspacePaths } from '../paths.js';
2
2
  import { applyRuntimeDatabaseUrl, captureCommand, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, localDependenciesInstalled, printNextSteps, runChecked, startDevServer, } from '../lib/onboarding.js';
3
3
  function parseOnboardArgs(args) {
4
4
  let yes = false;
5
- let start = true;
5
+ let start = false;
6
6
  for (const arg of args) {
7
7
  if (arg === '--yes' || arg === '-y') {
8
8
  yes = true;
9
9
  }
10
+ if (arg === '--start') {
11
+ start = true;
12
+ }
13
+ // Keep --no-start and --setup-only as compatibility aliases (no-ops now
14
+ // that the default is already no-start).
10
15
  if (arg === '--no-start' || arg === '--setup-only') {
11
16
  start = false;
12
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAcA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CzC"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAcA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA8CzC"}
@@ -32,6 +32,7 @@ export async function run() {
32
32
  runChecked('pnpm', ['payload:generate-types:main'], paths.root, 'Type generation');
33
33
  console.log('✔ Payload types generated');
34
34
  }
35
+ runChecked('pnpm', ['--dir', 'apps/main-app', 'exec', 'tsx', 'src/lib/seed.ts'], paths.root, 'Seeding demo user', runtimeEnv);
35
36
  console.log('\nStarting development server...\n');
36
37
  await startDevServer(paths, runtimeEnv);
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA8B1C"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqC1C"}