@ductape/cli 0.2.23 → 0.3.1
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/db-migrate.js +47 -8
- package/dist/commands/migrate-codebase.d.ts +190 -0
- package/dist/commands/migrate-codebase.js +776 -0
- package/dist/commands/migration-artifact.d.ts +28 -0
- package/dist/commands/migration-artifact.js +101 -0
- package/dist/commands/migration-assurance.d.ts +10 -0
- package/dist/commands/migration-assurance.js +120 -0
- package/dist/commands/migration-capabilities.d.ts +53 -0
- package/dist/commands/migration-capabilities.js +66 -0
- package/dist/commands/migration-database.d.ts +26 -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 +376 -4
- package/dist/lib/migration-artifact.d.ts +19 -0
- package/dist/lib/migration-artifact.js +114 -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 -110
- 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/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { runDb, runDbContext } from './commands/db.js';
|
|
|
18
18
|
import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
|
|
19
19
|
import { runDbSchemaGenerate, runDbSchemaPush } from './commands/db-schema.js';
|
|
20
20
|
import { runGraph } from './commands/graph.js';
|
|
21
|
-
import { runSecrets } from './commands/secrets.js';
|
|
21
|
+
import { runSecretImportEnv, runSecrets } from './commands/secrets.js';
|
|
22
22
|
import { runWorkspacesCurrent, runWorkspacesList, runWorkspacesRefresh, runWorkspacesUse, } from './commands/workspaces.js';
|
|
23
23
|
import { runGeneratePayload, runGenerateSnippet } from './commands/generate.js';
|
|
24
24
|
import { runCompletion } from './commands/completion.js';
|
|
@@ -26,13 +26,31 @@ import { runProducts, runProductApps, runProductComponents, runProductEnvironmen
|
|
|
26
26
|
import { runApps } from './commands/apps.js';
|
|
27
27
|
import { runAppsImport } from './commands/apps-import.js';
|
|
28
28
|
import { runApply } from './commands/apply.js';
|
|
29
|
+
import { runMigrateCodebase } from './commands/migrate-codebase.js';
|
|
30
|
+
import { runMigrationReviewInit, runMigrationReviewRecord, runMigrationReviewFollowUp, runMigrationReviewExclude, runMigrationReviewParity, runMigrationReviewReopen, runMigrationReviewMove, runMigrationReviewMerge, runMigrationReviewSign, runMigrationReviewSnapshot, runMigrationReviewVerifySignature, runMigrationReviewValidate, } from './commands/migration-review.js';
|
|
31
|
+
import { runMigrationSliceDefine, runMigrationSliceInit, runMigrationSliceValidate, } from './commands/migration-slice.js';
|
|
32
|
+
import { runMigrationPortfolioInit, runMigrationPortfolioPropose, runMigrationPortfolioValidate, } from './commands/migration-portfolio.js';
|
|
33
|
+
import { runMigrationDatabaseInit, runMigrationDatabaseValidate } from './commands/migration-database.js';
|
|
34
|
+
import { runMigrationEnvironmentsReconcile } from './commands/migration-environments.js';
|
|
35
|
+
import { runMigrationProductsInit, runMigrationProductsValidate } from './commands/migration-products.js';
|
|
36
|
+
import { runMigrationSecretsInit, runMigrationSecretsValidate } from './commands/migration-secrets.js';
|
|
37
|
+
import { runMigrationE2EFinal, runMigrationE2EInit, runMigrationE2EValidate, } from './commands/migration-e2e.js';
|
|
38
|
+
import { runMigrationGeneratedInit, runMigrationGeneratedValidate, } from './commands/migration-generated.js';
|
|
39
|
+
import { runMigrationAssuranceValidate } from './commands/migration-assurance.js';
|
|
40
|
+
import { runMigrationVerificationValidate } from './commands/migration-verification.js';
|
|
41
|
+
import { runMigrationArtifactMigrate, runMigrationArtifactRecover, runMigrationArtifactSchemas, runMigrationArtifactValidate, } from './commands/migration-artifact.js';
|
|
42
|
+
import { runMigrationCapabilities } from './commands/migration-capabilities.js';
|
|
43
|
+
import { structuredMigrationError } from './lib/migration-artifact.js';
|
|
29
44
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
45
|
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
31
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
47
|
function wrap(fn) {
|
|
33
48
|
return (...args) => {
|
|
34
|
-
Promise.resolve(fn(...args)).catch((err) => {
|
|
35
|
-
|
|
49
|
+
Promise.resolve().then(() => fn(...args)).catch((err) => {
|
|
50
|
+
const command = process.argv[2] ?? '';
|
|
51
|
+
console.error(command === 'migrate-codebase' || command.startsWith('migration-')
|
|
52
|
+
? structuredMigrationError(err)
|
|
53
|
+
: err instanceof Error ? err.message : err);
|
|
36
54
|
process.exit(1);
|
|
37
55
|
});
|
|
38
56
|
};
|
|
@@ -143,6 +161,331 @@ program
|
|
|
143
161
|
env: opts.env,
|
|
144
162
|
language: opts.language,
|
|
145
163
|
})));
|
|
164
|
+
program
|
|
165
|
+
.command('migrate-codebase')
|
|
166
|
+
.description('Inspect a codebase and write advisory guidance for an AI-led Ductape migration')
|
|
167
|
+
.option('-C, --source <path>', 'Existing codebase', process.cwd())
|
|
168
|
+
.requiredOption('--e2e-baseline <path>', 'Passing immutable original E2E baseline manifest')
|
|
169
|
+
.option('--mode <mode>', 'in-place | new-codebase', 'new-codebase')
|
|
170
|
+
.option('--destination <path>', 'Destination for new-codebase mode')
|
|
171
|
+
.option('--product <tag>', 'Ductape product tag')
|
|
172
|
+
.option('--name <name>', 'Product display name')
|
|
173
|
+
.option('--database <tag>', 'Database tag for generated Ductape schema')
|
|
174
|
+
.option('--max-file-bytes <bytes>', 'Maximum reviewable file size', '2000000')
|
|
175
|
+
.option('--include <patterns>', 'Comma-separated repository-relative glob patterns')
|
|
176
|
+
.option('--exclude <patterns>', 'Comma-separated repository-relative glob patterns')
|
|
177
|
+
.option('--ensure-product', 'Create the product when it does not exist')
|
|
178
|
+
.option('--write', 'Write redacted advisory artifacts only; never writes application code or executable assets')
|
|
179
|
+
.option('--mcp-client-name <name>', 'Protocol-asserted MCP client name (unverified)', undefined)
|
|
180
|
+
.option('--mcp-client-version <version>', 'Protocol-asserted MCP client version (unverified)', undefined)
|
|
181
|
+
.option('--json', 'JSON output')
|
|
182
|
+
.action(wrap((opts) => runMigrateCodebase({
|
|
183
|
+
source: opts.source,
|
|
184
|
+
e2eBaseline: opts.e2eBaseline,
|
|
185
|
+
mode: opts.mode,
|
|
186
|
+
destination: opts.destination,
|
|
187
|
+
product: opts.product,
|
|
188
|
+
name: opts.name,
|
|
189
|
+
database: opts.database,
|
|
190
|
+
maxFileBytes: Number(opts.maxFileBytes),
|
|
191
|
+
include: opts.include ? String(opts.include).split(',').map((value) => value.trim()).filter(Boolean) : [],
|
|
192
|
+
exclude: opts.exclude ? String(opts.exclude).split(',').map((value) => value.trim()).filter(Boolean) : [],
|
|
193
|
+
ensureProduct: Boolean(opts.ensureProduct),
|
|
194
|
+
write: Boolean(opts.write),
|
|
195
|
+
json: Boolean(opts.json),
|
|
196
|
+
mcpClientName: opts.mcpClientName,
|
|
197
|
+
mcpClientVersion: opts.mcpClientVersion,
|
|
198
|
+
})));
|
|
199
|
+
const migrationE2E = program
|
|
200
|
+
.command('migration-e2e')
|
|
201
|
+
.description('Bind the original E2E suite and validate final migration parity without executing tests');
|
|
202
|
+
migrationE2E
|
|
203
|
+
.command('init')
|
|
204
|
+
.requiredOption('-C, --source <path>', 'Original codebase')
|
|
205
|
+
.requiredOption('--definition <path>', 'JSON containing suite files, command, and passing baseline evidence')
|
|
206
|
+
.option('--output <path>', 'Baseline manifest output path')
|
|
207
|
+
.option('--json', 'JSON output')
|
|
208
|
+
.action(wrap((opts) => runMigrationE2EInit({ ...opts, json: Boolean(opts.json) })));
|
|
209
|
+
migrationE2E
|
|
210
|
+
.command('final')
|
|
211
|
+
.requiredOption('--manifest <path>', 'Original E2E baseline manifest')
|
|
212
|
+
.requiredOption('--evidence <path>', 'JSON containing passing final-run evidence')
|
|
213
|
+
.option('--json', 'JSON output')
|
|
214
|
+
.action(wrap((opts) => runMigrationE2EFinal({ ...opts, json: Boolean(opts.json) })));
|
|
215
|
+
migrationE2E
|
|
216
|
+
.command('validate')
|
|
217
|
+
.requiredOption('--manifest <path>', 'Original E2E baseline manifest')
|
|
218
|
+
.option('--strict', 'Fail unless baseline integrity and the final run are verified')
|
|
219
|
+
.option('--json', 'JSON output')
|
|
220
|
+
.action(wrap((opts) => runMigrationE2EValidate({ ...opts, strict: Boolean(opts.strict), json: Boolean(opts.json) })));
|
|
221
|
+
const migrationGenerated = program
|
|
222
|
+
.command('migration-generated')
|
|
223
|
+
.description('Validate generated contracts and locally modified vendor source without executing generators');
|
|
224
|
+
migrationGenerated
|
|
225
|
+
.command('init')
|
|
226
|
+
.requiredOption('--analysis <path>', 'Migration analysis containing generated-source hints')
|
|
227
|
+
.requiredOption('--definition <path>', 'Contextually reviewed generated/vendor definition JSON')
|
|
228
|
+
.option('--output <path>', 'Evidence manifest output path')
|
|
229
|
+
.option('--json', 'JSON output')
|
|
230
|
+
.action(wrap((opts) => runMigrationGeneratedInit({ ...opts, json: Boolean(opts.json) })));
|
|
231
|
+
migrationGenerated
|
|
232
|
+
.command('validate')
|
|
233
|
+
.requiredOption('--file <path>', 'Generated/vendor evidence manifest')
|
|
234
|
+
.option('--strict', 'Fail on stale inputs, regeneration mismatch, or missing contract evidence')
|
|
235
|
+
.option('--json', 'JSON output')
|
|
236
|
+
.action(wrap((opts) => runMigrationGeneratedValidate({
|
|
237
|
+
...opts,
|
|
238
|
+
strict: Boolean(opts.strict),
|
|
239
|
+
json: Boolean(opts.json),
|
|
240
|
+
})));
|
|
241
|
+
program
|
|
242
|
+
.command('migration-assurance')
|
|
243
|
+
.description('Validate repository-wide component, session, resilience, runtime, API, and SDK evidence')
|
|
244
|
+
.requiredOption('--file <path>', 'Versioned migration assurance JSON')
|
|
245
|
+
.option('--strict', 'Fail unless every required assurance category is evidenced and tested')
|
|
246
|
+
.option('--json', 'JSON output')
|
|
247
|
+
.action(wrap((opts) => runMigrationAssuranceValidate({
|
|
248
|
+
file: opts.file,
|
|
249
|
+
strict: Boolean(opts.strict),
|
|
250
|
+
json: Boolean(opts.json),
|
|
251
|
+
})));
|
|
252
|
+
program
|
|
253
|
+
.command('migration-capabilities')
|
|
254
|
+
.description('Print the source-verified capability matrix for supported SDK language/version pairs')
|
|
255
|
+
.option('--language <language>', 'typescript | go | java | dotnet')
|
|
256
|
+
.option('--version <version>', 'Exact supported SDK version')
|
|
257
|
+
.option('--json', 'JSON output')
|
|
258
|
+
.action(wrap((opts) => runMigrationCapabilities({
|
|
259
|
+
language: opts.language,
|
|
260
|
+
version: opts.version,
|
|
261
|
+
json: Boolean(opts.json),
|
|
262
|
+
})));
|
|
263
|
+
program
|
|
264
|
+
.command('migration-verification')
|
|
265
|
+
.description('Validate cited automation evidence for large-repo, parity, frontend, database, and asset migration')
|
|
266
|
+
.requiredOption('--file <path>', 'Versioned migration verification matrix JSON')
|
|
267
|
+
.option('--strict', 'Fail unless every verification requirement is passing, cited, and current')
|
|
268
|
+
.option('--json', 'JSON output')
|
|
269
|
+
.action(wrap((opts) => runMigrationVerificationValidate({
|
|
270
|
+
file: opts.file,
|
|
271
|
+
strict: Boolean(opts.strict),
|
|
272
|
+
json: Boolean(opts.json),
|
|
273
|
+
})));
|
|
274
|
+
const migrationArtifact = program
|
|
275
|
+
.command('migration-artifact')
|
|
276
|
+
.description('Validate, recover, and version-migrate secured migration JSON artifacts');
|
|
277
|
+
migrationArtifact.command('validate').requiredOption('--file <path>').option('--json')
|
|
278
|
+
.action(wrap((opts) => runMigrationArtifactValidate({ file: opts.file, json: Boolean(opts.json) })));
|
|
279
|
+
migrationArtifact.command('recover').requiredOption('--file <path>').option('--json')
|
|
280
|
+
.action(wrap((opts) => runMigrationArtifactRecover({ file: opts.file, json: Boolean(opts.json) })));
|
|
281
|
+
migrationArtifact.command('migrate').requiredOption('--file <path>').requiredOption('--output <path>').option('--json')
|
|
282
|
+
.action(wrap((opts) => runMigrationArtifactMigrate({ file: opts.file, output: opts.output, json: Boolean(opts.json) })));
|
|
283
|
+
migrationArtifact.command('schemas').option('--json')
|
|
284
|
+
.action(wrap((opts) => runMigrationArtifactSchemas(Boolean(opts.json))));
|
|
285
|
+
const migrationReview = program
|
|
286
|
+
.command('migration-review')
|
|
287
|
+
.description('Track and validate an AI-led migration review without generating application code');
|
|
288
|
+
migrationReview
|
|
289
|
+
.command('init')
|
|
290
|
+
.requiredOption('--analysis <path>', 'Path to migration-guidance/analysis.json')
|
|
291
|
+
.option('--output <path>', 'Review ledger output path')
|
|
292
|
+
.option('--json', 'JSON output')
|
|
293
|
+
.action(wrap((opts) => runMigrationReviewInit({
|
|
294
|
+
analysis: opts.analysis,
|
|
295
|
+
output: opts.output,
|
|
296
|
+
json: Boolean(opts.json),
|
|
297
|
+
})));
|
|
298
|
+
migrationReview
|
|
299
|
+
.command('record')
|
|
300
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
301
|
+
.requiredOption('--file <path>', 'Repository-relative file')
|
|
302
|
+
.requiredOption('--data <path>', 'JSON file containing contextual review evidence')
|
|
303
|
+
.option('--json', 'JSON output')
|
|
304
|
+
.action(wrap((opts) => runMigrationReviewRecord({ ...opts, json: Boolean(opts.json) })));
|
|
305
|
+
migrationReview
|
|
306
|
+
.command('follow-up')
|
|
307
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
308
|
+
.requiredOption('--file <path>', 'Repository-relative source file')
|
|
309
|
+
.requiredOption('--follow-up <path>', 'Repository-relative file discovered during review')
|
|
310
|
+
.option('--json', 'JSON output')
|
|
311
|
+
.action(wrap((opts) => runMigrationReviewFollowUp({ ...opts, json: Boolean(opts.json) })));
|
|
312
|
+
migrationReview
|
|
313
|
+
.command('exclude')
|
|
314
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
315
|
+
.requiredOption('--file <path>', 'Repository-relative file')
|
|
316
|
+
.requiredOption('--classification <type>', 'generated | vendor | build | snapshot | other')
|
|
317
|
+
.requiredOption('--reason <text>', 'Why the file is excluded')
|
|
318
|
+
.option('--owner <text>', 'Owner or source of the excluded file')
|
|
319
|
+
.option('--generator <text>', 'Generator command/tool (required for generated)')
|
|
320
|
+
.option('--regeneration-test <text>', 'Regeneration verification (required for generated)')
|
|
321
|
+
.option('--json', 'JSON output')
|
|
322
|
+
.action(wrap((opts) => runMigrationReviewExclude({ ...opts, json: Boolean(opts.json) })));
|
|
323
|
+
migrationReview
|
|
324
|
+
.command('parity')
|
|
325
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
326
|
+
.requiredOption('--file <path>', 'Repository-relative file')
|
|
327
|
+
.requiredOption('--concern <type>', 'interface | functional | operational')
|
|
328
|
+
.requiredOption('--status <status>', 'verified | not_applicable')
|
|
329
|
+
.option('--evidence <text>', 'Concrete parity evidence (required for verified)')
|
|
330
|
+
.option('--reason <text>', 'Reason (required for not_applicable)')
|
|
331
|
+
.option('--json', 'JSON output')
|
|
332
|
+
.action(wrap((opts) => runMigrationReviewParity({ ...opts, json: Boolean(opts.json) })));
|
|
333
|
+
migrationReview
|
|
334
|
+
.command('reopen')
|
|
335
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
336
|
+
.requiredOption('--file <path>', 'Repository-relative file')
|
|
337
|
+
.option('--json', 'JSON output')
|
|
338
|
+
.action(wrap((opts) => runMigrationReviewReopen({ ...opts, json: Boolean(opts.json) })));
|
|
339
|
+
migrationReview
|
|
340
|
+
.command('move')
|
|
341
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
342
|
+
.requiredOption('--from <path>', 'Previous repository-relative file')
|
|
343
|
+
.requiredOption('--to <path>', 'New repository-relative file with identical reviewed content')
|
|
344
|
+
.option('--json', 'JSON output')
|
|
345
|
+
.action(wrap((opts) => runMigrationReviewMove({ ...opts, json: Boolean(opts.json) })));
|
|
346
|
+
migrationReview
|
|
347
|
+
.command('merge')
|
|
348
|
+
.requiredOption('--base <path>', 'Common-base review ledger')
|
|
349
|
+
.requiredOption('--current <path>', 'Current review ledger')
|
|
350
|
+
.requiredOption('--incoming <path>', 'Incoming parallel review ledger')
|
|
351
|
+
.requiredOption('--output <path>', 'New merged ledger; existing ledgers are never overwritten')
|
|
352
|
+
.option('--json', 'JSON output')
|
|
353
|
+
.action(wrap((opts) => runMigrationReviewMerge({ ...opts, json: Boolean(opts.json) })));
|
|
354
|
+
migrationReview
|
|
355
|
+
.command('sign')
|
|
356
|
+
.requiredOption('--ledger <path>', 'Review ledger')
|
|
357
|
+
.requiredOption('--private-key <path>', 'Trusted Ed25519 private key PEM file')
|
|
358
|
+
.requiredOption('--key-id <id>', 'Trusted signing identity')
|
|
359
|
+
.option('--output <path>', 'Detached signature output')
|
|
360
|
+
.option('--json', 'JSON output')
|
|
361
|
+
.action(wrap((opts) => runMigrationReviewSign({ ...opts, json: Boolean(opts.json) })));
|
|
362
|
+
migrationReview
|
|
363
|
+
.command('verify-signature')
|
|
364
|
+
.requiredOption('--ledger <path>', 'Review ledger')
|
|
365
|
+
.requiredOption('--signature <path>', 'Detached signature JSON')
|
|
366
|
+
.requiredOption('--public-key <path>', 'Trusted Ed25519 public key PEM file')
|
|
367
|
+
.option('--json', 'JSON output')
|
|
368
|
+
.action(wrap((opts) => runMigrationReviewVerifySignature({ ...opts, json: Boolean(opts.json) })));
|
|
369
|
+
migrationReview
|
|
370
|
+
.command('snapshot')
|
|
371
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
372
|
+
.requiredOption('--file <path>', 'Repository-relative reviewed file')
|
|
373
|
+
.option('--json', 'JSON output')
|
|
374
|
+
.action(wrap((opts) => runMigrationReviewSnapshot({
|
|
375
|
+
ledger: opts.ledger,
|
|
376
|
+
file: opts.file,
|
|
377
|
+
json: Boolean(opts.json),
|
|
378
|
+
})));
|
|
379
|
+
migrationReview
|
|
380
|
+
.command('validate')
|
|
381
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
382
|
+
.option('--strict', 'Fail unless coverage, staleness, context, follow-ups, and parity are complete')
|
|
383
|
+
.option('--json', 'JSON output')
|
|
384
|
+
.action(wrap((opts) => runMigrationReviewValidate({
|
|
385
|
+
ledger: opts.ledger,
|
|
386
|
+
strict: Boolean(opts.strict),
|
|
387
|
+
json: Boolean(opts.json),
|
|
388
|
+
})));
|
|
389
|
+
const migrationSlice = program
|
|
390
|
+
.command('migration-slice')
|
|
391
|
+
.description('Define and validate a parity-gated vertical migration slice');
|
|
392
|
+
migrationSlice
|
|
393
|
+
.command('init')
|
|
394
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
395
|
+
.requiredOption('--tag <tag>', 'Stable slice tag')
|
|
396
|
+
.requiredOption('--name <name>', 'Slice display name')
|
|
397
|
+
.requiredOption('--files <path>', 'JSON array of repository-relative slice files')
|
|
398
|
+
.option('--output <path>', 'Slice manifest output path')
|
|
399
|
+
.option('--json', 'JSON output')
|
|
400
|
+
.action(wrap((opts) => runMigrationSliceInit({ ...opts, json: Boolean(opts.json) })));
|
|
401
|
+
migrationSlice
|
|
402
|
+
.command('define')
|
|
403
|
+
.requiredOption('--slice <path>', 'Migration slice manifest path')
|
|
404
|
+
.requiredOption('--data <path>', 'JSON file containing contracts, parity requirements, assets, tests, and cutover controls')
|
|
405
|
+
.option('--json', 'JSON output')
|
|
406
|
+
.action(wrap((opts) => runMigrationSliceDefine({
|
|
407
|
+
slice: opts.slice,
|
|
408
|
+
data: opts.data,
|
|
409
|
+
json: Boolean(opts.json),
|
|
410
|
+
})));
|
|
411
|
+
migrationSlice
|
|
412
|
+
.command('validate')
|
|
413
|
+
.requiredOption('--slice <path>', 'Migration slice manifest path')
|
|
414
|
+
.option('--strict', 'Fail unless review, parity, assets, tests, cutover, and rollback are complete')
|
|
415
|
+
.option('--json', 'JSON output')
|
|
416
|
+
.action(wrap((opts) => runMigrationSliceValidate({
|
|
417
|
+
slice: opts.slice,
|
|
418
|
+
strict: Boolean(opts.strict),
|
|
419
|
+
json: Boolean(opts.json),
|
|
420
|
+
})));
|
|
421
|
+
const migrationPortfolio = program
|
|
422
|
+
.command('migration-portfolio')
|
|
423
|
+
.description('Partition and validate a large migration across bounded AI contexts');
|
|
424
|
+
migrationPortfolio
|
|
425
|
+
.command('propose')
|
|
426
|
+
.requiredOption('--ledger <path>', 'Review ledger with contextual internal references')
|
|
427
|
+
.option('--max-bytes <bytes>', 'Maximum estimated bytes per proposed partition', '128000')
|
|
428
|
+
.option('--max-tokens <tokens>', 'Maximum estimated tokens per proposed partition', '32000')
|
|
429
|
+
.option('--output <path>', 'Advisory proposal output path')
|
|
430
|
+
.option('--json', 'JSON output')
|
|
431
|
+
.action(wrap((opts) => runMigrationPortfolioPropose({
|
|
432
|
+
ledger: opts.ledger,
|
|
433
|
+
maxBytes: Number(opts.maxBytes),
|
|
434
|
+
maxTokens: Number(opts.maxTokens),
|
|
435
|
+
output: opts.output,
|
|
436
|
+
json: Boolean(opts.json),
|
|
437
|
+
})));
|
|
438
|
+
migrationPortfolio
|
|
439
|
+
.command('init')
|
|
440
|
+
.requiredOption('--ledger <path>', 'Review ledger path')
|
|
441
|
+
.requiredOption('--definition <path>', 'JSON definition containing partitions, slices, and cross-slice contracts')
|
|
442
|
+
.option('--output <path>', 'Portfolio output path')
|
|
443
|
+
.option('--json', 'JSON output')
|
|
444
|
+
.action(wrap((opts) => runMigrationPortfolioInit({ ...opts, json: Boolean(opts.json) })));
|
|
445
|
+
migrationPortfolio
|
|
446
|
+
.command('validate')
|
|
447
|
+
.requiredOption('--portfolio <path>', 'Migration portfolio path')
|
|
448
|
+
.option('--strict', 'Fail unless partitions, provenance, slices, and cross-slice contracts are complete')
|
|
449
|
+
.option('--json', 'JSON output')
|
|
450
|
+
.action(wrap((opts) => runMigrationPortfolioValidate({
|
|
451
|
+
portfolio: opts.portfolio,
|
|
452
|
+
strict: Boolean(opts.strict),
|
|
453
|
+
json: Boolean(opts.json),
|
|
454
|
+
})));
|
|
455
|
+
const migrationDatabase = program.command('migration-database').description('Validate database baseline, data movement, and cutover evidence');
|
|
456
|
+
migrationDatabase.command('init').requiredOption('--definition <path>').requiredOption('--output <path>').option('--json')
|
|
457
|
+
.action(wrap((opts) => runMigrationDatabaseInit({ ...opts, json: Boolean(opts.json) })));
|
|
458
|
+
migrationDatabase.command('validate').requiredOption('--directory <path>').option('--strict').option('--json')
|
|
459
|
+
.action(wrap((opts) => runMigrationDatabaseValidate({ ...opts, strict: Boolean(opts.strict), json: Boolean(opts.json) })));
|
|
460
|
+
program.command('migration-environments')
|
|
461
|
+
.description('Reconcile detected environments with an authenticated product environment inventory')
|
|
462
|
+
.requiredOption('--analysis <path>')
|
|
463
|
+
.requiredOption('--inventory <path>')
|
|
464
|
+
.option('--strict')
|
|
465
|
+
.option('--json')
|
|
466
|
+
.action(wrap((opts) => runMigrationEnvironmentsReconcile({
|
|
467
|
+
...opts, strict: Boolean(opts.strict), json: Boolean(opts.json),
|
|
468
|
+
})));
|
|
469
|
+
const migrationProducts = program.command('migration-products')
|
|
470
|
+
.description('Validate monorepo service ownership, product mapping, asset environment coverage, and promotion');
|
|
471
|
+
migrationProducts.command('init')
|
|
472
|
+
.requiredOption('--analysis <path>').requiredOption('--definition <path>').requiredOption('--output <path>')
|
|
473
|
+
.option('--json').action(wrap((opts) => runMigrationProductsInit({ ...opts, json: Boolean(opts.json) })));
|
|
474
|
+
migrationProducts.command('validate')
|
|
475
|
+
.requiredOption('--file <path>').option('--strict').option('--json')
|
|
476
|
+
.action(wrap((opts) => runMigrationProductsValidate({
|
|
477
|
+
...opts, strict: Boolean(opts.strict), json: Boolean(opts.json),
|
|
478
|
+
})));
|
|
479
|
+
const migrationSecrets = program.command('migration-secrets')
|
|
480
|
+
.description('Classify and reconcile names-only secret references and rotation plans');
|
|
481
|
+
migrationSecrets.command('init')
|
|
482
|
+
.requiredOption('--analysis <path>').requiredOption('--definition <path>').requiredOption('--output <path>')
|
|
483
|
+
.option('--json').action(wrap((opts) => runMigrationSecretsInit({ ...opts, json: Boolean(opts.json) })));
|
|
484
|
+
migrationSecrets.command('validate')
|
|
485
|
+
.requiredOption('--file <path>').option('--strict').option('--json')
|
|
486
|
+
.action(wrap((opts) => runMigrationSecretsValidate({
|
|
487
|
+
...opts, strict: Boolean(opts.strict), json: Boolean(opts.json),
|
|
488
|
+
})));
|
|
146
489
|
const APPLY_TYPES = ['sessions', 'notifications', 'events'];
|
|
147
490
|
program
|
|
148
491
|
.command('apply')
|
|
@@ -210,13 +553,25 @@ productComponents
|
|
|
210
553
|
.action(wrap((opts) => runProductComponents('get', opts)));
|
|
211
554
|
const productEnvironments = products
|
|
212
555
|
.command('environments')
|
|
213
|
-
.description('Product environments (list, get)');
|
|
556
|
+
.description('Product environments (list, get, idempotent create, update)');
|
|
214
557
|
productEnvironments
|
|
215
558
|
.command('list [product_tag]')
|
|
216
559
|
.description('List all environments for a product')
|
|
217
560
|
.option('--product <tag>', 'Product tag (defaults to linked project)')
|
|
218
561
|
.option('--json', 'JSON output')
|
|
219
562
|
.action(wrap((productTagArg, opts) => runProductEnvironments('list', { product: opts.product ?? productTagArg, json: opts.json }, [])));
|
|
563
|
+
productEnvironments
|
|
564
|
+
.command('create <product_tag>')
|
|
565
|
+
.description('Idempotently create a product environment and verify persistence')
|
|
566
|
+
.requiredOption('-f, --file <path>', 'Environment JSON: env_name, description, slug, active?')
|
|
567
|
+
.option('--json', 'JSON output')
|
|
568
|
+
.action(wrap((productTag, opts) => runProductEnvironments('create', { product: productTag, file: opts.file, json: opts.json }, [])));
|
|
569
|
+
productEnvironments
|
|
570
|
+
.command('update <product_tag> <slug>')
|
|
571
|
+
.description('Update a product environment and verify persistence')
|
|
572
|
+
.requiredOption('-f, --file <path>', 'Environment patch JSON')
|
|
573
|
+
.option('--json', 'JSON output')
|
|
574
|
+
.action(wrap((productTag, slug, opts) => runProductEnvironments('update', { product: productTag, slug, file: opts.file, json: opts.json }, [])));
|
|
220
575
|
productEnvironments
|
|
221
576
|
.command('get <product_tag> [slug]')
|
|
222
577
|
.description('Fetch a single product environment by slug')
|
|
@@ -369,6 +724,23 @@ program
|
|
|
369
724
|
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
370
725
|
.option('--json', 'JSON output')
|
|
371
726
|
.action(wrap((verb, opts) => runSecrets(verb, opts)));
|
|
727
|
+
program
|
|
728
|
+
.command('secrets-import-env')
|
|
729
|
+
.description('Import one secret directly from a local .env file without placing its value in CLI arguments')
|
|
730
|
+
.requiredOption('--env-file <path>', 'Local environment file')
|
|
731
|
+
.requiredOption('--source-key <name>', 'Environment variable name to read')
|
|
732
|
+
.option('-k, --key <key>', 'Destination Ductape secret key; defaults to source key')
|
|
733
|
+
.option('--env <slug>', 'Ductape environment scope')
|
|
734
|
+
.option('--description <text>')
|
|
735
|
+
.option('--json', 'Redacted JSON output')
|
|
736
|
+
.action(wrap((opts) => runSecretImportEnv({
|
|
737
|
+
envFile: opts.envFile,
|
|
738
|
+
sourceKey: opts.sourceKey,
|
|
739
|
+
key: opts.key,
|
|
740
|
+
env: opts.env,
|
|
741
|
+
description: opts.description,
|
|
742
|
+
json: Boolean(opts.json),
|
|
743
|
+
})));
|
|
372
744
|
const generate = program.command('generate').description('Payload templates & code snippets');
|
|
373
745
|
generate
|
|
374
746
|
.command('payload <family> <method>')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type MigrationArtifactErrorCode = 'ARTIFACT_TOO_LARGE' | 'ARTIFACT_INVALID_JSON' | 'ARTIFACT_SECRET_MATERIAL' | 'ARTIFACT_LOCKED' | 'ARTIFACT_EXISTS' | 'ARTIFACT_VERSION_UNSUPPORTED' | 'ARTIFACT_RECOVERY_FAILED';
|
|
2
|
+
export declare const MIGRATION_ERROR_CODES: {
|
|
3
|
+
readonly validation: "MIGRATION_VALIDATION_FAILED";
|
|
4
|
+
readonly configuration: "MIGRATION_CONFIGURATION_INVALID";
|
|
5
|
+
readonly external: "MIGRATION_EXTERNAL_OPERATION_FAILED";
|
|
6
|
+
};
|
|
7
|
+
export declare function structuredMigrationError(error: unknown): string;
|
|
8
|
+
export declare class MigrationArtifactError extends Error {
|
|
9
|
+
readonly code: MigrationArtifactErrorCode;
|
|
10
|
+
constructor(code: MigrationArtifactErrorCode, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare function readMigrationJson<T>(file: string, maxBytes?: number): T;
|
|
13
|
+
export declare function writeMigrationJson(file: string, value: unknown, opts?: {
|
|
14
|
+
createOnly?: boolean;
|
|
15
|
+
backup?: boolean;
|
|
16
|
+
mode?: number;
|
|
17
|
+
}): void;
|
|
18
|
+
export declare function recoverMigrationJson(file: string): void;
|
|
19
|
+
export declare function migrateMigrationJson(file: string, output: string): void;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const MIGRATION_ERROR_CODES = {
|
|
4
|
+
validation: 'MIGRATION_VALIDATION_FAILED',
|
|
5
|
+
configuration: 'MIGRATION_CONFIGURATION_INVALID',
|
|
6
|
+
external: 'MIGRATION_EXTERNAL_OPERATION_FAILED',
|
|
7
|
+
};
|
|
8
|
+
export function structuredMigrationError(error) {
|
|
9
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10
|
+
if (/^\[[A-Z0-9_]+\]/.test(message))
|
|
11
|
+
return message;
|
|
12
|
+
return `[${MIGRATION_ERROR_CODES.validation}] ${message}`;
|
|
13
|
+
}
|
|
14
|
+
export class MigrationArtifactError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
constructor(code, message) {
|
|
17
|
+
super(`[${code}] ${message}`);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.name = 'MigrationArtifactError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const SECRET_KEY = /^(?:value|password|passwd|token|access_key|accesskey|private_key|credential|client_secret)$/i;
|
|
23
|
+
const SECRET_VALUE = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:^|\s)eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}|[a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:[^/\s@]+@/i;
|
|
24
|
+
const SAFE_PLACEHOLDER = /^\[(?:REDACTED|NOT_READ|NOT_RETURNED)\]$|^\$Secret\{[^}]+\}$/;
|
|
25
|
+
function inspect(value, location = '$') {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
value.forEach((item, index) => inspect(item, `${location}[${index}]`));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!value || typeof value !== 'object')
|
|
31
|
+
return;
|
|
32
|
+
for (const [key, child] of Object.entries(value)) {
|
|
33
|
+
const childLocation = `${location}.${key}`;
|
|
34
|
+
if (typeof child === 'string' &&
|
|
35
|
+
((SECRET_KEY.test(key) && child.trim() && !SAFE_PLACEHOLDER.test(child)) || SECRET_VALUE.test(child))) {
|
|
36
|
+
throw new MigrationArtifactError('ARTIFACT_SECRET_MATERIAL', `Potential secret material or value-bearing field is forbidden at ${childLocation}; store only a name or $Secret{tag} reference.`);
|
|
37
|
+
}
|
|
38
|
+
inspect(child, childLocation);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function readMigrationJson(file, maxBytes = 5_000_000) {
|
|
42
|
+
const resolved = path.resolve(file);
|
|
43
|
+
const stats = fs.statSync(resolved);
|
|
44
|
+
if (stats.size > maxBytes) {
|
|
45
|
+
throw new MigrationArtifactError('ARTIFACT_TOO_LARGE', `${resolved} exceeds ${maxBytes} bytes.`);
|
|
46
|
+
}
|
|
47
|
+
let value;
|
|
48
|
+
try {
|
|
49
|
+
value = JSON.parse(fs.readFileSync(resolved, 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new MigrationArtifactError('ARTIFACT_INVALID_JSON', `${resolved}: JSON parsing failed; parser context is intentionally suppressed to prevent evidence leakage.`);
|
|
53
|
+
}
|
|
54
|
+
inspect(value);
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
export function writeMigrationJson(file, value, opts = {}) {
|
|
58
|
+
inspect(value);
|
|
59
|
+
const resolved = path.resolve(file);
|
|
60
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
61
|
+
const lock = `${resolved}.lock`;
|
|
62
|
+
let descriptor;
|
|
63
|
+
try {
|
|
64
|
+
try {
|
|
65
|
+
descriptor = fs.openSync(lock, 'wx');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code === 'EEXIST') {
|
|
69
|
+
throw new MigrationArtifactError('ARTIFACT_LOCKED', `${resolved} is being written by another process.`);
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
if (opts.createOnly && fs.existsSync(resolved)) {
|
|
74
|
+
throw new MigrationArtifactError('ARTIFACT_EXISTS', `${resolved} already exists.`);
|
|
75
|
+
}
|
|
76
|
+
if (opts.backup && fs.existsSync(resolved)) {
|
|
77
|
+
fs.copyFileSync(resolved, `${resolved}.bak`);
|
|
78
|
+
}
|
|
79
|
+
const temporary = `${resolved}.tmp-${process.pid}-${Date.now()}`;
|
|
80
|
+
fs.writeFileSync(temporary, JSON.stringify(value, null, 2) + '\n', { mode: opts.mode });
|
|
81
|
+
fs.renameSync(temporary, resolved);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (descriptor !== undefined) {
|
|
85
|
+
fs.closeSync(descriptor);
|
|
86
|
+
if (fs.existsSync(lock))
|
|
87
|
+
fs.unlinkSync(lock);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function recoverMigrationJson(file) {
|
|
92
|
+
const resolved = path.resolve(file);
|
|
93
|
+
const backup = `${resolved}.bak`;
|
|
94
|
+
try {
|
|
95
|
+
readMigrationJson(backup);
|
|
96
|
+
fs.copyFileSync(backup, resolved);
|
|
97
|
+
readMigrationJson(resolved);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new MigrationArtifactError('ARTIFACT_RECOVERY_FAILED', error instanceof Error ? error.message : String(error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function migrateMigrationJson(file, output) {
|
|
104
|
+
const value = readMigrationJson(file);
|
|
105
|
+
if (value.version === 1) {
|
|
106
|
+
writeMigrationJson(output, value, { createOnly: true });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (value.version === 0 && typeof value.artifact_kind === 'string') {
|
|
110
|
+
writeMigrationJson(output, { ...value, version: 1, migrated_from_version: 0 }, { createOnly: true });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw new MigrationArtifactError('ARTIFACT_VERSION_UNSUPPORTED', `No controlled migration exists for version ${String(value.version)}.`);
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ductape/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ductape": "dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
".env.ductape.example"
|
|
16
|
+
],
|
|
10
17
|
"scripts": {
|
|
11
18
|
"build": "tsc",
|
|
12
19
|
"dev": "tsx src/index.ts",
|
package/API_PARITY.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# CLI ↔ API parity (Nest / Workbench)
|
|
2
|
-
|
|
3
|
-
The CLI uses the **same REST paths as the Workbench and TypeScript SDK**.
|
|
4
|
-
|
|
5
|
-
| Profile | `api_url` | Example login URL |
|
|
6
|
-
|---------|-----------|-------------------|
|
|
7
|
-
| `cloud` | `https://api.ductape.app` | `https://api.ductape.app/users/v1/login` |
|
|
8
|
-
| `local` | `http://localhost:4311/api` | `http://localhost:4311/api/users/v1/login` |
|
|
9
|
-
|
|
10
|
-
Production does **not** use an `/api` prefix; the local docker gateway does.
|
|
11
|
-
|
|
12
|
-
## Gateway routing
|
|
13
|
-
|
|
14
|
-
| Client calls | Gateway (`/api/...`) | Nest service receives |
|
|
15
|
-
|--------------|----------------------|------------------------|
|
|
16
|
-
| `{api}/users/v1/login` | `/api/users/` → users:8002 | `/users/v1/login` |
|
|
17
|
-
| `{api}/workspaces/v1/fetch/...` | `/api/workspaces/` → workspaces:8001 | `/workspaces/v1/...` |
|
|
18
|
-
| `{api}/apps/v1/import` | `/api/apps/` → apps:8004 | `/apps/v1/import` |
|
|
19
|
-
| `{api}/integrations/v1/create` | `/api/integrations/` → integrations:8009 | `/integrations/v1/create` |
|
|
20
|
-
| `{api}/proxy/v1/sdk-proxy/execute` | `/api/proxy/` → proxy:8020 | encrypted sdk-proxy |
|
|
21
|
-
|
|
22
|
-
Legacy Express services are **not** called by the CLI. All commands target Nest controllers under `platform/nest/apps/*/src/**/controllers/`.
|
|
23
|
-
|
|
24
|
-
## Command → endpoint map
|
|
25
|
-
|
|
26
|
-
| CLI | Method | Path (after `{api_url}`) | Workbench equivalent |
|
|
27
|
-
|-----|--------|---------------------------|-------------------|
|
|
28
|
-
| `ductape login` | POST | `/users/v1/login` | Login modal |
|
|
29
|
-
| `ductape workspaces list` | GET | `/workspaces/v1/fetch/:user_id` | Workspace list |
|
|
30
|
-
| `ductape products list` | GET | `/integrations/v1/workspace/:id/:status` | Products sidebar |
|
|
31
|
-
| `ductape products create` | POST | `/integrations/v1/create` | Create product |
|
|
32
|
-
| `ductape products update` | sdk-proxy | `product.update` | Product settings |
|
|
33
|
-
| `ductape products delete` | DELETE | `/integrations/v1/:id` | Delete product |
|
|
34
|
-
| `ductape apps list` | GET | `/apps/v1/workspace/:id/:status` | Apps sidebar |
|
|
35
|
-
| `ductape apps create` | POST | `/apps/v1/create` | Create app |
|
|
36
|
-
| `ductape apps import` | POST multipart | `/apps/v1/import` | App import dialog |
|
|
37
|
-
| `ductape apps update` | PUT | `/apps/v1/:app_id` | App settings |
|
|
38
|
-
| `ductape apps delete` | DELETE | `/apps/v1/:app_id` | Delete app |
|
|
39
|
-
| `ductape resources *` | POST | `/proxy/v1/sdk-proxy/execute` | SDK proxy (encrypted) |
|
|
40
|
-
| `ductape db *` | POST | `/proxy/v1/db-proxy/...` | Database proxy |
|
|
41
|
-
| `ductape graph *` | POST | `/proxy/v1/graph-proxy/...` | Graph proxy |
|
|
42
|
-
|
|
43
|
-
## Import body fields (`apps import`)
|
|
44
|
-
|
|
45
|
-
Matches `AppsImportController` and `@ductape/sdk` `AppApiService.importApp`:
|
|
46
|
-
|
|
47
|
-
| Field | CLI / SDK value |
|
|
48
|
-
|-------|-----------------|
|
|
49
|
-
| `type` | `PostmanV2.1` or `OpenApiV3.0` |
|
|
50
|
-
| `version` | default `0.0.1` |
|
|
51
|
-
| `update` | `true` / `false` |
|
|
52
|
-
| `workspace_id`, `user_id`, `public_key` | from session |
|
|
53
|
-
| `app_id` | optional (`--app-id` / `--app-tag`) |
|
|
54
|
-
| `file` | multipart upload |
|
|
55
|
-
|
|
56
|
-
## SDK base URL note
|
|
57
|
-
|
|
58
|
-
The TS SDK client uses host `https://api.ductape.app` with paths like `/apps/v1/...` (no `/api` segment). Production routing may terminate TLS at a layer that forwards directly to services. **CLI and Workbench** use `{host}/api` as `api_url` — always set `ductape profiles use local` to `http://localhost:4311/api` for local parity.
|