@dboio/cli 0.8.2 → 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 +134 -56
- package/package.json +1 -1
- package/src/commands/add.js +114 -10
- package/src/commands/clone.js +245 -85
- package/src/commands/deploy.js +1 -1
- package/src/commands/init.js +4 -3
- package/src/commands/input.js +2 -2
- package/src/commands/pull.js +1 -0
- package/src/commands/push.js +121 -25
- package/src/commands/rm.js +48 -16
- 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 +73 -5
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
|
|
|
@@ -197,13 +231,13 @@ A gitignore-style file in the project root that controls which files and directo
|
|
|
197
231
|
**/*.CustomSQL.sql
|
|
198
232
|
|
|
199
233
|
# Ignore a specific record (by metadata file path)
|
|
200
|
-
|
|
234
|
+
bins/app/my-draft-page.metadata.json
|
|
201
235
|
|
|
202
236
|
# Ignore an entire directory
|
|
203
|
-
|
|
237
|
+
bins/staging/
|
|
204
238
|
|
|
205
|
-
# Ignore all content in
|
|
206
|
-
|
|
239
|
+
# Ignore all content in bins/ (still push entity-dir records like extension/)
|
|
240
|
+
bins/
|
|
207
241
|
```
|
|
208
242
|
|
|
209
243
|
**Syntax:** Same as `.gitignore` — glob patterns, `#` comments, blank lines, negation with `!`, directory-only patterns with trailing `/`.
|
|
@@ -272,8 +306,9 @@ dbo init --scaffold --yes # scaffold dirs non-intera
|
|
|
272
306
|
| `--clone` | Clone the app after initialization |
|
|
273
307
|
| `-g, --global` | Install Claude commands globally (`~/.claude/commands/`) |
|
|
274
308
|
| `--local` | Install Claude commands to project (`.claude/commands/`) |
|
|
275
|
-
| `--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`) |
|
|
276
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`) |
|
|
277
312
|
| `-y, --yes` | Skip all interactive prompts (legacy migration, Claude Code setup) |
|
|
278
313
|
| `--non-interactive` | Alias for `--yes` |
|
|
279
314
|
|
|
@@ -309,7 +344,8 @@ dbo clone -e extension --descriptor-types false # Clone extensions flat (no de
|
|
|
309
344
|
| `--app <name>` | App short name to fetch from server |
|
|
310
345
|
| `-e, --entity <type>` | Only clone a specific entity type (e.g. `output`, `content`, `media`, `extension`) |
|
|
311
346
|
| `--documentation-only` | When used with `-e extension`, clone only documentation extensions |
|
|
312
|
-
| `--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 |
|
|
313
349
|
| `--domain <host>` | Override domain. Triggers a domain-change confirmation prompt when it differs from the project reference domain |
|
|
314
350
|
| `--force` | Skip source mismatch confirmation and change detection; re-processes all files |
|
|
315
351
|
| `-y, --yes` | Auto-accept all prompts (also skips source mismatch confirmation) |
|
|
@@ -330,7 +366,7 @@ The project's reference domain is stored in `app.json._domain` (committed to git
|
|
|
330
366
|
5. **Saves `.dbo/structure.json`** — maps BinIDs to directory paths for file placement
|
|
331
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
|
|
332
368
|
7. **Downloads media files** — fetches binary files (images, CSS, fonts) from the server via `/api/media/{uid}` and saves with metadata
|
|
333
|
-
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/`)
|
|
334
370
|
9. **Processes other entities** — remaining entities with a `BinID` are placed in the corresponding bin directory
|
|
335
371
|
10. **Saves `app.json`** — clone of the original JSON with processed entries replaced by `@path/to/*.metadata.json` references
|
|
336
372
|
|
|
@@ -367,7 +403,7 @@ When cloning an app that was already cloned locally, the CLI detects existing fi
|
|
|
367
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:
|
|
368
404
|
|
|
369
405
|
```
|
|
370
|
-
⚠ Collision: 2 records want to create "
|
|
406
|
+
⚠ Collision: 2 records want to create "bins/app/colors.css"
|
|
371
407
|
? Which record should create this file?
|
|
372
408
|
❯ [content] colors (UID: abc123)
|
|
373
409
|
[media] colors.css (UID: def456)
|
|
@@ -405,13 +441,13 @@ Entity types that correspond to project directories (`extension`, `app_version`,
|
|
|
405
441
|
|
|
406
442
|
| Entity Key | Directory |
|
|
407
443
|
|------------|-----------|
|
|
408
|
-
| `extension` | `
|
|
409
|
-
| `app_version` | `
|
|
410
|
-
| `data_source` | `
|
|
411
|
-
| `site` | `
|
|
412
|
-
| `group` | `
|
|
413
|
-
| `integration` | `
|
|
414
|
-
| `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/` |
|
|
415
451
|
|
|
416
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`).
|
|
417
453
|
|
|
@@ -421,13 +457,13 @@ Use `-y` to skip prompts (uses `Name` column, no content extraction).
|
|
|
421
457
|
|
|
422
458
|
#### Extension descriptor sub-directories
|
|
423
459
|
|
|
424
|
-
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`).
|
|
425
461
|
|
|
426
462
|
During clone, the CLI:
|
|
427
463
|
|
|
428
464
|
1. **Pre-scans** all extension records for `descriptor_definition` entries and builds a `String1 → Name` mapping
|
|
429
|
-
2. **Creates sub-directories** under `
|
|
430
|
-
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
|
|
431
467
|
4. **Prompts per descriptor** — filename column and content extraction prompts fire once per unique `Descriptor` value (not once for all extensions)
|
|
432
468
|
5. **Persists the mapping** in `.dbo/structure.json` under `descriptorMapping`
|
|
433
469
|
|
|
@@ -441,10 +477,10 @@ During clone, the CLI:
|
|
|
441
477
|
|
|
442
478
|
**Documentation alternate placement**: When extracting MD content from `documentation` descriptor extensions, the CLI offers a choice:
|
|
443
479
|
|
|
444
|
-
- **Root placement** (`/
|
|
445
|
-
- **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
|
|
446
482
|
|
|
447
|
-
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.
|
|
448
484
|
|
|
449
485
|
**Entity filter support**:
|
|
450
486
|
|
|
@@ -455,7 +491,7 @@ dbo clone -e extension # Clone all extensions (all
|
|
|
455
491
|
dbo clone -e extension --descriptor-types false # Flat layout (no descriptor sorting)
|
|
456
492
|
```
|
|
457
493
|
|
|
458
|
-
**`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.
|
|
459
495
|
|
|
460
496
|
#### Output structure
|
|
461
497
|
|
|
@@ -467,7 +503,7 @@ project/
|
|
|
467
503
|
.gitignore # credentials.json + cookies.txt added
|
|
468
504
|
package.json # Updated with app info + deploy script
|
|
469
505
|
app.json # Clone of original with @references
|
|
470
|
-
|
|
506
|
+
bins/ # ← root for all bin-placed files
|
|
471
507
|
app/ # ← directory from children.bin
|
|
472
508
|
thomas-scratch.md
|
|
473
509
|
thomas-scratch.metadata.json
|
|
@@ -475,67 +511,93 @@ project/
|
|
|
475
511
|
CurrentTicketID.metadata.json
|
|
476
512
|
ticket_test/ # ← another bin directory
|
|
477
513
|
...
|
|
478
|
-
|
|
514
|
+
extension/ # ← extension records organized by descriptor
|
|
479
515
|
Documentation/ # ← descriptor sub-directory (from descriptor_definition)
|
|
480
516
|
MyDoc.uid1.metadata.json
|
|
481
517
|
MyDoc.uid1.String10.md # ← extracted content column
|
|
482
518
|
Includes/
|
|
483
519
|
Header.uid2.metadata.json
|
|
484
|
-
|
|
485
|
-
|
|
520
|
+
_unsupported/ # ← always created (unmapped/null descriptors)
|
|
521
|
+
docs/ # ← root-placed documentation MD files (optional)
|
|
486
522
|
MyDoc.md
|
|
487
|
-
|
|
523
|
+
data_source/
|
|
488
524
|
MySQL-Primary.metadata.json
|
|
489
|
-
|
|
525
|
+
site/
|
|
490
526
|
MainSite.metadata.json
|
|
491
527
|
media/operator/app/... # ← FullPath placement (when MediaPlacement=fullpath)
|
|
492
528
|
```
|
|
493
529
|
|
|
494
|
-
#### Understanding the `
|
|
530
|
+
#### Understanding the `bins/` directory structure
|
|
495
531
|
|
|
496
|
-
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.
|
|
497
533
|
|
|
498
534
|
**Directory Organization:**
|
|
499
535
|
|
|
500
|
-
- **`
|
|
501
|
-
- **`
|
|
502
|
-
- **`
|
|
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.)
|
|
503
539
|
|
|
504
|
-
**Important: The `
|
|
540
|
+
**Important: The `bins/app/` special case**
|
|
505
541
|
|
|
506
|
-
The `app/` subdirectory under `
|
|
542
|
+
The `app/` subdirectory under `bins/` is treated specially:
|
|
507
543
|
|
|
508
|
-
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**.
|
|
509
545
|
|
|
510
|
-
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:
|
|
511
547
|
```
|
|
512
|
-
Local file:
|
|
548
|
+
Local file: bins/app/assets/css/operator.css
|
|
513
549
|
Server Path: assets/css/operator.css
|
|
514
550
|
→ These are considered the same path ✓
|
|
515
551
|
```
|
|
516
552
|
|
|
517
|
-
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:
|
|
518
554
|
```
|
|
519
|
-
Local file:
|
|
555
|
+
Local file: bins/tpl/header.html
|
|
520
556
|
Server Path: tpl/header.html
|
|
521
557
|
→ The 'tpl/' directory is preserved ✓
|
|
522
558
|
```
|
|
523
559
|
|
|
524
560
|
**Why this matters:**
|
|
525
561
|
|
|
526
|
-
- When you `dbo push` files from `
|
|
527
|
-
- If your metadata `Path` column contains `assets/css/colors.css`, it will correctly match files in `
|
|
528
|
-
- 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
|
|
529
565
|
|
|
530
566
|
**Leading slash handling:**
|
|
531
567
|
|
|
532
568
|
The CLI handles leading `/` in metadata paths flexibly:
|
|
533
|
-
- `Path: assets/css/file.css` matches `
|
|
534
|
-
- `Path: /assets/css/file.css` also matches `
|
|
535
|
-
- `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` ✓
|
|
536
572
|
|
|
537
573
|
This ensures compatibility with various path formats from the server while maintaining correct local file organization.
|
|
538
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
|
+
|
|
539
601
|
---
|
|
540
602
|
|
|
541
603
|
### `dbo login`
|
|
@@ -747,8 +809,20 @@ The save-to-disk feature fetches data and persists records as local files. It us
|
|
|
747
809
|
4. **Extension** — use an entity column or specify manually
|
|
748
810
|
|
|
749
811
|
Each record produces:
|
|
750
|
-
- A content file: `[filename].[extension]`
|
|
751
|
-
- 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`).
|
|
752
826
|
|
|
753
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.
|
|
754
828
|
|
|
@@ -1133,29 +1207,29 @@ Remove a file or directory locally and stage server deletions for the next `dbo
|
|
|
1133
1207
|
|
|
1134
1208
|
```bash
|
|
1135
1209
|
# Remove a content file (finds companion .metadata.json automatically)
|
|
1136
|
-
dbo rm
|
|
1210
|
+
dbo rm bins/app/some-file.txt
|
|
1137
1211
|
|
|
1138
1212
|
# Remove by metadata file directly
|
|
1139
|
-
dbo rm
|
|
1213
|
+
dbo rm bins/app/some-file.metadata.json
|
|
1140
1214
|
|
|
1141
1215
|
# Skip confirmation prompt
|
|
1142
|
-
dbo rm -f
|
|
1216
|
+
dbo rm -f bins/app/some-file.txt
|
|
1143
1217
|
|
|
1144
1218
|
# Stage server deletion without deleting local files
|
|
1145
|
-
dbo rm --keep-local
|
|
1219
|
+
dbo rm --keep-local bins/app/some-file.txt
|
|
1146
1220
|
```
|
|
1147
1221
|
|
|
1148
1222
|
#### Directory
|
|
1149
1223
|
|
|
1150
1224
|
```bash
|
|
1151
1225
|
# Remove a directory and all its contents (prompts for approach)
|
|
1152
|
-
dbo rm
|
|
1226
|
+
dbo rm bins/app/ui/
|
|
1153
1227
|
|
|
1154
1228
|
# Remove directory without prompts
|
|
1155
|
-
dbo rm -f
|
|
1229
|
+
dbo rm -f bins/app/ui/
|
|
1156
1230
|
|
|
1157
1231
|
# Stage deletions without removing local files/directories
|
|
1158
|
-
dbo rm --keep-local
|
|
1232
|
+
dbo rm --keep-local bins/app/ui/
|
|
1159
1233
|
```
|
|
1160
1234
|
|
|
1161
1235
|
When removing a directory, the CLI:
|
|
@@ -1170,6 +1244,7 @@ When removing a directory, the CLI:
|
|
|
1170
1244
|
| `<path>` | File, `.metadata.json`, or directory to remove |
|
|
1171
1245
|
| `-f, --force` | Skip all confirmation prompts |
|
|
1172
1246
|
| `--keep-local` | Only stage server deletions, keep local files/directories |
|
|
1247
|
+
| `--hard` | Immediately delete local files (no `Trash/` move; legacy behavior) |
|
|
1173
1248
|
|
|
1174
1249
|
#### What rm does (single file)
|
|
1175
1250
|
|
|
@@ -1178,7 +1253,10 @@ When removing a directory, the CLI:
|
|
|
1178
1253
|
3. **Prompts for confirmation** — "Do you really want to remove this file and all of its nodes?"
|
|
1179
1254
|
4. **Stages deletion** — adds a delete entry to `.dbo/synchronize.json`
|
|
1180
1255
|
5. **Updates app.json** — removes the `@path/to/file.metadata.json` reference from children arrays
|
|
1181
|
-
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.
|
|
1182
1260
|
|
|
1183
1261
|
#### synchronize.json
|
|
1184
1262
|
|
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,7 +70,7 @@ 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
|
}
|
|
@@ -110,12 +113,58 @@ async function addSingleFile(filePath, client, options, batchDefaults) {
|
|
|
110
113
|
return await submitAdd(meta, metaPath, filePath, client, options);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
// 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
|
|
114
163
|
const docInfo = await detectDocumentationFile(filePath);
|
|
115
164
|
if (docInfo) {
|
|
116
165
|
const filenameCol = await loadDescriptorFilenamePreference('documentation') || 'Name';
|
|
117
166
|
const docBase = basename(filePath, extname(filePath));
|
|
118
|
-
const companionDir = join(process.cwd(), '
|
|
167
|
+
const companionDir = join(process.cwd(), 'extension', 'documentation');
|
|
119
168
|
await mkdir(companionDir, { recursive: true });
|
|
120
169
|
|
|
121
170
|
const docMeta = {
|
|
@@ -337,20 +386,75 @@ async function submitAdd(meta, metaPath, filePath, client, options) {
|
|
|
337
386
|
result = await client.postUrlEncoded('/api/input/submit', body);
|
|
338
387
|
}
|
|
339
388
|
|
|
340
|
-
formatResponse(result, { json: options.json, jq: options.jq });
|
|
389
|
+
formatResponse(result, { json: options.json, jq: options.jq, verbose: options.verbose });
|
|
341
390
|
|
|
342
391
|
if (!result.successful) {
|
|
343
392
|
throw new Error('Add failed');
|
|
344
393
|
}
|
|
345
394
|
|
|
346
|
-
// Extract UID from response and
|
|
395
|
+
// Extract UID from response and rename files to ~uid convention
|
|
347
396
|
const addResults = result.payload?.Results?.Add || result.data?.Payload?.Results?.Add || [];
|
|
348
397
|
if (addResults.length > 0) {
|
|
349
398
|
const returnedUID = addResults[0].UID;
|
|
399
|
+
const returnedLastUpdated = addResults[0]._LastUpdated;
|
|
400
|
+
|
|
350
401
|
if (returnedUID) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
458
|
log.dim(` Run "dbo pull -e ${entity} ${returnedUID}" to populate all columns`);
|
|
355
459
|
}
|
|
356
460
|
}
|