@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.
- package/LICENSE +21 -0
- package/dist/codegen.d.ts +12 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +107 -0
- package/dist/commands/add.d.ts +26 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +548 -0
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +116 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +218 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +64 -0
- package/dist/commands/disable.d.ts +5 -0
- package/dist/commands/disable.d.ts.map +1 -0
- package/dist/commands/disable.js +29 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +88 -0
- package/dist/commands/enable.d.ts +5 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/enable.js +29 -0
- package/dist/commands/info.d.ts +8 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +84 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +44 -0
- package/dist/commands/marketplace.d.ts +11 -0
- package/dist/commands/marketplace.d.ts.map +1 -0
- package/dist/commands/marketplace.js +205 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +58 -0
- package/dist/commands/outdated.d.ts +8 -0
- package/dist/commands/outdated.d.ts.map +1 -0
- package/dist/commands/outdated.js +107 -0
- package/dist/commands/reconcile.d.ts +12 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +175 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +37 -0
- package/dist/commands/sync.d.ts +5 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +34 -0
- package/dist/commands/uninstall.d.ts +14 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +190 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +181 -0
- package/dist/lib/github-auth.d.ts +8 -0
- package/dist/lib/github-auth.d.ts.map +1 -0
- package/dist/lib/github-auth.js +30 -0
- package/dist/lib/lock-file.d.ts +67 -0
- package/dist/lib/lock-file.d.ts.map +1 -0
- package/dist/lib/lock-file.js +117 -0
- package/dist/lib/marketplace-schema.d.ts +607 -0
- package/dist/lib/marketplace-schema.d.ts.map +1 -0
- package/dist/lib/marketplace-schema.js +111 -0
- package/dist/lib/marketplace.d.ts +57 -0
- package/dist/lib/marketplace.d.ts.map +1 -0
- package/dist/lib/marketplace.js +270 -0
- package/dist/lib/onboarding.d.ts +84 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/lib/onboarding.js +1004 -0
- package/dist/lib/rewrite-extension-tsconfig.d.ts +22 -0
- package/dist/lib/rewrite-extension-tsconfig.d.ts.map +1 -0
- package/dist/lib/rewrite-extension-tsconfig.js +80 -0
- package/dist/lib/source-parser.d.ts +35 -0
- package/dist/lib/source-parser.d.ts.map +1 -0
- package/dist/lib/source-parser.js +121 -0
- package/dist/lib/validators.d.ts +73 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +435 -0
- package/dist/paths.d.ts +46 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +85 -0
- package/dist/scanner.d.ts +41 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +100 -0
- package/package.json +49 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dd sync — Regenerate micro-apps.ts and payload-plugins.ts from config + discovered packages.
|
|
3
|
+
*/
|
|
4
|
+
import { resolveWorkspacePaths } from '../paths.js';
|
|
5
|
+
import { readConfig, isEntryEnabled } from '../config.js';
|
|
6
|
+
import { scanWorkspace } from '../scanner.js';
|
|
7
|
+
import { writeMicroAppsModule, writePayloadPluginsModule } from '../codegen.js';
|
|
8
|
+
export async function sync() {
|
|
9
|
+
const paths = resolveWorkspacePaths();
|
|
10
|
+
const config = readConfig(paths.configPath);
|
|
11
|
+
const { microApps: allApps, payloadPlugins } = scanWorkspace(paths);
|
|
12
|
+
// Apply DD_APPS env var override (micro-apps only)
|
|
13
|
+
const envApps = process.env.DD_APPS;
|
|
14
|
+
let enabledApps = allApps;
|
|
15
|
+
if (envApps) {
|
|
16
|
+
const whitelist = new Set(envApps.split(',').map((s) => s.trim()).filter(Boolean));
|
|
17
|
+
enabledApps = allApps.filter((app) => whitelist.has(app.key));
|
|
18
|
+
console.log(`📋 DD_APPS override: ${[...whitelist].join(', ')}`);
|
|
19
|
+
}
|
|
20
|
+
else if (config.apps) {
|
|
21
|
+
enabledApps = allApps.filter((app) => isEntryEnabled(config.apps[app.key]));
|
|
22
|
+
}
|
|
23
|
+
writeMicroAppsModule(paths.microAppsOutputPath, allApps);
|
|
24
|
+
writePayloadPluginsModule(paths.payloadPluginsOutputPath, payloadPlugins);
|
|
25
|
+
console.log(`✅ Synced ${enabledApps.length}/${allApps.length} micro-apps → micro-apps.ts`);
|
|
26
|
+
for (const app of allApps) {
|
|
27
|
+
const isEnabled = enabledApps.some((e) => e.key === app.key);
|
|
28
|
+
console.log(` ${isEnabled ? '✅' : '❌'} ${app.key} (${app.npmName})`);
|
|
29
|
+
}
|
|
30
|
+
console.log(`✅ Synced ${payloadPlugins.length} payload plugin(s) → payload-plugins.ts`);
|
|
31
|
+
for (const plugin of payloadPlugins) {
|
|
32
|
+
console.log(` 🔌 ${plugin.key} (${plugin.npmName})`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dd uninstall <name> — Completely remove a micro-app from the workspace.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Resolve the actual npm package name (from package.json or lock file)
|
|
6
|
+
* 2. Remove dependency from main-app/package.json
|
|
7
|
+
* 3. Remove TS path mappings from tsconfig.base.json and apps/main-app/tsconfig.json
|
|
8
|
+
* 4. Remove entry from dd-apps.config.json
|
|
9
|
+
* 5. Remove entry from dd-apps.lock.json
|
|
10
|
+
* 6. Run sync to regenerate micro-apps.ts
|
|
11
|
+
* 7. Delete the package directory
|
|
12
|
+
*/
|
|
13
|
+
export declare function uninstall(name: string): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=uninstall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6CH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0J3D"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dd uninstall <name> — Completely remove a micro-app from the workspace.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Resolve the actual npm package name (from package.json or lock file)
|
|
6
|
+
* 2. Remove dependency from main-app/package.json
|
|
7
|
+
* 3. Remove TS path mappings from tsconfig.base.json and apps/main-app/tsconfig.json
|
|
8
|
+
* 4. Remove entry from dd-apps.config.json
|
|
9
|
+
* 5. Remove entry from dd-apps.lock.json
|
|
10
|
+
* 6. Run sync to regenerate micro-apps.ts
|
|
11
|
+
* 7. Delete the package directory
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { resolveWorkspacePaths, installProbePaths } from '../paths.js';
|
|
17
|
+
import { readConfig, writeConfig } from '../config.js';
|
|
18
|
+
import { sync } from './sync.js';
|
|
19
|
+
import { readLockFile, writeLockFile } from '../lib/lock-file.js';
|
|
20
|
+
import { readManagedPackageMetadata } from '../lib/validators.js';
|
|
21
|
+
/** Resolve the actual npm package name for a micro-app. */
|
|
22
|
+
function resolveNpmName(name, targetDir, lockFilePath) {
|
|
23
|
+
// First try: read from the package's own package.json (most reliable)
|
|
24
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
25
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
28
|
+
if (pkg.name && typeof pkg.name === 'string') {
|
|
29
|
+
return pkg.name;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Fall through to lock file
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Second try: read from lock file (survives manual directory deletion)
|
|
37
|
+
try {
|
|
38
|
+
const lock = readLockFile(lockFilePath);
|
|
39
|
+
if (lock.apps[name]?.npmName) {
|
|
40
|
+
return lock.apps[name].npmName;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Fall through to default
|
|
45
|
+
}
|
|
46
|
+
// Fallback: conventional scope
|
|
47
|
+
return `@doubledigit/${name}`;
|
|
48
|
+
}
|
|
49
|
+
export async function uninstall(name) {
|
|
50
|
+
const paths = resolveWorkspacePaths();
|
|
51
|
+
let lockEntry;
|
|
52
|
+
try {
|
|
53
|
+
lockEntry = readLockFile(paths.lockFilePath).apps[name];
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
lockEntry = undefined;
|
|
57
|
+
}
|
|
58
|
+
const candidateDirs = [
|
|
59
|
+
...(lockEntry?.installPath ? [path.join(paths.root, lockEntry.installPath)] : []),
|
|
60
|
+
...installProbePaths(paths, name, lockEntry?.kind),
|
|
61
|
+
];
|
|
62
|
+
const seenCandidates = new Set();
|
|
63
|
+
const managedInstalls = candidateDirs
|
|
64
|
+
.filter((dir) => {
|
|
65
|
+
if (seenCandidates.has(dir))
|
|
66
|
+
return false;
|
|
67
|
+
seenCandidates.add(dir);
|
|
68
|
+
return fs.existsSync(dir);
|
|
69
|
+
})
|
|
70
|
+
.map((dir) => ({
|
|
71
|
+
dir,
|
|
72
|
+
...readManagedPackageMetadata(dir),
|
|
73
|
+
}))
|
|
74
|
+
.filter((install) => install.isManagedExtension);
|
|
75
|
+
const targetDir = managedInstalls[0]?.dir ?? candidateDirs[0];
|
|
76
|
+
const npmNames = new Set();
|
|
77
|
+
if (lockEntry?.npmName) {
|
|
78
|
+
npmNames.add(lockEntry.npmName);
|
|
79
|
+
}
|
|
80
|
+
for (const install of managedInstalls) {
|
|
81
|
+
if (install.npmName) {
|
|
82
|
+
npmNames.add(install.npmName);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (npmNames.size === 0) {
|
|
86
|
+
npmNames.add(resolveNpmName(name, targetDir, paths.lockFilePath));
|
|
87
|
+
}
|
|
88
|
+
const removedKinds = new Set();
|
|
89
|
+
if (lockEntry?.kind) {
|
|
90
|
+
removedKinds.add(lockEntry.kind);
|
|
91
|
+
}
|
|
92
|
+
for (const install of managedInstalls) {
|
|
93
|
+
if (install.kind) {
|
|
94
|
+
removedKinds.add(install.kind);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const isPayloadPlugin = removedKinds.size > 0 &&
|
|
98
|
+
removedKinds.has('payload-plugin') &&
|
|
99
|
+
!removedKinds.has('micro-app');
|
|
100
|
+
const shouldRemoveConfig = !isPayloadPlugin || removedKinds.has('micro-app');
|
|
101
|
+
const kindLabel = isPayloadPlugin ? 'payload plugin' : 'micro-app';
|
|
102
|
+
// Check target doesn't already exist
|
|
103
|
+
if (managedInstalls.length === 0) {
|
|
104
|
+
console.warn(`⚠ Directory does not exist: ${targetDir}`);
|
|
105
|
+
}
|
|
106
|
+
console.log(`\n🗑️ Uninstalling ${kindLabel}: ${name} (${[...npmNames].join(', ')})`);
|
|
107
|
+
// 1. Remove dependency from main-app/package.json
|
|
108
|
+
if (fs.existsSync(paths.mainAppPackageJsonPath)) {
|
|
109
|
+
const mainPkg = JSON.parse(fs.readFileSync(paths.mainAppPackageJsonPath, 'utf-8'));
|
|
110
|
+
if (mainPkg.dependencies) {
|
|
111
|
+
let removedDependency = false;
|
|
112
|
+
for (const npmName of npmNames) {
|
|
113
|
+
if (!(npmName in mainPkg.dependencies))
|
|
114
|
+
continue;
|
|
115
|
+
delete mainPkg.dependencies[npmName];
|
|
116
|
+
removedDependency = true;
|
|
117
|
+
}
|
|
118
|
+
if (removedDependency) {
|
|
119
|
+
fs.writeFileSync(paths.mainAppPackageJsonPath, JSON.stringify(mainPkg, null, 2) + '\n', 'utf-8');
|
|
120
|
+
console.log(` ✔ Removed ${[...npmNames].join(', ')} from main-app/package.json`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// 2. Remove TS path mappings
|
|
125
|
+
const baseTsConfigPath = path.join(paths.root, 'tsconfig.base.json');
|
|
126
|
+
if (fs.existsSync(baseTsConfigPath)) {
|
|
127
|
+
const tsconfig = JSON.parse(fs.readFileSync(baseTsConfigPath, 'utf-8'));
|
|
128
|
+
if (tsconfig.compilerOptions?.paths) {
|
|
129
|
+
for (const npmName of npmNames) {
|
|
130
|
+
delete tsconfig.compilerOptions.paths[npmName];
|
|
131
|
+
delete tsconfig.compilerOptions.paths[`${npmName}/*`];
|
|
132
|
+
}
|
|
133
|
+
fs.writeFileSync(baseTsConfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf-8');
|
|
134
|
+
console.log(` ✔ Removed ${[...npmNames].join(', ')} paths from tsconfig.base.json`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const mainTsConfigPath = path.join(paths.packagesDir, '../apps/main-app/tsconfig.json');
|
|
138
|
+
if (fs.existsSync(mainTsConfigPath)) {
|
|
139
|
+
const tsconfig = JSON.parse(fs.readFileSync(mainTsConfigPath, 'utf-8'));
|
|
140
|
+
if (tsconfig.compilerOptions?.paths) {
|
|
141
|
+
for (const npmName of npmNames) {
|
|
142
|
+
delete tsconfig.compilerOptions.paths[npmName];
|
|
143
|
+
delete tsconfig.compilerOptions.paths[`${npmName}/*`];
|
|
144
|
+
}
|
|
145
|
+
fs.writeFileSync(mainTsConfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf-8');
|
|
146
|
+
console.log(` ✔ Removed ${[...npmNames].join(', ')} paths from apps/main-app/tsconfig.json`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// 3. Remove entry from dd-apps.config.json (micro-app only)
|
|
150
|
+
if (shouldRemoveConfig) {
|
|
151
|
+
const config = readConfig(paths.configPath);
|
|
152
|
+
if (config.apps && config.apps[name] !== undefined) {
|
|
153
|
+
delete config.apps[name];
|
|
154
|
+
writeConfig(paths.configPath, config);
|
|
155
|
+
console.log(` ✔ Removed ${name} from dd-apps.config.json`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// 4. Remove entry from dd-apps.lock.json
|
|
159
|
+
try {
|
|
160
|
+
const lock = readLockFile(paths.lockFilePath);
|
|
161
|
+
if (name in lock.apps) {
|
|
162
|
+
delete lock.apps[name];
|
|
163
|
+
writeLockFile(paths.lockFilePath, lock);
|
|
164
|
+
console.log(` ✔ Removed ${name} from dd-apps.lock.json`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
console.warn(' ⚠ Could not update lock file');
|
|
169
|
+
}
|
|
170
|
+
// 5. Delete the package directory
|
|
171
|
+
if (managedInstalls.length > 0) {
|
|
172
|
+
for (const install of managedInstalls) {
|
|
173
|
+
fs.rmSync(install.dir, { recursive: true, force: true });
|
|
174
|
+
console.log(` ✔ Deleted directory ${install.dir}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// 6. Run sync
|
|
178
|
+
await sync();
|
|
179
|
+
// 7. Run pnpm install
|
|
180
|
+
console.log('\n📦 Running pnpm install to clean up lockfile...');
|
|
181
|
+
try {
|
|
182
|
+
execSync('pnpm install', { cwd: paths.root, stdio: 'inherit' });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
console.warn('⚠ pnpm install failed. Run it manually.');
|
|
186
|
+
}
|
|
187
|
+
console.log(`
|
|
188
|
+
✅ ${isPayloadPlugin ? 'Payload plugin' : 'Micro-app'} "${name}" uninstalled successfully!
|
|
189
|
+
`);
|
|
190
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read and write the dd-apps.config.json file.
|
|
3
|
+
*
|
|
4
|
+
* Supports both legacy boolean values and extended MicroAppConfigEntry objects:
|
|
5
|
+
* { "meal-planner": true } // legacy
|
|
6
|
+
* { "agent-v2": { "enabled": true, "settings": { "mcp": false } } } // extended
|
|
7
|
+
*/
|
|
8
|
+
export interface MicroAppConfigEntry {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
settings?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface DDAppsConfigFile {
|
|
13
|
+
apps?: Record<string, boolean | MicroAppConfigEntry>;
|
|
14
|
+
}
|
|
15
|
+
/** Check if an app is enabled, handling both boolean and extended entry. */
|
|
16
|
+
export declare function isEntryEnabled(value: boolean | MicroAppConfigEntry | undefined): boolean;
|
|
17
|
+
export declare function readConfig(configPath: string): DDAppsConfigFile;
|
|
18
|
+
export declare function writeConfig(configPath: string, config: DDAppsConfigFile): void;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,mBAAmB,CAAC,CAAC;CACtD;AAMD,4EAA4E;AAC5E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,GAAG,mBAAmB,GAAG,SAAS,GAC/C,OAAO,CAIT;AAMD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAU/D;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAE9E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read and write the dd-apps.config.json file.
|
|
3
|
+
*
|
|
4
|
+
* Supports both legacy boolean values and extended MicroAppConfigEntry objects:
|
|
5
|
+
* { "meal-planner": true } // legacy
|
|
6
|
+
* { "agent-v2": { "enabled": true, "settings": { "mcp": false } } } // extended
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Check if an app is enabled, handling both boolean and extended entry. */
|
|
13
|
+
export function isEntryEnabled(value) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return true; // default enabled
|
|
16
|
+
if (typeof value === 'boolean')
|
|
17
|
+
return value;
|
|
18
|
+
return value.enabled;
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Read / Write
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export function readConfig(configPath) {
|
|
24
|
+
if (!fs.existsSync(configPath)) {
|
|
25
|
+
return { apps: {} };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
console.warn(`⚠ Could not parse ${configPath}, using empty config.`);
|
|
32
|
+
return { apps: {} };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function writeConfig(configPath, config) {
|
|
36
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
37
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @doubledigit/cli — Manage extensions and local setup for Double Digit.
|
|
4
|
+
*
|
|
5
|
+
* Commands are loaded lazily so onboarding/doctor can run from a fresh clone
|
|
6
|
+
* without importing the full extension-management dependency graph first.
|
|
7
|
+
*/
|
|
8
|
+
declare const command: string, args: string[];
|
|
9
|
+
declare const HELP = "\n@doubledigit/cli \u2014 Manage extensions and local setup\n\nCommands:\n doctor Check local prerequisites and project health\n onboard Prepare a local development environment\n run Start the local app with automatic DB bootstrap\n db <subcommand> Database helpers (status, migrate)\n create <name> Scaffold a new micro-app from the template\n add <source> Install an extension from GitHub or a marketplace\n sync Regenerate micro-apps.ts from dd-apps.config.json\n enable <name> Enable a micro-app (updates config + runs sync)\n disable <name> Disable a micro-app (updates config + runs sync)\n uninstall <name> Completely remove a micro-app\n list List all discovered micro-apps with enabled/disabled status\n info <name> Show detailed info about an installed extension\n outdated Check for outdated marketplace extensions\n reconcile Detect drift between lock file, marketplace, and local files\n marketplace <sub> Manage marketplace registrations (add/list/update/remove)\n browse [marketplace] Browse available extensions in registered marketplaces\n\nOptions:\n --help, -h Show this help message\n\nExamples:\n dd doctor\n dd onboard --yes --no-start\n dd run\n dd db status\n dd create invoice-tracker\n dd add gh:owner/repo/extensions/micro-apps/habit-tracker\n dd add habit-tracker@community\n dd info habit-tracker\n dd reconcile\n dd marketplace add digitaldouble/dd-marketplace\n dd browse community\n DD_APPS=tasks,agent-v2 dd sync\n";
|
|
10
|
+
declare function requireArg(value: string | undefined, usage: string): string;
|
|
11
|
+
declare function runAddCommand(rawArgs: string[]): Promise<void>;
|
|
12
|
+
declare function main(): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,QAAA,MAAW,OAAO,UAAK,IAAI,UAAgB,CAAC;AAE5C,QAAA,MAAM,IAAI,yoDAqCT,CAAC;AAEF,iBAAS,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,iBAAe,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB7D;AAED,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmHnC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* @doubledigit/cli — Manage extensions and local setup for Double Digit.
|
|
5
|
+
*
|
|
6
|
+
* Commands are loaded lazily so onboarding/doctor can run from a fresh clone
|
|
7
|
+
* without importing the full extension-management dependency graph first.
|
|
8
|
+
*/
|
|
9
|
+
const [, , command, ...args] = process.argv;
|
|
10
|
+
const HELP = `
|
|
11
|
+
@doubledigit/cli — Manage extensions and local setup
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
doctor Check local prerequisites and project health
|
|
15
|
+
onboard Prepare a local development environment
|
|
16
|
+
run Start the local app with automatic DB bootstrap
|
|
17
|
+
db <subcommand> Database helpers (status, migrate)
|
|
18
|
+
create <name> Scaffold a new micro-app from the template
|
|
19
|
+
add <source> Install an extension from GitHub or a marketplace
|
|
20
|
+
sync Regenerate micro-apps.ts from dd-apps.config.json
|
|
21
|
+
enable <name> Enable a micro-app (updates config + runs sync)
|
|
22
|
+
disable <name> Disable a micro-app (updates config + runs sync)
|
|
23
|
+
uninstall <name> Completely remove a micro-app
|
|
24
|
+
list List all discovered micro-apps with enabled/disabled status
|
|
25
|
+
info <name> Show detailed info about an installed extension
|
|
26
|
+
outdated Check for outdated marketplace extensions
|
|
27
|
+
reconcile Detect drift between lock file, marketplace, and local files
|
|
28
|
+
marketplace <sub> Manage marketplace registrations (add/list/update/remove)
|
|
29
|
+
browse [marketplace] Browse available extensions in registered marketplaces
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--help, -h Show this help message
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
dd doctor
|
|
36
|
+
dd onboard --yes --no-start
|
|
37
|
+
dd run
|
|
38
|
+
dd db status
|
|
39
|
+
dd create invoice-tracker
|
|
40
|
+
dd add gh:owner/repo/extensions/micro-apps/habit-tracker
|
|
41
|
+
dd add habit-tracker@community
|
|
42
|
+
dd info habit-tracker
|
|
43
|
+
dd reconcile
|
|
44
|
+
dd marketplace add digitaldouble/dd-marketplace
|
|
45
|
+
dd browse community
|
|
46
|
+
DD_APPS=tasks,agent-v2 dd sync
|
|
47
|
+
`;
|
|
48
|
+
function requireArg(value, usage) {
|
|
49
|
+
if (!value) {
|
|
50
|
+
console.error(`Error: missing required argument. Usage: ${usage}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
async function runAddCommand(rawArgs) {
|
|
56
|
+
const positional = [];
|
|
57
|
+
let forceFlag = false;
|
|
58
|
+
let nameOverride;
|
|
59
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
60
|
+
if (rawArgs[i] === '--force') {
|
|
61
|
+
forceFlag = true;
|
|
62
|
+
}
|
|
63
|
+
else if (rawArgs[i] === '--name') {
|
|
64
|
+
i++;
|
|
65
|
+
if (i >= rawArgs.length || rawArgs[i].startsWith('--')) {
|
|
66
|
+
console.error('Error: --name requires a value. Usage: dd add <source> --name <name>');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
nameOverride = rawArgs[i];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
positional.push(rawArgs[i]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const source = requireArg(positional[0], 'dd add <source>');
|
|
76
|
+
const { add } = await import('./commands/add.js');
|
|
77
|
+
await add(source, { name: nameOverride, force: forceFlag });
|
|
78
|
+
}
|
|
79
|
+
async function main() {
|
|
80
|
+
switch (command) {
|
|
81
|
+
case 'doctor': {
|
|
82
|
+
const { doctor } = await import('./commands/doctor.js');
|
|
83
|
+
await doctor();
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'onboard': {
|
|
87
|
+
const { onboard } = await import('./commands/onboard.js');
|
|
88
|
+
await onboard(args);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'run': {
|
|
92
|
+
const { run } = await import('./commands/run.js');
|
|
93
|
+
await run();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'db': {
|
|
97
|
+
const { db } = await import('./commands/db.js');
|
|
98
|
+
await db(args);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case 'create': {
|
|
102
|
+
const name = requireArg(args[0], 'dd create <name>');
|
|
103
|
+
const { create } = await import('./commands/create.js');
|
|
104
|
+
await create(name);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'add':
|
|
108
|
+
case 'install':
|
|
109
|
+
await runAddCommand(args);
|
|
110
|
+
break;
|
|
111
|
+
case 'sync': {
|
|
112
|
+
const { sync } = await import('./commands/sync.js');
|
|
113
|
+
await sync();
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'enable': {
|
|
117
|
+
const name = requireArg(args[0], 'dd enable <name>');
|
|
118
|
+
const { enable } = await import('./commands/enable.js');
|
|
119
|
+
await enable(name);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case 'disable': {
|
|
123
|
+
const name = requireArg(args[0], 'dd disable <name>');
|
|
124
|
+
const { disable } = await import('./commands/disable.js');
|
|
125
|
+
await disable(name);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'list': {
|
|
129
|
+
const { list } = await import('./commands/list.js');
|
|
130
|
+
await list();
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'uninstall':
|
|
134
|
+
case 'remove': {
|
|
135
|
+
const name = requireArg(args[0], 'dd uninstall <name>');
|
|
136
|
+
const { uninstall } = await import('./commands/uninstall.js');
|
|
137
|
+
await uninstall(name);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'marketplace':
|
|
141
|
+
case 'mp': {
|
|
142
|
+
const { marketplace } = await import('./commands/marketplace.js');
|
|
143
|
+
await marketplace(args);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'browse': {
|
|
147
|
+
const { browse } = await import('./commands/browse.js');
|
|
148
|
+
await browse(args);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'info': {
|
|
152
|
+
const name = requireArg(args[0], 'dd info <name>');
|
|
153
|
+
const { info } = await import('./commands/info.js');
|
|
154
|
+
await info(name);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'outdated': {
|
|
158
|
+
const { outdated } = await import('./commands/outdated.js');
|
|
159
|
+
await outdated();
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'reconcile': {
|
|
163
|
+
const { reconcile } = await import('./commands/reconcile.js');
|
|
164
|
+
await reconcile();
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case '--help':
|
|
168
|
+
case '-h':
|
|
169
|
+
case undefined:
|
|
170
|
+
console.log(HELP);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
console.error(`Unknown command: ${command}`);
|
|
174
|
+
console.log(HELP);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
main().catch((err) => {
|
|
179
|
+
console.error('Fatal error:', err instanceof Error ? err.message : err);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a GitHub token for private repo access.
|
|
3
|
+
* Preference order:
|
|
4
|
+
* 1. GITHUB_TOKEN env var
|
|
5
|
+
* 2. `gh auth token` from an authenticated GitHub CLI session
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveGitHubToken(): string | undefined;
|
|
8
|
+
//# sourceMappingURL=github-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-auth.d.ts","sourceRoot":"","sources":["../../src/lib/github-auth.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAuBvD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
let cachedGitHubToken;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a GitHub token for private repo access.
|
|
5
|
+
* Preference order:
|
|
6
|
+
* 1. GITHUB_TOKEN env var
|
|
7
|
+
* 2. `gh auth token` from an authenticated GitHub CLI session
|
|
8
|
+
*/
|
|
9
|
+
export function resolveGitHubToken() {
|
|
10
|
+
if (cachedGitHubToken !== undefined) {
|
|
11
|
+
return cachedGitHubToken || undefined;
|
|
12
|
+
}
|
|
13
|
+
const envToken = process.env.GITHUB_TOKEN?.trim();
|
|
14
|
+
if (envToken) {
|
|
15
|
+
cachedGitHubToken = envToken;
|
|
16
|
+
return envToken;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const ghToken = execFileSync('gh', ['auth', 'token'], {
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
22
|
+
}).trim();
|
|
23
|
+
cachedGitHubToken = ghToken || null;
|
|
24
|
+
return ghToken || undefined;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
cachedGitHubToken = null;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage dd-apps.lock.json — tracks externally-installed micro-apps.
|
|
3
|
+
*
|
|
4
|
+
* The lock file lives at the monorepo root and records the source,
|
|
5
|
+
* content hash, and timestamps for every app installed via `dd install`.
|
|
6
|
+
*/
|
|
7
|
+
export interface LockEntry {
|
|
8
|
+
/** Source URL as provided by the user */
|
|
9
|
+
source: string;
|
|
10
|
+
/** Normalized source (gh:owner/repo/path#ref) */
|
|
11
|
+
resolvedSource: string;
|
|
12
|
+
/** SHA-256 hash of all files in the installed package */
|
|
13
|
+
contentHash: string;
|
|
14
|
+
/** Git ref that was installed (branch, tag, SHA) */
|
|
15
|
+
ref?: string;
|
|
16
|
+
/** The actual npm package name from package.json (e.g. @community/habit-tracker) */
|
|
17
|
+
npmName?: string;
|
|
18
|
+
/** ISO timestamp of installation */
|
|
19
|
+
installedAt: string;
|
|
20
|
+
/** ISO timestamp of last update */
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
/** Extension kind */
|
|
23
|
+
kind?: 'micro-app' | 'payload-plugin';
|
|
24
|
+
/** Which marketplace this was installed from */
|
|
25
|
+
marketplace?: string;
|
|
26
|
+
/** Pinned commit SHA for reproducibility */
|
|
27
|
+
sha?: string;
|
|
28
|
+
/** Marketplace version at time of install (e.g. "1.2.0") */
|
|
29
|
+
marketplaceVersion?: string;
|
|
30
|
+
/** Relative path within monorepo (e.g. "packages/habit-tracker") */
|
|
31
|
+
installPath?: string;
|
|
32
|
+
/** Install strategy used */
|
|
33
|
+
installStrategy?: 'workspace-vendor' | 'node-modules';
|
|
34
|
+
}
|
|
35
|
+
export interface LockFile {
|
|
36
|
+
version: 1;
|
|
37
|
+
apps: Record<string, LockEntry>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read and parse the lock file. Returns an empty lock structure if the file
|
|
41
|
+
* does not exist. Throws on malformed files to avoid silent data loss.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readLockFile(lockPath: string): LockFile;
|
|
44
|
+
/**
|
|
45
|
+
* Write the lock file to disk with deterministic key ordering.
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeLockFile(lockPath: string, lock: LockFile): void;
|
|
48
|
+
/**
|
|
49
|
+
* Add or update a single entry in the lock file.
|
|
50
|
+
*/
|
|
51
|
+
export declare function addLockEntry(lockPath: string, key: string, entry: LockEntry): void;
|
|
52
|
+
/**
|
|
53
|
+
* Remove an entry from the lock file. No-op if the key doesn't exist.
|
|
54
|
+
*/
|
|
55
|
+
export declare function removeLockEntry(lockPath: string, key: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Check whether an app was externally installed (i.e. tracked in the lock file).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isExternalApp(lockPath: string, key: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Compute a deterministic SHA-256 content hash of every file in a directory.
|
|
62
|
+
*
|
|
63
|
+
* Excludes `node_modules`, `.turbo`, and `dist`. File paths are sorted for
|
|
64
|
+
* determinism and the hash covers both path and content.
|
|
65
|
+
*/
|
|
66
|
+
export declare function computeContentHash(dir: string): Promise<string>;
|
|
67
|
+
//# sourceMappingURL=lock-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-file.d.ts","sourceRoot":"","sources":["../../src/lib/lock-file.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB;IACrB,IAAI,CAAC,EAAE,WAAW,GAAG,gBAAgB,CAAC;IACtC,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,kBAAkB,GAAG,cAAc,CAAC;CACvD;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACjC;AAOD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAqCvD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAUpE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,GACf,IAAI,CAIN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAMnE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAGpE;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBrE"}
|