@ductape/cli 0.2.2 → 0.2.4

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.
@@ -1,7 +1,11 @@
1
- type CloudTarget = 'connections' | 'resources';
1
+ type CloudTarget = 'connections' | 'resources' | 'tiers';
2
2
  export declare function runCloud(target: CloudTarget, verb: string, opts: {
3
3
  id?: string;
4
4
  file?: string;
5
+ provider?: string;
6
+ type?: string;
7
+ dbType?: string;
8
+ tier?: string;
5
9
  json?: boolean;
6
10
  }, extraArgs: string[]): Promise<void>;
7
11
  export {};
@@ -1,10 +1,25 @@
1
1
  import { readJsonBody } from '../lib/read-body.js';
2
2
  import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
3
  import { printJson } from '../lib/output.js';
4
+ import { listCloudTiers } from '../lib/platform-api.js';
5
+ import { requireWorkspaceContext } from '../lib/workspace-context.js';
4
6
  export async function runCloud(target, verb, opts, extraArgs) {
7
+ if (target === 'tiers') {
8
+ const ctx = requireWorkspaceContext();
9
+ const result = await listCloudTiers(ctx, {
10
+ provider: opts.provider,
11
+ resource_type: opts.type,
12
+ db_type: opts.dbType,
13
+ });
14
+ printJson(result, Boolean(opts.json));
15
+ return;
16
+ }
5
17
  requireSession();
6
18
  const proxy = getSdkProxy();
7
- const body = readJsonBody(opts.file);
19
+ let body = readJsonBody(opts.file);
20
+ if (opts.tier) {
21
+ body = { ...(body && typeof body === 'object' && !Array.isArray(body) ? body : {}), tier: opts.tier };
22
+ }
8
23
  const id = opts.id ?? extraArgs[0];
9
24
  let method;
10
25
  let params;
@@ -30,7 +45,9 @@ export async function runCloud(target, verb, opts, extraArgs) {
30
45
  import: ['resources.import', [body]],
31
46
  provision: ['resources.provision', [body]],
32
47
  'import-persist': ['resources.importAndPersist', [body]],
48
+ 'import-persist-all': ['resources.importAndPersistAll', [body]],
33
49
  'provision-persist': ['resources.provisionAndPersist', [body]],
50
+ 'provision-persist-all': ['resources.provisionAndPersistAll', [body]],
34
51
  };
35
52
  const entry = map[verb.toLowerCase()];
36
53
  if (!entry)
package/dist/index.js CHANGED
@@ -249,8 +249,17 @@ cloud
249
249
  cloud
250
250
  .command('resources <verb>')
251
251
  .option('-f, --file <path>')
252
+ .option('--tier <name>', 'Tier name from `cloud tiers list` (e.g. db.t3.micro, Standard_B1ms, M0)')
252
253
  .option('--json', 'JSON output')
253
- .action(wrap((verb, opts) => runCloud('resources', verb, { file: opts.file, json: opts.json }, [])));
254
+ .action(wrap((verb, opts) => runCloud('resources', verb, { file: opts.file, tier: opts.tier, json: opts.json }, [])));
255
+ cloud
256
+ .command('tiers')
257
+ .description('List available cloud resource tiers and estimated costs')
258
+ .option('--provider <name>', 'Filter by provider: aws, gcp, azure, mongodb_atlas, neo4j_aura')
259
+ .option('--type <resource_type>', 'Filter by resource type: database, storage, graph')
260
+ .option('--db-type <db_type>', 'Filter by database type: postgresql, mysql, mongodb, dynamodb, neo4j')
261
+ .option('--json', 'JSON output')
262
+ .action(wrap((opts) => runCloud('tiers', 'list', { provider: opts.provider, type: opts.type, dbType: opts.dbType, json: opts.json }, [])));
254
263
  const db = program.command('db').description('Database runtime (db-proxy)');
255
264
  db.command('context').option('--json', 'JSON output').action(wrap((opts) => runDbContext(Boolean(opts.json))));
256
265
  const dbMigrate = db
@@ -1,17 +1,31 @@
1
+ const REDACTED_KEYS = new Set(['private_key']);
2
+ function redactSensitiveFields(data) {
3
+ if (Array.isArray(data))
4
+ return data.map(redactSensitiveFields);
5
+ if (data !== null && typeof data === 'object') {
6
+ const result = {};
7
+ for (const [key, value] of Object.entries(data)) {
8
+ result[key] = REDACTED_KEYS.has(key) ? '[REDACTED]' : redactSensitiveFields(value);
9
+ }
10
+ return result;
11
+ }
12
+ return data;
13
+ }
1
14
  export function printJson(data, jsonMode) {
15
+ const safe = redactSensitiveFields(data);
2
16
  if (jsonMode) {
3
- console.log(JSON.stringify(data, null, 2));
17
+ console.log(JSON.stringify(safe, null, 2));
4
18
  return;
5
19
  }
6
- if (data === undefined || data === null) {
20
+ if (safe === undefined || safe === null) {
7
21
  console.log('(no data)');
8
22
  return;
9
23
  }
10
- if (typeof data === 'object') {
11
- console.log(JSON.stringify(data, null, 2));
24
+ if (typeof safe === 'object') {
25
+ console.log(JSON.stringify(safe, null, 2));
12
26
  return;
13
27
  }
14
- console.log(String(data));
28
+ console.log(String(safe));
15
29
  }
16
30
  export function success(message) {
17
31
  console.log(`✓ ${message}`);
@@ -17,3 +17,9 @@ export declare function createApp(ctx: WorkspaceContext, body: Record<string, un
17
17
  export declare function updateApp(ctx: WorkspaceContext, appId: string, body: Record<string, unknown>): Promise<unknown>;
18
18
  export declare function updateAppViaProxy(ctx: WorkspaceContext, tag: string, body: Record<string, unknown>): Promise<unknown>;
19
19
  export declare function deleteApp(ctx: WorkspaceContext, appId: string): Promise<unknown>;
20
+ export interface TierQuery {
21
+ provider?: string;
22
+ resource_type?: string;
23
+ db_type?: string;
24
+ }
25
+ export declare function listCloudTiers(ctx: WorkspaceContext, q: TierQuery): Promise<unknown[]>;
@@ -135,3 +135,14 @@ export async function deleteApp(ctx, appId) {
135
135
  };
136
136
  return client(ctx).delete(`/apps/v1/${appId}?${new URLSearchParams(q).toString()}`);
137
137
  }
138
+ export async function listCloudTiers(ctx, q) {
139
+ const params = workspaceAuthQuery(ctx);
140
+ if (q.provider)
141
+ params.provider = q.provider;
142
+ if (q.resource_type)
143
+ params.resource_type = q.resource_type;
144
+ if (q.db_type)
145
+ params.db_type = q.db_type;
146
+ const result = await client(ctx).getPath('/integrations/v1/cloud/tiers', params);
147
+ return unwrapList(result);
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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,18 +1,34 @@
1
1
  import { readJsonBody } from '../lib/read-body.js';
2
2
  import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
3
  import { printJson } from '../lib/output.js';
4
+ import { listCloudTiers } from '../lib/platform-api.js';
5
+ import { requireWorkspaceContext } from '../lib/workspace-context.js';
4
6
 
5
- type CloudTarget = 'connections' | 'resources';
7
+ type CloudTarget = 'connections' | 'resources' | 'tiers';
6
8
 
7
9
  export async function runCloud(
8
10
  target: CloudTarget,
9
11
  verb: string,
10
- opts: { id?: string; file?: string; json?: boolean },
12
+ opts: { id?: string; file?: string; provider?: string; type?: string; dbType?: string; tier?: string; json?: boolean },
11
13
  extraArgs: string[],
12
14
  ): Promise<void> {
15
+ if (target === 'tiers') {
16
+ const ctx = requireWorkspaceContext();
17
+ const result = await listCloudTiers(ctx, {
18
+ provider: opts.provider,
19
+ resource_type: opts.type,
20
+ db_type: opts.dbType,
21
+ });
22
+ printJson(result, Boolean(opts.json));
23
+ return;
24
+ }
25
+
13
26
  requireSession();
14
27
  const proxy = getSdkProxy();
15
- const body = readJsonBody(opts.file);
28
+ let body = readJsonBody(opts.file);
29
+ if (opts.tier) {
30
+ body = { ...(body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}), tier: opts.tier };
31
+ }
16
32
  const id = opts.id ?? extraArgs[0];
17
33
 
18
34
  let method: string;
@@ -38,7 +54,9 @@ export async function runCloud(
38
54
  import: ['resources.import', [body]],
39
55
  provision: ['resources.provision', [body]],
40
56
  'import-persist': ['resources.importAndPersist', [body]],
57
+ 'import-persist-all': ['resources.importAndPersistAll', [body]],
41
58
  'provision-persist': ['resources.provisionAndPersist', [body]],
59
+ 'provision-persist-all': ['resources.provisionAndPersistAll', [body]],
42
60
  };
43
61
  const entry = map[verb.toLowerCase()];
44
62
  if (!entry) throw new Error(`Unknown cloud resources verb: ${verb}`);
package/src/index.ts CHANGED
@@ -326,8 +326,18 @@ cloud
326
326
  cloud
327
327
  .command('resources <verb>')
328
328
  .option('-f, --file <path>')
329
+ .option('--tier <name>', 'Tier name from `cloud tiers list` (e.g. db.t3.micro, Standard_B1ms, M0)')
329
330
  .option('--json', 'JSON output')
330
- .action(wrap((verb: string, opts) => runCloud('resources', verb, { file: opts.file, json: opts.json }, [])));
331
+ .action(wrap((verb: string, opts) => runCloud('resources', verb, { file: opts.file, tier: opts.tier, json: opts.json }, [])));
332
+
333
+ cloud
334
+ .command('tiers')
335
+ .description('List available cloud resource tiers and estimated costs')
336
+ .option('--provider <name>', 'Filter by provider: aws, gcp, azure, mongodb_atlas, neo4j_aura')
337
+ .option('--type <resource_type>', 'Filter by resource type: database, storage, graph')
338
+ .option('--db-type <db_type>', 'Filter by database type: postgresql, mysql, mongodb, dynamodb, neo4j')
339
+ .option('--json', 'JSON output')
340
+ .action(wrap((opts) => runCloud('tiers', 'list', { provider: opts.provider, type: opts.type, dbType: opts.dbType, json: opts.json }, [])));
331
341
 
332
342
  const db = program.command('db').description('Database runtime (db-proxy)');
333
343
 
package/src/lib/output.ts CHANGED
@@ -1,17 +1,32 @@
1
+ const REDACTED_KEYS = new Set(['private_key']);
2
+
3
+ function redactSensitiveFields(data: unknown): unknown {
4
+ if (Array.isArray(data)) return data.map(redactSensitiveFields);
5
+ if (data !== null && typeof data === 'object') {
6
+ const result: Record<string, unknown> = {};
7
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
8
+ result[key] = REDACTED_KEYS.has(key) ? '[REDACTED]' : redactSensitiveFields(value);
9
+ }
10
+ return result;
11
+ }
12
+ return data;
13
+ }
14
+
1
15
  export function printJson(data: unknown, jsonMode: boolean): void {
16
+ const safe = redactSensitiveFields(data);
2
17
  if (jsonMode) {
3
- console.log(JSON.stringify(data, null, 2));
18
+ console.log(JSON.stringify(safe, null, 2));
4
19
  return;
5
20
  }
6
- if (data === undefined || data === null) {
21
+ if (safe === undefined || safe === null) {
7
22
  console.log('(no data)');
8
23
  return;
9
24
  }
10
- if (typeof data === 'object') {
11
- console.log(JSON.stringify(data, null, 2));
25
+ if (typeof safe === 'object') {
26
+ console.log(JSON.stringify(safe, null, 2));
12
27
  return;
13
28
  }
14
- console.log(String(data));
29
+ console.log(String(safe));
15
30
  }
16
31
 
17
32
  export function success(message: string): void {
@@ -190,3 +190,20 @@ export async function deleteApp(ctx: WorkspaceContext, appId: string): Promise<u
190
190
  `/apps/v1/${appId}?${new URLSearchParams(q).toString()}`,
191
191
  );
192
192
  }
193
+
194
+ // —— Cloud tiers ——
195
+
196
+ export interface TierQuery {
197
+ provider?: string;
198
+ resource_type?: string;
199
+ db_type?: string;
200
+ }
201
+
202
+ export async function listCloudTiers(ctx: WorkspaceContext, q: TierQuery): Promise<unknown[]> {
203
+ const params: Record<string, string> = workspaceAuthQuery(ctx);
204
+ if (q.provider) params.provider = q.provider;
205
+ if (q.resource_type) params.resource_type = q.resource_type;
206
+ if (q.db_type) params.db_type = q.db_type;
207
+ const result = await client(ctx).getPath<unknown>('/integrations/v1/cloud/tiers', params);
208
+ return unwrapList<unknown>(result);
209
+ }