@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.
- package/LICENSE +1 -1
- package/dist/codegen.d.ts +6 -2
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +81 -13
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +36 -3
- package/dist/commands/create.js +2 -2
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +24 -11
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +46 -12
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +163 -0
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +6 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/lib/marketplace-schema.d.ts +24 -24
- package/dist/lib/onboarding.d.ts +3 -3
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +282 -237
- package/dist/lib/package-entry.d.ts +6 -0
- package/dist/lib/package-entry.d.ts.map +1 -0
- package/dist/lib/package-entry.js +63 -0
- package/dist/lib/scoped-migrations.d.ts +11 -0
- package/dist/lib/scoped-migrations.d.ts.map +1 -0
- package/dist/lib/scoped-migrations.js +156 -0
- package/dist/lib/validators.d.ts.map +1 -1
- package/dist/lib/validators.js +12 -49
- package/dist/paths.d.ts +4 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +2 -0
- package/dist/scanner.d.ts +4 -0
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +21 -0
- package/package.json +10 -4
package/LICENSE
CHANGED
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
|
|
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
|
package/dist/codegen.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
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
|
-
|
|
14
|
-
const
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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(
|
|
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;
|
|
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"}
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
|
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 {
|
package/dist/commands/create.js
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/commands/db.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveWorkspacePaths } from '../paths.js';
|
|
2
|
-
import { applyRuntimeDatabaseUrl,
|
|
3
|
-
|
|
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
|
-
|
|
22
|
+
process.env.DATABASE_URL = databaseUrl;
|
|
22
23
|
console.log('\nMigration status:\n');
|
|
23
|
-
|
|
24
|
-
if (!reachable
|
|
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
|
-
|
|
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
|
|
43
|
-
migrate
|
|
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":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAyBA,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAqI5C"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
83
|
-
console.log(' pnpm dd onboard
|
|
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 @@
|
|
|
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":"
|
|
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"}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -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 =
|
|
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,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAcA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA8CzC"}
|
package/dist/commands/run.js
CHANGED
|
@@ -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;
|
|
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"}
|