@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,223 +0,0 @@
1
- # `ductape db migrate` — Research & Implementation Plan
2
-
3
- ## What already exists
4
-
5
- ### 1. `ductape init` creates `.ductape/`
6
- [`src/commands/init.ts`](src/commands/init.ts) calls `writeInitArtifacts()` and `saveProjectConfig()`.
7
- [`src/lib/config.ts`](src/lib/config.ts) line 13 defines:
8
- ```ts
9
- export const PROJECT_CONFIG_DIR = '.ductape';
10
- export const PROJECT_CONFIG_PATH = path.join(PROJECT_CONFIG_DIR, 'config.json');
11
- ```
12
- This is the only change needed in `config.ts` to rename to `ductape/`.
13
-
14
- ### 2. `ductape db <verb>` — proxy passthrough
15
- [`src/commands/db.ts`](src/commands/db.ts) is a generic proxy that already understands `migration.list` and `migration.run` as method strings (defined in [`src/lib/db-methods.ts`](src/lib/db-methods.ts)). These work today but require a JSON body file (`-f`) and are not user-friendly for the workflow we're building.
16
-
17
- ### 3. SDK `MigrationEngine` — fully built
18
- [`sdk/ts/src/database/migrations/migration-engine.ts`](../sdk/ts/src/database/migrations/migration-engine.ts):
19
- - Tracks applied migrations in a `_ductape_migrations` table/collection created automatically on first run.
20
- - `migrate(migrations[])` — runs all pending (not yet in `_ductape_migrations`) in dependency order.
21
- - `rollback(count)` / `rollbackAll()` / `reset()`.
22
- - `getPendingMigrations(migrations[])` — diffs supplied list against `_ductape_migrations`.
23
- - `getAppliedMigrations()` — returns history with tag, name, checksum, appliedAt.
24
- - `getStatus(migrations[])` — returns `{ total, completed, pending, pendingMigrations[], appliedMigrations[] }`.
25
- - Works across PostgreSQL, MySQL, MariaDB, MongoDB, DynamoDB, Cassandra.
26
-
27
- ### 4. SDK `SchemaManager` + `MigrationBuilder`
28
- [`sdk/ts/src/database/schema/schema-manager.ts`](../sdk/ts/src/database/schema/schema-manager.ts):
29
- - `createCollection(name, fields[], options?)` — generates an `IMigration` (up: createCollection, down: dropCollection).
30
- - `addField()`, `dropField()`, `renameField()`, `modifyField()` — each generates a reversible `IMigration`.
31
- - `createIndex()`, `dropIndex()`, `addConstraint()`, `dropConstraint()`.
32
- - `MigrationBuilder` — fluent API for batching multiple operations into one migration object.
33
- - All operations produce `IMigration` objects with `up[]` and `down[]` — the same shape the engine consumes.
34
-
35
- ### 5. `IFieldDefinition` type system
36
- Supported field types: `string`, `text`, `integer`, `bigint`, `smallint`, `decimal`, `float`, `double`, `uuid`, `boolean`, `date`, `time`, `datetime`, `timestamp`, `binary`, `blob`, `json`, `object`, `array`, `enum`.
37
-
38
- Field options: `nullable`, `unique`, `primaryKey`, `autoGenerate` (auto-increment/SERIAL), `defaultValue`, `maxLength`, `precision`, `scale`, `enumValues`, `arrayElementType`.
39
-
40
- ---
41
-
42
- ## What does not exist yet
43
-
44
- ### Gap 1 — `ductape/` folder structure (not `.ductape/`)
45
- Currently `PROJECT_CONFIG_DIR = '.ductape'`. Needs to become `'ductape'`.
46
-
47
- `ductape init` should also scaffold:
48
- ```
49
- ductape/
50
- config.json ← existing (project linking)
51
- database/
52
- schema.json ← NEW: declarative schema definition
53
- migrations/ ← NEW: generated migration files (one JSON per migration)
54
- ```
55
-
56
- ### Gap 2 — `ductape/database/schema.json` format
57
- No parser, type-mapper, or loader exists. This needs to be written.
58
-
59
- User-specified format:
60
- ```json
61
- [
62
- {
63
- "db": "dbTag",
64
- "tables": {
65
- "users": {
66
- "name": { "type": "String", "required": true },
67
- "email": { "type": "String", "unique": true },
68
- "age": { "type": "Number" }
69
- },
70
- "orders": {
71
- "userId": { "type": "String", "required": true },
72
- "total": { "type": "Number" },
73
- "createdAt": { "type": "Date", "default": "now" }
74
- }
75
- }
76
- }
77
- ]
78
- ```
79
-
80
- The CLI must translate this mongoose-style syntax to `IFieldDefinition[]` before it can call `SchemaManager` or `MigrationEngine`. Mapping table needed (see below).
81
-
82
- ### Gap 3 — `ductape db migrate` command
83
- No high-level migrate command exists. The existing `ductape db migration.run` requires a structured JSON body and is a raw proxy call, not schema-aware.
84
-
85
- ### Gap 4 — Schema diff logic
86
- The CLI needs to determine which tables and fields in `schema.json` are new (not yet migrated). Two available signals:
87
- - `migration.list` via proxy — returns `_ductape_migrations` history (knows what ran).
88
- - `schema.describe` / `schema.list` via proxy — introspects current DB structure directly.
89
-
90
- The safer approach is to use the migration history (what the CLI itself applied) as the source of truth, and use `schema.describe` only as a secondary check. This keeps the system append-only and prevents false positives from manual DB changes.
91
-
92
- ### Gap 5 — Generated migration files
93
- When the CLI generates a migration from a schema diff, it should write a timestamped JSON file to `ductape/database/migrations/`:
94
- ```
95
- ductape/database/migrations/
96
- 20250703_120000_create_users.json
97
- 20250703_120001_create_orders.json
98
- ```
99
- These files are the local record of what was generated and are committed to version control. On `ductape db migrate`, the CLI reads all files from this folder, diffs against applied migrations, and runs the pending ones.
100
-
101
- ### Gap 6 — Remote schema sync (deferred — see below)
102
- No mechanism exists to push/pull `schema.json` to/from a remote location. This is the biggest open architectural question.
103
-
104
- ---
105
-
106
- ## Mongoose-to-Ductape type mapping
107
-
108
- | Mongoose style | `FieldType` |
109
- |---|---|
110
- | `String` | `string` |
111
- | `Number` | `integer` (default) or `decimal` if `float: true` |
112
- | `Boolean` | `boolean` |
113
- | `Date` | `datetime` |
114
- | `Buffer` | `binary` |
115
- | `Mixed` / `Object` | `object` |
116
- | `Array` | `array` |
117
- | `ObjectId` / `UUID` | `uuid` |
118
- | `Map` | `object` |
119
- | `Decimal128` | `decimal` |
120
-
121
- Mongoose field options → `IFieldDefinition`:
122
- | Mongoose | `IFieldDefinition` |
123
- |---|---|
124
- | `required: true` | `nullable: false` |
125
- | `unique: true` | `unique: true` |
126
- | `default: value` | `defaultValue: value` |
127
- | `default: 'now'` | `defaultValue: 'CURRENT_TIMESTAMP'` |
128
- | `maxlength: N` | `maxLength: N` |
129
- | `enum: [...]` | `type: 'enum', enumValues: [...]` |
130
- | `index: true` | generates a separate `createIndex` operation |
131
- | `primaryKey: true` | `primaryKey: true` |
132
- | `autoGenerate: true` | `autoGenerate: true` |
133
-
134
- ---
135
-
136
- ## Proposed command surface
137
-
138
- ```
139
- ductape db migrate Run all pending migrations for all dbs in schema.json
140
- ductape db migrate --env <tag> Run against a specific environment
141
- ductape db migrate --dry-run Print what would run without applying
142
- ductape db migrate --db <tag> Limit to one db entry from schema.json
143
-
144
- ductape db migrate status Show applied vs pending migrations
145
- ductape db migrate rollback Roll back the last migration
146
- ductape db migrate rollback -n 3 Roll back the last 3
147
-
148
- ductape db schema generate Diff schema.json vs applied migrations and write new migration files to migrations/ WITHOUT running them
149
- ductape db schema push Push local schema.json to remote (Ductape platform storage) — deferred
150
- ductape db schema pull Fetch remote schema.json to local — deferred
151
- ```
152
-
153
- The core flow for `ductape db migrate`:
154
- 1. Load `ductape/database/schema.json`.
155
- 2. For each `{ db, tables }` entry:
156
- a. Resolve the db tag to a database_context via `db connect`.
157
- b. Load all migration files from `ductape/database/migrations/` that match this db.
158
- c. Call `migration.list` via proxy to get already-applied migration tags.
159
- d. Filter to pending migrations (files whose tag is not in applied list).
160
- e. Call `migration.run` via proxy for each pending migration in order.
161
- 3. Report results.
162
-
163
- The `ductape db schema generate` flow:
164
- 1. Load `schema.json`.
165
- 2. Load existing migration files from `migrations/`.
166
- 3. Reconstruct the "current intended schema" from applied migrations (replay up-ops in memory).
167
- 4. Diff declared schema (from `schema.json`) vs current intended schema.
168
- 5. For new tables → generate `createCollection` migration.
169
- 6. For new fields on existing tables → generate `addField` migration.
170
- 7. For removed fields/tables → refuse by default; require `--destructive` flag.
171
- 8. Write each generated migration as a JSON file in `migrations/`.
172
-
173
- ---
174
-
175
- ## Things to watch out for
176
-
177
- ### Non-destructive by default
178
- `ductape db schema generate` must never emit `dropCollection` or `dropField` operations unless `--destructive` is explicitly passed. Removing a field from `schema.json` silently generating a drop migration could wipe production data.
179
-
180
- ### Schema.json vs migration files as source of truth
181
- `schema.json` expresses intent. Migration files express history. The CLI generates migration files from `schema.json` changes. Once a migration file exists, it is the immutable record — `schema.json` can diverge (e.g., someone deletes a table definition) but the migration file should not be regenerated or deleted automatically.
182
-
183
- ### db tag resolution
184
- Each `{ db: 'dbTag' }` in schema.json needs to be connected before migrations can run. The existing `ductape db connect` / `database_context` mechanism handles this. The `migrate` command should do a `connect` call per db entry before running its migrations.
185
-
186
- ### Migration ordering across tables
187
- When generating migrations for multiple new tables in one run, ordering matters if tables have foreign keys. The user can declare `dependencies` on a migration to enforce order. For the initial scaffold, generate one migration file per table in the order they appear in `schema.json`. A future improvement could auto-detect FK dependencies.
188
-
189
- ### Idempotency
190
- `migration.run` via the engine already checks `_ductape_migrations` before applying. Re-running `ductape db migrate` is safe — it skips already-applied migrations.
191
-
192
- ### Checksum integrity
193
- The `MigrationEngine` records a SHA-256 checksum of the migration content at apply time. If a migration file is edited after being applied, the engine will detect a mismatch. The CLI should warn about this rather than silently failing.
194
-
195
- ---
196
-
197
- ## The multi-codebase schema sync problem
198
-
199
- This is the hardest part of the system and is explicitly deferred.
200
-
201
- **The problem:** Team member A adds `products` table to `schema.json` and runs `ductape db schema generate`, producing `20250703_create_products.json`. Team member B pulls code and gets the migration file. But if B's `schema.json` is out of date, running `ductape db schema generate` again might produce a duplicate or conflicting migration.
202
-
203
- **Proposed approach (to validate before implementing):**
204
- - Migration files (`ductape/database/migrations/*.json`) are committed to version control. This is the primary sync mechanism — same as Prisma, Flyway, Liquibase.
205
- - `schema.json` is the declarative desired state. It should also be committed, but is the secondary source. Migration files take precedence.
206
- - `ductape db schema push` uploads `schema.json` to a `_ductape_schema` collection (or Ductape platform storage) so other machines can fetch it.
207
- - `ductape db schema pull` fetches the remote schema.json and merges it — or simply overwrites the local one (simpler, with a warning).
208
- - The authoritative migration history is always `_ductape_migrations` in the actual database. The CLI's job is just to generate and apply files; the DB is always the final word on what ran.
209
-
210
- **Key constraint to solve before implementing sync:** If two developers generate migrations from different `schema.json` states at the same time (e.g., both add different tables locally), their migration files will have different tags and could conflict when both are applied to the same DB. This is the same problem Git branch migrations have in Prisma. The recommended solution is to treat migration file generation as a gated step that only runs on `main` / via CI, not locally in parallel. This is a workflow decision that should be locked in before the sync commands are built.
211
-
212
- ---
213
-
214
- ## Implementation order
215
-
216
- 1. Rename `PROJECT_CONFIG_DIR` from `.ductape` to `ductape` in `config.ts`.
217
- 2. Update `writeInitArtifacts()` in `templates.ts` to scaffold `database/schema.json` and `database/migrations/`.
218
- 3. Write `src/lib/schema-loader.ts` — loads and validates `schema.json`, maps mongoose types to `IFieldDefinition`.
219
- 4. Write `src/lib/migration-files.ts` — reads/writes migration JSON files from `ductape/database/migrations/`.
220
- 5. Add `ductape db schema generate` subcommand.
221
- 6. Add `ductape db migrate` subcommand (run pending migration files via proxy).
222
- 7. Add `ductape db migrate status` and `ductape db migrate rollback`.
223
- 8. Deferred: `ductape db schema push` / `ductape db schema pull`.
@@ -1,18 +0,0 @@
1
- /**
2
- * Ductape SDK example — product/env defaults are set on the constructor only.
3
- * CLI: `ductape login` + `ductape link` (see .ductape/config.json).
4
- */
5
- import Ductape from '@ductape/sdk';
6
-
7
- const ductape = new Ductape({
8
- accessKey: process.env.DUCTAPE_ACCESS_KEY!,
9
- product: process.env.DUCTAPE_PRODUCT!,
10
- env: process.env.DUCTAPE_ENV ?? 'dev',
11
- });
12
-
13
- async function main() {
14
- // await ductape.databases.connect({ database: 'main-db' });
15
- console.log('Ductape client ready');
16
- }
17
-
18
- main().catch(console.error);
@@ -1,153 +0,0 @@
1
- import { findProjectConfig } from '../lib/config.js';
2
- import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
- import { fail } from '../lib/output.js';
4
- import { buildCrudParams } from '../lib/resources.js';
5
- import {
6
- loadSessions,
7
- loadNotifications,
8
- loadEvents,
9
- type ApplyItem,
10
- } from '../lib/apply-loaders.js';
11
- import type { SDKModule } from '../lib/proxy/sdk-proxy.js';
12
-
13
- export type ApplyType = 'sessions' | 'notifications' | 'events';
14
-
15
- interface ApplyOpts {
16
- dryRun?: boolean;
17
- }
18
-
19
- const MODULE_FOR: Record<ApplyType, SDKModule> = {
20
- sessions: 'sessions',
21
- notifications: 'notifications',
22
- events: 'messageBrokers',
23
- };
24
-
25
- async function syncItems(
26
- type: ApplyType,
27
- items: ApplyItem[],
28
- productTag: string,
29
- proxy: ReturnType<typeof getSdkProxy>,
30
- dryRun: boolean,
31
- ): Promise<void> {
32
- const module = MODULE_FOR[type];
33
-
34
- const listResult = await proxy.execute<unknown>(module, 'list', buildCrudParams(module, 'list', productTag, {}));
35
- const existingList = Array.isArray(listResult) ? listResult : [];
36
- const existingTags = new Set(
37
- existingList.map((r) => (r as { tag?: string }).tag ?? '').filter(Boolean),
38
- );
39
- const existingNotificationMessages = new Set<string>();
40
- if (type === 'notifications') {
41
- for (const notification of existingList) {
42
- const record = notification as { tag?: string; messages?: Array<{ tag?: string }> };
43
- for (const message of record.messages ?? []) {
44
- if (record.tag && message.tag) {
45
- existingNotificationMessages.add(
46
- message.tag.includes(':') ? message.tag : `${record.tag}:${message.tag}`,
47
- );
48
- }
49
- }
50
- }
51
- }
52
-
53
- for (const item of items) {
54
- if (!item.tag) {
55
- console.warn(` [${type}] skipped — missing "tag" field`);
56
- continue;
57
- }
58
-
59
- const verb: 'create' | 'update' = existingTags.has(item.tag) ? 'update' : 'create';
60
- const nestedMessages =
61
- type === 'notifications' && Array.isArray(item.messages)
62
- ? item.messages as Array<Record<string, unknown>>
63
- : [];
64
- const componentBody =
65
- type === 'notifications'
66
- ? Object.fromEntries(Object.entries(item).filter(([key]) => key !== 'messages'))
67
- : item;
68
-
69
- if (dryRun) {
70
- console.log(` [${type}] would ${verb}: ${item.tag}`);
71
- for (const message of nestedMessages) {
72
- const rawTag = String(message.tag ?? '');
73
- const fullTag = rawTag.includes(':') ? rawTag : `${item.tag}:${rawTag}`;
74
- const messageVerb = existingNotificationMessages.has(fullTag) ? 'update' : 'create';
75
- console.log(` [notifications] would ${messageVerb} message: ${fullTag}`);
76
- }
77
- continue;
78
- }
79
-
80
- try {
81
- const params = buildCrudParams(module, verb, productTag, {
82
- tag: item.tag,
83
- body: componentBody,
84
- });
85
- await proxy.execute(module, verb, params);
86
- console.log(` [${type}] ${verb}d: ${item.tag}`);
87
-
88
- for (const message of nestedMessages) {
89
- const rawTag = String(message.tag ?? '');
90
- if (!rawTag) throw new Error(`notification "${item.tag}" contains a message without "tag"`);
91
- const fullTag = rawTag.includes(':') ? rawTag : `${item.tag}:${rawTag}`;
92
- if (!fullTag.startsWith(`${item.tag}:`)) {
93
- throw new Error(
94
- `message tag "${fullTag}" must belong to notification "${item.tag}"`,
95
- );
96
- }
97
- const messageBody = { ...message, tag: fullTag };
98
- const messageVerb = existingNotificationMessages.has(fullTag) ? 'update' : 'create';
99
- const messageParams =
100
- messageVerb === 'create'
101
- ? [productTag, messageBody]
102
- : [productTag, fullTag, messageBody];
103
- await proxy.execute('notifications', `messages.${messageVerb}`, messageParams);
104
- console.log(` [notifications] ${messageVerb}d message: ${fullTag}`);
105
- }
106
- } catch (err) {
107
- console.error(
108
- ` [${type}] ${verb} failed for "${item.tag}": ${err instanceof Error ? err.message : String(err)}`,
109
- );
110
- }
111
- }
112
- }
113
-
114
- export async function runApply(type: ApplyType | undefined, opts: ApplyOpts): Promise<void> {
115
- const found = findProjectConfig();
116
- if (!found) fail('No linked project. Run `ductape link` from your project directory.');
117
-
118
- const { dir } = found;
119
- const session = requireSession();
120
- const proxy = getSdkProxy(session);
121
- const productTag = session.project.product_tag;
122
- const dryRun = Boolean(opts.dryRun);
123
-
124
- const targets: ApplyType[] = type ? [type] : ['sessions', 'notifications', 'events'];
125
-
126
- const loaders: Record<ApplyType, () => ApplyItem[] | null> = {
127
- sessions: () => loadSessions(dir),
128
- notifications: () => loadNotifications(dir),
129
- events: () => loadEvents(dir),
130
- };
131
-
132
- for (const t of targets) {
133
- let items: ApplyItem[] | null;
134
- try {
135
- items = loaders[t]();
136
- } catch (err) {
137
- fail(`[${t}] parse error: ${err instanceof Error ? err.message : String(err)}`);
138
- }
139
-
140
- if (items === null) {
141
- console.log(`[${t}] no ductape/${t}.json found — skipping`);
142
- continue;
143
- }
144
-
145
- if (items.length === 0) {
146
- console.log(`[${t}] empty — nothing to apply`);
147
- continue;
148
- }
149
-
150
- console.log(`[${t}] applying ${items.length} item(s)${dryRun ? ' (dry run)' : ''}...`);
151
- await syncItems(t, items, productTag, proxy, dryRun);
152
- }
153
- }
@@ -1,43 +0,0 @@
1
- import path from 'node:path';
2
- import { getApp } from '../lib/platform-api.js';
3
- import { importAppFromFile, parseImportFormat } from '../lib/app-import.js';
4
- import { printJson, success } from '../lib/output.js';
5
- import { requireWorkspaceContext } from '../lib/workspace-context.js';
6
-
7
- export async function runAppsImport(
8
- file: string,
9
- opts: {
10
- profile?: string;
11
- type: string;
12
- appId?: string;
13
- appTag?: string;
14
- update?: boolean;
15
- version?: string;
16
- json?: boolean;
17
- },
18
- ): Promise<void> {
19
- const ctx = requireWorkspaceContext({ profile: opts.profile });
20
- const format = parseImportFormat(opts.type);
21
-
22
- let appId = opts.appId;
23
- if (!appId && opts.appTag) {
24
- const app = (await getApp(ctx, { tag: opts.appTag })) as { _id?: string };
25
- appId = app._id;
26
- if (!appId) throw new Error(`App not found for tag: ${opts.appTag}`);
27
- }
28
-
29
- const result = await importAppFromFile(ctx, {
30
- filePath: path.resolve(file),
31
- format,
32
- appId,
33
- update: Boolean(opts.update),
34
- version: opts.version,
35
- });
36
-
37
- if (opts.json) {
38
- printJson(result, true);
39
- return;
40
- }
41
- const label = format === 'postman' ? 'Postman v2.1' : 'OpenAPI 3.0';
42
- success(`Imported ${label} collection from ${path.basename(file)}`);
43
- }
@@ -1,92 +0,0 @@
1
- import { schemaKeyForEntity } from '../lib/forms/index.js';
2
- import { isInteractive, promptTag } from '../lib/forms/prompt.js';
3
- import { toInteractiveOpts, type CommandInteractiveFlags } from '../lib/interactive-opts.js';
4
- import { bodyRequiredHint, resolveBody } from '../lib/read-body.js';
5
- import {
6
- createApp,
7
- deleteApp,
8
- getApp,
9
- listApps,
10
- updateApp,
11
- updateAppViaProxy,
12
- } from '../lib/platform-api.js';
13
- import { printJson } from '../lib/output.js';
14
- import { requireWorkspaceContext } from '../lib/workspace-context.js';
15
-
16
- const VERBS = ['list', 'get', 'create', 'update', 'delete'] as const;
17
- type Verb = (typeof VERBS)[number];
18
-
19
- export async function runApps(
20
- verb: string,
21
- opts: {
22
- profile?: string;
23
- status?: string;
24
- id?: string;
25
- tag?: string;
26
- file?: string;
27
- json?: boolean;
28
- proxy?: boolean;
29
- } & CommandInteractiveFlags,
30
- extraArgs: string[],
31
- ): Promise<void> {
32
- const v = verb.toLowerCase() as Verb;
33
- if (!VERBS.includes(v)) {
34
- throw new Error(`Unknown apps verb "${verb}". Use: ${VERBS.join(', ')}`);
35
- }
36
-
37
- const ctx = requireWorkspaceContext({ profile: opts.profile });
38
- const status = opts.status ?? 'all';
39
- const interactive = toInteractiveOpts(opts);
40
- const schemaKey = schemaKeyForEntity('apps', v);
41
- const body =
42
- v === 'create' || v === 'update'
43
- ? await resolveBody({
44
- ...interactive,
45
- file: opts.file,
46
- schemaKey,
47
- patch: v === 'update',
48
- requireBody: v === 'create',
49
- })
50
- : undefined;
51
- const id = opts.id ?? extraArgs[0];
52
- let tag = opts.tag ?? (extraArgs[0] && !opts.id ? extraArgs[0] : undefined);
53
- if (v === 'update' && (opts.proxy || !id) && !tag && isInteractive(interactive)) {
54
- tag = await promptTag('App tag');
55
- }
56
-
57
- let result: unknown;
58
-
59
- switch (v) {
60
- case 'list':
61
- result = await listApps(ctx, status);
62
- break;
63
- case 'get':
64
- result = await getApp(ctx, { id, tag });
65
- break;
66
- case 'create':
67
- if (!body || Object.keys(body).length === 0) {
68
- throw new Error(`create requires a body. ${bodyRequiredHint('apps:create')}`);
69
- }
70
- result = await createApp(ctx, body);
71
- break;
72
- case 'update': {
73
- if (opts.proxy || tag) {
74
- const appTag = tag ?? (body?.tag as string | undefined);
75
- if (!appTag) throw new Error('update via proxy requires --tag <app_tag>');
76
- result = await updateAppViaProxy(ctx, appTag, body ?? {});
77
- break;
78
- }
79
- if (!id) throw new Error('update requires --id <app_id> or --tag with --proxy');
80
- result = await updateApp(ctx, id, body ?? {});
81
- break;
82
- }
83
- case 'delete':
84
- if (!id) throw new Error('delete requires --id <app_id>');
85
- result = await deleteApp(ctx, id);
86
- break;
87
- default:
88
- throw new Error(`Unsupported: apps ${v}`);
89
- }
90
-
91
- printJson(result, Boolean(opts.json));
92
- }
@@ -1,68 +0,0 @@
1
- import { readJsonBody } from '../lib/read-body.js';
2
- import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
- import { printJson } from '../lib/output.js';
4
- import { listCloudTiers } from '../lib/platform-api.js';
5
- import { requireWorkspaceContext } from '../lib/workspace-context.js';
6
-
7
- type CloudTarget = 'connections' | 'resources' | 'tiers';
8
-
9
- export async function runCloud(
10
- target: CloudTarget,
11
- verb: string,
12
- opts: { id?: string; file?: string; provider?: string; type?: string; dbType?: string; tier?: string; json?: boolean },
13
- extraArgs: string[],
14
- ): Promise<void> {
15
- if (target === 'tiers') {
16
- const ctx = requireWorkspaceContext();
17
- const result = await listCloudTiers(ctx, {
18
- provider: opts.provider,
19
- resource_type: opts.type,
20
- db_type: opts.dbType,
21
- });
22
- printJson(result, Boolean(opts.json));
23
- return;
24
- }
25
-
26
- requireSession();
27
- const proxy = getSdkProxy();
28
- let body = readJsonBody(opts.file);
29
- if (opts.tier) {
30
- body = { ...(body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}), tier: opts.tier };
31
- }
32
- const id = opts.id ?? extraArgs[0];
33
-
34
- let method: string;
35
- let params: unknown[];
36
-
37
- if (target === 'connections') {
38
- const map: Record<string, [string, unknown[]]> = {
39
- create: ['connections.create', [body]],
40
- list: ['connections.list', []],
41
- get: ['connections.fetch', [id]],
42
- fetch: ['connections.fetch', [id]],
43
- delete: ['connections.delete', [id]],
44
- update: ['connections.update', [id, body]],
45
- complete: ['connections.complete', [id, body]],
46
- validate: ['connections.validate', [id]],
47
- };
48
- const entry = map[verb.toLowerCase()];
49
- if (!entry) throw new Error(`Unknown cloud connections verb: ${verb}`);
50
- [method, params] = entry;
51
- } else {
52
- const map: Record<string, [string, unknown[]]> = {
53
- list: ['resources.list', [body]],
54
- import: ['resources.import', [body]],
55
- provision: ['resources.provision', [body]],
56
- 'import-persist': ['resources.importAndPersist', [body]],
57
- 'import-persist-all': ['resources.importAndPersistAll', [body]],
58
- 'provision-persist': ['resources.provisionAndPersist', [body]],
59
- 'provision-persist-all': ['resources.provisionAndPersistAll', [body]],
60
- };
61
- const entry = map[verb.toLowerCase()];
62
- if (!entry) throw new Error(`Unknown cloud resources verb: ${verb}`);
63
- [method, params] = entry;
64
- }
65
-
66
- const result = await proxy.execute('cloud', method, params);
67
- printJson(result, Boolean(opts.json));
68
- }