chore: refactor how outputs are created in CLI
Some checks failed
Build and Publish Docker Image / Build and Publish Docker Image (amd64) (push) Has been cancelled
Build and Publish Docker Image / Build and Publish Docker Image (arm64) (push) Has been cancelled

This commit is contained in:
mrjvs 2025-09-23 22:31:32 +02:00
parent f8fca8f082
commit 5d426bcf94
4 changed files with 80 additions and 36 deletions

View File

@ -7,13 +7,21 @@ const listCmd = new Command('ls')
.action(commandHandler<[]>(async (cmd): Promise<void> => {
const ctx = getCliContext();
const { apps } = await ctx.grpc.listKnownBOSSApps({});
logOutputList(cmd.format, apps.map(v => ({
name: prettyTrunc(v.name, 20)
})), {
bossAppId: 'App ID',
name: 'Name',
titleId: 'Title ID',
titleRegion: 'Title region'
logOutputList(apps, {
format: cmd.format,
onlyIncludeKeys: ['bossAppId', 'name', 'tasks', 'titleRegion'],
mapping: {
bossAppId: 'App ID',
name: 'Name',
titleId: 'Title ID',
titleRegion: 'Title region'
},
prettify(key, value) {
if (key === 'name') {
return prettyTrunc(value, 20);
}
return value;
}
});
}));
@ -30,12 +38,15 @@ const viewCmd = new Command('view')
return;
}
logOutputObject(cmd.format, {
const obj = {
appId: app.bossAppId,
name: app.name,
titleId: app.titleId,
titleRegion: app.titleRegion,
knownTasks: app.tasks
};
logOutputObject(obj, {
format: cmd.format
});
}));

View File

@ -18,16 +18,16 @@ const listCmd = new Command('ls')
bossAppId: appId,
taskId: taskId
});
logOutputList(cmd.format, files.map(v => ({
...v,
size: v.size,
dataId: v.dataId
}), {
dataId: 'Data ID',
name: 'Name',
type: 'Type',
size: 'Size (bytes)'
}));
logOutputList(files, {
format: cmd.format,
onlyIncludeKeys: ['dataId', 'name', 'type', 'size'],
mapping: {
dataId: 'Data ID',
name: 'Name',
type: 'Type',
size: 'Size (bytes)'
}
});
}));
const viewCmd = new Command('view')
@ -47,7 +47,7 @@ const viewCmd = new Command('view')
console.log(`Could not find task file with data ID ${dataId} in task ${taskId}`);
return;
}
logOutputObject(cmd.format, {
logOutputObject({
dataId: file.dataId,
name: file.name,
type: file.type,
@ -62,6 +62,8 @@ const viewCmd = new Command('view')
},
createdAt: new Date(Number(file.createdTimestamp)),
updatedAt: new Date(Number(file.updatedTimestamp))
}, {
format: cmd.format
});
}));

View File

@ -1,11 +1,25 @@
export type FormattableObject = Record<string, any>;
export type FieldMapping = Record<string, string>;
export type FormatOption = 'json' | 'pretty';
function mapOutputObject(obj: FormattableObject, mapping: FieldMapping): FormattableObject {
function preprocessObject(obj: FormattableObject, ops: FormattedOutputOptions): FormattableObject {
const onlyIncludeKeys = ops.onlyIncludeKeys;
if (!onlyIncludeKeys) {
return obj;
}
const entries = Object.entries(obj);
const mappedEntries = entries.map((v) => {
const newKey = mapping[v[0]] ?? v[0];
const filteredEntries = entries.filter(v => onlyIncludeKeys.includes(v[0]));
return Object.fromEntries(filteredEntries);
}
function makePrettyOutputObject(obj: FormattableObject, ops: FormattedOutputOptions): FormattableObject {
const entries = Object.entries(obj);
const prettifiedEntries = entries.map((v) => {
const value = ops.prettify ? ops.prettify(v[0], v[1]) : v[1];
return [v[0], value];
});
const mappedEntries = prettifiedEntries.map((v) => {
const newKey = ops.mapping?.[v[0]] ?? v[0];
return [newKey, v[1]] as const;
});
return Object.fromEntries(mappedEntries);
@ -18,19 +32,30 @@ function jsonReplacer(key: string, value: any): any {
return value;
}
export function logOutputList<T extends FormattableObject>(format: FormatOption, items: T[], mapping: FieldMapping = {}): void {
if (format === 'json') {
console.log(JSON.stringify(items, jsonReplacer, 2));
export type FormattedOutputOptions<T extends FormattableObject = any> = {
format: FormatOption;
onlyIncludeKeys?: Array<keyof T>;
mapping?: Partial<Record<keyof T, string>>;
prettify?: (key: string, value: any) => any;
};
export function logOutputList<T extends FormattableObject>(items: T[], ops: FormattedOutputOptions<T>): void {
const processedItems = items.map(v => preprocessObject(v, ops));
if (ops.format === 'json') {
console.log(JSON.stringify(processedItems, jsonReplacer, 2));
return;
}
const mappedItems = items.map(item => mapOutputObject(item, mapping));
const mappedItems = processedItems.map(item => makePrettyOutputObject(item, ops));
console.table(mappedItems);
}
export function logOutputObject<T extends FormattableObject>(format: FormatOption, obj: T, mapping: FieldMapping = {}): void {
if (format === 'json') {
console.log(JSON.stringify(obj, jsonReplacer, 2));
export function logOutputObject<T extends FormattableObject>(obj: T, ops: FormattedOutputOptions<T>): void {
const processedObj = preprocessObject(obj, ops);
if (ops.format === 'json') {
console.log(JSON.stringify(processedObj, jsonReplacer, 2));
return;
}
console.log(mapOutputObject(obj, mapping));
console.log(makePrettyOutputObject(processedObj, ops));
}

View File

@ -10,10 +10,14 @@ const listCmd = new Command('ls')
const ctx = getCliContext();
const { tasks } = await ctx.grpc.listTasks({});
const filteredTasks = tasks.filter(v => v.bossAppId === appId);
logOutputList(cmd.format, filteredTasks, {
id: 'Task ID',
description: 'Description',
status: 'Status'
logOutputList(filteredTasks, {
format: cmd.format,
mapping: {
id: 'Task ID',
description: 'Description',
status: 'Status'
},
onlyIncludeKeys: ['id', 'description', 'status']
});
}));
@ -30,7 +34,7 @@ const viewCmd = new Command('view')
console.log(`Could not find task with ID ${taskId} in app ${appId}`);
return;
}
logOutputObject(cmd.format, {
logOutputObject({
taskId: task.id,
inGameId: task.inGameId,
description: task.description,
@ -40,6 +44,8 @@ const viewCmd = new Command('view')
status: task.status,
createdAt: new Date(Number(task.createdTimestamp)),
updatedAt: new Date(Number(task.updatedTimestamp))
}, {
format: cmd.format
});
}));