@ductape/cli 0.3.17 → 0.3.19

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.
@@ -0,0 +1,8 @@
1
+ import { type CommandInteractiveFlags } from '../lib/interactive-opts.js';
2
+ export declare function runAppActions(verb: string, opts: {
3
+ profile?: string;
4
+ app?: string;
5
+ action?: string;
6
+ file?: string;
7
+ json?: boolean;
8
+ } & CommandInteractiveFlags): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import { toInteractiveOpts } from '../lib/interactive-opts.js';
2
+ import { bodyRequiredHint, resolveBody } from '../lib/read-body.js';
3
+ import { createAppAction, deleteAppAction, getAppAction, listAppActions, updateAppAction, } from '../lib/platform-api.js';
4
+ import { printJson } from '../lib/output.js';
5
+ import { requireWorkspaceContext } from '../lib/workspace-context.js';
6
+ const VERBS = ['list', 'get', 'create', 'update', 'delete'];
7
+ export async function runAppActions(verb, opts) {
8
+ const v = verb.toLowerCase();
9
+ if (!VERBS.includes(v)) {
10
+ throw new Error(`Unknown apps actions verb "${verb}". Use: ${VERBS.join(', ')}`);
11
+ }
12
+ if (!opts.app)
13
+ throw new Error('Provide --app <app_tag>');
14
+ const ctx = requireWorkspaceContext({ profile: opts.profile });
15
+ const interactive = toInteractiveOpts(opts);
16
+ const body = v === 'create' || v === 'update'
17
+ ? await resolveBody({
18
+ ...interactive,
19
+ file: opts.file,
20
+ patch: v === 'update',
21
+ requireBody: v === 'create',
22
+ })
23
+ : undefined;
24
+ let result;
25
+ switch (v) {
26
+ case 'list':
27
+ result = await listAppActions(ctx, opts.app);
28
+ break;
29
+ case 'get':
30
+ if (!opts.action)
31
+ throw new Error('Provide --action <action_tag>');
32
+ result = await getAppAction(ctx, opts.app, opts.action);
33
+ break;
34
+ case 'create':
35
+ if (!body || Object.keys(body).length === 0) {
36
+ throw new Error(`create requires a body. ${bodyRequiredHint('apps:actions:create')}`);
37
+ }
38
+ result = await createAppAction(ctx, opts.app, body);
39
+ break;
40
+ case 'update': {
41
+ const actionTag = opts.action ?? body?.tag;
42
+ if (!actionTag)
43
+ throw new Error('update requires --action <action_tag>');
44
+ result = await updateAppAction(ctx, opts.app, actionTag, body ?? {});
45
+ break;
46
+ }
47
+ case 'delete':
48
+ if (!opts.action)
49
+ throw new Error('Provide --action <action_tag>');
50
+ result = await deleteAppAction(ctx, opts.app, opts.action);
51
+ break;
52
+ default:
53
+ throw new Error(`Unsupported: apps actions ${v}`);
54
+ }
55
+ printJson(result, Boolean(opts.json));
56
+ }
@@ -2,4 +2,22 @@ export declare function runDb(method: string, opts: {
2
2
  file?: string;
3
3
  json?: boolean;
4
4
  }): Promise<void>;
5
+ /**
6
+ * Database Actions — reusable, parameterized query/mutation definitions saved against a
7
+ * database component (databases.action.* in the SDK). This is administrative metadata
8
+ * (uses the access-key-backed sdk-proxy, same as `resources <type> create`), not a runtime
9
+ * query — it never connects to the live database. Required for any Feature step that calls
10
+ * ctx.database.query/insert/update/delete, since those take an `event` tag pointing at one
11
+ * of these, not a raw {table, where} shape.
12
+ *
13
+ * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
14
+ * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
15
+ * that already has that product's builder cached, which a fresh CLI invocation never has).
16
+ */
17
+ export declare function runDbActions(verb: string, opts: {
18
+ tag?: string;
19
+ database?: string;
20
+ file?: string;
21
+ json?: boolean;
22
+ }, extraArgs: string[]): Promise<void>;
5
23
  export declare function runDbContext(json: boolean): void;
@@ -1,7 +1,7 @@
1
1
  import { readJsonBody } from '../lib/read-body.js';
2
2
  import { setDatabaseContext } from '../lib/context-store.js';
3
3
  import { DB_PROXY_METHODS } from '../lib/db-methods.js';
4
- import { getDbContext, getDbProxy, requireSession } from '../lib/proxy/context.js';
4
+ import { getDbContext, getDbProxy, getSdkProxy, requireSession } from '../lib/proxy/context.js';
5
5
  import { loadRuntimeContext } from '../lib/context-store.js';
6
6
  import { printJson } from '../lib/output.js';
7
7
  export async function runDb(method, opts) {
@@ -47,6 +47,72 @@ export async function runDb(method, opts) {
47
47
  }
48
48
  printJson(result, Boolean(opts.json));
49
49
  }
50
+ const DB_ACTION_VERBS = ['create', 'update', 'get', 'list', 'delete'];
51
+ /**
52
+ * Database Actions — reusable, parameterized query/mutation definitions saved against a
53
+ * database component (databases.action.* in the SDK). This is administrative metadata
54
+ * (uses the access-key-backed sdk-proxy, same as `resources <type> create`), not a runtime
55
+ * query — it never connects to the live database. Required for any Feature step that calls
56
+ * ctx.database.query/insert/update/delete, since those take an `event` tag pointing at one
57
+ * of these, not a raw {table, where} shape.
58
+ *
59
+ * Tag format is always "product_tag:database_tag:action_tag" — always pass it fully
60
+ * qualified here (the SDK's 2-part fallback only works inside a single long-lived process
61
+ * that already has that product's builder cached, which a fresh CLI invocation never has).
62
+ */
63
+ export async function runDbActions(verb, opts, extraArgs) {
64
+ const v = verb.toLowerCase();
65
+ if (!DB_ACTION_VERBS.includes(v)) {
66
+ throw new Error(`Unknown db actions verb "${verb}". Use: ${DB_ACTION_VERBS.join(', ')}`);
67
+ }
68
+ const proxy = getSdkProxy();
69
+ const tag = opts.tag ?? extraArgs[0];
70
+ let method;
71
+ let params;
72
+ switch (v) {
73
+ case 'create': {
74
+ const body = readJsonBody(opts.file);
75
+ if (!body || Object.keys(body).length === 0) {
76
+ throw new Error('create requires a body: { tag: "product:database:action", name, tableName, ' +
77
+ 'operation, template, description?, filterTemplate? } (pass -f <file.json>).');
78
+ }
79
+ method = 'action.create';
80
+ params = [body];
81
+ break;
82
+ }
83
+ case 'update': {
84
+ if (!tag)
85
+ throw new Error('update requires the action tag ("product:database:action")');
86
+ const body = readJsonBody(opts.file) ?? {};
87
+ method = 'action.update';
88
+ params = [{ tag, ...body }];
89
+ break;
90
+ }
91
+ case 'get':
92
+ if (!tag)
93
+ throw new Error('get requires the action tag ("product:database:action")');
94
+ method = 'action.fetch';
95
+ params = [tag];
96
+ break;
97
+ case 'list': {
98
+ const databaseTag = opts.database ?? tag;
99
+ if (!databaseTag) {
100
+ throw new Error('list requires --database <product_tag:database_tag>');
101
+ }
102
+ method = 'action.fetchAll';
103
+ params = [databaseTag];
104
+ break;
105
+ }
106
+ case 'delete':
107
+ if (!tag)
108
+ throw new Error('delete requires the action tag ("product:database:action")');
109
+ method = 'action.delete';
110
+ params = [tag];
111
+ break;
112
+ }
113
+ const result = await proxy.execute('databases', method, params);
114
+ printJson(result, Boolean(opts.json));
115
+ }
50
116
  export function runDbContext(json) {
51
117
  const session = requireSession();
52
118
  const ctx = getDbContext(session);
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { runInstall } from './commands/install.js';
15
15
  import { runStart, runStop, runStatus } from './commands/platform.js';
16
16
  import { runResourceCrud, runResourcesList, runEventTopicCrud, runNotificationMessageCrud } from './commands/resources.js';
17
17
  import { runCloud, runCloudPreflight } from './commands/cloud.js';
18
- import { runDb, runDbContext } from './commands/db.js';
18
+ import { runDb, runDbActions, runDbContext } from './commands/db.js';
19
19
  import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
20
20
  import { runFeaturesSync } from './commands/features-sync.js';
21
21
  import { runDbSchemaGenerate, runDbSchemaPush } from './commands/db-schema.js';
@@ -27,6 +27,7 @@ import { runCompletion } from './commands/completion.js';
27
27
  import { runProducts, runProductApps, runProductAppActions, runProductComponents, runProductEnvironments } from './commands/products.js';
28
28
  import { runApps } from './commands/apps.js';
29
29
  import { runAppsImport } from './commands/apps-import.js';
30
+ import { runAppActions } from './commands/app-actions.js';
30
31
  import { runMarketplace } from './commands/marketplace.js';
31
32
  import { runApply } from './commands/apply.js';
32
33
  import { runMigrateCodebase } from './commands/migrate-codebase.js';
@@ -663,6 +664,49 @@ apps
663
664
  version: opts.version,
664
665
  json: opts.json,
665
666
  })));
667
+ const appActions = apps.command('actions').description("An app's own actions (endpoints). resource is a path relative to each of the app's " +
668
+ 'environment base_urls (e.g. "/v1/users/{id}"), not a full URL.');
669
+ appActions
670
+ .command('list')
671
+ .requiredOption('--app <tag>', 'App tag')
672
+ .option('--profile <name>')
673
+ .option('--json', 'JSON output')
674
+ .action(wrap((opts) => runAppActions('list', opts)));
675
+ appActions
676
+ .command('get')
677
+ .requiredOption('--app <tag>', 'App tag')
678
+ .requiredOption('--action <tag>', 'Action tag')
679
+ .option('--profile <name>')
680
+ .option('--json', 'JSON output')
681
+ .action(wrap((opts) => runAppActions('get', opts)));
682
+ appActions
683
+ .command('create')
684
+ .requiredOption('--app <tag>', 'App tag')
685
+ // NOTE: cannot use -f/--file here — it collides with the parent `apps` command's own -f/--file
686
+ // option (used by `apps create`/`apps update`). Commander resolves a flag shared by an ancestor
687
+ // and a descendant command against the ancestor, so the value never reaches this subcommand's
688
+ // own opts, and requiredOption fails even when -f is supplied. Same fix as productEnvironments
689
+ // create/update (--env-file) above.
690
+ .requiredOption('--action-file <path>', 'JSON body: { tag, name, resource, method, description?, params?, query?, headers?, body?, response? }')
691
+ .option('--profile <name>')
692
+ .option('--json', 'JSON output')
693
+ .action(wrap((opts) => runAppActions('create', { ...opts, file: opts.actionFile })));
694
+ appActions
695
+ .command('update')
696
+ .requiredOption('--app <tag>', 'App tag')
697
+ .requiredOption('--action <tag>', 'Action tag')
698
+ // See NOTE above on `create` — same -f/--file collision with the parent `apps` command.
699
+ .requiredOption('--action-file <path>', 'JSON body (partial update)')
700
+ .option('--profile <name>')
701
+ .option('--json', 'JSON output')
702
+ .action(wrap((opts) => runAppActions('update', { ...opts, file: opts.actionFile })));
703
+ appActions
704
+ .command('delete')
705
+ .requiredOption('--app <tag>', 'App tag')
706
+ .requiredOption('--action <tag>', 'Action tag')
707
+ .option('--profile <name>')
708
+ .option('--json', 'JSON output')
709
+ .action(wrap((opts) => runAppActions('delete', opts)));
666
710
  program
667
711
  .command('marketplace')
668
712
  .description('Discover public apps by capability and inspect their actions')
@@ -761,6 +805,43 @@ dbMigrate
761
805
  .option('--db <tag>', 'Limit to one db entry')
762
806
  .option('-n <count>', 'Number of migrations to roll back', '1')
763
807
  .action(wrap((opts) => runDbMigrateRollback({ env: opts.env, db: opts.db, n: Number(opts.n) })));
808
+ const dbActions = db
809
+ .command('actions')
810
+ .description('Reusable database action definitions (saved parameterized queries). Required for any ' +
811
+ 'Feature step that uses ctx.database.query/insert/update/delete — those take an `event` ' +
812
+ 'tag pointing at one of these, not a raw table/where shape. Administrative (access-key), ' +
813
+ 'not a runtime query — create/update never touch the live database.');
814
+ dbActions
815
+ .command('create')
816
+ .description('Create a database action from a file: { tag: "product:database:action", name, tableName, operation, template }')
817
+ .requiredOption('-f, --file <path>', 'JSON file with the action definition')
818
+ .option('--json', 'JSON output')
819
+ .action(wrap((opts) => runDbActions('create', { file: opts.file, json: opts.json }, [])));
820
+ dbActions
821
+ .command('update')
822
+ .description('Update an existing database action')
823
+ .requiredOption('-t, --tag <tag>', 'Action tag ("product:database:action")')
824
+ .requiredOption('-f, --file <path>', 'JSON file with the fields to change')
825
+ .option('--json', 'JSON output')
826
+ .action(wrap((opts) => runDbActions('update', { tag: opts.tag, file: opts.file, json: opts.json }, [])));
827
+ dbActions
828
+ .command('get')
829
+ .description('Fetch one database action')
830
+ .argument('<tag>', 'Action tag ("product:database:action")')
831
+ .option('--json', 'JSON output')
832
+ .action(wrap((tag, opts) => runDbActions('get', { tag, json: opts.json }, [])));
833
+ dbActions
834
+ .command('list')
835
+ .description('List all actions on a database')
836
+ .requiredOption('-d, --database <tag>', 'Database tag ("product_tag:database_tag")')
837
+ .option('--json', 'JSON output')
838
+ .action(wrap((opts) => runDbActions('list', { database: opts.database, json: opts.json }, [])));
839
+ dbActions
840
+ .command('delete')
841
+ .description('Delete a database action')
842
+ .argument('<tag>', 'Action tag ("product:database:action")')
843
+ .option('--json', 'JSON output')
844
+ .action(wrap((tag, opts) => runDbActions('delete', { tag, json: opts.json }, [])));
764
845
  const dbSchema = db.command('schema').description('Schema management for ductape/database/schema.json');
765
846
  dbSchema
766
847
  .command('generate')
@@ -24,6 +24,11 @@ export declare function createApp(ctx: WorkspaceContext, body: Record<string, un
24
24
  export declare function updateApp(ctx: WorkspaceContext, appId: string, body: Record<string, unknown>): Promise<unknown>;
25
25
  export declare function updateAppViaProxy(ctx: WorkspaceContext, tag: string, body: Record<string, unknown>): Promise<unknown>;
26
26
  export declare function deleteApp(ctx: WorkspaceContext, appId: string): Promise<unknown>;
27
+ export declare function listAppActions(ctx: WorkspaceContext, appTag: string): Promise<unknown>;
28
+ export declare function getAppAction(ctx: WorkspaceContext, appTag: string, actionTag: string): Promise<unknown>;
29
+ export declare function createAppAction(ctx: WorkspaceContext, appTag: string, body: Record<string, unknown>): Promise<unknown>;
30
+ export declare function updateAppAction(ctx: WorkspaceContext, appTag: string, actionTag: string, body: Record<string, unknown>): Promise<unknown>;
31
+ export declare function deleteAppAction(ctx: WorkspaceContext, appTag: string, actionTag: string): Promise<unknown>;
27
32
  export interface MarketplaceSearchOptions {
28
33
  query?: string;
29
34
  category?: string;
@@ -333,6 +333,26 @@ export async function deleteApp(ctx, appId) {
333
333
  };
334
334
  return client(ctx).delete(`/apps/v1/${appId}?${new URLSearchParams(q).toString()}`);
335
335
  }
336
+ // —— App actions (sdk-proxy — admin op, requires access key) ——
337
+ //
338
+ // resource is a path RELATIVE to each of the app's environment base_urls
339
+ // (e.g. "/v1/users/{id}", not a full URL) — Ductape joins base_url + resource
340
+ // per environment at call time.
341
+ export async function listAppActions(ctx, appTag) {
342
+ return unwrapList(await proxy(ctx).execute('actions', 'list', [appTag]));
343
+ }
344
+ export async function getAppAction(ctx, appTag, actionTag) {
345
+ return unwrapOne(await proxy(ctx).execute('actions', 'fetch', [appTag, actionTag]));
346
+ }
347
+ export async function createAppAction(ctx, appTag, body) {
348
+ return unwrapOne(await proxy(ctx).execute('actions', 'create', [appTag, body]));
349
+ }
350
+ export async function updateAppAction(ctx, appTag, actionTag, body) {
351
+ return unwrapOne(await proxy(ctx).execute('actions', 'update', [appTag, actionTag, body]));
352
+ }
353
+ export async function deleteAppAction(ctx, appTag, actionTag) {
354
+ return proxy(ctx).execute('actions', 'delete', [appTag, actionTag]);
355
+ }
336
356
  function searchableMarketplaceText(app) {
337
357
  if (!app || typeof app !== 'object')
338
358
  return '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "description": "Ductape CLI — local platform, login, link projects, and manage resources via the proxy (Workbench-compatible)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",