diff --git a/src/commands/metrics.ts b/src/commands/metrics.ts index abe4dfb..5209f18 100644 --- a/src/commands/metrics.ts +++ b/src/commands/metrics.ts @@ -78,10 +78,30 @@ export async function usage(opts: { from?: string; to?: string; json?: boolean; } } +// pure: platform path for a compute deploy-events request (used by `insta logs --deploy`). +export function deployEventsPath(projectId: string, opts: { group?: string; branch?: string; limit?: string; instance?: string }): string { + return `/projects/${projectId}/deploy-events${qs({ group: opts.group, branch: opts.branch, limit: opts.limit, instance: opts.instance })}` +} + +// pure: render one deploy event as a log-style line. +export function deployEventLine(ev: { ts?: string; origin?: string; type?: string; status?: string; instance?: string }): string { + const inst = ev.instance ? ` (${ev.instance})` : '' + return `${ev.ts ?? ''} [${ev.origin ?? ''}] ${ev.type ?? ''}: ${ev.status ?? ''}${inst}` +} + // insta logs [group] -export async function logs(component: string, group: string | undefined, opts: { branch?: string; limit?: string; region?: string; instance?: string; json?: boolean }): Promise { +export async function logs(component: string, group: string | undefined, opts: { branch?: string; limit?: string; region?: string; instance?: string; json?: boolean; deploy?: boolean }): Promise { const api = await ApiClient.load() const p = await requireProject() + if (opts.deploy) { + if (component !== 'compute') return info('deploy events are only available for compute') + const res = await api.request('GET', deployEventsPath(p.projectId, { group, branch: opts.branch ?? p.branch, limit: opts.limit, instance: opts.instance })) + if (opts.json) return printJson(res) + if (res.note) info(`note: ${res.note}`) + if (!res.events?.length) return info('(no deploy events)') + for (const ev of res.events) info(deployEventLine(ev)) + return + } const res = await api.request('GET', `/projects/${p.projectId}/logs${qs({ component, group, branch: opts.branch ?? p.branch, limit: opts.limit, region: opts.region, instance: opts.instance })}`) if (opts.json) return printJson(res) if (res.note) info(`note: ${res.note}`) diff --git a/src/index.ts b/src/index.ts index b494e9b..8ccdc6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -173,8 +173,8 @@ program.command('regions').description('List regions available for postgres/comp program.command('metrics [group]').description('Service metrics (target: db|compute)') .option('--branch ').option('--from ').option('--to ').option('--step ').option('--json') .action(guard((target, group, o) => obs.metrics(target, group, o))) -program.command('logs [group]').description('Service runtime logs (target: db|compute)') - .option('--branch ').option('--limit ').option('--region ').option('--instance ').option('--json') +program.command('logs [group]').description('Service logs (runtime by default; --deploy = compute deploy events; target: db|compute)') + .option('--branch ').option('--limit ').option('--region ').option('--instance ').option('--deploy', 'show compute deploy events (machine lifecycle) instead of runtime logs').option('--json') .action(guard((target, group, o) => obs.logs(target, group, o))) program.command('usage').description('Usage for the current billing cycle by billing dimension (org by default; --proj for one project)') .option('--from ').option('--to ').option('--proj [id]', 'show one project (the linked one, or a given id) instead of the whole org').option('--json') diff --git a/test/logs-deploy.test.ts b/test/logs-deploy.test.ts new file mode 100644 index 0000000..d64e482 --- /dev/null +++ b/test/logs-deploy.test.ts @@ -0,0 +1,24 @@ +// `insta logs --deploy` targets /deploy-events and renders each machine event as one line. +import { describe, it, expect } from 'vitest' +import { deployEventsPath, deployEventLine } from '../src/commands/metrics.js' + +describe('deployEventsPath', () => { + it('targets /deploy-events with defined params in order', () => { + expect(deployEventsPath('p1', { group: 'api', branch: 'main', limit: '50' })) + .toBe('/projects/p1/deploy-events?group=api&branch=main&limit=50') + }) + it('omits undefined/empty params', () => { + expect(deployEventsPath('p1', {})).toBe('/projects/p1/deploy-events') + }) +}) + +describe('deployEventLine', () => { + it('renders [origin] type: status with the instance suffix', () => { + expect(deployEventLine({ ts: 'T', origin: 'flyd', type: 'start', status: 'started', instance: 'm-1' })) + .toBe('T [flyd] start: started (m-1)') + }) + it('omits the instance suffix when absent', () => { + expect(deployEventLine({ ts: 'T', origin: 'user', type: 'launch', status: 'created' })) + .toBe('T [user] launch: created') + }) +})