@ductape/cli 0.2.22 → 0.3.0
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 +7 -0
- package/README.md +7 -0
- package/dist/commands/generate.js +1 -1
- package/dist/commands/migrate-codebase.d.ts +176 -0
- package/dist/commands/migrate-codebase.js +753 -0
- package/dist/commands/migration-artifact.d.ts +32 -0
- package/dist/commands/migration-artifact.js +48 -0
- package/dist/commands/migration-assurance.d.ts +10 -0
- package/dist/commands/migration-assurance.js +120 -0
- package/dist/commands/migration-database.d.ts +21 -0
- package/dist/commands/migration-database.js +162 -0
- package/dist/commands/migration-e2e.d.ts +68 -0
- package/dist/commands/migration-e2e.js +189 -0
- package/dist/commands/migration-environments.d.ts +20 -0
- package/dist/commands/migration-environments.js +58 -0
- package/dist/commands/migration-generated.d.ts +68 -0
- package/dist/commands/migration-generated.js +217 -0
- package/dist/commands/migration-portfolio.d.ts +88 -0
- package/dist/commands/migration-portfolio.js +223 -0
- package/dist/commands/migration-products.d.ts +23 -0
- package/dist/commands/migration-products.js +88 -0
- package/dist/commands/migration-review.d.ts +248 -0
- package/dist/commands/migration-review.js +624 -0
- package/dist/commands/migration-secrets.d.ts +24 -0
- package/dist/commands/migration-secrets.js +93 -0
- package/dist/commands/migration-slice.d.ts +132 -0
- package/dist/commands/migration-slice.js +266 -0
- package/dist/commands/migration-verification.d.ts +12 -0
- package/dist/commands/migration-verification.js +194 -0
- package/dist/commands/products.d.ts +1 -0
- package/dist/commands/products.js +33 -1
- package/dist/commands/secrets.d.ts +8 -0
- package/dist/commands/secrets.js +44 -0
- package/dist/index.js +354 -2
- package/dist/lib/integrations.js +6 -1
- package/dist/lib/migration-artifact.d.ts +13 -0
- package/dist/lib/migration-artifact.js +103 -0
- package/package.json +8 -1
- package/API_PARITY.md +0 -58
- package/DB_MIGRATE_PLAN.md +0 -223
- package/ductape.example.ts +0 -18
- package/src/commands/apply.ts +0 -153
- package/src/commands/apps-import.ts +0 -43
- package/src/commands/apps.ts +0 -92
- package/src/commands/cloud.ts +0 -68
- package/src/commands/completion.ts +0 -119
- package/src/commands/db-migrate.ts +0 -209
- package/src/commands/db-schema.ts +0 -429
- package/src/commands/db.ts +0 -63
- package/src/commands/generate.ts +0 -74
- package/src/commands/graph.ts +0 -51
- package/src/commands/init.ts +0 -39
- package/src/commands/install.ts +0 -76
- package/src/commands/link.ts +0 -84
- package/src/commands/login.ts +0 -145
- package/src/commands/logout.ts +0 -7
- package/src/commands/platform.ts +0 -123
- package/src/commands/products.ts +0 -228
- package/src/commands/profiles.ts +0 -23
- package/src/commands/resources.ts +0 -213
- package/src/commands/secrets.ts +0 -46
- package/src/commands/unlink.ts +0 -15
- package/src/commands/whoami.ts +0 -27
- package/src/commands/workspaces.ts +0 -98
- package/src/index.ts +0 -523
- package/src/lib/api-client.ts +0 -88
- package/src/lib/app-import.ts +0 -82
- package/src/lib/apply-loaders.ts +0 -30
- package/src/lib/config.ts +0 -230
- package/src/lib/context-store.ts +0 -77
- package/src/lib/db-methods.ts +0 -56
- package/src/lib/db-types.ts +0 -104
- package/src/lib/encryption.ts +0 -12
- package/src/lib/forms/enums.ts +0 -52
- package/src/lib/forms/index.ts +0 -8
- package/src/lib/forms/prompt.ts +0 -209
- package/src/lib/forms/schemas.ts +0 -288
- package/src/lib/forms/types.ts +0 -27
- package/src/lib/http.ts +0 -76
- package/src/lib/integrations.ts +0 -105
- package/src/lib/interactive-opts.ts +0 -13
- package/src/lib/migration-files.ts +0 -58
- package/src/lib/output.ts +0 -39
- package/src/lib/platform-api.ts +0 -209
- package/src/lib/proxy/context.ts +0 -94
- package/src/lib/proxy/db-proxy.ts +0 -40
- package/src/lib/proxy/graph-proxy.ts +0 -64
- package/src/lib/proxy/sdk-proxy.ts +0 -58
- package/src/lib/read-body.ts +0 -57
- package/src/lib/resources.ts +0 -160
- package/src/lib/schema-loader.ts +0 -152
- package/src/lib/snippets.ts +0 -165
- package/src/lib/templates.ts +0 -157
- package/src/lib/workspace-context.ts +0 -43
- package/src/lib/workspaces.ts +0 -283
- package/test/apply-loaders.test.ts +0 -40
- package/test/encryption.test.ts +0 -20
- package/test/forms.test.ts +0 -25
- package/test/http.test.ts +0 -15
- package/test/platform-api.test.ts +0 -42
- package/test/resources.test.ts +0 -23
- package/test/workspace-resolve.test.ts +0 -29
- package/tsconfig.json +0 -15
package/src/index.ts
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { runLogin } from './commands/login.js';
|
|
7
|
-
import { runLogout } from './commands/logout.js';
|
|
8
|
-
import { runWhoami } from './commands/whoami.js';
|
|
9
|
-
import { runProfilesList, runProfilesUse } from './commands/profiles.js';
|
|
10
|
-
import { runLink } from './commands/link.js';
|
|
11
|
-
import { runUnlink } from './commands/unlink.js';
|
|
12
|
-
import { runInit } from './commands/init.js';
|
|
13
|
-
import { runInstall } from './commands/install.js';
|
|
14
|
-
import { runStart, runStop, runStatus } from './commands/platform.js';
|
|
15
|
-
import { runResourceCrud, runResourcesList, runEventTopicCrud, runNotificationMessageCrud } from './commands/resources.js';
|
|
16
|
-
import { runCloud } from './commands/cloud.js';
|
|
17
|
-
import { runDb, runDbContext } from './commands/db.js';
|
|
18
|
-
import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
|
|
19
|
-
import { runDbSchemaGenerate, runDbSchemaPush } from './commands/db-schema.js';
|
|
20
|
-
import { runGraph } from './commands/graph.js';
|
|
21
|
-
import { runSecrets } from './commands/secrets.js';
|
|
22
|
-
import {
|
|
23
|
-
runWorkspacesCurrent,
|
|
24
|
-
runWorkspacesList,
|
|
25
|
-
runWorkspacesRefresh,
|
|
26
|
-
runWorkspacesUse,
|
|
27
|
-
} from './commands/workspaces.js';
|
|
28
|
-
import { runGeneratePayload, runGenerateSnippet } from './commands/generate.js';
|
|
29
|
-
import { runCompletion } from './commands/completion.js';
|
|
30
|
-
import { runProducts, runProductApps, runProductComponents, runProductEnvironments } from './commands/products.js';
|
|
31
|
-
import { runApps } from './commands/apps.js';
|
|
32
|
-
import { runAppsImport } from './commands/apps-import.js';
|
|
33
|
-
import { runApply, type ApplyType } from './commands/apply.js';
|
|
34
|
-
|
|
35
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
-
const pkg = JSON.parse(
|
|
37
|
-
readFileSync(path.join(__dirname, '../package.json'), 'utf8'),
|
|
38
|
-
) as { version: string };
|
|
39
|
-
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
-
function wrap(fn: (...args: any[]) => void | Promise<void>) {
|
|
42
|
-
return (...args: any[]) => {
|
|
43
|
-
Promise.resolve(fn(...args)).catch((err: unknown) => {
|
|
44
|
-
console.error(err instanceof Error ? err.message : err);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const program = new Command();
|
|
51
|
-
program
|
|
52
|
-
.name('ductape')
|
|
53
|
-
.description(
|
|
54
|
-
'Ductape CLI — local platform, auth, project linking, and resource CRUD via Workbench-compatible encrypted proxy',
|
|
55
|
-
)
|
|
56
|
-
.version(pkg.version);
|
|
57
|
-
|
|
58
|
-
program
|
|
59
|
-
.command('login')
|
|
60
|
-
.description('Log in (email/password, OAuth token, or browser OAuth)')
|
|
61
|
-
.option('-e, --email <email>')
|
|
62
|
-
.option('-p, --password <password>')
|
|
63
|
-
.option('--token <token>', 'OAuth callback token from browser redirect URL')
|
|
64
|
-
.option('--browser [provider]', 'Open browser for Google OAuth (google|github)')
|
|
65
|
-
.option('--profile <name>', 'API profile', 'cloud')
|
|
66
|
-
.option('-w, --workspace <id>', 'Workspace index, name, or id (skips interactive picker)')
|
|
67
|
-
.option('--skip-workspace-select', 'Keep prior active workspace or default without prompting')
|
|
68
|
-
.action(
|
|
69
|
-
wrap((opts) =>
|
|
70
|
-
runLogin({
|
|
71
|
-
email: opts.email,
|
|
72
|
-
password: opts.password,
|
|
73
|
-
profile: opts.profile,
|
|
74
|
-
token: opts.token,
|
|
75
|
-
browser: opts.browser === true ? 'google' : opts.browser,
|
|
76
|
-
workspace: opts.workspace,
|
|
77
|
-
skipWorkspaceSelect: Boolean(opts.skipWorkspaceSelect),
|
|
78
|
-
}),
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
program.command('logout').description('Clear local credentials').action(wrap(runLogout));
|
|
83
|
-
|
|
84
|
-
program
|
|
85
|
-
.command('whoami')
|
|
86
|
-
.description('Show login and linked project')
|
|
87
|
-
.option('--json', 'JSON output')
|
|
88
|
-
.action(wrap((opts) => runWhoami(Boolean(opts.json))));
|
|
89
|
-
|
|
90
|
-
program
|
|
91
|
-
.command('install')
|
|
92
|
-
.description('Check Docker/Node prerequisites and prepare platform .env')
|
|
93
|
-
.action(wrap(runInstall));
|
|
94
|
-
|
|
95
|
-
const profiles = program.command('profiles').description('Manage API profiles (cloud / local)');
|
|
96
|
-
|
|
97
|
-
profiles
|
|
98
|
-
.command('list')
|
|
99
|
-
.option('--json', 'JSON output')
|
|
100
|
-
.action(wrap((opts) => runProfilesList(Boolean(opts.json))));
|
|
101
|
-
|
|
102
|
-
profiles.command('use <name>').action(wrap((name: string) => runProfilesUse(name)));
|
|
103
|
-
|
|
104
|
-
const workspaces = program
|
|
105
|
-
.command('workspaces')
|
|
106
|
-
.description('List and switch active workspace (workspace_id is stored globally at login)');
|
|
107
|
-
|
|
108
|
-
workspaces
|
|
109
|
-
.command('list')
|
|
110
|
-
.description('List workspaces (marks active)')
|
|
111
|
-
.option('--profile <name>')
|
|
112
|
-
.option('--json', 'JSON output')
|
|
113
|
-
.action(wrap((opts) => runWorkspacesList({ profile: opts.profile, json: opts.json })));
|
|
114
|
-
|
|
115
|
-
workspaces
|
|
116
|
-
.command('current')
|
|
117
|
-
.description('Show active workspace')
|
|
118
|
-
.option('--json', 'JSON output')
|
|
119
|
-
.action(wrap((opts) => runWorkspacesCurrent(Boolean(opts.json))));
|
|
120
|
-
|
|
121
|
-
workspaces
|
|
122
|
-
.command('use [selector]')
|
|
123
|
-
.description('Switch active workspace (interactive picker). Updates linked .ductape/config.json if present')
|
|
124
|
-
.option('--profile <name>')
|
|
125
|
-
.option('-C, --dir <path>', 'Project directory to sync', process.cwd())
|
|
126
|
-
.option('--json', 'JSON output')
|
|
127
|
-
.action(wrap((selector: string | undefined, opts) => runWorkspacesUse(selector, opts)));
|
|
128
|
-
|
|
129
|
-
workspaces
|
|
130
|
-
.command('switch [selector]')
|
|
131
|
-
.description('Alias for workspaces use')
|
|
132
|
-
.option('--profile <name>')
|
|
133
|
-
.option('-C, --dir <path>', 'Project directory to sync', process.cwd())
|
|
134
|
-
.option('--json', 'JSON output')
|
|
135
|
-
.action(wrap((selector: string | undefined, opts) => runWorkspacesUse(selector, opts)));
|
|
136
|
-
|
|
137
|
-
workspaces
|
|
138
|
-
.command('refresh')
|
|
139
|
-
.description('Refresh workspace list from API')
|
|
140
|
-
.option('--profile <name>')
|
|
141
|
-
.option('--json', 'JSON output')
|
|
142
|
-
.action(wrap((opts) => runWorkspacesRefresh(opts)));
|
|
143
|
-
|
|
144
|
-
program
|
|
145
|
-
.command('link')
|
|
146
|
-
.option('-w, --workspace <id>')
|
|
147
|
-
.option('--product <tag>')
|
|
148
|
-
.option('--env <slug>')
|
|
149
|
-
.option('--profile <name>')
|
|
150
|
-
.option('-C, --dir <path>', 'Project directory', process.cwd())
|
|
151
|
-
.action(
|
|
152
|
-
wrap((opts) =>
|
|
153
|
-
runLink({
|
|
154
|
-
workspace: opts.workspace,
|
|
155
|
-
product: opts.product,
|
|
156
|
-
env: opts.env,
|
|
157
|
-
profile: opts.profile,
|
|
158
|
-
dir: opts.dir,
|
|
159
|
-
}),
|
|
160
|
-
),
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
program
|
|
164
|
-
.command('unlink')
|
|
165
|
-
.option('-C, --dir <path>', 'Project directory', process.cwd())
|
|
166
|
-
.action(wrap((opts) => runUnlink({ dir: opts.dir })));
|
|
167
|
-
|
|
168
|
-
program
|
|
169
|
-
.command('init')
|
|
170
|
-
.option('--link', 'Run link after init')
|
|
171
|
-
.option('-w, --workspace <id>')
|
|
172
|
-
.option('--product <tag>')
|
|
173
|
-
.option('--env <slug>')
|
|
174
|
-
.option('--language <lang>', 'typescript|python|go|dotnet')
|
|
175
|
-
.action(
|
|
176
|
-
wrap((opts) =>
|
|
177
|
-
runInit({
|
|
178
|
-
link: Boolean(opts.link),
|
|
179
|
-
workspace: opts.workspace,
|
|
180
|
-
product: opts.product,
|
|
181
|
-
env: opts.env,
|
|
182
|
-
language: opts.language,
|
|
183
|
-
}),
|
|
184
|
-
),
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const APPLY_TYPES = ['sessions', 'notifications', 'events'] as const;
|
|
188
|
-
|
|
189
|
-
program
|
|
190
|
-
.command('apply')
|
|
191
|
-
.description('Sync declared sessions, notifications and events to your product')
|
|
192
|
-
.argument('[type]', `${APPLY_TYPES.join(' | ')}`)
|
|
193
|
-
.option('--dry-run', 'Print what would be created/updated without applying')
|
|
194
|
-
.action(
|
|
195
|
-
wrap((type: string | undefined, opts) => {
|
|
196
|
-
if (type && !(APPLY_TYPES as readonly string[]).includes(type)) {
|
|
197
|
-
throw new Error(`Unknown type "${type}". Use: ${APPLY_TYPES.join(', ')}`);
|
|
198
|
-
}
|
|
199
|
-
return runApply(type as ApplyType | undefined, { dryRun: Boolean(opts.dryRun) });
|
|
200
|
-
}),
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
program
|
|
204
|
-
.command('start')
|
|
205
|
-
.description('Start platform via docker compose')
|
|
206
|
-
.option('--backend-only', 'Backend without workbench UI')
|
|
207
|
-
.option('--remote', 'Workbench UI only, built against cloud API (no local Nest/Mongo/Kafka)')
|
|
208
|
-
.option('--build', 'Build images before start')
|
|
209
|
-
.action(
|
|
210
|
-
wrap((opts) =>
|
|
211
|
-
runStart({
|
|
212
|
-
backendOnly: Boolean(opts.backendOnly),
|
|
213
|
-
remote: Boolean(opts.remote),
|
|
214
|
-
build: Boolean(opts.build),
|
|
215
|
-
}),
|
|
216
|
-
),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
program.command('stop').action(wrap(runStop));
|
|
220
|
-
|
|
221
|
-
program.command('status').option('--json', 'JSON output').action(wrap((opts) => runStatus(Boolean(opts.json))));
|
|
222
|
-
|
|
223
|
-
const products = program
|
|
224
|
-
.command('products')
|
|
225
|
-
.description('Workspace products CRUD (REST + sdk-proxy update)')
|
|
226
|
-
.argument('[verb]', 'list | get | create | update | delete')
|
|
227
|
-
.option('--profile <name>')
|
|
228
|
-
.option('--status <status>', 'Filter for list: all, active, draft, …', 'all')
|
|
229
|
-
.option('--id <id>', 'Product _id (get, delete)')
|
|
230
|
-
.option('-t, --tag <tag>', 'Product tag (get, update)')
|
|
231
|
-
.option('-f, --file <path>', 'JSON body (create, update)')
|
|
232
|
-
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
233
|
-
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
234
|
-
.option('--json', 'JSON output')
|
|
235
|
-
.action(
|
|
236
|
-
wrap((verb: string | undefined, opts) => {
|
|
237
|
-
if (!verb) throw new Error('Specify a verb: list | get | create | update | delete');
|
|
238
|
-
return runProducts(verb, opts, []);
|
|
239
|
-
}),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
const productApps = products.command('apps').description('Apps connected to a product');
|
|
243
|
-
|
|
244
|
-
productApps
|
|
245
|
-
.command('list')
|
|
246
|
-
.option('--profile <name>')
|
|
247
|
-
.option('--product <id>', 'Product _id')
|
|
248
|
-
.option('--json', 'JSON output')
|
|
249
|
-
.action(wrap((opts) => runProductApps('list', opts, [])));
|
|
250
|
-
|
|
251
|
-
const productComponents = products
|
|
252
|
-
.command('components')
|
|
253
|
-
.description('Compact, non-secret product component inventory');
|
|
254
|
-
|
|
255
|
-
productComponents
|
|
256
|
-
.command('list')
|
|
257
|
-
.requiredOption('-t, --tag <tag>', 'Product tag')
|
|
258
|
-
.option('--profile <name>')
|
|
259
|
-
.option('--json', 'JSON output')
|
|
260
|
-
.action(wrap((opts) => runProductComponents('list', opts)));
|
|
261
|
-
|
|
262
|
-
productComponents
|
|
263
|
-
.command('get')
|
|
264
|
-
.requiredOption('-t, --tag <tag>', 'Product tag')
|
|
265
|
-
.requiredOption('--type <type>', 'Component type, e.g. notifications or events')
|
|
266
|
-
.option('--profile <name>')
|
|
267
|
-
.option('--json', 'JSON output')
|
|
268
|
-
.action(wrap((opts) => runProductComponents('get', opts)));
|
|
269
|
-
|
|
270
|
-
const productEnvironments = products
|
|
271
|
-
.command('environments')
|
|
272
|
-
.description('Product environments (list, get)');
|
|
273
|
-
|
|
274
|
-
productEnvironments
|
|
275
|
-
.command('list [product_tag]')
|
|
276
|
-
.description('List all environments for a product')
|
|
277
|
-
.option('--product <tag>', 'Product tag (defaults to linked project)')
|
|
278
|
-
.option('--json', 'JSON output')
|
|
279
|
-
.action(wrap((productTagArg: string | undefined, opts) =>
|
|
280
|
-
runProductEnvironments('list', { product: opts.product ?? productTagArg, json: opts.json }, []),
|
|
281
|
-
));
|
|
282
|
-
|
|
283
|
-
productEnvironments
|
|
284
|
-
.command('get <product_tag> [slug]')
|
|
285
|
-
.description('Fetch a single product environment by slug')
|
|
286
|
-
.option('--slug <slug>', 'Environment slug')
|
|
287
|
-
.option('--json', 'JSON output')
|
|
288
|
-
.action(wrap((productTagArg: string, slugArg: string | undefined, opts) =>
|
|
289
|
-
runProductEnvironments('get', { product: productTagArg, slug: opts.slug ?? slugArg, json: opts.json }, []),
|
|
290
|
-
));
|
|
291
|
-
|
|
292
|
-
const apps = program
|
|
293
|
-
.command('apps')
|
|
294
|
-
.description('Workspace integration apps CRUD (REST)')
|
|
295
|
-
.argument('[verb]', 'list | get | create | update | delete')
|
|
296
|
-
.option('--profile <name>')
|
|
297
|
-
.option('--status <status>', 'Filter for list', 'all')
|
|
298
|
-
.option('--id <id>', 'App _id')
|
|
299
|
-
.option('-t, --tag <tag>', 'App tag (get; update with --proxy)')
|
|
300
|
-
.option('--proxy', 'Update via sdk-proxy (use with --tag)')
|
|
301
|
-
.option('-f, --file <path>', 'JSON body')
|
|
302
|
-
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
303
|
-
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
304
|
-
.option('--json', 'JSON output')
|
|
305
|
-
.action(
|
|
306
|
-
wrap((verb: string | undefined, opts) => {
|
|
307
|
-
if (!verb) throw new Error('Specify a verb: list | get | create | update | delete');
|
|
308
|
-
return runApps(verb, opts, []);
|
|
309
|
-
}),
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
apps
|
|
313
|
-
.command('import <file>')
|
|
314
|
-
.description('Import Postman v2.1 or OpenAPI 3.0 into a new or existing app')
|
|
315
|
-
.requiredOption(
|
|
316
|
-
'-t, --type <format>',
|
|
317
|
-
'postman | openapi (Postman Collection v2.1 or OpenAPI 3.0)',
|
|
318
|
-
)
|
|
319
|
-
.option('--profile <name>')
|
|
320
|
-
.option('--app-id <id>', 'Update existing app by _id')
|
|
321
|
-
.option('--app-tag <tag>', 'Update existing app by tag')
|
|
322
|
-
.option('--update', 'Replace/update if app already exists')
|
|
323
|
-
.option('--version <tag>', 'App version tag', '0.0.1')
|
|
324
|
-
.option('--json', 'JSON output')
|
|
325
|
-
.action(
|
|
326
|
-
wrap((file: string, opts) =>
|
|
327
|
-
runAppsImport(file, {
|
|
328
|
-
profile: opts.profile,
|
|
329
|
-
type: opts.type,
|
|
330
|
-
appId: opts.appId,
|
|
331
|
-
appTag: opts.appTag,
|
|
332
|
-
update: Boolean(opts.update),
|
|
333
|
-
version: opts.version,
|
|
334
|
-
json: opts.json,
|
|
335
|
-
}),
|
|
336
|
-
),
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const events = program.command('events').description('Message broker CRUD (sdk-proxy, requires access key login)');
|
|
340
|
-
|
|
341
|
-
const eventTopics = events.command('topics').description('Topic CRUD for a message broker');
|
|
342
|
-
|
|
343
|
-
eventTopics
|
|
344
|
-
.argument('<verb>', 'list | get | create | update | delete')
|
|
345
|
-
.option('-t, --tag <tag>', 'Broker tag (list) or topic tag in broker:topic form (get, update, delete)')
|
|
346
|
-
.option('-f, --file <path>', 'JSON body (create, update)')
|
|
347
|
-
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
348
|
-
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
349
|
-
.option('--json', 'JSON output')
|
|
350
|
-
.action(wrap((verb: string, opts) => runEventTopicCrud(verb, opts, [])));
|
|
351
|
-
|
|
352
|
-
const resources = program.command('resources').description('Component CRUD (sdk-proxy)');
|
|
353
|
-
|
|
354
|
-
const notificationMessages = program.command('notifications').description('Notification templates');
|
|
355
|
-
notificationMessages
|
|
356
|
-
.command('messages <verb>')
|
|
357
|
-
.option('-t, --tag <tag>', 'Full notification:message tag')
|
|
358
|
-
.option('-n, --notification <tag>', 'Notification tag for list')
|
|
359
|
-
.option('-f, --file <path>', 'JSON template body for create/update')
|
|
360
|
-
.option('--json', 'JSON output')
|
|
361
|
-
.action(wrap((verb: string, opts) => runNotificationMessageCrud(verb, opts)));
|
|
362
|
-
|
|
363
|
-
resources.command('types').option('--json', 'JSON output').action(wrap((opts) => runResourcesList(Boolean(opts.json))));
|
|
364
|
-
|
|
365
|
-
resources
|
|
366
|
-
.argument('<type>', 'Resource type (e.g. storage, database, cache …)')
|
|
367
|
-
.argument('<verb>', 'list | get | create | update | delete | connect')
|
|
368
|
-
.option('-t, --tag <tag>')
|
|
369
|
-
.option('-f, --file <path>', 'JSON body (or use interactive prompts)')
|
|
370
|
-
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
371
|
-
.option('--no-interactive', 'Require -f JSON for create/update/connect')
|
|
372
|
-
.option('--json', 'JSON output')
|
|
373
|
-
.action(
|
|
374
|
-
wrap((type: string, verb: string, opts) => runResourceCrud(type, verb, opts, [])),
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
const cloud = program.command('cloud').description('Cloud connections & resources');
|
|
378
|
-
|
|
379
|
-
cloud
|
|
380
|
-
.command('connections <verb> [id]')
|
|
381
|
-
.option('-f, --file <path>')
|
|
382
|
-
.option('--json', 'JSON output')
|
|
383
|
-
.action(
|
|
384
|
-
wrap((verb: string, id: string | undefined, opts) =>
|
|
385
|
-
runCloud('connections', verb, { id, file: opts.file, json: opts.json }, []),
|
|
386
|
-
),
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
cloud
|
|
390
|
-
.command('resources <verb>')
|
|
391
|
-
.option('-f, --file <path>')
|
|
392
|
-
.option('--tier <name>', 'Tier name from `cloud tiers list` (e.g. db.t3.micro, Standard_B1ms, M0)')
|
|
393
|
-
.option('--json', 'JSON output')
|
|
394
|
-
.action(wrap((verb: string, opts) => runCloud('resources', verb, { file: opts.file, tier: opts.tier, json: opts.json }, [])));
|
|
395
|
-
|
|
396
|
-
cloud
|
|
397
|
-
.command('tiers')
|
|
398
|
-
.description('List available cloud resource tiers and estimated costs')
|
|
399
|
-
.option('--provider <name>', 'Filter by provider: aws, gcp, azure, mongodb_atlas, neo4j_aura')
|
|
400
|
-
.option('--type <resource_type>', 'Filter by resource type: database, storage, graph')
|
|
401
|
-
.option('--db-type <db_type>', 'Filter by database type: postgresql, mysql, mongodb, dynamodb, neo4j')
|
|
402
|
-
.option('--json', 'JSON output')
|
|
403
|
-
.action(wrap((opts) => runCloud('tiers', 'list', { provider: opts.provider, type: opts.type, dbType: opts.dbType, json: opts.json }, [])));
|
|
404
|
-
|
|
405
|
-
const db = program.command('db').description('Database runtime (db-proxy)');
|
|
406
|
-
|
|
407
|
-
db.command('context').option('--json', 'JSON output').action(wrap((opts) => runDbContext(Boolean(opts.json))));
|
|
408
|
-
|
|
409
|
-
const dbMigrate = db
|
|
410
|
-
.command('migrate')
|
|
411
|
-
.description('Run pending migrations from ductape/database/migrations/')
|
|
412
|
-
.option('--env <tag>', 'Override environment slug')
|
|
413
|
-
.option('--db <tag>', 'Limit to one db entry from schema.json')
|
|
414
|
-
.option('--dry-run', 'Print what would run without applying')
|
|
415
|
-
.action(wrap((opts) => runDbMigrate({ env: opts.env, db: opts.db, dryRun: Boolean(opts.dryRun) })));
|
|
416
|
-
|
|
417
|
-
dbMigrate
|
|
418
|
-
.command('status')
|
|
419
|
-
.description('Show applied vs pending migrations')
|
|
420
|
-
.option('--env <tag>', 'Override environment slug')
|
|
421
|
-
.option('--db <tag>', 'Limit to one db entry')
|
|
422
|
-
.option('--json', 'JSON output')
|
|
423
|
-
.action(wrap((opts) => runDbMigrateStatus({ env: opts.env, db: opts.db, json: Boolean(opts.json) })));
|
|
424
|
-
|
|
425
|
-
dbMigrate
|
|
426
|
-
.command('rollback')
|
|
427
|
-
.description('Roll back the last N migrations')
|
|
428
|
-
.option('--env <tag>', 'Override environment slug')
|
|
429
|
-
.option('--db <tag>', 'Limit to one db entry')
|
|
430
|
-
.option('-n <count>', 'Number of migrations to roll back', '1')
|
|
431
|
-
.action(wrap((opts) => runDbMigrateRollback({ env: opts.env, db: opts.db, n: Number(opts.n) })));
|
|
432
|
-
|
|
433
|
-
const dbSchema = db.command('schema').description('Schema management for ductape/database/schema.json');
|
|
434
|
-
|
|
435
|
-
dbSchema
|
|
436
|
-
.command('generate')
|
|
437
|
-
.description('Diff schema.json vs applied migrations and write new migration files')
|
|
438
|
-
.option('--db <tag>', 'Limit to one db entry from schema.json')
|
|
439
|
-
.option('--destructive', 'Also generate drop migrations for removed fields/tables')
|
|
440
|
-
.action(wrap((opts) => runDbSchemaGenerate({ db: opts.db, destructive: Boolean(opts.destructive) })));
|
|
441
|
-
|
|
442
|
-
dbSchema
|
|
443
|
-
.command('push')
|
|
444
|
-
.description('Read live table schema from the database and sync it to the Ductape server (enables AI field guidance)')
|
|
445
|
-
.requiredOption('--db <tag>', 'Database tag to sync')
|
|
446
|
-
.requiredOption('--env <slug>', 'Environment slug (e.g. snd, prd) whose live database to read from')
|
|
447
|
-
.action(wrap((opts) => runDbSchemaPush({ db: opts.db, env: opts.env })));
|
|
448
|
-
|
|
449
|
-
db
|
|
450
|
-
.argument('[verb]', 'connect | query | …')
|
|
451
|
-
.option('-f, --file <path>')
|
|
452
|
-
.option('--json', 'JSON output')
|
|
453
|
-
.action(
|
|
454
|
-
wrap((verb: string | undefined, opts) => {
|
|
455
|
-
if (!verb) throw new Error('Specify a verb: connect | query');
|
|
456
|
-
return runDb(verb, { file: opts.file, json: opts.json });
|
|
457
|
-
}),
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
const graph = program.command('graph').description('Graph runtime (graph-proxy)');
|
|
461
|
-
|
|
462
|
-
graph
|
|
463
|
-
.argument('<verb>', 'connect | query | …')
|
|
464
|
-
.option('-f, --file <path>')
|
|
465
|
-
.option('--json', 'JSON output')
|
|
466
|
-
.action(wrap((verb: string, opts) => runGraph(verb, { file: opts.file, json: opts.json })));
|
|
467
|
-
|
|
468
|
-
program
|
|
469
|
-
.command('secrets <verb>')
|
|
470
|
-
.description('Workspace secrets CRUD')
|
|
471
|
-
.option('-k, --key <key>')
|
|
472
|
-
.option('-f, --file <path>')
|
|
473
|
-
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
474
|
-
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
475
|
-
.option('--json', 'JSON output')
|
|
476
|
-
.action(
|
|
477
|
-
wrap((verb: string, opts) =>
|
|
478
|
-
runSecrets(verb, opts),
|
|
479
|
-
),
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
const generate = program.command('generate').description('Payload templates & code snippets');
|
|
483
|
-
|
|
484
|
-
generate
|
|
485
|
-
.command('payload <family> <method>')
|
|
486
|
-
.option('-f, --file <path>', 'targets JSON')
|
|
487
|
-
.option('--json', 'JSON output')
|
|
488
|
-
.action(
|
|
489
|
-
wrap((family: string, method: string, opts) =>
|
|
490
|
-
runGeneratePayload({ family, method, file: opts.file, json: opts.json }),
|
|
491
|
-
),
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
generate
|
|
495
|
-
.command('snippet <family> <method>')
|
|
496
|
-
.requiredOption('-l, --language <lang>', 'typescript|python|go|dotnet')
|
|
497
|
-
.option('-f, --file <path>')
|
|
498
|
-
.option('-o, --out <path>', 'Write snippet to file')
|
|
499
|
-
.option('--json', 'JSON output')
|
|
500
|
-
.action(
|
|
501
|
-
wrap((family: string, method: string, opts) =>
|
|
502
|
-
runGenerateSnippet({
|
|
503
|
-
family,
|
|
504
|
-
method,
|
|
505
|
-
language: opts.language,
|
|
506
|
-
file: opts.file,
|
|
507
|
-
json: opts.json,
|
|
508
|
-
out: opts.out,
|
|
509
|
-
}),
|
|
510
|
-
),
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
program
|
|
514
|
-
.command('completion <shell>')
|
|
515
|
-
.description('Print shell completions (bash|zsh)')
|
|
516
|
-
.action(wrap((shell: string) => {
|
|
517
|
-
if (shell !== 'bash' && shell !== 'zsh' && shell !== 'fish') {
|
|
518
|
-
throw new Error('Shell must be bash, zsh, or fish');
|
|
519
|
-
}
|
|
520
|
-
runCompletion(shell);
|
|
521
|
-
}));
|
|
522
|
-
|
|
523
|
-
program.parse();
|
package/src/lib/api-client.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { Credentials } from './config.js';
|
|
2
|
-
import { getApiUrl } from './config.js';
|
|
3
|
-
|
|
4
|
-
export interface ApiEnvelope<T> {
|
|
5
|
-
status?: boolean;
|
|
6
|
-
data?: T;
|
|
7
|
-
message?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class ApiClient {
|
|
11
|
-
constructor(
|
|
12
|
-
private readonly apiUrl: string,
|
|
13
|
-
private readonly token?: string,
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
static forProfile(profileName?: string, creds?: Credentials | null): ApiClient {
|
|
17
|
-
return new ApiClient(getApiUrl(profileName), creds?.auth_token);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async request<T>(
|
|
21
|
-
method: string,
|
|
22
|
-
path: string,
|
|
23
|
-
body?: unknown,
|
|
24
|
-
extraHeaders?: Record<string, string>,
|
|
25
|
-
): Promise<T> {
|
|
26
|
-
const url = `${this.apiUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
27
|
-
const headers: Record<string, string> = {
|
|
28
|
-
'Content-Type': 'application/json',
|
|
29
|
-
...extraHeaders,
|
|
30
|
-
};
|
|
31
|
-
if (this.token) {
|
|
32
|
-
headers.Authorization = `Bearer ${this.token}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const res = await fetch(url, {
|
|
36
|
-
method,
|
|
37
|
-
headers,
|
|
38
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const text = await res.text();
|
|
42
|
-
let parsed: ApiEnvelope<T> & T;
|
|
43
|
-
try {
|
|
44
|
-
parsed = text ? JSON.parse(text) : {};
|
|
45
|
-
} catch {
|
|
46
|
-
throw new Error(`Invalid JSON from ${url}: ${text.slice(0, 200)}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
const msg =
|
|
51
|
-
(parsed as ApiEnvelope<T>).message ??
|
|
52
|
-
(parsed as { error?: string }).error ??
|
|
53
|
-
`HTTP ${res.status}`;
|
|
54
|
-
throw new Error(msg);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (typeof (parsed as ApiEnvelope<T>).status === 'boolean' && !(parsed as ApiEnvelope<T>).status) {
|
|
58
|
-
throw new Error((parsed as ApiEnvelope<T>).message ?? 'Request failed');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const data = (parsed as ApiEnvelope<{ result?: T }>).data;
|
|
62
|
-
if (data && typeof data === 'object' && 'result' in data) {
|
|
63
|
-
return (data as { result: T }).result;
|
|
64
|
-
}
|
|
65
|
-
return (data ?? parsed) as T;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {
|
|
69
|
-
return this.request<T>('POST', path, body, headers);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
put<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {
|
|
73
|
-
return this.request<T>('PUT', path, body, headers);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
delete<T>(path: string, headers?: Record<string, string>): Promise<T> {
|
|
77
|
-
return this.request<T>('DELETE', path, undefined, headers);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
get<T>(path: string, headers?: Record<string, string>): Promise<T> {
|
|
81
|
-
return this.request<T>('GET', path, undefined, headers);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
getPath<T>(path: string, query?: Record<string, string>): Promise<T> {
|
|
85
|
-
const qs = query ? `?${new URLSearchParams(query).toString()}` : '';
|
|
86
|
-
return this.get<T>(`${path}${qs}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
package/src/lib/app-import.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import type { WorkspaceContext } from './workspace-context.js';
|
|
4
|
-
|
|
5
|
-
/** Values accepted by POST /apps/v1/import `type` field */
|
|
6
|
-
export const IMPORT_DOC_TYPES = {
|
|
7
|
-
postman: 'PostmanV2.1',
|
|
8
|
-
openapi: 'OpenApiV3.0',
|
|
9
|
-
} as const;
|
|
10
|
-
|
|
11
|
-
export type ImportFormat = keyof typeof IMPORT_DOC_TYPES;
|
|
12
|
-
|
|
13
|
-
export function parseImportFormat(value: string): ImportFormat {
|
|
14
|
-
const v = value.toLowerCase().replace(/[_\s]/g, '');
|
|
15
|
-
if (v === 'postman' || v === 'postmanv21' || v === 'postman2.1' || v === 'postmanv2.1') {
|
|
16
|
-
return 'postman';
|
|
17
|
-
}
|
|
18
|
-
if (v === 'openapi' || v === 'openapiv30' || v === 'openapi3.0' || v === 'openapi3') {
|
|
19
|
-
return 'openapi';
|
|
20
|
-
}
|
|
21
|
-
throw new Error(
|
|
22
|
-
`Unknown import type "${value}". Use: postman (Postman Collection v2.1) or openapi (OpenAPI 3.0)`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function uploadFilename(filePath: string): string {
|
|
27
|
-
const base = path.basename(filePath);
|
|
28
|
-
if (base.endsWith('.yaml') || base.endsWith('.yml')) return base;
|
|
29
|
-
if (base.endsWith('.json')) return base;
|
|
30
|
-
return `${base}.json`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function importAppFromFile(
|
|
34
|
-
ctx: WorkspaceContext,
|
|
35
|
-
opts: {
|
|
36
|
-
filePath: string;
|
|
37
|
-
format: ImportFormat;
|
|
38
|
-
appId?: string;
|
|
39
|
-
update?: boolean;
|
|
40
|
-
version?: string;
|
|
41
|
-
},
|
|
42
|
-
): Promise<unknown> {
|
|
43
|
-
const abs = path.resolve(opts.filePath);
|
|
44
|
-
if (!fs.existsSync(abs)) throw new Error(`File not found: ${abs}`);
|
|
45
|
-
|
|
46
|
-
const buffer = fs.readFileSync(abs);
|
|
47
|
-
const filename = uploadFilename(abs);
|
|
48
|
-
const form = new FormData();
|
|
49
|
-
|
|
50
|
-
form.append('file', new Blob([buffer]), filename);
|
|
51
|
-
form.append('workspace_id', ctx.workspaceId);
|
|
52
|
-
form.append('public_key', ctx.credentials.public_key);
|
|
53
|
-
form.append('user_id', ctx.credentials.user_id);
|
|
54
|
-
form.append('type', IMPORT_DOC_TYPES[opts.format]);
|
|
55
|
-
form.append('version', opts.version ?? '0.0.1');
|
|
56
|
-
form.append('update', opts.update ? 'true' : 'false');
|
|
57
|
-
if (opts.appId) form.append('app_id', opts.appId);
|
|
58
|
-
|
|
59
|
-
const url = `${ctx.apiUrl}/apps/v1/import`;
|
|
60
|
-
const res = await fetch(url, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { Authorization: `Bearer ${ctx.credentials.auth_token}` },
|
|
63
|
-
body: form,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const text = await res.text();
|
|
67
|
-
let parsed: { status?: boolean; data?: unknown; message?: string };
|
|
68
|
-
try {
|
|
69
|
-
parsed = text ? JSON.parse(text) : {};
|
|
70
|
-
} catch {
|
|
71
|
-
throw new Error(`Invalid JSON from import API: ${text.slice(0, 200)}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!res.ok) {
|
|
75
|
-
throw new Error(parsed.message ?? `Import failed (HTTP ${res.status})`);
|
|
76
|
-
}
|
|
77
|
-
if (parsed.status === false) {
|
|
78
|
-
throw new Error(parsed.message ?? 'Import failed');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return parsed.data ?? parsed;
|
|
82
|
-
}
|