@ductape/cli 0.3.8 → 0.3.9

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/CHANGELOG.md CHANGED
@@ -20,7 +20,7 @@
20
20
  - `ductape workspaces current`, `workspaces switch`, `workspaces refresh`
21
21
  - `ductape login --workspace`, `--skip-workspace-select`
22
22
  - `ductape link` defaults to active workspace; `whoami` shows `active_workspace`
23
- - Switching workspaces updates linked `.ductape/config.json` when present
23
+ - Switching workspaces updates linked `ductape/config.json` when present
24
24
 
25
25
  ## 0.2.0
26
26
 
@@ -57,5 +57,5 @@ export async function runLink(opts) {
57
57
  };
58
58
  saveProjectConfig(targetDir, config);
59
59
  setActiveWorkspace(workspaceIdValue, workspaceLabel(workspaceSummary), workspaceTagValue);
60
- success(`Linked project in ${path.join(targetDir, '.ductape/config.json')}`);
60
+ success(`Linked project in ${path.join(targetDir, 'ductape/config.json')}`);
61
61
  }
@@ -1,4 +1,15 @@
1
1
  import { type CommandInteractiveFlags } from '../lib/interactive-opts.js';
2
+ import type { SDKModule } from '../lib/proxy/sdk-proxy.js';
3
+ export declare function resolveResourceProductTag(override: string | undefined, linkedProductTag: string): string;
4
+ type ResourceProxy = {
5
+ execute<T>(module: SDKModule, method: string, params?: unknown[]): Promise<T>;
6
+ };
7
+ /**
8
+ * Session inventories on older platform versions can fail when the component does not exist.
9
+ * Confirm the state through the product catalogue before treating that failure as an empty list,
10
+ * so transport, authentication, and genuine server failures are never silently swallowed.
11
+ */
12
+ export declare function listSessionsWithProductFallback(proxy: ResourceProxy, productTag: string): Promise<unknown[]>;
2
13
  export declare function runResourcesList(json: boolean): void;
3
14
  export declare function runEventTopicCrud(verb: string, opts: {
4
15
  tag?: string;
@@ -13,6 +24,8 @@ export declare function runNotificationMessageCrud(verb: string, opts: {
13
24
  }): Promise<void>;
14
25
  export declare function runResourceCrud(typeName: string, verb: string, opts: {
15
26
  tag?: string;
27
+ product?: string;
16
28
  file?: string;
17
29
  json?: boolean;
18
30
  } & CommandInteractiveFlags, extraArgs: string[]): Promise<void>;
31
+ export {};
@@ -6,6 +6,43 @@ import { bodyRequiredHint, resolveBody } from '../lib/read-body.js';
6
6
  import { requireSession, getSdkProxy } from '../lib/proxy/context.js';
7
7
  import { printJson } from '../lib/output.js';
8
8
  const CRUD_VERBS = ['create', 'list', 'get', 'update', 'delete', 'connect'];
9
+ export function resolveResourceProductTag(override, linkedProductTag) {
10
+ return override?.trim() || linkedProductTag;
11
+ }
12
+ function unwrapArray(value) {
13
+ if (Array.isArray(value))
14
+ return value;
15
+ if (value && typeof value === 'object' && Array.isArray(value.data)) {
16
+ return value.data;
17
+ }
18
+ return undefined;
19
+ }
20
+ /**
21
+ * Session inventories on older platform versions can fail when the component does not exist.
22
+ * Confirm the state through the product catalogue before treating that failure as an empty list,
23
+ * so transport, authentication, and genuine server failures are never silently swallowed.
24
+ */
25
+ export async function listSessionsWithProductFallback(proxy, productTag) {
26
+ try {
27
+ const result = await proxy.execute('sessions', 'list', [productTag]);
28
+ return unwrapArray(result) ?? [];
29
+ }
30
+ catch (listError) {
31
+ try {
32
+ const fetched = await proxy.execute('product', 'fetch', [productTag]);
33
+ const product = fetched && typeof fetched === 'object' && 'data' in fetched
34
+ ? fetched.data
35
+ : fetched;
36
+ if (!product || typeof product !== 'object')
37
+ throw listError;
38
+ const sessions = unwrapArray(product.sessions) ?? [];
39
+ return sessions.filter((session) => !session || typeof session !== 'object' || session.deleted !== true);
40
+ }
41
+ catch {
42
+ throw listError;
43
+ }
44
+ }
45
+ }
9
46
  export function runResourcesList(json) {
10
47
  printJson({ resource_types: listResourceTypes() }, json);
11
48
  }
@@ -168,11 +205,28 @@ export async function runResourceCrud(typeName, verb, opts, extraArgs) {
168
205
  tag,
169
206
  };
170
207
  }
171
- const params = buildCrudParams(module, method, session.project.product_tag, {
208
+ const productTag = resolveResourceProductTag(opts.product, session.project.product_tag);
209
+ const params = buildCrudParams(module, method, productTag, {
172
210
  tag,
173
211
  body: payload,
174
212
  });
175
213
  const proxy = getSdkProxy(session);
214
+ if (crud === 'list' && module === 'sessions') {
215
+ printJson(await listSessionsWithProductFallback(proxy, productTag), Boolean(opts.json));
216
+ return;
217
+ }
176
218
  const result = await proxy.execute(module, method, params);
219
+ if (crud === 'create' && module === 'sessions') {
220
+ const createdTag = String(body?.tag ?? '').trim();
221
+ if (!createdTag) {
222
+ throw new Error('Session creation could not be verified because body.tag is missing');
223
+ }
224
+ const verified = await proxy.execute(module, 'fetch', buildCrudParams(module, 'fetch', productTag, { tag: createdTag }));
225
+ if (!verified) {
226
+ throw new Error(`Session creation returned without an error, but "${createdTag}" was not found during verification`);
227
+ }
228
+ printJson({ created: true, session: verified }, Boolean(opts.json));
229
+ return;
230
+ }
177
231
  printJson(result, Boolean(opts.json));
178
232
  }
package/dist/index.js CHANGED
@@ -112,7 +112,7 @@ workspaces
112
112
  .action(wrap((opts) => runWorkspacesCurrent(Boolean(opts.json))));
113
113
  workspaces
114
114
  .command('use [selector]')
115
- .description('Switch active workspace (interactive picker). Updates linked .ductape/config.json if present')
115
+ .description('Switch active workspace (interactive picker). Updates linked ductape/config.json if present')
116
116
  .option('--profile <name>')
117
117
  .option('-C, --dir <path>', 'Project directory to sync', process.cwd())
118
118
  .option('--json', 'JSON output')
@@ -661,6 +661,7 @@ resources
661
661
  .argument('<type>', 'Resource type (e.g. storage, database, cache …)')
662
662
  .argument('<verb>', 'list | get | create | update | delete | connect')
663
663
  .option('-t, --tag <tag>')
664
+ .option('--product <tag>', 'Product tag (defaults to linked project)')
664
665
  .option('-f, --file <path>', 'JSON body (or use interactive prompts)')
665
666
  .option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
666
667
  .option('--no-interactive', 'Require -f JSON for create/update/connect')
@@ -9,6 +9,7 @@ export const GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, 'config.json');
9
9
  export const HUB_PLATFORM_DIR = path.join(GLOBAL_DIR, 'platform');
10
10
  export const PROJECT_CONFIG_DIR = 'ductape';
11
11
  export const PROJECT_CONFIG_PATH = path.join(PROJECT_CONFIG_DIR, 'config.json');
12
+ const LEGACY_PROJECT_CONFIG_PATH = path.join('.ductape', 'config.json');
12
13
  const DEFAULT_GLOBAL = {
13
14
  default_profile: 'cloud',
14
15
  profiles: {
@@ -114,10 +115,13 @@ export function findProjectConfig(startDir = process.cwd()) {
114
115
  let dir = startDir;
115
116
  const root = path.parse(dir).root;
116
117
  while (true) {
117
- const candidate = path.join(dir, PROJECT_CONFIG_PATH);
118
- const cfg = readJson(candidate);
119
- if (cfg?.product_tag && (cfg.workspace_tag || cfg.workspace_id)) {
120
- return { dir, config: cfg };
118
+ // The CLI writes ductape/config.json. Read the former hidden path only as
119
+ // an upgrade fallback; canonical config always wins when both exist.
120
+ for (const relativePath of [PROJECT_CONFIG_PATH, LEGACY_PROJECT_CONFIG_PATH]) {
121
+ const cfg = readJson(path.join(dir, relativePath));
122
+ if (cfg?.product_tag && (cfg.workspace_tag || cfg.workspace_id)) {
123
+ return { dir, config: cfg };
124
+ }
121
125
  }
122
126
  if (dir === root)
123
127
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
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",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Ductape SDK example — product/env defaults are set on the constructor only.
3
- * CLI: `ductape login` + `ductape link` (see .ductape/config.json).
3
+ * CLI: `ductape login` + `ductape link` (see ductape/config.json).
4
4
  */
5
5
  import Ductape from '@ductape/sdk';
6
6