@dboio/cli 0.4.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 +1161 -0
- package/bin/dbo.js +51 -0
- package/package.json +22 -0
- package/src/commands/add.js +374 -0
- package/src/commands/cache.js +49 -0
- package/src/commands/clone.js +742 -0
- package/src/commands/content.js +143 -0
- package/src/commands/deploy.js +89 -0
- package/src/commands/init.js +105 -0
- package/src/commands/input.js +111 -0
- package/src/commands/install.js +186 -0
- package/src/commands/instance.js +44 -0
- package/src/commands/login.js +97 -0
- package/src/commands/logout.js +22 -0
- package/src/commands/media.js +46 -0
- package/src/commands/message.js +28 -0
- package/src/commands/output.js +129 -0
- package/src/commands/pull.js +109 -0
- package/src/commands/push.js +309 -0
- package/src/commands/status.js +41 -0
- package/src/commands/update.js +168 -0
- package/src/commands/upload.js +37 -0
- package/src/lib/client.js +161 -0
- package/src/lib/columns.js +30 -0
- package/src/lib/config.js +269 -0
- package/src/lib/cookie-jar.js +104 -0
- package/src/lib/formatter.js +310 -0
- package/src/lib/input-parser.js +212 -0
- package/src/lib/logger.js +12 -0
- package/src/lib/save-to-disk.js +383 -0
- package/src/lib/structure.js +129 -0
- package/src/lib/timestamps.js +67 -0
- package/src/plugins/claudecommands/dbo.md +248 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadConfig, saveUserInfo, saveUserProfile, ensureGitignore } from '../lib/config.js';
|
|
3
|
+
import { DboClient } from '../lib/client.js';
|
|
4
|
+
import { log } from '../lib/logger.js';
|
|
5
|
+
|
|
6
|
+
export const loginCommand = new Command('login')
|
|
7
|
+
.description('Authenticate with a DBO.io instance')
|
|
8
|
+
.option('-u, --username <value>', 'Username')
|
|
9
|
+
.option('-e, --email <value>', 'Email')
|
|
10
|
+
.option('--phone <value>', 'Phone number')
|
|
11
|
+
.option('-p, --password <value>', 'Password')
|
|
12
|
+
.option('--passkey <value>', 'Passkey')
|
|
13
|
+
.option('--domain <host>', 'Override domain')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
const config = await loadConfig();
|
|
17
|
+
const client = new DboClient({ domain: options.domain });
|
|
18
|
+
|
|
19
|
+
let username = options.username || options.email || options.phone;
|
|
20
|
+
let password = options.password || options.passkey;
|
|
21
|
+
|
|
22
|
+
// Fall back to stored username (password is never stored — prompt every time)
|
|
23
|
+
if (!username) username = config.username;
|
|
24
|
+
|
|
25
|
+
// Interactive prompt if still missing
|
|
26
|
+
if (!username || !password) {
|
|
27
|
+
const inquirer = (await import('inquirer')).default;
|
|
28
|
+
const answers = await inquirer.prompt([
|
|
29
|
+
{ type: 'input', name: 'username', message: 'Username (email):', default: config.username, when: !username },
|
|
30
|
+
{ type: 'password', name: 'password', message: 'Password:', mask: '*', when: !password },
|
|
31
|
+
]);
|
|
32
|
+
username = username || answers.username;
|
|
33
|
+
password = password || answers.password;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Build auth params
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
if (options.email) params.append('_email', username);
|
|
39
|
+
else if (options.phone) params.append('_phonenumber', username);
|
|
40
|
+
else params.append('_username', username);
|
|
41
|
+
|
|
42
|
+
if (options.passkey) params.append('_passkey', password);
|
|
43
|
+
else params.append('_password', password);
|
|
44
|
+
|
|
45
|
+
const result = await client.postUrlEncoded('/api/authenticate', params.toString());
|
|
46
|
+
|
|
47
|
+
if (result.successful) {
|
|
48
|
+
log.success(`Authenticated as ${username} on ${await client.getDomain()}`);
|
|
49
|
+
|
|
50
|
+
// Ensure sensitive files are gitignored
|
|
51
|
+
await ensureGitignore(['.dbo/credentials.json', '.dbo/cookies.txt']);
|
|
52
|
+
|
|
53
|
+
// Fetch current user info to store ID and UID for future submissions
|
|
54
|
+
try {
|
|
55
|
+
const userResult = await client.get('/api/output/31799e38f0854956a47f10', { '_format': 'json_raw' });
|
|
56
|
+
const userData = userResult.payload || userResult.data;
|
|
57
|
+
const rows = Array.isArray(userData) ? userData : (userData?.Rows || userData?.rows || []);
|
|
58
|
+
if (rows.length > 0) {
|
|
59
|
+
const row = rows[0];
|
|
60
|
+
const userUid = row.UID || row.uid;
|
|
61
|
+
const userId = row.ID || row.id || row.UserID || row.userId;
|
|
62
|
+
const info = {};
|
|
63
|
+
if (userUid) info.userUid = userUid;
|
|
64
|
+
if (userId) info.userId = String(userId);
|
|
65
|
+
if (info.userUid || info.userId) {
|
|
66
|
+
await saveUserInfo(info);
|
|
67
|
+
if (info.userId) log.dim(` User ID: ${info.userId}`);
|
|
68
|
+
if (info.userUid) log.dim(` User UID: ${info.userUid}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Save user profile (name, email) for package.json population
|
|
72
|
+
const firstName = row.FirstName || row.firstname || row.first_name;
|
|
73
|
+
const lastName = row.LastName || row.lastname || row.last_name;
|
|
74
|
+
const email = row.Email || row.email;
|
|
75
|
+
const profile = {};
|
|
76
|
+
if (firstName) profile.FirstName = firstName;
|
|
77
|
+
if (lastName) profile.LastName = lastName;
|
|
78
|
+
if (email) profile.Email = email;
|
|
79
|
+
if (Object.keys(profile).length > 0) {
|
|
80
|
+
await saveUserProfile(profile);
|
|
81
|
+
if (firstName || lastName) log.dim(` Name: ${[firstName, lastName].filter(Boolean).join(' ')}`);
|
|
82
|
+
if (email) log.dim(` Email: ${email}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
log.dim(' Could not retrieve user info (non-critical)');
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
log.error('Authentication failed');
|
|
90
|
+
for (const msg of result.messages) log.label('Message', msg);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
log.error(err.message);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { unlink } from 'fs/promises';
|
|
3
|
+
import { DboClient } from '../lib/client.js';
|
|
4
|
+
import { getActiveCookiesPath } from '../lib/config.js';
|
|
5
|
+
import { log } from '../lib/logger.js';
|
|
6
|
+
|
|
7
|
+
export const logoutCommand = new Command('logout')
|
|
8
|
+
.description('Log out and clear session')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
try {
|
|
11
|
+
const client = new DboClient();
|
|
12
|
+
await client.postUrlEncoded('/api/authenticate', '_logout=true');
|
|
13
|
+
|
|
14
|
+
const cookiesPath = await getActiveCookiesPath();
|
|
15
|
+
try { await unlink(cookiesPath); } catch { /* no cookies file */ }
|
|
16
|
+
|
|
17
|
+
log.success('Logged out and session cleared.');
|
|
18
|
+
} catch (err) {
|
|
19
|
+
log.error(err.message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { writeFile } from 'fs/promises';
|
|
3
|
+
import { DboClient } from '../lib/client.js';
|
|
4
|
+
import { formatResponse, formatError } from '../lib/formatter.js';
|
|
5
|
+
import { log } from '../lib/logger.js';
|
|
6
|
+
|
|
7
|
+
export const mediaCommand = new Command('media')
|
|
8
|
+
.description('Get media from DBO.io')
|
|
9
|
+
.argument('[uid]', 'Media UID')
|
|
10
|
+
.option('--id <id>', 'Media by numeric ID')
|
|
11
|
+
.option('--path <path>', 'Media by path')
|
|
12
|
+
.option('-o, --output <path>', 'Save to local file')
|
|
13
|
+
.option('--download', 'Force download (attachment disposition)')
|
|
14
|
+
.option('-v, --verbose', 'Show HTTP request details')
|
|
15
|
+
.option('--domain <host>', 'Override domain')
|
|
16
|
+
.action(async (uid, options) => {
|
|
17
|
+
try {
|
|
18
|
+
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
19
|
+
let path;
|
|
20
|
+
if (options.id) path = `/api/media/id/${options.id}`;
|
|
21
|
+
else if (options.path) path = `/dir/${options.path}`;
|
|
22
|
+
else if (uid) path = `/api/media/${uid}`;
|
|
23
|
+
else { console.error('Error: provide a media UID, --id, or --path'); process.exit(1); }
|
|
24
|
+
|
|
25
|
+
const params = {};
|
|
26
|
+
if (options.download) params['_download'] = 'true';
|
|
27
|
+
|
|
28
|
+
const baseUrl = await client.getBaseUrl();
|
|
29
|
+
const fullUrl = `${baseUrl}${path}${Object.keys(params).length ? '?' + new URLSearchParams(params) : ''}`;
|
|
30
|
+
|
|
31
|
+
// For media, we need raw binary response
|
|
32
|
+
const response = await client.request(path + (Object.keys(params).length ? '?' + new URLSearchParams(params) : ''));
|
|
33
|
+
|
|
34
|
+
if (options.output) {
|
|
35
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
36
|
+
await writeFile(options.output, buffer);
|
|
37
|
+
log.success(`Saved to ${options.output} (${buffer.length} bytes)`);
|
|
38
|
+
} else {
|
|
39
|
+
const text = await response.text();
|
|
40
|
+
log.plain(text);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
formatError(err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { DboClient } from '../lib/client.js';
|
|
3
|
+
import { formatResponse, formatError } from '../lib/formatter.js';
|
|
4
|
+
|
|
5
|
+
export const messageCommand = new Command('message')
|
|
6
|
+
.description('Send a message via DBO.io (email, SMS, chatbot)')
|
|
7
|
+
.argument('<uid>', 'Content UID for the message template')
|
|
8
|
+
.option('-C, --confirm <value>', 'Commit: true (default) or false', 'true')
|
|
9
|
+
.option('--thread <id>', 'Thread ID for chatbot conversations')
|
|
10
|
+
.option('--json', 'Output raw JSON')
|
|
11
|
+
.option('--jq <expr>', 'Filter JSON response with jq-like expression')
|
|
12
|
+
.option('-v, --verbose', 'Show HTTP request details')
|
|
13
|
+
.option('--domain <host>', 'Override domain')
|
|
14
|
+
.action(async (uid, options) => {
|
|
15
|
+
try {
|
|
16
|
+
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
17
|
+
const params = new URLSearchParams();
|
|
18
|
+
params.append('_confirm', options.confirm);
|
|
19
|
+
if (options.thread) params.append('ThreadID', options.thread);
|
|
20
|
+
|
|
21
|
+
const result = await client.postUrlEncoded(`/api/message/content/${uid}`, params.toString());
|
|
22
|
+
formatResponse(result, { json: options.json, jq: options.jq });
|
|
23
|
+
if (!result.successful) process.exit(1);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
formatError(err);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { DboClient } from '../lib/client.js';
|
|
4
|
+
import { formatResponse, formatError } from '../lib/formatter.js';
|
|
5
|
+
import { saveToDisk } from '../lib/save-to-disk.js';
|
|
6
|
+
import { log } from '../lib/logger.js';
|
|
7
|
+
|
|
8
|
+
function collect(value, previous) {
|
|
9
|
+
return previous.concat([value]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const outputCommand = new Command('output')
|
|
13
|
+
.description('Query data from DBO.io outputs or entities')
|
|
14
|
+
.argument('[uid]', 'Output UID')
|
|
15
|
+
.option('-e, --entity <uid>', 'Query by entity UID')
|
|
16
|
+
.option('--row <id>', 'Specific row ID')
|
|
17
|
+
.option('--filter <expr>', 'Filter expression (repeatable)', collect, [])
|
|
18
|
+
.option('--format <type>', 'Output format: json, html, csv, xml, txt, pdf')
|
|
19
|
+
.option('--sort <expr>', 'Sort expression (repeatable)', collect, [])
|
|
20
|
+
.option('--search <expr>', 'Full-text search')
|
|
21
|
+
.option('--page <n>', 'Page number')
|
|
22
|
+
.option('--rows-per-page <n>', 'Rows per page')
|
|
23
|
+
.option('--maxrows <n>', 'Maximum rows')
|
|
24
|
+
.option('--rows <range>', 'Row range (e.g., 1-10)')
|
|
25
|
+
.option('--template <value>', 'Custom template')
|
|
26
|
+
.option('--debug', 'Include debug info')
|
|
27
|
+
.option('--debug-sql', 'Include SQL debug info')
|
|
28
|
+
.option('--meta', 'Use meta output endpoint')
|
|
29
|
+
.option('--meta-column <uid>', 'Column metadata')
|
|
30
|
+
.option('--save', 'Interactive save-to-disk mode')
|
|
31
|
+
.option('--save-filename <col>', 'Column for filenames (non-interactive)')
|
|
32
|
+
.option('--save-path <col>', 'Column for file path (non-interactive)')
|
|
33
|
+
.option('--save-content <col>', 'Column for file content (non-interactive)')
|
|
34
|
+
.option('--save-extension <col>', 'Column or literal extension (non-interactive)')
|
|
35
|
+
.option('--columns <list>', 'Comma-separated columns to display (e.g., UserID,FirstName,Email)')
|
|
36
|
+
.option('--json', 'Output raw JSON')
|
|
37
|
+
.option('--jq <expr>', 'Filter JSON response with jq-like expression (implies --json)')
|
|
38
|
+
.option('-v, --verbose', 'Show HTTP request details')
|
|
39
|
+
.option('--domain <host>', 'Override domain')
|
|
40
|
+
.action(async (uid, options) => {
|
|
41
|
+
try {
|
|
42
|
+
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
43
|
+
|
|
44
|
+
// Build request path
|
|
45
|
+
let path;
|
|
46
|
+
if (options.metaColumn) {
|
|
47
|
+
path = `/api/output/meta/column/${options.metaColumn}`;
|
|
48
|
+
} else if (options.meta && options.entity) {
|
|
49
|
+
path = `/api/output/meta/entity/${options.entity}`;
|
|
50
|
+
} else if (options.entity) {
|
|
51
|
+
path = options.row
|
|
52
|
+
? `/api/output/entity/${options.entity}/${options.row}`
|
|
53
|
+
: `/api/output/entity/${options.entity}`;
|
|
54
|
+
} else if (uid) {
|
|
55
|
+
path = `/api/output/${uid}`;
|
|
56
|
+
} else {
|
|
57
|
+
console.error('Error: provide an output UID or use --entity <uid>');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build query params — default to json_raw for normalized data
|
|
62
|
+
const params = { '_format': options.format || 'json_raw' };
|
|
63
|
+
if (options.search) params['_search'] = options.search;
|
|
64
|
+
if (options.page) params['_page'] = options.page;
|
|
65
|
+
if (options.rowsPerPage) params['_RowsPerPage'] = options.rowsPerPage;
|
|
66
|
+
if (options.maxrows) params['_maxrows'] = options.maxrows;
|
|
67
|
+
if (options.rows) params['_rows'] = options.rows;
|
|
68
|
+
if (options.template) params['_template'] = options.template;
|
|
69
|
+
if (options.debug) params['_debug'] = 'true';
|
|
70
|
+
if (options.debugSql) params['_debug_sql'] = 'true';
|
|
71
|
+
|
|
72
|
+
for (const f of options.filter) {
|
|
73
|
+
const atIdx = f.indexOf('=');
|
|
74
|
+
if (atIdx !== -1) {
|
|
75
|
+
const key = f.substring(0, atIdx);
|
|
76
|
+
const value = f.substring(atIdx + 1);
|
|
77
|
+
// Support key:modifier=value or key=value
|
|
78
|
+
if (key.includes(':')) {
|
|
79
|
+
const [col, mod] = key.split(':');
|
|
80
|
+
params[`_filter@${col}:${mod}`] = value;
|
|
81
|
+
} else {
|
|
82
|
+
params[`_filter@${key}`] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const s of options.sort) {
|
|
88
|
+
const [col, dir] = s.includes(':') ? s.split(':') : [s, 'asc'];
|
|
89
|
+
params[`_sort@${col}`] = dir;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// For save mode or jq mode, force json_raw if user didn't set a custom format
|
|
93
|
+
if ((options.save || options.saveFilename || options.jq) && !options.format) {
|
|
94
|
+
params['_format'] = 'json_raw';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await client.get(path, params);
|
|
98
|
+
|
|
99
|
+
// Save to disk mode
|
|
100
|
+
if (options.save || options.saveFilename) {
|
|
101
|
+
log.success(`Pulled from ${chalk.underline(result.url || '')}`);
|
|
102
|
+
const data = result.payload || result.data;
|
|
103
|
+
const rows = Array.isArray(data) ? data : (data?.Rows || data?.rows || [data]);
|
|
104
|
+
if (rows.length === 0) {
|
|
105
|
+
log.warn('No records found.');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
log.info(`${rows.length} record(s) to save`);
|
|
109
|
+
const columns = Object.keys(rows[0]);
|
|
110
|
+
await saveToDisk(rows, columns, {
|
|
111
|
+
entity: options.entity || null,
|
|
112
|
+
saveFilename: options.saveFilename,
|
|
113
|
+
savePath: options.savePath,
|
|
114
|
+
saveContent: options.saveContent,
|
|
115
|
+
saveExtension: options.saveExtension,
|
|
116
|
+
nonInteractive: !!options.saveFilename,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default to colorized JSON when using json_raw (no explicit --format, --json, or --jq)
|
|
122
|
+
const jq = options.jq || (!options.format && !options.json ? '.' : undefined);
|
|
123
|
+
formatResponse(result, { json: options.json, jq, columns: options.columns });
|
|
124
|
+
if (!result.successful && !result.ok) process.exit(1);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
formatError(err);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { DboClient } from '../lib/client.js';
|
|
4
|
+
import { formatError } from '../lib/formatter.js';
|
|
5
|
+
import { saveToDisk } from '../lib/save-to-disk.js';
|
|
6
|
+
import { log } from '../lib/logger.js';
|
|
7
|
+
|
|
8
|
+
function collect(value, previous) {
|
|
9
|
+
return previous.concat([value]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const pullCommand = new Command('pull')
|
|
13
|
+
.description('Pull records from DBO.io to local files (default entity: content)')
|
|
14
|
+
.argument('[uid]', 'Record UID (optional)')
|
|
15
|
+
.option('-e, --entity <uid>', 'Entity to pull from (default: content)', 'content')
|
|
16
|
+
.option('--filter <expr>', 'Filter expression (repeatable)', collect, [])
|
|
17
|
+
.option('--maxrows <n>', 'Maximum rows to pull')
|
|
18
|
+
.option('--sort <expr>', 'Sort expression (repeatable)', collect, [])
|
|
19
|
+
.option('-v, --verbose', 'Show HTTP request details')
|
|
20
|
+
.option('--domain <host>', 'Override domain')
|
|
21
|
+
.action(async (uid, options) => {
|
|
22
|
+
try {
|
|
23
|
+
const client = new DboClient({ domain: options.domain, verbose: options.verbose });
|
|
24
|
+
const entity = options.entity;
|
|
25
|
+
const params = { '_format': 'json_raw' };
|
|
26
|
+
if (options.maxrows) params['_maxrows'] = options.maxrows;
|
|
27
|
+
|
|
28
|
+
for (const f of options.filter) {
|
|
29
|
+
const atIdx = f.indexOf('=');
|
|
30
|
+
if (atIdx !== -1) {
|
|
31
|
+
const key = f.substring(0, atIdx);
|
|
32
|
+
const value = f.substring(atIdx + 1);
|
|
33
|
+
if (key.includes(':')) {
|
|
34
|
+
const [col, mod] = key.split(':');
|
|
35
|
+
params[`_filter@${col}:${mod}`] = value;
|
|
36
|
+
} else {
|
|
37
|
+
params[`_filter@${key}`] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const s of options.sort) {
|
|
43
|
+
const [col, dir] = s.includes(':') ? s.split(':') : [s, 'asc'];
|
|
44
|
+
params[`_sort@${col}`] = dir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let path;
|
|
48
|
+
if (uid) {
|
|
49
|
+
path = `/api/output/entity/${entity}/${uid}`;
|
|
50
|
+
} else {
|
|
51
|
+
path = `/api/output/entity/${entity}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = await client.get(path, params);
|
|
55
|
+
|
|
56
|
+
log.success(`Pulled from ${chalk.underline(result.url || '')}`);
|
|
57
|
+
|
|
58
|
+
const data = result.payload || result.data;
|
|
59
|
+
|
|
60
|
+
// Validate we got actual JSON data, not raw HTML
|
|
61
|
+
if (data && data.raw && typeof data.raw === 'string') {
|
|
62
|
+
log.error(`Received non-JSON response. Check that entity "${entity}" exists.`);
|
|
63
|
+
log.dim(` Hint: use --entity (double dash) not -entity`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const rows = Array.isArray(data) ? data : (data?.Rows || data?.rows || [data]);
|
|
68
|
+
|
|
69
|
+
if (rows.length === 0) {
|
|
70
|
+
log.warn('No records found.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
log.info(`${rows.length} record(s) to save`);
|
|
75
|
+
|
|
76
|
+
const columns = Object.keys(rows[0]);
|
|
77
|
+
|
|
78
|
+
if (columns.length <= 1) {
|
|
79
|
+
log.error(`Response has no usable columns. Check the entity name and UID.`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Content entity has well-known defaults — skip prompts
|
|
84
|
+
const isContent = entity === 'content';
|
|
85
|
+
|
|
86
|
+
if (isContent) {
|
|
87
|
+
await saveToDisk(rows, columns, {
|
|
88
|
+
entity,
|
|
89
|
+
saveFilename: columns.includes('Name') ? 'Name' : 'UID',
|
|
90
|
+
savePath: columns.includes('Path') ? 'Path' : null,
|
|
91
|
+
saveContent: columns.includes('Content') ? 'Content' : null,
|
|
92
|
+
saveExtension: columns.includes('Extension') ? 'Extension' : null,
|
|
93
|
+
nonInteractive: false, // still prompt for overwrites
|
|
94
|
+
contentFileMap: columns.includes('Content') ? {
|
|
95
|
+
Content: { suffix: '', extensionSource: columns.includes('Extension') ? 'Extension' : null, extension: 'txt' },
|
|
96
|
+
} : null,
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
// Other entities: interactive prompts for column selection
|
|
100
|
+
await saveToDisk(rows, columns, {
|
|
101
|
+
entity,
|
|
102
|
+
nonInteractive: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
formatError(err);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|