@ductape/cli 0.3.26 → 0.3.28

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.
@@ -20,11 +20,23 @@ export class ApiClient {
20
20
  if (this.token) {
21
21
  headers.Authorization = `Bearer ${this.token}`;
22
22
  }
23
- const res = await fetch(url, {
24
- method,
25
- headers,
26
- body: body !== undefined ? JSON.stringify(body) : undefined,
27
- });
23
+ let res;
24
+ try {
25
+ res = await fetch(url, {
26
+ method,
27
+ headers,
28
+ body: body !== undefined ? JSON.stringify(body) : undefined,
29
+ });
30
+ }
31
+ catch (cause) {
32
+ throw new DuctapeOperationError(`Unable to reach ${safeEndpoint(url)}: ${cause instanceof Error ? cause.message : String(cause)}`, {
33
+ code: 'NETWORK_ERROR',
34
+ operation: `${method.toUpperCase()} ${path.split('?')[0]}`,
35
+ endpoint: safeEndpoint(url),
36
+ retryable: true,
37
+ mutationState: ['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase()) ? 'not_attempted' : 'unknown',
38
+ }, { cause });
39
+ }
28
40
  const mutating = !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase());
29
41
  const parsed = await parseJsonResponse(res, url, {
30
42
  operation: `${method.toUpperCase()} ${path.split('?')[0]}`,
@@ -283,7 +283,10 @@ export async function configureProductApp(ctx, productRef, appRef, updates) {
283
283
  const productTag = String(product.tag ?? (!/^[a-f\d]{24}$/i.test(productRef) ? productRef : ''));
284
284
  if (!productTag)
285
285
  throw new Error(`Could not resolve product tag for "${productRef}"`);
286
- const links = Array.isArray(product.apps) ? product.apps : [];
286
+ // Product fetch responses may omit app links. Resolve through the canonical
287
+ // connected-App inventory so every app returned by `products apps list` works here.
288
+ const listedLinks = await listProductApps(ctx, productTag, true);
289
+ const links = listedLinks.filter((item) => Boolean(item && typeof item === 'object'));
287
290
  const normalized = appRef.toLowerCase();
288
291
  const link = links.find((item) => String(item.access_tag ?? '').toLowerCase() === normalized
289
292
  || String(item.app_tag ?? '').toLowerCase() === normalized
@@ -324,6 +327,7 @@ export async function configureProductApp(ctx, productRef, appRef, updates) {
324
327
  app_env_slug: env.app_env_slug,
325
328
  variables_configured: Array.isArray(env.variables) && env.variables.length > 0,
326
329
  auth_configured: Boolean(env.auth),
330
+ shared_credentials_configured: Boolean(env.credentials && typeof env.credentials === 'object' && Object.keys(env.credentials).length > 0),
327
331
  })),
328
332
  };
329
333
  }
@@ -398,7 +402,12 @@ export async function createAppAction(ctx, appTag, body) {
398
402
  return unwrapOne(await proxy(ctx).execute('actions', 'create', [appTag, body]));
399
403
  }
400
404
  export async function updateAppAction(ctx, appTag, actionTag, body) {
401
- return unwrapOne(await proxy(ctx).execute('actions', 'update', [appTag, actionTag, body]));
405
+ const suppliedTag = typeof body.tag === 'string' ? body.tag : undefined;
406
+ if (suppliedTag && suppliedTag !== actionTag) {
407
+ throw new Error(`Action asset tag "${suppliedTag}" does not match requested action "${actionTag}"`);
408
+ }
409
+ const { tag: _immutableTag, ...patch } = body;
410
+ return unwrapOne(await proxy(ctx).execute('actions', 'update', [appTag, actionTag, patch]));
402
411
  }
403
412
  export async function deleteAppAction(ctx, appTag, actionTag) {
404
413
  return proxy(ctx).execute('actions', 'delete', [appTag, actionTag]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
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",