@dboio/cli 0.9.8 → 0.11.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/README.md +172 -70
- package/bin/dbo.js +2 -0
- package/bin/postinstall.js +9 -1
- package/package.json +3 -3
- package/plugins/claude/dbo/commands/dbo.md +3 -3
- package/plugins/claude/dbo/skills/cli/SKILL.md +3 -3
- package/src/commands/add.js +50 -0
- package/src/commands/clone.js +720 -552
- package/src/commands/content.js +7 -3
- package/src/commands/deploy.js +22 -7
- package/src/commands/diff.js +41 -3
- package/src/commands/init.js +42 -79
- package/src/commands/input.js +5 -0
- package/src/commands/login.js +2 -2
- package/src/commands/mv.js +3 -0
- package/src/commands/output.js +8 -10
- package/src/commands/pull.js +268 -87
- package/src/commands/push.js +814 -94
- package/src/commands/rm.js +4 -1
- package/src/commands/status.js +12 -1
- package/src/commands/sync.js +71 -0
- package/src/lib/client.js +10 -0
- package/src/lib/config.js +80 -8
- package/src/lib/delta.js +178 -25
- package/src/lib/diff.js +150 -20
- package/src/lib/folder-icon.js +120 -0
- package/src/lib/ignore.js +2 -3
- package/src/lib/input-parser.js +37 -10
- package/src/lib/metadata-templates.js +21 -4
- package/src/lib/migrations.js +75 -0
- package/src/lib/save-to-disk.js +1 -1
- package/src/lib/scaffold.js +58 -3
- package/src/lib/structure.js +158 -21
- package/src/lib/toe-stepping.js +381 -0
- package/src/migrations/001-transaction-key-preset-scope.js +35 -0
- package/src/migrations/002-move-entity-dirs-to-lib.js +190 -0
- package/src/migrations/003-move-deploy-config.js +50 -0
- package/src/migrations/004-rename-output-files.js +101 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, rename, access, readdir, rmdir } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { log } from '../lib/logger.js';
|
|
4
|
+
|
|
5
|
+
export const description = 'Relocated server entity directories into lib/';
|
|
6
|
+
|
|
7
|
+
/** Entity dirs that should live under lib/ (the full list, in detection order) */
|
|
8
|
+
const ENTITY_DIRS_TO_MOVE = [
|
|
9
|
+
'bins', 'extension', 'automation', 'app_version', 'entity',
|
|
10
|
+
'entity_column', 'entity_column_value', 'integration', 'security',
|
|
11
|
+
'security_column', 'data_source', 'group', 'site', 'redirect',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Migration 002 — Move root-level entity directories into lib/.
|
|
16
|
+
*
|
|
17
|
+
* Detects old flat-root layout, moves dirs to lib/, rewrites app.json @path
|
|
18
|
+
* references, updates structure.json extensionDescriptorDirs, renames tests/ → test/.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} _options - Command options (unused)
|
|
21
|
+
*/
|
|
22
|
+
export default async function run(_options) {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
|
|
25
|
+
// Detect which old-layout dirs exist at root
|
|
26
|
+
const detected = [];
|
|
27
|
+
for (const dirName of ENTITY_DIRS_TO_MOVE) {
|
|
28
|
+
try {
|
|
29
|
+
await access(join(cwd, dirName));
|
|
30
|
+
detected.push(dirName);
|
|
31
|
+
} catch { /* not present */ }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (detected.length === 0) {
|
|
35
|
+
// Check for tests/ → test/ rename even if no entity dirs to move
|
|
36
|
+
await renameTestsDir(cwd);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create lib/ directory
|
|
41
|
+
await mkdir(join(cwd, 'lib'), { recursive: true });
|
|
42
|
+
|
|
43
|
+
let movedCount = 0;
|
|
44
|
+
for (const dirName of detected) {
|
|
45
|
+
const src = join(cwd, dirName);
|
|
46
|
+
const dest = join(cwd, 'lib', dirName);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await access(dest);
|
|
50
|
+
// Destination already exists — merge: move files from src not in dest
|
|
51
|
+
await mergeDir(src, dest);
|
|
52
|
+
log.dim(` ✓ Merged ${dirName}/ → lib/${dirName}/ (partial migration detected)`);
|
|
53
|
+
} catch {
|
|
54
|
+
// Destination absent — simple rename
|
|
55
|
+
await rename(src, dest);
|
|
56
|
+
log.dim(` ✓ Moved ${dirName}/ → lib/${dirName}/`);
|
|
57
|
+
}
|
|
58
|
+
movedCount++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Rewrite app.json @path references
|
|
62
|
+
await rewriteAppJson(cwd, detected);
|
|
63
|
+
|
|
64
|
+
// Update .dbo/structure.json extensionDescriptorDirs
|
|
65
|
+
await rewriteStructureJson(cwd);
|
|
66
|
+
|
|
67
|
+
// Rename tests/ → test/ if present
|
|
68
|
+
await renameTestsDir(cwd);
|
|
69
|
+
|
|
70
|
+
log.success(` Migration 002: Relocated ${movedCount} server director${movedCount === 1 ? 'y' : 'ies'} into lib/`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Merge contents of srcDir into destDir without overwriting existing files.
|
|
75
|
+
* Recursively processes subdirectories.
|
|
76
|
+
*/
|
|
77
|
+
async function mergeDir(srcDir, destDir) {
|
|
78
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
79
|
+
await mkdir(destDir, { recursive: true });
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const srcPath = join(srcDir, entry.name);
|
|
83
|
+
const destPath = join(destDir, entry.name);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await mergeDir(srcPath, destPath);
|
|
87
|
+
} else {
|
|
88
|
+
try {
|
|
89
|
+
await access(destPath);
|
|
90
|
+
// File already in dest — skip to avoid overwriting
|
|
91
|
+
} catch {
|
|
92
|
+
await rename(srcPath, destPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove now-empty source dir (best-effort)
|
|
98
|
+
try {
|
|
99
|
+
const remaining = await readdir(srcDir);
|
|
100
|
+
if (remaining.length === 0) {
|
|
101
|
+
await rmdir(srcDir);
|
|
102
|
+
}
|
|
103
|
+
} catch { /* ignore */ }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rewrite app.json @path references from old root-level paths to lib/-prefixed paths.
|
|
108
|
+
* e.g. "@extension/foo.metadata.json" → "@lib/extension/foo.metadata.json"
|
|
109
|
+
* "@bins/app/styles.css" → "@lib/bins/app/styles.css"
|
|
110
|
+
*/
|
|
111
|
+
async function rewriteAppJson(cwd, movedDirs) {
|
|
112
|
+
const appJsonPath = join(cwd, 'app.json');
|
|
113
|
+
let raw;
|
|
114
|
+
try {
|
|
115
|
+
raw = await readFile(appJsonPath, 'utf8');
|
|
116
|
+
} catch {
|
|
117
|
+
return; // no app.json — skip
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let updated = raw;
|
|
121
|
+
for (const dirName of movedDirs) {
|
|
122
|
+
// Replace "@dirName/" with "@lib/dirName/"
|
|
123
|
+
// Use a global regex that handles the @ prefix in JSON string values
|
|
124
|
+
const pattern = new RegExp(`"@${dirName}/`, 'g');
|
|
125
|
+
updated = updated.replace(pattern, `"@lib/${dirName}/`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (updated !== raw) {
|
|
129
|
+
await writeFile(appJsonPath, updated);
|
|
130
|
+
log.dim(' ✓ Rewrote app.json @path references to lib/ prefix');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Update .dbo/structure.json extensionDescriptorDirs from "extension/..." to "lib/extension/...".
|
|
136
|
+
*/
|
|
137
|
+
async function rewriteStructureJson(cwd) {
|
|
138
|
+
const structurePath = join(cwd, '.dbo', 'structure.json');
|
|
139
|
+
let raw;
|
|
140
|
+
try {
|
|
141
|
+
raw = await readFile(structurePath, 'utf8');
|
|
142
|
+
} catch {
|
|
143
|
+
return; // no structure.json — skip
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let structure;
|
|
147
|
+
try {
|
|
148
|
+
structure = JSON.parse(raw);
|
|
149
|
+
} catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!Array.isArray(structure.extensionDescriptorDirs)) return;
|
|
154
|
+
|
|
155
|
+
const updated = structure.extensionDescriptorDirs.map(dir => {
|
|
156
|
+
if (dir.startsWith('extension/') || dir === 'extension/_unsupported') {
|
|
157
|
+
return `lib/${dir}`;
|
|
158
|
+
}
|
|
159
|
+
// Already prefixed (re-entrant safety)
|
|
160
|
+
return dir;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
structure.extensionDescriptorDirs = updated;
|
|
164
|
+
await writeFile(structurePath, JSON.stringify(structure, null, 2) + '\n');
|
|
165
|
+
log.dim(' ✓ Updated .dbo/structure.json extensionDescriptorDirs to lib/ prefix');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Rename tests/ → test/ at project root if tests/ exists and test/ does not.
|
|
170
|
+
*/
|
|
171
|
+
async function renameTestsDir(cwd) {
|
|
172
|
+
const testsSrc = join(cwd, 'tests');
|
|
173
|
+
const testDest = join(cwd, 'test');
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await access(testsSrc);
|
|
177
|
+
} catch {
|
|
178
|
+
return; // tests/ doesn't exist — nothing to do
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await access(testDest);
|
|
183
|
+
// test/ already exists — skip rename to avoid conflict
|
|
184
|
+
log.dim(' ⚠ Both tests/ and test/ exist — tests/ was not renamed; resolve manually');
|
|
185
|
+
return;
|
|
186
|
+
} catch { /* test/ absent — safe to rename */ }
|
|
187
|
+
|
|
188
|
+
await rename(testsSrc, testDest);
|
|
189
|
+
log.dim(' ✓ Renamed tests/ → test/');
|
|
190
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { access, rename, readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { log } from '../lib/logger.js';
|
|
4
|
+
|
|
5
|
+
export const description = 'Moved deploy manifest from dbo.deploy.json to .dbo/deploy_config.json';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Migration 003 — Move dbo.deploy.json → .dbo/deploy_config.json
|
|
9
|
+
*
|
|
10
|
+
* The deploy manifest now lives inside .dbo/ alongside other config files.
|
|
11
|
+
* Also removes the legacy `dbo.deploy.json` entry from .dboignore if present.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} _options - Command options (unused)
|
|
14
|
+
*/
|
|
15
|
+
export default async function run(_options) {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const legacyPath = join(cwd, 'dbo.deploy.json');
|
|
18
|
+
const newPath = join(cwd, '.dbo', 'deploy_config.json');
|
|
19
|
+
|
|
20
|
+
// Check if legacy file exists
|
|
21
|
+
try {
|
|
22
|
+
await access(legacyPath);
|
|
23
|
+
} catch {
|
|
24
|
+
return; // No legacy file — nothing to do
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check if new location already exists (avoid overwriting)
|
|
28
|
+
try {
|
|
29
|
+
await access(newPath);
|
|
30
|
+
log.dim(' ⚠ Both dbo.deploy.json and .dbo/deploy_config.json exist — skipping move; resolve manually');
|
|
31
|
+
return;
|
|
32
|
+
} catch { /* new path absent — safe to move */ }
|
|
33
|
+
|
|
34
|
+
await rename(legacyPath, newPath);
|
|
35
|
+
log.dim(' ✓ Moved dbo.deploy.json → .dbo/deploy_config.json');
|
|
36
|
+
|
|
37
|
+
// Clean up .dboignore — remove the dbo.deploy.json entry if present
|
|
38
|
+
const dboignorePath = join(cwd, '.dboignore');
|
|
39
|
+
try {
|
|
40
|
+
const content = await readFile(dboignorePath, 'utf8');
|
|
41
|
+
const updated = content
|
|
42
|
+
.split('\n')
|
|
43
|
+
.filter(line => line.trim() !== 'dbo.deploy.json')
|
|
44
|
+
.join('\n');
|
|
45
|
+
if (updated !== content) {
|
|
46
|
+
await writeFile(dboignorePath, updated);
|
|
47
|
+
log.dim(' ✓ Removed dbo.deploy.json from .dboignore');
|
|
48
|
+
}
|
|
49
|
+
} catch { /* no .dboignore — skip */ }
|
|
50
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile, rename, access } from 'fs/promises';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { log } from '../lib/logger.js';
|
|
4
|
+
|
|
5
|
+
export const description = 'Rename _output~ files to Name~UID convention';
|
|
6
|
+
|
|
7
|
+
const OUTPUT_PREFIX = '_output~';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Migration 004 — Rename output hierarchy files to drop the _output~ prefix.
|
|
11
|
+
*
|
|
12
|
+
* Old: _output~task.info~UID.json, _output~task.info~UID.column~ColUID.CustomSQL.sql
|
|
13
|
+
* New: task.info~UID.json, task.info~UID.column~ColUID.CustomSQL.sql
|
|
14
|
+
*
|
|
15
|
+
* Also rewrites @_output~ references inside the root JSON files.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} _options - Command options (unused)
|
|
18
|
+
*/
|
|
19
|
+
export default async function run(_options) {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
let totalRenamed = 0;
|
|
22
|
+
|
|
23
|
+
// Recursively find directories that contain _output~ files
|
|
24
|
+
const dirs = await findDirsWithOutputFiles(cwd);
|
|
25
|
+
|
|
26
|
+
for (const dir of dirs) {
|
|
27
|
+
const entries = await readdir(dir);
|
|
28
|
+
const outputFiles = entries.filter(f => f.startsWith(OUTPUT_PREFIX));
|
|
29
|
+
if (outputFiles.length === 0) continue;
|
|
30
|
+
|
|
31
|
+
for (const oldName of outputFiles) {
|
|
32
|
+
const newName = oldName.substring(OUTPUT_PREFIX.length);
|
|
33
|
+
const oldPath = join(dir, oldName);
|
|
34
|
+
const newPath = join(dir, newName);
|
|
35
|
+
|
|
36
|
+
// Skip if destination already exists (avoid overwrite)
|
|
37
|
+
try {
|
|
38
|
+
await access(newPath);
|
|
39
|
+
continue;
|
|
40
|
+
} catch { /* good — doesn't exist */ }
|
|
41
|
+
|
|
42
|
+
await rename(oldPath, newPath);
|
|
43
|
+
totalRenamed++;
|
|
44
|
+
|
|
45
|
+
// If it's a root JSON, also rewrite @_output~ references inside it
|
|
46
|
+
if (newName.endsWith('.json') && !newName.includes('.CustomSQL.')) {
|
|
47
|
+
await rewriteReferences(newPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (totalRenamed > 0) {
|
|
53
|
+
log.dim(` Renamed ${totalRenamed} output file${totalRenamed === 1 ? '' : 's'} (dropped _output~ prefix)`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Rewrite @_output~ references in a JSON file's string values.
|
|
59
|
+
* e.g. "@_output~task.info~UID.column~ColUID.CustomSQL.sql"
|
|
60
|
+
* → "@task.info~UID.column~ColUID.CustomSQL.sql"
|
|
61
|
+
*/
|
|
62
|
+
async function rewriteReferences(filePath) {
|
|
63
|
+
let raw;
|
|
64
|
+
try {
|
|
65
|
+
raw = await readFile(filePath, 'utf8');
|
|
66
|
+
} catch { return; }
|
|
67
|
+
|
|
68
|
+
const updated = raw.replace(/@_output~/g, '@');
|
|
69
|
+
if (updated !== raw) {
|
|
70
|
+
await writeFile(filePath, updated);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recursively find directories that contain _output~ files.
|
|
76
|
+
* Skips .dbo/, node_modules/, trash/, .git/.
|
|
77
|
+
*/
|
|
78
|
+
async function findDirsWithOutputFiles(root) {
|
|
79
|
+
const SKIP = new Set(['.dbo', 'node_modules', 'trash', '.git', '.claude']);
|
|
80
|
+
const results = [];
|
|
81
|
+
|
|
82
|
+
async function walk(dir) {
|
|
83
|
+
let entries;
|
|
84
|
+
try { entries = await readdir(dir, { withFileTypes: true }); } catch { return; }
|
|
85
|
+
|
|
86
|
+
let hasOutputFiles = false;
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
if (!SKIP.has(entry.name)) {
|
|
90
|
+
await walk(join(dir, entry.name));
|
|
91
|
+
}
|
|
92
|
+
} else if (entry.name.startsWith(OUTPUT_PREFIX)) {
|
|
93
|
+
hasOutputFiles = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (hasOutputFiles) results.push(dir);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await walk(root);
|
|
100
|
+
return results;
|
|
101
|
+
}
|