@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +7 -0
  3. package/dist/commands/generate.js +1 -1
  4. package/dist/commands/migrate-codebase.d.ts +176 -0
  5. package/dist/commands/migrate-codebase.js +753 -0
  6. package/dist/commands/migration-artifact.d.ts +32 -0
  7. package/dist/commands/migration-artifact.js +48 -0
  8. package/dist/commands/migration-assurance.d.ts +10 -0
  9. package/dist/commands/migration-assurance.js +120 -0
  10. package/dist/commands/migration-database.d.ts +21 -0
  11. package/dist/commands/migration-database.js +162 -0
  12. package/dist/commands/migration-e2e.d.ts +68 -0
  13. package/dist/commands/migration-e2e.js +189 -0
  14. package/dist/commands/migration-environments.d.ts +20 -0
  15. package/dist/commands/migration-environments.js +58 -0
  16. package/dist/commands/migration-generated.d.ts +68 -0
  17. package/dist/commands/migration-generated.js +217 -0
  18. package/dist/commands/migration-portfolio.d.ts +88 -0
  19. package/dist/commands/migration-portfolio.js +223 -0
  20. package/dist/commands/migration-products.d.ts +23 -0
  21. package/dist/commands/migration-products.js +88 -0
  22. package/dist/commands/migration-review.d.ts +248 -0
  23. package/dist/commands/migration-review.js +624 -0
  24. package/dist/commands/migration-secrets.d.ts +24 -0
  25. package/dist/commands/migration-secrets.js +93 -0
  26. package/dist/commands/migration-slice.d.ts +132 -0
  27. package/dist/commands/migration-slice.js +266 -0
  28. package/dist/commands/migration-verification.d.ts +12 -0
  29. package/dist/commands/migration-verification.js +194 -0
  30. package/dist/commands/products.d.ts +1 -0
  31. package/dist/commands/products.js +33 -1
  32. package/dist/commands/secrets.d.ts +8 -0
  33. package/dist/commands/secrets.js +44 -0
  34. package/dist/index.js +354 -2
  35. package/dist/lib/integrations.js +6 -1
  36. package/dist/lib/migration-artifact.d.ts +13 -0
  37. package/dist/lib/migration-artifact.js +103 -0
  38. package/package.json +8 -1
  39. package/API_PARITY.md +0 -58
  40. package/DB_MIGRATE_PLAN.md +0 -223
  41. package/ductape.example.ts +0 -18
  42. package/src/commands/apply.ts +0 -153
  43. package/src/commands/apps-import.ts +0 -43
  44. package/src/commands/apps.ts +0 -92
  45. package/src/commands/cloud.ts +0 -68
  46. package/src/commands/completion.ts +0 -119
  47. package/src/commands/db-migrate.ts +0 -209
  48. package/src/commands/db-schema.ts +0 -429
  49. package/src/commands/db.ts +0 -63
  50. package/src/commands/generate.ts +0 -74
  51. package/src/commands/graph.ts +0 -51
  52. package/src/commands/init.ts +0 -39
  53. package/src/commands/install.ts +0 -76
  54. package/src/commands/link.ts +0 -84
  55. package/src/commands/login.ts +0 -145
  56. package/src/commands/logout.ts +0 -7
  57. package/src/commands/platform.ts +0 -123
  58. package/src/commands/products.ts +0 -228
  59. package/src/commands/profiles.ts +0 -23
  60. package/src/commands/resources.ts +0 -213
  61. package/src/commands/secrets.ts +0 -46
  62. package/src/commands/unlink.ts +0 -15
  63. package/src/commands/whoami.ts +0 -27
  64. package/src/commands/workspaces.ts +0 -98
  65. package/src/index.ts +0 -523
  66. package/src/lib/api-client.ts +0 -88
  67. package/src/lib/app-import.ts +0 -82
  68. package/src/lib/apply-loaders.ts +0 -30
  69. package/src/lib/config.ts +0 -230
  70. package/src/lib/context-store.ts +0 -77
  71. package/src/lib/db-methods.ts +0 -56
  72. package/src/lib/db-types.ts +0 -104
  73. package/src/lib/encryption.ts +0 -12
  74. package/src/lib/forms/enums.ts +0 -52
  75. package/src/lib/forms/index.ts +0 -8
  76. package/src/lib/forms/prompt.ts +0 -209
  77. package/src/lib/forms/schemas.ts +0 -288
  78. package/src/lib/forms/types.ts +0 -27
  79. package/src/lib/http.ts +0 -76
  80. package/src/lib/integrations.ts +0 -105
  81. package/src/lib/interactive-opts.ts +0 -13
  82. package/src/lib/migration-files.ts +0 -58
  83. package/src/lib/output.ts +0 -39
  84. package/src/lib/platform-api.ts +0 -209
  85. package/src/lib/proxy/context.ts +0 -94
  86. package/src/lib/proxy/db-proxy.ts +0 -40
  87. package/src/lib/proxy/graph-proxy.ts +0 -64
  88. package/src/lib/proxy/sdk-proxy.ts +0 -58
  89. package/src/lib/read-body.ts +0 -57
  90. package/src/lib/resources.ts +0 -160
  91. package/src/lib/schema-loader.ts +0 -152
  92. package/src/lib/snippets.ts +0 -165
  93. package/src/lib/templates.ts +0 -157
  94. package/src/lib/workspace-context.ts +0 -43
  95. package/src/lib/workspaces.ts +0 -283
  96. package/test/apply-loaders.test.ts +0 -40
  97. package/test/encryption.test.ts +0 -20
  98. package/test/forms.test.ts +0 -25
  99. package/test/http.test.ts +0 -15
  100. package/test/platform-api.test.ts +0 -42
  101. package/test/resources.test.ts +0 -23
  102. package/test/workspace-resolve.test.ts +0 -29
  103. package/tsconfig.json +0 -15
@@ -1,429 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { findProjectConfig } from '../lib/config.js';
4
- import { loadSchemaFile, getSchemaPath, parseTableDef } from '../lib/schema-loader.js';
5
- import { loadMigrationFiles, writeMigrationFile } from '../lib/migration-files.js';
6
- import { fail } from '../lib/output.js';
7
- import { getDbProxy, requireSession } from '../lib/proxy/context.js';
8
- import { pushDatabaseSchema } from '../lib/integrations.js';
9
- import type { TableSchemaRecord, TableColumnRecord } from '../lib/integrations.js';
10
- import type { SchemaEntry, MongooseFieldDef } from '../lib/schema-loader.js';
11
- import type {
12
- IMigration,
13
- IMigrationOperation,
14
- ICreateCollectionOp,
15
- IDropCollectionOp,
16
- IAddFieldOp,
17
- ICreateIndexOp,
18
- } from '../lib/db-types.js';
19
-
20
- interface SchemaGenerateOpts {
21
- db?: string;
22
- destructive?: boolean;
23
- }
24
-
25
- interface ReconstructedTable {
26
- fields: Set<string>;
27
- }
28
-
29
- function replayMigrations(migrations: IMigration[]): Map<string, ReconstructedTable> {
30
- const schema = new Map<string, ReconstructedTable>();
31
- for (const migration of migrations) {
32
- for (const op of migration.up) {
33
- if (op.type === 'createCollection') {
34
- if (!schema.has(op.name)) {
35
- schema.set(op.name, { fields: new Set(op.fields.map((f) => f.name)) });
36
- }
37
- } else if (op.type === 'dropCollection') {
38
- schema.delete(op.name);
39
- } else if (op.type === 'addField') {
40
- const t = schema.get(op.collection);
41
- if (t) t.fields.add(op.field.name);
42
- } else if (op.type === 'dropField') {
43
- const t = schema.get(op.collection);
44
- if (t) t.fields.delete(op.fieldName);
45
- }
46
- }
47
- }
48
- return schema;
49
- }
50
-
51
- function columnTypeToMongooseType(raw: string): string {
52
- // Normalise: uppercase, collapse whitespace (handles "character varying", "timestamp without time zone", etc.)
53
- const t = raw.toUpperCase().replace(/\s+/g, ' ').trim();
54
- switch (t) {
55
- case 'BIGINT': case 'INT8': return 'bigint';
56
- case 'SMALLINT': case 'TINYINT': case 'INT2': return 'smallint';
57
- case 'INTEGER': case 'INT': case 'INT4': case 'NUMBER': return 'number';
58
- case 'FLOAT': case 'REAL': case 'FLOAT4': case 'FLOAT8': case 'DOUBLE PRECISION': return 'number'; // caller sets float:true
59
- case 'DOUBLE': return 'double';
60
- case 'DECIMAL': case 'NUMERIC': case 'DECIMAL128': return 'decimal';
61
- case 'TEXT': case 'MEDIUMTEXT': case 'LONGTEXT': case 'TINYTEXT': return 'text';
62
- case 'STRING': case 'VARCHAR': case 'CHARACTER VARYING': case 'CHAR': case 'CHARACTER': case 'BPCHAR': return 'string';
63
- case 'BOOLEAN': case 'BOOL': return 'boolean';
64
- case 'DATE': case 'DATETIME': return 'date';
65
- case 'TIME': case 'TIME WITHOUT TIME ZONE': case 'TIME WITH TIME ZONE': return 'time';
66
- case 'TIMESTAMP': case 'TIMESTAMPTZ': case 'TIMESTAMP WITHOUT TIME ZONE': case 'TIMESTAMP WITH TIME ZONE': return 'timestamp';
67
- case 'JSON': case 'JSONB': return 'json';
68
- case 'BINARY': case 'VARBINARY': case 'BYTEA': return 'binary';
69
- case 'BLOB': case 'MEDIUMBLOB': case 'LONGBLOB': case 'TINYBLOB': return 'blob';
70
- case 'UUID': case 'OBJECTID': return 'uuid';
71
- case 'OBJECT': case 'MIXED': case 'MAP': case 'HSTORE': return 'mixed';
72
- case 'ARRAY': return 'array';
73
- default: return 'string';
74
- }
75
- }
76
-
77
- function buildMongooseFieldDef(col: Record<string, unknown>): MongooseFieldDef {
78
- const rawType = String(col.type ?? 'string');
79
- const mongooseType = columnTypeToMongooseType(rawType);
80
- const def: MongooseFieldDef = { type: mongooseType };
81
-
82
- const upperType = rawType.toUpperCase().trim();
83
- if (upperType === 'FLOAT' || upperType === 'REAL' || upperType === 'FLOAT4' || upperType === 'FLOAT8' || upperType === 'DOUBLE PRECISION') def.float = true;
84
-
85
- // Handle both IColumnDefinition style (primaryKey/unique) and IColumnInfo style (isPrimaryKey/isUnique)
86
- if (col.nullable === false || col.required === true) def.required = true;
87
- if (col.unique === true || col.isUnique === true) def.unique = true;
88
- if (col.primaryKey === true || col.isPrimaryKey === true) def.primaryKey = true;
89
- if (col.autoIncrement === true || col.isAutoIncrement === true || col.autoGenerate === true) def.autoGenerate = true;
90
-
91
- // Handle both camelCase (defaultValue) and snake_case (default_value) from different adapters
92
- const defVal = col.defaultValue ?? col.default_value;
93
- if (defVal !== undefined && defVal !== null) def.default = defVal as unknown;
94
-
95
- // Handle both camelCase (length/maxLength) and snake_case (max_length) from different adapters
96
- const len = col.length ?? col.maxLength ?? col.max_length;
97
- if (typeof len === 'number') def.maxlength = len;
98
-
99
- if (Array.isArray(col.enumValues) && (col.enumValues as string[]).length > 0) {
100
- def.type = 'string';
101
- def.enum = col.enumValues as string[];
102
- }
103
-
104
- return def;
105
- }
106
-
107
- async function tryHydrateFromCloud(
108
- dir: string,
109
- schemaPath: string,
110
- opts: SchemaGenerateOpts,
111
- ): Promise<SchemaEntry[]> {
112
- if (!opts.db) return [];
113
-
114
- let session: ReturnType<typeof requireSession>;
115
- try {
116
- session = requireSession();
117
- } catch {
118
- return [];
119
- }
120
-
121
- const proxy = getDbProxy(session);
122
- const envSlug = session.project.env_slug;
123
- const productTag = session.project.product_tag;
124
- const dbContext = { database: opts.db, env: envSlug, product: productTag };
125
-
126
- let tableNames: string[] = [];
127
- try {
128
- const raw = await proxy.execute<unknown>('schema.list', [null], dbContext);
129
- if (Array.isArray(raw)) {
130
- tableNames = (raw as unknown[]).map((r) =>
131
- typeof r === 'string' ? r : typeof (r as Record<string, unknown>).name === 'string' ? String((r as Record<string, unknown>).name) : null,
132
- ).filter((n): n is string => n !== null);
133
- }
134
- } catch {
135
- return [];
136
- }
137
-
138
- if (tableNames.length === 0) return [];
139
-
140
- const tables: Record<string, Record<string, MongooseFieldDef>> = {};
141
-
142
- for (const tableName of tableNames) {
143
- try {
144
- const tableSchema = await proxy.execute<unknown>('schema.describe', [tableName], dbContext);
145
- const cols: unknown[] = Array.isArray((tableSchema as Record<string, unknown>)?.columns)
146
- ? ((tableSchema as Record<string, unknown>).columns as unknown[])
147
- : Array.isArray(tableSchema)
148
- ? (tableSchema as unknown[])
149
- : [];
150
-
151
- if (cols.length === 0) {
152
- tables[tableName] = {};
153
- continue;
154
- }
155
-
156
- const indexedFields = new Set<string>();
157
- const uniqueIndexFields = new Set<string>();
158
- const rawIndexes = (tableSchema as Record<string, unknown>)?.indexes;
159
- if (Array.isArray(rawIndexes)) {
160
- for (const idx of rawIndexes as Record<string, unknown>[]) {
161
- const idxCols = Array.isArray(idx.columns) ? (idx.columns as Record<string, unknown>[]) : [];
162
- if (idxCols.length === 1) {
163
- const fieldName = String(idxCols[0].name ?? idxCols[0]);
164
- if (idx.unique) uniqueIndexFields.add(fieldName);
165
- else indexedFields.add(fieldName);
166
- }
167
- }
168
- }
169
-
170
- const fieldMap: Record<string, MongooseFieldDef> = {};
171
- for (const col of cols as Record<string, unknown>[]) {
172
- const name = String(col.name ?? '');
173
- if (!name) continue;
174
- const def = buildMongooseFieldDef(col);
175
- if (uniqueIndexFields.has(name)) { def.unique = true; def.index = true; }
176
- else if (indexedFields.has(name)) def.index = true;
177
- fieldMap[name] = def;
178
- }
179
- tables[tableName] = fieldMap;
180
- } catch {
181
- tables[tableName] = {};
182
- }
183
- }
184
-
185
- const entry: SchemaEntry = { db: opts.db, tables };
186
- const entries = [entry];
187
- fs.writeFileSync(schemaPath, JSON.stringify(entries, null, 2), 'utf8');
188
- console.log(`[${opts.db}] Hydrated schema.json from cloud (${tableNames.length} collection(s)): ${tableNames.join(', ')}`);
189
- return entries;
190
- }
191
-
192
- export async function runDbSchemaGenerate(opts: SchemaGenerateOpts): Promise<void> {
193
- const found = findProjectConfig();
194
- if (!found) fail('No linked project. Run `ductape link` from your project directory.');
195
-
196
- const { dir } = found;
197
- const schemaPath = getSchemaPath(dir);
198
-
199
- let schemaEntries: SchemaEntry[];
200
- let wasScaffolded = false;
201
-
202
- if (!fs.existsSync(schemaPath)) {
203
- // Create directory and empty schema.json first
204
- fs.mkdirSync(path.dirname(schemaPath), { recursive: true });
205
- fs.writeFileSync(schemaPath, '[]', 'utf8');
206
- wasScaffolded = true;
207
- console.log(`Created ${path.relative(dir, schemaPath)}`);
208
-
209
- // Try to hydrate from the cloud (requires --db and an active session)
210
- schemaEntries = await tryHydrateFromCloud(dir, schemaPath, opts);
211
-
212
- if (schemaEntries.length === 0) {
213
- const hint = opts.db
214
- ? `(no existing collections found in "${opts.db}" for env "${requireSilent()?.project?.env_slug ?? 'default'}")`
215
- : '(pass --db <tag> to auto-import existing collections from the cloud)';
216
- console.log(`schema.json is empty ${hint}. Add your collection definitions and run \`ductape db schema generate\` again.`);
217
- return;
218
- }
219
- } else {
220
- schemaEntries = loadSchemaFile(dir);
221
- }
222
-
223
- const targets = opts.db
224
- ? schemaEntries.filter((e) => e.db === opts.db)
225
- : schemaEntries;
226
-
227
- if (targets.length === 0) {
228
- if (wasScaffolded) return; // already printed a message above
229
- fail(opts.db ? `No db entry found for tag "${opts.db}".` : 'schema.json is empty.');
230
- }
231
-
232
- let totalGenerated = 0;
233
-
234
- for (const entry of targets) {
235
- const { db, tables } = entry;
236
- const existing = loadMigrationFiles(dir, db);
237
- const current = replayMigrations(existing);
238
- const newMigrations: IMigration[] = [];
239
- let counter = existing.length + 1;
240
-
241
- for (const [tableName, fieldDefs] of Object.entries(tables)) {
242
- const parsed = parseTableDef(tableName, fieldDefs);
243
- const currentTable = current.get(tableName);
244
-
245
- if (!currentTable) {
246
- const up: IMigrationOperation[] = [
247
- {
248
- type: 'createCollection',
249
- name: tableName,
250
- fields: parsed.fields,
251
- ifNotExists: true,
252
- } as ICreateCollectionOp,
253
- ];
254
- for (const { fieldName, unique } of parsed.indexFields) {
255
- up.push({
256
- type: 'createIndex',
257
- collection: tableName,
258
- name: `${tableName}_${fieldName}_idx`,
259
- fields: [{ name: fieldName }],
260
- unique,
261
- ifNotExists: true,
262
- } as ICreateIndexOp);
263
- }
264
- const down: IMigrationOperation[] = [
265
- { type: 'dropCollection', name: tableName, ifExists: true } as IDropCollectionOp,
266
- ];
267
-
268
- newMigrations.push({
269
- tag: `create_${tableName}`,
270
- name: `Create collection ${tableName}`,
271
- up,
272
- down,
273
- createdAt: new Date().toISOString(),
274
- });
275
- } else {
276
- const addOps: IMigrationOperation[] = [];
277
- const dropOps: IMigrationOperation[] = [];
278
-
279
- for (const field of parsed.fields) {
280
- if (!currentTable.fields.has(field.name)) {
281
- addOps.push({ type: 'addField', collection: tableName, field } as IAddFieldOp);
282
- }
283
- }
284
-
285
- const desiredFields = new Set(parsed.fields.map((f) => f.name));
286
- const removedFields: string[] = [];
287
- for (const existingField of currentTable.fields) {
288
- if (!desiredFields.has(existingField)) {
289
- removedFields.push(existingField);
290
- if (opts.destructive) {
291
- dropOps.push({ type: 'dropField', collection: tableName, fieldName: existingField });
292
- }
293
- }
294
- }
295
- if (!opts.destructive && removedFields.length > 0) {
296
- console.warn(
297
- ` [${db}] Warning: field(s) removed from schema.json for "${tableName}": ${removedFields.join(', ')}. Pass --destructive to generate drop migrations.`,
298
- );
299
- }
300
-
301
- const allOps = [...addOps, ...dropOps];
302
- if (allOps.length > 0) {
303
- const down: IMigrationOperation[] = addOps.map((op) => ({
304
- type: 'dropField' as const,
305
- collection: tableName,
306
- fieldName: (op as IAddFieldOp).field.name,
307
- }));
308
-
309
- newMigrations.push({
310
- tag: `alter_${tableName}_${Date.now()}`,
311
- name: `Alter collection ${tableName}`,
312
- up: allOps,
313
- down,
314
- createdAt: new Date().toISOString(),
315
- });
316
- }
317
- }
318
- }
319
-
320
- if (newMigrations.length === 0) {
321
- console.log(`[${db}] Schema is up to date. No migrations generated.`);
322
- continue;
323
- }
324
-
325
- for (const migration of newMigrations) {
326
- const filePath = writeMigrationFile(dir, db, migration, counter++);
327
- console.log(`[${db}] Generated ${path.basename(filePath)}`);
328
- totalGenerated++;
329
- }
330
- }
331
-
332
- if (totalGenerated > 0) {
333
- console.log(`\nGenerated ${totalGenerated} migration file(s). Run \`ductape db migrate\` to apply them.`);
334
- }
335
- }
336
-
337
- function requireSilent(): ReturnType<typeof requireSession> | null {
338
- try { return requireSession(); } catch { return null; }
339
- }
340
-
341
- interface SchemaPushOpts {
342
- db: string;
343
- env: string;
344
- }
345
-
346
- function adaptColumn(col: Record<string, unknown>): TableColumnRecord {
347
- return {
348
- name: String(col.name ?? ''),
349
- type: String(col.type ?? 'string'),
350
- nullable: col.nullable !== false && col.nullable !== 'NO',
351
- primaryKey: Boolean(col.primaryKey ?? col.isPrimaryKey),
352
- unique: Boolean(col.unique ?? col.isUnique),
353
- autoIncrement: Boolean(col.autoIncrement ?? col.isAutoIncrement ?? col.autoGenerate),
354
- ...(col.defaultValue != null ? { defaultValue: col.defaultValue } : {}),
355
- };
356
- }
357
-
358
- export async function runDbSchemaPush(opts: SchemaPushOpts): Promise<void> {
359
- const session = requireSession();
360
- const proxy = getDbProxy(session);
361
- const envSlug = opts.env;
362
- const productTag = session.project.product_tag;
363
- const dbContext = { database: opts.db, env: envSlug, product: productTag };
364
-
365
- console.log(`Connecting to database "${opts.db}" (env: ${envSlug})...`);
366
-
367
- let tableNames: string[] = [];
368
- try {
369
- const raw = await proxy.execute<unknown>('schema.list', [null], dbContext);
370
- if (Array.isArray(raw)) {
371
- tableNames = (raw as unknown[]).map((r) =>
372
- typeof r === 'string' ? r : String((r as Record<string, unknown>).name ?? ''),
373
- ).filter((n) => Boolean(n) && !n.startsWith('_ductape_'));
374
- }
375
- } catch (e) {
376
- throw new Error(`Failed to list tables: ${(e as Error).message}. Make sure the database is connected.`);
377
- }
378
-
379
- if (tableNames.length === 0) {
380
- console.log('No tables found in the database. Nothing to push.');
381
- return;
382
- }
383
-
384
- console.log(`Found ${tableNames.length} table(s): ${tableNames.join(', ')}`);
385
-
386
- const now = new Date().toISOString();
387
- const tableSchemas: TableSchemaRecord[] = [];
388
-
389
- for (const tableName of tableNames) {
390
- try {
391
- const tableSchema = await proxy.execute<unknown>('schema.describe', [tableName], dbContext);
392
- const cols: unknown[] = Array.isArray((tableSchema as Record<string, unknown>)?.columns)
393
- ? ((tableSchema as Record<string, unknown>).columns as unknown[])
394
- : Array.isArray(tableSchema) ? (tableSchema as unknown[]) : [];
395
-
396
- if (cols.length === 0) {
397
- console.warn(` [skip] ${tableName}: no columns returned`);
398
- continue;
399
- }
400
-
401
- const columns = (cols as Record<string, unknown>[])
402
- .filter((c) => c.name)
403
- .map(adaptColumn);
404
-
405
- tableSchemas.push({ table: tableName, columns, updated_at: now });
406
- console.log(` ${tableName}: ${columns.length} column(s)`);
407
- } catch (e) {
408
- console.warn(` [skip] ${tableName}: ${(e as Error).message}`);
409
- }
410
- }
411
-
412
- if (tableSchemas.length === 0) {
413
- console.log('No schema data collected. Nothing pushed.');
414
- return;
415
- }
416
-
417
- console.log(`\nPushing schema for ${tableSchemas.length} table(s) to Ductape...`);
418
-
419
- const result = await pushDatabaseSchema(session.apiUrl, session.credentials, {
420
- workspace_id: session.project.workspace_id,
421
- product_tag: productTag,
422
- database_tag: opts.db,
423
- env_slug: envSlug,
424
- table_schemas: tableSchemas,
425
- });
426
-
427
- console.log(`Done. Synced ${result.synced} table(s): ${result.tables.join(', ')}`);
428
- console.log('The AI payload generator will now use this schema for field guidance.');
429
- }
@@ -1,63 +0,0 @@
1
- import { readJsonBody } from '../lib/read-body.js';
2
- import { setDatabaseContext } from '../lib/context-store.js';
3
- import { DB_PROXY_METHODS } from '../lib/db-methods.js';
4
- import { getDbContext, getDbProxy, requireSession } from '../lib/proxy/context.js';
5
- import { loadRuntimeContext } from '../lib/context-store.js';
6
- import { printJson } from '../lib/output.js';
7
-
8
- export async function runDb(
9
- method: string,
10
- opts: { file?: string; json?: boolean },
11
- ): Promise<void> {
12
- const session = requireSession();
13
- const proxy = getDbProxy(session);
14
- const runtime = loadRuntimeContext(session.project.workspace_id, session.project.product_tag);
15
- const dbContext = runtime?.database_context ?? null;
16
- const body = readJsonBody(opts.file) as Record<string, unknown>;
17
-
18
- const m = method.toLowerCase();
19
- if (!DB_PROXY_METHODS.includes(m as (typeof DB_PROXY_METHODS)[number]) && !m.includes('.')) {
20
- console.warn(`Method "${m}" not in known list; sending anyway.`);
21
- }
22
-
23
- let params: unknown[] = [];
24
- if (m === 'connect') {
25
- const cfg = {
26
- product: session.project.product_tag,
27
- env: session.project.env_slug,
28
- database: body.database ?? body.tag,
29
- ...body,
30
- };
31
- params = [cfg];
32
- } else if (m === 'list') {
33
- params = [session.project.product_tag];
34
- } else if (Object.keys(body).length > 0) {
35
- params = [
36
- {
37
- product: session.project.product_tag,
38
- env: session.project.env_slug,
39
- ...body,
40
- },
41
- ];
42
- }
43
-
44
- const result = await proxy.execute(m, params, dbContext);
45
-
46
- if (m === 'connect' && params[0] && typeof params[0] === 'object') {
47
- const cfg = params[0] as { database: string; env: string; product: string };
48
- setDatabaseContext(session.project.workspace_id, session.project.product_tag, session.project.env_slug, {
49
- database: String(cfg.database),
50
- env: String(cfg.env),
51
- product: String(cfg.product),
52
- });
53
- }
54
-
55
- printJson(result, Boolean(opts.json));
56
- }
57
-
58
- export function runDbContext(json: boolean): void {
59
- const session = requireSession();
60
- const ctx = getDbContext(session);
61
- const runtime = loadRuntimeContext(session.project.workspace_id, session.project.product_tag);
62
- printJson({ database_context: runtime?.database_context ?? ctx }, json);
63
- }
@@ -1,74 +0,0 @@
1
- import fs from 'node:fs';
2
- import { requireSession } from '../lib/proxy/context.js';
3
- import { generateExecutablePayload } from '../lib/integrations.js';
4
- import {
5
- buildSnippet,
6
- ensureSupportedSnippetOperation,
7
- } from '../lib/snippets.js';
8
- import { printJson } from '../lib/output.js';
9
-
10
- export async function runGeneratePayload(opts: {
11
- family: string;
12
- method: string;
13
- file?: string;
14
- json?: boolean;
15
- }): Promise<void> {
16
- const session = requireSession();
17
- let targets: Record<string, unknown> | undefined;
18
- if (opts.file) {
19
- targets = JSON.parse(fs.readFileSync(opts.file, 'utf8')) as Record<string, unknown>;
20
- }
21
-
22
- const result = await generateExecutablePayload(session.apiUrl, session.credentials, {
23
- workspace_id: session.project.workspace_id,
24
- user_id: session.credentials.user_id,
25
- public_key: session.credentials.public_key,
26
- product_tag: session.project.product_tag,
27
- env_slug: session.project.env_slug,
28
- operation_family: opts.family,
29
- method: opts.method,
30
- targets,
31
- include_session: true,
32
- include_cache: true,
33
- schema_mode: 'best_effort',
34
- });
35
-
36
- printJson(result, Boolean(opts.json));
37
- }
38
-
39
- export async function runGenerateSnippet(opts: {
40
- family: string;
41
- method: string;
42
- language: 'typescript' | 'python' | 'go' | 'dotnet';
43
- file?: string;
44
- json?: boolean;
45
- out?: string;
46
- }): Promise<void> {
47
- ensureSupportedSnippetOperation(opts.family, opts.method);
48
- const session = requireSession();
49
- let targets: Record<string, unknown> | undefined;
50
- if (opts.file) {
51
- targets = JSON.parse(fs.readFileSync(opts.file, 'utf8')) as Record<string, unknown>;
52
- }
53
-
54
- const generated = await generateExecutablePayload(session.apiUrl, session.credentials, {
55
- workspace_id: session.project.workspace_id,
56
- user_id: session.credentials.user_id,
57
- public_key: session.credentials.public_key,
58
- product_tag: session.project.product_tag,
59
- env_slug: session.project.env_slug,
60
- operation_family: opts.family,
61
- method: opts.method,
62
- targets,
63
- });
64
-
65
- const payload = generated.payload ?? {};
66
- const snippet = buildSnippet(opts.language, payload, opts.family, opts.method);
67
- const output = { payload: generated, snippet };
68
-
69
- if (opts.out) {
70
- fs.writeFileSync(opts.out, snippet);
71
- console.log(`Wrote snippet to ${opts.out}`);
72
- }
73
- printJson(output, Boolean(opts.json));
74
- }
@@ -1,51 +0,0 @@
1
- import { readJsonBody } from '../lib/read-body.js';
2
- import { setGraphContext } from '../lib/context-store.js';
3
- import { GRAPH_PROXY_METHODS } from '../lib/db-methods.js';
4
- import { getGraphProxy, requireSession } from '../lib/proxy/context.js';
5
- import { printJson } from '../lib/output.js';
6
-
7
- export async function runGraph(
8
- method: string,
9
- opts: { file?: string; json?: boolean },
10
- ): Promise<void> {
11
- const session = requireSession();
12
- const proxy = getGraphProxy(session);
13
- const body = readJsonBody(opts.file) as Record<string, unknown>;
14
-
15
- const m = method.includes('.') ? method : method.toLowerCase();
16
- if (!GRAPH_PROXY_METHODS.includes(m as (typeof GRAPH_PROXY_METHODS)[number]) && !m.includes('.')) {
17
- console.warn(`Method "${m}" not in known list; sending anyway. Known: ${GRAPH_PROXY_METHODS.join(', ')}`);
18
- }
19
-
20
- const params: unknown[] = [];
21
- if (m === 'connect') {
22
- const cfg = {
23
- product: session.project.product_tag,
24
- env: session.project.env_slug,
25
- graph: body.graph ?? body.tag,
26
- ...body,
27
- };
28
- params.push(cfg);
29
- } else if (Object.keys(body).length > 0) {
30
- params.push({
31
- product: session.project.product_tag,
32
- env: session.project.env_slug,
33
- ...body,
34
- });
35
- } else if (m === 'fetchAll' || m === 'list') {
36
- params.push(session.project.product_tag);
37
- }
38
-
39
- const result = await proxy.execute(m === 'list' ? 'fetchAll' : m, params);
40
-
41
- if (m === 'connect' && params[0] && typeof params[0] === 'object') {
42
- const cfg = params[0] as { graph: string; env: string; product: string };
43
- setGraphContext(session.project.workspace_id, session.project.product_tag, session.project.env_slug, {
44
- graph: String(cfg.graph),
45
- env: String(cfg.env),
46
- product: String(cfg.product),
47
- });
48
- }
49
-
50
- printJson(result, Boolean(opts.json));
51
- }
@@ -1,39 +0,0 @@
1
- import { saveProjectConfig, type ProjectConfig } from '../lib/config.js';
2
- import { detectLanguage, writeInitArtifacts } from '../lib/templates.js';
3
- import { success } from '../lib/output.js';
4
- import { runLink } from './link.js';
5
-
6
- export async function runInit(opts: {
7
- link?: boolean;
8
- workspace?: string;
9
- product?: string;
10
- env?: string;
11
- language?: string;
12
- }): Promise<void> {
13
- const dir = process.cwd();
14
- const lang =
15
- opts.language && ['typescript', 'python', 'go', 'dotnet'].includes(opts.language)
16
- ? (opts.language as 'typescript' | 'python' | 'go' | 'dotnet')
17
- : detectLanguage(dir);
18
-
19
- writeInitArtifacts(dir, lang);
20
-
21
- if (!opts.link) {
22
- const stub: ProjectConfig = {
23
- workspace_tag: '',
24
- product_tag: '',
25
- env_slug: opts.env ?? 'dev',
26
- };
27
- saveProjectConfig(dir, stub);
28
- success(`Initialized Ductape (${lang}). Run \`ductape link\` to connect workspace/product.`);
29
- return;
30
- }
31
-
32
- await runLink({
33
- workspace: opts.workspace,
34
- product: opts.product,
35
- env: opts.env,
36
- dir,
37
- });
38
- success(`Initialized and linked Ductape project (${lang})`);
39
- }