@dboio/cli 0.8.0 → 0.9.2
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 +157 -57
- package/package.json +1 -1
- package/src/commands/add.js +122 -10
- package/src/commands/clone.js +351 -99
- package/src/commands/deploy.js +1 -1
- package/src/commands/init.js +13 -4
- package/src/commands/input.js +2 -2
- package/src/commands/login.js +69 -0
- package/src/commands/pull.js +1 -0
- package/src/commands/push.js +202 -34
- package/src/commands/rm.js +48 -16
- package/src/lib/delta.js +14 -1
- package/src/lib/diff.js +4 -2
- package/src/lib/filenames.js +151 -0
- package/src/lib/formatter.js +93 -11
- package/src/lib/ignore.js +8 -2
- package/src/lib/input-parser.js +30 -4
- package/src/lib/metadata-templates.js +264 -0
- package/src/lib/structure.js +30 -26
- package/src/lib/ticketing.js +79 -8
package/README.md
CHANGED
|
@@ -116,6 +116,39 @@ dbo content deploy albain3dwkofbhnd1qtd1q assets/css/colors.css
|
|
|
116
116
|
|
|
117
117
|
---
|
|
118
118
|
|
|
119
|
+
## Project Directory Structure
|
|
120
|
+
|
|
121
|
+
When you run `dbo init --scaffold` or `dbo clone`, the following standard directories are created in your project root:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
my-project/
|
|
125
|
+
├── .dbo/ # Config, synchronization and session info for dbo cli commands
|
|
126
|
+
├── .claude/ # Claude Code plugin config (commands, specs, plans, skills)
|
|
127
|
+
├── node_modules/ # Automatically created; contains all external libraries
|
|
128
|
+
├── src/ # Your actual source code (sass, typescript, etc.)
|
|
129
|
+
├── tests/ # Project-level tests
|
|
130
|
+
├── bins/ # Assets (contents, outputs, images, HTML, CSS)
|
|
131
|
+
├── trash/ # Staged soft-deleted files from dbo rm
|
|
132
|
+
├── automation/ # Automation entity records
|
|
133
|
+
├── app_version/ # App version entity records
|
|
134
|
+
├── data_source/ # Data source entity records
|
|
135
|
+
├── docs/ # Project documentation and docs entities
|
|
136
|
+
├── extension/ # Extension entity records
|
|
137
|
+
│ ├── <DescriptorType>/ # Sub-directory per descriptor type (e.g., component/, form/)
|
|
138
|
+
│ └── _unsupported/ # Extensions with unrecognized descriptor types
|
|
139
|
+
├── group/ # Group entity records
|
|
140
|
+
├── integration/ # Integration entity records
|
|
141
|
+
├── site/ # Site entity records
|
|
142
|
+
├── .gitignore # Tells Git to ignore files in the repo sync
|
|
143
|
+
├── .dboignore # Tells dbo cli to ignore files in commands
|
|
144
|
+
├── package.json # Metadata, scripts, and dependency list
|
|
145
|
+
└── package-lock.json # Records exact versions of dependencies installed
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Breaking change from pre-0.9.1**: Directory names have changed from mixed-case (`Extensions/`, `App Versions/`, `bins/`) to lowercase snake_case (`extension/`, `app_version/`, `bins/`). If you have an existing project, rename your directories manually before running `dbo clone` again.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
119
152
|
## Configuration
|
|
120
153
|
|
|
121
154
|
All configuration is **directory-scoped**. Each project folder maintains its own `.dbo/` directory with its own domain and session. Switch environments by switching directories:
|
|
@@ -135,6 +168,7 @@ All configuration is **directory-scoped**. Each project folder maintains its own
|
|
|
135
168
|
| `credentials.json` | Username, user ID, UID, name, email (no password) | Gitignored (per-user) |
|
|
136
169
|
| `cookies.txt` | Session cookie (Netscape format) | Gitignored (per-user) |
|
|
137
170
|
| `structure.json` | Bin directory mapping (created by `dbo clone`) | Committable (shared) |
|
|
171
|
+
| `metadata_templates.json` | Column templates per entity/descriptor (auto-generated by `dbo clone`) | Committable (shared) |
|
|
138
172
|
|
|
139
173
|
`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).
|
|
140
174
|
|
|
@@ -185,8 +219,26 @@ A gitignore-style file in the project root that controls which files and directo
|
|
|
185
219
|
|
|
186
220
|
**Used by:**
|
|
187
221
|
- `dbo init` — scaffold empty-check (determines if directory is "effectively empty")
|
|
188
|
-
- `dbo add .` — directory scanning (which files/dirs to skip)
|
|
189
|
-
- `dbo push` — metadata file discovery (
|
|
222
|
+
- `dbo add .` — directory scanning (which files/dirs to skip); also checked in single-file mode (`dbo add file.html`)
|
|
223
|
+
- `dbo push` — metadata file discovery: skips matching `.metadata.json` / `_output~*.json` files AND any record whose companion content file (`@reference`) matches an ignore pattern
|
|
224
|
+
|
|
225
|
+
**Bypass:** Use `dbo input -d '...'` to submit expressions for a file that would otherwise be ignored — `dbo input` never does file discovery so `.dboignore` does not apply.
|
|
226
|
+
|
|
227
|
+
**Pattern examples:**
|
|
228
|
+
|
|
229
|
+
```gitignore
|
|
230
|
+
# Ignore all SQL companion files (output records with CustomSQL)
|
|
231
|
+
**/*.CustomSQL.sql
|
|
232
|
+
|
|
233
|
+
# Ignore a specific record (by metadata file path)
|
|
234
|
+
bins/app/my-draft-page.metadata.json
|
|
235
|
+
|
|
236
|
+
# Ignore an entire directory
|
|
237
|
+
bins/staging/
|
|
238
|
+
|
|
239
|
+
# Ignore all content in bins/ (still push entity-dir records like extension/)
|
|
240
|
+
bins/
|
|
241
|
+
```
|
|
190
242
|
|
|
191
243
|
**Syntax:** Same as `.gitignore` — glob patterns, `#` comments, blank lines, negation with `!`, directory-only patterns with trailing `/`.
|
|
192
244
|
|
|
@@ -250,12 +302,13 @@ dbo init --scaffold --yes # scaffold dirs non-intera
|
|
|
250
302
|
| `--domain <host>` | DBO instance domain |
|
|
251
303
|
| `--username <user>` | DBO username (stored for login default) |
|
|
252
304
|
| `--force` | Overwrite existing configuration. Triggers a domain-change confirmation prompt when the new domain differs from the project reference domain |
|
|
253
|
-
| `--app <shortName>` | App short name (triggers clone after init) |
|
|
305
|
+
| `--app <shortName>` | App short name (triggers clone after init). Prompts for password and authenticates automatically before fetching app data from the server |
|
|
254
306
|
| `--clone` | Clone the app after initialization |
|
|
255
307
|
| `-g, --global` | Install Claude commands globally (`~/.claude/commands/`) |
|
|
256
308
|
| `--local` | Install Claude commands to project (`.claude/commands/`) |
|
|
257
|
-
| `--scaffold` | Pre-create standard project directories (`
|
|
309
|
+
| `--scaffold` | Pre-create standard project directories (`app_version`, `automation`, `bins`, `data_source`, `docs`, `extension`, `group`, `integration`, `site`, `src`, `tests`, `trash`) |
|
|
258
310
|
| `--dboignore` | Create `.dboignore` with default patterns (use with `--force` to overwrite existing) |
|
|
311
|
+
| `--media-placement <placement>` | Set media placement when cloning: `fullpath` or `binpath` (default: `bin`) |
|
|
259
312
|
| `-y, --yes` | Skip all interactive prompts (legacy migration, Claude Code setup) |
|
|
260
313
|
| `--non-interactive` | Alias for `--yes` |
|
|
261
314
|
|
|
@@ -291,7 +344,8 @@ dbo clone -e extension --descriptor-types false # Clone extensions flat (no de
|
|
|
291
344
|
| `--app <name>` | App short name to fetch from server |
|
|
292
345
|
| `-e, --entity <type>` | Only clone a specific entity type (e.g. `output`, `content`, `media`, `extension`) |
|
|
293
346
|
| `--documentation-only` | When used with `-e extension`, clone only documentation extensions |
|
|
294
|
-
| `--descriptor-types <bool>` | Sort extensions into descriptor sub-directories (default: `true`). Set to `false` to use flat `
|
|
347
|
+
| `--descriptor-types <bool>` | Sort extensions into descriptor sub-directories (default: `true`). Set to `false` to use flat `extension/` layout |
|
|
348
|
+
| `--media-placement <placement>` | Set media placement: `fullpath` or `binpath` (default: `bin`). Skips interactive media placement prompt |
|
|
295
349
|
| `--domain <host>` | Override domain. Triggers a domain-change confirmation prompt when it differs from the project reference domain |
|
|
296
350
|
| `--force` | Skip source mismatch confirmation and change detection; re-processes all files |
|
|
297
351
|
| `-y, --yes` | Auto-accept all prompts (also skips source mismatch confirmation) |
|
|
@@ -305,14 +359,14 @@ The project's reference domain is stored in `app.json._domain` (committed to git
|
|
|
305
359
|
|
|
306
360
|
#### What clone does
|
|
307
361
|
|
|
308
|
-
1. **Loads app JSON** — from a local file, server API, or interactive prompt
|
|
362
|
+
1. **Loads app JSON** — from a local file, server API, or interactive prompt. A spinner shows progress while fetching from the server (responses can be slow as the JSON is assembled on demand)
|
|
309
363
|
2. **Updates `.dbo/config.json`** — saves `AppID`, `AppUID`, `AppName`, `AppShortName`, `AppModifyKey` (if the app is locked), and `cloneSource` (the source used for this clone)
|
|
310
364
|
3. **Updates `package.json`** — populates `name`, `productName`, `description`, `homepage`, and `deploy` script
|
|
311
365
|
4. **Creates directories** — processes `children.bin` to build the directory hierarchy based on `ParentBinID` relationships
|
|
312
366
|
5. **Saves `.dbo/structure.json`** — maps BinIDs to directory paths for file placement
|
|
313
|
-
6. **Writes content files** — decodes base64 content, creates `*.metadata.json` + content files in the correct bin directory
|
|
367
|
+
6. **Writes content files** — decodes base64 content, creates `*.metadata.json` + content files in the correct bin directory. When a record's `Extension` field is empty, prompts you to choose from `css`, `js`, `html`, `xml`, `txt`, `md`, `cs`, `json`, `sql` (or skip to keep no extension). The chosen extension is saved back into the `metadata.json` `Extension` field
|
|
314
368
|
7. **Downloads media files** — fetches binary files (images, CSS, fonts) from the server via `/api/media/{uid}` and saves with metadata
|
|
315
|
-
8. **Processes entity-dir records** — entities matching project directories (`extension`, `app_version`, `data_source`, `site`, `group`, `integration`, `automation`) are saved as `.metadata.json` files in their corresponding directory (e.g., `
|
|
369
|
+
8. **Processes entity-dir records** — entities matching project directories (`extension`, `app_version`, `data_source`, `site`, `group`, `integration`, `automation`) are saved as `.metadata.json` files in their corresponding directory (e.g., `extension/`, `data_source/`)
|
|
316
370
|
9. **Processes other entities** — remaining entities with a `BinID` are placed in the corresponding bin directory
|
|
317
371
|
10. **Saves `app.json`** — clone of the original JSON with processed entries replaced by `@path/to/*.metadata.json` references
|
|
318
372
|
|
|
@@ -349,7 +403,7 @@ When cloning an app that was already cloned locally, the CLI detects existing fi
|
|
|
349
403
|
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:
|
|
350
404
|
|
|
351
405
|
```
|
|
352
|
-
⚠ Collision: 2 records want to create "
|
|
406
|
+
⚠ Collision: 2 records want to create "bins/app/colors.css"
|
|
353
407
|
? Which record should create this file?
|
|
354
408
|
❯ [content] colors (UID: abc123)
|
|
355
409
|
[media] colors.css (UID: def456)
|
|
@@ -387,13 +441,13 @@ Entity types that correspond to project directories (`extension`, `app_version`,
|
|
|
387
441
|
|
|
388
442
|
| Entity Key | Directory |
|
|
389
443
|
|------------|-----------|
|
|
390
|
-
| `extension` | `
|
|
391
|
-
| `app_version` | `
|
|
392
|
-
| `data_source` | `
|
|
393
|
-
| `site` | `
|
|
394
|
-
| `group` | `
|
|
395
|
-
| `integration` | `
|
|
396
|
-
| `automation` | `
|
|
444
|
+
| `extension` | `extension/<Descriptor>/` (see below) |
|
|
445
|
+
| `app_version` | `app_version/` |
|
|
446
|
+
| `data_source` | `data_source/` |
|
|
447
|
+
| `site` | `site/` |
|
|
448
|
+
| `group` | `group/` |
|
|
449
|
+
| `integration` | `integration/` |
|
|
450
|
+
| `automation` | `automation/` |
|
|
397
451
|
|
|
398
452
|
For each entity type, the CLI prompts to choose which column becomes the filename (defaults to `Name`, fallback `UID`). The choice is saved per entity type in `config.json` (e.g., `ExtensionFilenameCol`).
|
|
399
453
|
|
|
@@ -403,13 +457,13 @@ Use `-y` to skip prompts (uses `Name` column, no content extraction).
|
|
|
403
457
|
|
|
404
458
|
#### Extension descriptor sub-directories
|
|
405
459
|
|
|
406
|
-
Extension records are organized into sub-directories under `
|
|
460
|
+
Extension records are organized into sub-directories under `extension/` based on their `Descriptor` column value. A special extension type called `descriptor_definition` (itself an extension with `Descriptor === "descriptor_definition"`) provides the mapping from descriptor key (`String1`) to display/directory name (`Name`).
|
|
407
461
|
|
|
408
462
|
During clone, the CLI:
|
|
409
463
|
|
|
410
464
|
1. **Pre-scans** all extension records for `descriptor_definition` entries and builds a `String1 → Name` mapping
|
|
411
|
-
2. **Creates sub-directories** under `
|
|
412
|
-
3. **Always creates** `
|
|
465
|
+
2. **Creates sub-directories** under `extension/` for each mapped descriptor (e.g., `extension/Documentation/`, `extension/Includes/`)
|
|
466
|
+
3. **Always creates** `extension/_unsupported/` — extensions with an unmapped or null `Descriptor` are placed here
|
|
413
467
|
4. **Prompts per descriptor** — filename column and content extraction prompts fire once per unique `Descriptor` value (not once for all extensions)
|
|
414
468
|
5. **Persists the mapping** in `.dbo/structure.json` under `descriptorMapping`
|
|
415
469
|
|
|
@@ -423,10 +477,10 @@ During clone, the CLI:
|
|
|
423
477
|
|
|
424
478
|
**Documentation alternate placement**: When extracting MD content from `documentation` descriptor extensions, the CLI offers a choice:
|
|
425
479
|
|
|
426
|
-
- **Root placement** (`/
|
|
427
|
-
- **Inline placement** (`
|
|
480
|
+
- **Root placement** (`/docs/<filename>.md`) — recommended for easy access; creates `@/docs/<filename>.md` references in metadata
|
|
481
|
+
- **Inline placement** (`extension/Documentation/<filename>.md`) — keeps files alongside metadata
|
|
428
482
|
|
|
429
|
-
Root-placed files use absolute-from-root `@/` references (e.g., `@/
|
|
483
|
+
Root-placed files use absolute-from-root `@/` references (e.g., `@/docs/my-doc.md`) so `dbo push` can locate them regardless of where the metadata lives.
|
|
430
484
|
|
|
431
485
|
**Entity filter support**:
|
|
432
486
|
|
|
@@ -437,7 +491,7 @@ dbo clone -e extension # Clone all extensions (all
|
|
|
437
491
|
dbo clone -e extension --descriptor-types false # Flat layout (no descriptor sorting)
|
|
438
492
|
```
|
|
439
493
|
|
|
440
|
-
**`dbo add` auto-inference**: Running `dbo add
|
|
494
|
+
**`dbo add` auto-inference**: Running `dbo add docs/my-doc.md` when `ExtensionDocumentationMDPlacement` is `"root"` auto-creates a companion `.metadata.json` in `extension/documentation/` with the correct `@/` reference.
|
|
441
495
|
|
|
442
496
|
#### Output structure
|
|
443
497
|
|
|
@@ -449,7 +503,7 @@ project/
|
|
|
449
503
|
.gitignore # credentials.json + cookies.txt added
|
|
450
504
|
package.json # Updated with app info + deploy script
|
|
451
505
|
app.json # Clone of original with @references
|
|
452
|
-
|
|
506
|
+
bins/ # ← root for all bin-placed files
|
|
453
507
|
app/ # ← directory from children.bin
|
|
454
508
|
thomas-scratch.md
|
|
455
509
|
thomas-scratch.metadata.json
|
|
@@ -457,67 +511,93 @@ project/
|
|
|
457
511
|
CurrentTicketID.metadata.json
|
|
458
512
|
ticket_test/ # ← another bin directory
|
|
459
513
|
...
|
|
460
|
-
|
|
514
|
+
extension/ # ← extension records organized by descriptor
|
|
461
515
|
Documentation/ # ← descriptor sub-directory (from descriptor_definition)
|
|
462
516
|
MyDoc.uid1.metadata.json
|
|
463
517
|
MyDoc.uid1.String10.md # ← extracted content column
|
|
464
518
|
Includes/
|
|
465
519
|
Header.uid2.metadata.json
|
|
466
|
-
|
|
467
|
-
|
|
520
|
+
_unsupported/ # ← always created (unmapped/null descriptors)
|
|
521
|
+
docs/ # ← root-placed documentation MD files (optional)
|
|
468
522
|
MyDoc.md
|
|
469
|
-
|
|
523
|
+
data_source/
|
|
470
524
|
MySQL-Primary.metadata.json
|
|
471
|
-
|
|
525
|
+
site/
|
|
472
526
|
MainSite.metadata.json
|
|
473
527
|
media/operator/app/... # ← FullPath placement (when MediaPlacement=fullpath)
|
|
474
528
|
```
|
|
475
529
|
|
|
476
|
-
#### Understanding the `
|
|
530
|
+
#### Understanding the `bins/` directory structure
|
|
477
531
|
|
|
478
|
-
The `
|
|
532
|
+
The `bins/` directory is the default location for all bin-placed content files during clone. It organizes files according to your app's bin hierarchy from DBO.io.
|
|
479
533
|
|
|
480
534
|
**Directory Organization:**
|
|
481
535
|
|
|
482
|
-
- **`
|
|
483
|
-
- **`
|
|
484
|
-
- **`
|
|
536
|
+
- **`bins/`** — Root directory for all bin-placed files (local organizational directory only)
|
|
537
|
+
- **`bins/app/`** — Special subdirectory for the main app bin (typically the default bin)
|
|
538
|
+
- **`bins/custom_name/`** — Custom bin directories (e.g., `tpl/`, `ticket_test/`, etc.)
|
|
485
539
|
|
|
486
|
-
**Important: The `
|
|
540
|
+
**Important: The `bins/app/` special case**
|
|
487
541
|
|
|
488
|
-
The `app/` subdirectory under `
|
|
542
|
+
The `app/` subdirectory under `bins/` is treated specially:
|
|
489
543
|
|
|
490
|
-
1. **It's organizational only** — The `
|
|
544
|
+
1. **It's organizational only** — The `bins/app/` prefix exists only for local file organization and is **not part of the server-side path**.
|
|
491
545
|
|
|
492
|
-
2. **Path normalization** — When comparing paths (during `dbo push`), the CLI automatically strips both `
|
|
546
|
+
2. **Path normalization** — When comparing paths (during `dbo push`), the CLI automatically strips both `bins/` and `app/` from paths:
|
|
493
547
|
```
|
|
494
|
-
Local file:
|
|
548
|
+
Local file: bins/app/assets/css/operator.css
|
|
495
549
|
Server Path: assets/css/operator.css
|
|
496
550
|
→ These are considered the same path ✓
|
|
497
551
|
```
|
|
498
552
|
|
|
499
|
-
3. **Custom bins are preserved** — Other subdirectories like `
|
|
553
|
+
3. **Custom bins are preserved** — Other subdirectories like `bins/tpl/` or `bins/ticket_test/` represent actual bin hierarchies and their names are meaningful:
|
|
500
554
|
```
|
|
501
|
-
Local file:
|
|
555
|
+
Local file: bins/tpl/header.html
|
|
502
556
|
Server Path: tpl/header.html
|
|
503
557
|
→ The 'tpl/' directory is preserved ✓
|
|
504
558
|
```
|
|
505
559
|
|
|
506
560
|
**Why this matters:**
|
|
507
561
|
|
|
508
|
-
- When you `dbo push` files from `
|
|
509
|
-
- If your metadata `Path` column contains `assets/css/colors.css`, it will correctly match files in `
|
|
510
|
-
- Custom bin directories like `
|
|
562
|
+
- When you `dbo push` files from `bins/app/`, the CLI knows these paths should match the root-level paths in your metadata
|
|
563
|
+
- If your metadata `Path` column contains `assets/css/colors.css`, it will correctly match files in `bins/app/assets/css/colors.css`
|
|
564
|
+
- Custom bin directories like `bins/tpl/` serve from the `tpl/` directive and maintain their path structure
|
|
511
565
|
|
|
512
566
|
**Leading slash handling:**
|
|
513
567
|
|
|
514
568
|
The CLI handles leading `/` in metadata paths flexibly:
|
|
515
|
-
- `Path: assets/css/file.css` matches `
|
|
516
|
-
- `Path: /assets/css/file.css` also matches `
|
|
517
|
-
- `Path: /assets/css/file.css` matches `
|
|
569
|
+
- `Path: assets/css/file.css` matches `bins/app/assets/css/file.css` ✓
|
|
570
|
+
- `Path: /assets/css/file.css` also matches `bins/app/assets/css/file.css` ✓
|
|
571
|
+
- `Path: /assets/css/file.css` matches `bins/assets/css/file.css` ✓
|
|
518
572
|
|
|
519
573
|
This ensures compatibility with various path formats from the server while maintaining correct local file organization.
|
|
520
574
|
|
|
575
|
+
#### Metadata Templates
|
|
576
|
+
|
|
577
|
+
During `dbo clone`, the CLI auto-generates `.dbo/metadata_templates.json` — a file that records which columns each entity/descriptor uses. This file is seeded from the first cloned record of each type and can be manually edited afterwards.
|
|
578
|
+
|
|
579
|
+
**File format:**
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
{
|
|
583
|
+
"site": ["AppID", "Name", "ShortName", "Active"],
|
|
584
|
+
"extension": {
|
|
585
|
+
"documentation": ["AppID", "Name", "Descriptor=documentation", "String10=@reference"],
|
|
586
|
+
"control": ["AppID", "Name", "ShortName", "Active"]
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Column syntax:**
|
|
592
|
+
|
|
593
|
+
| Syntax | Meaning |
|
|
594
|
+
|--------|---------|
|
|
595
|
+
| `Name` | Plain column — populated from filename or left empty |
|
|
596
|
+
| `Descriptor=documentation` | Literal value — always set to `documentation` |
|
|
597
|
+
| `String10=@reference` | Content reference — links to the file being added |
|
|
598
|
+
|
|
599
|
+
`dbo add` uses these templates to auto-detect the entity type from a file's directory placement and generate metadata without prompting. For example, `dbo add extension/include/nav.html` resolves to entity `extension` / descriptor `include` and applies the matching template.
|
|
600
|
+
|
|
521
601
|
---
|
|
522
602
|
|
|
523
603
|
### `dbo login`
|
|
@@ -729,8 +809,20 @@ The save-to-disk feature fetches data and persists records as local files. It us
|
|
|
729
809
|
4. **Extension** — use an entity column or specify manually
|
|
730
810
|
|
|
731
811
|
Each record produces:
|
|
732
|
-
- A content file: `[filename].[extension]`
|
|
733
|
-
- A metadata file: `[filename].metadata.json` (all column values)
|
|
812
|
+
- A content file: `[filename]~[uid].[extension]` (e.g., `colors~abc123.css`)
|
|
813
|
+
- A metadata file: `[filename]~[uid].metadata.json` (all column values)
|
|
814
|
+
|
|
815
|
+
### Tilde UID Convention
|
|
816
|
+
|
|
817
|
+
Every local file whose server record has a known UID embeds that UID in the filename, separated by a tilde (`~`):
|
|
818
|
+
|
|
819
|
+
- Content: `<basename>~<uid>.<ext>` / `<basename>~<uid>.metadata.json`
|
|
820
|
+
- Media: `<basename>~<uid>.<ext>` / `<basename>~<uid>.<ext>.metadata.json`
|
|
821
|
+
- Entity-dir: `<name>~<uid>.metadata.json`
|
|
822
|
+
|
|
823
|
+
Exception: if the chosen filename column value *is* the UID itself, the tilde suffix is omitted: `<uid>.<ext>`.
|
|
824
|
+
|
|
825
|
+
When pushing, the `~<uid>` portion is automatically stripped from filenames sent to the server (e.g., `logo~def456.png` is uploaded as `logo.png`).
|
|
734
826
|
|
|
735
827
|
If the path column contains a filename (e.g., `assets/js/main.js`), the CLI detects it and asks if you want to use it.
|
|
736
828
|
|
|
@@ -1015,6 +1107,8 @@ After accepting changes, file modification times are synced to the server's `_La
|
|
|
1015
1107
|
|
|
1016
1108
|
Push local files back to DBO.io using metadata from a previous pull. This is the counterpart to `dbo content pull` and `dbo output --save`.
|
|
1017
1109
|
|
|
1110
|
+
Files and records matching `.dboignore` patterns are skipped — both by metadata file path (e.g. `*.metadata.json`) and by companion content file path (e.g. a record whose `@Content` points to an ignored `.sql` file). To push an ignored file directly, use `dbo input -d '...'` with an explicit expression.
|
|
1111
|
+
|
|
1018
1112
|
#### Round-trip workflow
|
|
1019
1113
|
|
|
1020
1114
|
```bash
|
|
@@ -1113,29 +1207,29 @@ Remove a file or directory locally and stage server deletions for the next `dbo
|
|
|
1113
1207
|
|
|
1114
1208
|
```bash
|
|
1115
1209
|
# Remove a content file (finds companion .metadata.json automatically)
|
|
1116
|
-
dbo rm
|
|
1210
|
+
dbo rm bins/app/some-file.txt
|
|
1117
1211
|
|
|
1118
1212
|
# Remove by metadata file directly
|
|
1119
|
-
dbo rm
|
|
1213
|
+
dbo rm bins/app/some-file.metadata.json
|
|
1120
1214
|
|
|
1121
1215
|
# Skip confirmation prompt
|
|
1122
|
-
dbo rm -f
|
|
1216
|
+
dbo rm -f bins/app/some-file.txt
|
|
1123
1217
|
|
|
1124
1218
|
# Stage server deletion without deleting local files
|
|
1125
|
-
dbo rm --keep-local
|
|
1219
|
+
dbo rm --keep-local bins/app/some-file.txt
|
|
1126
1220
|
```
|
|
1127
1221
|
|
|
1128
1222
|
#### Directory
|
|
1129
1223
|
|
|
1130
1224
|
```bash
|
|
1131
1225
|
# Remove a directory and all its contents (prompts for approach)
|
|
1132
|
-
dbo rm
|
|
1226
|
+
dbo rm bins/app/ui/
|
|
1133
1227
|
|
|
1134
1228
|
# Remove directory without prompts
|
|
1135
|
-
dbo rm -f
|
|
1229
|
+
dbo rm -f bins/app/ui/
|
|
1136
1230
|
|
|
1137
1231
|
# Stage deletions without removing local files/directories
|
|
1138
|
-
dbo rm --keep-local
|
|
1232
|
+
dbo rm --keep-local bins/app/ui/
|
|
1139
1233
|
```
|
|
1140
1234
|
|
|
1141
1235
|
When removing a directory, the CLI:
|
|
@@ -1150,6 +1244,7 @@ When removing a directory, the CLI:
|
|
|
1150
1244
|
| `<path>` | File, `.metadata.json`, or directory to remove |
|
|
1151
1245
|
| `-f, --force` | Skip all confirmation prompts |
|
|
1152
1246
|
| `--keep-local` | Only stage server deletions, keep local files/directories |
|
|
1247
|
+
| `--hard` | Immediately delete local files (no `Trash/` move; legacy behavior) |
|
|
1153
1248
|
|
|
1154
1249
|
#### What rm does (single file)
|
|
1155
1250
|
|
|
@@ -1158,7 +1253,10 @@ When removing a directory, the CLI:
|
|
|
1158
1253
|
3. **Prompts for confirmation** — "Do you really want to remove this file and all of its nodes?"
|
|
1159
1254
|
4. **Stages deletion** — adds a delete entry to `.dbo/synchronize.json`
|
|
1160
1255
|
5. **Updates app.json** — removes the `@path/to/file.metadata.json` reference from children arrays
|
|
1161
|
-
6. **
|
|
1256
|
+
6. **Soft-deletes local files** — renames files with `__WILL_DELETE__` prefix (unless `--keep-local` or `--hard`)
|
|
1257
|
+
7. **After `dbo push`** — successfully deleted records have their `__WILL_DELETE__` files moved to `Trash/`
|
|
1258
|
+
|
|
1259
|
+
Use `--hard` to immediately delete files without the `Trash/` safety net.
|
|
1162
1260
|
|
|
1163
1261
|
#### synchronize.json
|
|
1164
1262
|
|
|
@@ -1188,6 +1286,8 @@ The next `dbo push` processes all pending deletions before pushing file changes.
|
|
|
1188
1286
|
|
|
1189
1287
|
Add a new file to DBO.io by creating a server record. Similar to `git add`, this registers a local file with the server.
|
|
1190
1288
|
|
|
1289
|
+
Files matching `.dboignore` patterns are skipped — both in directory-scan mode (`dbo add .`) and single-file mode (`dbo add file.html`). Use `dbo input` to create a record for an ignored file directly.
|
|
1290
|
+
|
|
1191
1291
|
#### Single file
|
|
1192
1292
|
|
|
1193
1293
|
```bash
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { readFile, readdir, stat, writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { readFile, readdir, stat, writeFile, mkdir, rename } from 'fs/promises';
|
|
3
3
|
import { join, dirname, basename, extname, relative } from 'path';
|
|
4
4
|
import { DboClient } from '../lib/client.js';
|
|
5
5
|
import { buildInputBody, checkSubmitErrors } from '../lib/input-parser.js';
|
|
6
6
|
import { formatResponse, formatError } from '../lib/formatter.js';
|
|
7
7
|
import { log } from '../lib/logger.js';
|
|
8
8
|
import { shouldSkipColumn } from '../lib/columns.js';
|
|
9
|
-
import { loadAppConfig, loadExtensionDocumentationMDPlacement, loadDescriptorFilenamePreference } from '../lib/config.js';
|
|
9
|
+
import { loadAppConfig, loadAppJsonBaseline, loadExtensionDocumentationMDPlacement, loadDescriptorFilenamePreference, loadConfig } from '../lib/config.js';
|
|
10
|
+
import { resolveDirective, resolveTemplateCols, assembleMetadata, promptReferenceColumn, setTemplateCols, saveMetadataTemplates, loadMetadataTemplates } from '../lib/metadata-templates.js';
|
|
11
|
+
import { buildUidFilename, hasUidInFilename } from '../lib/filenames.js';
|
|
12
|
+
import { setFileTimestamps } from '../lib/timestamps.js';
|
|
10
13
|
import { checkStoredTicket, clearGlobalTicket } from '../lib/ticketing.js';
|
|
11
14
|
import { checkModifyKey, isModifyKeyError, handleModifyKeyError } from '../lib/modify-key.js';
|
|
12
15
|
import { loadIgnore } from '../lib/ignore.js';
|
|
@@ -67,12 +70,20 @@ async function detectDocumentationFile(filePath) {
|
|
|
67
70
|
if (placement !== 'root') return null;
|
|
68
71
|
|
|
69
72
|
const rel = relative(process.cwd(), filePath).replace(/\\/g, '/');
|
|
70
|
-
if (!rel.startsWith('
|
|
73
|
+
if (!rel.startsWith('docs/')) return null;
|
|
71
74
|
|
|
72
75
|
return { entity: 'extension', descriptor: 'documentation' };
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
async function addSingleFile(filePath, client, options, batchDefaults) {
|
|
79
|
+
// Check .dboignore before doing any processing
|
|
80
|
+
const ig = await loadIgnore();
|
|
81
|
+
const relPath = relative(process.cwd(), filePath).replace(/\\/g, '/');
|
|
82
|
+
if (ig.ignores(relPath)) {
|
|
83
|
+
log.dim(`Skipped (dboignored): ${relPath}`);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
const dir = dirname(filePath);
|
|
77
88
|
const ext = extname(filePath);
|
|
78
89
|
const base = basename(filePath, ext);
|
|
@@ -102,12 +113,58 @@ async function addSingleFile(filePath, client, options, batchDefaults) {
|
|
|
102
113
|
return await submitAdd(meta, metaPath, filePath, client, options);
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
// Step 3b:
|
|
116
|
+
// Step 3b: Directive / template path
|
|
117
|
+
const directive = resolveDirective(filePath);
|
|
118
|
+
if (directive) {
|
|
119
|
+
const { entity, descriptor } = directive;
|
|
120
|
+
const appConfig = await loadAppConfig();
|
|
121
|
+
const appJson = await loadAppJsonBaseline().catch(() => null);
|
|
122
|
+
const resolved = await resolveTemplateCols(entity, descriptor, appConfig, appJson);
|
|
123
|
+
|
|
124
|
+
if (resolved !== null) {
|
|
125
|
+
let { cols, templates } = resolved;
|
|
126
|
+
|
|
127
|
+
// Check if @reference col is known
|
|
128
|
+
const hasRefMarker = cols.some(c => c.includes('=@reference'));
|
|
129
|
+
if (!hasRefMarker) {
|
|
130
|
+
if (options.yes) {
|
|
131
|
+
console.warn(`Warning: No @reference column in template for ${entity}${descriptor ? '.' + descriptor : ''} — skipping content column. Update .dbo/metadata_templates.json to add Key=@reference.`);
|
|
132
|
+
} else {
|
|
133
|
+
const chosen = await promptReferenceColumn(cols, entity, descriptor);
|
|
134
|
+
if (chosen) {
|
|
135
|
+
cols = cols.map(c => c === chosen ? `${chosen}=@reference` : c);
|
|
136
|
+
templates = templates ?? await loadMetadataTemplates() ?? {};
|
|
137
|
+
setTemplateCols(templates, entity, descriptor, cols);
|
|
138
|
+
await saveMetadataTemplates(templates);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const { meta } = assembleMetadata(cols, filePath, entity, descriptor, appConfig);
|
|
144
|
+
|
|
145
|
+
// Determine metadata path
|
|
146
|
+
let templateMetaPath;
|
|
147
|
+
const rel = relative(process.cwd(), filePath).replace(/\\/g, '/');
|
|
148
|
+
if (rel.startsWith('docs/')) {
|
|
149
|
+
// docs/ files: companion metadata goes to extension/documentation/
|
|
150
|
+
const docDir = join(process.cwd(), 'extension', 'documentation');
|
|
151
|
+
await mkdir(docDir, { recursive: true });
|
|
152
|
+
templateMetaPath = join(docDir, `${basename(filePath, extname(filePath))}.metadata.json`);
|
|
153
|
+
} else {
|
|
154
|
+
templateMetaPath = join(dirname(filePath), `${basename(filePath, extname(filePath))}.metadata.json`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await writeFile(templateMetaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
158
|
+
return await submitAdd(meta, templateMetaPath, filePath, client, options);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Step 3c: Auto-infer entity/descriptor for Documentation/ files when root placement is configured
|
|
106
163
|
const docInfo = await detectDocumentationFile(filePath);
|
|
107
164
|
if (docInfo) {
|
|
108
165
|
const filenameCol = await loadDescriptorFilenamePreference('documentation') || 'Name';
|
|
109
166
|
const docBase = basename(filePath, extname(filePath));
|
|
110
|
-
const companionDir = join(process.cwd(), '
|
|
167
|
+
const companionDir = join(process.cwd(), 'extension', 'documentation');
|
|
111
168
|
await mkdir(companionDir, { recursive: true });
|
|
112
169
|
|
|
113
170
|
const docMeta = {
|
|
@@ -329,20 +386,75 @@ async function submitAdd(meta, metaPath, filePath, client, options) {
|
|
|
329
386
|
result = await client.postUrlEncoded('/api/input/submit', body);
|
|
330
387
|
}
|
|
331
388
|
|
|
332
|
-
formatResponse(result, { json: options.json, jq: options.jq });
|
|
389
|
+
formatResponse(result, { json: options.json, jq: options.jq, verbose: options.verbose });
|
|
333
390
|
|
|
334
391
|
if (!result.successful) {
|
|
335
392
|
throw new Error('Add failed');
|
|
336
393
|
}
|
|
337
394
|
|
|
338
|
-
// Extract UID from response and
|
|
395
|
+
// Extract UID from response and rename files to ~uid convention
|
|
339
396
|
const addResults = result.payload?.Results?.Add || result.data?.Payload?.Results?.Add || [];
|
|
340
397
|
if (addResults.length > 0) {
|
|
341
398
|
const returnedUID = addResults[0].UID;
|
|
399
|
+
const returnedLastUpdated = addResults[0]._LastUpdated;
|
|
400
|
+
|
|
342
401
|
if (returnedUID) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
402
|
+
// Check if UID is already embedded in the filename (idempotency guard)
|
|
403
|
+
const currentMetaBase = basename(metaPath);
|
|
404
|
+
if (hasUidInFilename(currentMetaBase, returnedUID)) {
|
|
405
|
+
meta.UID = returnedUID;
|
|
406
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
407
|
+
log.success(`UID already in filename: ${returnedUID}`);
|
|
408
|
+
} else {
|
|
409
|
+
// Compute new names
|
|
410
|
+
const metaDir = dirname(metaPath);
|
|
411
|
+
const currentExt = extname(filePath).substring(1);
|
|
412
|
+
const currentBase = basename(filePath, extname(filePath));
|
|
413
|
+
const newBase = buildUidFilename(currentBase, returnedUID);
|
|
414
|
+
const newFilename = currentExt ? `${newBase}.${currentExt}` : newBase;
|
|
415
|
+
const newFilePath = join(metaDir, newFilename);
|
|
416
|
+
const newMetaPath = join(metaDir, `${newBase}.metadata.json`);
|
|
417
|
+
|
|
418
|
+
// Rename content file
|
|
419
|
+
try {
|
|
420
|
+
await rename(filePath, newFilePath);
|
|
421
|
+
log.success(`Renamed: ${basename(filePath)} → ${newFilename}`);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
log.warn(` Could not rename content file: ${err.message}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Update @reference in metadata
|
|
427
|
+
meta.UID = returnedUID;
|
|
428
|
+
for (const col of (meta._contentColumns || [])) {
|
|
429
|
+
const ref = meta[col];
|
|
430
|
+
if (ref && String(ref).startsWith('@')) {
|
|
431
|
+
const oldRefFile = String(ref).substring(1);
|
|
432
|
+
if (oldRefFile === basename(filePath)) {
|
|
433
|
+
meta[col] = `@${newFilename}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Write and rename metadata file
|
|
439
|
+
await writeFile(newMetaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
440
|
+
if (metaPath !== newMetaPath) {
|
|
441
|
+
try { await rename(metaPath, newMetaPath); } catch { /* already written */ }
|
|
442
|
+
}
|
|
443
|
+
log.dim(` Renamed metadata: ${basename(metaPath)} → ${basename(newMetaPath)}`);
|
|
444
|
+
|
|
445
|
+
// Restore timestamps from server _LastUpdated
|
|
446
|
+
const config = await loadConfig();
|
|
447
|
+
const serverTz = config.ServerTimezone;
|
|
448
|
+
if (serverTz && returnedLastUpdated) {
|
|
449
|
+
try {
|
|
450
|
+
await setFileTimestamps(newFilePath, returnedLastUpdated, returnedLastUpdated, serverTz);
|
|
451
|
+
await setFileTimestamps(newMetaPath, returnedLastUpdated, returnedLastUpdated, serverTz);
|
|
452
|
+
} catch { /* non-critical */ }
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
log.success(`UID assigned: ${returnedUID}`);
|
|
456
|
+
log.dim(` Files renamed to ~${returnedUID} convention`);
|
|
457
|
+
}
|
|
346
458
|
log.dim(` Run "dbo pull -e ${entity} ${returnedUID}" to populate all columns`);
|
|
347
459
|
}
|
|
348
460
|
}
|