@doubledigit/cli 0.1.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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/dist/codegen.d.ts +12 -0
  3. package/dist/codegen.d.ts.map +1 -0
  4. package/dist/codegen.js +107 -0
  5. package/dist/commands/add.d.ts +26 -0
  6. package/dist/commands/add.d.ts.map +1 -0
  7. package/dist/commands/add.js +548 -0
  8. package/dist/commands/browse.d.ts +8 -0
  9. package/dist/commands/browse.d.ts.map +1 -0
  10. package/dist/commands/browse.js +116 -0
  11. package/dist/commands/create.d.ts +12 -0
  12. package/dist/commands/create.d.ts.map +1 -0
  13. package/dist/commands/create.js +218 -0
  14. package/dist/commands/db.d.ts +2 -0
  15. package/dist/commands/db.d.ts.map +1 -0
  16. package/dist/commands/db.js +64 -0
  17. package/dist/commands/disable.d.ts +5 -0
  18. package/dist/commands/disable.d.ts.map +1 -0
  19. package/dist/commands/disable.js +29 -0
  20. package/dist/commands/doctor.d.ts +2 -0
  21. package/dist/commands/doctor.d.ts.map +1 -0
  22. package/dist/commands/doctor.js +88 -0
  23. package/dist/commands/enable.d.ts +5 -0
  24. package/dist/commands/enable.d.ts.map +1 -0
  25. package/dist/commands/enable.js +29 -0
  26. package/dist/commands/info.d.ts +8 -0
  27. package/dist/commands/info.d.ts.map +1 -0
  28. package/dist/commands/info.js +84 -0
  29. package/dist/commands/list.d.ts +5 -0
  30. package/dist/commands/list.d.ts.map +1 -0
  31. package/dist/commands/list.js +44 -0
  32. package/dist/commands/marketplace.d.ts +11 -0
  33. package/dist/commands/marketplace.d.ts.map +1 -0
  34. package/dist/commands/marketplace.js +205 -0
  35. package/dist/commands/onboard.d.ts +2 -0
  36. package/dist/commands/onboard.d.ts.map +1 -0
  37. package/dist/commands/onboard.js +58 -0
  38. package/dist/commands/outdated.d.ts +8 -0
  39. package/dist/commands/outdated.d.ts.map +1 -0
  40. package/dist/commands/outdated.js +107 -0
  41. package/dist/commands/reconcile.d.ts +12 -0
  42. package/dist/commands/reconcile.d.ts.map +1 -0
  43. package/dist/commands/reconcile.js +175 -0
  44. package/dist/commands/run.d.ts +2 -0
  45. package/dist/commands/run.d.ts.map +1 -0
  46. package/dist/commands/run.js +37 -0
  47. package/dist/commands/sync.d.ts +5 -0
  48. package/dist/commands/sync.d.ts.map +1 -0
  49. package/dist/commands/sync.js +34 -0
  50. package/dist/commands/uninstall.d.ts +14 -0
  51. package/dist/commands/uninstall.d.ts.map +1 -0
  52. package/dist/commands/uninstall.js +190 -0
  53. package/dist/config.d.ts +19 -0
  54. package/dist/config.d.ts.map +1 -0
  55. package/dist/config.js +37 -0
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +181 -0
  59. package/dist/lib/github-auth.d.ts +8 -0
  60. package/dist/lib/github-auth.d.ts.map +1 -0
  61. package/dist/lib/github-auth.js +30 -0
  62. package/dist/lib/lock-file.d.ts +67 -0
  63. package/dist/lib/lock-file.d.ts.map +1 -0
  64. package/dist/lib/lock-file.js +117 -0
  65. package/dist/lib/marketplace-schema.d.ts +607 -0
  66. package/dist/lib/marketplace-schema.d.ts.map +1 -0
  67. package/dist/lib/marketplace-schema.js +111 -0
  68. package/dist/lib/marketplace.d.ts +57 -0
  69. package/dist/lib/marketplace.d.ts.map +1 -0
  70. package/dist/lib/marketplace.js +270 -0
  71. package/dist/lib/onboarding.d.ts +84 -0
  72. package/dist/lib/onboarding.d.ts.map +1 -0
  73. package/dist/lib/onboarding.js +1004 -0
  74. package/dist/lib/rewrite-extension-tsconfig.d.ts +22 -0
  75. package/dist/lib/rewrite-extension-tsconfig.d.ts.map +1 -0
  76. package/dist/lib/rewrite-extension-tsconfig.js +80 -0
  77. package/dist/lib/source-parser.d.ts +35 -0
  78. package/dist/lib/source-parser.d.ts.map +1 -0
  79. package/dist/lib/source-parser.js +121 -0
  80. package/dist/lib/validators.d.ts +73 -0
  81. package/dist/lib/validators.d.ts.map +1 -0
  82. package/dist/lib/validators.js +435 -0
  83. package/dist/paths.d.ts +46 -0
  84. package/dist/paths.d.ts.map +1 -0
  85. package/dist/paths.js +85 -0
  86. package/dist/scanner.d.ts +41 -0
  87. package/dist/scanner.d.ts.map +1 -0
  88. package/dist/scanner.js +100 -0
  89. package/package.json +49 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * dd create <name> — Scaffold a new micro-app from the template.
3
+ *
4
+ * Steps:
5
+ * 1. Copy template-micro-app → packages/<name>/
6
+ * 2. Replace template tokens in files
7
+ * 3. Add dependency to main-app/package.json
8
+ * 4. Add entry to dd-apps.config.json
9
+ * 5. Run sync to regenerate micro-apps.ts
10
+ */
11
+ export declare function create(name: string): Promise<void>;
12
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwFH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8JxD"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * dd create <name> — Scaffold a new micro-app from the template.
3
+ *
4
+ * Steps:
5
+ * 1. Copy template-micro-app → packages/<name>/
6
+ * 2. Replace template tokens in files
7
+ * 3. Add dependency to main-app/package.json
8
+ * 4. Add entry to dd-apps.config.json
9
+ * 5. Run sync to regenerate micro-apps.ts
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { execSync } from 'node:child_process';
14
+ import { resolveWorkspacePaths, installDirForKind, installRelPath, allInstallRoots, allScanRoots, } from '../paths.js';
15
+ import { readConfig, writeConfig } from '../config.js';
16
+ import { sync } from './sync.js';
17
+ import { rewriteExtensionTsconfig } from '../lib/rewrite-extension-tsconfig.js';
18
+ import { validateNoNameCollision, validateSlugPrefix, } from '../lib/validators.js';
19
+ const SLUG_PREFIX_PATTERN = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
20
+ function toSlugPrefix(name) {
21
+ // Take first letter of each hyphenated word, max 3 chars
22
+ const parts = name.split('-');
23
+ return parts
24
+ .map((p) => p[0])
25
+ .join('')
26
+ .slice(0, 3)
27
+ .toLowerCase();
28
+ }
29
+ function toPascalCase(name) {
30
+ return name
31
+ .split('-')
32
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
33
+ .join('');
34
+ }
35
+ function toCamelCase(name) {
36
+ const pascal = toPascalCase(name);
37
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
38
+ }
39
+ function toTitleCase(name) {
40
+ return name
41
+ .split('-')
42
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
43
+ .join(' ');
44
+ }
45
+ function copyDirRecursive(src, dest, replacements, name) {
46
+ fs.mkdirSync(dest, { recursive: true });
47
+ const entries = fs.readdirSync(src, { withFileTypes: true });
48
+ for (const entry of entries) {
49
+ const srcPath = path.join(src, entry.name);
50
+ const destPath = path.join(dest, entry.name);
51
+ // Skip node_modules, dist, .turbo, etc.
52
+ if (['node_modules', 'dist', '.turbo', 'tsconfig.tsbuildinfo'].includes(entry.name)) {
53
+ continue;
54
+ }
55
+ if (entry.isDirectory()) {
56
+ copyDirRecursive(srcPath, destPath, replacements, name);
57
+ }
58
+ else {
59
+ let content = fs.readFileSync(srcPath, 'utf-8');
60
+ // Apply replacements
61
+ for (const [search, replace] of replacements) {
62
+ content = content.replaceAll(search, replace);
63
+ }
64
+ // Rename file if necessary
65
+ let finalDestName = entry.name;
66
+ if (entry.name === 'template-client.tsx') {
67
+ finalDestName = `${name}-client.tsx`;
68
+ }
69
+ const finalDestPath = path.join(dest, finalDestName);
70
+ fs.writeFileSync(finalDestPath, content, 'utf-8');
71
+ }
72
+ }
73
+ }
74
+ export async function create(name) {
75
+ // Validate name
76
+ if (!SLUG_PREFIX_PATTERN.test(name)) {
77
+ console.error(`āŒ Invalid name "${name}". Must be lowercase kebab-case (e.g. "invoice-tracker").`);
78
+ process.exit(1);
79
+ }
80
+ const paths = resolveWorkspacePaths();
81
+ const targetDir = installDirForKind(paths, 'micro-app', name);
82
+ const targetRelPath = installRelPath('micro-app', name);
83
+ const templateDir = path.join(paths.packagesDir, 'template-micro-app');
84
+ // Check template exists
85
+ if (!fs.existsSync(templateDir)) {
86
+ console.error(`āŒ Template not found at ${templateDir}`);
87
+ process.exit(1);
88
+ }
89
+ // Check target doesn't already exist
90
+ if (fs.existsSync(targetDir)) {
91
+ console.error(`āŒ Directory already exists: ${targetDir}`);
92
+ process.exit(1);
93
+ }
94
+ const slugPrefix = toSlugPrefix(name);
95
+ const pascalName = toPascalCase(name);
96
+ const camelName = toCamelCase(name);
97
+ const titleName = toTitleCase(name);
98
+ const npmName = `@doubledigit/${name}`;
99
+ const collisionResult = validateNoNameCollision(allInstallRoots(paths), name);
100
+ if (!collisionResult.valid) {
101
+ for (const error of collisionResult.errors) {
102
+ console.error(`āŒ ${error}`);
103
+ }
104
+ process.exit(1);
105
+ }
106
+ const slugResult = validateSlugPrefix(allScanRoots(paths), slugPrefix, name);
107
+ if (!slugResult.valid) {
108
+ for (const error of slugResult.errors) {
109
+ console.error(`āŒ ${error}`);
110
+ }
111
+ process.exit(1);
112
+ }
113
+ console.log(`\nšŸ“¦ Creating micro-app: ${name}`);
114
+ console.log(` Slug prefix: ${slugPrefix}`);
115
+ console.log(` Package: ${npmName}`);
116
+ console.log(` Directory: ${targetRelPath}\n`);
117
+ // Replacements map (template tokens → actual values)
118
+ const replacements = new Map([
119
+ // Package name
120
+ ['@doubledigit/template-micro-app', npmName],
121
+ // DDApp key
122
+ ['template-micro-app', name],
123
+ // Slug prefix (tp- → new prefix)
124
+ ['tp-', `${slugPrefix}-`],
125
+ // PascalCase identifiers
126
+ ['TemplateClient', `${pascalName}Client`],
127
+ ['Template App', titleName],
128
+ ['Template', pascalName],
129
+ // camelCase identifiers
130
+ ['templateMicroApp', `${camelName}MicroApp`],
131
+ ['templateCollections', `${camelName}Collections`],
132
+ ['templateManifest', `${camelName}Manifest`],
133
+ ['templateApiHandler', `${camelName}ApiHandler`],
134
+ // Description
135
+ ['Template for scaffolding new Payload Micro-Apps', `${titleName} micro-app`],
136
+ // File names inside imports
137
+ ['template-client', `${name}-client`],
138
+ // Static strings
139
+ ["'/template'", `'/${name}'`],
140
+ ['Template Micro-App', `${titleName} App`],
141
+ ]);
142
+ // 1. Copy template (ensure parent dir exists for extensions/ layout)
143
+ fs.mkdirSync(path.dirname(targetDir), { recursive: true });
144
+ copyDirRecursive(templateDir, targetDir, replacements, name);
145
+ // 1b. Rewrite tsconfig relative paths for the new installation depth
146
+ rewriteExtensionTsconfig(targetDir, paths.root);
147
+ // 2. Fix package.json in target
148
+ const targetPkgPath = path.join(targetDir, 'package.json');
149
+ if (fs.existsSync(targetPkgPath)) {
150
+ const pkg = JSON.parse(fs.readFileSync(targetPkgPath, 'utf-8'));
151
+ pkg.name = npmName;
152
+ pkg.ddapp = true;
153
+ pkg.version = '0.1.0';
154
+ pkg.private = true;
155
+ pkg.description = `${titleName} micro-app`;
156
+ // Remove publishConfig if present (template has it)
157
+ delete pkg.publishConfig;
158
+ fs.writeFileSync(targetPkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
159
+ }
160
+ // 3. Add dependency to main-app/package.json
161
+ const mainPkg = JSON.parse(fs.readFileSync(paths.mainAppPackageJsonPath, 'utf-8'));
162
+ if (!mainPkg.dependencies)
163
+ mainPkg.dependencies = {};
164
+ mainPkg.dependencies[npmName] = 'workspace:*';
165
+ // Sort dependencies alphabetically
166
+ mainPkg.dependencies = Object.fromEntries(Object.entries(mainPkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
167
+ fs.writeFileSync(paths.mainAppPackageJsonPath, JSON.stringify(mainPkg, null, 2) + '\n', 'utf-8');
168
+ // 3b. Add TS path mappings
169
+ const baseTsConfigPath = path.join(paths.root, 'tsconfig.base.json');
170
+ if (fs.existsSync(baseTsConfigPath)) {
171
+ const tsconfig = JSON.parse(fs.readFileSync(baseTsConfigPath, 'utf-8'));
172
+ if (!tsconfig.compilerOptions)
173
+ tsconfig.compilerOptions = {};
174
+ if (!tsconfig.compilerOptions.paths)
175
+ tsconfig.compilerOptions.paths = {};
176
+ tsconfig.compilerOptions.paths[npmName] = [`${targetRelPath}/src/index.tsx`];
177
+ tsconfig.compilerOptions.paths[`${npmName}/*`] = [`${targetRelPath}/src/*`];
178
+ fs.writeFileSync(baseTsConfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf-8');
179
+ }
180
+ const mainTsConfigPath = path.join(paths.packagesDir, '../apps/main-app/tsconfig.json');
181
+ if (fs.existsSync(mainTsConfigPath)) {
182
+ const tsconfig = JSON.parse(fs.readFileSync(mainTsConfigPath, 'utf-8'));
183
+ if (!tsconfig.compilerOptions)
184
+ tsconfig.compilerOptions = {};
185
+ if (!tsconfig.compilerOptions.paths)
186
+ tsconfig.compilerOptions.paths = {};
187
+ tsconfig.compilerOptions.paths[npmName] = [`../../${targetRelPath}/src/index.tsx`];
188
+ tsconfig.compilerOptions.paths[`${npmName}/*`] = [`../../${targetRelPath}/src/*`];
189
+ fs.writeFileSync(mainTsConfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf-8');
190
+ }
191
+ // 4. Add entry to dd-apps.config.json
192
+ const config = readConfig(paths.configPath);
193
+ if (!config.apps)
194
+ config.apps = {};
195
+ config.apps[name] = true;
196
+ writeConfig(paths.configPath, config);
197
+ // 5. Run sync
198
+ await sync();
199
+ // 6. Run pnpm install
200
+ console.log('\nšŸ“¦ Running pnpm install...');
201
+ try {
202
+ execSync('pnpm install', { cwd: paths.root, stdio: 'inherit' });
203
+ }
204
+ catch {
205
+ console.warn('⚠ pnpm install failed. Run it manually.');
206
+ }
207
+ console.log(`
208
+ āœ… Micro-app "${name}" created successfully!
209
+
210
+ Next steps:
211
+ 1. Edit ${targetRelPath}/src/micro-app/index.tsx to define your routes, widgets, and API entrypoints
212
+ 2. Edit ${targetRelPath}/src/collections to define your collections
213
+ 3. Run: pnpm db:migrate:create
214
+ 4. Run: pnpm db:migrate
215
+ 5. Run: pnpm payload:generate-types:main
216
+ 6. Run: pnpm dev
217
+ `);
218
+ }
@@ -0,0 +1,2 @@
1
+ export declare function db(args: string[]): Promise<void>;
2
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,64 @@
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() {
4
+ const paths = resolveWorkspacePaths();
5
+ const envInspection = inspectLocalEnv(paths);
6
+ const databasePreference = resolveDatabasePreference(envInspection.env);
7
+ const databaseMode = databasePreference.mode;
8
+ const databaseUrl = databasePreference.databaseUrl;
9
+ if (!databaseUrl) {
10
+ console.error('Error: no database configuration is available. Run `pnpm dd onboard` first.');
11
+ process.exit(1);
12
+ }
13
+ const info = parseDatabaseUrl(databaseUrl);
14
+ const reachable = await checkDatabaseReachability(databaseUrl);
15
+ console.log('\nšŸ—„ļø Database status\n');
16
+ console.log(`Mode: ${databaseMode}`);
17
+ console.log(`Host: ${info.host}`);
18
+ console.log(`Port: ${info.port}`);
19
+ console.log(`Database: ${info.database}`);
20
+ console.log(`Reachable: ${reachable ? 'yes' : 'no'}`);
21
+ const migrationStatus = captureCommand('pnpm', ['db:migrate:status'], paths.root);
22
+ console.log('\nMigration status:\n');
23
+ console.log(migrationStatus.output || 'Unavailable');
24
+ if (!reachable || !migrationStatus.ok) {
25
+ process.exitCode = 1;
26
+ }
27
+ }
28
+ async function dbMigrate() {
29
+ const paths = resolveWorkspacePaths();
30
+ const runtime = await ensureDatabaseReady(paths, {
31
+ yes: true,
32
+ allowDockerFallback: true,
33
+ });
34
+ applyRuntimeDatabaseUrl(ensureLocalEnv(paths, databaseRuntimeToEnvOptions(runtime)).env, runtime);
35
+ runChecked('pnpm', ['db:migrate'], paths.root, 'Migration run');
36
+ console.log('āœ” Migrations applied');
37
+ }
38
+ const HELP = `
39
+ dd db — Database helpers
40
+
41
+ Subcommands:
42
+ status Show database connectivity and migration status
43
+ migrate Apply committed migrations
44
+ `;
45
+ export async function db(args) {
46
+ const subcommand = args[0];
47
+ switch (subcommand) {
48
+ case 'status':
49
+ await dbStatus();
50
+ break;
51
+ case 'migrate':
52
+ await dbMigrate();
53
+ break;
54
+ case '--help':
55
+ case '-h':
56
+ case undefined:
57
+ console.log(HELP);
58
+ break;
59
+ default:
60
+ console.error(`Unknown db subcommand: ${subcommand}`);
61
+ console.log(HELP);
62
+ process.exit(1);
63
+ }
64
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * dd disable <name> — Disable a micro-app in dd-apps.config.json and run sync.
3
+ */
4
+ export declare function disable(name: string): Promise<void>;
5
+ //# sourceMappingURL=disable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disable.d.ts","sourceRoot":"","sources":["../../src/commands/disable.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * dd disable <name> — Disable a micro-app in dd-apps.config.json and run sync.
3
+ */
4
+ import { resolveWorkspacePaths } from '../paths.js';
5
+ import { readConfig, writeConfig } from '../config.js';
6
+ import { scanWorkspace } from '../scanner.js';
7
+ import { sync } from './sync.js';
8
+ export async function disable(name) {
9
+ const paths = resolveWorkspacePaths();
10
+ const { microApps: allApps, payloadPlugins } = scanWorkspace(paths);
11
+ const app = allApps.find((a) => a.key === name);
12
+ if (!app) {
13
+ const plugin = payloadPlugins.find((p) => p.key === name);
14
+ if (plugin) {
15
+ console.error(`āŒ "${name}" is a payload plugin — payload plugins are always-on and cannot be enabled or disabled.`);
16
+ process.exit(1);
17
+ }
18
+ console.error(`āŒ Micro-app "${name}" not found in workspace.`);
19
+ console.error(` Available: ${allApps.map((a) => a.key).join(', ')}`);
20
+ process.exit(1);
21
+ }
22
+ const config = readConfig(paths.configPath);
23
+ if (!config.apps)
24
+ config.apps = {};
25
+ config.apps[name] = false;
26
+ writeConfig(paths.configPath, config);
27
+ console.log(`āŒ Disabled "${name}" in dd-apps.config.json`);
28
+ await sync();
29
+ }
@@ -0,0 +1,2 @@
1
+ export declare function doctor(): Promise<void>;
2
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,88 @@
1
+ import fs from 'node:fs';
2
+ import { resolveWorkspacePaths } from '../paths.js';
3
+ import { captureCommand, checkDatabaseReachability, commandExists, dockerAvailable, getGeneratedTypesPath, probeEmbeddedPostgresSupport, inspectLocalEnv, resolveDatabasePreference, requiredEnvKeysMissing, trimOutput, } from '../lib/onboarding.js';
4
+ export async function doctor() {
5
+ const paths = resolveWorkspacePaths();
6
+ const checks = [];
7
+ const nodeOk = commandExists('node');
8
+ checks.push({
9
+ label: 'Node.js',
10
+ ok: nodeOk,
11
+ detail: nodeOk ? process.version : 'Install Node.js 20+.',
12
+ });
13
+ const pnpmResult = captureCommand('pnpm', ['--version'], paths.root);
14
+ checks.push({
15
+ label: 'pnpm',
16
+ ok: pnpmResult.ok,
17
+ detail: pnpmResult.ok ? pnpmResult.output.trim() : 'Install pnpm 9+ with corepack.',
18
+ });
19
+ const envInspection = inspectLocalEnv(paths);
20
+ const missingKeys = requiredEnvKeysMissing(envInspection.env);
21
+ const databasePreference = resolveDatabasePreference(envInspection.env);
22
+ const databaseMode = databasePreference.mode;
23
+ checks.push({
24
+ label: '.env',
25
+ ok: envInspection.exists && missingKeys.length === 0,
26
+ detail: missingKeys.length === 0
27
+ ? envInspection.exists
28
+ ? `present at ${envInspection.envPath.replace(`${paths.root}/`, '')}`
29
+ : 'Missing apps/main-app/.env.'
30
+ : `Missing keys: ${missingKeys.join(', ')}`,
31
+ });
32
+ checks.push({
33
+ label: 'Database mode',
34
+ ok: true,
35
+ detail: databaseMode,
36
+ });
37
+ const dbReachable = databasePreference.databaseUrl
38
+ ? await checkDatabaseReachability(databasePreference.databaseUrl)
39
+ : false;
40
+ const embeddedSupport = databaseMode === 'embedded'
41
+ ? await probeEmbeddedPostgresSupport()
42
+ : { supported: true };
43
+ checks.push({
44
+ label: 'Database reachability',
45
+ ok: databaseMode === 'embedded' ? embeddedSupport.supported : dbReachable,
46
+ detail: databaseMode === 'embedded'
47
+ ? embeddedSupport.supported
48
+ ? dbReachable
49
+ ? 'Embedded PostgreSQL is reachable.'
50
+ : 'Embedded PostgreSQL is not running yet. `pnpm dev` or `dd run` will start it automatically.'
51
+ : embeddedSupport.reason || 'Embedded PostgreSQL is not supported on this host.'
52
+ : dbReachable
53
+ ? 'PostgreSQL is reachable from DATABASE_URL.'
54
+ : dockerAvailable()
55
+ ? 'Database is not reachable yet. Run `pnpm dd onboard` or start Docker/Postgres manually.'
56
+ : 'Database is not reachable. Start PostgreSQL or update DATABASE_URL.',
57
+ });
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
+ });
66
+ const generatedTypesPath = getGeneratedTypesPath(paths);
67
+ const generatedTypesExists = fs.existsSync(generatedTypesPath);
68
+ checks.push({
69
+ label: 'Generated Payload types',
70
+ ok: generatedTypesExists,
71
+ detail: generatedTypesExists
72
+ ? 'packages/shared/src/types/payload-types.ts exists.'
73
+ : 'Run `pnpm payload:generate-types:main`.',
74
+ });
75
+ console.log('\n🩺 Double Digit doctor\n');
76
+ for (const check of checks) {
77
+ console.log(`${check.ok ? 'āœ”' : '✘'} ${check.label}`);
78
+ console.log(` ${check.detail}`);
79
+ }
80
+ const failed = checks.filter((check) => !check.ok);
81
+ if (failed.length > 0) {
82
+ console.log('\nDoctor found issues. Recommended next step:\n');
83
+ console.log(' pnpm dd onboard\n');
84
+ process.exitCode = 1;
85
+ return;
86
+ }
87
+ console.log('\nAll checks passed.\n');
88
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * dd enable <name> — Enable a micro-app in dd-apps.config.json and run sync.
3
+ */
4
+ export declare function enable(name: string): Promise<void>;
5
+ //# sourceMappingURL=enable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enable.d.ts","sourceRoot":"","sources":["../../src/commands/enable.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBxD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * dd enable <name> — Enable a micro-app in dd-apps.config.json and run sync.
3
+ */
4
+ import { resolveWorkspacePaths } from '../paths.js';
5
+ import { readConfig, writeConfig } from '../config.js';
6
+ import { scanWorkspace } from '../scanner.js';
7
+ import { sync } from './sync.js';
8
+ export async function enable(name) {
9
+ const paths = resolveWorkspacePaths();
10
+ const { microApps: allApps, payloadPlugins } = scanWorkspace(paths);
11
+ const app = allApps.find((a) => a.key === name);
12
+ if (!app) {
13
+ const plugin = payloadPlugins.find((p) => p.key === name);
14
+ if (plugin) {
15
+ console.error(`āŒ "${name}" is a payload plugin — payload plugins are always-on and cannot be enabled or disabled.`);
16
+ process.exit(1);
17
+ }
18
+ console.error(`āŒ Micro-app "${name}" not found in workspace.`);
19
+ console.error(` Available: ${allApps.map((a) => a.key).join(', ')}`);
20
+ process.exit(1);
21
+ }
22
+ const config = readConfig(paths.configPath);
23
+ if (!config.apps)
24
+ config.apps = {};
25
+ config.apps[name] = true;
26
+ writeConfig(paths.configPath, config);
27
+ console.log(`āœ… Enabled "${name}" in dd-apps.config.json`);
28
+ await sync();
29
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * dd info <name> — Show detailed info about an installed extension.
3
+ *
4
+ * Reads from the lock file and cross-references with marketplace cache
5
+ * if the extension was installed from a marketplace.
6
+ */
7
+ export declare function info(name: string): Promise<void>;
8
+ //# sourceMappingURL=info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8EtD"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * dd info <name> — Show detailed info about an installed extension.
3
+ *
4
+ * Reads from the lock file and cross-references with marketplace cache
5
+ * if the extension was installed from a marketplace.
6
+ */
7
+ import { resolveWorkspacePaths } from '../paths.js';
8
+ import { readLockFile } from '../lib/lock-file.js';
9
+ import { readCachedMarketplace, readKnownMarketplaces, } from '../lib/marketplace.js';
10
+ export async function info(name) {
11
+ const paths = resolveWorkspacePaths();
12
+ const lock = readLockFile(paths.lockFilePath);
13
+ const entry = lock.apps[name];
14
+ if (!entry) {
15
+ console.error(`Error: "${name}" is not installed (not found in lock file).`);
16
+ process.exit(1);
17
+ }
18
+ console.log(`\nšŸ“¦ ${name}\n`);
19
+ console.log(` Kind: ${entry.kind ?? 'unknown'}`);
20
+ console.log(` Source: ${entry.source}`);
21
+ console.log(` Resolved source: ${entry.resolvedSource}`);
22
+ if (entry.ref) {
23
+ console.log(` Ref: ${entry.ref}`);
24
+ }
25
+ if (entry.sha) {
26
+ console.log(` SHA: ${entry.sha}`);
27
+ }
28
+ if (entry.marketplaceVersion) {
29
+ console.log(` Marketplace ver: ${entry.marketplaceVersion}`);
30
+ }
31
+ if (entry.npmName) {
32
+ console.log(` npm name: ${entry.npmName}`);
33
+ }
34
+ console.log(` Content hash: ${entry.contentHash}`);
35
+ console.log(` Install path: ${entry.installPath ?? 'unknown'}`);
36
+ console.log(` Install strategy: ${entry.installStrategy ?? 'unknown'}`);
37
+ console.log(` Installed at: ${entry.installedAt}`);
38
+ console.log(` Updated at: ${entry.updatedAt}`);
39
+ // Cross-reference with marketplace cache
40
+ if (entry.marketplace) {
41
+ console.log(`\n Marketplace: ${entry.marketplace}`);
42
+ const known = readKnownMarketplaces(paths.root);
43
+ const mpEntry = known.marketplaces[entry.marketplace];
44
+ if (mpEntry) {
45
+ console.log(` Marketplace src: ${mpEntry.source}`);
46
+ if (mpEntry.cachedAt) {
47
+ console.log(` Catalog cached: ${mpEntry.cachedAt}`);
48
+ }
49
+ }
50
+ const cached = readCachedMarketplace(paths.root, entry.marketplace);
51
+ if (cached) {
52
+ const ext = cached.manifest.extensions.find((e) => e.name === name);
53
+ if (ext) {
54
+ console.log(`\n Marketplace entry:`);
55
+ if (ext.description) {
56
+ console.log(` Description: ${ext.description}`);
57
+ }
58
+ if (ext.version) {
59
+ console.log(` Version: ${ext.version}`);
60
+ }
61
+ if (ext.author) {
62
+ console.log(` Author: ${ext.author}`);
63
+ }
64
+ console.log(` Verified: ${ext.verified ? 'yes' : 'no'}`);
65
+ if (ext.tags.length > 0) {
66
+ console.log(` Tags: ${ext.tags.join(', ')}`);
67
+ }
68
+ if (ext.source.sha) {
69
+ console.log(` Latest SHA: ${ext.source.sha}`);
70
+ }
71
+ if (ext.source.ref) {
72
+ console.log(` Latest ref: ${ext.source.ref}`);
73
+ }
74
+ }
75
+ else {
76
+ console.log(` ⚠ Extension not found in current marketplace catalog`);
77
+ }
78
+ }
79
+ else {
80
+ console.log(` ⚠ Marketplace catalog not cached — run: dd marketplace update ${entry.marketplace}`);
81
+ }
82
+ }
83
+ console.log('');
84
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * dd list — Show all discovered micro-apps with their enabled/disabled status.
3
+ */
4
+ export declare function list(): Promise<void>;
5
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA4C1C"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * dd list — Show all discovered micro-apps with their enabled/disabled status.
3
+ */
4
+ import { resolveWorkspacePaths } from '../paths.js';
5
+ import { readConfig, isEntryEnabled } from '../config.js';
6
+ import { scanWorkspace } from '../scanner.js';
7
+ export async function list() {
8
+ const paths = resolveWorkspacePaths();
9
+ const config = readConfig(paths.configPath);
10
+ const { microApps: allApps, payloadPlugins } = scanWorkspace(paths);
11
+ if (allApps.length === 0 && payloadPlugins.length === 0) {
12
+ console.log('No extensions found. Add "ddapp": true or "ddPackageType": "payload-plugin" to a package.json to register one.');
13
+ return;
14
+ }
15
+ // Apply DD_APPS env var override for display
16
+ const envApps = process.env.DD_APPS;
17
+ const whitelist = envApps
18
+ ? new Set(envApps.split(',').map((s) => s.trim()).filter(Boolean))
19
+ : null;
20
+ if (allApps.length > 0) {
21
+ console.log(`\nMicro-Apps (${allApps.length} discovered):\n`);
22
+ for (const app of allApps) {
23
+ let status;
24
+ if (whitelist) {
25
+ status = whitelist.has(app.key) ? 'āœ…' : 'āŒ (DD_APPS override)';
26
+ }
27
+ else if (config.apps && !isEntryEnabled(config.apps[app.key])) {
28
+ status = 'āŒ (disabled in config)';
29
+ }
30
+ else {
31
+ status = 'āœ…';
32
+ }
33
+ console.log(` ${status} ${app.key.padEnd(20)} ${app.npmName}`);
34
+ }
35
+ console.log('');
36
+ }
37
+ if (payloadPlugins.length > 0) {
38
+ console.log(`Payload Plugins (${payloadPlugins.length} discovered):\n`);
39
+ for (const plugin of payloadPlugins) {
40
+ console.log(` šŸ”Œ always-on ${plugin.key.padEnd(20)} ${plugin.npmName}`);
41
+ }
42
+ console.log('');
43
+ }
44
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * dd marketplace <subcommand> — Manage marketplace registrations.
3
+ *
4
+ * Subcommands:
5
+ * add <source> Register a marketplace (GitHub repo with .doubledigit/marketplace.json)
6
+ * list List registered marketplaces
7
+ * update [name] Update marketplace catalog(s)
8
+ * remove <name> Remove a registered marketplace
9
+ */
10
+ export declare function marketplace(args: string[]): Promise<void>;
11
+ //# sourceMappingURL=marketplace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../src/commands/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+NH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B/D"}