Skip to content

Commit 04c0db6

Browse files
authored
Merge pull request #14 from DataZooDE/feature/gh-11-cli-config-update
Update fix smaller issues with the config cli and the vscode extension
2 parents c4600d2 + 430bb7c commit 04c0db6

18 files changed

Lines changed: 732 additions & 109 deletions

File tree

cli/src/commands/cache/index.ts

Lines changed: 137 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,30 @@ export function registerCacheCommands(program: Command, ctx: CliContext) {
7171
if (typeof cacheConfig === 'object' && cacheConfig !== null) {
7272
const config = cacheConfig as Record<string, any>;
7373
Console.info(chalk.bold.blue('Enabled: ') + chalk.white(config.enabled ? 'Yes' : 'No'));
74-
Console.info(chalk.bold.blue('TTL: ') + chalk.white(config.ttl || 'N/A'));
75-
Console.info(chalk.bold.blue('Max Size: ') + chalk.white(config.max_size || 'N/A'));
76-
Console.info(chalk.bold.blue('Strategy: ') + chalk.white(config.strategy || 'N/A'));
74+
Console.info(chalk.bold.blue('Table: ') + chalk.white(config.table || 'N/A'));
75+
Console.info(chalk.bold.blue('Schema: ') + chalk.white(config.schema || 'N/A'));
76+
Console.info(chalk.bold.blue('Schedule: ') + chalk.white(config.schedule || 'N/A'));
77+
if (Array.isArray(config['primary-key'])) {
78+
Console.info(chalk.bold.blue('Primary Key: ') + chalk.white(config['primary-key'].join(', ')));
79+
}
80+
if (config.cursor) {
81+
Console.info(
82+
chalk.bold.blue('Cursor: ') +
83+
chalk.white(`${config.cursor.column || 'N/A'} (${config.cursor.type || 'unknown'})`)
84+
);
85+
}
86+
if (config['rollback-window']) {
87+
Console.info(chalk.bold.blue('Rollback Window: ') + chalk.white(config['rollback-window']));
88+
}
89+
if (config.retention) {
90+
Console.info(chalk.bold.blue('Retention: ') + chalk.white(JSON.stringify(config.retention)));
91+
}
92+
if (config['delete-handling']) {
93+
Console.info(chalk.bold.blue('Delete Handling: ') + chalk.white(config['delete-handling']));
94+
}
95+
if (config['template-file']) {
96+
Console.info(chalk.bold.blue('Template File: ') + chalk.white(config['template-file']));
97+
}
7798
} else {
7899
Console.info(chalk.gray('No cache configuration found'));
79100
}
@@ -89,16 +110,32 @@ export function registerCacheCommands(program: Command, ctx: CliContext) {
89110
.command('update <path>')
90111
.description('Update cache configuration')
91112
.option('-e, --enabled <enabled>', 'Enable/disable caching (true/false)')
92-
.option('-t, --ttl <ttl>', 'Cache TTL in seconds')
93-
.option('-s, --max-size <size>', 'Maximum cache size')
94-
.option('--strategy <strategy>', 'Cache strategy (lru, ttl, etc.)')
113+
.option('--table <table>', 'Cache table name')
114+
.option('--schema <schema>', 'Cache schema name')
115+
.option('--schedule <cron>', 'Cache refresh schedule or interval string')
116+
.option('--primary-key <columns...>', 'Primary key columns (space separated)')
117+
.option('--cursor-column <column>', 'Cursor column name')
118+
.option('--cursor-type <type>', 'Cursor data type')
119+
.option('--rollback-window <duration>', 'Rollback window (e.g. 6h)')
120+
.option('--retention-keep <count>', 'Number of snapshots to keep')
121+
.option('--retention-age <duration>', 'Max snapshot age (e.g. 7d)')
122+
.option('--delete-handling <mode>', 'Delete handling strategy')
123+
.option('--template-file <path>', 'Cache template SQL file')
95124
.option('-f, --file <file>', 'JSON file containing cache configuration')
96125
.option('--stdin', 'Read cache configuration from stdin')
97126
.action(async (path: string, options: {
98127
enabled?: string;
99-
ttl?: string;
100-
maxSize?: string;
101-
strategy?: string;
128+
table?: string;
129+
schema?: string;
130+
schedule?: string;
131+
primaryKey?: string[];
132+
cursorColumn?: string;
133+
cursorType?: string;
134+
rollbackWindow?: string;
135+
retentionKeep?: string;
136+
retentionAge?: string;
137+
deleteHandling?: string;
138+
templateFile?: string;
102139
file?: string;
103140
stdin?: boolean;
104141
}) => {
@@ -138,14 +175,46 @@ export function registerCacheCommands(program: Command, ctx: CliContext) {
138175
if (options.enabled !== undefined) {
139176
cacheConfig.enabled = options.enabled === 'true';
140177
}
141-
if (options.ttl !== undefined) {
142-
cacheConfig.ttl = parseInt(options.ttl);
178+
if (options.table) {
179+
cacheConfig.table = options.table;
180+
}
181+
if (options.schema) {
182+
cacheConfig.schema = options.schema;
183+
}
184+
if (options.schedule) {
185+
cacheConfig.schedule = options.schedule;
186+
}
187+
if (options.primaryKey && options.primaryKey.length > 0) {
188+
cacheConfig['primary-key'] = options.primaryKey;
189+
}
190+
if ((options.cursorColumn && !options.cursorType) || (!options.cursorColumn && options.cursorType)) {
191+
Console.error('Both --cursor-column and --cursor-type must be provided together');
192+
process.exitCode = 1;
193+
return;
194+
}
195+
if (options.cursorColumn && options.cursorType) {
196+
cacheConfig.cursor = {
197+
column: options.cursorColumn,
198+
type: options.cursorType,
199+
};
200+
}
201+
if (options.rollbackWindow) {
202+
cacheConfig['rollback-window'] = options.rollbackWindow;
203+
}
204+
if (options.retentionKeep || options.retentionAge) {
205+
cacheConfig.retention = {};
206+
if (options.retentionKeep) {
207+
cacheConfig.retention['keep-last-snapshots'] = Number(options.retentionKeep);
208+
}
209+
if (options.retentionAge) {
210+
cacheConfig.retention['max-snapshot-age'] = options.retentionAge;
211+
}
143212
}
144-
if (options.maxSize !== undefined) {
145-
cacheConfig.max_size = parseInt(options.maxSize);
213+
if (options.deleteHandling) {
214+
cacheConfig['delete-handling'] = options.deleteHandling;
146215
}
147-
if (options.strategy !== undefined) {
148-
cacheConfig.strategy = options.strategy;
216+
if (options.templateFile) {
217+
cacheConfig['template-file'] = options.templateFile;
149218
}
150219

151220
if (Object.keys(cacheConfig).length === 0) {
@@ -176,14 +245,14 @@ export function registerCacheCommands(program: Command, ctx: CliContext) {
176245
const endpointUrl = buildEndpointUrl(path, 'cache/template');
177246
const response = await ctx.client.get(endpointUrl);
178247
spinner.succeed(chalk.green(`✓ Cache template for ${path} retrieved`));
179-
const template = response.data;
248+
const template = typeof response.data === 'string' ? response.data : response.data?.template;
180249

181250
if (ctx.config.output === 'json') {
182-
renderJson(template, ctx.config.jsonStyle);
251+
renderJson({ template: template ?? '' }, ctx.config.jsonStyle);
183252
} else {
184253
Console.info(chalk.cyan(`\n📄 Cache Template: ${path}`));
185254
Console.info(chalk.gray('═'.repeat(60)));
186-
Console.info(chalk.white(template));
255+
Console.info(chalk.white(template ?? ''));
187256
}
188257
} catch (error) {
189258
spinner.fail(chalk.red(`✗ Failed to fetch cache template for ${path}`));
@@ -256,23 +325,65 @@ export function registerCacheCommands(program: Command, ctx: CliContext) {
256325
const result = response.data;
257326

258327
if (ctx.config.output === 'json') {
259-
renderJson(result, ctx.config.jsonStyle);
328+
renderJson(result || { success: true }, ctx.config.jsonStyle);
260329
} else {
261330
Console.info(chalk.cyan(`\n🔄 Cache Refresh: ${path}`));
262331
Console.info(chalk.gray('═'.repeat(60)));
263332
Console.info(chalk.green('✓ Cache refreshed successfully'));
264-
265-
if (result.entries_cleared !== undefined) {
266-
Console.info(chalk.blue(`Entries cleared: ${result.entries_cleared}`));
267-
}
268-
if (result.cache_size !== undefined) {
269-
Console.info(chalk.blue(`New cache size: ${result.cache_size}`));
270-
}
271333
}
272334
} catch (error) {
273335
spinner.fail(chalk.red(`✗ Failed to refresh cache for ${path}`));
274336
handleError(error, ctx.config);
275337
process.exitCode = 1;
276338
}
277339
});
340+
341+
cache
342+
.command('gc <path>')
343+
.description('Run DuckLake garbage collection for endpoint cache')
344+
.action(async (path: string) => {
345+
const spinner = Console.spinner(`Running cache GC for ${path}...`);
346+
try {
347+
const endpointUrl = buildEndpointUrl(path, 'cache/gc');
348+
await ctx.client.post(endpointUrl);
349+
spinner.succeed(chalk.green(`✓ Cache GC triggered for ${path}`));
350+
} catch (error) {
351+
spinner.fail(chalk.red(`✗ Failed to run cache GC for ${path}`));
352+
handleError(error, ctx.config);
353+
process.exitCode = 1;
354+
}
355+
});
356+
357+
cache
358+
.command('audit <path>')
359+
.description('Show DuckLake audit log entries for an endpoint')
360+
.action(async (path: string) => {
361+
const spinner = Console.spinner(`Fetching cache audit for ${path}...`);
362+
try {
363+
const endpointUrl = buildEndpointUrl(path, 'cache/audit');
364+
const response = await ctx.client.get(endpointUrl);
365+
spinner.succeed(chalk.green(`✓ Cache audit for ${path} retrieved`));
366+
renderJson(response.data, ctx.config.jsonStyle);
367+
} catch (error) {
368+
spinner.fail(chalk.red(`✗ Failed to fetch cache audit for ${path}`));
369+
handleError(error, ctx.config);
370+
process.exitCode = 1;
371+
}
372+
});
373+
374+
cache
375+
.command('audit-all')
376+
.description('Show DuckLake audit log across all caches')
377+
.action(async () => {
378+
const spinner = Console.spinner('Fetching cache audit log...');
379+
try {
380+
const response = await ctx.client.get('/api/v1/_config/cache/audit');
381+
spinner.succeed(chalk.green('✓ Cache audit log retrieved'));
382+
renderJson(response.data, ctx.config.jsonStyle);
383+
} catch (error) {
384+
spinner.fail(chalk.red('✗ Failed to fetch cache audit log'));
385+
handleError(error, ctx.config);
386+
process.exitCode = 1;
387+
}
388+
});
278389
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Command } from 'commander';
2+
import type { CliContext } from '../../lib/types';
3+
import { Console } from '../../lib/console';
4+
import { handleError } from '../../lib/errors';
5+
import { renderJson } from '../../lib/render';
6+
import chalk from 'chalk';
7+
8+
export function registerEnvironmentCommand(config: Command, ctx: CliContext) {
9+
config
10+
.command('env')
11+
.description('Show environment variables exposed by the server')
12+
.action(async () => {
13+
const spinner = Console.spinner('Fetching environment variables...');
14+
try {
15+
const response = await ctx.client.get('/api/v1/_config/environment-variables');
16+
spinner.succeed(chalk.green('✓ Environment variables retrieved'));
17+
const payload = response.data;
18+
19+
if (ctx.config.output === 'json') {
20+
renderJson(payload, ctx.config.jsonStyle);
21+
return;
22+
}
23+
24+
Console.info(chalk.cyan('\n🌿 Environment Variables'));
25+
Console.info(chalk.gray('═'.repeat(60)));
26+
27+
const variables = Array.isArray(payload?.variables) ? payload.variables : [];
28+
if (variables.length === 0) {
29+
Console.info(chalk.gray('No environment variables are configured.'));
30+
return;
31+
}
32+
33+
variables.forEach((variable: any) => {
34+
const available = variable.available ? chalk.green('available') : chalk.red('missing');
35+
const value = variable.value ?? '';
36+
Console.info(`${chalk.bold(variable.name)} - ${available}`);
37+
if (value) {
38+
Console.info(chalk.gray(` Value: ${value}`));
39+
}
40+
});
41+
} catch (error) {
42+
spinner.fail(chalk.red('✗ Failed to fetch environment variables'));
43+
handleError(error, ctx.config);
44+
process.exitCode = 1;
45+
}
46+
});
47+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { Command } from 'commander';
2+
import type { CliContext } from '../../lib/types';
3+
import { Console } from '../../lib/console';
4+
import { handleError } from '../../lib/errors';
5+
import { renderJson } from '../../lib/render';
6+
import chalk from 'chalk';
7+
8+
interface FilesystemNode {
9+
name: string;
10+
type: 'file' | 'directory';
11+
path: string;
12+
children?: FilesystemNode[];
13+
extension?: string;
14+
yaml_type?: string;
15+
}
16+
17+
function renderTree(nodes: FilesystemNode[], prefix = '') {
18+
nodes.forEach((node, index) => {
19+
const isLast = index === nodes.length - 1;
20+
const branch = isLast ? '└─' : '├─';
21+
const nextPrefix = prefix + (isLast ? ' ' : '│ ');
22+
23+
const label = node.type === 'directory'
24+
? chalk.blue(`[dir] ${node.name}`)
25+
: chalk.white(node.name);
26+
27+
Console.info(`${prefix}${branch} ${label}`);
28+
29+
if (node.type === 'file' && node.yaml_type) {
30+
Console.info(`${nextPrefix}${chalk.gray(`(${node.yaml_type})`)}`);
31+
}
32+
33+
if (node.children && node.children.length > 0) {
34+
renderTree(node.children, nextPrefix);
35+
}
36+
});
37+
}
38+
39+
export function registerFilesystemCommand(config: Command, ctx: CliContext) {
40+
config
41+
.command('filesystem')
42+
.description('Inspect server-side filesystem tree for templates')
43+
.action(async () => {
44+
const spinner = Console.spinner('Fetching filesystem structure...');
45+
try {
46+
const response = await ctx.client.get('/api/v1/_config/filesystem');
47+
spinner.succeed(chalk.green('✓ Filesystem data retrieved'));
48+
const payload = response.data;
49+
50+
if (ctx.config.output === 'json') {
51+
renderJson(payload, ctx.config.jsonStyle);
52+
return;
53+
}
54+
55+
Console.info(chalk.cyan('\n📁 flapi Filesystem'));
56+
Console.info(chalk.gray('═'.repeat(60)));
57+
Console.info(`${chalk.bold('Base Path:')} ${payload?.base_path || 'N/A'}`);
58+
Console.info(`${chalk.bold('Templates Path:')} ${payload?.template_path || 'N/A'}`);
59+
if (payload?.config_file) {
60+
Console.info(`${chalk.bold('Config File:')} ${payload.config_file} (${payload?.config_file_exists ? 'found' : 'missing'})`);
61+
}
62+
Console.info('');
63+
64+
if (Array.isArray(payload?.tree) && payload.tree.length > 0) {
65+
renderTree(payload.tree);
66+
} else {
67+
Console.info(chalk.gray('No files discovered under the template path.'));
68+
}
69+
} catch (error) {
70+
spinner.fail(chalk.red('✗ Failed to fetch filesystem data'));
71+
handleError(error, ctx.config);
72+
process.exitCode = 1;
73+
}
74+
});
75+
}

cli/src/commands/config/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import type { CliContext } from '../../lib/types';
33
import { registerConfigCommand } from './show';
44
import { registerValidateCommand } from './validate';
55
import { registerLogLevelCommands } from './log-level';
6+
import { registerEnvironmentCommand } from './environment';
7+
import { registerFilesystemCommand } from './filesystem';
68

79
export function registerConfigCommands(program: Command, ctx: CliContext) {
810
const configCmd = registerConfigCommand(program, ctx);
911
registerValidateCommand(configCmd, ctx);
1012
registerLogLevelCommands(configCmd, ctx);
13+
registerEnvironmentCommand(configCmd, ctx);
14+
registerFilesystemCommand(configCmd, ctx);
1115
}
12-

0 commit comments

Comments
 (0)