@dboio/cli 0.15.0 → 0.15.1
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/package.json +1 -1
- package/src/commands/push.js +66 -94
package/package.json
CHANGED
package/src/commands/push.js
CHANGED
|
@@ -20,7 +20,7 @@ import { BINS_DIR, ENTITY_DIR_NAMES, loadStructureFile, findBinByPath } from '..
|
|
|
20
20
|
import { ensureTrashIcon, setFileTag } from '../lib/tagging.js';
|
|
21
21
|
import { checkToeStepping } from '../lib/toe-stepping.js';
|
|
22
22
|
import { runPendingMigrations } from '../lib/migrations.js';
|
|
23
|
-
import { findUnaddedFiles, detectBinFile, submitAdd } from './add.js';
|
|
23
|
+
// AUTO-ADD DISABLED: import { findUnaddedFiles, detectBinFile, submitAdd } from './add.js';
|
|
24
24
|
|
|
25
25
|
function _getMetaCompanionPaths(meta, metaPath) {
|
|
26
26
|
const dir = dirname(metaPath);
|
|
@@ -96,7 +96,7 @@ export const pushCommand = new Command('push')
|
|
|
96
96
|
const transactionKey = await resolveTransactionKey(options);
|
|
97
97
|
|
|
98
98
|
// Process pending deletions from synchronize.json
|
|
99
|
-
await processPendingDeletes(client, options, modifyKey, transactionKey);
|
|
99
|
+
const deletedCount = await processPendingDeletes(client, options, modifyKey, transactionKey) || 0;
|
|
100
100
|
|
|
101
101
|
// ── Resolution order ──────────────────────────────────────────
|
|
102
102
|
// 1. Commas → UID list
|
|
@@ -135,7 +135,7 @@ export const pushCommand = new Command('push')
|
|
|
135
135
|
log.dim(` Found: ${relative(process.cwd(), resolved)}`);
|
|
136
136
|
const resolvedStat = await stat(resolved);
|
|
137
137
|
if (resolvedStat.isDirectory()) {
|
|
138
|
-
await pushDirectory(resolved, client, options, modifyKey, transactionKey);
|
|
138
|
+
await pushDirectory(resolved, client, options, modifyKey, transactionKey, deletedCount);
|
|
139
139
|
} else {
|
|
140
140
|
await pushSingleFile(resolved, client, options, modifyKey, transactionKey);
|
|
141
141
|
}
|
|
@@ -156,7 +156,7 @@ export const pushCommand = new Command('push')
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
if (pathStat.isDirectory()) {
|
|
159
|
-
await pushDirectory(targetPath, client, options, modifyKey, transactionKey);
|
|
159
|
+
await pushDirectory(targetPath, client, options, modifyKey, transactionKey, deletedCount);
|
|
160
160
|
} else {
|
|
161
161
|
await pushSingleFile(targetPath, client, options, modifyKey, transactionKey);
|
|
162
162
|
}
|
|
@@ -171,18 +171,27 @@ export const pushCommand = new Command('push')
|
|
|
171
171
|
*/
|
|
172
172
|
async function processPendingDeletes(client, options, modifyKey = null, transactionKey = 'RowUID') {
|
|
173
173
|
const sync = await loadSynchronize();
|
|
174
|
-
if (!sync.delete || sync.delete.length === 0) return;
|
|
174
|
+
if (!sync.delete || sync.delete.length === 0) return 0;
|
|
175
175
|
|
|
176
176
|
log.info(`Processing ${sync.delete.length} pending deletion(s)...`);
|
|
177
177
|
|
|
178
178
|
const remaining = [];
|
|
179
179
|
const deletedUids = [];
|
|
180
180
|
|
|
181
|
+
// Load stored ticket once for all deletions (same as main push loop)
|
|
182
|
+
const { getGlobalTicket, getRecordTicket } = await import('../lib/ticketing.js');
|
|
183
|
+
const globalTicket = !options.ticket ? await getGlobalTicket() : null;
|
|
184
|
+
|
|
181
185
|
for (const entry of sync.delete) {
|
|
182
186
|
log.info(`Deleting "${entry.name}" (${entry.entity}:${entry.RowID})`);
|
|
183
187
|
|
|
184
188
|
const extraParams = { '_confirm': options.confirm || 'true' };
|
|
185
|
-
if (options.ticket)
|
|
189
|
+
if (options.ticket) {
|
|
190
|
+
extraParams['_OverrideTicketID'] = options.ticket;
|
|
191
|
+
} else {
|
|
192
|
+
const ticket = await getRecordTicket(entry.UID) || globalTicket;
|
|
193
|
+
if (ticket) extraParams['_OverrideTicketID'] = ticket;
|
|
194
|
+
}
|
|
186
195
|
if (modifyKey) extraParams['_modify_key'] = modifyKey;
|
|
187
196
|
const cachedUser2 = getSessionUserOverride();
|
|
188
197
|
if (cachedUser2) extraParams['_OverrideUserID'] = cachedUser2;
|
|
@@ -244,6 +253,8 @@ async function processPendingDeletes(client, options, modifyKey = null, transact
|
|
|
244
253
|
if (remaining.length > 0) {
|
|
245
254
|
log.warn(`${remaining.length} deletion(s) failed and remain staged.`);
|
|
246
255
|
}
|
|
256
|
+
|
|
257
|
+
return deletedUids.length;
|
|
247
258
|
}
|
|
248
259
|
|
|
249
260
|
/**
|
|
@@ -343,27 +354,12 @@ async function pushSingleFile(filePath, client, options, modifyKey = null, trans
|
|
|
343
354
|
try { meta = JSON.parse(await readFile(metaPath, 'utf8')); } catch {}
|
|
344
355
|
}
|
|
345
356
|
if (!meta) {
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// After successful add, re-read the metadata (now has UID)
|
|
353
|
-
metaPath = binMeta.metaPath;
|
|
354
|
-
// The metadata file may have been renamed with ~UID, so scan for it
|
|
355
|
-
const updatedMeta = await findMetadataForCompanion(filePath);
|
|
356
|
-
if (updatedMeta) metaPath = updatedMeta;
|
|
357
|
-
meta = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
358
|
-
log.info(`Successfully added — now pushing updates`);
|
|
359
|
-
} catch (err) {
|
|
360
|
-
log.error(`Auto-add failed for "${basename(filePath)}": ${err.message}`);
|
|
361
|
-
process.exit(1);
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
log.error(`No metadata found for "${basename(filePath)}". Pull the record first with "dbo pull".`);
|
|
365
|
-
process.exit(1);
|
|
366
|
-
}
|
|
357
|
+
// AUTO-ADD DISABLED: local-file-first approach is being replaced by server-first workflow.
|
|
358
|
+
// New records should be created via the DBO REST API first, then `dbo pull` to populate locally.
|
|
359
|
+
// The auto-add code (detectBinFile + submitAdd) has been commented out intentionally.
|
|
360
|
+
// See: .claude/2_specs/auto-deploy-config-generation.md — "server-first approach"
|
|
361
|
+
log.error(`No metadata found for "${basename(filePath)}". Create the record on the server first, then run "dbo pull".`);
|
|
362
|
+
process.exit(1);
|
|
367
363
|
}
|
|
368
364
|
}
|
|
369
365
|
|
|
@@ -473,61 +469,16 @@ async function ensureManifestMetadata() {
|
|
|
473
469
|
/**
|
|
474
470
|
* Push all records found in a directory (recursive)
|
|
475
471
|
*/
|
|
476
|
-
async function pushDirectory(dirPath, client, options, modifyKey = null, transactionKey = 'RowUID') {
|
|
472
|
+
async function pushDirectory(dirPath, client, options, modifyKey = null, transactionKey = 'RowUID', deletedCount = 0) {
|
|
477
473
|
// Auto-create manifest.metadata.json if manifest.json exists at root without companion metadata
|
|
478
474
|
await ensureManifestMetadata();
|
|
479
475
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const unadded = await findUnaddedFiles(dirPath, ig);
|
|
485
|
-
if (unadded.length > 0) {
|
|
486
|
-
// Filter to files that detectBinFile can auto-classify (content/media in bins)
|
|
487
|
-
const autoAddable = [];
|
|
488
|
-
for (const filePath of unadded) {
|
|
489
|
-
const binMeta = await detectBinFile(filePath);
|
|
490
|
-
if (binMeta) autoAddable.push({ filePath, ...binMeta });
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (autoAddable.length > 0) {
|
|
494
|
-
log.info(`Found ${autoAddable.length} new file(s) to add before push:`);
|
|
495
|
-
for (const { filePath } of autoAddable) {
|
|
496
|
-
log.plain(` ${relative(process.cwd(), filePath)}`);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const doAdd = async () => {
|
|
500
|
-
for (const { meta, metaPath, filePath } of autoAddable) {
|
|
501
|
-
try {
|
|
502
|
-
await submitAdd(meta, metaPath, filePath, client, options);
|
|
503
|
-
// After submitAdd, meta.UID is set if successful
|
|
504
|
-
if (meta.UID) justAddedUIDs.add(meta.UID);
|
|
505
|
-
} catch (err) {
|
|
506
|
-
log.error(`Failed to add ${relative(process.cwd(), filePath)}: ${err.message}`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
if (!options.yes) {
|
|
512
|
-
const inquirer = (await import('inquirer')).default;
|
|
513
|
-
const { proceed } = await inquirer.prompt([{
|
|
514
|
-
type: 'confirm',
|
|
515
|
-
name: 'proceed',
|
|
516
|
-
message: `Add ${autoAddable.length} file(s) to the server before pushing?`,
|
|
517
|
-
default: true,
|
|
518
|
-
}]);
|
|
519
|
-
if (!proceed) {
|
|
520
|
-
log.dim('Skipping auto-add — continuing with push');
|
|
521
|
-
} else {
|
|
522
|
-
await doAdd();
|
|
523
|
-
}
|
|
524
|
-
} else {
|
|
525
|
-
await doAdd();
|
|
526
|
-
}
|
|
527
|
-
if (justAddedUIDs.size > 0) log.plain('');
|
|
528
|
-
}
|
|
529
|
-
}
|
|
476
|
+
// AUTO-ADD DISABLED: local-file-first approach is being replaced by server-first workflow.
|
|
477
|
+
// New records should be created via the DBO REST API first, then `dbo pull` to populate locally.
|
|
478
|
+
// The auto-add code (findUnaddedFiles + detectBinFile + submitAdd) has been commented out.
|
|
479
|
+
// See: .claude/2_specs/auto-deploy-config-generation.md — "server-first approach"
|
|
530
480
|
|
|
481
|
+
const ig = await loadIgnore();
|
|
531
482
|
const metaFiles = await findMetadataFiles(dirPath, ig);
|
|
532
483
|
|
|
533
484
|
// ── Load scripts config early (before delta detection) ──────────────
|
|
@@ -614,16 +565,16 @@ async function pushDirectory(dirPath, client, options, modifyKey = null, transac
|
|
|
614
565
|
continue;
|
|
615
566
|
}
|
|
616
567
|
|
|
617
|
-
//
|
|
618
|
-
if (meta.UID && justAddedUIDs.has(meta.UID)) {
|
|
619
|
-
log.dim(` Skipped (just added): ${basename(metaPath)}`);
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
568
|
+
// AUTO-ADD DISABLED: justAddedUIDs skip removed (server-first workflow)
|
|
622
569
|
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
570
|
+
// Output hierarchy entities: only push compound output files (root with inline children).
|
|
571
|
+
// Flat output metadata without .children are skipped — they are pushed as part of
|
|
572
|
+
// their parent compound file, or via `dbo deploy`. Never as standalone records.
|
|
573
|
+
if (meta._entity === 'output' || meta._entity === 'output_value'
|
|
574
|
+
|| meta._entity === 'output_value_filter' || meta._entity === 'output_value_entity_column_rel') {
|
|
575
|
+
if (meta._entity === 'output' && meta.children) {
|
|
576
|
+
outputCompoundFiles.push({ meta, metaPath });
|
|
577
|
+
}
|
|
627
578
|
continue;
|
|
628
579
|
}
|
|
629
580
|
|
|
@@ -719,23 +670,44 @@ async function pushDirectory(dirPath, client, options, modifyKey = null, transac
|
|
|
719
670
|
}
|
|
720
671
|
} catch { /* structure file missing or bin lookup failed — skip */ }
|
|
721
672
|
|
|
722
|
-
|
|
723
|
-
|
|
673
|
+
// Pre-filter compound output files: run delta detection early so unchanged outputs
|
|
674
|
+
// are excluded from the record count and ticket prompt (avoids false-positive prompts).
|
|
675
|
+
const outputsWithChanges = [];
|
|
676
|
+
for (const item of outputCompoundFiles) {
|
|
677
|
+
if (baseline) {
|
|
678
|
+
try {
|
|
679
|
+
const delta = await detectOutputChanges(item.metaPath, baseline);
|
|
680
|
+
const totalChanges = delta.root.length +
|
|
681
|
+
(delta.children ? Object.values(delta.children).reduce((s, c) => s + c.length, 0) : 999);
|
|
682
|
+
if (totalChanges === 0) {
|
|
683
|
+
log.dim(` Skipping ${basename(item.metaPath)} — no changes detected`);
|
|
684
|
+
skipped++;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
} catch { /* delta detection failed — include in push for safety */ }
|
|
688
|
+
}
|
|
689
|
+
outputsWithChanges.push(item);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (toPush.length === 0 && outputsWithChanges.length === 0 && binPushItems.length === 0) {
|
|
693
|
+
if (metaFiles.length === 0 && deletedCount === 0) {
|
|
724
694
|
log.warn(`No .metadata.json files found in "${dirPath}".`);
|
|
695
|
+
} else if (deletedCount > 0) {
|
|
696
|
+
log.info(`${deletedCount} deletion(s) processed. No other changes to push.`);
|
|
725
697
|
} else {
|
|
726
698
|
log.info('No changes to push');
|
|
727
699
|
}
|
|
728
700
|
return;
|
|
729
701
|
}
|
|
730
702
|
|
|
731
|
-
log.info(`Found ${
|
|
703
|
+
log.info(`Found ${toPush.length + outputsWithChanges.length + binPushItems.length} record(s) to push`);
|
|
732
704
|
|
|
733
705
|
// Pre-flight ticket validation (only if no --ticket flag)
|
|
734
|
-
const totalRecords = toPush.length +
|
|
706
|
+
const totalRecords = toPush.length + outputsWithChanges.length + binPushItems.length;
|
|
735
707
|
if (!options.ticket && totalRecords > 0) {
|
|
736
708
|
const recordSummary = [
|
|
737
709
|
...toPush.map(r => { const p = parseMetaFilename(basename(r.metaPath)); return p ? p.naturalBase : basename(r.metaPath, '.metadata.json'); }),
|
|
738
|
-
...
|
|
710
|
+
...outputsWithChanges.map(r => basename(r.metaPath, '.json')),
|
|
739
711
|
...binPushItems.map(r => `bin:${r.meta.Name}`),
|
|
740
712
|
].join(', ');
|
|
741
713
|
const ticketCheck = await checkStoredTicket(options, `${totalRecords} record(s): ${recordSummary}`);
|
|
@@ -835,8 +807,8 @@ async function pushDirectory(dirPath, client, options, modifyKey = null, transac
|
|
|
835
807
|
}
|
|
836
808
|
}
|
|
837
809
|
|
|
838
|
-
// Process compound output files (root + inline children)
|
|
839
|
-
for (const { meta, metaPath } of
|
|
810
|
+
// Process compound output files (root + inline children) — already pre-filtered for changes
|
|
811
|
+
for (const { meta, metaPath } of outputsWithChanges) {
|
|
840
812
|
try {
|
|
841
813
|
const result = await pushOutputCompound(meta, metaPath, client, options, baseline, modifyKey, transactionKey);
|
|
842
814
|
if (result.pushed > 0) {
|