From fea32a56279ee8f5b7c1619b8866cef7ffa8af32 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 14:32:51 +0200 Subject: [PATCH 1/7] feat: tab-complete commands, buckets, and filesystem paths Adds a position-aware completer covering built-in bash commands (via just-bash's getCommandNames), REPL + custom commands, bucket names for mount/snapshot/fork/forks, mount points for umount/flush, and filesystem paths inside the virtual FS. Also ignore .claude/ session files so they don't trip the pre-commit biome check. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 1 + biome.json | 2 +- src/cli.ts | 25 ++--- src/repl/complete.ts | 146 ++++++++++++++++++++++++++++ src/repl/session.ts | 9 ++ tests/complete.test.ts | 216 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 381 insertions(+), 18 deletions(-) create mode 100644 src/repl/complete.ts create mode 100644 tests/complete.test.ts diff --git a/.gitignore b/.gitignore index 478c22e..c981ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ coverage *.tsbuildinfo .env .env.* +.claude/ diff --git a/biome.json b/biome.json index 29552c9..c0fc4dd 100644 --- a/biome.json +++ b/biome.json @@ -37,6 +37,6 @@ } }, "files": { - "includes": ["**", "!**/dist", "!**/node_modules", "!**/coverage"] + "includes": ["**", "!**/dist", "!**/node_modules", "!**/coverage", "!**/.claude"] } } diff --git a/src/cli.ts b/src/cli.ts index deff4dd..34ad8ee 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -70,27 +70,18 @@ async function main() { const args = parseArgs(rawArgs); const session = new ReplSession({ loginFn: deviceLogin }); - const REPL_COMMANDS = [ - "login", - "configure", - "mount", - "umount", - "df", - "flush", - "whoami", - "logout", - "clear", - "help", - "exit", - ]; - const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "$ ", - completer: (line: string) => { - const hits = REPL_COMMANDS.filter((cmd) => cmd.startsWith(line)); - return [hits.length ? hits : REPL_COMMANDS, line]; + completer: ( + line: string, + callback: (err: Error | null, result?: [string[], string]) => void, + ) => { + session.complete(line).then( + (result) => callback(null, result), + (err) => callback(err instanceof Error ? err : new Error(String(err))), + ); }, }); diff --git a/src/repl/complete.ts b/src/repl/complete.ts new file mode 100644 index 0000000..db8967b --- /dev/null +++ b/src/repl/complete.ts @@ -0,0 +1,146 @@ +import { getCommandNames } from "just-bash"; +import type { TigrisShell } from "../shell.js"; + +type ShellFs = TigrisShell["engine"]["fs"]; + +const REPL_COMMANDS = [ + "login", + "configure", + "mount", + "umount", + "df", + "flush", + "whoami", + "logout", + "clear", + "help", + "exit", + "quit", +]; + +const CUSTOM_COMMANDS = ["presign", "snapshot", "fork", "forks"]; + +const BUCKET_ARG_COMMANDS = new Set(["mount", "snapshot", "fork", "forks"]); +const MOUNT_POINT_ARG_COMMANDS = new Set(["umount", "flush"]); + +export interface CompleteContext { + shell: TigrisShell | null; + cwd: string | undefined; +} + +let cachedAllCommands: string[] | undefined; + +function allCommandNames(): string[] { + if (!cachedAllCommands) { + const seen = new Set(); + const ordered: string[] = []; + for (const name of [...REPL_COMMANDS, ...CUSTOM_COMMANDS, ...getCommandNames()]) { + if (seen.has(name)) continue; + seen.add(name); + ordered.push(name); + } + cachedAllCommands = ordered; + } + return cachedAllCommands; +} + +/** + * Compute completion candidates for a partial command line. + * + * Returns `[hits, completedToken]` in the shape that node:readline expects: + * readline appends `hit.slice(completedToken.length)` when there is a single hit. + */ +export async function computeCompletions( + line: string, + ctx: CompleteContext, +): Promise<[string[], string]> { + const match = /\s(\S*)$/.exec(line); + const tokenStart = match ? match.index + 1 : 0; + const currentToken = line.slice(tokenStart); + + const before = line.slice(0, tokenStart).trim(); + const argIndex = before === "" ? 0 : before.split(/\s+/).length; + + if (argIndex === 0) { + const all = allCommandNames(); + const hits = all.filter((c) => c.startsWith(currentToken)); + return [hits.length > 0 ? hits : all, currentToken]; + } + + const commandName = before.split(/\s+/)[0] ?? ""; + + if (BUCKET_ARG_COMMANDS.has(commandName) && argIndex === 1) { + const buckets = ctx.shell?.listMounts().map((m) => m.bucket) ?? []; + const unique = Array.from(new Set(buckets)); + return [unique.filter((b) => b.startsWith(currentToken)), currentToken]; + } + + if (MOUNT_POINT_ARG_COMMANDS.has(commandName) && argIndex === 1) { + const points = ctx.shell?.listMounts().map((m) => m.mountPoint) ?? []; + return [points.filter((p) => p.startsWith(currentToken)), currentToken]; + } + + return [await completePath(currentToken, ctx), currentToken]; +} + +interface ResolvedPath { + dir: string; + prefix: string; + displayDir: string; +} + +function resolvePathToken(token: string, cwd: string): ResolvedPath { + const lastSlash = token.lastIndexOf("/"); + if (lastSlash === -1) { + return { dir: cwd, prefix: token, displayDir: "" }; + } + const beforeSlash = token.slice(0, lastSlash); + const prefix = token.slice(lastSlash + 1); + const dir = token.startsWith("/") + ? beforeSlash === "" + ? "/" + : beforeSlash + : `${cwd}/${beforeSlash}`.replace(/\/\/+/g, "/"); + return { dir, prefix, displayDir: `${beforeSlash}/` }; +} + +interface DirEntry { + name: string; + isDirectory: boolean; +} + +async function readDirEntries(fs: ShellFs, dir: string): Promise { + if (typeof fs.readdirWithFileTypes === "function") { + const result = await fs.readdirWithFileTypes(dir); + return result.map((d) => ({ name: d.name, isDirectory: d.isDirectory })); + } + const names = await fs.readdir(dir); + return Promise.all( + names.map(async (name) => { + try { + const stat = await fs.stat(`${dir}/${name}`.replace(/\/\/+/g, "/")); + return { name, isDirectory: stat.isDirectory }; + } catch { + return { name, isDirectory: false }; + } + }), + ); +} + +async function completePath(token: string, ctx: CompleteContext): Promise { + if (!ctx.shell) return []; + const fs = ctx.shell.engine.fs; + const cwd = ctx.cwd ?? ctx.shell.engine.getCwd(); + const { dir, prefix, displayDir } = resolvePathToken(token, cwd); + + let entries: DirEntry[]; + try { + entries = await readDirEntries(fs, dir); + } catch { + return []; + } + + return entries + .filter((e) => e.name.startsWith(prefix)) + .map((e) => `${displayDir}${e.name}${e.isDirectory ? "/" : ""}`); +} diff --git a/src/repl/session.ts b/src/repl/session.ts index 4a62c99..fc6886b 100644 --- a/src/repl/session.ts +++ b/src/repl/session.ts @@ -3,6 +3,7 @@ import type { BashExecResult } from "just-bash"; import { TigrisShell } from "../shell.js"; import { type TigrisConfig, withConfigDefaults } from "../types.js"; import type { LoginFn } from "./auth.js"; +import { computeCompletions } from "./complete.js"; import type { ReplIO } from "./io.js"; export interface ReplSessionOptions { @@ -428,6 +429,14 @@ export class ReplSession { return this.shell !== null; } + /** + * Tab-completion entry point. Returns `[hits, completedToken]` in the + * shape that node:readline expects. + */ + async complete(line: string): Promise<[string[], string]> { + return computeCompletions(line, { shell: this.shell, cwd: this.cwd }); + } + /** Get the current prompt string (e.g. "/my-bucket $ "). */ get promptText(): string { if (this.cwd) { diff --git a/tests/complete.test.ts b/tests/complete.test.ts new file mode 100644 index 0000000..2fb7bbe --- /dev/null +++ b/tests/complete.test.ts @@ -0,0 +1,216 @@ +import { describe, expect, it, vi } from "vitest"; +import { computeCompletions } from "../src/repl/complete.js"; +import { TigrisShell } from "../src/shell.js"; +import { TEST_CONFIG, TEST_CONFIG_WITH_BUCKET } from "./helpers.js"; + +vi.mock("@tigrisdata/storage", () => ({ + get: vi.fn(), + put: vi.fn(), + head: vi.fn(), + list: vi.fn(), + remove: vi.fn(), + updateObject: vi.fn(), + getPresignedUrl: vi.fn(), + createBucketSnapshot: vi.fn(), + listBucketSnapshots: vi.fn(), + createBucket: vi.fn(), + listBuckets: vi.fn(), + bundle: vi.fn(), +})); + +describe("computeCompletions", () => { + describe("command completion", () => { + it("returns full command list for empty line", async () => { + const [hits, token] = await computeCompletions("", { shell: null, cwd: undefined }); + expect(token).toBe(""); + expect(hits).toContain("login"); + expect(hits).toContain("configure"); + expect(hits).toContain("presign"); + expect(hits).toContain("ls"); + expect(hits).toContain("grep"); + }); + + it("filters built-in commands by prefix", async () => { + const [hits, token] = await computeCompletions("gr", { shell: null, cwd: undefined }); + expect(token).toBe("gr"); + expect(hits).toContain("grep"); + expect(hits.every((h) => h.startsWith("gr"))).toBe(true); + }); + + it("filters REPL commands by prefix", async () => { + const [hits, token] = await computeCompletions("mou", { shell: null, cwd: undefined }); + expect(token).toBe("mou"); + expect(hits).toEqual(["mount"]); + }); + + it("filters custom commands by prefix", async () => { + const [hits, token] = await computeCompletions("pres", { shell: null, cwd: undefined }); + expect(token).toBe("pres"); + expect(hits).toEqual(["presign"]); + }); + + it("returns full list when no command matches prefix", async () => { + const [hits] = await computeCompletions("zzzzzz", { shell: null, cwd: undefined }); + expect(hits.length).toBeGreaterThan(10); + }); + }); + + describe("bucket-name argument completion", () => { + it("completes bucket names for 'mount'", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/a"); + shell.mount("beta", "/b"); + + const [hits, token] = await computeCompletions("mount ", { + shell, + cwd: undefined, + }); + expect(token).toBe(""); + expect(hits.sort()).toEqual(["alpha", "beta"]); + }); + + it("filters bucket names by prefix", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/a"); + shell.mount("beta", "/b"); + + const [hits, token] = await computeCompletions("snapshot al", { + shell, + cwd: undefined, + }); + expect(token).toBe("al"); + expect(hits).toEqual(["alpha"]); + }); + + it("completes bucket names for 'fork' and 'forks'", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/a"); + + const [forkHits] = await computeCompletions("fork ", { shell, cwd: undefined }); + expect(forkHits).toEqual(["alpha"]); + + const [forksHits] = await computeCompletions("forks ", { shell, cwd: undefined }); + expect(forksHits).toEqual(["alpha"]); + }); + + it("falls back to path completion for the second arg of mount", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/a"); + + const [, token] = await computeCompletions("mount alpha /", { + shell, + cwd: undefined, + }); + expect(token).toBe("/"); + }); + }); + + describe("mount-point argument completion", () => { + it("completes mount points for 'umount'", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/data"); + shell.mount("beta", "/models"); + + const [hits, token] = await computeCompletions("umount /m", { + shell, + cwd: undefined, + }); + expect(token).toBe("/m"); + expect(hits).toEqual(["/models"]); + }); + + it("completes mount points for 'flush'", async () => { + const shell = new TigrisShell(TEST_CONFIG); + shell.mount("alpha", "/data"); + + const [hits] = await computeCompletions("flush ", { shell, cwd: undefined }); + expect(hits).toEqual(["/data"]); + }); + }); + + describe("path completion", () => { + it("lists cwd entries for bare token", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/workspace" }); + await shell.exec("mkdir -p /workspace && touch /workspace/notes.md /workspace/data.json"); + await shell.exec("mkdir /workspace/sub"); + + const [hits, token] = await computeCompletions("ls ", { + shell, + cwd: "/workspace", + }); + expect(token).toBe(""); + expect(hits.sort()).toEqual(["data.json", "notes.md", "sub/"]); + }); + + it("filters by file prefix in cwd", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/workspace" }); + await shell.exec("mkdir -p /workspace && touch /workspace/notes.md /workspace/data.json"); + + const [hits] = await computeCompletions("cat no", { + shell, + cwd: "/workspace", + }); + expect(hits).toEqual(["notes.md"]); + }); + + it("appends a trailing slash to directories", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/workspace" }); + await shell.exec("mkdir -p /workspace/projects"); + + const [hits] = await computeCompletions("cd pro", { + shell, + cwd: "/workspace", + }); + expect(hits).toEqual(["projects/"]); + }); + + it("completes absolute paths", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/" }); + await shell.exec("mkdir -p /etc /home"); + + const [hits, token] = await computeCompletions("ls /e", { + shell, + cwd: "/", + }); + expect(token).toBe("/e"); + expect(hits).toEqual(["/etc/"]); + }); + + it("lists the parent directory when token ends with a slash", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/" }); + await shell.exec("mkdir -p /tmp/a /tmp/b"); + + const [hits, token] = await computeCompletions("ls /tmp/", { + shell, + cwd: "/", + }); + expect(token).toBe("/tmp/"); + expect(hits.sort()).toEqual(["/tmp/a/", "/tmp/b/"]); + }); + + it("returns empty when the directory does not exist", async () => { + const shell = new TigrisShell(TEST_CONFIG, { cwd: "/" }); + + const [hits] = await computeCompletions("ls /does-not-exist/", { + shell, + cwd: "/", + }); + expect(hits).toEqual([]); + }); + + it("returns empty for path completion when shell is not configured", async () => { + const [hits] = await computeCompletions("ls foo", { shell: null, cwd: undefined }); + expect(hits).toEqual([]); + }); + }); + + describe("via ReplSession default cwd", () => { + it("falls back to engine cwd when ctx.cwd is undefined", async () => { + const shell = new TigrisShell(TEST_CONFIG_WITH_BUCKET); + // Auto-mounted at /workspace via TigrisAdapter — we only test that the engine cwd is used, + // not the contents (which would require mocking the adapter). + const [, token] = await computeCompletions("ls fo", { shell, cwd: undefined }); + expect(token).toBe("fo"); + }); + }); +}); From 00a8c282b07c0ad98de87c7c62d4892826dbfc1c Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 14:49:10 +0200 Subject: [PATCH 2/7] chore: update packages --- package-lock.json | 983 +++++++++++++++++++++++----------------------- package.json | 12 +- 2 files changed, 497 insertions(+), 498 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67abbee..a3bc279 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,21 @@ "version": "0.0.2", "license": "MIT", "dependencies": { - "@tigrisdata/storage": "^3.2.1", - "just-bash": "^2.14.2" + "@tigrisdata/storage": "^3.7.0", + "just-bash": "^3.0.1" }, "bin": { "agent-shell": "dist/cli.js", "tigris-agent-shell": "dist/cli.js" }, "devDependencies": { - "@biomejs/biome": "^2.4.13", - "@commitlint/cli": "^20.5.2", - "@commitlint/config-conventional": "^20.5.0", + "@biomejs/biome": "^2.4.15", + "@commitlint/cli": "^21.0.1", + "@commitlint/config-conventional": "^21.0.1", "husky": "^9.1.7", "semantic-release": "^25.0.3", "typescript": "~5.8.0", - "vitest": "^4.1.5" + "vitest": "^4.1.7" }, "engines": { "node": ">=20.0.0" @@ -965,9 +965,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.13.tgz", - "integrity": "sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", + "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -981,20 +981,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.13", - "@biomejs/cli-darwin-x64": "2.4.13", - "@biomejs/cli-linux-arm64": "2.4.13", - "@biomejs/cli-linux-arm64-musl": "2.4.13", - "@biomejs/cli-linux-x64": "2.4.13", - "@biomejs/cli-linux-x64-musl": "2.4.13", - "@biomejs/cli-win32-arm64": "2.4.13", - "@biomejs/cli-win32-x64": "2.4.13" + "@biomejs/cli-darwin-arm64": "2.4.15", + "@biomejs/cli-darwin-x64": "2.4.15", + "@biomejs/cli-linux-arm64": "2.4.15", + "@biomejs/cli-linux-arm64-musl": "2.4.15", + "@biomejs/cli-linux-x64": "2.4.15", + "@biomejs/cli-linux-x64-musl": "2.4.15", + "@biomejs/cli-win32-arm64": "2.4.15", + "@biomejs/cli-win32-x64": "2.4.15" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.13.tgz", - "integrity": "sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz", + "integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==", "cpu": [ "arm64" ], @@ -1009,9 +1009,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.13.tgz", - "integrity": "sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", + "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", "cpu": [ "x64" ], @@ -1026,9 +1026,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.13.tgz", - "integrity": "sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", + "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", "cpu": [ "arm64" ], @@ -1043,9 +1043,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.13.tgz", - "integrity": "sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", + "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", "cpu": [ "arm64" ], @@ -1060,9 +1060,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.13.tgz", - "integrity": "sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", + "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", "cpu": [ "x64" ], @@ -1077,9 +1077,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.13.tgz", - "integrity": "sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", + "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", "cpu": [ "x64" ], @@ -1094,9 +1094,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.13.tgz", - "integrity": "sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", + "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", "cpu": [ "arm64" ], @@ -1111,9 +1111,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.13.tgz", - "integrity": "sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", + "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", "cpu": [ "x64" ], @@ -1149,251 +1149,245 @@ } }, "node_modules/@commitlint/cli": { - "version": "20.5.2", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.2.tgz", - "integrity": "sha512-IXr5xd3IX8SEG936P8gcpozRplkDeDSwJlt8UvoY1winwIy2udTbQ/cOCgbaaxcjdDqVoS29VUcz/wkwnSozbA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.1.tgz", + "integrity": "sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^20.5.0", - "@commitlint/lint": "^20.5.0", - "@commitlint/load": "^20.5.2", - "@commitlint/read": "^20.5.0", - "@commitlint/types": "^20.5.0", + "@commitlint/format": "^21.0.1", + "@commitlint/lint": "^21.0.1", + "@commitlint/load": "^21.0.1", + "@commitlint/read": "^21.0.1", + "@commitlint/types": "^21.0.1", "tinyexec": "^1.0.0", - "yargs": "^17.0.0" + "yargs": "^18.0.0" }, "bin": { "commitlint": "cli.js" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/config-conventional": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.0.tgz", - "integrity": "sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.1.tgz", + "integrity": "sha512-gRorrkfWOh/+V5X8GYWWbQvrzPczopGMS4CCNrQdHkK4xWElv82BDvIsDhJZWTlI7TazOlYea6VATufCsFs+sw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.1", "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/config-validator": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", - "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz", + "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.1", "ajv": "^8.11.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/ensure": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.0.tgz", - "integrity": "sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", + "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" + "@commitlint/types": "^21.0.1", + "es-toolkit": "^1.46.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/execute-rule": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", - "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/format": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", - "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz", + "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.1", "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/is-ignored": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", - "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.1.tgz", + "integrity": "sha512-iNDP8SFdw8JEkM0CHZ2XFnhTN4Zg5jKUY2d8kBOSFrI2aA+3YJI7fcqVpfgbpJ9xtxFVYpi+DBATU5AvhoTq8g==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.1", "semver": "^7.6.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/lint": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.0.tgz", - "integrity": "sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.1.tgz", + "integrity": "sha512-gF+iYtUw1gBG3HUH9z3VxwUjGg2R2G5j+nmvPs8aIeYkiB7TtneBu3wO85I0bUl93bYNsvsCNI9Nte2fmDUMww==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^20.5.0", - "@commitlint/parse": "^20.5.0", - "@commitlint/rules": "^20.5.0", - "@commitlint/types": "^20.5.0" + "@commitlint/is-ignored": "^21.0.1", + "@commitlint/parse": "^21.0.1", + "@commitlint/rules": "^21.0.1", + "@commitlint/types": "^21.0.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/load": { - "version": "20.5.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.2.tgz", - "integrity": "sha512-zmr0RGDz7vThxW1I8ohb9yBjnGuH9mqwJpn21hInjGla+IlLOkS9ey0+dD5HlkzFlY0lX2NYdA2lDW6/0rO7Gw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.1.tgz", + "integrity": "sha512-Btg1q1mKmiihN4W3x0EsPDrJMOQfMa9NIqlzlJyXAfxvsOGdGXOW5p3R3RcSxDCaY7JabY9flIl+Om1af3PSrw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.5.0", - "@commitlint/execute-rule": "^20.0.0", - "@commitlint/resolve-extends": "^20.5.2", - "@commitlint/types": "^20.5.0", + "@commitlint/config-validator": "^21.0.1", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.0.1", + "@commitlint/types": "^21.0.1", "cosmiconfig": "^9.0.1", "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", "is-plain-obj": "^4.1.0", - "lodash.mergewith": "^4.6.2", "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/message": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", - "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.1.tgz", + "integrity": "sha512-R3dVQeJQ0B6yqrZEjkUHD4r7UJYLV9Lvk2xs3PTOmtWk2G3mI6Xgc+YdRxL1PwcDfBiUjv2SkIkW4AUc976w1w==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/parse": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", - "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.1.tgz", + "integrity": "sha512-oh/nCSOqdoeQNA1tO8aAmxkq5EBo8/NzcFQRvv66AWc9HpED28sL2iSicCKU6hPintWuscL6BJEWi77Wq1LPMQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.1", "conventional-changelog-angular": "^8.2.0", "conventional-commits-parser": "^6.3.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/read": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", - "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.1.tgz", + "integrity": "sha512-pMEu4lbpC8W0ZgKJj2U6WaobXIZWdFlULpIEewYhkPXx+WZcnoO53YrVPc7QErQuNolq2Me8dP58Wu7YAVXVOA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^20.4.3", - "@commitlint/types": "^20.5.0", + "@commitlint/top-level": "^21.0.1", + "@commitlint/types": "^21.0.1", "git-raw-commits": "^5.0.0", - "minimist": "^1.2.8", "tinyexec": "^1.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/resolve-extends": { - "version": "20.5.2", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.2.tgz", - "integrity": "sha512-8EhSCU9eNos/5cI1yg64GW79UH1c64O69AfStCsj4zqy6An/qIphVEXj4/+2M6056T8coz00f+UXFn4WUUP1HQ==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz", + "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.5.0", - "@commitlint/types": "^20.5.0", + "@commitlint/config-validator": "^21.0.1", + "@commitlint/types": "^21.0.1", + "es-toolkit": "^1.46.0", "global-directory": "^5.0.0", - "import-meta-resolve": "^4.0.0", - "lodash.mergewith": "^4.6.2", "resolve-from": "^5.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/rules": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.0.tgz", - "integrity": "sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.1.tgz", + "integrity": "sha512-VMooYpz4nJg7xlaUso6CCOWEz8D/ChkvsvZUMARcoJ1ZpfKPyFCGrHNha2tbsETNAb6ErgiRuCr2DvghrvPDYQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^20.5.0", - "@commitlint/message": "^20.4.3", - "@commitlint/to-lines": "^20.0.0", - "@commitlint/types": "^20.5.0" + "@commitlint/ensure": "^21.0.1", + "@commitlint/message": "^21.0.1", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.0.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/to-lines": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", - "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/top-level": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", - "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.1.tgz", + "integrity": "sha512-4esUYqzY7K0FCgcJ/1xWEZekV7Ch4yZT1+xjEb7KzqbJ05XEkxHVsTfC8ADKNNtlCE2pj98KEbPGZWw9WwEnVw==", "dev": true, "license": "MIT", "dependencies": { "escalade": "^3.2.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/types": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", - "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz", + "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1401,7 +1395,7 @@ "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@conventional-changelog/git-client": { @@ -1432,9 +1426,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -1444,9 +1438,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -1724,9 +1718,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.126.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", - "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==", + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", "dev": true, "license": "MIT", "funding": { @@ -1779,9 +1773,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -1796,9 +1790,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ "arm64" ], @@ -1813,9 +1807,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz", - "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", "cpu": [ "x64" ], @@ -1830,9 +1824,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz", - "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", "cpu": [ "x64" ], @@ -1847,9 +1841,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz", - "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", "cpu": [ "arm" ], @@ -1864,9 +1858,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", "cpu": [ "arm64" ], @@ -1881,9 +1875,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz", - "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", "cpu": [ "arm64" ], @@ -1898,9 +1892,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", "cpu": [ "ppc64" ], @@ -1915,9 +1909,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", "cpu": [ "s390x" ], @@ -1932,9 +1926,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", "cpu": [ "x64" ], @@ -1949,9 +1943,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz", - "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", "cpu": [ "x64" ], @@ -1966,9 +1960,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", "cpu": [ "arm64" ], @@ -1983,9 +1977,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz", - "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", "cpu": [ "wasm32" ], @@ -1993,8 +1987,8 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { @@ -2002,9 +1996,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz", - "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", "cpu": [ "arm64" ], @@ -2019,9 +2013,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz", - "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", "cpu": [ "x64" ], @@ -2036,9 +2030,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz", - "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -3162,15 +3156,16 @@ "license": "MIT" }, "node_modules/@tigrisdata/storage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@tigrisdata/storage/-/storage-3.2.1.tgz", - "integrity": "sha512-mbOVzSAJ0KPTJjhGbsNo6ok1gAmnmALWB3AuBHPky1+lXfvFty96gDYWteu7Vcz5hLJnKuo3Cni2Nk6o8d6cHg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@tigrisdata/storage/-/storage-3.7.0.tgz", + "integrity": "sha512-MtJE2sspBPq3t/tJ1BVICCtoNXw+GyP0in0/ykmdraryi4/5s0jKZL87RvjEYoS7/Ryih1ueaD9yP6E5nmcy3w==", "license": "MIT", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/client-s3": "^3.1038.0", "@aws-sdk/lib-storage": "^3.1038.0", "@aws-sdk/s3-request-presigner": "^3.1038.0", + "@aws-sdk/types": "^3.973.8", "@smithy/signature-v4": "^5.3.14", "dotenv": "^17.4.2" } @@ -3199,9 +3194,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -3228,21 +3223,21 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/normalize-package-data": { @@ -3253,16 +3248,16 @@ "license": "MIT" }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3271,13 +3266,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3298,9 +3293,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", "dev": true, "license": "MIT", "dependencies": { @@ -3311,13 +3306,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.7", "pathe": "^2.0.3" }, "funding": { @@ -3325,14 +3320,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3341,9 +3336,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", "dev": true, "license": "MIT", "funding": { @@ -3351,13 +3346,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3569,9 +3564,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -3766,18 +3761,90 @@ } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/color-convert": { @@ -4317,6 +4384,17 @@ "dev": true, "license": "MIT" }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4448,9 +4526,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -4465,9 +4543,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", - "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", @@ -4476,7 +4554,8 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { @@ -4649,9 +4728,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -5197,13 +5276,13 @@ } }, "node_modules/just-bash": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-2.14.2.tgz", - "integrity": "sha512-9Na1rH03Ta5ydHTNotJ7dms1iZwb2kToOnKbnS29AlrCvi1CQ21Fm2lfu4S4rfwDGHYi4E4evgTDC/DcDx8tuQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-3.0.1.tgz", + "integrity": "sha512-YVyzCN08fKarUnwqy7rKOAcX+2MLYLnYInuowmUXn3mqhrtd4ieZNBuzdQG+qYV9DqnIWuv9Whiph0WRIWsBtw==", "license": "Apache-2.0", "dependencies": { "diff": "^8.0.2", - "fast-xml-parser": "^5.3.3", + "fast-xml-parser": "^5.7.3", "file-type": "^21.2.0", "ini": "^6.0.0", "minimatch": "^10.1.1", @@ -5227,6 +5306,28 @@ "node-liblzma": "^2.0.3" } }, + "node_modules/just-bash/node_modules/fast-xml-parser": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", + "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.2.0", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.3.0", + "xml-naming": "^0.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -5532,13 +5633,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", @@ -5567,34 +5661,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -5602,13 +5668,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -5815,9 +5874,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -8303,9 +8362,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -8323,7 +8382,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8570,14 +8629,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz", - "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.126.0", - "@rolldown/pluginutils": "1.0.0-rc.16" + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -8586,21 +8645,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.16", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.16", - "@rolldown/binding-darwin-x64": "1.0.0-rc.16", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.16", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16" + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" } }, "node_modules/safe-buffer": { @@ -8665,41 +8724,6 @@ "node": "^22.14.0 || >= 24.10.0" } }, - "node_modules/semantic-release/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/semantic-release/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/semantic-release/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, "node_modules/semantic-release/node_modules/normalize-package-data": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", @@ -8784,40 +8808,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/semantic-release/node_modules/type-fest": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", @@ -8847,52 +8837,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/semantic-release/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/semantic-release/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -9345,9 +9289,9 @@ } }, "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", "funding": [ { "type": "github", @@ -9808,9 +9752,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT", "peer": true @@ -9899,16 +9843,16 @@ } }, "node_modules/vite": { - "version": "8.0.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz", - "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.16", + "postcss": "^8.5.15", + "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "bin": { @@ -9925,7 +9869,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -9990,19 +9934,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -10030,12 +9974,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -10164,6 +10108,21 @@ "license": "ISC", "optional": true }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10200,32 +10159,72 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/yoctocolors": { diff --git a/package.json b/package.json index 6365bbc..c3f6d71 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "node": ">=20.0.0" }, "dependencies": { - "@tigrisdata/storage": "^3.2.1", - "just-bash": "^2.14.2" + "@tigrisdata/storage": "^3.7.0", + "just-bash": "^3.0.1" }, "release": { "branches": [ @@ -81,12 +81,12 @@ ] }, "devDependencies": { - "@biomejs/biome": "^2.4.13", - "@commitlint/cli": "^20.5.2", - "@commitlint/config-conventional": "^20.5.0", + "@biomejs/biome": "^2.4.15", + "@commitlint/cli": "^21.0.1", + "@commitlint/config-conventional": "^21.0.1", "husky": "^9.1.7", "semantic-release": "^25.0.3", "typescript": "~5.8.0", - "vitest": "^4.1.5" + "vitest": "^4.1.7" } } From b79e0643b5576c898c180c07461e2e768e4f22d6 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 14:50:38 +0200 Subject: [PATCH 3/7] chore: update packages --- biome.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biome.json b/biome.json index c0fc4dd..3a5efeb 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, From 224c8eb37b2e1c79034137f070f88f7130f4cfea Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 14:50:54 +0200 Subject: [PATCH 4/7] fix: fork command --- src/commands/fork.ts | 6 +++--- tests/commands.test.ts | 31 ++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/commands/fork.ts b/src/commands/fork.ts index 073cf2b..ed1af2d 100644 --- a/src/commands/fork.ts +++ b/src/commands/fork.ts @@ -1,4 +1,4 @@ -import { createBucket, listBuckets } from "@tigrisdata/storage"; +import { createBucket, listForks } from "@tigrisdata/storage"; import { defineCommand } from "just-bash"; import type { TigrisConfig } from "../types.js"; @@ -65,7 +65,7 @@ export function createForksListCommand(config: TigrisConfig) { }; } - const result = await listBuckets({ config }); + const result = await listForks(bucket, { config }); if ("error" in result) { return { stdout: "", @@ -74,7 +74,7 @@ export function createForksListCommand(config: TigrisConfig) { }; } - const lines = result.data.buckets.map((b) => b.name).join("\n"); + const lines = result.data.forks.map((b) => b.name).join("\n"); return { stdout: lines ? `${lines}\n` : "", stderr: "", diff --git a/tests/commands.test.ts b/tests/commands.test.ts index edbcf28..cabc590 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -5,7 +5,7 @@ vi.mock("@tigrisdata/storage", () => ({ createBucketSnapshot: vi.fn(), listBucketSnapshots: vi.fn(), createBucket: vi.fn(), - listBuckets: vi.fn(), + listForks: vi.fn(), })); import { @@ -13,8 +13,9 @@ import { createBucketSnapshot, getPresignedUrl, listBucketSnapshots, - listBuckets, + listForks, } from "@tigrisdata/storage"; +import { EMPTY_BYTES } from "just-bash"; import { createForkCommand, createForksListCommand } from "../src/commands/fork.js"; import { createPresignCommand } from "../src/commands/presign.js"; import { createSnapshotCommand } from "../src/commands/snapshot.js"; @@ -28,7 +29,7 @@ function makeCtx() { fs: {} as never, cwd: "/", env: new Map(), - stdin: "", + stdin: EMPTY_BYTES, }; } @@ -190,14 +191,26 @@ describe("forks", () => { expect(result.stderr).toContain("missing bucket"); }); - it("lists buckets", async () => { - vi.mocked(listBuckets).mockResolvedValue({ + it("lists forks", async () => { + const now = new Date(); + vi.mocked(listForks).mockResolvedValue({ data: { - buckets: [ - { name: "bucket-a", creationDate: new Date() }, - { name: "bucket-b", creationDate: new Date() }, + forks: [ + { + name: "bucket-a", + creationDate: now, + forkCreatedAt: now, + snapshot: "snap-a", + snapshotCreatedAt: now, + }, + { + name: "bucket-b", + creationDate: now, + forkCreatedAt: now, + snapshot: "snap-b", + snapshotCreatedAt: now, + }, ], - owner: { name: "test", id: "1" }, }, }); From 6cbd1614c5a05edb2d16a8d9e491d12229f734dc Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 15:34:35 +0200 Subject: [PATCH 5/7] refactor: tighten custom commands and merge fork/forks - Add src/commands/args.ts with argError/sdkError/parseFlags helpers so error formatting is consistent and unknown options / missing values are rejected up front. - Tighten presign, snapshot, fork parsers: reject unknown flags and extra positional args, validate --expires as a positive integer, forbid --name+--list, distinguish missing source vs missing name, reject source == name. - Print 'No snapshots.' / 'No forks.' on stdout when listings are empty (matching df's pattern), instead of silent output. - Merge the standalone 'forks' command into 'fork --list'. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/commands/args.ts | 61 ++++++++++++++ src/commands/fork.ts | 148 ++++++++++++++++---------------- src/commands/index.ts | 11 +-- src/commands/presign.ts | 137 +++++++++++++++--------------- src/commands/snapshot.ts | 108 ++++++++++++------------ src/repl/complete.ts | 4 +- src/repl/session.ts | 2 +- src/shell.ts | 3 +- tests/commands.test.ts | 178 ++++++++++++++++++++++++++++++--------- tests/complete.test.ts | 5 +- 10 files changed, 403 insertions(+), 254 deletions(-) create mode 100644 src/commands/args.ts diff --git a/src/commands/args.ts b/src/commands/args.ts new file mode 100644 index 0000000..e975e3a --- /dev/null +++ b/src/commands/args.ts @@ -0,0 +1,61 @@ +import type { ExecResult } from "just-bash"; + +/** Format a usage error. The `usage` line is appended on the next line if given. */ +export function argError(cmd: string, message: string, usage?: string): ExecResult { + const usageLine = usage ? `Usage: ${usage}\n` : ""; + return { + stdout: "", + stderr: `${cmd}: ${message}\n${usageLine}`, + exitCode: 1, + }; +} + +/** Format an SDK error consistently across commands. */ +export function sdkError(cmd: string, err: { message: string }): ExecResult { + return { stdout: "", stderr: `${cmd}: ${err.message}\n`, exitCode: 1 }; +} + +export type FlagSchema = Record; + +export interface FlagParseResult { + flags: Record; + positional: string[]; +} + +/** + * Parse a flag-and-positional argv. Returns either the parsed result or an + * error describing an unknown option or a missing value. Does not allow + * `--flag=value` syntax — values are taken from the following arg. + */ +export function parseFlags( + args: string[], + schema: FlagSchema, +): FlagParseResult | { error: string } { + const flags: Record = {}; + const positional: string[] = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === undefined) continue; + if (!arg.startsWith("--")) { + positional.push(arg); + continue; + } + const kind = schema[arg]; + if (kind === undefined) { + return { error: `unknown option: ${arg}` }; + } + if (kind === "boolean") { + flags[arg] = true; + continue; + } + const value = args[i + 1]; + if (value === undefined || value.startsWith("--")) { + return { error: `option ${arg} requires a value` }; + } + flags[arg] = value; + i++; + } + + return { flags, positional }; +} diff --git a/src/commands/fork.ts b/src/commands/fork.ts index ed1af2d..9c73e04 100644 --- a/src/commands/fork.ts +++ b/src/commands/fork.ts @@ -1,84 +1,88 @@ import { createBucket, listForks } from "@tigrisdata/storage"; -import { defineCommand } from "just-bash"; +import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; +import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; + +const USAGE = "fork [--snapshot version] | fork --list"; +const SCHEMA: FlagSchema = { + "--snapshot": "value", + "--list": "boolean", +}; + +type ForkInput = + | { mode: "create"; sourceBucket: string; forkName: string; snapshotVersion: string | undefined } + | { mode: "list"; sourceBucket: string }; -/** - * fork [--snapshot version] - * - * Create a fork of a bucket, optionally from a specific snapshot. - */ export function createForkCommand(config: TigrisConfig) { return defineCommand("fork", async (args) => { - const sourceBucket = args[0]; - const forkName = args[1]; - - if (!sourceBucket || !forkName) { - return { - stdout: "", - stderr: - "fork: missing arguments\nUsage: fork [--snapshot version]\n", - exitCode: 1, - }; - } - - let snapshotVersion: string | undefined; - for (let i = 2; i < args.length; i++) { - if (args[i] === "--snapshot" && args[i + 1]) { - snapshotVersion = args[i + 1]; - i++; - } - } - - const result = await createBucket(forkName, { - sourceBucketName: sourceBucket, - ...(snapshotVersion !== undefined && { sourceBucketSnapshot: snapshotVersion }), + const input = parseInput(args); + if ("stderr" in input) return input; + + if (input.mode === "list") return listForksOf(input.sourceBucket, config); + + const result = await createBucket(input.forkName, { + sourceBucketName: input.sourceBucket, + ...(input.snapshotVersion !== undefined && { + sourceBucketSnapshot: input.snapshotVersion, + }), config, }); + if ("error" in result) return sdkError("fork", result.error); - if ("error" in result) { - return { - stdout: "", - stderr: `fork: ${result.error.message}\n`, - exitCode: 1, - }; - } - - return { - stdout: `${forkName}\n`, - stderr: "", - exitCode: 0, - }; + return { stdout: `${input.forkName}\n`, stderr: "", exitCode: 0 }; }); } -/** - * forks — list all forks of a bucket. - */ -export function createForksListCommand(config: TigrisConfig) { - return defineCommand("forks", async (args) => { - const bucket = args[0]; - if (!bucket) { - return { - stdout: "", - stderr: "forks: missing bucket argument\nUsage: forks \n", - exitCode: 1, - }; - } - - const result = await listForks(bucket, { config }); - if ("error" in result) { - return { - stdout: "", - stderr: `forks: ${result.error.message}\n`, - exitCode: 1, - }; - } - - const lines = result.data.forks.map((b) => b.name).join("\n"); - return { - stdout: lines ? `${lines}\n` : "", - stderr: "", - exitCode: 0, - }; - }); +function parseInput(args: string[]): ForkInput | ExecResult { + const parsed = parseFlags(args, SCHEMA); + if ("error" in parsed) return argError("fork", parsed.error, USAGE); + const { flags, positional } = parsed; + + const isList = flags["--list"] === true; + const snapshotVersion = typeof flags["--snapshot"] === "string" ? flags["--snapshot"] : undefined; + + if (isList && snapshotVersion !== undefined) { + return argError("fork", "--snapshot and --list cannot be combined", USAGE); + } + + if (isList) return parseListInput(positional); + return parseCreateInput(positional, snapshotVersion); +} + +function parseListInput(positional: string[]): ForkInput | ExecResult { + if (positional.length === 0) return argError("fork", "missing ", USAGE); + if (positional.length > 1) { + return argError("fork", `unexpected argument: ${positional[1]}`, USAGE); + } + return { mode: "list", sourceBucket: positional[0] ?? "" }; +} + +function parseCreateInput( + positional: string[], + snapshotVersion: string | undefined, +): ForkInput | ExecResult { + if (positional.length < 1) return argError("fork", "missing ", USAGE); + if (positional.length < 2) return argError("fork", "missing ", USAGE); + if (positional.length > 2) { + return argError("fork", `unexpected argument: ${positional[2]}`, USAGE); + } + + const [sourceBucket, forkName] = positional as [string, string]; + if (sourceBucket === forkName) { + return argError("fork", " and must differ"); + } + + return { mode: "create", sourceBucket, forkName, snapshotVersion }; +} + +async function listForksOf(bucket: string, config: TigrisConfig) { + const result = await listForks(bucket, { config }); + if ("error" in result) return sdkError("fork", result.error); + + const forks = result.data.forks; + if (forks.length === 0) { + return { stdout: "No forks.\n", stderr: "", exitCode: 0 }; + } + const lines = forks.map((b) => b.name).join("\n"); + return { stdout: `${lines}\n`, stderr: "", exitCode: 0 }; } diff --git a/src/commands/index.ts b/src/commands/index.ts index 8963386..4dbdff9 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,6 +1,6 @@ import type { Command } from "just-bash"; import type { TigrisConfig } from "../types.js"; -import { createForkCommand, createForksListCommand } from "./fork.js"; +import { createForkCommand } from "./fork.js"; import { createPresignCommand } from "./presign.js"; import { createSnapshotCommand } from "./snapshot.js"; @@ -8,15 +8,10 @@ import { createSnapshotCommand } from "./snapshot.js"; * Create all Tigris custom commands for use with just-bash. */ export function createTigrisCommands(config: TigrisConfig): Command[] { - return [ - createPresignCommand(config), - createSnapshotCommand(config), - createForkCommand(config), - createForksListCommand(config), - ]; + return [createPresignCommand(config), createSnapshotCommand(config), createForkCommand(config)]; } -export { createForkCommand, createForksListCommand } from "./fork.js"; +export { createForkCommand } from "./fork.js"; export type { PresignOptions } from "./presign.js"; export { createPresignCommand } from "./presign.js"; export { createSnapshotCommand } from "./snapshot.js"; diff --git a/src/commands/presign.ts b/src/commands/presign.ts index e4e5672..706883b 100644 --- a/src/commands/presign.ts +++ b/src/commands/presign.ts @@ -1,95 +1,90 @@ import { getPresignedUrl } from "@tigrisdata/storage"; -import { defineCommand } from "just-bash"; +import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; +import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; -/** - * presign [--expires N] [--put] - * - * Generate a presigned URL for a Tigris object. - * Defaults to GET with 1 hour expiry. - */ -function parsePresignArgs(args: string[]): { - expiresIn: number; - operation: "get" | "put"; -} { - let expiresIn = 3600; - let operation: "get" | "put" = "get"; - - for (let i = 0; i < args.length; i++) { - if (args[i] === "--expires" && args[i + 1]) { - expiresIn = Number.parseInt(args[i + 1] ?? "3600", 10); - i++; - } else if (args[i] === "--put") { - operation = "put"; - } - } - - return { expiresIn, operation }; -} +const USAGE = "presign [--expires N] [--put]"; +const SCHEMA: FlagSchema = { + "--expires": "value", + "--put": "boolean", +}; +const DEFAULT_EXPIRES = 3600; export interface PresignOptions { /** Resolve an absolute path to bucket + key from the mount table. */ resolveBucket?: (path: string) => { bucket: string; key: string } | null; } +interface PresignInput { + path: string; + expiresIn: number; + operation: "get" | "put"; +} + export function createPresignCommand(config: TigrisConfig, options?: PresignOptions) { return defineCommand("presign", async (args, ctx) => { - const rawPath = args[0]; - if (!rawPath) { - return { - stdout: "", - stderr: "presign: missing path argument\nUsage: presign [--expires N] [--put]\n", - exitCode: 1, - }; - } + const input = parseInput(args); + if ("stderr" in input) return input; if (!config.accessKeyId) { - return { - stdout: "", - stderr: "presign: requires access key auth. Use 'configure' instead of 'login'.\n", - exitCode: 1, - }; + return argError("presign", "requires access key auth. Use 'configure' instead of 'login'."); } - let resolved: { bucket: string; key: string } | null; - if (config.bucket) { - // Single-bucket mode — rawPath is relative to the bucket root - const key = rawPath.startsWith("/") ? rawPath.slice(1) : rawPath; - resolved = { bucket: config.bucket, key }; - } else { - // Multi-bucket — resolve against cwd to find the mount - const absolutePath = rawPath.startsWith("/") - ? rawPath - : `${ctx.cwd.replace(/\/$/, "")}/${rawPath}`; - resolved = options?.resolveBucket?.(absolutePath) ?? null; - } + const resolved = resolveTarget(input.path, ctx.cwd, config, options); if (!resolved) { - return { - stdout: "", - stderr: "presign: cannot determine bucket. cd into a mounted bucket first.\n", - exitCode: 1, - }; + return argError("presign", "cannot determine bucket. cd into a mounted bucket first."); } - const { expiresIn, operation } = parsePresignArgs(args.slice(1)); const result = await getPresignedUrl(resolved.key, { - operation, - expiresIn, + operation: input.operation, + expiresIn: input.expiresIn, config: { ...config, bucket: resolved.bucket }, }); + if ("error" in result) return sdkError("presign", result.error); - if ("error" in result) { - return { - stdout: "", - stderr: `presign: ${result.error.message}\n`, - exitCode: 1, - }; - } - - return { - stdout: `${result.data.url}\n`, - stderr: "", - exitCode: 0, - }; + return { stdout: `${result.data.url}\n`, stderr: "", exitCode: 0 }; }); } + +function parseInput(args: string[]): PresignInput | ExecResult { + const parsed = parseFlags(args, SCHEMA); + if ("error" in parsed) return argError("presign", parsed.error, USAGE); + const { flags, positional } = parsed; + + if (positional.length === 0) return argError("presign", "missing ", USAGE); + if (positional.length > 1) { + return argError("presign", `unexpected argument: ${positional[1]}`, USAGE); + } + + const expires = parseExpires(flags["--expires"]); + if (typeof expires !== "number") return expires; + + return { + path: positional[0] ?? "", + expiresIn: expires, + operation: flags["--put"] === true ? "put" : "get", + }; +} + +function parseExpires(raw: string | true | undefined): number | ExecResult { + if (typeof raw !== "string") return DEFAULT_EXPIRES; + const value = Number(raw); + if (!Number.isInteger(value) || value <= 0) { + return argError("presign", `--expires must be a positive integer (got '${raw}')`, USAGE); + } + return value; +} + +function resolveTarget( + rawPath: string, + cwd: string, + config: TigrisConfig, + options: PresignOptions | undefined, +): { bucket: string; key: string } | null { + if (config.bucket) { + const key = rawPath.startsWith("/") ? rawPath.slice(1) : rawPath; + return { bucket: config.bucket, key }; + } + const absolutePath = rawPath.startsWith("/") ? rawPath : `${cwd.replace(/\/$/, "")}/${rawPath}`; + return options?.resolveBucket?.(absolutePath) ?? null; +} diff --git a/src/commands/snapshot.ts b/src/commands/snapshot.ts index d791402..d3e7791 100644 --- a/src/commands/snapshot.ts +++ b/src/commands/snapshot.ts @@ -1,72 +1,68 @@ import { createBucketSnapshot, listBucketSnapshots } from "@tigrisdata/storage"; -import { defineCommand } from "just-bash"; +import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; +import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; -/** - * snapshot [--name label] [--list] - * - * Create or list point-in-time bucket snapshots. - */ -function parseSnapshotArgs(args: string[]): { - isList: boolean; - name: string | undefined; -} { - let isList = false; - let name: string | undefined; - - for (let i = 0; i < args.length; i++) { - if (args[i] === "--list") { - isList = true; - } else if (args[i] === "--name" && args[i + 1]) { - name = args[i + 1]; - i++; - } - } +const USAGE = "snapshot [--name label] [--list]"; +const SCHEMA: FlagSchema = { + "--name": "value", + "--list": "boolean", +}; - return { isList, name }; -} - -async function listSnapshots(bucket: string, config: TigrisConfig) { - const result = await listBucketSnapshots(bucket, { config }); - if ("error" in result) { - return { stdout: "", stderr: `snapshot: ${result.error.message}\n`, exitCode: 1 }; - } - const lines = result.data.snapshots - .map((s) => { - const label = s.name ? ` (${s.name})` : ""; - const date = s.creationDate?.toISOString() ?? "unknown"; - return `${s.version}${label} ${date}`; - }) - .join("\n"); - return { stdout: lines ? `${lines}\n` : "", stderr: "", exitCode: 0 }; +interface SnapshotInput { + bucket: string; + mode: "list" | "create"; + name: string | undefined; } export function createSnapshotCommand(config: TigrisConfig) { return defineCommand("snapshot", async (args) => { - const bucket = args[0]; - if (!bucket) { - return { - stdout: "", - stderr: - "snapshot: missing bucket argument\nUsage: snapshot [--name label] [--list]\n", - exitCode: 1, - }; - } + const input = parseInput(args); + if ("stderr" in input) return input; - const { isList, name } = parseSnapshotArgs(args.slice(1)); + if (input.mode === "list") return listSnapshots(input.bucket, config); - if (isList) { - return listSnapshots(bucket, config); - } - - const result = await createBucketSnapshot(bucket, { - ...(name !== undefined && { name }), + const result = await createBucketSnapshot(input.bucket, { + ...(input.name !== undefined && { name: input.name }), config, }); - if ("error" in result) { - return { stdout: "", stderr: `snapshot: ${result.error.message}\n`, exitCode: 1 }; - } + if ("error" in result) return sdkError("snapshot", result.error); return { stdout: `${result.data.snapshotVersion}\n`, stderr: "", exitCode: 0 }; }); } + +function parseInput(args: string[]): SnapshotInput | ExecResult { + const parsed = parseFlags(args, SCHEMA); + if ("error" in parsed) return argError("snapshot", parsed.error, USAGE); + const { flags, positional } = parsed; + + if (positional.length === 0) return argError("snapshot", "missing ", USAGE); + if (positional.length > 1) { + return argError("snapshot", `unexpected argument: ${positional[1]}`, USAGE); + } + + const isList = flags["--list"] === true; + const name = typeof flags["--name"] === "string" ? flags["--name"] : undefined; + if (isList && name !== undefined) { + return argError("snapshot", "--name and --list cannot be combined", USAGE); + } + + return { bucket: positional[0] ?? "", mode: isList ? "list" : "create", name }; +} + +async function listSnapshots(bucket: string, config: TigrisConfig) { + const result = await listBucketSnapshots(bucket, { config }); + if ("error" in result) return sdkError("snapshot", result.error); + + const snapshots = result.data.snapshots; + if (snapshots.length === 0) { + return { stdout: "No snapshots.\n", stderr: "", exitCode: 0 }; + } + const lines = snapshots.map((s) => { + const label = s.name ? ` (${s.name})` : ""; + const date = s.creationDate?.toISOString() ?? "unknown"; + return `${s.version}${label} ${date}`; + }); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} diff --git a/src/repl/complete.ts b/src/repl/complete.ts index db8967b..ae4ccb7 100644 --- a/src/repl/complete.ts +++ b/src/repl/complete.ts @@ -18,9 +18,9 @@ const REPL_COMMANDS = [ "quit", ]; -const CUSTOM_COMMANDS = ["presign", "snapshot", "fork", "forks"]; +const CUSTOM_COMMANDS = ["presign", "snapshot", "fork"]; -const BUCKET_ARG_COMMANDS = new Set(["mount", "snapshot", "fork", "forks"]); +const BUCKET_ARG_COMMANDS = new Set(["mount", "snapshot", "fork"]); const MOUNT_POINT_ARG_COMMANDS = new Set(["umount", "flush"]); export interface CompleteContext { diff --git a/src/repl/session.ts b/src/repl/session.ts index fc6886b..6517fbc 100644 --- a/src/repl/session.ts +++ b/src/repl/session.ts @@ -416,7 +416,7 @@ export class ReplSession { io.write(" presign [--expires N] [--put] Generate a presigned URL\n"); io.write(" snapshot [--name N] [--list] Create or list snapshots\n"); io.write(" fork [--snapshot V] Fork a bucket\n"); - io.write(" forks List forks\n"); + io.write(" fork --list List forks\n"); io.write("\nShell:\n"); io.write(" clear Clear screen\n"); io.write(" help Show this help\n"); diff --git a/src/shell.ts b/src/shell.ts index 5e29b21..02c58e8 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -1,6 +1,6 @@ import type { BashExecResult } from "just-bash"; import { Bash, InMemoryFs, MountableFs } from "just-bash"; -import { createForkCommand, createForksListCommand } from "./commands/fork.js"; +import { createForkCommand } from "./commands/fork.js"; import { createPresignCommand } from "./commands/presign.js"; import { createSnapshotCommand } from "./commands/snapshot.js"; import { TigrisAdapter } from "./fs/tigris-adapter.js"; @@ -52,7 +52,6 @@ export class TigrisShell { }), createSnapshotCommand(resolvedConfig), createForkCommand(resolvedConfig), - createForksListCommand(resolvedConfig), ], }); } diff --git a/tests/commands.test.ts b/tests/commands.test.ts index cabc590..37c07be 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -16,7 +16,7 @@ import { listForks, } from "@tigrisdata/storage"; import { EMPTY_BYTES } from "just-bash"; -import { createForkCommand, createForksListCommand } from "../src/commands/fork.js"; +import { createForkCommand } from "../src/commands/fork.js"; import { createPresignCommand } from "../src/commands/presign.js"; import { createSnapshotCommand } from "../src/commands/snapshot.js"; import { TEST_CONFIG_WITH_BUCKET } from "./helpers.js"; @@ -43,7 +43,38 @@ describe("presign", () => { it("returns error when path is missing", async () => { const result = await cmd.execute([], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing path"); + expect(result.stderr).toContain("missing "); + expect(result.stderr).toContain("Usage: presign"); + }); + + it("rejects unknown options", async () => { + const result = await cmd.execute(["/f.txt", "--exires", "60"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unknown option: --exires"); + }); + + it("rejects extra positional args", async () => { + const result = await cmd.execute(["/a.txt", "/b.txt"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unexpected argument: /b.txt"); + }); + + it("rejects --expires without a value", async () => { + const result = await cmd.execute(["/f.txt", "--expires"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--expires requires a value"); + }); + + it("rejects non-numeric --expires", async () => { + const result = await cmd.execute(["/f.txt", "--expires", "abc"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--expires must be a positive integer"); + }); + + it("rejects non-positive --expires", async () => { + const result = await cmd.execute(["/f.txt", "--expires", "-1"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--expires must be a positive integer"); }); it("generates a GET presigned URL", async () => { @@ -90,7 +121,36 @@ describe("snapshot", () => { it("returns error when bucket is missing", async () => { const result = await cmd.execute([], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing bucket"); + expect(result.stderr).toContain("missing "); + expect(result.stderr).toContain("Usage: snapshot"); + }); + + it("rejects unknown options", async () => { + const result = await cmd.execute(["b", "--label", "v1"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unknown option: --label"); + }); + + it("rejects --name and --list together", async () => { + const result = await cmd.execute(["b", "--list", "--name", "v1"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--name and --list cannot be combined"); + }); + + it("rejects extra positional args", async () => { + const result = await cmd.execute(["b", "extra"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unexpected argument: extra"); + }); + + it("prints 'No snapshots.' when listing is empty", async () => { + vi.mocked(listBucketSnapshots).mockResolvedValue({ + data: { snapshots: [] }, + }); + + const result = await cmd.execute(["my-bucket", "--list"], makeCtx()); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("No snapshots."); }); it("creates a snapshot", async () => { @@ -144,10 +204,34 @@ describe("snapshot", () => { describe("fork", () => { const cmd = createForkCommand(config); - it("returns error when arguments are missing", async () => { + it("returns error when source bucket is missing", async () => { + const result = await cmd.execute([], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("missing "); + }); + + it("returns error when fork name is missing", async () => { const result = await cmd.execute(["source-only"], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing arguments"); + expect(result.stderr).toContain("missing "); + }); + + it("rejects source == name", async () => { + const result = await cmd.execute(["same", "same"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("must differ"); + }); + + it("rejects unknown options", async () => { + const result = await cmd.execute(["a", "b", "--snap", "v"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unknown option: --snap"); + }); + + it("rejects extra positional args", async () => { + const result = await cmd.execute(["a", "b", "c"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unexpected argument: c"); }); it("creates a fork", async () => { @@ -180,43 +264,61 @@ describe("fork", () => { expect(result.exitCode).toBe(1); expect(result.stderr).toContain("already exists"); }); -}); -describe("forks", () => { - const cmd = createForksListCommand(config); + describe("--list", () => { + it("returns error when source bucket is missing", async () => { + const result = await cmd.execute(["--list"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("missing "); + }); - it("returns error when bucket is missing", async () => { - const result = await cmd.execute([], makeCtx()); - expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing bucket"); - }); + it("rejects extra positional args", async () => { + const result = await cmd.execute(["a", "b", "--list"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("unexpected argument: b"); + }); - it("lists forks", async () => { - const now = new Date(); - vi.mocked(listForks).mockResolvedValue({ - data: { - forks: [ - { - name: "bucket-a", - creationDate: now, - forkCreatedAt: now, - snapshot: "snap-a", - snapshotCreatedAt: now, - }, - { - name: "bucket-b", - creationDate: now, - forkCreatedAt: now, - snapshot: "snap-b", - snapshotCreatedAt: now, - }, - ], - }, + it("rejects --snapshot + --list", async () => { + const result = await cmd.execute(["a", "--list", "--snapshot", "v1"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--snapshot and --list cannot be combined"); }); - const result = await cmd.execute(["my-bucket"], makeCtx()); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("bucket-a"); - expect(result.stdout).toContain("bucket-b"); + it("prints 'No forks.' when listing is empty", async () => { + vi.mocked(listForks).mockResolvedValue({ data: { forks: [] } }); + + const result = await cmd.execute(["my-bucket", "--list"], makeCtx()); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("No forks."); + }); + + it("lists forks", async () => { + const now = new Date(); + vi.mocked(listForks).mockResolvedValue({ + data: { + forks: [ + { + name: "bucket-a", + creationDate: now, + forkCreatedAt: now, + snapshot: "snap-a", + snapshotCreatedAt: now, + }, + { + name: "bucket-b", + creationDate: now, + forkCreatedAt: now, + snapshot: "snap-b", + snapshotCreatedAt: now, + }, + ], + }, + }); + + const result = await cmd.execute(["my-bucket", "--list"], makeCtx()); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("bucket-a"); + expect(result.stdout).toContain("bucket-b"); + }); }); }); diff --git a/tests/complete.test.ts b/tests/complete.test.ts index 2fb7bbe..51efd44 100644 --- a/tests/complete.test.ts +++ b/tests/complete.test.ts @@ -82,15 +82,12 @@ describe("computeCompletions", () => { expect(hits).toEqual(["alpha"]); }); - it("completes bucket names for 'fork' and 'forks'", async () => { + it("completes bucket names for 'fork'", async () => { const shell = new TigrisShell(TEST_CONFIG); shell.mount("alpha", "/a"); const [forkHits] = await computeCompletions("fork ", { shell, cwd: undefined }); expect(forkHits).toEqual(["alpha"]); - - const [forksHits] = await computeCompletions("forks ", { shell, cwd: undefined }); - expect(forksHits).toEqual(["alpha"]); }); it("falls back to path completion for the second arg of mount", async () => { From 6d080d022327727d5e88435cc1d10a8d22465fb7 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 16:20:27 +0200 Subject: [PATCH 6/7] fix: disable just-bash defense-in-depth so ls works in mounted buckets just-bash v3 enabled defense-in-depth by default, which blocks process.env reads during script execution. The Tigris/AWS SDK reads env (e.g. AWS_PROFILE) inside TigrisAdapter, so ls inside a mounted bucket threw a SecurityViolationError. We don't enable js-exec / python / node, so the sandbox provides no value in our setup. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/shell.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/shell.ts b/src/shell.ts index 02c58e8..9e49d6b 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -45,13 +45,23 @@ export class TigrisShell { this.bash = new Bash({ fs: this.mountableFs, cwd, + // just-bash v3 enables defense-in-depth by default, which blocks + // process.env reads. The Tigris/AWS SDK reads env eagerly (e.g. + // AWS_PROFILE) from inside the TigrisAdapter, so we disable the + // sandbox here. We don't enable js-exec / python / node, so the + // sandbox provides no real value in our setup. + defenseInDepth: false, ...(shellOptions?.env !== undefined && { env: shellOptions.env }), customCommands: [ createPresignCommand(resolvedConfig, { resolveBucket: (path) => this.resolveBucketForPath(path), }), - createSnapshotCommand(resolvedConfig), - createForkCommand(resolvedConfig), + createSnapshotCommand(resolvedConfig, { + resolveBucket: (path) => this.resolveBucketForPath(path), + }), + createForkCommand(resolvedConfig, { + resolveBucket: (path) => this.resolveBucketForPath(path), + }), ], }); } From 8eb642a6b607d8e739c3e18c928c2a1508dc9748 Mon Sep 17 00:00:00 2001 From: A Ibrahim Date: Thu, 21 May 2026 16:20:38 +0200 Subject: [PATCH 7/7] feat: presign --key, cwd-bucket fallback, fork --name - presign: add --key flag. Wins over config.accessKeyId when both exist, required when logged in via 'login' (no access key in session). Passed to GetPresignedUrlOptions.accessKeyId so the URL signer is whatever the user names. - snapshot: is now optional; falls back to the cwd's mounted bucket via the shell's resolveBucket helper. - fork: same cwd fallback for ; the previous positional becomes a --name flag, so the shape is fork [] --name [--snapshot V] for create and fork [] --list for listing. Help text and tests updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/commands/fork.ts | 60 +++++++++-------- src/commands/index.ts | 2 + src/commands/presign.ts | 11 +++- src/commands/snapshot.ts | 27 ++++++-- src/repl/session.ts | 8 +-- tests/commands.test.ts | 138 ++++++++++++++++++++++++++++++++++----- 6 files changed, 188 insertions(+), 58 deletions(-) diff --git a/src/commands/fork.ts b/src/commands/fork.ts index 9c73e04..bbee945 100644 --- a/src/commands/fork.ts +++ b/src/commands/fork.ts @@ -3,19 +3,26 @@ import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; -const USAGE = "fork [--snapshot version] | fork --list"; +const USAGE = + "fork [] --name [--snapshot version] | fork [] --list"; const SCHEMA: FlagSchema = { + "--name": "value", "--snapshot": "value", "--list": "boolean", }; +export interface ForkOptions { + /** Resolve cwd to a mounted bucket so can be omitted. */ + resolveBucket?: (path: string) => { bucket: string; key: string } | null; +} + type ForkInput = | { mode: "create"; sourceBucket: string; forkName: string; snapshotVersion: string | undefined } | { mode: "list"; sourceBucket: string }; -export function createForkCommand(config: TigrisConfig) { - return defineCommand("fork", async (args) => { - const input = parseInput(args); +export function createForkCommand(config: TigrisConfig, options?: ForkOptions) { + return defineCommand("fork", async (args, ctx) => { + const input = parseInput(args, ctx.cwd, options); if ("stderr" in input) return input; if (input.mode === "list") return listForksOf(input.sourceBucket, config); @@ -33,46 +40,45 @@ export function createForkCommand(config: TigrisConfig) { }); } -function parseInput(args: string[]): ForkInput | ExecResult { +function parseInput( + args: string[], + cwd: string, + options: ForkOptions | undefined, +): ForkInput | ExecResult { const parsed = parseFlags(args, SCHEMA); if ("error" in parsed) return argError("fork", parsed.error, USAGE); const { flags, positional } = parsed; + if (positional.length > 1) { + return argError("fork", `unexpected argument: ${positional[1]}`, USAGE); + } + const isList = flags["--list"] === true; + const forkName = typeof flags["--name"] === "string" ? flags["--name"] : undefined; const snapshotVersion = typeof flags["--snapshot"] === "string" ? flags["--snapshot"] : undefined; + if (isList && forkName !== undefined) { + return argError("fork", "--name and --list cannot be combined", USAGE); + } if (isList && snapshotVersion !== undefined) { return argError("fork", "--snapshot and --list cannot be combined", USAGE); } - - if (isList) return parseListInput(positional); - return parseCreateInput(positional, snapshotVersion); -} - -function parseListInput(positional: string[]): ForkInput | ExecResult { - if (positional.length === 0) return argError("fork", "missing ", USAGE); - if (positional.length > 1) { - return argError("fork", `unexpected argument: ${positional[1]}`, USAGE); + if (!isList && forkName === undefined) { + return argError("fork", "either --name or --list is required", USAGE); } - return { mode: "list", sourceBucket: positional[0] ?? "" }; -} -function parseCreateInput( - positional: string[], - snapshotVersion: string | undefined, -): ForkInput | ExecResult { - if (positional.length < 1) return argError("fork", "missing ", USAGE); - if (positional.length < 2) return argError("fork", "missing ", USAGE); - if (positional.length > 2) { - return argError("fork", `unexpected argument: ${positional[2]}`, USAGE); + const sourceBucket = positional[0] ?? options?.resolveBucket?.(cwd)?.bucket; + if (!sourceBucket) { + return argError("fork", "missing (cwd not in a mounted bucket)", USAGE); } - const [sourceBucket, forkName] = positional as [string, string]; + if (isList) return { mode: "list", sourceBucket }; + if (sourceBucket === forkName) { - return argError("fork", " and must differ"); + return argError("fork", " and --name must differ"); } - return { mode: "create", sourceBucket, forkName, snapshotVersion }; + return { mode: "create", sourceBucket, forkName: forkName as string, snapshotVersion }; } async function listForksOf(bucket: string, config: TigrisConfig) { diff --git a/src/commands/index.ts b/src/commands/index.ts index 4dbdff9..7b5a057 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -11,7 +11,9 @@ export function createTigrisCommands(config: TigrisConfig): Command[] { return [createPresignCommand(config), createSnapshotCommand(config), createForkCommand(config)]; } +export type { ForkOptions } from "./fork.js"; export { createForkCommand } from "./fork.js"; export type { PresignOptions } from "./presign.js"; export { createPresignCommand } from "./presign.js"; +export type { SnapshotOptions } from "./snapshot.js"; export { createSnapshotCommand } from "./snapshot.js"; diff --git a/src/commands/presign.ts b/src/commands/presign.ts index 706883b..138c7e3 100644 --- a/src/commands/presign.ts +++ b/src/commands/presign.ts @@ -3,10 +3,11 @@ import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; -const USAGE = "presign [--expires N] [--put]"; +const USAGE = "presign [--expires N] [--put] [--key accessKeyId]"; const SCHEMA: FlagSchema = { "--expires": "value", "--put": "boolean", + "--key": "value", }; const DEFAULT_EXPIRES = 3600; @@ -19,6 +20,7 @@ interface PresignInput { path: string; expiresIn: number; operation: "get" | "put"; + accessKeyOverride: string | undefined; } export function createPresignCommand(config: TigrisConfig, options?: PresignOptions) { @@ -26,8 +28,9 @@ export function createPresignCommand(config: TigrisConfig, options?: PresignOpti const input = parseInput(args); if ("stderr" in input) return input; - if (!config.accessKeyId) { - return argError("presign", "requires access key auth. Use 'configure' instead of 'login'."); + const accessKeyId = input.accessKeyOverride ?? config.accessKeyId; + if (!accessKeyId) { + return argError("presign", "--key is required when logged in via 'login'", USAGE); } const resolved = resolveTarget(input.path, ctx.cwd, config, options); @@ -38,6 +41,7 @@ export function createPresignCommand(config: TigrisConfig, options?: PresignOpti const result = await getPresignedUrl(resolved.key, { operation: input.operation, expiresIn: input.expiresIn, + accessKeyId, config: { ...config, bucket: resolved.bucket }, }); if ("error" in result) return sdkError("presign", result.error); @@ -63,6 +67,7 @@ function parseInput(args: string[]): PresignInput | ExecResult { path: positional[0] ?? "", expiresIn: expires, operation: flags["--put"] === true ? "put" : "get", + accessKeyOverride: typeof flags["--key"] === "string" ? flags["--key"] : undefined, }; } diff --git a/src/commands/snapshot.ts b/src/commands/snapshot.ts index d3e7791..ce7705e 100644 --- a/src/commands/snapshot.ts +++ b/src/commands/snapshot.ts @@ -3,21 +3,26 @@ import { defineCommand, type ExecResult } from "just-bash"; import type { TigrisConfig } from "../types.js"; import { argError, type FlagSchema, parseFlags, sdkError } from "./args.js"; -const USAGE = "snapshot [--name label] [--list]"; +const USAGE = "snapshot [] [--name label] [--list]"; const SCHEMA: FlagSchema = { "--name": "value", "--list": "boolean", }; +export interface SnapshotOptions { + /** Resolve cwd to a mounted bucket so can be omitted. */ + resolveBucket?: (path: string) => { bucket: string; key: string } | null; +} + interface SnapshotInput { bucket: string; mode: "list" | "create"; name: string | undefined; } -export function createSnapshotCommand(config: TigrisConfig) { - return defineCommand("snapshot", async (args) => { - const input = parseInput(args); +export function createSnapshotCommand(config: TigrisConfig, options?: SnapshotOptions) { + return defineCommand("snapshot", async (args, ctx) => { + const input = parseInput(args, ctx.cwd, options); if ("stderr" in input) return input; if (input.mode === "list") return listSnapshots(input.bucket, config); @@ -32,12 +37,15 @@ export function createSnapshotCommand(config: TigrisConfig) { }); } -function parseInput(args: string[]): SnapshotInput | ExecResult { +function parseInput( + args: string[], + cwd: string, + options: SnapshotOptions | undefined, +): SnapshotInput | ExecResult { const parsed = parseFlags(args, SCHEMA); if ("error" in parsed) return argError("snapshot", parsed.error, USAGE); const { flags, positional } = parsed; - if (positional.length === 0) return argError("snapshot", "missing ", USAGE); if (positional.length > 1) { return argError("snapshot", `unexpected argument: ${positional[1]}`, USAGE); } @@ -48,7 +56,12 @@ function parseInput(args: string[]): SnapshotInput | ExecResult { return argError("snapshot", "--name and --list cannot be combined", USAGE); } - return { bucket: positional[0] ?? "", mode: isList ? "list" : "create", name }; + const bucket = positional[0] ?? options?.resolveBucket?.(cwd)?.bucket; + if (!bucket) { + return argError("snapshot", "missing (cwd not in a mounted bucket)", USAGE); + } + + return { bucket, mode: isList ? "list" : "create", name }; } async function listSnapshots(bucket: string, config: TigrisConfig) { diff --git a/src/repl/session.ts b/src/repl/session.ts index 6517fbc..dfb70ee 100644 --- a/src/repl/session.ts +++ b/src/repl/session.ts @@ -413,10 +413,10 @@ export class ReplSession { io.write(" umount Unmount a path\n"); io.write(" df List mounts\n"); io.write(" flush [path] Flush changes to Tigris\n"); - io.write(" presign [--expires N] [--put] Generate a presigned URL\n"); - io.write(" snapshot [--name N] [--list] Create or list snapshots\n"); - io.write(" fork [--snapshot V] Fork a bucket\n"); - io.write(" fork --list List forks\n"); + io.write(" presign [--expires N] [--put] [--key] Generate a presigned URL\n"); + io.write(" snapshot [] [--name N] [--list] Create or list snapshots\n"); + io.write(" fork [] --name [--snapshot V] Fork a bucket\n"); + io.write(" fork [] --list List forks\n"); io.write("\nShell:\n"); io.write(" clear Clear screen\n"); io.write(" help Show this help\n"); diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 37c07be..3457ecf 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -24,15 +24,23 @@ import { TEST_CONFIG_WITH_BUCKET } from "./helpers.js"; const config = TEST_CONFIG_WITH_BUCKET; // Minimal CommandContext for testing -function makeCtx() { +function makeCtx(cwd = "/") { return { fs: {} as never, - cwd: "/", + cwd, env: new Map(), stdin: EMPTY_BYTES, }; } +// Resolves any path under //* to that bucket. Used to test the +// cwd-bucket fallback in snapshot and fork. +function stubResolveBucket(path: string): { bucket: string; key: string } | null { + const match = /^\/([^/]+)(?:\/(.*))?$/.exec(path); + if (!match) return null; + return { bucket: match[1] ?? "", key: match[2] ?? "" }; +} + afterEach(() => { vi.clearAllMocks(); }); @@ -88,6 +96,7 @@ describe("presign", () => { expect(vi.mocked(getPresignedUrl)).toHaveBeenCalledWith("file.txt", { operation: "get", expiresIn: 3600, + accessKeyId: "tid_test", config, }); }); @@ -102,10 +111,52 @@ describe("presign", () => { expect(vi.mocked(getPresignedUrl)).toHaveBeenCalledWith("file.txt", { operation: "put", expiresIn: 7200, + accessKeyId: "tid_test", config, }); }); + it("--key overrides the configured access key", async () => { + vi.mocked(getPresignedUrl).mockResolvedValue({ + data: { url: "https://example.com/signed", expiresIn: 3600, operation: "get" }, + }); + + const result = await cmd.execute(["/file.txt", "--key", "tid_other"], makeCtx()); + expect(result.exitCode).toBe(0); + expect(vi.mocked(getPresignedUrl)).toHaveBeenCalledWith( + "file.txt", + expect.objectContaining({ accessKeyId: "tid_other" }), + ); + }); + + describe("OAuth session (no access key in config)", () => { + const oauthConfig: typeof config = { + sessionToken: "session_test", + organizationId: "org_test", + bucket: "test", + }; + const oauthCmd = createPresignCommand(oauthConfig); + + it("errors when --key is not provided", async () => { + const result = await oauthCmd.execute(["/file.txt"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--key is required"); + }); + + it("succeeds when --key is provided", async () => { + vi.mocked(getPresignedUrl).mockResolvedValue({ + data: { url: "https://example.com/signed", expiresIn: 3600, operation: "get" }, + }); + + const result = await oauthCmd.execute(["/file.txt", "--key", "tid_user"], makeCtx()); + expect(result.exitCode).toBe(0); + expect(vi.mocked(getPresignedUrl)).toHaveBeenCalledWith( + "file.txt", + expect.objectContaining({ accessKeyId: "tid_user" }), + ); + }); + }); + it("returns error on SDK failure", async () => { vi.mocked(getPresignedUrl).mockResolvedValue({ error: new Error("denied") }); @@ -118,13 +169,40 @@ describe("presign", () => { describe("snapshot", () => { const cmd = createSnapshotCommand(config); - it("returns error when bucket is missing", async () => { + it("returns error when bucket is missing and cwd not in a mount", async () => { const result = await cmd.execute([], makeCtx()); expect(result.exitCode).toBe(1); expect(result.stderr).toContain("missing "); expect(result.stderr).toContain("Usage: snapshot"); }); + it("falls back to cwd bucket when positional omitted", async () => { + const cmdWithResolve = createSnapshotCommand(config, { resolveBucket: stubResolveBucket }); + vi.mocked(createBucketSnapshot).mockResolvedValue({ + data: { snapshotVersion: "1713200000" }, + }); + + const result = await cmdWithResolve.execute([], makeCtx("/my-bucket/sub")); + expect(result.exitCode).toBe(0); + expect(vi.mocked(createBucketSnapshot)).toHaveBeenCalledWith( + "my-bucket", + expect.objectContaining({ config }), + ); + }); + + it("explicit bucket wins over cwd fallback", async () => { + const cmdWithResolve = createSnapshotCommand(config, { resolveBucket: stubResolveBucket }); + vi.mocked(createBucketSnapshot).mockResolvedValue({ + data: { snapshotVersion: "1713200000" }, + }); + + await cmdWithResolve.execute(["explicit"], makeCtx("/my-bucket")); + expect(vi.mocked(createBucketSnapshot)).toHaveBeenCalledWith( + "explicit", + expect.objectContaining({ config }), + ); + }); + it("rejects unknown options", async () => { const result = await cmd.execute(["b", "--label", "v1"], makeCtx()); expect(result.exitCode).toBe(1); @@ -204,40 +282,40 @@ describe("snapshot", () => { describe("fork", () => { const cmd = createForkCommand(config); - it("returns error when source bucket is missing", async () => { - const result = await cmd.execute([], makeCtx()); + it("returns error when neither --name nor --list is given", async () => { + const result = await cmd.execute(["source-only"], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing "); + expect(result.stderr).toContain("either --name or --list is required"); }); - it("returns error when fork name is missing", async () => { - const result = await cmd.execute(["source-only"], makeCtx()); + it("returns error when source bucket is missing and cwd not in a mount", async () => { + const result = await cmd.execute(["--name", "fork-name"], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("missing "); + expect(result.stderr).toContain("missing "); }); it("rejects source == name", async () => { - const result = await cmd.execute(["same", "same"], makeCtx()); + const result = await cmd.execute(["same", "--name", "same"], makeCtx()); expect(result.exitCode).toBe(1); expect(result.stderr).toContain("must differ"); }); it("rejects unknown options", async () => { - const result = await cmd.execute(["a", "b", "--snap", "v"], makeCtx()); + const result = await cmd.execute(["a", "--name", "b", "--snap", "v"], makeCtx()); expect(result.exitCode).toBe(1); expect(result.stderr).toContain("unknown option: --snap"); }); it("rejects extra positional args", async () => { - const result = await cmd.execute(["a", "b", "c"], makeCtx()); + const result = await cmd.execute(["a", "b", "--name", "fork"], makeCtx()); expect(result.exitCode).toBe(1); - expect(result.stderr).toContain("unexpected argument: c"); + expect(result.stderr).toContain("unexpected argument: b"); }); it("creates a fork", async () => { vi.mocked(createBucket).mockResolvedValue({ data: {} as never }); - const result = await cmd.execute(["source-bucket", "my-fork"], makeCtx()); + const result = await cmd.execute(["source-bucket", "--name", "my-fork"], makeCtx()); expect(result.exitCode).toBe(0); expect(result.stdout.trim()).toBe("my-fork"); expect(vi.mocked(createBucket)).toHaveBeenCalledWith("my-fork", { @@ -249,7 +327,7 @@ describe("fork", () => { it("creates a fork from a snapshot", async () => { vi.mocked(createBucket).mockResolvedValue({ data: {} as never }); - await cmd.execute(["source", "fork-name", "--snapshot", "1713200000"], makeCtx()); + await cmd.execute(["source", "--name", "fork-name", "--snapshot", "1713200000"], makeCtx()); expect(vi.mocked(createBucket)).toHaveBeenCalledWith("fork-name", { sourceBucketName: "source", sourceBucketSnapshot: "1713200000", @@ -257,21 +335,41 @@ describe("fork", () => { }); }); + it("falls back to cwd bucket for source when omitted", async () => { + const cmdWithResolve = createForkCommand(config, { resolveBucket: stubResolveBucket }); + vi.mocked(createBucket).mockResolvedValue({ data: {} as never }); + + await cmdWithResolve.execute(["--name", "new-fork"], makeCtx("/my-bucket/sub")); + expect(vi.mocked(createBucket)).toHaveBeenCalledWith( + "new-fork", + expect.objectContaining({ sourceBucketName: "my-bucket" }), + ); + }); + it("returns error on SDK failure", async () => { vi.mocked(createBucket).mockResolvedValue({ error: new Error("already exists") }); - const result = await cmd.execute(["source", "fork"], makeCtx()); + const result = await cmd.execute(["source", "--name", "fork"], makeCtx()); expect(result.exitCode).toBe(1); expect(result.stderr).toContain("already exists"); }); describe("--list", () => { - it("returns error when source bucket is missing", async () => { + it("returns error when source bucket is missing and cwd not in a mount", async () => { const result = await cmd.execute(["--list"], makeCtx()); expect(result.exitCode).toBe(1); expect(result.stderr).toContain("missing "); }); + it("falls back to cwd bucket when source omitted", async () => { + const cmdWithResolve = createForkCommand(config, { resolveBucket: stubResolveBucket }); + vi.mocked(listForks).mockResolvedValue({ data: { forks: [] } }); + + const result = await cmdWithResolve.execute(["--list"], makeCtx("/my-bucket")); + expect(result.exitCode).toBe(0); + expect(vi.mocked(listForks)).toHaveBeenCalledWith("my-bucket", expect.anything()); + }); + it("rejects extra positional args", async () => { const result = await cmd.execute(["a", "b", "--list"], makeCtx()); expect(result.exitCode).toBe(1); @@ -284,6 +382,12 @@ describe("fork", () => { expect(result.stderr).toContain("--snapshot and --list cannot be combined"); }); + it("rejects --name + --list", async () => { + const result = await cmd.execute(["a", "--list", "--name", "x"], makeCtx()); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("--name and --list cannot be combined"); + }); + it("prints 'No forks.' when listing is empty", async () => { vi.mocked(listForks).mockResolvedValue({ data: { forks: [] } });