diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2e5b33a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + pull_request: + branches: [main] + paths-ignore: + - "docs/**" + - "*.md" + - "LICENSE" + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + lint: + name: Lint & Format + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .nvmrc + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Generate Wrangler types + run: pnpm types + + - name: Oxlint + run: pnpm lint + + - name: Oxfmt + run: pnpm format:check + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .nvmrc + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Generate Wrangler types + run: pnpm types + + - name: Run tests + run: pnpm --filter @uploads/storage test && pnpm --filter @buildinternet/uploads test diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..adb5558 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.14.0 \ No newline at end of file diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..4ae2d1f --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,22 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "arrowParens": "always", + "endOfLine": "lf", + "sortPackageJson": false, + "ignorePatterns": [ + "node_modules", + ".wrangler", + "apps/web/dist", + "apps/web/.astro", + "**/*.d.ts", + "pnpm-lock.yaml", + "packages/*/dist", + ".superpowers" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..b5bb8fd --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,111 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc"], + "options": { + "typeAware": true, + "typeCheck": true + }, + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn" + }, + "rules": { + "no-console": "off", + "no-underscore-dangle": "off", + "no-await-in-loop": "off", + "no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }], + "typescript/no-explicit-any": "off", + "typescript/no-non-null-assertion": "off", + "unicorn/no-null": "off", + "unicorn/no-array-reduce": "off", + "unicorn/no-await-expression-member": "off", + + "typescript/await-thenable": "error", + "typescript/consistent-return": "off", + "typescript/consistent-type-exports": "off", + "typescript/dot-notation": "off", + "typescript/no-array-delete": "off", + "typescript/no-base-to-string": "off", + "typescript/no-confusing-void-expression": "off", + "typescript/no-deprecated": "off", + "typescript/no-duplicate-type-constituents": "off", + "typescript/no-floating-promises": "error", + "typescript/no-for-in-array": "off", + "typescript/no-implied-eval": "off", + "typescript/no-meaningless-void-operator": "off", + "typescript/no-misused-promises": [ + "error", + { + "checksVoidReturn": { + "attributes": false + } + } + ], + "typescript/no-misused-spread": "off", + "typescript/no-mixed-enums": "off", + "typescript/no-redundant-type-constituents": "off", + "typescript/no-unnecessary-boolean-literal-compare": "off", + "typescript/no-unnecessary-condition": "off", + "typescript/no-unnecessary-qualifier": "off", + "typescript/no-unnecessary-template-expression": "off", + "typescript/no-unnecessary-type-arguments": "off", + "typescript/no-unnecessary-type-assertion": "off", + "typescript/no-unnecessary-type-conversion": "off", + "typescript/no-unnecessary-type-parameters": "off", + "typescript/no-unsafe-argument": "off", + "typescript/no-unsafe-assignment": "off", + "typescript/no-unsafe-call": "off", + "typescript/no-unsafe-enum-comparison": "off", + "typescript/no-unsafe-member-access": "off", + "typescript/no-unsafe-return": "off", + "typescript/no-unsafe-type-assertion": "off", + "typescript/no-unsafe-unary-minus": "off", + "typescript/no-useless-default-assignment": "off", + "typescript/non-nullable-type-assertion-style": "off", + "typescript/only-throw-error": "off", + "typescript/prefer-find": "off", + "typescript/prefer-includes": "off", + "typescript/prefer-nullish-coalescing": "off", + "typescript/prefer-optional-chain": "off", + "typescript/prefer-promise-reject-errors": "off", + "typescript/prefer-readonly-parameter-types": "off", + "typescript/prefer-readonly": "off", + "typescript/prefer-reduce-type-parameter": "off", + "typescript/prefer-regexp-exec": "off", + "typescript/prefer-return-this-type": "off", + "typescript/prefer-string-starts-ends-with": "off", + "typescript/promise-function-async": "off", + "typescript/related-getter-setter-pairs": "off", + "typescript/require-array-sort-compare": "off", + "typescript/require-await": "off", + "typescript/restrict-plus-operands": "off", + "typescript/restrict-template-expressions": "off", + "typescript/return-await": "off", + "typescript/strict-boolean-expressions": "off", + "typescript/strict-void-return": "off", + "typescript/switch-exhaustiveness-check": "off", + "typescript/unbound-method": "off", + "typescript/use-unknown-in-catch-callback-variable": "off" + }, + "overrides": [ + { + "files": ["**/*.test.ts", "packages/storage/test/**", "packages/uploads/test/**"], + "rules": { + "typescript/no-floating-promises": "off", + "typescript/await-thenable": "off", + "unicorn/no-array-sort": "off" + } + } + ], + "ignorePatterns": [ + "node_modules", + ".wrangler", + "apps/web/dist", + "apps/web/.astro", + "**/*.d.ts", + "packages/*/dist/**", + "apps/api/scripts/**", + ".superpowers/**" + ] +} diff --git a/README.md b/README.md index 599e7d8..e8aea5e 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ register with `--bucket` and the record points at a dedicated bucket (own binding or S3 credentials). Nothing in the code treats any workspace as special. The default workspace ships ready to use: -| Workspace | Bucket | Public base URL | -|---|---|---| +| Workspace | Bucket | Public base URL | +| --------- | ----------------- | ---------------------------------------------- | | `default` | `uploads-default` | `https://storage.uploads.sh` — generic hosting | Workspace records live in the `REGISTRY` KV namespace (`ws:`). Each @@ -105,13 +105,13 @@ curl -XDELETE https://api.uploads.sh/admin/tokens \ All `/v1` routes require the workspace's `Authorization: Bearer `. Unknown workspaces and bad tokens are indistinguishable (both 401). -| Route | Description | -|---|---| -| `GET /health` | Liveness (no auth) | -| `PUT /v1/:workspace/files/:key` | Upload raw body; `Content-Type` header is stored. Returns `{ workspace, key, url, size }` | -| `GET /v1/:workspace/files?prefix=&limit=&cursor=` | List objects | -| `GET /v1/:workspace/files/:key` | Object metadata | -| `DELETE /v1/:workspace/files/:key` | Delete object | +| Route | Description | +| ------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `GET /health` | Liveness (no auth) | +| `PUT /v1/:workspace/files/:key` | Upload raw body; `Content-Type` header is stored. Returns `{ workspace, key, url, size }` | +| `GET /v1/:workspace/files?prefix=&limit=&cursor=` | List objects | +| `GET /v1/:workspace/files/:key` | Object metadata | +| `DELETE /v1/:workspace/files/:key` | Delete object | `url` in responses is the public URL when the workspace has a `publicBaseUrl`, otherwise `null`. diff --git a/apps/api/scripts/add-workspace.mjs b/apps/api/scripts/add-workspace.mjs index f2a3379..a182884 100644 --- a/apps/api/scripts/add-workspace.mjs +++ b/apps/api/scripts/add-workspace.mjs @@ -31,11 +31,17 @@ for (let i = 0; i < rest.length; i++) { const flag = rest[i]; if (!flag.startsWith("--")) fail(`unexpected argument: ${flag}`); const key = flag.slice(2); - if (key === "local") { opts.local = true; continue; } + if (key === "local") { + opts.local = true; + continue; + } opts[key] = rest[++i]; } -function fail(msg) { console.error(`error: ${msg}`); process.exit(1); } +function fail(msg) { + console.error(`error: ${msg}`); + process.exit(1); +} if (!name || !/^[a-z0-9][a-z0-9-]{1,62}$/.test(name)) { fail("workspace name must be lowercase alphanumeric/hyphens, 2-63 chars"); @@ -48,11 +54,13 @@ const SHARED = { publicBaseUrl: "https://storage.uploads.sh", }; -const tokens = [{ - hash: crypto.createHash("sha256").update(token).digest("hex"), - label: "initial", - createdAt: new Date().toISOString(), -}]; +const tokens = [ + { + hash: crypto.createHash("sha256").update(token).digest("hex"), + label: "initial", + createdAt: new Date().toISOString(), + }, +]; const record = opts.bucket ? { @@ -85,9 +93,15 @@ Object.keys(record).forEach((k) => record[k] === undefined && delete record[k]); execFileSync( "pnpm", [ - "exec", "wrangler", "kv", "key", "put", `ws:${name}`, + "exec", + "wrangler", + "kv", + "key", + "put", + `ws:${name}`, JSON.stringify(record), - "--binding", "REGISTRY", + "--binding", + "REGISTRY", opts.local ? "--local" : "--remote", ], { stdio: "inherit" }, @@ -96,4 +110,6 @@ execFileSync( console.log(`\nworkspace : ${name}`); console.log(`token : ${token}`); console.log("\nStore the token now — only its hash is kept in KV."); -console.log(`try it : curl -H "Authorization: Bearer ${token}" https://api.uploads.sh/v1/${name}/files`); +console.log( + `try it : curl -H "Authorization: Bearer ${token}" https://api.uploads.sh/v1/${name}/files`, +); diff --git a/apps/api/src/routes/admin.ts b/apps/api/src/routes/admin.ts index bce9a8b..c9c6c1d 100644 --- a/apps/api/src/routes/admin.ts +++ b/apps/api/src/routes/admin.ts @@ -6,8 +6,13 @@ const WS_NAME_RE = /^[a-z0-9][a-z0-9-]{1,62}$/; const HASH_PREFIX_LEN = 8; /** Token list for a record, migrating a legacy `tokenHash`-only record into the list shape. */ -function migrateTokens(record: WorkspaceRecord): { hash: string; label?: string; createdAt: string }[] { - return record.tokens ?? (record.tokenHash ? [{ hash: record.tokenHash, createdAt: new Date(0).toISOString() }] : []); +function migrateTokens( + record: WorkspaceRecord, +): { hash: string; label?: string; createdAt: string }[] { + return ( + record.tokens ?? + (record.tokenHash ? [{ hash: record.tokenHash, createdAt: new Date(0).toISOString() }] : []) + ); } export const admin = new Hono<{ Bindings: Env }>() @@ -26,8 +31,12 @@ export const admin = new Hono<{ Bindings: Env }>() const record = await c.env.REGISTRY.get(`ws:${name}`, { type: "json" }); if (!record) return c.json({ error: "workspace not found" }, 404); - const token = `up_${name}_${btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(24)))) - .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`; + const token = `up_${name}_${btoa( + String.fromCharCode(...crypto.getRandomValues(new Uint8Array(24))), + ) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "")}`; const entry = { hash: await sha256Hex(token), label, createdAt: new Date().toISOString() }; const tokens = migrateTokens(record); diff --git a/apps/api/src/routes/files.ts b/apps/api/src/routes/files.ts index 9782d4f..be7dd0f 100644 --- a/apps/api/src/routes/files.ts +++ b/apps/api/src/routes/files.ts @@ -2,7 +2,7 @@ import { Hono } from "hono"; import { publicUrl, storage, storageConfig } from "../storage"; import type { WorkspaceVars } from "../workspace"; -const KEY_RE = /^[\w!*'().\/-]+$/; +const KEY_RE = /^[\w!*'()./-]+$/; function badKey(key: string): boolean { return ( @@ -26,7 +26,10 @@ export const files = new Hono() await storage(c.env, ws).upload(key, new Uint8Array(body), { contentType }); const url = publicUrl(storageConfig(c.env, ws), key); - return c.json({ workspace: c.get("workspaceName"), key, url, size: body.byteLength, contentType }, 201); + return c.json( + { workspace: c.get("workspaceName"), key, url, size: body.byteLength, contentType }, + 201, + ); }) // List diff --git a/apps/api/wrangler.jsonc b/apps/api/wrangler.jsonc index 09c6807..9884f2c 100644 --- a/apps/api/wrangler.jsonc +++ b/apps/api/wrangler.jsonc @@ -7,12 +7,12 @@ "routes": [ { "pattern": "api.uploads.sh", - "custom_domain": true - } + "custom_domain": true, + }, ], "observability": { "enabled": true, - "head_sampling_rate": 1 + "head_sampling_rate": 1, }, // Workspace registry: `ws:` → WorkspaceRecord (see src/workspace.ts). // Create with `wrangler kv namespace create ` and paste the id here. @@ -21,8 +21,8 @@ "kv_namespaces": [ { "binding": "REGISTRY", - "id": "808bc1c3a86a4675b134e11b6dca303d" - } + "id": "808bc1c3a86a4675b134e11b6dca303d", + }, ], // One binding per same-account bucket a workspace wants binding-mode I/O on. // Workspaces reference these by name; workspaces without a binding fall back @@ -30,11 +30,11 @@ "r2_buckets": [ { "binding": "UPLOADS", - "bucket_name": "buildinternet-dev" + "bucket_name": "buildinternet-dev", }, { "binding": "UPLOADS_DEFAULT", - "bucket_name": "uploads-default" - } - ] + "bucket_name": "uploads-default", + }, + ], } diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index f7d04af..1e76ad2 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -4,12 +4,12 @@ "compatibility_date": "2026-07-06", "assets": { "directory": "./dist", - "not_found_handling": "404-page" + "not_found_handling": "404-page", }, "routes": [ { "pattern": "uploads.sh", - "custom_domain": true - } - ] + "custom_domain": true, + }, + ], } diff --git a/docs/superpowers/plans/2026-07-07-admin-token-minting.md b/docs/superpowers/plans/2026-07-07-admin-token-minting.md index 21cd0fe..59a9658 100644 --- a/docs/superpowers/plans/2026-07-07-admin-token-minting.md +++ b/docs/superpowers/plans/2026-07-07-admin-token-minting.md @@ -23,10 +23,12 @@ ### Task 1: Multi-token record schema + auth **Files:** + - Modify: `apps/api/src/workspace.ts` (schema `WorkspaceRecord`; `workspaceAuth` hash-list compare) - Create: `apps/api/src/env.d.ts` (type `ADMIN_TOKEN` onto `Env`) **Interfaces:** + - Produces: - `interface WorkspaceRecord` gains `tokens?: { hash: string; label?: string; createdAt: string }[]` and marks `tokenHash?` optional/legacy. - `export function workspaceTokenHashes(record: WorkspaceRecord): string[]` — returns `record.tokens?.map(t => t.hash) ?? (record.tokenHash ? [record.tokenHash] : [])`. Reused by Task 2's endpoint. @@ -72,18 +74,18 @@ export function workspaceTokenHashes(record: WorkspaceRecord): string[] { Replace the compare block (the `const providedHash` / `expectedHash` / `ok` section) in `workspaceAuth` with a candidate-list match that keeps a constant-ish work profile: ```ts - const providedHash = await sha256Hex(token); - const providedBytes = hexToBytes(providedHash); - const candidates = record ? workspaceTokenHashes(record) : []; - // Always compare at least once so unknown workspaces cost the same. - const toCheck = candidates.length > 0 ? candidates : [providedHash.replace(/./g, "0")]; - let matched = false; - for (const hash of toCheck) { - if (crypto.subtle.timingSafeEqual(providedBytes, hexToBytes(hash))) matched = true; - } - const ok = record !== null && token.length > 0 && candidates.length > 0 && matched; - - if (!ok || !record || !name) return c.json({ error: "unauthorized" }, 401); +const providedHash = await sha256Hex(token); +const providedBytes = hexToBytes(providedHash); +const candidates = record ? workspaceTokenHashes(record) : []; +// Always compare at least once so unknown workspaces cost the same. +const toCheck = candidates.length > 0 ? candidates : [providedHash.replace(/./g, "0")]; +let matched = false; +for (const hash of toCheck) { + if (crypto.subtle.timingSafeEqual(providedBytes, hexToBytes(hash))) matched = true; +} +const ok = record !== null && token.length > 0 && candidates.length > 0 && matched; + +if (!ok || !record || !name) return c.json({ error: "unauthorized" }, 401); ``` - [ ] **Step 4: Typecheck** @@ -115,11 +117,13 @@ git commit -m "Support multiple bearer tokens per workspace" ### Task 2: Admin auth middleware + mint endpoint **Files:** + - Create: `apps/api/src/admin.ts` (adminAuth middleware) - Create: `apps/api/src/routes/admin.ts` (POST /admin/tokens) - Modify: `apps/api/src/index.ts` (mount the route) **Interfaces:** + - Consumes: `sha256Hex`, `workspaceTokenHashes`, `WorkspaceRecord` from `./workspace` (Task 1). - Produces: - `export const adminAuth: MiddlewareHandler<{ Bindings: Env }>` — 401 unless `Authorization: Bearer <ADMIN_TOKEN>` matches the secret; fails closed if the secret is unset. @@ -183,11 +187,17 @@ export const admin = new Hono<{ Bindings: Env }>() const record = await c.env.REGISTRY.get<WorkspaceRecord>(`ws:${name}`, { type: "json" }); if (!record) return c.json({ error: "workspace not found" }, 404); - const token = `up_${name}_${btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(24)))) - .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`; + const token = `up_${name}_${btoa( + String.fromCharCode(...crypto.getRandomValues(new Uint8Array(24))), + ) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "")}`; const entry = { hash: await sha256Hex(token), label, createdAt: new Date().toISOString() }; - const tokens = record.tokens ?? (record.tokenHash ? [{ hash: record.tokenHash, createdAt: entry.createdAt }] : []); + const tokens = + record.tokens ?? + (record.tokenHash ? [{ hash: record.tokenHash, createdAt: entry.createdAt }] : []); tokens.push(entry); const { tokenHash: _drop, ...rest } = record; await c.env.REGISTRY.put(`ws:${name}`, JSON.stringify({ ...rest, tokens })); @@ -260,9 +270,11 @@ git commit -m "Add admin endpoint to mint workspace tokens" ### Task 3: Emit the new token format from `add-workspace.mjs` **Files:** + - Modify: `apps/api/scripts/add-workspace.mjs` (write `tokens: [...]` instead of `tokenHash`) **Interfaces:** + - Consumes: nothing new. Produces records in the Task 1 `tokens[]` shape. - [ ] **Step 1: Write the `tokens` list instead of `tokenHash`** @@ -309,6 +321,7 @@ git commit -m "Emit list-format token from add-workspace script" ### Task 4: Docs + env templates **Files:** + - Modify: `README.md` (active-development callout + "Minting tokens" section) - Modify: `apps/api/.dev.vars.example` (add `ADMIN_TOKEN`) - Modify: `.env.example` (note the admin mint flow) @@ -410,6 +423,7 @@ git commit -m "Document token minting and admin secret" ## Self-Review **Spec coverage:** + - Multi-token schema + legacy read → Task 1. ✓ - Auth matches any hash → Task 1 Step 3. ✓ - `adminAuth` secret, fails closed → Task 2 Step 1. ✓ diff --git a/docs/superpowers/plans/2026-07-07-github-companion-v1.md b/docs/superpowers/plans/2026-07-07-github-companion-v1.md index 70b0415..5ba084a 100644 --- a/docs/superpowers/plans/2026-07-07-github-companion-v1.md +++ b/docs/superpowers/plans/2026-07-07-github-companion-v1.md @@ -29,10 +29,12 @@ The repo currently has **no tests anywhere**. Set up vitest for `packages/uploads` and prove the harness with one test of an existing function. **Files:** + - Modify: `packages/uploads/package.json` (add devDependency + script) - Create: `packages/uploads/test/embed.test.ts` **Interfaces:** + - Consumes: `buildMarkdown(url, {alt, width?})` from `packages/uploads/src/embed.ts` (exists). - Produces: the `test` script (`pnpm --filter @buildinternet/uploads test`) that every later task uses. Tests live in `packages/uploads/test/`, import sources as `../src/<module>.js`, and are excluded from the build (tsconfig `include` is `["src"]` — leave it alone). @@ -94,10 +96,12 @@ git commit -m "test: add vitest harness to @buildinternet/uploads" ### Task 2: Pure GitHub key helpers (`src/github.ts`, part 1) **Files:** + - Create: `packages/uploads/src/github.ts` - Test: `packages/uploads/test/github.test.ts` **Interfaces:** + - Consumes: `sanitizeKeySegment(s: string): string` from `src/keys.ts` (exists; strips everything but `A-Za-z0-9._-`). - Produces (later tasks depend on these exact names): - `type GhTargetKind = "pull" | "issues"` @@ -164,9 +168,7 @@ describe("ghKeyPrefix / ghAttachmentKey", () => { expect(ghKeyPrefix({ repo: "o/r", kind: "issues", num: 7 })).toBe("gh/o/r/issues/7/"); }); it("builds a stable key with no content hash", () => { - expect(ghAttachmentKey(pr, "after.png")).toBe( - "gh/buildinternet/uploads/pull/123/after.png", - ); + expect(ghAttachmentKey(pr, "after.png")).toBe("gh/buildinternet/uploads/pull/123/after.png"); }); it("sanitizes filename characters", () => { expect(ghAttachmentKey(pr, "my shot (1).png")).toBe( @@ -249,10 +251,12 @@ git commit -m "feat(uploads): stable PR/issue attachment keys" ### Task 3: Managed-comment marker and body (`src/github.ts`, part 2) **Files:** + - Modify: `packages/uploads/src/github.ts` (append) - Test: `packages/uploads/test/github.test.ts` (append) **Interfaces:** + - Consumes: `inferContentType(filename: string): string` from `src/embed.ts` (exists). - Produces: - `ATTACHMENTS_MARKER` — the exact string `<!-- uploads.sh:attachments -->` @@ -352,10 +356,12 @@ git commit -m "feat(uploads): managed attachments-comment body generation" ### Task 4: `gh`/`git` wrapper with injectable runner (`src/github-gh.ts`) **Files:** + - Create: `packages/uploads/src/github-gh.ts` - Test: `packages/uploads/test/github-gh.test.ts` **Interfaces:** + - Consumes: `UsageError` from `src/cli-args.ts`; `ATTACHMENTS_MARKER`, `isValidRepo`, `parseRepoFromRemoteUrl`, `GhTarget` from `src/github.ts` (Tasks 2–3). - Produces: - `type CommandRunner = (cmd: string, args: string[], input?: string) => string` — returns stdout, throws on non-zero exit @@ -493,10 +499,7 @@ export const execRunner: CommandRunner = (cmd, args, input) => * Resolve "owner/name". Order: explicit --repo (validated) → `gh repo view` * (fork-aware) → parse the origin remote → UsageError. */ -export function resolveRepo( - explicit: string | undefined, - run: CommandRunner = execRunner, -): string { +export function resolveRepo(explicit: string | undefined, run: CommandRunner = execRunner): string { if (explicit !== undefined) { if (!isValidRepo(explicit)) { throw new UsageError(`--repo must be owner/name (got: ${explicit})`); @@ -538,9 +541,7 @@ interface GhComment { function findManagedComment(target: GhTarget, run: CommandRunner): GhComment | undefined { const raw = run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments?per_page=100`]); const comments = JSON.parse(raw) as GhComment[]; - return comments.find( - (c) => typeof c.body === "string" && c.body.includes(ATTACHMENTS_MARKER), - ); + return comments.find((c) => typeof c.body === "string" && c.body.includes(ATTACHMENTS_MARKER)); } /** @@ -569,11 +570,7 @@ export function upsertAttachmentsComment( ); return { created: false }; } - run( - "gh", - ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], - body, - ); + run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], body); return { created: true }; } ``` @@ -597,10 +594,12 @@ git commit -m "feat(uploads): gh wrapper — repo inference and comment upsert" ### Task 5: `put --pr` / `put --issue` (stable keys) **Files:** + - Modify: `packages/uploads/src/commands.ts` - Test: `packages/uploads/test/commands-put.test.ts` **Interfaces:** + - Consumes: `ghAttachmentKey`, `GhTarget` (Task 2); `resolveRepo`, `execRunner`, `CommandRunner` (Task 4); existing `runPut`, `CliContext`, `flagInt`, `flagString`, `flagBool`, `UsageError`. - Produces: - `runPut(ctx, args, help?, run?: CommandRunner)` — 4th parameter added, defaults to `execRunner`. `cli.ts` keeps calling it with 3 args. @@ -693,21 +692,36 @@ describe("runPut --pr/--issue", () => { it("rejects --pr with --issue", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--issue", "2", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--issue", "2", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); it("rejects --pr with --key", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--key", "x/y.png", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--key", "x/y.png", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); it("rejects --pr with --ref", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--ref", "abc", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--ref", "abc", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); @@ -732,8 +746,19 @@ Expected: FAIL — `runPut` takes 3 args / keys don't match. Add imports at the top (merge with existing import lines): ```ts -import { ghAttachmentKey, ghKeyPrefix, attachmentsCommentBody, type GhTarget, type AttachmentItem } from "./github.js"; -import { resolveRepo, execRunner, upsertAttachmentsComment, type CommandRunner } from "./github-gh.js"; +import { + ghAttachmentKey, + ghKeyPrefix, + attachmentsCommentBody, + type GhTarget, + type AttachmentItem, +} from "./github.js"; +import { + resolveRepo, + execRunner, + upsertAttachmentsComment, + type CommandRunner, +} from "./github-gh.js"; import type { CommandFlags } from "./cli-args.js"; ``` @@ -743,10 +768,7 @@ Add the shared helper (above `runPut`): ```ts /** Reads --pr/--issue (+ --repo) into a GhTarget; undefined when neither flag is present. */ -function ghTargetFromFlags( - flags: CommandFlags["flags"], - run: CommandRunner, -): GhTarget | undefined { +function ghTargetFromFlags(flags: CommandFlags["flags"], run: CommandRunner): GhTarget | undefined { const pr = flagInt(flags, "--pr", "--pr"); const issue = flagInt(flags, "--issue", "--issue"); if (pr === undefined && issue === undefined) return undefined; @@ -772,13 +794,13 @@ export async function runPut( Inside `runPut`, right after `const keyHint = flagString(parsed.flags, "--key");` (before reading file bytes), add: ```ts - const ghTarget = ghTargetFromFlags(parsed.flags, run); - if (ghTarget) { - if (keyHint) throw new UsageError("--key cannot be combined with --pr/--issue"); - if (flagString(parsed.flags, "--ref")) { - throw new UsageError("--ref cannot be combined with --pr/--issue"); - } +const ghTarget = ghTargetFromFlags(parsed.flags, run); +if (ghTarget) { + if (keyHint) throw new UsageError("--key cannot be combined with --pr/--issue"); + if (flagString(parsed.flags, "--ref")) { + throw new UsageError("--ref cannot be combined with --pr/--issue"); } +} ``` Change the `ctx.client.put` call's `key` line from `key: keyHint,` to: @@ -818,11 +840,13 @@ git commit -m "feat(uploads): put --pr/--issue with stable attachment keys" ### Task 6: `uploads comment` command + `put --comment` **Files:** + - Modify: `packages/uploads/src/commands.ts` - Modify: `packages/uploads/src/cli.ts` - Test: `packages/uploads/test/commands-comment.test.ts` **Interfaces:** + - Consumes: `ghTargetFromFlags` (Task 5), `ghKeyPrefix`, `attachmentsCommentBody`, `AttachmentItem` (Tasks 2–3), `upsertAttachmentsComment` (Task 4). - Produces: - `runComment(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>` — exported from `commands.ts`, wired into `cli.ts` as the `comment` command (authenticated path, same as `put`). @@ -875,9 +899,7 @@ function ghRunner() { describe("runComment", () => { it("requires --pr or --issue", async () => { const { run } = ghRunner(); - await expect(runComment(ctxWith(listClient([])), [], false, run)).rejects.toThrow( - UsageError, - ); + await expect(runComment(ctxWith(listClient([])), [], false, run)).rejects.toThrow(UsageError); }); it("creates a comment listing the PR's attachments", async () => { @@ -885,12 +907,7 @@ describe("runComment", () => { const client = listClient([ { key: "gh/o/r/pull/5/after.png", url: "https://x.test/gh/o/r/pull/5/after.png" }, ]); - const code = await runComment( - ctxWith(client), - ["--pr", "5", "--repo", "o/r"], - false, - run, - ); + const code = await runComment(ctxWith(client), ["--pr", "5", "--repo", "o/r"], false, run); expect(code).toBe(0); const create = calls.find((c) => c.args.includes("repos/o/r/issues/5/comments")); expect(create).toBeDefined(); @@ -999,36 +1016,36 @@ export async function runComment( Wire `put --comment` in `runPut`. In the validation block added in Task 5, extend to: ```ts - const ghTarget = ghTargetFromFlags(parsed.flags, run); - const wantComment = parsed.flags.has("--comment"); - if (wantComment && typeof parsed.flags.get("--comment") === "string") { - throw new UsageError("--comment takes no value — place it after the file argument"); - } - if (wantComment && !ghTarget) throw new UsageError("--comment requires --pr or --issue"); - if (ghTarget) { - if (keyHint) throw new UsageError("--key cannot be combined with --pr/--issue"); - if (flagString(parsed.flags, "--ref")) { - throw new UsageError("--ref cannot be combined with --pr/--issue"); - } +const ghTarget = ghTargetFromFlags(parsed.flags, run); +const wantComment = parsed.flags.has("--comment"); +if (wantComment && typeof parsed.flags.get("--comment") === "string") { + throw new UsageError("--comment takes no value — place it after the file argument"); +} +if (wantComment && !ghTarget) throw new UsageError("--comment requires --pr or --issue"); +if (ghTarget) { + if (keyHint) throw new UsageError("--key cannot be combined with --pr/--issue"); + if (flagString(parsed.flags, "--ref")) { + throw new UsageError("--ref cannot be combined with --pr/--issue"); } +} ``` At the end of `runPut`, just before `return 0;`, add: ```ts - if (wantComment && ghTarget) { - try { - const sync = await syncAttachmentsComment(ctx, ghTarget, run); - if (!ctx.quiet && format === "human") { - process.stderr.write(`>> attachments comment ${sync.action}\n`); - } - } catch (err) { - // Upload already succeeded; the comment is best-effort by design. - process.stderr.write( - `warning: upload succeeded but the GitHub comment failed (is gh installed and authenticated?): ${err instanceof Error ? err.message : String(err)}\n`, - ); +if (wantComment && ghTarget) { + try { + const sync = await syncAttachmentsComment(ctx, ghTarget, run); + if (!ctx.quiet && format === "human") { + process.stderr.write(`>> attachments comment ${sync.action}\n`); } + } catch (err) { + // Upload already succeeded; the comment is best-effort by design. + process.stderr.write( + `warning: upload succeeded but the GitHub comment failed (is gh installed and authenticated?): ${err instanceof Error ? err.message : String(err)}\n`, + ); } +} ``` Wire the command in `packages/uploads/src/cli.ts`: @@ -1071,10 +1088,12 @@ git commit -m "feat(uploads): comment command and put --comment" ### Task 7: `list --pr` / `list --issue` **Files:** + - Modify: `packages/uploads/src/commands.ts` (`runList`, `LIST_HELP`) - Test: `packages/uploads/test/commands-list.test.ts` **Interfaces:** + - Consumes: `ghTargetFromFlags` (Task 5), `ghKeyPrefix` (Task 2). - Produces: `runList(ctx, args, help?, run?: CommandRunner)` — 4th parameter added, defaulting to `execRunner`; `cli.ts` keeps calling with 3 args. @@ -1168,12 +1187,12 @@ export async function runList( Replace the line `const prefix = flagString(parsed.flags, "--prefix");` with: ```ts - let prefix = flagString(parsed.flags, "--prefix"); - const ghTarget = ghTargetFromFlags(parsed.flags, run); - if (ghTarget) { - if (prefix) throw new UsageError("--prefix cannot be combined with --pr/--issue"); - prefix = ghKeyPrefix(ghTarget); - } +let prefix = flagString(parsed.flags, "--prefix"); +const ghTarget = ghTargetFromFlags(parsed.flags, run); +if (ghTarget) { + if (prefix) throw new UsageError("--prefix cannot be combined with --pr/--issue"); + prefix = ghKeyPrefix(ghTarget); +} ``` Update `LIST_HELP` to: @@ -1207,11 +1226,13 @@ git commit -m "feat(uploads): list --pr/--issue" ### Task 8: Public exports and docs sweep **Files:** + - Modify: `packages/uploads/src/index.ts` - Modify: `AGENTS.md` (repo root — one line) - Modify: `packages/uploads/README.md` **only if it exists** (check with `ls packages/uploads/README.md`); skip silently if not. **Interfaces:** + - Consumes: everything from Tasks 2–4. - Produces: library consumers can import the GitHub helpers from `@buildinternet/uploads`. @@ -1270,9 +1291,11 @@ git commit -m "feat(uploads): export GitHub helpers; document PR attachments" **Different repo:** `/Users/zachdunn/Code/buildinternet-skills`. Check `git -C /Users/zachdunn/Code/buildinternet-skills status` first; if the working tree is dirty, create a branch before committing and mention it in the task report. This task is documentation-only — do not modify the skill's `scripts/`. **Files:** + - Modify: `/Users/zachdunn/Code/buildinternet-skills/skills/github-screenshots/SKILL.md` **Interfaces:** + - Consumes: the CLI behavior shipped in Tasks 5–6 (`uploads put --pr/--issue [--comment]`). - Produces: the skill instructs agents to prefer the uploads.sh CLI when configured, keeping direct-R2 as fallback. @@ -1332,7 +1355,7 @@ Expected: key `gh/buildinternet/uploads/pull/<NUM>/<some.png>` and a URL that se - [ ] **Step 2: Replace the file, confirm the URL content changes** -Upload a *different* image with the same filename and the same `--pr`. Expected: same key, same URL; `curl` now returns the new bytes. This is the core stable-URL guarantee — if the key gained a hash suffix, the implementation is wrong. +Upload a _different_ image with the same filename and the same `--pr`. Expected: same key, same URL; `curl` now returns the new bytes. This is the core stable-URL guarantee — if the key gained a hash suffix, the implementation is wrong. - [ ] **Step 3: Comment idempotence** diff --git a/docs/superpowers/plans/2026-07-07-shared-bucket-workspace-prefixes.md b/docs/superpowers/plans/2026-07-07-shared-bucket-workspace-prefixes.md index 0d087c6..513eaf5 100644 --- a/docs/superpowers/plans/2026-07-07-shared-bucket-workspace-prefixes.md +++ b/docs/superpowers/plans/2026-07-07-shared-bucket-workspace-prefixes.md @@ -28,17 +28,20 @@ ### Task 1: Prefix plumbing in `@uploads/storage` (config, validation, publicUrl) + vitest setup **Files:** + - Modify: `packages/storage/src/index.ts` - Modify: `packages/storage/package.json` - Create: `packages/storage/test/index.test.ts` **Interfaces:** + - Consumes: nothing from other tasks. - Produces: `StorageConfig.prefix?: string` (optional, must match `/^([a-z0-9][a-z0-9._-]*\/)+$/`); `createStorage(config)` returns a `Files` instance whose operations are confined under `config.prefix`; `publicUrl(config, key)` returns `<base>/<prefix><key>` (segments URI-encoded). Task 2 tests behavior; Task 3 passes `prefix` from the workspace record. - [ ] **Step 1: Add vitest to the storage package** Run: + ```bash pnpm --filter @uploads/storage add -D vitest ``` @@ -202,10 +205,12 @@ git commit -m "feat(storage): confine operations under an optional key prefix" ### Task 2: Behavioral prefix-confinement tests (fake R2 binding) **Files:** + - Create: `packages/storage/test/fake-r2.ts` - Create: `packages/storage/test/prefix-confinement.test.ts` **Interfaces:** + - Consumes: `createStorage` with `prefix` from Task 1. - Produces: `FakeR2Bucket` (in-memory stand-in for a Workers `R2Bucket` binding, exposing its raw `store: Map<string, ...>` for assertions). Test-only; nothing else depends on it. @@ -435,10 +440,12 @@ git commit -m "test(storage): prove prefix confinement across shared-bucket tena ### Task 3: Thread `prefix` through the API (workspace record → storage config) **Files:** + - Modify: `apps/api/src/workspace.ts` (the `WorkspaceRecord` interface, around line 9-24) - Modify: `apps/api/src/storage.ts` (the `storageConfig` return object, around line 13-21) **Interfaces:** + - Consumes: `StorageConfig.prefix` from Task 1. - Produces: `WorkspaceRecord.prefix?: string` — read by `storageConfig()`; Task 4 writes it when registering shared-bucket workspaces. Route code is NOT modified. @@ -476,9 +483,11 @@ git commit -m "feat(api): carry workspace key prefix into storage config" ### Task 4: Flip `add-workspace.mjs` to shared-bucket defaults **Files:** + - Modify: `apps/api/scripts/add-workspace.mjs` **Interfaces:** + - Consumes: `WorkspaceRecord.prefix` shape from Task 3 (the script writes raw JSON to KV; it does not import the type). - Produces: registration behavior — no `--bucket` ⇒ shared-bucket record (`bucket: "uploads-default"`, `binding: "UPLOADS_DEFAULT"`, `prefix: "<name>/"`, `publicBaseUrl: "https://storage.uploads.sh"`); `--bucket <name>` ⇒ BYO record identical to today's output. @@ -526,11 +535,13 @@ const SHARED = { publicBaseUrl: "https://storage.uploads.sh", }; -const tokens = [{ - hash: crypto.createHash("sha256").update(token).digest("hex"), - label: "initial", - createdAt: new Date().toISOString(), -}]; +const tokens = [ + { + hash: crypto.createHash("sha256").update(token).digest("hex"), + label: "initial", + createdAt: new Date().toISOString(), + }, +]; const record = opts.bucket ? { @@ -564,24 +575,29 @@ Object.keys(record).forEach((k) => record[k] === undefined && delete record[k]); - [ ] **Step 3: Verify shared mode against local KV** Run (from `apps/api`): + ```bash node scripts/add-workspace.mjs planck --local pnpm exec wrangler kv key get ws:planck --binding REGISTRY --local ``` + Expected: the second command prints a JSON record with `"bucket": "uploads-default"`, `"binding": "UPLOADS_DEFAULT"`, `"prefix": "planck/"`, `"publicBaseUrl": "https://storage.uploads.sh"`, a `tokens` array, and NO `accountId`/`accessKeyId`/`secretAccessKey` keys. - [ ] **Step 4: Verify BYO mode against local KV** Run (from `apps/api`): + ```bash node scripts/add-workspace.mjs byocheck --bucket custom-bucket --binding UPLOADS --local pnpm exec wrangler kv key get ws:byocheck --binding REGISTRY --local ``` + Expected: JSON with `"bucket": "custom-bucket"`, `"binding": "UPLOADS"`, and NO `prefix` key. (Run without `--env-file`, so no env credentials leak in.) - [ ] **Step 5: Clean up the local test records** Run (from `apps/api`): + ```bash pnpm exec wrangler kv key delete ws:planck --binding REGISTRY --local pnpm exec wrangler kv key delete ws:byocheck --binding REGISTRY --local @@ -599,10 +615,12 @@ git commit -m "feat(api): default new workspaces to shared-bucket prefix mode" ### Task 5: Documentation **Files:** + - Modify: `AGENTS.md` (the "Workspaces (multi-tenant model)" section and the `workspace:add` line in "Commands") - Modify: `README.md` only if it mentions `--bucket` being required or describes the one-bucket-per-workspace model (check with `grep -n "bucket" README.md`); otherwise leave it. **Interfaces:** + - Consumes: final behavior from Tasks 1-4. - Produces: docs matching reality. Nothing depends on this task. diff --git a/docs/superpowers/specs/2026-07-07-admin-token-minting-design.md b/docs/superpowers/specs/2026-07-07-admin-token-minting-design.md index a9edfbc..fe6aeb7 100644 --- a/docs/superpowers/specs/2026-07-07-admin-token-minting-design.md +++ b/docs/superpowers/specs/2026-07-07-admin-token-minting-design.md @@ -42,9 +42,7 @@ export interface WorkspaceRecord { Auth builds its candidate hash list as: ```ts -const hashes = - record.tokens?.map((t) => t.hash) ?? - (record.tokenHash ? [record.tokenHash] : []); +const hashes = record.tokens?.map((t) => t.hash) ?? (record.tokenHash ? [record.tokenHash] : []); ``` and does a timing-safe compare of the presented token's hash against each @@ -80,7 +78,7 @@ Mirrors `workspaceAuth`: Write `tokens: [{ hash, label: "initial", createdAt }]` instead of `tokenHash`. Still prints the token once. (Existing overwrite semantics for that script are -unchanged — it is workspace *creation*.) +unchanged — it is workspace _creation_.) ### 5. Docs & env templates diff --git a/docs/superpowers/specs/2026-07-07-github-companion-design.md b/docs/superpowers/specs/2026-07-07-github-companion-design.md index ef9658d..477a28d 100644 --- a/docs/superpowers/specs/2026-07-07-github-companion-design.md +++ b/docs/superpowers/specs/2026-07-07-github-companion-design.md @@ -17,11 +17,11 @@ the CLI directly get the same experience for free. ## Decisions made -| Question | Decision | -| --- | --- | -| First user | Agents (skills / Claude Code), humans second | -| Sync model | Stable URLs + optional comment posted via the agent's local `gh` auth. No server-side GitHub integration. | -| Key policy | Client-side convention only. API unchanged — `GET /v1/:ws/files?prefix=` already covers listing (`apps/api/src/routes/files.ts`). | +| Question | Decision | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| First user | Agents (skills / Claude Code), humans second | +| Sync model | Stable URLs + optional comment posted via the agent's local `gh` auth. No server-side GitHub integration. | +| Key policy | Client-side convention only. API unchanged — `GET /v1/:ws/files?prefix=` already covers listing (`apps/api/src/routes/files.ts`). | | Agent surface | Extend the existing CLI (`@buildinternet/uploads`) and update the `github-screenshots` skill. MCP server stays a later roadmap item. | ## Key convention (client-built) @@ -32,7 +32,7 @@ gh/<org>/<repo>/issues/<num>/<filename> ``` - `org`/`repo` inferred from the git remote (prefer `gh repo view - --json nameWithOwner`, fall back to parsing `origin`), overridable with +--json nameWithOwner`, fall back to parsing `origin`), overridable with `--repo org/name`. - Same filename → same key → overwrite → stable URL. This is the auto-update mechanism; it requires nothing else. diff --git a/package.json b/package.json index f031971..880d28b 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,22 @@ "upload:api": "pnpm --filter @uploads/api exec wrangler versions upload", "upload:web": "pnpm --filter @uploads/web run build && pnpm --filter @uploads/web exec wrangler versions upload", "types": "pnpm --filter @uploads/api types", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt", + "format:check": "oxfmt --check", + "check": "pnpm run lint && pnpm run format:check", "typecheck": "pnpm --filter @buildinternet/uploads run build && pnpm -r typecheck", "workspace:add": "pnpm --filter @uploads/api run workspace:add", "preuploads": "pnpm --filter @buildinternet/uploads run build", "uploads": "pnpm exec uploads" }, "devDependencies": { - "@buildinternet/uploads": "workspace:*" + "@buildinternet/uploads": "workspace:*", + "oxfmt": "^0.57.0", + "oxlint": "^1.72.0", + "oxlint-tsgolint": "^0.24.0", + "typescript": "^6.0.3" }, "engines": { "node": ">=22", diff --git a/packages/uploads/bin/uploads.js b/packages/uploads/bin/uploads.js index a8c684a..3639ce7 100755 --- a/packages/uploads/bin/uploads.js +++ b/packages/uploads/bin/uploads.js @@ -6,4 +6,4 @@ runCli(process.argv) .catch((err) => { console.error(err); process.exit(1); - }); \ No newline at end of file + }); diff --git a/packages/uploads/package.json b/packages/uploads/package.json index bbb576f..136ddbc 100644 --- a/packages/uploads/package.json +++ b/packages/uploads/package.json @@ -56,4 +56,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/uploads/src/agent.ts b/packages/uploads/src/agent.ts index 9113af4..42a86b0 100644 --- a/packages/uploads/src/agent.ts +++ b/packages/uploads/src/agent.ts @@ -29,4 +29,4 @@ export function createUploadsWorkerFileTools( }, ...rest, }); -} \ No newline at end of file +} diff --git a/packages/uploads/src/cli-args.ts b/packages/uploads/src/cli-args.ts index b4dca3c..379048a 100644 --- a/packages/uploads/src/cli-args.ts +++ b/packages/uploads/src/cli-args.ts @@ -169,4 +169,4 @@ export function flagInt( throw new UsageError(`invalid ${label}: must be a positive integer (got ${raw})`); } return n; -} \ No newline at end of file +} diff --git a/packages/uploads/src/cli.ts b/packages/uploads/src/cli.ts index a3bcc60..076cc8a 100644 --- a/packages/uploads/src/cli.ts +++ b/packages/uploads/src/cli.ts @@ -138,11 +138,7 @@ export async function runCli(argv: string[]): Promise<number> { switch (parsed.command) { case "health": - return runHealth( - { apiUrl: resolveApiUrl(parsed.globals), json }, - cmdArgs, - showHelp, - ); + return runHealth({ apiUrl: resolveApiUrl(parsed.globals), json }, cmdArgs, showHelp); case "config": return runConfig(cmdArgs, { json, envFile: parsed.globals.envFile }, showHelp); case "setup": @@ -172,8 +168,8 @@ export async function runCli(argv: string[]): Promise<number> { } } catch (err) { errorOut(err, argv.includes("--json")); - if (err instanceof UsageError && !argv.includes("--json")) process.stderr.write(`\n${ROOT_HELP}`); + if (err instanceof UsageError && !argv.includes("--json")) + process.stderr.write(`\n${ROOT_HELP}`); return exitCode(err); } - return 1; -} \ No newline at end of file +} diff --git a/packages/uploads/src/client.ts b/packages/uploads/src/client.ts index f0f9d3c..b0e0977 100644 --- a/packages/uploads/src/client.ts +++ b/packages/uploads/src/client.ts @@ -159,10 +159,7 @@ export function createUploadsClient(config: UploadsClientConfig) { if (opts.limit != null) params.set("limit", String(opts.limit)); if (opts.cursor) params.set("cursor", opts.cursor); const qs = params.toString(); - return request<ListResult>( - "GET", - `${filesBase(config)}${qs ? `?${qs}` : ""}`, - ); + return request<ListResult>("GET", `${filesBase(config)}${qs ? `?${qs}` : ""}`); }, async delete(key: string): Promise<DeleteResult> { @@ -179,4 +176,4 @@ export function createUploadsClient(config: UploadsClientConfig) { }; } -export type UploadsClient = ReturnType<typeof createUploadsClient>; \ No newline at end of file +export type UploadsClient = ReturnType<typeof createUploadsClient>; diff --git a/packages/uploads/src/commands.ts b/packages/uploads/src/commands.ts index 5893f61..a97432f 100644 --- a/packages/uploads/src/commands.ts +++ b/packages/uploads/src/commands.ts @@ -17,8 +17,19 @@ import { } from "./config.js"; import { buildMarkdown } from "./embed.js"; import { UploadsError } from "./errors.js"; -import { ghAttachmentKey, ghKeyPrefix, attachmentsCommentBody, type GhTarget, type AttachmentItem } from "./github.js"; -import { resolveRepo, execRunner, upsertAttachmentsComment, type CommandRunner } from "./github-gh.js"; +import { + ghAttachmentKey, + ghKeyPrefix, + attachmentsCommentBody, + type GhTarget, + type AttachmentItem, +} from "./github.js"; +import { + resolveRepo, + execRunner, + upsertAttachmentsComment, + type CommandRunner, +} from "./github-gh.js"; export interface CliContext { config: ResolvedConfig; @@ -66,10 +77,7 @@ Examples: `; /** Reads --pr/--issue (+ --repo) into a GhTarget; undefined when neither flag is present. */ -function ghTargetFromFlags( - flags: CommandFlags["flags"], - run: CommandRunner, -): GhTarget | undefined { +function ghTargetFromFlags(flags: CommandFlags["flags"], run: CommandRunner): GhTarget | undefined { const pr = flagInt(flags, "--pr", "--pr"); const issue = flagInt(flags, "--issue", "--issue"); if (pr === undefined && issue === undefined) return undefined; @@ -105,7 +113,12 @@ async function syncAttachmentsComment( return { action: created ? "created" : "updated", count: items.length }; } -export async function runPut(ctx: CliContext, args: string[], help = false, run: CommandRunner = execRunner): Promise<number> { +export async function runPut( + ctx: CliContext, + args: string[], + help = false, + run: CommandRunner = execRunner, +): Promise<number> { if (help) { process.stderr.write(PUT_HELP); return 0; @@ -139,10 +152,9 @@ export async function runPut(ctx: CliContext, args: string[], help = false, run: } } const bytes = - fileArg === "-" - ? new Uint8Array(readFileSync(0)) - : new Uint8Array(readFileSync(fileArg)); - const filename = fileArg === "-" ? (keyHint ? basename(keyHint) : "stdin.bin") : basename(fileArg); + fileArg === "-" ? new Uint8Array(readFileSync(0)) : new Uint8Array(readFileSync(fileArg)); + const filename = + fileArg === "-" ? (keyHint ? basename(keyHint) : "stdin.bin") : basename(fileArg); const format = ctx.json ? "json" @@ -260,14 +272,17 @@ export async function runList( next = page.cursor; } while (next); if (ctx.json) await writeJson({ items, cursor: null }); - else for (const item of items) await writeStdout(`${item.key}${item.url ? ` ${item.url}` : ""}\n`); + else + for (const item of items) + await writeStdout(`${item.key}${item.url ? ` ${item.url}` : ""}\n`); return 0; } const result = await ctx.client.list({ prefix, limit, cursor }); if (ctx.json) await writeJson(result); else { - for (const item of result.items) await writeStdout(`${item.key}${item.url ? ` ${item.url}` : ""}\n`); + for (const item of result.items) + await writeStdout(`${item.key}${item.url ? ` ${item.url}` : ""}\n`); if (result.cursor) process.stderr.write(`cursor: ${result.cursor}\n`); } return 0; @@ -406,7 +421,9 @@ export async function runDoctor(ctx: CliContext, args: string[], help = false): } catch (err) { authError = err instanceof UploadsError ? err.message : String(err); if (err instanceof UploadsError && err.code === "UNAUTHORIZED") { - hints.push("if this token works on api.uploads.sh, set UPLOADS_API_URL=https://api.uploads.sh"); + hints.push( + "if this token works on api.uploads.sh, set UPLOADS_API_URL=https://api.uploads.sh", + ); } } @@ -442,4 +459,4 @@ export async function runDoctor(ctx: CliContext, args: string[], help = false): for (const h of hints) if (h !== mismatch) lines.push(`hint: ${h}`); await writeStdout(lines.join("\n") + "\n"); return report.ok ? 0 : 1; -} \ No newline at end of file +} diff --git a/packages/uploads/src/commands/config.ts b/packages/uploads/src/commands/config.ts index 76b6c89..2422d13 100644 --- a/packages/uploads/src/commands/config.ts +++ b/packages/uploads/src/commands/config.ts @@ -11,12 +11,7 @@ import { writeConfigKeys, type UploadsConfigKey, } from "../config.js"; -import { - flagBool, - flagString, - parseCommandArgs, - UsageError, -} from "../cli-args.js"; +import { flagBool, flagString, parseCommandArgs, UsageError } from "../cli-args.js"; const CONFIG_HELP = `uploads config — manage shared buildinternet config @@ -198,9 +193,7 @@ Examples: process.stdout.write(`${verb} ${result.path}\n`); if (result.updated.length) process.stdout.write(`keys: ${result.updated.join(", ")}\n`); if (!token) { - process.stderr.write( - "hint: add a token with uploads config set UPLOADS_TOKEN <token>\n", - ); + process.stderr.write("hint: add a token with uploads config set UPLOADS_TOKEN <token>\n"); } } return 0; @@ -244,4 +237,4 @@ Examples: if (opts.json) writeJson(payload); else process.stdout.write(`set ${key} in ${result.path}\n`); return 0; -} \ No newline at end of file +} diff --git a/packages/uploads/src/commands/setup.ts b/packages/uploads/src/commands/setup.ts index c046a2c..9a5946a 100644 --- a/packages/uploads/src/commands/setup.ts +++ b/packages/uploads/src/commands/setup.ts @@ -13,13 +13,7 @@ import { workspaceFromToken, type UploadsConfigValues, } from "../config.js"; -import { - flagBool, - flagInt, - flagString, - parseCommandArgs, - UsageError, -} from "../cli-args.js"; +import { flagBool, flagInt, flagString, parseCommandArgs, UsageError } from "../cli-args.js"; import { UploadsError } from "../errors.js"; const SETUP_HELP = `uploads setup — guided CLI configuration @@ -104,7 +98,9 @@ function formatWizard(status: SetupStatus): string { if (!status.token) { lines.push("Step 1 — Mint a token"); - lines.push(" You need ADMIN_TOKEN for the API (ask your uploads.sh admin, or set it locally in apps/api/.dev.vars)."); + lines.push( + " You need ADMIN_TOKEN for the API (ask your uploads.sh admin, or set it locally in apps/api/.dev.vars).", + ); lines.push(" Mint:"); lines.push(` ${mintTokenCommand(status.apiUrl, status.workspace)}`); lines.push(" Save:"); @@ -220,9 +216,7 @@ export async function runSetup( const result = writeConfigKeys(path, keys, { force }); const savedApiUrl = apiUrl ?? resolveApiUrl({ envFile: opts.envFile }); const savedWorkspace = - workspace ?? - (token ? workspaceFromToken(token) : undefined) ?? - DEFAULT_WORKSPACE; + workspace ?? (token ? workspaceFromToken(token) : undefined) ?? DEFAULT_WORKSPACE; const shouldCheck = !noCheck && (checkExplicit || Boolean(token)); let doctorOk: boolean | undefined; @@ -277,4 +271,4 @@ export async function runSetup( } return doctorOk === false ? 1 : 0; -} \ No newline at end of file +} diff --git a/packages/uploads/src/config-file.ts b/packages/uploads/src/config-file.ts index 610095d..5e83ada 100644 --- a/packages/uploads/src/config-file.ts +++ b/packages/uploads/src/config-file.ts @@ -61,10 +61,12 @@ function parsePutDefaultsFromRaw(raw: UploadsConfigValues): PutDefaults { function parsePutDefaultsFromEnv(): PutDefaults { const raw: UploadsConfigValues = {}; - if (process.env.UPLOADS_DEFAULT_PREFIX) raw.UPLOADS_DEFAULT_PREFIX = process.env.UPLOADS_DEFAULT_PREFIX; + if (process.env.UPLOADS_DEFAULT_PREFIX) + raw.UPLOADS_DEFAULT_PREFIX = process.env.UPLOADS_DEFAULT_PREFIX; if (process.env.UPLOADS_DEFAULT_REPO) raw.UPLOADS_DEFAULT_REPO = process.env.UPLOADS_DEFAULT_REPO; if (process.env.UPLOADS_DEFAULT_REF) raw.UPLOADS_DEFAULT_REF = process.env.UPLOADS_DEFAULT_REF; - if (process.env.UPLOADS_DEFAULT_WIDTH) raw.UPLOADS_DEFAULT_WIDTH = process.env.UPLOADS_DEFAULT_WIDTH; + if (process.env.UPLOADS_DEFAULT_WIDTH) + raw.UPLOADS_DEFAULT_WIDTH = process.env.UPLOADS_DEFAULT_WIDTH; if (process.env.UPLOADS_NO_GIT) raw.UPLOADS_NO_GIT = process.env.UPLOADS_NO_GIT; return parsePutDefaultsFromRaw(raw); } @@ -136,7 +138,9 @@ export function mergePutDefaults(...layers: PutDefaults[]): PutDefaults { export function resolvePutDefaults(flags?: { envFile?: string }): PutDefaults { const fromEnv = parsePutDefaultsFromEnv(); const fromEnvFile = flags?.envFile ? parsePutDefaultsFromRaw(loadConfigFile(flags.envFile)) : {}; - const fromUser = flags?.envFile ? {} : parsePutDefaultsFromRaw(loadConfigFile(resolveConfigPath(flags))); + const fromUser = flags?.envFile + ? {} + : parsePutDefaultsFromRaw(loadConfigFile(resolveConfigPath(flags))); return mergePutDefaults(fromUser, fromEnvFile, fromEnv); } @@ -210,4 +214,4 @@ export function configValuesFromClient( return out; } -export { PUT_DEFAULT_KEY_MAP }; \ No newline at end of file +export { PUT_DEFAULT_KEY_MAP }; diff --git a/packages/uploads/src/config.ts b/packages/uploads/src/config.ts index 8499602..8d8c0e4 100644 --- a/packages/uploads/src/config.ts +++ b/packages/uploads/src/config.ts @@ -89,13 +89,7 @@ function pickApiUrl(flags?: ConfigLayer & { apiUrl?: string; envFile?: string }) /** How the active workspace was chosen (for doctor hints). */ export type WorkspaceSource = "override" | "env" | "file" | "user-config" | "token" | "default"; -export type ConfigValueSource = - | "flag" - | "env" - | "env-file" - | "user-config" - | "token" - | "default"; +export type ConfigValueSource = "flag" | "env" | "env-file" | "user-config" | "token" | "default"; export interface ResolvedConfig extends UploadsClientConfig { workspaceSource: WorkspaceSource; @@ -149,8 +143,7 @@ export function resolveConfig( const fromUser = layerFromUserConfig(flags); const configPath = resolveConfigPath(flags); - const token = - flags?.token ?? process.env.UPLOADS_TOKEN ?? fromEnvFile.token ?? fromUser.token; + const token = flags?.token ?? process.env.UPLOADS_TOKEN ?? fromEnvFile.token ?? fromUser.token; const apiUrl = pickApiUrl(flags); let workspace: string; @@ -205,4 +198,4 @@ export function workspaceMismatch(config: ResolvedConfig): string | undefined { if (!fromToken || fromToken === config.workspace) return undefined; if (config.workspaceSource === "token" || config.workspaceSource === "default") return undefined; return `workspace override "${config.workspace}" (token encodes "${fromToken}") — ensure the token is valid for the override workspace`; -} \ No newline at end of file +} diff --git a/packages/uploads/src/embed.ts b/packages/uploads/src/embed.ts index 10a27b5..7e66aa6 100644 --- a/packages/uploads/src/embed.ts +++ b/packages/uploads/src/embed.ts @@ -1,7 +1,9 @@ /** GitHub-embed helpers (content type + markdown). */ export function inferContentType(filename: string): string { - const ext = filename.includes(".") ? filename.slice(filename.lastIndexOf(".") + 1).toLowerCase() : ""; + const ext = filename.includes(".") + ? filename.slice(filename.lastIndexOf(".") + 1).toLowerCase() + : ""; switch (ext) { case "png": return "image/png"; @@ -27,4 +29,4 @@ export function buildMarkdown(url: string, opts: { alt: string; width?: number } return `<img width="${opts.width}" alt="${alt}" src="${url}">`; } return `![${opts.alt}](${url})`; -} \ No newline at end of file +} diff --git a/packages/uploads/src/errors.ts b/packages/uploads/src/errors.ts index 3b2888a..3449ea1 100644 --- a/packages/uploads/src/errors.ts +++ b/packages/uploads/src/errors.ts @@ -18,4 +18,4 @@ export class UploadsError extends Error { this.code = code; this.status = status; } -} \ No newline at end of file +} diff --git a/packages/uploads/src/github-gh.ts b/packages/uploads/src/github-gh.ts index 1165084..d31ac47 100644 --- a/packages/uploads/src/github-gh.ts +++ b/packages/uploads/src/github-gh.ts @@ -17,10 +17,7 @@ export const execRunner: CommandRunner = (cmd, args, input) => * Resolve "owner/name". Order: explicit --repo (validated) → `gh repo view` * (fork-aware) → parse the origin remote → UsageError. */ -export function resolveRepo( - explicit: string | undefined, - run: CommandRunner = execRunner, -): string { +export function resolveRepo(explicit: string | undefined, run: CommandRunner = execRunner): string { if (explicit !== undefined) { if (!isValidRepo(explicit)) { throw new UsageError(`--repo must be owner/name (got: ${explicit})`); @@ -62,9 +59,7 @@ interface GhComment { function findManagedComment(target: GhTarget, run: CommandRunner): GhComment | undefined { const raw = run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments?per_page=100`]); const comments = JSON.parse(raw) as GhComment[]; - return comments.find( - (c) => typeof c.body === "string" && c.body.includes(ATTACHMENTS_MARKER), - ); + return comments.find((c) => typeof c.body === "string" && c.body.includes(ATTACHMENTS_MARKER)); } /** @@ -93,10 +88,6 @@ export function upsertAttachmentsComment( ); return { created: false }; } - run( - "gh", - ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], - body, - ); + run("gh", ["api", `repos/${target.repo}/issues/${target.num}/comments`, "-F", "body=@-"], body); return { created: true }; } diff --git a/packages/uploads/src/github.ts b/packages/uploads/src/github.ts index fccbccd..840ad7f 100644 --- a/packages/uploads/src/github.ts +++ b/packages/uploads/src/github.ts @@ -46,7 +46,7 @@ export interface AttachmentItem { } export function attachmentsCommentBody(items: AttachmentItem[]): string { - const sorted = [...items].sort((a, b) => a.key.localeCompare(b.key)); + const sorted = items.toSorted((a, b) => a.key.localeCompare(b.key)); const lines: string[] = [ATTACHMENTS_MARKER, "### 📎 Attachments", ""]; for (const item of sorted) { const name = item.key.slice(item.key.lastIndexOf("/") + 1); diff --git a/packages/uploads/src/index.ts b/packages/uploads/src/index.ts index 37c85e5..00c3a75 100644 --- a/packages/uploads/src/index.ts +++ b/packages/uploads/src/index.ts @@ -1,10 +1,5 @@ export { inferContentType, buildMarkdown } from "./embed.js"; -export { - sanitizeKeySegment, - sha256Short, - deriveRepoFromGit, - buildScreenshotKey, -} from "./keys.js"; +export { sanitizeKeySegment, sha256Short, deriveRepoFromGit, buildScreenshotKey } from "./keys.js"; export { DEFAULT_API_URL, DEFAULT_WORKSPACE, @@ -62,4 +57,4 @@ export { resolveRepo, upsertAttachmentsComment, type CommandRunner, -} from "./github-gh.js"; \ No newline at end of file +} from "./github-gh.js"; diff --git a/packages/uploads/src/keys.ts b/packages/uploads/src/keys.ts index 3a8260a..62d394c 100644 --- a/packages/uploads/src/keys.ts +++ b/packages/uploads/src/keys.ts @@ -42,4 +42,4 @@ export async function buildScreenshotKey(opts: { const prefix = sanitizeKeySegment(opts.prefix ?? "screenshots"); return `${prefix}/${repo}/${ref}/${sanitizeKeySegment(stem)}-${short}${ext ? `.${sanitizeKeySegment(ext)}` : ""}`; -} \ No newline at end of file +} diff --git a/packages/uploads/test/commands-comment.test.ts b/packages/uploads/test/commands-comment.test.ts index a715f2a..e3f904e 100644 --- a/packages/uploads/test/commands-comment.test.ts +++ b/packages/uploads/test/commands-comment.test.ts @@ -42,9 +42,7 @@ function ghRunner() { describe("runComment", () => { it("requires --pr or --issue", async () => { const { run } = ghRunner(); - await expect(runComment(ctxWith(listClient([])), [], false, run)).rejects.toThrow( - UsageError, - ); + await expect(runComment(ctxWith(listClient([])), [], false, run)).rejects.toThrow(UsageError); }); it("creates a comment listing the PR's attachments", async () => { @@ -52,12 +50,7 @@ describe("runComment", () => { const client = listClient([ { key: "gh/o/r/pull/5/after.png", url: "https://x.test/gh/o/r/pull/5/after.png" }, ]); - const code = await runComment( - ctxWith(client), - ["--pr", "5", "--repo", "o/r"], - false, - run, - ); + const code = await runComment(ctxWith(client), ["--pr", "5", "--repo", "o/r"], false, run); expect(code).toBe(0); const create = calls.find((c) => c.args.includes("repos/o/r/issues/5/comments")); expect(create).toBeDefined(); diff --git a/packages/uploads/test/commands-put.test.ts b/packages/uploads/test/commands-put.test.ts index 23521d3..58d3783 100644 --- a/packages/uploads/test/commands-put.test.ts +++ b/packages/uploads/test/commands-put.test.ts @@ -82,21 +82,36 @@ describe("runPut --pr/--issue", () => { it("rejects --pr with --issue", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--issue", "2", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--issue", "2", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); it("rejects --pr with --key", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--key", "x/y.png", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--key", "x/y.png", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); it("rejects --pr with --ref", async () => { const { client } = fakeClient(); await expect( - runPut(ctxWith(client), [tmpFile(), "--pr", "1", "--ref", "abc", "--repo", "o/r"], false, noRun), + runPut( + ctxWith(client), + [tmpFile(), "--pr", "1", "--ref", "abc", "--repo", "o/r"], + false, + noRun, + ), ).rejects.toThrow(UsageError); }); diff --git a/packages/uploads/test/github.test.ts b/packages/uploads/test/github.test.ts index a8ca1cb..30a7417 100644 --- a/packages/uploads/test/github.test.ts +++ b/packages/uploads/test/github.test.ts @@ -52,9 +52,7 @@ describe("ghKeyPrefix / ghAttachmentKey", () => { expect(ghKeyPrefix({ repo: "o/r", kind: "issues", num: 7 })).toBe("gh/o/r/issues/7/"); }); it("builds a stable key with no content hash", () => { - expect(ghAttachmentKey(pr, "after.png")).toBe( - "gh/buildinternet/uploads/pull/123/after.png", - ); + expect(ghAttachmentKey(pr, "after.png")).toBe("gh/buildinternet/uploads/pull/123/after.png"); }); it("sanitizes filename characters", () => { expect(ghAttachmentKey(pr, "my shot (1).png")).toBe( diff --git a/packages/uploads/tsconfig.json b/packages/uploads/tsconfig.json index 0640409..977643b 100644 --- a/packages/uploads/tsconfig.json +++ b/packages/uploads/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2022", - "lib": ["ES2022"], + "lib": ["ES2023"], "module": "ESNext", "moduleResolution": "bundler", "strict": true, @@ -12,4 +12,4 @@ "types": ["node"] }, "include": ["src"] -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aaf41b8..8f6cf2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,18 @@ importers: '@buildinternet/uploads': specifier: workspace:* version: link:packages/uploads + oxfmt: + specifier: ^0.57.0 + version: 0.57.0 + oxlint: + specifier: ^1.72.0 + version: 1.72.0(oxlint-tsgolint@0.24.0) + oxlint-tsgolint: + specifier: ^0.24.0 + version: 0.24.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 apps/api: dependencies: @@ -964,6 +976,280 @@ packages: '@oxc-project/types@0.138.0': resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} + '@oxfmt/binding-android-arm-eabi@0.57.0': + resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.57.0': + resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.57.0': + resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.57.0': + resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.57.0': + resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.57.0': + resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.57.0': + resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@0.24.0': + resolution: {integrity: sha512-C2uMmwK5Bc4ri4ysZ6sA8Rcu+A5zBQTp6ml2u0CLLbRZp4kMFPV3yWk8B5DK9Aw7y9bbjogIm75tUwGLFzlsYQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.24.0': + resolution: {integrity: sha512-Wgvt/1lRbDxmoNqWQKKcL+UIiqLmdJ+EWLpQa1qzoNVAfNB0PJpa82/8dH1twT/3rSs4zrP5TXPWl4juB71WuQ==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.24.0': + resolution: {integrity: sha512-PB1rxII7KV83+ASY4sSkXtqvpij6ME66+QCRL49uksi/ofs2Rf/UVboYr095n0Rkbl2wgvlsHGl6DHC361jQUQ==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.24.0': + resolution: {integrity: sha512-xcz3CxKmjTQLREtE/UShh+ruWmm9nAb7UM9zKcD65BStiuYgOakAKkPHl4YS5DztpVcDrE0+HqbOolTlRKYWmw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.24.0': + resolution: {integrity: sha512-A2i6ZGBec3i20S7RaxkgHc6r3HYtD5Mn7j/mb22NkTz14u0JuudvTu6JggAnbGMcv8+dBKQI//EasxSPJLD8pw==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.24.0': + resolution: {integrity: sha512-0ZbGd9qRB6zs82moekaKdEvncRANq49EAwfNX62JpTS46feXUhKAuoyVDvZMj6Rywejylrmmu79Wo6faYCo4Ew==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.72.0': + resolution: {integrity: sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.72.0': + resolution: {integrity: sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.72.0': + resolution: {integrity: sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.72.0': + resolution: {integrity: sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.72.0': + resolution: {integrity: sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + resolution: {integrity: sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + resolution: {integrity: sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + resolution: {integrity: sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.72.0': + resolution: {integrity: sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + resolution: {integrity: sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + resolution: {integrity: sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + resolution: {integrity: sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + resolution: {integrity: sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.72.0': + resolution: {integrity: sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.72.0': + resolution: {integrity: sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.72.0': + resolution: {integrity: sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + resolution: {integrity: sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + resolution: {integrity: sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.72.0': + resolution: {integrity: sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -2135,6 +2421,36 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + oxfmt@0.57.0: + resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint-tsgolint@0.24.0: + resolution: {integrity: sha512-giCk5sEvG02d5tzPmFMX3hem8ndzEEu1xvGYS5OwNfO2WGl6ZVxt5LjE0yiMDoz94INI7XkXwgFAQiydPvVHDw==} + hasBin: true + + oxlint@1.72.0: + resolution: {integrity: sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-limit@7.3.0: resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} engines: {node: '>=20'} @@ -2413,6 +2729,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -3655,6 +3975,138 @@ snapshots: '@oxc-project/types@0.138.0': {} + '@oxfmt/binding-android-arm-eabi@0.57.0': + optional: true + + '@oxfmt/binding-android-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.57.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.57.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.57.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.24.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.24.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.24.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.24.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.72.0': + optional: true + + '@oxlint/binding-android-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.72.0': + optional: true + + '@oxlint/binding-darwin-x64@1.72.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.72.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.72.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.72.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.72.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.72.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.72.0': + optional: true + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -4803,6 +5255,62 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + oxfmt@0.57.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 + + oxlint-tsgolint@0.24.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.24.0 + '@oxlint-tsgolint/darwin-x64': 0.24.0 + '@oxlint-tsgolint/linux-arm64': 0.24.0 + '@oxlint-tsgolint/linux-x64': 0.24.0 + '@oxlint-tsgolint/win32-arm64': 0.24.0 + '@oxlint-tsgolint/win32-x64': 0.24.0 + + oxlint@1.72.0(oxlint-tsgolint@0.24.0): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.72.0 + '@oxlint/binding-android-arm64': 1.72.0 + '@oxlint/binding-darwin-arm64': 1.72.0 + '@oxlint/binding-darwin-x64': 1.72.0 + '@oxlint/binding-freebsd-x64': 1.72.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.72.0 + '@oxlint/binding-linux-arm-musleabihf': 1.72.0 + '@oxlint/binding-linux-arm64-gnu': 1.72.0 + '@oxlint/binding-linux-arm64-musl': 1.72.0 + '@oxlint/binding-linux-ppc64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-musl': 1.72.0 + '@oxlint/binding-linux-s390x-gnu': 1.72.0 + '@oxlint/binding-linux-x64-gnu': 1.72.0 + '@oxlint/binding-linux-x64-musl': 1.72.0 + '@oxlint/binding-openharmony-arm64': 1.72.0 + '@oxlint/binding-win32-arm64-msvc': 1.72.0 + '@oxlint/binding-win32-ia32-msvc': 1.72.0 + '@oxlint/binding-win32-x64-msvc': 1.72.0 + oxlint-tsgolint: 0.24.0 + p-limit@7.3.0: dependencies: yocto-queue: 1.2.2 @@ -5176,6 +5684,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} toidentifier@1.0.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a3c88d3..ac3ba31 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,23 +6,23 @@ allowBuilds: sharp: true workerd: true minimumReleaseAgeExclude: - - '@aws-sdk/checksums@3.1000.13' - - '@aws-sdk/client-s3@3.1080.0' - - '@aws-sdk/core@3.974.28' - - '@aws-sdk/credential-provider-env@3.972.54' - - '@aws-sdk/credential-provider-http@3.972.56' - - '@aws-sdk/credential-provider-ini@3.972.61' - - '@aws-sdk/credential-provider-login@3.972.60' - - '@aws-sdk/credential-provider-node@3.972.63' - - '@aws-sdk/credential-provider-process@3.972.54' - - '@aws-sdk/credential-provider-sso@3.972.60' - - '@aws-sdk/credential-provider-web-identity@3.972.60' - - '@aws-sdk/middleware-sdk-s3@3.972.59' - - '@aws-sdk/nested-clients@3.997.28' - - '@aws-sdk/s3-presigned-post@3.1080.0' - - '@aws-sdk/s3-request-presigner@3.1080.0' - - '@aws-sdk/token-providers@3.1080.0' - - '@cloudflare/workers-types@5.20260706.1' + - "@aws-sdk/checksums@3.1000.13" + - "@aws-sdk/client-s3@3.1080.0" + - "@aws-sdk/core@3.974.28" + - "@aws-sdk/credential-provider-env@3.972.54" + - "@aws-sdk/credential-provider-http@3.972.56" + - "@aws-sdk/credential-provider-ini@3.972.61" + - "@aws-sdk/credential-provider-login@3.972.60" + - "@aws-sdk/credential-provider-node@3.972.63" + - "@aws-sdk/credential-provider-process@3.972.54" + - "@aws-sdk/credential-provider-sso@3.972.60" + - "@aws-sdk/credential-provider-web-identity@3.972.60" + - "@aws-sdk/middleware-sdk-s3@3.972.59" + - "@aws-sdk/nested-clients@3.997.28" + - "@aws-sdk/s3-presigned-post@3.1080.0" + - "@aws-sdk/s3-request-presigner@3.1080.0" + - "@aws-sdk/token-providers@3.1080.0" + - "@cloudflare/workers-types@5.20260706.1" - files-sdk@2.1.0 - hono@4.12.28 patchedDependencies: