@dboio/cli 0.11.4 → 0.15.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 +183 -3
- package/bin/dbo.js +6 -0
- package/package.json +1 -1
- package/plugins/claude/dbo/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/dbo/commands/dbo.md +66 -243
- package/plugins/claude/dbo/docs/_audit_required/API/all.md +40 -0
- package/plugins/claude/dbo/docs/_audit_required/API/app.md +38 -0
- package/plugins/claude/dbo/docs/_audit_required/API/athenticate.md +26 -0
- package/plugins/claude/dbo/docs/_audit_required/API/cache.md +29 -0
- package/plugins/claude/dbo/docs/_audit_required/API/content.md +14 -0
- package/plugins/claude/dbo/docs/_audit_required/API/data_source.md +28 -0
- package/plugins/claude/dbo/docs/_audit_required/API/email.md +18 -0
- package/plugins/claude/dbo/docs/_audit_required/API/input.md +25 -0
- package/plugins/claude/dbo/docs/_audit_required/API/instance.md +28 -0
- package/plugins/claude/dbo/docs/_audit_required/API/log.md +8 -0
- package/plugins/claude/dbo/docs/_audit_required/API/media.md +12 -0
- package/plugins/claude/dbo/docs/_audit_required/API/output_by_entity.md +12 -0
- package/plugins/claude/dbo/docs/_audit_required/API/upload.md +7 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-api-syntax.md +1487 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-code.md +111 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-performance.md +109 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-syntax.md +97 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-product-market.md +119 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-white-paper.md +125 -0
- package/plugins/claude/dbo/docs/dbo-cheat-sheet.md +323 -0
- package/plugins/claude/dbo/docs/dbo-cli-readme.md +2279 -0
- package/plugins/claude/dbo/docs/dbo-core-entities.md +878 -0
- package/plugins/claude/dbo/docs/dbo-output-customsql.md +677 -0
- package/plugins/claude/dbo/docs/dbo-output-query.md +967 -0
- package/plugins/claude/dbo/skills/cli/SKILL.md +63 -246
- package/src/commands/add.js +373 -64
- package/src/commands/build.js +102 -0
- package/src/commands/clone.js +719 -212
- package/src/commands/deploy.js +9 -2
- package/src/commands/diff.js +7 -3
- package/src/commands/init.js +16 -2
- package/src/commands/input.js +3 -1
- package/src/commands/login.js +30 -4
- package/src/commands/mv.js +28 -7
- package/src/commands/push.js +298 -78
- package/src/commands/rm.js +21 -6
- package/src/commands/run.js +81 -0
- package/src/commands/tag.js +65 -0
- package/src/lib/config.js +67 -0
- package/src/lib/delta.js +7 -1
- package/src/lib/deploy-config.js +137 -0
- package/src/lib/diff.js +28 -5
- package/src/lib/filenames.js +198 -54
- package/src/lib/ignore.js +6 -0
- package/src/lib/input-parser.js +13 -4
- package/src/lib/scaffold.js +1 -1
- package/src/lib/scripts.js +232 -0
- package/src/lib/tagging.js +380 -0
- package/src/lib/toe-stepping.js +2 -1
- package/src/migrations/006-remove-uid-companion-filenames.js +181 -0
- package/src/migrations/007-natural-entity-companion-filenames.js +165 -0
- package/src/migrations/008-metadata-uid-in-suffix.js +70 -0
package/src/commands/deploy.js
CHANGED
|
@@ -57,10 +57,17 @@ export const deployCommand = new Command('deploy')
|
|
|
57
57
|
process.exit(1);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// If name doesn't match a key directly, try scanning by UID value
|
|
61
|
+
let resolvedName = name;
|
|
62
|
+
if (name && !manifest.deployments[name]) {
|
|
63
|
+
const byUid = Object.entries(manifest.deployments).find(([, v]) => v && v.uid === name);
|
|
64
|
+
if (byUid) resolvedName = byUid[0];
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
const entries = options.all
|
|
61
68
|
? Object.entries(manifest.deployments)
|
|
62
|
-
:
|
|
63
|
-
? [[
|
|
69
|
+
: resolvedName
|
|
70
|
+
? [[resolvedName, manifest.deployments[resolvedName]]]
|
|
64
71
|
: [];
|
|
65
72
|
|
|
66
73
|
if (entries.length === 0 || (entries.length === 1 && !entries[0][1])) {
|
package/src/commands/diff.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '../lib/diff.js';
|
|
15
15
|
import { fetchServerRecordsBatch } from '../lib/toe-stepping.js';
|
|
16
16
|
import { findBaselineEntry } from '../lib/delta.js';
|
|
17
|
+
import { isMetadataFile, parseMetaFilename, findMetadataForCompanion } from '../lib/filenames.js';
|
|
17
18
|
import { runPendingMigrations } from '../lib/migrations.js';
|
|
18
19
|
|
|
19
20
|
export const diffCommand = new Command('diff')
|
|
@@ -81,7 +82,7 @@ export const diffCommand = new Command('diff')
|
|
|
81
82
|
let bulkAction = null;
|
|
82
83
|
|
|
83
84
|
for (const metaPath of metaFiles) {
|
|
84
|
-
const metaBase = basename(metaPath, '.metadata.json');
|
|
85
|
+
const metaBase = parseMetaFilename(basename(metaPath))?.naturalBase ?? basename(metaPath, '.metadata.json');
|
|
85
86
|
|
|
86
87
|
const result = await compareRecord(metaPath, config, serverRecordsMap);
|
|
87
88
|
|
|
@@ -203,7 +204,7 @@ async function resolveTargetToMetaFiles(targetPath) {
|
|
|
203
204
|
pathStat = await stat(targetPath);
|
|
204
205
|
} catch {
|
|
205
206
|
// Maybe it's a file without extension — try finding metadata
|
|
206
|
-
const metaPath = targetPath.endsWith('.metadata.json')
|
|
207
|
+
const metaPath = (isMetadataFile(basename(targetPath)) || targetPath.endsWith('.metadata.json'))
|
|
207
208
|
? targetPath
|
|
208
209
|
: `${targetPath}.metadata.json`;
|
|
209
210
|
|
|
@@ -220,7 +221,7 @@ async function resolveTargetToMetaFiles(targetPath) {
|
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
// Single file — find its companion metadata
|
|
223
|
-
if (targetPath.endsWith('.metadata.json')) {
|
|
224
|
+
if (isMetadataFile(basename(targetPath)) || targetPath.endsWith('.metadata.json')) {
|
|
224
225
|
return [targetPath];
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -239,6 +240,9 @@ async function resolveTargetToMetaFiles(targetPath) {
|
|
|
239
240
|
await stat(mediaMetaPath);
|
|
240
241
|
return [mediaMetaPath];
|
|
241
242
|
} catch {
|
|
243
|
+
// Fallback: scan sibling metadata files for @reference match
|
|
244
|
+
const found = await findMetadataForCompanion(targetPath);
|
|
245
|
+
if (found) return [found];
|
|
242
246
|
log.warn(`No metadata found for "${targetPath}"`);
|
|
243
247
|
return [];
|
|
244
248
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { mkdir } from 'fs/promises';
|
|
2
|
+
import { mkdir, writeFile, access } from 'fs/promises';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { isInitialized, hasLegacyConfig, readLegacyConfig, initConfig, saveCredentials, ensureGitignore, saveTransactionKeyPreset, saveTicketSuggestionOutput } from '../lib/config.js';
|
|
5
5
|
import { installOrUpdateClaudeCommands } from './install.js';
|
|
@@ -102,7 +102,7 @@ export const initCommand = new Command('init')
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Ensure sensitive files are gitignored
|
|
105
|
-
await ensureGitignore(['.dbo/credentials.json', '.dbo/cookies.txt', '.dbo/config.local.json', '.dbo/ticketing.local.json', '.dbo/errors.log', 'trash/', 'Icon\\r']);
|
|
105
|
+
await ensureGitignore(['.dbo/credentials.json', '.dbo/cookies.txt', '.dbo/config.local.json', '.dbo/ticketing.local.json', '.dbo/scripts.local.json', '.dbo/errors.log', 'trash/', 'Icon\\r']);
|
|
106
106
|
|
|
107
107
|
const createdIgnore = await createDboignore();
|
|
108
108
|
if (createdIgnore) log.dim(' Created .dboignore');
|
|
@@ -114,6 +114,20 @@ export const initCommand = new Command('init')
|
|
|
114
114
|
}
|
|
115
115
|
log.dim(' Created .claude/ directory structure');
|
|
116
116
|
|
|
117
|
+
// Create empty scripts.json and scripts.local.json if they don't exist
|
|
118
|
+
const emptyScripts = JSON.stringify({ scripts: {}, targets: {}, entities: {} }, null, 2) + '\n';
|
|
119
|
+
const dboDir = join(process.cwd(), '.dbo');
|
|
120
|
+
const scriptsPath = join(dboDir, 'scripts.json');
|
|
121
|
+
const scriptsLocalPath = join(dboDir, 'scripts.local.json');
|
|
122
|
+
try { await access(scriptsPath); } catch {
|
|
123
|
+
await writeFile(scriptsPath, emptyScripts);
|
|
124
|
+
log.dim(' Created .dbo/scripts.json');
|
|
125
|
+
}
|
|
126
|
+
try { await access(scriptsLocalPath); } catch {
|
|
127
|
+
await writeFile(scriptsLocalPath, emptyScripts);
|
|
128
|
+
log.dim(' Created .dbo/scripts.local.json');
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
log.success(`Initialized .dbo/ for ${domain}`);
|
|
118
132
|
|
|
119
133
|
// Authenticate early so the session is ready for subsequent operations
|
package/src/commands/input.js
CHANGED
|
@@ -60,8 +60,10 @@ export const inputCommand = new Command('input')
|
|
|
60
60
|
if (modifyKeyResult.modifyKey) extraParams['_modify_key'] = modifyKeyResult.modifyKey;
|
|
61
61
|
|
|
62
62
|
// Check if data expressions include AppID; if not and config has one, prompt
|
|
63
|
+
// Skip AppID prompt for delete-only submissions — deletes don't need it
|
|
63
64
|
const allDataText = options.data.join(' ');
|
|
64
|
-
const
|
|
65
|
+
const isDeleteOnly = options.data.every(d => /RowID:del\d+/.test(d));
|
|
66
|
+
const hasAppId = isDeleteOnly || /\.AppID[=@]/.test(allDataText) || /AppID=/.test(allDataText);
|
|
65
67
|
if (!hasAppId) {
|
|
66
68
|
const appConfig = await loadAppConfig();
|
|
67
69
|
if (appConfig.AppID) {
|
package/src/commands/login.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { loadConfig, saveUserInfo, saveUserProfile, ensureGitignore } from '../lib/config.js';
|
|
2
|
+
import { loadConfig, initConfig, saveUserInfo, saveUserProfile, ensureGitignore } from '../lib/config.js';
|
|
3
3
|
import { DboClient } from '../lib/client.js';
|
|
4
4
|
import { log } from '../lib/logger.js';
|
|
5
5
|
|
|
@@ -13,13 +13,24 @@ import { log } from '../lib/logger.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export async function performLogin(domain, knownUsername) {
|
|
15
15
|
const config = await loadConfig();
|
|
16
|
+
const inquirer = (await import('inquirer')).default;
|
|
17
|
+
|
|
18
|
+
// Prompt for domain if not provided and not configured
|
|
19
|
+
if (!domain && !config.domain) {
|
|
20
|
+
const { domain: inputDomain } = await inquirer.prompt([
|
|
21
|
+
{ type: 'input', name: 'domain', message: 'Domain (e.g. myapp.dbo.io):' },
|
|
22
|
+
]);
|
|
23
|
+
if (!inputDomain) throw new Error('Domain is required.');
|
|
24
|
+
domain = inputDomain.trim();
|
|
25
|
+
await initConfig(domain);
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
const client = new DboClient({ domain });
|
|
17
29
|
|
|
18
30
|
let username = knownUsername || config.username;
|
|
19
31
|
let password;
|
|
20
32
|
|
|
21
33
|
// Interactive prompt for missing credentials
|
|
22
|
-
const inquirer = (await import('inquirer')).default;
|
|
23
34
|
const answers = await inquirer.prompt([
|
|
24
35
|
{ type: 'input', name: 'username', message: 'Username (email):', default: username || undefined, when: !username },
|
|
25
36
|
{ type: 'password', name: 'password', message: 'Password:', mask: '*' },
|
|
@@ -83,7 +94,23 @@ export const loginCommand = new Command('login')
|
|
|
83
94
|
.action(async (options) => {
|
|
84
95
|
try {
|
|
85
96
|
const config = await loadConfig();
|
|
86
|
-
const
|
|
97
|
+
const inquirer = (await import('inquirer')).default;
|
|
98
|
+
let domain = options.domain;
|
|
99
|
+
|
|
100
|
+
// Prompt for domain if not provided and not configured
|
|
101
|
+
if (!domain && !config.domain) {
|
|
102
|
+
const { domain: inputDomain } = await inquirer.prompt([
|
|
103
|
+
{ type: 'input', name: 'domain', message: 'Domain (e.g. myapp.dbo.io):' },
|
|
104
|
+
]);
|
|
105
|
+
if (!inputDomain) {
|
|
106
|
+
log.error('Domain is required.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
domain = inputDomain.trim();
|
|
110
|
+
await initConfig(domain);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const client = new DboClient({ domain });
|
|
87
114
|
|
|
88
115
|
let username = options.username || options.email || options.phone;
|
|
89
116
|
let password = options.password || options.passkey;
|
|
@@ -93,7 +120,6 @@ export const loginCommand = new Command('login')
|
|
|
93
120
|
|
|
94
121
|
// Interactive prompt if still missing
|
|
95
122
|
if (!username || !password) {
|
|
96
|
-
const inquirer = (await import('inquirer')).default;
|
|
97
123
|
const answers = await inquirer.prompt([
|
|
98
124
|
{ type: 'input', name: 'username', message: 'Username (email):', default: config.username, when: !username },
|
|
99
125
|
{ type: 'password', name: 'password', message: 'Password:', mask: '*', when: !password },
|
package/src/commands/mv.js
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
BINS_DIR
|
|
14
14
|
} from '../lib/structure.js';
|
|
15
15
|
import { findMetadataFiles } from '../lib/diff.js';
|
|
16
|
+
import { isMetadataFile, parseMetaFilename, findMetadataForCompanion } from '../lib/filenames.js';
|
|
16
17
|
import { runPendingMigrations } from '../lib/migrations.js';
|
|
18
|
+
import { removeDeployEntry, upsertDeployEntry } from '../lib/deploy-config.js';
|
|
17
19
|
|
|
18
20
|
export const mvCommand = new Command('mv')
|
|
19
21
|
.description('Move files or bins to a new location and update metadata')
|
|
@@ -199,7 +201,7 @@ function checkCircularReference(sourceBinId, targetBinId, structure) {
|
|
|
199
201
|
* Resolve a file path to its metadata.json path.
|
|
200
202
|
*/
|
|
201
203
|
function resolveMetaPath(filePath) {
|
|
202
|
-
if (filePath.endsWith('.metadata.json')) {
|
|
204
|
+
if (isMetadataFile(basename(filePath)) || filePath.endsWith('.metadata.json')) {
|
|
203
205
|
return filePath;
|
|
204
206
|
}
|
|
205
207
|
const dir = dirname(filePath);
|
|
@@ -507,14 +509,21 @@ async function checkNameConflict(fileName, targetDir, options) {
|
|
|
507
509
|
*/
|
|
508
510
|
async function mvFile(sourceFile, targetBinId, structure, options) {
|
|
509
511
|
// Resolve metadata
|
|
510
|
-
|
|
512
|
+
let metaPath = resolveMetaPath(sourceFile);
|
|
511
513
|
let meta;
|
|
512
514
|
try {
|
|
513
515
|
meta = JSON.parse(await readFile(metaPath, 'utf8'));
|
|
514
516
|
} catch {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
517
|
+
const found = await findMetadataForCompanion(sourceFile);
|
|
518
|
+
if (found) {
|
|
519
|
+
metaPath = found;
|
|
520
|
+
try { meta = JSON.parse(await readFile(metaPath, 'utf8')); } catch {}
|
|
521
|
+
}
|
|
522
|
+
if (!meta) {
|
|
523
|
+
log.error(`No metadata found for "${basename(sourceFile)}"`);
|
|
524
|
+
log.dim(' Use "dbo pull" to create metadata files.');
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
const entity = meta._entity;
|
|
@@ -536,13 +545,13 @@ async function mvFile(sourceFile, targetBinId, structure, options) {
|
|
|
536
545
|
// Calculate paths
|
|
537
546
|
const targetDir = resolveBinPath(targetBinId, structure);
|
|
538
547
|
const sourceDir = dirname(metaPath);
|
|
539
|
-
const contentFileName = sourceFile.endsWith('.metadata.json')
|
|
548
|
+
const contentFileName = (isMetadataFile(basename(sourceFile)) || sourceFile.endsWith('.metadata.json'))
|
|
540
549
|
? null
|
|
541
550
|
: basename(sourceFile);
|
|
542
551
|
const metaFileName = basename(metaPath);
|
|
543
552
|
|
|
544
553
|
// Determine display name
|
|
545
|
-
const displayName = basename(metaPath, '.metadata.json');
|
|
554
|
+
const displayName = parseMetaFilename(basename(metaPath))?.naturalBase ?? basename(metaPath, '.metadata.json');
|
|
546
555
|
const targetBin = structure[targetBinId];
|
|
547
556
|
const targetBinName = targetBin ? targetBin.name : String(targetBinId);
|
|
548
557
|
const targetBinFullPath = targetBin ? `${BINS_DIR}/${targetBin.fullPath}` : targetDir;
|
|
@@ -664,6 +673,18 @@ async function mvFile(sourceFile, targetBinId, structure, options) {
|
|
|
664
673
|
}
|
|
665
674
|
}
|
|
666
675
|
|
|
676
|
+
if (!options.dryRun) {
|
|
677
|
+
// Update deploy config: remove old entry (by UID), re-insert with new path + correct key
|
|
678
|
+
await removeDeployEntry(uid);
|
|
679
|
+
if (newContentPath) {
|
|
680
|
+
const col = (meta._contentColumns || [])[0] || 'Content';
|
|
681
|
+
await upsertDeployEntry(newContentPath, uid, entity, col);
|
|
682
|
+
} else if (meta._mediaFile && String(meta._mediaFile).startsWith('@')) {
|
|
683
|
+
const movedMediaPath = join(targetDir, String(meta._mediaFile).substring(1));
|
|
684
|
+
await upsertDeployEntry(movedMediaPath, uid, entity, 'File');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
667
688
|
if (options.dryRun) {
|
|
668
689
|
log.info(`[DRY RUN] Would move "${displayName}" to "${targetBinName}" (${targetBinFullPath})`);
|
|
669
690
|
} else {
|