@ductape/cli 0.3.24 → 0.3.26
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.
|
@@ -2,8 +2,9 @@ import { schemaKeyForEntity } from '../lib/forms/index.js';
|
|
|
2
2
|
import { isInteractive, promptTag } from '../lib/forms/prompt.js';
|
|
3
3
|
import { toInteractiveOpts } from '../lib/interactive-opts.js';
|
|
4
4
|
import { bodyRequiredHint, resolveBody } from '../lib/read-body.js';
|
|
5
|
-
import { connectMarketplaceApp, createProduct, deleteProduct, getProduct, getProductAppAction, listProductAppActions, listProductApps, listProducts, updateProduct, } from '../lib/platform-api.js';
|
|
5
|
+
import { connectMarketplaceApp, configureProductApp, createProduct, deleteProduct, getProduct, getProductAppAction, listProductAppActions, listProductApps, listProducts, updateProduct, } from '../lib/platform-api.js';
|
|
6
6
|
import { printJson } from '../lib/output.js';
|
|
7
|
+
import { readJsonBody } from '../lib/read-body.js';
|
|
7
8
|
import { requireWorkspaceContext } from '../lib/workspace-context.js';
|
|
8
9
|
import { getSdkProxyForContext, getOptionalProject } from '../lib/proxy/context.js';
|
|
9
10
|
const VERBS = ['list', 'get', 'create', 'update', 'delete'];
|
|
@@ -83,8 +84,23 @@ export async function runProductApps(verb, opts, extraArgs) {
|
|
|
83
84
|
});
|
|
84
85
|
result = await connectMarketplaceApp(ctx, productId, opts.app, envs);
|
|
85
86
|
}
|
|
87
|
+
else if (verb.toLowerCase() === 'configure') {
|
|
88
|
+
if (!opts.app)
|
|
89
|
+
throw new Error('Provide --app <connected_app_tag_or_access_tag>');
|
|
90
|
+
if (!opts.connectionFile)
|
|
91
|
+
throw new Error('Provide --connection-file <connection.json>');
|
|
92
|
+
const body = readJsonBody(opts.connectionFile);
|
|
93
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
94
|
+
throw new Error('Connection file must contain a JSON object');
|
|
95
|
+
}
|
|
96
|
+
const envs = body.envs;
|
|
97
|
+
if (!Array.isArray(envs) || envs.length === 0) {
|
|
98
|
+
throw new Error('Connection file requires a non-empty envs array');
|
|
99
|
+
}
|
|
100
|
+
result = await configureProductApp(ctx, productId, opts.app, envs);
|
|
101
|
+
}
|
|
86
102
|
else {
|
|
87
|
-
throw new Error('Supported verbs: list | connect');
|
|
103
|
+
throw new Error('Supported verbs: list | connect | configure');
|
|
88
104
|
}
|
|
89
105
|
printJson(result, Boolean(opts.json));
|
|
90
106
|
}
|
package/dist/index.js
CHANGED
|
@@ -568,6 +568,15 @@ productApps
|
|
|
568
568
|
.option('--profile <name>')
|
|
569
569
|
.option('--json', 'JSON output')
|
|
570
570
|
.action(wrap((opts) => runProductApps('connect', opts, [])));
|
|
571
|
+
productApps
|
|
572
|
+
.command('configure')
|
|
573
|
+
.description('Configure or repair a connected App without exposing existing credentials')
|
|
574
|
+
.requiredOption('--product <id-or-tag>', 'Product _id or tag')
|
|
575
|
+
.requiredOption('--app <tag>', 'Connected app tag or product access tag')
|
|
576
|
+
.requiredOption('--connection-file <path>', 'Connection JSON: { envs: [{ product_env_slug, app_env_slug, variables?, auth? }] }')
|
|
577
|
+
.option('--profile <name>')
|
|
578
|
+
.option('--json', 'JSON output')
|
|
579
|
+
.action(wrap((opts) => runProductApps('configure', opts, [])));
|
|
571
580
|
const productAppActions = productApps.command('actions').description('Actions exposed by an app linked to a product');
|
|
572
581
|
productAppActions
|
|
573
582
|
.command('list')
|
|
@@ -15,6 +15,8 @@ export declare function connectMarketplaceApp(ctx: WorkspaceContext, product: st
|
|
|
15
15
|
app_env_slug: string;
|
|
16
16
|
product_env_slug: string;
|
|
17
17
|
}>): Promise<unknown>;
|
|
18
|
+
/** Configure an existing product App link while preserving omitted secret-bearing fields. */
|
|
19
|
+
export declare function configureProductApp(ctx: WorkspaceContext, productRef: string, appRef: string, updates: Array<Record<string, unknown>>): Promise<unknown>;
|
|
18
20
|
export declare function listApps(ctx: WorkspaceContext, status?: string): Promise<unknown[]>;
|
|
19
21
|
export declare function getApp(ctx: WorkspaceContext, opts: {
|
|
20
22
|
id?: string;
|
package/dist/lib/platform-api.js
CHANGED
|
@@ -277,6 +277,56 @@ export async function connectMarketplaceApp(ctx, product, appTag, envs) {
|
|
|
277
277
|
environments: envs,
|
|
278
278
|
};
|
|
279
279
|
}
|
|
280
|
+
/** Configure an existing product App link while preserving omitted secret-bearing fields. */
|
|
281
|
+
export async function configureProductApp(ctx, productRef, appRef, updates) {
|
|
282
|
+
const product = unwrapOne(await getProduct(ctx, /^[a-f\d]{24}$/i.test(productRef) ? { id: productRef } : { tag: productRef }));
|
|
283
|
+
const productTag = String(product.tag ?? (!/^[a-f\d]{24}$/i.test(productRef) ? productRef : ''));
|
|
284
|
+
if (!productTag)
|
|
285
|
+
throw new Error(`Could not resolve product tag for "${productRef}"`);
|
|
286
|
+
const links = Array.isArray(product.apps) ? product.apps : [];
|
|
287
|
+
const normalized = appRef.toLowerCase();
|
|
288
|
+
const link = links.find((item) => String(item.access_tag ?? '').toLowerCase() === normalized
|
|
289
|
+
|| String(item.app_tag ?? '').toLowerCase() === normalized
|
|
290
|
+
|| String(item.tag ?? '').toLowerCase() === normalized);
|
|
291
|
+
if (!link)
|
|
292
|
+
throw new Error(`App "${appRef}" is not connected to product "${productTag}"`);
|
|
293
|
+
const accessTag = String(link.access_tag ?? '');
|
|
294
|
+
if (!accessTag)
|
|
295
|
+
throw new Error(`Connected App "${appRef}" has no access tag`);
|
|
296
|
+
const existing = Array.isArray(link.envs) ? link.envs : [];
|
|
297
|
+
const byProductEnv = new Map(existing.map((env) => [String(env.product_env_slug ?? ''), { ...env }]));
|
|
298
|
+
for (const update of updates) {
|
|
299
|
+
const productEnv = String(update.product_env_slug ?? '');
|
|
300
|
+
const appEnv = String(update.app_env_slug ?? '');
|
|
301
|
+
if (!productEnv || !appEnv) {
|
|
302
|
+
throw new Error('Each connection env requires product_env_slug and app_env_slug');
|
|
303
|
+
}
|
|
304
|
+
const previous = byProductEnv.get(productEnv) ?? {};
|
|
305
|
+
byProductEnv.set(productEnv, {
|
|
306
|
+
...previous,
|
|
307
|
+
...update,
|
|
308
|
+
// Omission means preserve; explicit auth/variables replace that field.
|
|
309
|
+
...(!Object.prototype.hasOwnProperty.call(update, 'auth') && previous.auth !== undefined
|
|
310
|
+
? { auth: previous.auth } : {}),
|
|
311
|
+
...(!Object.prototype.hasOwnProperty.call(update, 'variables') && previous.variables !== undefined
|
|
312
|
+
? { variables: previous.variables } : {}),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const envs = [...byProductEnv.values()];
|
|
316
|
+
await proxy(ctx).execute('product', 'apps.update', [productTag, accessTag, { envs }]);
|
|
317
|
+
return {
|
|
318
|
+
configured: true,
|
|
319
|
+
product: productTag,
|
|
320
|
+
app_tag: appRef,
|
|
321
|
+
access_tag: accessTag,
|
|
322
|
+
environments: envs.map((env) => ({
|
|
323
|
+
product_env_slug: env.product_env_slug,
|
|
324
|
+
app_env_slug: env.app_env_slug,
|
|
325
|
+
variables_configured: Array.isArray(env.variables) && env.variables.length > 0,
|
|
326
|
+
auth_configured: Boolean(env.auth),
|
|
327
|
+
})),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
280
330
|
// —— Apps (apps REST) ——
|
|
281
331
|
export async function listApps(ctx, status = 'all') {
|
|
282
332
|
const result = await client(ctx).getPath(`/apps/v1/workspace/${ctx.workspaceId}/${status}`, {
|
package/package.json
CHANGED