@doubledigit/cli 0.2.1 → 0.3.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/README.md CHANGED
@@ -16,17 +16,21 @@ pnpm dlx @doubledigit/cli init my-project
16
16
  Inside a Double Digit project, use the repo-local shortcut:
17
17
 
18
18
  ```bash
19
+ pnpm dev
19
20
  pnpm dd doctor
20
- pnpm dd onboard
21
- pnpm dd run
21
+ pnpm onboard
22
+ pnpm onboard -- --no-run
23
+ pnpm dd actions remotion-hub
22
24
  ```
23
25
 
24
26
  If installed globally, the binary is available as `dd`:
25
27
 
26
28
  ```bash
29
+ dd dev
27
30
  dd doctor
28
31
  dd onboard
29
- dd run
32
+ dd onboard --no-run
33
+ dd actions remotion-hub
30
34
  ```
31
35
 
32
36
  ## Requirements
@@ -45,17 +49,22 @@ corepack enable
45
49
  ```bash
46
50
  dd init <project-name> # scaffold a new Double Digit project
47
51
  dd doctor # check local prerequisites and project health
48
- dd onboard # prepare env, dependencies, DB, migrations, and types
49
- dd run # bootstrap the runtime DB and start the app
52
+ dd onboard # prepare env, dependencies, DB, migrations, types, and start
53
+ dd onboard --no-run # setup only
54
+ dd run # lower-level runtime bootstrap/start command
55
+ dd dev # start DB + app without migrations or seed data
50
56
  dd db status # inspect migration state
51
57
  dd db migrate # run migrations
52
58
  dd db create <target> <name> # create a migration for shared or micro-app schema
59
+ dd actions <app> [action] # discover and invoke micro-app actions
53
60
  dd add <source> # install an extension
54
61
  dd sync # regenerate workspace registries
55
62
  dd list # list discovered micro-apps
56
63
  dd enable <name> # enable a micro-app
57
64
  dd disable <name> # disable a micro-app
58
65
  dd remove <name> # remove a micro-app package and wiring
66
+ dd info <name> # show detailed extension metadata
67
+ dd outdated # compare marketplace installs against cached catalogs
59
68
  dd reconcile # detect registry, lock file, and package drift
60
69
  dd marketplace <subcommand> # manage marketplace registrations
61
70
  dd browse # browse marketplace extensions
@@ -68,7 +77,7 @@ dd browse # browse marketplace extensions
68
77
  ```bash
69
78
  npx @doubledigit/cli init my-project --yes
70
79
  cd my-project
71
- pnpm dd run
80
+ pnpm onboard
72
81
  ```
73
82
 
74
83
  Useful `init` options:
@@ -80,20 +89,37 @@ Useful `init` options:
80
89
 
81
90
  ## Existing Project Setup
82
91
 
83
- Use `onboard` for setup-only work:
92
+ Use `onboard` for first-time setup and startup:
84
93
 
85
94
  ```bash
86
- pnpm dd onboard
95
+ pnpm onboard
87
96
  ```
88
97
 
89
- Use `run` when you want the CLI to prepare the runtime database and then start the app:
98
+ Pass `--no-run` when you want setup-only work:
90
99
 
91
100
  ```bash
92
- pnpm dd run
101
+ pnpm onboard -- --no-run
93
102
  ```
94
103
 
95
104
  Leave `DATABASE_URL` empty in `apps/main-app/.env` to use the default CLI-managed embedded PostgreSQL flow. Provide `DATABASE_URL` through the environment or `.env` when using your own PostgreSQL instance.
96
105
 
106
+ Use `dd dev` when you want the app and database ready without running migrations or seed data. It defaults to port `3000` and accepts `--port`, for example:
107
+
108
+ ```bash
109
+ dd dev --port 3000
110
+ ```
111
+
112
+ ## Action Invocations
113
+
114
+ Use `dd actions` to discover or call enabled micro-app actions through the running app:
115
+
116
+ ```bash
117
+ dd actions remotion-hub
118
+ dd actions remotion-hub search-components --query "animated chart" --limit 5
119
+ ```
120
+
121
+ The command resolves the app URL from `DD_APP_URL`, `APP_URL`, or `BETTER_AUTH_URL` and prints JSON responses to stdout.
122
+
97
123
  ## Links
98
124
 
99
125
  - Repository: https://github.com/crystalphantom/double-digit
@@ -0,0 +1,2 @@
1
+ export declare function actions(args: string[]): Promise<void>;
2
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/commands/actions.ts"],"names":[],"mappings":"AAyDA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAa3D"}
@@ -0,0 +1,59 @@
1
+ import { readConfig, isEntryEnabled } from '../config.js';
2
+ import { DEFAULT_APP_URL } from '../lib/onboarding.js';
3
+ import { buildActionRequest, fetchActionRequest, parseActionsArgs, } from '../lib/actions-client.js';
4
+ import { resolveWorkspacePaths } from '../paths.js';
5
+ import { scanWorkspace } from '../scanner.js';
6
+ const HELP = `
7
+ dd actions — Discover and invoke micro-app actions
8
+
9
+ Usage:
10
+ dd actions <micro-app>
11
+ dd actions <micro-app> <action> [options]
12
+
13
+ Options:
14
+ --url, --app-url <url> App URL (default: DD_APP_URL, APP_URL, or ${DEFAULT_APP_URL})
15
+ --json <json> JSON request body to send to the action
16
+ --query <text> Set input.query
17
+ --limit <number> Set input.limit
18
+ --namespace <namespace> Set input namespace/filter
19
+ --slug <slug> Set input.slug
20
+ --id-or-slug <value> Set input.idOrSlug
21
+ --kind <kind> Set input kind/filter
22
+ --preview-type <type> Set input previewType/filter
23
+ --tag, --tags <tags> Add one tag or comma-separated tags to filters.tags
24
+
25
+ Examples:
26
+ dd actions remotion-hub
27
+ dd actions remotion-hub search-components --query "animated chart" --limit 5
28
+ dd actions remotion-hub get-component --namespace doubledigit --slug motion-strip
29
+ dd actions remotion-hub get-registry-payload --json '{"namespace":"doubledigit","slug":"motion-strip"}'
30
+ `;
31
+ function resolveAppUrl(parsedUrl) {
32
+ return parsedUrl
33
+ ?? process.env.DD_APP_URL
34
+ ?? process.env.APP_URL
35
+ ?? process.env.BETTER_AUTH_URL
36
+ ?? DEFAULT_APP_URL;
37
+ }
38
+ function validateMicroApp(microApp) {
39
+ const paths = resolveWorkspacePaths();
40
+ const app = scanWorkspace(paths).microApps.find((candidate) => candidate.key === microApp);
41
+ if (!app) {
42
+ throw new Error(`Unknown micro-app: ${microApp}`);
43
+ }
44
+ const config = readConfig(paths.configPath);
45
+ if (!isEntryEnabled(config.apps?.[microApp])) {
46
+ throw new Error(`Micro-app is disabled: ${microApp}`);
47
+ }
48
+ }
49
+ export async function actions(args) {
50
+ const parsed = parseActionsArgs(args);
51
+ if (parsed.help || !parsed.microApp) {
52
+ console.log(HELP);
53
+ return;
54
+ }
55
+ validateMicroApp(parsed.microApp);
56
+ const request = buildActionRequest(parsed, resolveAppUrl(parsed.appUrl));
57
+ const result = await fetchActionRequest(request);
58
+ console.log(JSON.stringify(result, null, 2));
59
+ }
@@ -8,7 +8,7 @@ async function dbStatus(args) {
8
8
  const databaseMode = databasePreference.mode;
9
9
  const databaseUrl = databasePreference.databaseUrl;
10
10
  if (!databaseUrl) {
11
- console.error('Error: no database configuration is available. Run `pnpm dd onboard` first.');
11
+ console.error('Error: no database configuration is available. Run `pnpm onboard` first.');
12
12
  process.exit(1);
13
13
  }
14
14
  const info = parseDatabaseUrl(databaseUrl);
@@ -0,0 +1,2 @@
1
+ export declare function dev(args?: string[]): Promise<void>;
2
+ //# sourceMappingURL=dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA+BA,wBAAsB,GAAG,CAAC,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC5D"}
@@ -0,0 +1,44 @@
1
+ import { resolveWorkspacePaths } from '../paths.js';
2
+ import { applyRuntimeDatabaseUrl, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, startDevServer, } from '../lib/onboarding.js';
3
+ function applyDevArgs(args) {
4
+ for (let index = 0; index < args.length; index++) {
5
+ const arg = args[index];
6
+ if (arg === '--port' || arg === '-p') {
7
+ const value = args[index + 1];
8
+ if (!value || value.startsWith('-')) {
9
+ throw new Error(`${arg} requires a port value.`);
10
+ }
11
+ process.env.APP_PORT = value;
12
+ index++;
13
+ continue;
14
+ }
15
+ if (arg.startsWith('--port=')) {
16
+ process.env.APP_PORT = arg.slice('--port='.length);
17
+ continue;
18
+ }
19
+ throw new Error(`Unsupported pnpm dev argument: ${arg}`);
20
+ }
21
+ }
22
+ export async function dev(args = []) {
23
+ applyDevArgs(args);
24
+ process.env.APP_PORT ??= '3000';
25
+ const paths = resolveWorkspacePaths();
26
+ const envSetup = ensureLocalEnv(paths);
27
+ const runtime = await ensureDatabaseReady(paths, {
28
+ yes: true,
29
+ allowDockerFallback: true,
30
+ });
31
+ const runtimeEnv = applyRuntimeDatabaseUrl(ensureLocalEnv(paths, databaseRuntimeToEnvOptions(runtime)).env, runtime);
32
+ console.log(`✔ ${runtime.mode === 'embedded' ? 'Embedded' : runtime.mode === 'docker' ? 'Docker' : 'External'} PostgreSQL is ready`);
33
+ if (runtime.mode === 'embedded' && runtime.embeddedPort) {
34
+ console.log(` Port: ${runtime.embeddedPort}`);
35
+ }
36
+ if (runtime.mode === 'docker' && runtime.dockerPort) {
37
+ console.log(` Docker host port: ${runtime.dockerPort}`);
38
+ }
39
+ if (envSetup.updatedKeys.length > 0) {
40
+ console.log(` Updated ${envSetup.envPath.replace(`${paths.root}/`, '')}: ${envSetup.updatedKeys.join(', ')}`);
41
+ }
42
+ console.log('Starting development server without running migrations or seed data...\n');
43
+ await startDevServer(paths, runtimeEnv);
44
+ }
@@ -62,19 +62,19 @@ export async function doctor() {
62
62
  ? embeddedSupport.supported
63
63
  ? dbReachable
64
64
  ? 'Embedded PostgreSQL is reachable.'
65
- : 'Embedded PostgreSQL is not running yet. `pnpm dev` or `dd run` will start it automatically.'
65
+ : 'Embedded PostgreSQL is not running yet. `pnpm onboard` will start it automatically.'
66
66
  : embeddedSupport.reason || 'Embedded PostgreSQL is not supported on this host.'
67
67
  : dbReachable
68
68
  ? 'PostgreSQL is reachable from DATABASE_URL.'
69
69
  : dockerAvailable()
70
- ? 'Database is not reachable yet. Run `pnpm dd onboard` or start Docker/Postgres manually.'
70
+ ? 'Database is not reachable yet. Run `pnpm onboard` or start Docker/Postgres manually.'
71
71
  : 'Database is not reachable. Start PostgreSQL or update DATABASE_URL.',
72
72
  });
73
73
  if (databaseMode === 'embedded' && embeddedSupport.supported && !dbReachable) {
74
74
  checks.push({
75
75
  label: 'Migration status',
76
76
  ok: true,
77
- detail: 'Skipped until embedded PostgreSQL starts. `pnpm dev` or `dd run` will start it automatically.',
77
+ detail: 'Skipped until embedded PostgreSQL starts. `pnpm onboard` will start it automatically.',
78
78
  });
79
79
  }
80
80
  else {
@@ -113,8 +113,8 @@ export async function doctor() {
113
113
  const failed = checks.filter((check) => !check.ok);
114
114
  if (failed.length > 0) {
115
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');
116
+ console.log(' pnpm onboard # fix setup issues and start the app');
117
+ console.log(' pnpm onboard -- --no-run # setup only\n');
118
118
  process.exitCode = 1;
119
119
  return;
120
120
  }
@@ -1 +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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAyJA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ExD"}
@@ -96,8 +96,7 @@ function printInitNextSteps(projectName, skipInstall) {
96
96
  console.log(`cd ${projectName}`);
97
97
  if (skipInstall) {
98
98
  console.log('pnpm install');
99
- console.log('pnpm dd onboard');
100
- console.log('pnpm dd run');
99
+ console.log('pnpm onboard');
101
100
  return;
102
101
  }
103
102
  printNextSteps();
@@ -1,2 +1,9 @@
1
+ interface OnboardOptions {
2
+ yes: boolean;
3
+ run: boolean;
4
+ help: boolean;
5
+ }
6
+ export declare function parseOnboardArgs(args: string[], env?: NodeJS.ProcessEnv): OnboardOptions;
1
7
  export declare function onboard(args: string[]): Promise<void>;
8
+ export {};
2
9
  //# sourceMappingURL=onboard.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAaA,UAAU,cAAc;IACtB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf;AAmBD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CA4BhB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyD3D"}
@@ -1,29 +1,51 @@
1
1
  import { resolveWorkspacePaths } from '../paths.js';
2
2
  import { applyRuntimeDatabaseUrl, captureCommand, databaseRuntimeToEnvOptions, ensureDatabaseReady, ensureLocalEnv, localDependenciesInstalled, printNextSteps, runChecked, startDevServer, } from '../lib/onboarding.js';
3
- function parseOnboardArgs(args) {
3
+ const ONBOARD_HELP = `Usage: dd onboard [--yes] [--no-run]
4
+
5
+ Prepare the local development environment and start the app.
6
+
7
+ Options:
8
+ --yes, -y Skip interactive prompts and use defaults.
9
+ --no-run Run setup only and exit without starting Next.js.
10
+ --no-start Alias for --no-run.
11
+ --setup-only Alias for --no-run.
12
+ --start Compatibility alias; running is now the default.
13
+ --help, -h Show this help message.
14
+
15
+ Examples:
16
+ dd onboard
17
+ dd onboard --no-run
18
+ APP_PORT=3111 dd onboard`;
19
+ export function parseOnboardArgs(args, env = process.env) {
4
20
  let yes = false;
5
- let start = false;
21
+ let run = true;
22
+ let help = false;
6
23
  for (const arg of args) {
24
+ if (arg === '--help' || arg === '-h') {
25
+ help = true;
26
+ }
7
27
  if (arg === '--yes' || arg === '-y') {
8
28
  yes = true;
9
29
  }
10
30
  if (arg === '--start') {
11
- start = true;
31
+ run = true;
12
32
  }
13
- // Keep --no-start and --setup-only as compatibility aliases (no-ops now
14
- // that the default is already no-start).
15
- if (arg === '--no-start' || arg === '--setup-only') {
16
- start = false;
33
+ if (arg === '--no-run' || arg === '--no-start' || arg === '--setup-only') {
34
+ run = false;
17
35
  }
18
36
  }
19
- if (process.env.CI === 'true') {
20
- start = false;
37
+ if (env.CI === 'true') {
38
+ run = false;
21
39
  }
22
- return { yes, start };
40
+ return { yes, run, help };
23
41
  }
24
42
  export async function onboard(args) {
25
- const paths = resolveWorkspacePaths();
26
43
  const options = parseOnboardArgs(args);
44
+ if (options.help) {
45
+ console.log(ONBOARD_HELP);
46
+ return;
47
+ }
48
+ const paths = resolveWorkspacePaths();
27
49
  console.log('\n🚀 Double Digit onboarding\n');
28
50
  const envSetup = ensureLocalEnv(paths);
29
51
  console.log(`✔ ${envSetup.created ? 'Created' : 'Found'} ${envSetup.envPath.replace(`${paths.root}/`, '')}`);
@@ -54,7 +76,7 @@ export async function onboard(args) {
54
76
  console.log('\nMigration status:');
55
77
  console.log(migrationStatus.output.trim());
56
78
  }
57
- if (options.start) {
79
+ if (options.run) {
58
80
  console.log('\nStarting development server...\n');
59
81
  await startDevServer(paths, runtimeEnv);
60
82
  return;
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * without importing the full extension-management dependency graph first.
7
7
  */
8
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 (setup only)\n run Start the local app with automatic DB bootstrap\n db <subcommand> Database helpers (status, migrate, create)\n create <name> Scaffold a new micro-app from the template\n init <project-name> Scaffold a new Double Digit project\n add|install <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|remove <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\n dd onboard --start # setup + start the dev server\n dd init my-project # scaffold and bootstrap a fresh project\n dd init my-project --run # scaffold + bootstrap + start\n dd init my-project --skip-install --no-git\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";
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 local development and start the app\n run Start the local app with automatic DB bootstrap\n dev Start DB + app without migrations or seed data\n db <subcommand> Database helpers (status, migrate, create)\n actions <app> [action] Discover and invoke micro-app actions\n create <name> Scaffold a new micro-app from the template\n init <project-name> Scaffold a new Double Digit project\n add|install <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|remove <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\n dd onboard # setup + start the dev server\n dd onboard --no-run # setup only\n dd init my-project # scaffold and bootstrap a fresh project\n dd init my-project --run # scaffold + bootstrap + start\n dd init my-project --skip-install --no-git\n dd run\n dd dev\n dd db status\n dd actions remotion-hub search-components --query \"animated chart\"\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
10
  declare function requireArg(value: string | undefined, usage: string): string;
11
11
  declare function runAddCommand(rawArgs: string[]): Promise<void>;
12
12
  declare function main(): Promise<void>;
@@ -1 +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,y8DA0CT,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,CAyHnC"}
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,ysEA+CT,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,CAsInC"}
package/dist/index.js CHANGED
@@ -12,9 +12,11 @@ const HELP = `
12
12
 
13
13
  Commands:
14
14
  doctor Check local prerequisites and project health
15
- onboard Prepare a local development environment (setup only)
15
+ onboard Prepare local development and start the app
16
16
  run Start the local app with automatic DB bootstrap
17
+ dev Start DB + app without migrations or seed data
17
18
  db <subcommand> Database helpers (status, migrate, create)
19
+ actions <app> [action] Discover and invoke micro-app actions
18
20
  create <name> Scaffold a new micro-app from the template
19
21
  init <project-name> Scaffold a new Double Digit project
20
22
  add|install <source> Install an extension from GitHub or a marketplace
@@ -35,12 +37,15 @@ Options:
35
37
  Examples:
36
38
  dd doctor
37
39
  dd onboard --yes
38
- dd onboard --start # setup + start the dev server
40
+ dd onboard # setup + start the dev server
41
+ dd onboard --no-run # setup only
39
42
  dd init my-project # scaffold and bootstrap a fresh project
40
43
  dd init my-project --run # scaffold + bootstrap + start
41
44
  dd init my-project --skip-install --no-git
42
45
  dd run
46
+ dd dev
43
47
  dd db status
48
+ dd actions remotion-hub search-components --query "animated chart"
44
49
  dd create invoice-tracker
45
50
  dd add gh:owner/repo/extensions/micro-apps/habit-tracker
46
51
  dd add habit-tracker@community
@@ -98,11 +103,22 @@ async function main() {
98
103
  await run();
99
104
  break;
100
105
  }
106
+ case 'dev': {
107
+ const { dev } = await import('./commands/dev.js');
108
+ await dev(args);
109
+ break;
110
+ }
101
111
  case 'db': {
102
112
  const { db } = await import('./commands/db.js');
103
113
  await db(args);
104
114
  break;
105
115
  }
116
+ case 'actions':
117
+ case 'action': {
118
+ const { actions } = await import('./commands/actions.js');
119
+ await actions(args);
120
+ break;
121
+ }
106
122
  case 'create': {
107
123
  const name = requireArg(args[0], 'dd create <name>');
108
124
  const { create } = await import('./commands/create.js');
@@ -0,0 +1,16 @@
1
+ export interface ParsedActionsArgs {
2
+ microApp?: string;
3
+ actionName?: string;
4
+ appUrl?: string;
5
+ input: Record<string, unknown>;
6
+ help: boolean;
7
+ }
8
+ export interface ActionRequest {
9
+ url: string;
10
+ init: RequestInit;
11
+ }
12
+ export declare function parseActionsArgs(args: string[]): ParsedActionsArgs;
13
+ export declare function normalizeAppUrl(appUrl: string): string;
14
+ export declare function buildActionRequest(parsed: Pick<ParsedActionsArgs, 'microApp' | 'actionName' | 'input'>, appUrl: string): ActionRequest;
15
+ export declare function fetchActionRequest(request: ActionRequest): Promise<unknown>;
16
+ //# sourceMappingURL=actions-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions-client.d.ts","sourceRoot":"","sources":["../../src/lib/actions-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;CACnB;AA2DD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAsDlE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,UAE7C;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAoBtI;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAajF"}
@@ -0,0 +1,150 @@
1
+ function requireValue(args, index, flag) {
2
+ const value = args[index + 1];
3
+ if (!value || value.startsWith('--')) {
4
+ throw new Error(`${flag} requires a value`);
5
+ }
6
+ return value;
7
+ }
8
+ function parseJson(value) {
9
+ try {
10
+ return JSON.parse(value);
11
+ }
12
+ catch {
13
+ throw new Error('--json must be valid JSON');
14
+ }
15
+ }
16
+ function asFilters(input) {
17
+ const filters = input.filters;
18
+ if (filters && typeof filters === 'object' && !Array.isArray(filters)) {
19
+ return filters;
20
+ }
21
+ const next = {};
22
+ input.filters = next;
23
+ return next;
24
+ }
25
+ function setInputValue(input, key, value) {
26
+ if (key === 'limit') {
27
+ const numeric = Number(value);
28
+ if (!Number.isFinite(numeric) || numeric <= 0) {
29
+ throw new Error('--limit must be a positive number');
30
+ }
31
+ input.limit = numeric;
32
+ return;
33
+ }
34
+ if (key === 'namespace' || key === 'kind' || key === 'previewType') {
35
+ input[key] = value;
36
+ asFilters(input)[key] = value;
37
+ return;
38
+ }
39
+ input[key] = value;
40
+ }
41
+ function addTag(input, value) {
42
+ const tags = value
43
+ .split(',')
44
+ .map((tag) => tag.trim())
45
+ .filter(Boolean);
46
+ if (tags.length === 0)
47
+ return;
48
+ const filters = asFilters(input);
49
+ const existing = Array.isArray(filters.tags) ? filters.tags : [];
50
+ filters.tags = [...existing, ...tags];
51
+ }
52
+ export function parseActionsArgs(args) {
53
+ const input = {};
54
+ const positional = [];
55
+ let appUrl;
56
+ let help = false;
57
+ for (let i = 0; i < args.length; i++) {
58
+ const arg = args[i];
59
+ if (arg === '--help' || arg === '-h') {
60
+ help = true;
61
+ }
62
+ else if (arg === '--url' || arg === '--app-url') {
63
+ appUrl = requireValue(args, i, arg);
64
+ i++;
65
+ }
66
+ else if (arg === '--json') {
67
+ Object.assign(input, parseJson(requireValue(args, i, arg)));
68
+ i++;
69
+ }
70
+ else if (arg === '--query') {
71
+ setInputValue(input, 'query', requireValue(args, i, arg));
72
+ i++;
73
+ }
74
+ else if (arg === '--limit') {
75
+ setInputValue(input, 'limit', requireValue(args, i, arg));
76
+ i++;
77
+ }
78
+ else if (arg === '--namespace') {
79
+ setInputValue(input, 'namespace', requireValue(args, i, arg));
80
+ i++;
81
+ }
82
+ else if (arg === '--slug') {
83
+ setInputValue(input, 'slug', requireValue(args, i, arg));
84
+ i++;
85
+ }
86
+ else if (arg === '--id-or-slug') {
87
+ setInputValue(input, 'idOrSlug', requireValue(args, i, arg));
88
+ i++;
89
+ }
90
+ else if (arg === '--kind') {
91
+ setInputValue(input, 'kind', requireValue(args, i, arg));
92
+ i++;
93
+ }
94
+ else if (arg === '--preview-type') {
95
+ setInputValue(input, 'previewType', requireValue(args, i, arg));
96
+ i++;
97
+ }
98
+ else if (arg === '--tag' || arg === '--tags') {
99
+ addTag(input, requireValue(args, i, arg));
100
+ i++;
101
+ }
102
+ else if (arg.startsWith('--')) {
103
+ throw new Error(`Unknown option: ${arg}`);
104
+ }
105
+ else {
106
+ positional.push(arg);
107
+ }
108
+ }
109
+ return {
110
+ microApp: positional[0],
111
+ actionName: positional[1],
112
+ appUrl,
113
+ input,
114
+ help,
115
+ };
116
+ }
117
+ export function normalizeAppUrl(appUrl) {
118
+ return appUrl.trim().replace(/\/+$/, '');
119
+ }
120
+ export function buildActionRequest(parsed, appUrl) {
121
+ if (!parsed.microApp) {
122
+ throw new Error('missing micro-app name');
123
+ }
124
+ const base = normalizeAppUrl(appUrl);
125
+ const actionPath = parsed.actionName
126
+ ? `/${encodeURIComponent(parsed.actionName)}`
127
+ : '';
128
+ return {
129
+ url: `${base}/api/${encodeURIComponent(parsed.microApp)}/actions${actionPath}`,
130
+ init: parsed.actionName
131
+ ? {
132
+ method: 'POST',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify(parsed.input),
135
+ }
136
+ : { method: 'GET' },
137
+ };
138
+ }
139
+ export async function fetchActionRequest(request) {
140
+ const response = await fetch(request.url, request.init);
141
+ const text = await response.text();
142
+ const payload = text ? JSON.parse(text) : null;
143
+ if (!response.ok) {
144
+ const error = payload && typeof payload === 'object' && 'error' in payload
145
+ ? String(payload.error)
146
+ : `Request failed with HTTP ${response.status}`;
147
+ throw new Error(error);
148
+ }
149
+ return payload;
150
+ }
@@ -493,9 +493,9 @@ export async function startDevServer(paths, envOverrides = {}) {
493
493
  }
494
494
  export function printNextSteps() {
495
495
  console.log("\n✅ Setup complete! Next steps:\n");
496
- console.log(" pnpm dev # start the development server");
497
- console.log(` open ${DEFAULT_APP_URL}`);
498
- console.log(` open ${buildAppUrl(DEFAULT_APP_PORT)}/admin`);
496
+ console.log(" pnpm dev # start DB + app without migrations (default port 3000)");
497
+ console.log(" pnpm onboard # setup + start through the DD CLI (default port 3111)");
498
+ console.log(" open http://localhost:3000");
499
499
  console.log("");
500
500
  }
501
501
  function createEmbeddedPostgresLogBuffer(limit = EMBEDDED_LOG_BUFFER_LIMIT) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doubledigit/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "CLI for Double Digit local setup and extension management.",
6
6
  "license": "MIT",