@dboio/cli 0.6.6 → 0.6.8
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/README.md +93 -2
- package/package.json +1 -1
- package/plugins/claude/dbo/.claude-plugin/plugin.json +1 -1
- package/src/commands/add.js +33 -4
- package/src/commands/clone.js +443 -22
- package/src/commands/init.js +1 -1
- package/src/commands/input.js +32 -8
- package/src/commands/login.js +1 -1
- package/src/commands/push.js +55 -9
- package/src/lib/config.js +31 -0
- package/src/lib/delta.js +5 -9
- package/src/lib/input-parser.js +180 -9
- package/src/lib/ticketing.js +189 -0
package/README.md
CHANGED
|
@@ -128,11 +128,12 @@ All configuration is **directory-scoped**. Each project folder maintains its own
|
|
|
128
128
|
|------|---------|-----|
|
|
129
129
|
| `config.json` | Domain, app metadata, placement preferences | Committable (shared) |
|
|
130
130
|
| `config.local.json` | Per-user settings: plugin scopes, future user prefs | Gitignored (per-user) |
|
|
131
|
+
| `ticketing.local.json` | Stored ticket IDs for submission error recovery | Gitignored (per-user) |
|
|
131
132
|
| `credentials.json` | Username, user ID, UID, name, email (no password) | Gitignored (per-user) |
|
|
132
133
|
| `cookies.txt` | Session cookie (Netscape format) | Gitignored (per-user) |
|
|
133
134
|
| `structure.json` | Bin directory mapping (created by `dbo clone`) | Committable (shared) |
|
|
134
135
|
|
|
135
|
-
`dbo init` automatically adds `.dbo/credentials.json`, `.dbo/cookies.txt`, and `.dbo/
|
|
136
|
+
`dbo init` automatically adds `.dbo/credentials.json`, `.dbo/cookies.txt`, `.dbo/config.local.json`, and `.dbo/ticketing.local.json` to `.gitignore` (creates the file if it doesn't exist).
|
|
136
137
|
|
|
137
138
|
#### config.json reference
|
|
138
139
|
|
|
@@ -252,6 +253,34 @@ dbo init --domain my-domain.com --app myapp --clone
|
|
|
252
253
|
|
|
253
254
|
When cloning an app that was already cloned locally, the CLI detects existing files and compares modification times against the server's `_LastUpdated`. You'll be prompted to overwrite, compare differences, or skip — same as `dbo pull`. Use `-y` to auto-accept all changes.
|
|
254
255
|
|
|
256
|
+
#### Collision detection
|
|
257
|
+
|
|
258
|
+
When multiple records would create files at the same path (e.g., a `content` record and a `media` record both named `colors.css`), the CLI detects the collision before writing any files and prompts you to choose which record to keep:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
⚠ Collision: 2 records want to create "Bins/app/colors.css"
|
|
262
|
+
? Which record should create this file?
|
|
263
|
+
❯ [content] colors (UID: abc123)
|
|
264
|
+
[media] colors.css (UID: def456)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The rejected record is automatically staged for deletion in `.dbo/synchronize.json`. Run `dbo push` to delete it from the server.
|
|
268
|
+
|
|
269
|
+
In non-interactive mode (`-y`), the first record is kept and others are auto-staged for deletion.
|
|
270
|
+
|
|
271
|
+
#### Stale media cleanup
|
|
272
|
+
|
|
273
|
+
During media downloads, files returning 404 (no longer exist on the server) are collected as "stale records". After all downloads complete, you'll be prompted to stage them for deletion:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
Found 3 stale media record(s) (404 - files no longer exist on server)
|
|
277
|
+
? Stage these 3 stale media records for deletion? (y/N)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This helps keep your app clean by removing database records for media files that have been deleted from the server.
|
|
281
|
+
|
|
282
|
+
In non-interactive mode (`-y`), stale cleanup is skipped (conservative default).
|
|
283
|
+
|
|
255
284
|
#### Path resolution
|
|
256
285
|
|
|
257
286
|
When a content record has both `Path` and `BinID`, the CLI prompts:
|
|
@@ -1090,7 +1119,35 @@ The `add` and `push` commands never submit these server-managed columns:
|
|
|
1090
1119
|
|
|
1091
1120
|
The `input`, `push`, and `add` commands automatically detect recoverable server errors and prompt for missing values instead of failing immediately.
|
|
1092
1121
|
|
|
1093
|
-
#### Ticket
|
|
1122
|
+
#### Ticket error recovery
|
|
1123
|
+
|
|
1124
|
+
When the server returns a `ticket_error` (record update requires a Ticket ID), the CLI prompts with interactive recovery options:
|
|
1125
|
+
|
|
1126
|
+
```
|
|
1127
|
+
⚠ This record update requires a Ticket ID.
|
|
1128
|
+
? Record update requires a Ticket ID:
|
|
1129
|
+
❯ Apply a Ticket ID to this record and resubmit
|
|
1130
|
+
Apply a Ticket ID to all updates in this transaction, and update my current Ticket ID reference
|
|
1131
|
+
Skip this record update
|
|
1132
|
+
Skip all updates that require a Ticket ID
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
When the server returns a `repo_mismatch` (Ticket ID belongs to a different repository), the CLI prompts:
|
|
1136
|
+
|
|
1137
|
+
```
|
|
1138
|
+
⚠ Ticket "TICKET-123" is for another repository.
|
|
1139
|
+
? The Ticket ID of "TICKET-123" is for another Repository:
|
|
1140
|
+
❯ Commit anyway
|
|
1141
|
+
Submit with another Ticket ID
|
|
1142
|
+
Skip this record
|
|
1143
|
+
Commit all transactions with this ID anyway
|
|
1144
|
+
Commit all transactions with another Ticket ID, and update my current Ticket ID reference
|
|
1145
|
+
Skip all
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
Ticket selections are stored in `.dbo/ticketing.local.json` for reuse across submissions. Per-record tickets are cleaned up after successful submission; the global ticket persists until explicitly cleared. The `--ticket` flag always takes precedence over stored tickets.
|
|
1149
|
+
|
|
1150
|
+
#### Ticket ID required (legacy)
|
|
1094
1151
|
|
|
1095
1152
|
When the server returns `ticket_lookup_required_error`, the CLI prompts:
|
|
1096
1153
|
|
|
@@ -1101,6 +1158,40 @@ When the server returns `ticket_lookup_required_error`, the CLI prompts:
|
|
|
1101
1158
|
|
|
1102
1159
|
The submission is then retried with `_OverrideTicketID`. To skip the prompt, pass `--ticket <id>` upfront.
|
|
1103
1160
|
|
|
1161
|
+
#### Pre-submission ticket prompt
|
|
1162
|
+
|
|
1163
|
+
When a stored ticket exists in `.dbo/ticketing.local.json`, the CLI prompts before batch submissions:
|
|
1164
|
+
|
|
1165
|
+
```
|
|
1166
|
+
? Use stored Ticket ID "TICKET-123" for this submission?
|
|
1167
|
+
❯ Yes, use "TICKET-123"
|
|
1168
|
+
No, clear stored ticket
|
|
1169
|
+
Cancel submission
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
#### `.dbo/ticketing.local.json`
|
|
1173
|
+
|
|
1174
|
+
Stores ticket IDs for automatic application during submissions:
|
|
1175
|
+
|
|
1176
|
+
```json
|
|
1177
|
+
{
|
|
1178
|
+
"ticket_id": "TICKET-123",
|
|
1179
|
+
"records": [
|
|
1180
|
+
{
|
|
1181
|
+
"UID": "a2dxvg23rk6xsmnum7pdxa",
|
|
1182
|
+
"RowID": 16012,
|
|
1183
|
+
"entity": "content",
|
|
1184
|
+
"ticket_id": "TICKET-456",
|
|
1185
|
+
"expression": "RowID:16012;column:content._LastUpdatedTicketID=TICKET-456"
|
|
1186
|
+
}
|
|
1187
|
+
]
|
|
1188
|
+
}
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
- `ticket_id` — Global ticket applied to all submissions until cleared
|
|
1192
|
+
- `records` — Per-record tickets (auto-cleared after successful submission)
|
|
1193
|
+
- `--ticket` flag always takes precedence over stored tickets
|
|
1194
|
+
|
|
1104
1195
|
#### User identity required
|
|
1105
1196
|
|
|
1106
1197
|
When the server returns an error mentioning `LoggedInUser_UID`, `LoggedInUserID`, `CurrentUserID`, or `UserID`, the CLI checks for a stored user identity from `dbo login`:
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -7,6 +7,7 @@ import { formatResponse, formatError } from '../lib/formatter.js';
|
|
|
7
7
|
import { log } from '../lib/logger.js';
|
|
8
8
|
import { shouldSkipColumn } from '../lib/columns.js';
|
|
9
9
|
import { loadAppConfig } from '../lib/config.js';
|
|
10
|
+
import { checkStoredTicket, clearGlobalTicket } from '../lib/ticketing.js';
|
|
10
11
|
|
|
11
12
|
// Directories and patterns to skip when scanning with `dbo add .`
|
|
12
13
|
const IGNORE_DIRS = new Set(['.dbo', '.git', 'node_modules', '.svn', '.hg']);
|
|
@@ -245,10 +246,21 @@ async function submitAdd(meta, metaPath, filePath, client, options) {
|
|
|
245
246
|
let body = await buildInputBody(dataExprs, extraParams);
|
|
246
247
|
let result = await client.postUrlEncoded('/api/input/submit', body);
|
|
247
248
|
|
|
248
|
-
// Retry with prompted params if needed (ticket, user)
|
|
249
|
-
const
|
|
250
|
-
if (
|
|
251
|
-
|
|
249
|
+
// Retry with prompted params if needed (ticket, user, repo mismatch)
|
|
250
|
+
const retryResult = await checkSubmitErrors(result);
|
|
251
|
+
if (retryResult) {
|
|
252
|
+
if (retryResult.skipRecord) {
|
|
253
|
+
log.warn(' Skipping record');
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
if (retryResult.skipAll) {
|
|
257
|
+
throw new Error('SKIP_ALL');
|
|
258
|
+
}
|
|
259
|
+
if (retryResult.ticketExpressions?.length > 0) {
|
|
260
|
+
dataExprs.push(...retryResult.ticketExpressions);
|
|
261
|
+
}
|
|
262
|
+
const params = retryResult.retryParams || retryResult;
|
|
263
|
+
Object.assign(extraParams, params);
|
|
252
264
|
body = await buildInputBody(dataExprs, extraParams);
|
|
253
265
|
result = await client.postUrlEncoded('/api/input/submit', body);
|
|
254
266
|
}
|
|
@@ -302,6 +314,19 @@ async function addDirectory(dirPath, client, options) {
|
|
|
302
314
|
if (!proceed) return;
|
|
303
315
|
}
|
|
304
316
|
|
|
317
|
+
// Pre-flight ticket validation
|
|
318
|
+
if (!options.ticket) {
|
|
319
|
+
const ticketCheck = await checkStoredTicket(options);
|
|
320
|
+
if (ticketCheck.cancel) {
|
|
321
|
+
log.info('Submission cancelled');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (ticketCheck.clearTicket) {
|
|
325
|
+
await clearGlobalTicket();
|
|
326
|
+
log.dim(' Cleared stored ticket');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
305
330
|
let succeeded = 0;
|
|
306
331
|
let failed = 0;
|
|
307
332
|
let batchDefaults = null;
|
|
@@ -314,6 +339,10 @@ async function addDirectory(dirPath, client, options) {
|
|
|
314
339
|
succeeded++;
|
|
315
340
|
}
|
|
316
341
|
} catch (err) {
|
|
342
|
+
if (err.message === 'SKIP_ALL') {
|
|
343
|
+
log.info('Skipping remaining records');
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
317
346
|
log.error(`Failed: ${relative(process.cwd(), filePath)} — ${err.message}`);
|
|
318
347
|
failed++;
|
|
319
348
|
}
|