From 644d0b757bd802fa7589a8e08e0d6f5f7e83cc70 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 12 Mar 2026 16:29:47 +0100 Subject: [PATCH] feat: support snapshot version in object commands --- src/cli-core.ts | 15 ++++++++++----- src/lib/ls.ts | 6 ++++++ src/lib/objects/get.ts | 7 +++++++ src/lib/objects/list.ts | 6 ++++++ src/lib/stat.ts | 23 +++++++++++++---------- src/specs.yaml | 12 ++++++++++++ 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/cli-core.ts b/src/cli-core.ts index e825109..c42fe0d 100644 --- a/src/cli-core.ts +++ b/src/cli-core.ts @@ -59,8 +59,9 @@ export function formatArgumentHelp(arg: Argument): string { optionPart = ` ${arg.name}`; } else { optionPart = ` --${arg.name}`; - if (arg.alias && typeof arg.alias === 'string' && arg.alias.length === 1) { - optionPart += `, -${arg.alias}`; + if (arg.alias && typeof arg.alias === 'string') { + optionPart += + arg.alias.length === 1 ? `, -${arg.alias}` : `, --${arg.alias}`; } } @@ -222,11 +223,15 @@ export function addArgumentsToCommand( const argumentName = arg.required ? `<${arg.name}>` : `[${arg.name}]`; cmd.argument(argumentName, arg.description); } else { - const hasValidShortOption = + const isShortAlias = arg.alias && typeof arg.alias === 'string' && arg.alias.length === 1; - let optionString = hasValidShortOption + const isLongAlias = + arg.alias && typeof arg.alias === 'string' && arg.alias.length > 1; + let optionString = isShortAlias ? `-${arg.alias}, --${arg.name}` - : `--${arg.name}`; + : isLongAlias + ? `--${arg.alias}, --${arg.name}` + : `--${arg.name}`; if (arg.type === 'flag') { // Flags don't take values diff --git a/src/lib/ls.ts b/src/lib/ls.ts index ed5b940..a7682ce 100644 --- a/src/lib/ls.ts +++ b/src/lib/ls.ts @@ -6,6 +6,11 @@ import { list, listBuckets } from '@tigrisdata/storage'; export default async function ls(options: Record) { const pathString = getOption(options, ['path']); + const snapshotVersion = getOption(options, [ + 'snapshot-version', + 'snapshotVersion', + 'snapshot', + ]); if (!pathString) { // No path provided, list all buckets @@ -45,6 +50,7 @@ export default async function ls(options: Record) { const { data, error } = await list({ prefix, + ...(snapshotVersion ? { snapshotVersion } : {}), config: { ...config, bucket, diff --git a/src/lib/objects/get.ts b/src/lib/objects/get.ts index 1c8469f..7c7ae16 100644 --- a/src/lib/objects/get.ts +++ b/src/lib/objects/get.ts @@ -112,6 +112,11 @@ export default async function getObject(options: Record) { const key = getOption(options, ['key']); const output = getOption(options, ['output', 'o', 'O']); const modeOption = getOption(options, ['mode', 'm', 'M']); + const snapshotVersion = getOption(options, [ + 'snapshot-version', + 'snapshotVersion', + 'snapshot', + ]); if (!bucket) { printFailure(context, 'Bucket name is required'); @@ -130,6 +135,7 @@ export default async function getObject(options: Record) { if (mode === 'stream') { const { data, error } = await get(key, 'stream', { + ...(snapshotVersion ? { snapshotVersion } : {}), config: { ...config, bucket, @@ -152,6 +158,7 @@ export default async function getObject(options: Record) { } } else { const { data, error } = await get(key, 'string', { + ...(snapshotVersion ? { snapshotVersion } : {}), config: { ...config, bucket, diff --git a/src/lib/objects/list.ts b/src/lib/objects/list.ts index af8ccdc..5f83235 100644 --- a/src/lib/objects/list.ts +++ b/src/lib/objects/list.ts @@ -18,6 +18,11 @@ export default async function listObjects(options: Record) { const bucket = getOption(options, ['bucket']); const prefix = getOption(options, ['prefix', 'p', 'P']); const format = getOption(options, ['format', 'f', 'F'], 'table'); + const snapshotVersion = getOption(options, [ + 'snapshot-version', + 'snapshotVersion', + 'snapshot', + ]); if (!bucket) { printFailure(context, 'Bucket name is required'); @@ -28,6 +33,7 @@ export default async function listObjects(options: Record) { const { data, error } = await list({ prefix: prefix || undefined, + ...(snapshotVersion ? { snapshotVersion } : {}), config: { ...config, bucket, diff --git a/src/lib/stat.ts b/src/lib/stat.ts index 7bfd51c..76abb67 100644 --- a/src/lib/stat.ts +++ b/src/lib/stat.ts @@ -1,4 +1,5 @@ import { parseAnyPath } from '../utils/path.js'; +import { getOption } from '../utils/options.js'; import { formatOutput, formatSize } from '../utils/format.js'; import { getStorageConfig } from '../auth/s3-client.js'; import { getStats, getBucketInfo, head } from '@tigrisdata/storage'; @@ -12,15 +13,16 @@ import { buildBucketInfo } from '../utils/bucket-info.js'; const context = msg('stat'); -export default async function stat(options: { - path?: string; - format?: string; - _positional?: string[]; -}) { +export default async function stat(options: Record) { printStart(context); - const pathString = options.path || options._positional?.[0]; - const format = options.format || 'table'; + const pathString = getOption(options, ['path']); + const format = getOption(options, ['format'], 'table'); + const snapshotVersion = getOption(options, [ + 'snapshot-version', + 'snapshotVersion', + 'snapshot', + ]); const config = await getStorageConfig(); // No path: show overall stats @@ -45,7 +47,7 @@ export default async function stat(options: { }, ]; - const output = formatOutput(stats, format, 'stats', 'stat', [ + const output = formatOutput(stats, format!, 'stats', 'stat', [ { key: 'metric', header: 'Metric' }, { key: 'value', header: 'Value' }, ]); @@ -76,7 +78,7 @@ export default async function stat(options: { value, })); - const output = formatOutput(info, format, 'bucket-info', 'info', [ + const output = formatOutput(info, format!, 'bucket-info', 'info', [ { key: 'metric', header: 'Metric' }, { key: 'value', header: 'Value' }, ]); @@ -88,6 +90,7 @@ export default async function stat(options: { // Object path: show object metadata const { data, error } = await head(path, { + ...(snapshotVersion ? { snapshotVersion } : {}), config: { ...config, bucket, @@ -112,7 +115,7 @@ export default async function stat(options: { { metric: 'Modified', value: data.modified.toISOString() }, ]; - const output = formatOutput(info, format, 'object-info', 'info', [ + const output = formatOutput(info, format!, 'object-info', 'info', [ { key: 'metric', header: 'Metric' }, { key: 'value', header: 'Value' }, ]); diff --git a/src/specs.yaml b/src/specs.yaml index 2ef0d08..4a96fe6 100644 --- a/src/specs.yaml +++ b/src/specs.yaml @@ -249,6 +249,9 @@ commands: - my-bucket/my-path - t3://my-bucket - t3://my-bucket/my-path + - name: snapshot-version + description: Read from a specific bucket snapshot. Accepts a snapshot version string or any UNIX nanosecond-precision timestamp (e.g. 1765889000501544464) + alias: snapshot # mk - name: mk @@ -347,6 +350,9 @@ commands: alias: f options: [json, table, xml] default: table + - name: snapshot-version + description: Read from a specific bucket snapshot. Accepts a snapshot version string or any UNIX nanosecond-precision timestamp (e.g. 1765889000501544464) + alias: snapshot # presign - name: presign @@ -1086,6 +1092,9 @@ commands: alias: f options: [json, table, xml] default: table + - name: snapshot-version + description: Read from a specific bucket snapshot. Accepts a snapshot version string or any UNIX nanosecond-precision timestamp (e.g. 1765889000501544464) + alias: snapshot # get - name: get description: Download an object by key. Prints to stdout by default, or saves to a file with --output @@ -1117,6 +1126,9 @@ commands: description: 'Response mode: "string" loads into memory, "stream" writes in chunks (auto-detected from extension if not specified)' alias: m options: [string, stream] + - name: snapshot-version + description: Read from a specific bucket snapshot. Accepts a snapshot version string or any UNIX nanosecond-precision timestamp (e.g. 1765889000501544464) + alias: snapshot # put - name: put description: Upload a local file as an object. Content-type is auto-detected from extension unless overridden