@dboio/cli 0.16.2 → 0.19.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.
- package/README.md +175 -138
- package/bin/dbo.js +2 -2
- package/package.json +1 -1
- package/plugins/claude/dbo/docs/dbo-cli-readme.md +175 -138
- package/src/commands/adopt.js +534 -0
- package/src/commands/build.js +3 -3
- package/src/commands/clone.js +209 -75
- package/src/commands/deploy.js +3 -3
- package/src/commands/init.js +11 -11
- package/src/commands/install.js +3 -3
- package/src/commands/login.js +2 -2
- package/src/commands/mv.js +15 -15
- package/src/commands/pull.js +1 -1
- package/src/commands/push.js +194 -15
- package/src/commands/rm.js +2 -2
- package/src/commands/run.js +4 -4
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +2 -2
- package/src/lib/config.js +186 -135
- package/src/lib/delta.js +119 -17
- package/src/lib/dependencies.js +51 -24
- package/src/lib/deploy-config.js +4 -4
- package/src/lib/domain-guard.js +8 -9
- package/src/lib/filenames.js +13 -2
- package/src/lib/ignore.js +2 -3
- package/src/{commands/add.js → lib/insert.js} +127 -472
- package/src/lib/metadata-schema.js +14 -20
- package/src/lib/metadata-templates.js +4 -4
- package/src/lib/migrations.js +1 -1
- package/src/lib/modify-key.js +1 -1
- package/src/lib/scaffold.js +5 -12
- package/src/lib/schema.js +67 -37
- package/src/lib/structure.js +6 -6
- package/src/lib/tagging.js +2 -2
- package/src/lib/ticketing.js +3 -7
- package/src/lib/toe-stepping.js +5 -5
- package/src/lib/transaction-key.js +1 -1
- package/src/migrations/004-rename-output-files.js +2 -2
- package/src/migrations/005-rename-output-metadata.js +2 -2
- package/src/migrations/006-remove-uid-companion-filenames.js +1 -1
- package/src/migrations/007-natural-entity-companion-filenames.js +1 -1
- package/src/migrations/008-metadata-uid-in-suffix.js +1 -1
- package/src/migrations/009-fix-media-collision-metadata-names.js +1 -1
- package/src/migrations/010-delete-paren-media-orphans.js +1 -1
- package/src/migrations/012-project-dir-restructure.js +211 -0
package/src/lib/delta.js
CHANGED
|
@@ -45,19 +45,16 @@ export function findBaselineEntry(baseline, entity, uid) {
|
|
|
45
45
|
if (found) return found;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
const found = nested.find(item => item.UID === uid);
|
|
59
|
-
if (found) return found;
|
|
60
|
-
}
|
|
48
|
+
// Search nested inside parent records' .children — server nests children
|
|
49
|
+
// per-parent (e.g. output→output_value, entity→entity_column).
|
|
50
|
+
for (const [parentEntity, parentArray] of Object.entries(baseline.children)) {
|
|
51
|
+
if (!Array.isArray(parentArray)) continue;
|
|
52
|
+
for (const parent of parentArray) {
|
|
53
|
+
if (!parent.children) continue;
|
|
54
|
+
const nested = parent.children[entity];
|
|
55
|
+
if (Array.isArray(nested)) {
|
|
56
|
+
const found = nested.find(item => item.UID === uid);
|
|
57
|
+
if (found) return found;
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
}
|
|
@@ -274,7 +271,7 @@ export function normalizeValue(value) {
|
|
|
274
271
|
|
|
275
272
|
// ─── Compound Output Delta Detection ────────────────────────────────────────
|
|
276
273
|
|
|
277
|
-
const
|
|
274
|
+
const _OUTPUT_CHILD_KEYS = ['output_value', 'output_value_filter', 'output_value_entity_column_rel'];
|
|
278
275
|
|
|
279
276
|
/**
|
|
280
277
|
* Detect changed columns across a compound output file (root + inline children).
|
|
@@ -344,10 +341,10 @@ const BIN_TRACKED_COLUMNS = ['Name', 'Path', 'ParentBinID', 'Active', 'Public'];
|
|
|
344
341
|
|
|
345
342
|
/**
|
|
346
343
|
* Detect changes between a current bin entry (from structure.json) and the
|
|
347
|
-
* baseline (from
|
|
344
|
+
* baseline (from baseline children.bin array).
|
|
348
345
|
*
|
|
349
346
|
* @param {Object} binEntry - Current bin entry from structure.json (with binId, name, path, etc.)
|
|
350
|
-
* @param {Object} baseline - The baseline JSON
|
|
347
|
+
* @param {Object} baseline - The baseline JSON
|
|
351
348
|
* @returns {string[]} - Array of changed column names
|
|
352
349
|
*/
|
|
353
350
|
export function detectBinChanges(binEntry, baseline) {
|
|
@@ -411,7 +408,7 @@ export function synthesizeBinMetadata(binEntry, appId) {
|
|
|
411
408
|
}
|
|
412
409
|
|
|
413
410
|
async function _walkChildrenForChanges(childrenObj, baseline, metaDir, result) {
|
|
414
|
-
for (const docKey of
|
|
411
|
+
for (const docKey of _OUTPUT_CHILD_KEYS) {
|
|
415
412
|
const entityArray = childrenObj[docKey];
|
|
416
413
|
if (!Array.isArray(entityArray) || entityArray.length === 0) continue;
|
|
417
414
|
for (const child of entityArray) {
|
|
@@ -424,3 +421,108 @@ async function _walkChildrenForChanges(childrenObj, baseline, metaDir, result) {
|
|
|
424
421
|
}
|
|
425
422
|
}
|
|
426
423
|
}
|
|
424
|
+
|
|
425
|
+
// ─── Generic Entity Children Delta Detection ─────────────────────────────────
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Detect changed, added, and removed child records in a metadata file with `children`.
|
|
429
|
+
* Applies to entity dir metadata (e.g. entity→entity_column, extension→...).
|
|
430
|
+
* Does NOT apply to output hierarchy — use detectOutputChanges() for those.
|
|
431
|
+
*
|
|
432
|
+
* @param {string} metaPath - Path to the parent metadata file
|
|
433
|
+
* @param {Object} baseline - Loaded baseline JSON
|
|
434
|
+
* @returns {Promise<Object>} - { [childUID]: { childEntity, changedColumns, isNew, isRemoved } }
|
|
435
|
+
*/
|
|
436
|
+
export async function detectEntityChildrenChanges(metaPath, baseline) {
|
|
437
|
+
const meta = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
438
|
+
const { children, UID: parentUid, _entity: parentEntity } = meta;
|
|
439
|
+
|
|
440
|
+
if (!children || typeof children !== 'object') return {};
|
|
441
|
+
|
|
442
|
+
const result = {};
|
|
443
|
+
|
|
444
|
+
for (const [childEntityName, childArray] of Object.entries(children)) {
|
|
445
|
+
if (!Array.isArray(childArray)) continue;
|
|
446
|
+
|
|
447
|
+
// Collect UIDs present in current metadata
|
|
448
|
+
const metaChildUIDs = new Set(childArray.map(c => c.UID).filter(Boolean));
|
|
449
|
+
|
|
450
|
+
// Find baseline children for this parent + child entity combo
|
|
451
|
+
const baselineChildren = findChildrenInBaseline(baseline, parentEntity, parentUid, childEntityName);
|
|
452
|
+
const baselineByUID = new Map(baselineChildren.map(c => [c.UID, c]));
|
|
453
|
+
|
|
454
|
+
// Detect new and edited children
|
|
455
|
+
for (const child of childArray) {
|
|
456
|
+
const uid = child.UID;
|
|
457
|
+
if (!uid) continue;
|
|
458
|
+
const baselineChild = baselineByUID.get(uid);
|
|
459
|
+
if (!baselineChild) {
|
|
460
|
+
result[uid] = { childEntity: childEntityName, changedColumns: getAllUserColumns(child), isNew: true };
|
|
461
|
+
} else {
|
|
462
|
+
const changed = _compareChildToBaseline(child, baselineChild);
|
|
463
|
+
if (changed.length > 0) {
|
|
464
|
+
result[uid] = { childEntity: childEntityName, changedColumns: changed, isNew: false };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Detect removed children (in baseline but not in metadata)
|
|
470
|
+
for (const [uid] of baselineByUID.entries()) {
|
|
471
|
+
if (!metaChildUIDs.has(uid)) {
|
|
472
|
+
result[uid] = { childEntity: childEntityName, changedColumns: [], isRemoved: true };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Find children in the baseline for a given parent record + child entity name.
|
|
482
|
+
* Checks the parent's nested children first; falls back to top-level baseline array.
|
|
483
|
+
*
|
|
484
|
+
* @param {Object} baseline - Loaded baseline JSON
|
|
485
|
+
* @param {string} parentEntity - e.g. 'entity'
|
|
486
|
+
* @param {string} parentUid - UID of the parent record
|
|
487
|
+
* @param {string} childEntityName - e.g. 'entity_column'
|
|
488
|
+
* @returns {Array} - Array of baseline child records
|
|
489
|
+
*/
|
|
490
|
+
export function findChildrenInBaseline(baseline, parentEntity, parentUid, childEntityName) {
|
|
491
|
+
if (!baseline || !baseline.children) return [];
|
|
492
|
+
|
|
493
|
+
// First: try nested inside the parent entity record
|
|
494
|
+
const parentArray = baseline.children[parentEntity];
|
|
495
|
+
if (Array.isArray(parentArray)) {
|
|
496
|
+
const parentRecord = parentArray.find(p => p.UID === parentUid);
|
|
497
|
+
if (parentRecord?.children && Array.isArray(parentRecord.children[childEntityName])) {
|
|
498
|
+
return parentRecord.children[childEntityName];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Fallback: top-level baseline array for the child entity (less common)
|
|
503
|
+
const topLevel = baseline.children[childEntityName];
|
|
504
|
+
return Array.isArray(topLevel) ? topLevel : [];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Compare a child record's fields against its baseline entry.
|
|
509
|
+
* Returns names of changed columns (excludes system columns and 'children').
|
|
510
|
+
*/
|
|
511
|
+
function _compareChildToBaseline(child, baselineChild) {
|
|
512
|
+
const changed = [];
|
|
513
|
+
for (const [col, val] of Object.entries(child)) {
|
|
514
|
+
if (shouldSkipColumn(col) || col === 'children') continue;
|
|
515
|
+
if (normalizeValue(val) !== normalizeValue(baselineChild[col])) {
|
|
516
|
+
changed.push(col);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Columns removed from metadata but present in baseline
|
|
520
|
+
for (const [col, baselineVal] of Object.entries(baselineChild)) {
|
|
521
|
+
if (shouldSkipColumn(col) || col === 'children') continue;
|
|
522
|
+
if (col in child) continue;
|
|
523
|
+
if (baselineVal !== null && baselineVal !== undefined && normalizeValue(baselineVal) !== '') {
|
|
524
|
+
changed.push(col);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return changed;
|
|
528
|
+
}
|
package/src/lib/dependencies.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency management for entity synchronization and app dependency cloning.
|
|
3
3
|
* - Entity ordering: ensures children are processed before parents for referential integrity.
|
|
4
|
-
* - App dependencies: auto-clone related apps into
|
|
4
|
+
* - App dependencies: auto-clone related apps into app_dependencies/<shortname>/.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
|
-
import { mkdir, symlink, access, readFile, writeFile } from 'fs/promises';
|
|
8
|
+
import { mkdir, symlink, access, readFile, readdir, writeFile, rm } from 'fs/promises';
|
|
9
9
|
import { join, resolve, relative, sep } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { DboClient } from './client.js';
|
|
@@ -219,7 +219,7 @@ export async function checkDependencyStaleness(shortname, options = {}) {
|
|
|
219
219
|
|
|
220
220
|
const dateStr = stored.substring(0, 10); // YYYY-MM-DD from ISO string
|
|
221
221
|
const result = await client.get(
|
|
222
|
-
`/api/app/object/${encodeURIComponent(shortname)}
|
|
222
|
+
`/api/app/object/${encodeURIComponent(shortname)}?UpdatedAfter=${dateStr}`
|
|
223
223
|
);
|
|
224
224
|
if (!result.ok || !result.data) return false; // Can't determine — assume fresh
|
|
225
225
|
|
|
@@ -229,7 +229,7 @@ export async function checkDependencyStaleness(shortname, options = {}) {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
|
-
* Sync dependency apps into
|
|
232
|
+
* Sync dependency apps into app_dependencies/<shortname>/.
|
|
233
233
|
*
|
|
234
234
|
* @param {object} options
|
|
235
235
|
* @param {string} [options.domain] - Override domain
|
|
@@ -243,7 +243,7 @@ export async function checkDependencyStaleness(shortname, options = {}) {
|
|
|
243
243
|
export async function syncDependencies(options = {}) {
|
|
244
244
|
// Recursive guard: don't run inside a checkout directory
|
|
245
245
|
const cwd = process.cwd();
|
|
246
|
-
if (cwd.includes(`${sep}
|
|
246
|
+
if (cwd.includes(`${sep}app_dependencies${sep}`)) {
|
|
247
247
|
log.dim(' Skipping dependency sync (inside a checkout directory)');
|
|
248
248
|
return;
|
|
249
249
|
}
|
|
@@ -252,8 +252,8 @@ export async function syncDependencies(options = {}) {
|
|
|
252
252
|
? [...new Set(['_system', ...options.only])]
|
|
253
253
|
: await getDependencies();
|
|
254
254
|
|
|
255
|
-
const
|
|
256
|
-
const depsRoot = join(
|
|
255
|
+
const parentProjectDir = join(cwd, '.app');
|
|
256
|
+
const depsRoot = join(cwd, 'app_dependencies');
|
|
257
257
|
|
|
258
258
|
const forceAll = !!(options.force || options.schema);
|
|
259
259
|
const execFn = options._execOverride || execDboInDir;
|
|
@@ -271,12 +271,12 @@ export async function syncDependencies(options = {}) {
|
|
|
271
271
|
spinner.update(`Syncing dependency: ${shortname}`);
|
|
272
272
|
|
|
273
273
|
const checkoutDir = join(depsRoot, shortname);
|
|
274
|
-
const
|
|
274
|
+
const checkoutProjectDir = join(checkoutDir, '.app');
|
|
275
275
|
|
|
276
276
|
try {
|
|
277
277
|
// 1. Create checkout dir + minimal config
|
|
278
|
-
await mkdir(
|
|
279
|
-
const minConfigPath = join(
|
|
278
|
+
await mkdir(checkoutProjectDir, { recursive: true });
|
|
279
|
+
const minConfigPath = join(checkoutProjectDir, 'config.json');
|
|
280
280
|
let configExists = false;
|
|
281
281
|
try { await access(minConfigPath); configExists = true; } catch {}
|
|
282
282
|
if (!configExists) {
|
|
@@ -286,20 +286,31 @@ export async function syncDependencies(options = {}) {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
// 2. Symlink credentials
|
|
289
|
-
await symlinkCredentials(
|
|
289
|
+
await symlinkCredentials(parentProjectDir, checkoutProjectDir);
|
|
290
290
|
|
|
291
291
|
// 3. Staleness check (unless --force or --schema)
|
|
292
292
|
if (!forceAll) {
|
|
293
|
-
|
|
293
|
+
// Also check if the checkout is essentially empty (only .app/ exists) —
|
|
294
|
+
// a previous clone may have failed or been cleaned up, leaving just config
|
|
295
|
+
let checkoutEmpty = true;
|
|
294
296
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (!
|
|
300
|
-
|
|
301
|
-
|
|
297
|
+
const entries = await readdir(checkoutDir);
|
|
298
|
+
checkoutEmpty = entries.every(e => e === '.app' || e.startsWith('.'));
|
|
299
|
+
} catch { /* dir doesn't exist yet — treat as empty */ }
|
|
300
|
+
|
|
301
|
+
if (!checkoutEmpty) {
|
|
302
|
+
let isStale = true;
|
|
303
|
+
try {
|
|
304
|
+
isStale = await checkDependencyStaleness(shortname, options);
|
|
305
|
+
} catch {
|
|
306
|
+
// Network unavailable — assume stale to attempt clone
|
|
307
|
+
}
|
|
308
|
+
if (!isStale) {
|
|
309
|
+
skipped.push(shortname);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
302
312
|
}
|
|
313
|
+
// If checkout is empty, always re-clone regardless of timestamp
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
// 4. Run the clone (quiet — suppress child process output)
|
|
@@ -310,18 +321,34 @@ export async function syncDependencies(options = {}) {
|
|
|
310
321
|
await execFn(checkoutDir, ['clone', '--app', shortname, '--force', '--yes', '--no-deps'], { quiet: true });
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
// 5. Read _LastUpdated from checkout's app.json and persist
|
|
324
|
+
// 5. Read _LastUpdated from checkout's .app/<shortname>.metadata.json and persist
|
|
314
325
|
try {
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
const ts =
|
|
326
|
+
const metaFile = join(checkoutDir, '.app', `${shortname}.metadata.json`);
|
|
327
|
+
const appMeta = JSON.parse(await readFile(metaFile, 'utf8'));
|
|
328
|
+
const ts = appMeta._LastUpdated || appMeta.LastUpdated || null;
|
|
318
329
|
if (ts) await setDependencyLastUpdated(shortname, ts);
|
|
319
330
|
} catch { /* can't read _LastUpdated — that's OK */ }
|
|
320
331
|
|
|
332
|
+
// 6. Clean up legacy and unnecessary files from dependency checkouts.
|
|
333
|
+
// - Legacy .dbo/ layout from pre-restructure clones
|
|
334
|
+
// - Root app.json/schema.json (now inside .app/)
|
|
335
|
+
// - package.json, manifest.json, .gitignore (not needed for deps)
|
|
336
|
+
// - src/, test/, trash/ (deps are not development projects)
|
|
337
|
+
try {
|
|
338
|
+
const legacyDbo = join(checkoutDir, '.dbo');
|
|
339
|
+
try { await access(legacyDbo); await rm(legacyDbo, { recursive: true }); } catch {}
|
|
340
|
+
for (const f of ['app.json', 'schema.json', 'package.json', 'manifest.json', '.gitignore']) {
|
|
341
|
+
try { await access(join(checkoutDir, f)); await rm(join(checkoutDir, f)); } catch {}
|
|
342
|
+
}
|
|
343
|
+
for (const d of ['src', 'test', 'trash']) {
|
|
344
|
+
try { await access(join(checkoutDir, d)); await rm(join(checkoutDir, d), { recursive: true }); } catch {}
|
|
345
|
+
}
|
|
346
|
+
} catch { /* ignore cleanup errors */ }
|
|
347
|
+
|
|
321
348
|
synced.push(shortname);
|
|
322
349
|
} catch (err) {
|
|
323
350
|
failed.push(shortname);
|
|
324
|
-
|
|
351
|
+
log.warn(` Dependency "${shortname}" failed: ${err.message}`);
|
|
325
352
|
}
|
|
326
353
|
}
|
|
327
354
|
|
package/src/lib/deploy-config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
|
2
2
|
import { join, relative, extname, basename } from 'path';
|
|
3
3
|
import { log } from './logger.js';
|
|
4
4
|
|
|
5
|
-
const DEPLOY_CONFIG_FILE = '.
|
|
5
|
+
const DEPLOY_CONFIG_FILE = '.app/deploy_config.json';
|
|
6
6
|
|
|
7
7
|
function deployConfigPath() {
|
|
8
8
|
return join(process.cwd(), DEPLOY_CONFIG_FILE);
|
|
@@ -13,7 +13,7 @@ function sortedKeys(obj) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Load .
|
|
16
|
+
* Load .app/deploy_config.json. Returns { deployments: {} } if missing.
|
|
17
17
|
* Throws on malformed JSON (do not silently recreate — would lose existing entries).
|
|
18
18
|
*/
|
|
19
19
|
export async function loadDeployConfig() {
|
|
@@ -27,10 +27,10 @@ export async function loadDeployConfig() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Write .
|
|
30
|
+
* Write .app/deploy_config.json with alphabetically sorted deployment keys.
|
|
31
31
|
*/
|
|
32
32
|
export async function saveDeployConfig(config) {
|
|
33
|
-
await mkdir(join(process.cwd(), '.
|
|
33
|
+
await mkdir(join(process.cwd(), '.app'), { recursive: true });
|
|
34
34
|
const sorted = { ...config, deployments: sortedKeys(config.deployments || {}) };
|
|
35
35
|
await writeFile(deployConfigPath(), JSON.stringify(sorted, null, 2) + '\n');
|
|
36
36
|
}
|
package/src/lib/domain-guard.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { readFile, writeFile, stat, utimes } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { loadConfig, loadTransactionKeyPreset } from './config.js';
|
|
3
|
+
import { loadConfig, loadTransactionKeyPreset, appMetadataPath } from './config.js';
|
|
4
4
|
import { log } from './logger.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Read _domain from app.json. Returns null if file missing or field absent. */
|
|
6
|
+
/** Read _domain from the metadata file. Returns null if file missing or field absent. */
|
|
9
7
|
export async function readAppJsonDomain() {
|
|
10
8
|
try {
|
|
11
|
-
const
|
|
9
|
+
const path = await appMetadataPath();
|
|
10
|
+
const obj = JSON.parse(await readFile(path, 'utf8'));
|
|
12
11
|
return obj._domain || null;
|
|
13
12
|
} catch { return null; }
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
|
-
* Write _domain to
|
|
18
|
-
* No-op if
|
|
16
|
+
* Write _domain to the metadata file, preserving mtime so git doesn't see a spurious change.
|
|
17
|
+
* No-op if the metadata file does not exist.
|
|
19
18
|
*/
|
|
20
19
|
export async function writeAppJsonDomain(domain) {
|
|
21
|
-
const path =
|
|
20
|
+
const path = await appMetadataPath();
|
|
22
21
|
let originalStat;
|
|
23
22
|
try { originalStat = await stat(path); } catch { return; }
|
|
24
23
|
const obj = JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -29,7 +28,7 @@ export async function writeAppJsonDomain(domain) {
|
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Check whether newDomain differs from the project reference domain.
|
|
32
|
-
* Reference =
|
|
31
|
+
* Reference = metadata._domain (authoritative) → fallback: config.json.domain.
|
|
33
32
|
* If reference is absent entirely, returns { changed: false } (no warning).
|
|
34
33
|
*
|
|
35
34
|
* Returns { changed: false } or { changed: true, proceed: bool }.
|
package/src/lib/filenames.js
CHANGED
|
@@ -131,7 +131,18 @@ export function detectLegacyDotUid(filename) {
|
|
|
131
131
|
* @returns {string}
|
|
132
132
|
*/
|
|
133
133
|
export function buildMetaFilename(naturalBase, uid) {
|
|
134
|
-
|
|
134
|
+
// Guard: strip any trailing .metadata suffix(es) and ~uid fragments from naturalBase
|
|
135
|
+
// to prevent double-metadata filenames (e.g., "app.metadata.metadata~app.json")
|
|
136
|
+
let base = naturalBase;
|
|
137
|
+
const metaParsed = parseMetaFilename(base + '.json');
|
|
138
|
+
if (metaParsed) {
|
|
139
|
+
base = metaParsed.naturalBase;
|
|
140
|
+
}
|
|
141
|
+
// Strip all trailing .metadata (handles single, double, triple, etc.)
|
|
142
|
+
while (base.endsWith('.metadata')) {
|
|
143
|
+
base = base.substring(0, base.length - 9);
|
|
144
|
+
}
|
|
145
|
+
return `${base}.metadata~${uid}.json`;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
/**
|
|
@@ -251,7 +262,7 @@ export async function renameToUidConvention(meta, metaPath, uid, lastUpdated, se
|
|
|
251
262
|
}
|
|
252
263
|
|
|
253
264
|
// Determine naturalBase from the temp/old metadata filename
|
|
254
|
-
// Temp format from
|
|
265
|
+
// Temp format from adopt.js: "colors.metadata.json" → naturalBase = "colors"
|
|
255
266
|
// Old tilde format: "colors~uid.metadata.json" → naturalBase = "colors"
|
|
256
267
|
let naturalBase;
|
|
257
268
|
const legacyParsed = detectLegacyTildeMetadata(metaFilename);
|
package/src/lib/ignore.js
CHANGED
|
@@ -12,11 +12,10 @@ const DEFAULT_FILE_CONTENT = `# DBO CLI ignore patterns
|
|
|
12
12
|
# (gitignore-style syntax — works like .gitignore)
|
|
13
13
|
|
|
14
14
|
# DBO internal
|
|
15
|
-
.
|
|
15
|
+
.app/
|
|
16
16
|
.dboignore
|
|
17
17
|
*.dboio.json
|
|
18
|
-
|
|
19
|
-
.dbo/dependencies/
|
|
18
|
+
app_dependencies/
|
|
20
19
|
|
|
21
20
|
# Editor / IDE / OS
|
|
22
21
|
.DS_Store
|