From 0c2206922385ae3d71b6efbbef897df99b915b9a Mon Sep 17 00:00:00 2001 From: Konstantin Tarkus Date: Sat, 24 Jan 2026 10:10:47 +0100 Subject: [PATCH 1/2] feat: add prompt, root, emptyOutDir options and CI workflow - Add `prompt` bundle option to prepend text/file to bundles - Add `root` config option for custom project root directory - Add `emptyOutDir` option to clear output directory before bundling - Add `exclude` option to skip bundles during upload - Add GitHub Actions CI workflow for automated testing - Fix gitignore negation pattern support (e.g., `!build/keep.txt`) - Optimize gitignore handling by passing patterns to fast-glob - Add Product Hunt badge to documentation site - Update dependencies (ora, prettier, vue, rollup) --- .github/workflows/ci.yml | 22 +++ .vitepress/theme/components/HomeCTA.vue | 2 +- .../theme/components/HomeProductHuntBadge.vue | 56 ++++++ .vitepress/theme/index.ts | 8 +- README.md | 31 ++-- bun.lock | 170 +++++------------- docs/configuration.md | 80 +++++++-- docs/upload.md | 27 +++ package.json | 6 +- src/bundle.ts | 145 +++++++++++++-- src/cli.ts | 95 ++++++++-- src/config.ts | 54 +++++- tests/fixtures/negation-project/.gitignore | 2 + .../fixtures/negation-project/build/bundle.js | 1 + .../fixtures/negation-project/build/keep.txt | 1 + tests/fixtures/negation-project/src/index.ts | 1 + .../fixtures/sample-project/prompts/review.md | 3 + tests/unit/bundle.test.ts | 146 +++++++++++++++ tests/unit/config.test.ts | 108 +++++++++++ 19 files changed, 779 insertions(+), 179 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .vitepress/theme/components/HomeProductHuntBadge.vue create mode 100644 tests/fixtures/negation-project/.gitignore create mode 100644 tests/fixtures/negation-project/build/bundle.js create mode 100644 tests/fixtures/negation-project/build/keep.txt create mode 100644 tests/fixtures/negation-project/src/index.ts create mode 100644 tests/fixtures/sample-project/prompts/review.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c6ac830 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run check + - run: bun prettier --check . + - run: bun test tests/unit/ tests/e2e/ + - run: bun run build diff --git a/.vitepress/theme/components/HomeCTA.vue b/.vitepress/theme/components/HomeCTA.vue index 5f52b39..35504b4 100644 --- a/.vitepress/theme/components/HomeCTA.vue +++ b/.vitepress/theme/components/HomeCTA.vue @@ -31,7 +31,7 @@ function copyCommand() {
Read the Docs diff --git a/.vitepress/theme/components/HomeProductHuntBadge.vue b/.vitepress/theme/components/HomeProductHuntBadge.vue new file mode 100644 index 0000000..94bc62c --- /dev/null +++ b/.vitepress/theme/components/HomeProductHuntBadge.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index 5bcc2a3..47c4821 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -1,8 +1,9 @@ // https://vitepress.dev/guide/custom-theme -import { h } from "vue"; import type { Theme } from "vitepress"; import DefaultTheme from "vitepress/theme"; +import { h } from "vue"; import HomeCustomSections from "./components/HomeCustomSections.vue"; +import HomeProductHuntBadge from "./components/HomeProductHuntBadge.vue"; import "./style.css"; export default { @@ -10,7 +11,10 @@ export default { Layout: () => { return h(DefaultTheme.Layout, null, { // https://vitepress.dev/guide/extending-default-theme#layout-slots - "home-features-after": () => h(HomeCustomSections), + "home-features-after": () => [ + h(HomeCustomSections), + h(HomeProductHuntBadge), + ], }); }, } satisfies Theme; diff --git a/README.md b/README.md index 9e4b6c0..a6a6531 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,14 @@ Or add to `package.json`: ### Options -| Option | Default | Description | -| --------- | ---------- | -------------------------------- | -| `outDir` | `.srcpack` | Output directory for bundles | -| `bundles` | — | Named bundles with glob patterns | -| `upload` | — | Upload destination(s) | +| Option | Default | Description | +| ------------- | ---------- | -------------------------------------- | +| `outDir` | `.srcpack` | Output directory for bundles | +| `emptyOutDir` | `true`\* | Empty output directory before bundling | +| `bundles` | — | Named bundles with glob patterns | +| `upload` | — | Upload destination(s) | + +\*`emptyOutDir` defaults to `true` when `outDir` is inside project root. When `outDir` is outside root, a warning is emitted unless explicitly set. ### Bundle Config @@ -70,7 +73,8 @@ Or add to `package.json`: { include: "src/**/*", outfile: "~/Downloads/bundle.txt", // custom output path - index: true // include index header (default) + index: true, // include index header (default) + prompt: "./prompts/review.md" // prepend from file (or inline text) } ``` @@ -90,6 +94,7 @@ export default defineConfig({ folderId: "1ABC...", // Google Drive folder ID (from URL) clientId: "...", clientSecret: "...", + exclude: ["local"], // skip specific bundles }, }); ``` @@ -127,12 +132,14 @@ export function utils() { ## CLI ```bash -npx srcpack # Bundle all, upload if configured -npx srcpack web api # Bundle specific bundles only -npx srcpack --dry-run # Preview without writing files -npx srcpack --no-upload # Bundle only, skip upload -npx srcpack init # Interactive config setup -npx srcpack login # Authenticate with Google Drive +npx srcpack # Bundle all, upload if configured +npx srcpack web api # Bundle specific bundles only +npx srcpack --dry-run # Preview without writing files +npx srcpack --emptyOutDir # Empty output directory before bundling +npx srcpack --no-emptyOutDir # Keep existing files in output directory +npx srcpack --no-upload # Bundle only, skip upload +npx srcpack init # Interactive config setup +npx srcpack login # Authenticate with Google Drive ``` ## API diff --git a/bun.lock b/bun.lock index 3532a24..f553d08 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "configVersion": 1, "workspaces": { "": { - "name": "rollzup", + "name": "srcpack", "dependencies": { "@clack/prompts": "^0.11.0", "@googleapis/drive": "^20.0.0", @@ -12,7 +12,7 @@ "google-auth-library": "^10.5.0", "ignore": "^7.0.5", "oauth-callback": "^1.2.5", - "ora": "^9.0.0", + "ora": "^9.1.0", "picomatch": "^4.0.2", "zod": "^4.3.5", }, @@ -20,7 +20,7 @@ "@types/bun": "^1.3.6", "@types/picomatch": "^4.0.2", "gh-pages": "^6.3.0", - "prettier": "^3.8.0", + "prettier": "^3.8.1", "typescript": "^5.9.3", "vitepress": "^2.0.0-alpha.15", "vitepress-plugin-llms": "^1.10.0", @@ -28,48 +28,6 @@ }, }, "packages": { - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8hbezMsGa0crSt7/DKjkYL1UbbJJW/UFxTfhmf5qcIeYeeWG4dTNmm+DWbUdIsTaWvp59KC4eeC9gYXBbTHd7w=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - - "@ai-sdk/react": ["@ai-sdk/react@2.0.123", "", { "dependencies": { "@ai-sdk/provider-utils": "3.0.20", "ai": "5.0.121", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1", "zod": "^3.25.76 || ^4.1.8" }, "optionalPeers": ["zod"] }, "sha512-exaEvHAsDdR0wgzF3l0BmC9U1nPLnkPK2CCnX3BP4RDj/PySZvPXjry3AOz1Ayb8KSPZgWklVRzxsQxrOYQJxA=="], - - "@algolia/abtesting": ["@algolia/abtesting@1.12.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-0SpSdnME0RCS6UHSs9XD3ox4bMcCg1JTmjAJ3AU6rcTlX54CZOAEPc2as8uSghX6wfKGT0HWes4TeUpjJMg6FQ=="], - - "@algolia/autocomplete-core": ["@algolia/autocomplete-core@1.19.2", "", { "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", "@algolia/autocomplete-shared": "1.19.2" } }, "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw=="], - - "@algolia/autocomplete-plugin-algolia-insights": ["@algolia/autocomplete-plugin-algolia-insights@1.19.2", "", { "dependencies": { "@algolia/autocomplete-shared": "1.19.2" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg=="], - - "@algolia/autocomplete-shared": ["@algolia/autocomplete-shared@1.19.2", "", { "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w=="], - - "@algolia/client-abtesting": ["@algolia/client-abtesting@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-i2C8sBcl3EKXuCd5nlGohW+pZ9pY3P3JKJ2OYqsbCPg6wURiR32hNDiDvDq7/dqJ7KWWwC2snxJhokZzGlckgQ=="], - - "@algolia/client-analytics": ["@algolia/client-analytics@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-uFmD7m3LOym1SAURHeiqupHT9jui+9HK0lAiIvm077gXEscOM5KKXM4rg/ICzQ3UDHLZEA0Lb5TglWsXnieE6w=="], - - "@algolia/client-common": ["@algolia/client-common@5.46.3", "", {}, "sha512-SN+yK840nXa+2+mF72hrDfGd8+B7eBjF8TK/8KoRMdjlAkO/P3o3vtpjKRKI/Sk4L8kYYkB/avW8l+cwR+O1Ew=="], - - "@algolia/client-insights": ["@algolia/client-insights@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-5ic1liG0VucNPi6gKCWh5bEUGWQfyEmVeXiNKS+rOSppg7B7nKH0PEEJOFXBbHmgK5aPfNNZINiKcyUoH4XsFA=="], - - "@algolia/client-personalization": ["@algolia/client-personalization@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-f4HNitgTip8tntKgluYBTc1LWSOkbNCdxZvRA3rRBZnEAYSvLe7jpE+AxRep6RY+prSWwMtyeCFhA/F1Um+TuQ=="], - - "@algolia/client-query-suggestions": ["@algolia/client-query-suggestions@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-/AaVqah2aYyJj7Cazu5QRkgcV3HF3lkBJo5TRkgqQ26xR4iHNRbLF2YsWJfJpJEFghlTF2HOCh7IgzaUCnM+8A=="], - - "@algolia/client-search": ["@algolia/client-search@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-hfpCIukPuwkrlwsYfJEWdU5R5bduBHEq2uuPcqmgPgNq5MSjmiNIzRuzxGZZgiBKcre6gZT00DR7G1AFn//wiQ=="], - - "@algolia/ingestion": ["@algolia/ingestion@1.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-ChVzNkCzAVxKozTnTgPWCG69WQLjzW7X6OqD91zUh8U38ZhPEX/t3qGhXs+M9ZNaHcJ7xToMB3jywNwONhpLGA=="], - - "@algolia/monitoring": ["@algolia/monitoring@1.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-MZa+Z5iPmVMxVAQ0aq4HpGsja5utSLEMcOuY01X8D46vvMrSPkP8DnlDFtu1PgJ0RwyIGqqx7v+ClFo6iRJ6bA=="], - - "@algolia/recommend": ["@algolia/recommend@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-cr3atJRJBKgAKZl/Oxo4sig6Se0+ukbyIOOluPV5H+ZAXVcxuMoXQgwQ1M5UHPnCnEsZ4uBXhBmilRgUQpUegw=="], - - "@algolia/requester-browser-xhr": ["@algolia/requester-browser-xhr@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3" } }, "sha512-/Ku9GImJf2SKoRM2S3e03MjCVaWJCP5olih4k54DRhNDdmxBkd3nsWuUXvDElY3Ucw/arBYGs5SYz79SoS5APw=="], - - "@algolia/requester-fetch": ["@algolia/requester-fetch@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3" } }, "sha512-Uw+SPy/zpfwbH1AxQaeOWvWVzPEcO0XbtLbbSz0HPcEIiBGWyfa9LUCxD5UferbDjrSQNVimmzl3FaWi4u8Ykw=="], - - "@algolia/requester-node-http": ["@algolia/requester-node-http@5.46.3", "", { "dependencies": { "@algolia/client-common": "5.46.3" } }, "sha512-4No9iTjr1GZ0JWsFbQJj9aZBnmKyY1sTxOoEud9+SGe3U6iAulF0A0lI4cWi/F/Gcfg8V3jkaddcqSQKDnE45w=="], - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -84,13 +42,9 @@ "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], - "@docsearch/core": ["@docsearch/core@4.4.0", "", { "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", "react": ">= 16.8.0 < 20.0.0", "react-dom": ">= 16.8.0 < 20.0.0" }, "optionalPeers": ["@types/react", "react", "react-dom"] }, "sha512-kiwNo5KEndOnrf5Kq/e5+D9NBMCFgNsDoRpKQJ9o/xnSlheh6b8AXppMuuUVVdAUIhIfQFk/07VLjjk/fYyKmw=="], - - "@docsearch/css": ["@docsearch/css@4.4.0", "", {}, "sha512-e9vPgtih6fkawakmYo0Y6V4BKBmDV7Ykudn7ADWXUs5b6pmtBRwDbpSG/WiaUG63G28OkJDEnsMvgIAnZgGwYw=="], + "@docsearch/css": ["@docsearch/css@4.5.3", "", {}, "sha512-kUpHaxn0AgI3LQfyzTYkNUuaFY4uEz/Ym9/N/FvyDE+PzSgZsCyDH9jE49B6N6f1eLCm9Yp64J9wENd6vypdxA=="], - "@docsearch/js": ["@docsearch/js@4.4.0", "", { "dependencies": { "@docsearch/react": "4.4.0", "htm": "3.1.1" } }, "sha512-vCiKzjYD54bugUIMZA6YzuLDilkD3TNH/kfbvqsnzxiLTMu8F13psD+hdMSEOn7j+dFJOaf49fZ+gwr+rXctMw=="], - - "@docsearch/react": ["@docsearch/react@4.4.0", "", { "dependencies": { "@ai-sdk/react": "^2.0.30", "@algolia/autocomplete-core": "1.19.2", "@docsearch/core": "4.4.0", "@docsearch/css": "4.4.0", "ai": "^5.0.30", "algoliasearch": "^5.28.0", "marked": "^16.3.0", "zod": "^4.1.8" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", "react": ">= 16.8.0 < 20.0.0", "react-dom": ">= 16.8.0 < 20.0.0", "search-insights": ">= 1 < 3" }, "optionalPeers": ["@types/react", "react", "react-dom", "search-insights"] }, "sha512-z12zeg1mV7WD4Ag4pKSuGukETJLaucVFwszDXL/qLaEgRqxEaVacO9SR1qqnCXvZztlvz2rt7cMqryi/7sKfjA=="], + "@docsearch/js": ["@docsearch/js@4.5.3", "", {}, "sha512-rcBiUMCXbZLqrLIT6F6FDcrG/tyvM2WM0zum6NPbIiQNDQxbSgmNc+/bToS0rxBsXaxiU64esiWoS02WqrWLsg=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], @@ -146,7 +100,7 @@ "@googleapis/drive": ["@googleapis/drive@20.0.0", "", { "dependencies": { "googleapis-common": "^8.0.0" } }, "sha512-qLi5ypZn0zYY2FcGjdlHQsv1DAFNRwCWFiE5kq23J0yTdUSZynh/mDph9NBaiQ9ybajrmttySR/rSaNfm8S/bA=="], - "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.66", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-D1OnnXwiQXFkVMw5M+Bt8mPsXeMkQyGmMdrmN7lsQlKMUkfLOp6JWhnUJ92po51WXT046aF/zzqSmkKqg08p4Q=="], + "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.67", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-RGJRwlxyup54L1UDAjCshy3ckX5zcvYIU74YLSnUgHGvqh6B4mvksbGNHAIEp7dZQ6cM13RZVT5KC07CmnFNew=="], "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], @@ -164,61 +118,59 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.56.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.56.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], @@ -236,8 +188,6 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], @@ -256,7 +206,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + "@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="], "@types/picomatch": ["@types/picomatch@4.0.2", "", {}, "sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA=="], @@ -266,17 +216,15 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], - "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.53" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.26", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w=="], + "@vue/compiler-core": ["@vue/compiler-core@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.27", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ=="], - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.26", "", { "dependencies": { "@vue/compiler-core": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A=="], + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.27", "", { "dependencies": { "@vue/compiler-core": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w=="], - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.26", "@vue/compiler-dom": "3.5.26", "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA=="], + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.27", "@vue/compiler-dom": "3.5.27", "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ=="], - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw=="], + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw=="], "@vue/devtools-api": ["@vue/devtools-api@8.0.5", "", { "dependencies": { "@vue/devtools-kit": "^8.0.5" } }, "sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw=="], @@ -284,15 +232,15 @@ "@vue/devtools-shared": ["@vue/devtools-shared@8.0.5", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg=="], - "@vue/reactivity": ["@vue/reactivity@3.5.26", "", { "dependencies": { "@vue/shared": "3.5.26" } }, "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ=="], + "@vue/reactivity": ["@vue/reactivity@3.5.27", "", { "dependencies": { "@vue/shared": "3.5.27" } }, "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ=="], - "@vue/runtime-core": ["@vue/runtime-core@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q=="], + "@vue/runtime-core": ["@vue/runtime-core@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A=="], - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/runtime-core": "3.5.26", "@vue/shared": "3.5.26", "csstype": "^3.2.3" } }, "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ=="], + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/runtime-core": "3.5.27", "@vue/shared": "3.5.27", "csstype": "^3.2.3" } }, "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg=="], - "@vue/server-renderer": ["@vue/server-renderer@3.5.26", "", { "dependencies": { "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "vue": "3.5.26" } }, "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA=="], + "@vue/server-renderer": ["@vue/server-renderer@3.5.27", "", { "dependencies": { "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "vue": "3.5.27" } }, "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA=="], - "@vue/shared": ["@vue/shared@3.5.26", "", {}, "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="], + "@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="], "@vueuse/core": ["@vueuse/core@14.1.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw=="], @@ -304,10 +252,6 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "ai": ["ai@5.0.121", "", { "dependencies": { "@ai-sdk/gateway": "2.0.27", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3iYPdARKGLryC/7OA9RgBUaym1gynvWS7UPy8NwoRNCKP52lshldtHB5xcEfVviw7liWH2zJlW9yEzsDglcIEQ=="], - - "algoliasearch": ["algoliasearch@5.46.3", "", { "dependencies": { "@algolia/abtesting": "1.12.3", "@algolia/client-abtesting": "5.46.3", "@algolia/client-analytics": "5.46.3", "@algolia/client-common": "5.46.3", "@algolia/client-insights": "5.46.3", "@algolia/client-personalization": "5.46.3", "@algolia/client-query-suggestions": "5.46.3", "@algolia/client-search": "5.46.3", "@algolia/ingestion": "1.46.3", "@algolia/monitoring": "1.46.3", "@algolia/recommend": "5.46.3", "@algolia/requester-browser-xhr": "5.46.3", "@algolia/requester-fetch": "5.46.3", "@algolia/requester-node-http": "5.46.3" } }, "sha512-n/NdPglzmkcNYZfIT3Fo8pnDR/lKiK1kZ1Yaa315UoLyHymADhWw15+bzN5gBxrCA8KyeNu0JJD6mLtTov43lQ=="], - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -382,7 +326,7 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], @@ -428,8 +372,6 @@ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], @@ -512,8 +454,6 @@ "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], - "htm": ["htm@3.1.1", "", {}, "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ=="], - "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -562,8 +502,6 @@ "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], @@ -594,8 +532,6 @@ "markdown-title": ["markdown-title@1.0.2", "", {}, "sha512-MqIQVVkz+uGEHi3TsHx/czcxxCbRIL7sv5K5DnYw/tI+apY54IbPefV/cmgxp6LoJSEx/TqcHdLs/298afG5QQ=="], - "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], @@ -692,7 +628,7 @@ "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], - "ora": ["ora@9.0.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0", "strip-ansi": "^7.1.2" } }, "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A=="], + "ora": ["ora@9.1.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0" } }, "sha512-53uuLsXHOAJl5zLrUrzY9/kE+uIFEx7iaH4g2BIJQK4LZjY4LpCCYZVKDWIkL+F01wAaCg93duQ1whnK/AmY1A=="], "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], @@ -716,7 +652,7 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -728,7 +664,7 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - "prettier": ["prettier@3.8.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA=="], + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], "pretty-bytes": ["pretty-bytes@7.1.0", "", {}, "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw=="], @@ -740,8 +676,6 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], - "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], @@ -768,7 +702,7 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], - "rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="], + "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -776,8 +710,6 @@ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="], - "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -828,12 +760,8 @@ "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], - "swr": ["swr@2.3.8", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w=="], - "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], - "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -870,8 +798,6 @@ "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], @@ -882,7 +808,7 @@ "vitepress-plugin-llms": ["vitepress-plugin-llms@1.10.0", "", { "dependencies": { "gray-matter": "^4.0.3", "markdown-it": "^14.1.0", "markdown-title": "^1.0.2", "mdast-util-from-markdown": "^2.0.2", "millify": "^6.1.0", "minimatch": "^10.1.1", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "pretty-bytes": "^7.1.0", "remark": "^15.0.1", "remark-frontmatter": "^5.0.0", "tokenx": "^1.2.1", "unist-util-remove": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-dgD5KV8D9vXlQtAf/KUjSgr3QymH1fHT7XkQ/UuIqvIjnKdzZI+0gT3puGxUBuqgvlFjYWA6f8k80tXl6gwWkw=="], - "vue": ["vue@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", "@vue/runtime-dom": "3.5.26", "@vue/server-renderer": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA=="], + "vue": ["vue@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27", "@vue/runtime-dom": "3.5.27", "@vue/server-renderer": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw=="], "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], @@ -910,7 +836,7 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@vue/compiler-core/entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], diff --git a/docs/configuration.md b/docs/configuration.md index 4e37757..37f7838 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,11 +24,28 @@ export default defineConfig({ ## Options -| Option | Type | Default | Description | -| --------- | -------- | ---------- | ---------------------------- | -| `outDir` | `string` | `.srcpack` | Output directory for bundles | -| `bundles` | `object` | — | Named bundles (required) | -| `upload` | `object` | — | Upload destination | +| Option | Type | Default | Description | +| ------------- | --------- | --------------- | -------------------------------------- | +| `root` | `string` | `process.cwd()` | Project root directory | +| `outDir` | `string` | `.srcpack` | Output directory for bundles | +| `emptyOutDir` | `boolean` | `true`\* | Empty output directory before bundling | +| `bundles` | `object` | — | Named bundles (required) | +| `upload` | `object` | — | Upload destination | + +\*`emptyOutDir` defaults to `true` when `outDir` is inside project root. + +### root + +Project root directory where files are bundled from. Can be absolute or relative to CWD. + +```ts +export default defineConfig({ + root: "./packages/app", // bundle from subdirectory + bundles: { + app: "src/**/*", // matches packages/app/src/**/* + }, +}); +``` ## Bundle Definitions @@ -66,11 +83,12 @@ bundles: { **Bundle options:** -| Option | Type | Default | Description | -| --------- | -------------------- | --------------------- | -------------------- | -| `include` | `string \| string[]` | — | Glob pattern(s) | -| `outfile` | `string` | `{outDir}/{name}.txt` | Custom output path | -| `index` | `boolean` | `true` | Include index header | +| Option | Type | Default | Description | +| --------- | -------------------- | --------------------- | ----------------------------------------- | +| `include` | `string \| string[]` | — | Glob pattern(s) | +| `outfile` | `string` | `{outDir}/{name}.txt` | Custom output path | +| `index` | `boolean` | `true` | Include index header | +| `prompt` | `string` | — | Text or file path (`./`, `~/`) to prepend | ## Pattern Syntax @@ -148,6 +166,19 @@ export default defineConfig({ }); ``` +### Code Review Bundle + +```ts +export default defineConfig({ + bundles: { + review: { + include: "src/**/*", + prompt: "./prompts/review.md", // or inline: "Review this code..." + }, + }, +}); +``` + ### Package.json Config ```json @@ -160,6 +191,35 @@ export default defineConfig({ } ``` +## Upload Configuration + +Configure cloud upload destinations. See [Google Drive Upload](/upload) for setup details. + +```ts +export default defineConfig({ + bundles: { + /* ... */ + }, + upload: { + provider: "gdrive", + folderId: "1ABC...", + clientId: process.env.GDRIVE_CLIENT_ID, + clientSecret: process.env.GDRIVE_CLIENT_SECRET, + exclude: ["local"], // skip these bundles + }, +}); +``` + +**Upload options:** + +| Option | Type | Default | Description | +| -------------- | ---------- | ------- | ---------------------------------- | +| `provider` | `"gdrive"` | — | Upload provider (required) | +| `folderId` | `string` | — | Target folder ID (optional) | +| `clientId` | `string` | — | OAuth client ID (required) | +| `clientSecret` | `string` | — | OAuth client secret (required) | +| `exclude` | `string[]` | — | Bundle names to skip during upload | + ## TypeScript Support The `defineConfig` helper provides type checking and autocomplete: diff --git a/docs/upload.md b/docs/upload.md index c0199ab..31b0446 100644 --- a/docs/upload.md +++ b/docs/upload.md @@ -24,16 +24,28 @@ export default defineConfig({ bundles: { web: "apps/web/**/*", api: "apps/api/**/*", + local: "local/**/*", // local-only bundle }, upload: { provider: "gdrive", folderId: "1ABC...", // From Drive folder URL clientId: "...", clientSecret: "...", + exclude: ["local"], // skip these bundles }, }); ``` +**Upload options:** + +| Option | Type | Description | +| -------------- | ---------- | ------------------------------------------------ | +| `provider` | `"gdrive"` | Upload provider (currently only gdrive) | +| `folderId` | `string` | Google Drive folder ID (optional, defaults root) | +| `clientId` | `string` | OAuth 2.0 client ID | +| `clientSecret` | `string` | OAuth 2.0 client secret | +| `exclude` | `string[]` | Bundle names to skip during upload | + **Finding your folder ID:** Open the target folder in Google Drive. The URL looks like: @@ -80,6 +92,21 @@ Once configured, `npx srcpack` uploads bundles after bundling: ↑ Uploaded to Google Drive ``` +### Exclude Bundles + +To skip specific bundles from upload, use the `exclude` option: + +```ts +upload: { + provider: "gdrive", + clientId: "...", + clientSecret: "...", + exclude: ["local", "debug"], // these bundles won't upload +} +``` + +This is useful for local-only bundles that shouldn't be shared. + ### Skip Upload To bundle without uploading: diff --git a/package.json b/package.json index 07caee5..a273a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "srcpack", - "version": "0.1.6", + "version": "0.1.10", "description": "Zero-config CLI for bundling code into LLM-optimized context files", "keywords": [ "llm", @@ -79,7 +79,7 @@ "google-auth-library": "^10.5.0", "ignore": "^7.0.5", "oauth-callback": "^1.2.5", - "ora": "^9.0.0", + "ora": "^9.1.0", "picomatch": "^4.0.2", "zod": "^4.3.5" }, @@ -87,7 +87,7 @@ "@types/bun": "^1.3.6", "@types/picomatch": "^4.0.2", "gh-pages": "^6.3.0", - "prettier": "^3.8.0", + "prettier": "^3.8.1", "typescript": "^5.9.3", "vitepress": "^2.0.0-alpha.15", "vitepress-plugin-llms": "^1.10.0" diff --git a/src/bundle.ts b/src/bundle.ts index 8614cbe..0a2e0d9 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { glob } from "fast-glob"; import picomatch from "picomatch"; import ignore, { type Ignore } from "ignore"; -import type { BundleConfigInput } from "./config.ts"; +import { expandPath, type BundleConfigInput } from "./config.ts"; // Binary file detection: check first 8KB for null bytes (same heuristic as git) const BINARY_CHECK_SIZE = 8192; @@ -86,20 +86,79 @@ function isExcluded(filePath: string, matchers: Matcher[]): boolean { } /** - * Load and parse .gitignore file from a directory + * Convert gitignore patterns to glob ignore patterns for fast-glob. + * This prevents traversing into ignored directories (performance optimization). + * + * Conservative approach: only convert simple, unambiguous directory patterns. + * Complex patterns (negations, root-anchored, globs) are left to the ignore filter. */ -async function loadGitignore(cwd: string): Promise { +function gitignoreToGlobPatterns(lines: string[]): string[] { + // If any negation patterns exist, skip optimization entirely + // (negations could re-include files in otherwise-ignored directories) + const hasNegation = lines.some((line) => { + const trimmed = line.trim(); + // Any line starting with ! is a negation (including !#file which negates "#file") + return trimmed.startsWith("!"); + }); + if (hasNegation) return []; + + const patterns: string[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + // Skip empty lines and comments + if (!trimmed || trimmed.startsWith("#")) continue; + + // Skip patterns with special gitignore features we can't safely convert: + // - Root-anchored (starts with /) + // - Contains globs (*, ?, [) + // - Contains path separators (complex paths) + // - Escaped characters + if ( + trimmed.startsWith("/") || + trimmed.includes("*") || + trimmed.includes("?") || + trimmed.includes("[") || + trimmed.includes("/") || + trimmed.includes("\\") + ) { + continue; + } + + // Only convert simple directory names (e.g., "node_modules", "dist") + // These are safe to prune at any depth + const name = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed; + if (name && /^[\w.-]+$/.test(name)) { + patterns.push(`**/${name}/**`); + } + } + + return patterns; +} + +interface GitignoreResult { + ignore: Ignore; + globPatterns: string[]; +} + +/** + * Load and parse .gitignore file from a directory. + * Returns both an Ignore instance for filtering and glob patterns for fast-glob. + */ +async function loadGitignore(cwd: string): Promise { const ig = ignore(); const gitignorePath = join(cwd, ".gitignore"); + let globPatterns: string[] = []; try { const content = await readFile(gitignorePath, "utf-8"); ig.add(content); + globPatterns = gitignoreToGlobPatterns(content.split("\n")); } catch { // No .gitignore file, return empty ignore instance } - return ig; + return { ignore: ig, globPatterns }; } /** @@ -114,12 +173,18 @@ export async function resolvePatterns( ): Promise { const { include, exclude, force } = normalizePatterns(config); const excludeMatchers = exclude.map((p) => picomatch(p)); - const gitignore = await loadGitignore(cwd); + const { ignore: gitignore, globPatterns } = await loadGitignore(cwd); const files = new Set(); // Regular includes: respect .gitignore + // Pass gitignore patterns to fast-glob to skip ignored directories during traversal if (include.length > 0) { - const matches = await glob(include, { cwd, onlyFiles: true, dot: true }); + const matches = await glob(include, { + cwd, + onlyFiles: true, + dot: true, + ignore: globPatterns, + }); for (const match of matches) { if (!isExcluded(match, excludeMatchers) && !gitignore.ignores(match)) { const fullPath = join(cwd, match); @@ -130,7 +195,7 @@ export async function resolvePatterns( } } - // Force includes: bypass .gitignore + // Force includes: bypass .gitignore (no ignore patterns passed to glob) if (force.length > 0) { const matches = await glob(force, { cwd, onlyFiles: true, dot: true }); for (const match of matches) { @@ -182,6 +247,7 @@ export function formatIndex(index: FileEntry[]): string { export interface BundleOptions { includeIndex?: boolean; // Default: true + prompt?: string; // Text to prepend to bundle } /** @@ -204,6 +270,8 @@ export async function createBundle( options: BundleOptions = {}, ): Promise { const { includeIndex = true } = options; + // Normalize prompt: trim and treat whitespace-only as no prompt + const prompt = options.prompt?.trim() || undefined; const index: FileEntry[] = []; const contentParts: string[] = []; let currentLine = 1; @@ -232,26 +300,40 @@ export async function createBundle( currentLine = entry.endLine + 1; } + // Calculate prompt offset (prompt text + blank + "---" + blank) + const promptLines = prompt ? countLines(prompt) + 3 : 0; + if (includeIndex) { // Adjust line numbers to account for index header // Header: "# Index (N files)" + N index lines + 1 blank line - const headerLines = index.length + 2; + const headerLines = index.length + 2 + promptLines; for (const entry of index) { entry.startLine += headerLines; entry.endLine += headerLines; } const indexBlock = formatIndex(index); - const content = + const bundleContent = index.length === 0 ? indexBlock : indexBlock + "\n\n" + contentParts.join("\n"); + const content = prompt ? `${prompt}\n\n---\n\n${bundleContent}` : bundleContent; return { content, index }; } // No index: just join file content - const content = contentParts.join("\n"); + const bundleContent = contentParts.join("\n"); + const content = prompt ? `${prompt}\n\n---\n\n${bundleContent}` : bundleContent; + + // Adjust line numbers for prompt offset (no index case) + if (promptLines > 0) { + for (const entry of index) { + entry.startLine += promptLines; + entry.endLine += promptLines; + } + } + return { content, index }; } @@ -265,6 +347,46 @@ function getIncludeIndex(config: BundleConfigInput): boolean { return true; } +/** + * Extract the prompt option from bundle config. + * Returns undefined for empty/null/undefined values. + */ +function getPrompt(config: BundleConfigInput): string | undefined { + if (typeof config === "object" && !Array.isArray(config)) { + const prompt = config.prompt; + // Treat empty string, null, undefined as no prompt + return prompt && prompt.trim() ? prompt : undefined; + } + return undefined; +} + +/** + * Resolve prompt value: load from file if path, otherwise return as-is. + * Paths starting with ./, ../, or ~/ are treated as file paths. + */ +async function resolvePrompt( + prompt: string | undefined, + cwd: string, +): Promise { + if (!prompt) return undefined; + + // Check if prompt looks like a file path + if (prompt.startsWith("./") || prompt.startsWith("../")) { + const filePath = join(cwd, prompt); + const content = await readFile(filePath, "utf-8"); + return content.trim() || undefined; + } + + if (prompt.startsWith("~/")) { + const filePath = expandPath(prompt); + const content = await readFile(filePath, "utf-8"); + return content.trim() || undefined; + } + + // Trim inline prompts for consistent behavior with file-based prompts + return prompt.trim() || undefined; +} + /** * Bundle a single named bundle from config */ @@ -275,5 +397,6 @@ export async function bundleOne( ): Promise { const files = await resolvePatterns(config, cwd); const includeIndex = getIncludeIndex(config); - return createBundle(files, cwd, { includeIndex }); + const prompt = await resolvePrompt(getPrompt(config), cwd); + return createBundle(files, cwd, { includeIndex, prompt }); } diff --git a/src/cli.ts b/src/cli.ts index 29d5484..bfd9a07 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,8 +1,8 @@ #!/usr/bin/env node // SPDX-License-Identifier: MIT -import { mkdir, writeFile } from "node:fs/promises"; -import { dirname, join } from "node:path"; +import { mkdir, readdir, rm, writeFile } from "node:fs/promises"; +import { dirname, isAbsolute, join, relative, resolve } from "node:path"; import ora from "ora"; import { bundleOne, type BundleResult } from "./bundle.ts"; import { @@ -38,6 +38,31 @@ function plural(n: number, singular: string, pluralForm?: string): string { return n === 1 ? singular : (pluralForm ?? singular + "s"); } +function isOutDirInsideRoot(outDir: string, root: string): boolean { + const absoluteOutDir = isAbsolute(outDir) ? outDir : resolve(root, outDir); + const rel = relative(root, absoluteOutDir); + return !rel.startsWith("..") && !isAbsolute(rel); +} + +/** + * Empty a directory while preserving specified entries (e.g., `.git`). + * Uses `force: true` to handle read-only or in-use files. + */ +async function emptyDirectory(dir: string, skip: string[] = []): Promise { + let entries: string[]; + try { + entries = await readdir(dir); + } catch { + return; // Directory doesn't exist, nothing to empty + } + const skipSet = new Set(skip); + await Promise.all( + entries + .filter((entry) => !skipSet.has(entry)) + .map((entry) => rm(join(dir, entry), { recursive: true, force: true })), + ); +} + async function main() { const args = process.argv.slice(2); @@ -54,9 +79,11 @@ Usage: npx srcpack login Authenticate with Google Drive Options: - --dry-run Preview bundles without writing files - --no-upload Skip uploading to cloud storage - -h, --help Show this help message + --dry-run Preview bundles without writing files + --emptyOutDir Empty output directory before bundling + --no-emptyOutDir Keep existing files in output directory + --no-upload Skip uploading to cloud storage + -h, --help Show this help message `); return; } @@ -73,6 +100,12 @@ Options: const dryRun = args.includes("--dry-run"); const noUpload = args.includes("--no-upload"); + // CLI flags: --emptyOutDir forces true, --no-emptyOutDir forces false + const emptyOutDirFlag = args.includes("--emptyOutDir") + ? true + : args.includes("--no-emptyOutDir") + ? false + : undefined; const subcommands = ["init", "login"]; const requestedBundles = args.filter( (arg) => !arg.startsWith("-") && !subcommands.includes(arg), @@ -105,7 +138,32 @@ Options: return; } - const cwd = process.cwd(); + const root = config.root; + + // Resolve emptyOutDir: CLI flag > config > auto (true if inside root) + const outDirInsideRoot = isOutDirInsideRoot(config.outDir, root); + const emptyOutDir = emptyOutDirFlag ?? config.emptyOutDir ?? outDirInsideRoot; + + // Warn if outDir is outside root and emptyOutDir is not explicitly set + if ( + !outDirInsideRoot && + emptyOutDirFlag === undefined && + config.emptyOutDir === undefined + ) { + console.warn( + `Warning: outDir "${config.outDir}" is outside project root. ` + + "Use --emptyOutDir to suppress this warning and empty the directory.", + ); + } + + // Empty outDir before bundling (unless dry-run) + if (emptyOutDir && !dryRun) { + const outDirPath = isAbsolute(config.outDir) + ? config.outDir + : resolve(root, config.outDir); + await emptyDirectory(outDirPath, [".git"]); + } + const outputs: BundleOutput[] = []; // Process all bundles with progress @@ -118,7 +176,7 @@ Options: const name = bundleNames[i]!; bundleSpinner.text = `Bundling ${name}... (${i + 1}/${bundleNames.length})`; const bundleConfig = config.bundles[name]!; - const result = await bundleOne(name, bundleConfig, cwd); + const result = await bundleOne(name, bundleConfig, root); const outfile = getOutfile(bundleConfig, name, config.outDir); outputs.push({ name, outfile, result }); } @@ -139,7 +197,7 @@ Options: for (const { name, outfile, result } of outputs) { const fileCount = result.index.length; const lineCount = sumLines(result); - const outPath = join(cwd, outfile); + const outPath = join(root, outfile); const nameCol = name.padEnd(maxNameLen); const filesCol = formatNumber(fileCount).padStart(maxFilesLen); @@ -186,7 +244,7 @@ Options: for (const uploadConfig of uploads) { if (isGdriveConfigured(uploadConfig)) { - await handleGdriveUpload(uploadConfig, outputs, cwd); + await handleGdriveUpload(uploadConfig, outputs, root); } } } @@ -277,8 +335,17 @@ function printUploadConfigHelp(): void { async function handleGdriveUpload( uploadConfig: UploadConfig, outputs: BundleOutput[], - cwd: string, + root: string, ): Promise { + // Filter out excluded bundles + const excludeSet = new Set(uploadConfig.exclude ?? []); + const toUpload = outputs.filter((o) => !excludeSet.has(o.name)); + + if (toUpload.length === 0) { + console.log("\nNo bundles to upload (all excluded)."); + return; + } + try { await ensureAuthenticated(uploadConfig); @@ -289,10 +356,10 @@ async function handleGdriveUpload( const results: UploadResult[] = []; - for (let i = 0; i < outputs.length; i++) { - const output = outputs[i]!; - const filePath = join(cwd, output.outfile); - uploadSpinner.text = `Uploading ${output.name}... (${i + 1}/${outputs.length})`; + for (let i = 0; i < toUpload.length; i++) { + const output = toUpload[i]!; + const filePath = join(root, output.outfile); + uploadSpinner.text = `Uploading ${output.name}... (${i + 1}/${toUpload.length})`; const result = await uploadFile(filePath, uploadConfig); results.push(result); } diff --git a/src/config.ts b/src/config.ts index 75a61cb..b81c8f2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT import { homedir } from "node:os"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { cosmiconfig } from "cosmiconfig"; import { z } from "zod"; @@ -12,33 +12,75 @@ export function expandPath(p: string): string { return p; } +/** Glob patterns for file matching. Single pattern or array of patterns. */ const PatternsSchema = z.union([ z.string().min(1), z.array(z.string().min(1)).min(1), ]); +/** + * Bundle configuration. Accepts a string pattern, array of patterns, or object. + * Patterns prefixed with `!` are exclusions. Patterns prefixed with `+` force + * inclusion (bypass .gitignore). + */ const BundleConfigSchema = z.union([ - z.string().min(1), // "src/**/*" - z.array(z.string().min(1)).min(1), // ["src/**/*", "!src/specs"] + z.string().min(1), + z.array(z.string().min(1)).min(1), z.object({ + /** Glob patterns to include in the bundle. */ include: PatternsSchema, + /** Custom output file path. Defaults to `/.txt`. */ outfile: z.string().optional(), - index: z.boolean().default(true), // Include index header in output + /** Include file index header in output. Defaults to true. */ + index: z.boolean().default(true), + /** Text to prepend to bundle (e.g., review instructions for LLMs). */ + prompt: z.string().optional(), }), ]); +/** + * Upload destination configuration. + * + * @example + * ```ts + * upload: { + * provider: "gdrive", + * clientId: process.env.GDRIVE_CLIENT_ID, + * clientSecret: process.env.GDRIVE_CLIENT_SECRET, + * folderId: "1abc...", + * exclude: ["local", "debug"], + * } + * ``` + */ const UploadConfigSchema = z.object({ + /** Upload provider. Currently only "gdrive" is supported. */ provider: z.literal("gdrive"), + /** Google Drive folder ID to upload files to. If omitted, uploads to root. */ folderId: z.string().optional(), + /** OAuth 2.0 client ID from Google Cloud Console. */ clientId: z.string().min(1), + /** OAuth 2.0 client secret from Google Cloud Console. */ clientSecret: z.string().min(1), + /** Bundle names to skip during upload. Supports exact names only. */ + exclude: z.array(z.string()).optional(), }); +/** Root configuration for srcpack. */ const ConfigSchema = z.object({ + /** + * Project root directory. Can be absolute or relative to CWD. + * @default process.cwd() + */ + root: z.string().default(""), + /** Output directory for bundle files. Defaults to ".srcpack". */ outDir: z.string().default(".srcpack"), + /** Empty outDir before bundling. Auto-enabled when outDir is inside project root. */ + emptyOutDir: z.boolean().optional(), + /** Upload configuration for cloud storage. Single destination or array. */ upload: z .union([UploadConfigSchema, z.array(UploadConfigSchema).min(1)]) .optional(), + /** Named bundles mapping bundle name to glob patterns or config object. */ bundles: z.record(z.string(), BundleConfigSchema), }); @@ -69,6 +111,10 @@ export function parseConfig(value: unknown): Config { } const config = result.data; + // Resolve root: absolute path, relative to CWD, or CWD if empty/unset + config.root = config.root + ? resolve(expandPath(config.root)) + : process.cwd(); config.outDir = expandPath(config.outDir); for (const bundle of Object.values(config.bundles)) { diff --git a/tests/fixtures/negation-project/.gitignore b/tests/fixtures/negation-project/.gitignore new file mode 100644 index 0000000..c509a5d --- /dev/null +++ b/tests/fixtures/negation-project/.gitignore @@ -0,0 +1,2 @@ +build/** +!build/keep.txt diff --git a/tests/fixtures/negation-project/build/bundle.js b/tests/fixtures/negation-project/build/bundle.js new file mode 100644 index 0000000..e05e687 --- /dev/null +++ b/tests/fixtures/negation-project/build/bundle.js @@ -0,0 +1 @@ +// This should be ignored diff --git a/tests/fixtures/negation-project/build/keep.txt b/tests/fixtures/negation-project/build/keep.txt new file mode 100644 index 0000000..52b8494 --- /dev/null +++ b/tests/fixtures/negation-project/build/keep.txt @@ -0,0 +1 @@ +This file should be included despite build/** being ignored diff --git a/tests/fixtures/negation-project/src/index.ts b/tests/fixtures/negation-project/src/index.ts new file mode 100644 index 0000000..1e29c45 --- /dev/null +++ b/tests/fixtures/negation-project/src/index.ts @@ -0,0 +1 @@ +export const hello = "world"; diff --git a/tests/fixtures/sample-project/prompts/review.md b/tests/fixtures/sample-project/prompts/review.md new file mode 100644 index 0000000..f1217df --- /dev/null +++ b/tests/fixtures/sample-project/prompts/review.md @@ -0,0 +1,3 @@ +Review this code for: +- Security issues +- Performance problems diff --git a/tests/unit/bundle.test.ts b/tests/unit/bundle.test.ts index dee8787..6cad97d 100644 --- a/tests/unit/bundle.test.ts +++ b/tests/unit/bundle.test.ts @@ -18,6 +18,7 @@ const forceIncludeDir = join( import.meta.dir, "../fixtures/force-include-project", ); +const negationDir = join(import.meta.dir, "../fixtures/negation-project"); describe("resolvePatterns", () => { test("should resolve string pattern", async () => { @@ -156,6 +157,16 @@ describe("resolvePatterns", () => { expect(files).toContain("docs/private.local.md"); expect(files).not.toContain("docs/guide.md"); }); + + test("should respect gitignore negation patterns", async () => { + // .gitignore contains: build/** + !build/keep.txt + // The negation re-includes build/keep.txt while other build files stay ignored + const files = await resolvePatterns("**/*", negationDir); + + expect(files).toContain("src/index.ts"); + expect(files).toContain("build/keep.txt"); // Re-included by negation + expect(files).not.toContain("build/bundle.js"); // Still ignored + }); }); describe("formatIndex", () => { @@ -289,6 +300,73 @@ describe("createBundle", () => { // Separator is line 1, content starts at line 2 expect(result.index[0]!.startLine).toBe(2); }); + + test("should prepend prompt with separator", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: "Review this code for security issues.", + }); + + expect(result.content).toStartWith("Review this code for security issues."); + expect(result.content).toContain("\n\n---\n\n"); + expect(result.content).toContain("# Index"); + }); + + test("should adjust line numbers for prompt offset", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: "Review this code.", + }); + + // Prompt: 1 line + blank + "---" + blank = 4 lines + // Index: header + 1 entry + blank = 3 lines + // Separator: 1 line, content starts next line + // Content at: 4 + 3 + 1 + 1 = 9 + expect(result.index[0]!.startLine).toBe(9); + }); + + test("should handle multi-line prompt", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: "Review this code.\nFocus on:\n- Security\n- Performance", + }); + + expect(result.content).toStartWith("Review this code."); + // Prompt: 4 lines + blank + "---" + blank = 7 lines + // Index: 3 lines, separator: 1 line, content next line + // Content at: 7 + 3 + 1 + 1 = 12 + expect(result.index[0]!.startLine).toBe(12); + }); + + test("should prepend prompt without index", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: "Review this code.", + includeIndex: false, + }); + + expect(result.content).toStartWith("Review this code."); + expect(result.content).toContain("\n\n---\n\n"); + expect(result.content).not.toContain("# Index"); + // Prompt: 1 line + blank + "---" + blank = 4 lines + // Separator: 1 line, content next line + // Content at: 4 + 1 + 1 = 6 + expect(result.index[0]!.startLine).toBe(6); + }); + + test("should ignore empty prompt", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: "", + }); + + expect(result.content).toStartWith("# Index"); + expect(result.content).not.toContain("---"); + }); + + test("should ignore whitespace-only prompt", async () => { + const result = await createBundle(["src/index.ts"], fixturesDir, { + prompt: " \n \n ", + }); + + expect(result.content).toStartWith("# Index"); + expect(result.content).not.toContain("---"); + }); }); describe("bundleOne", () => { @@ -334,4 +412,72 @@ describe("bundleOne", () => { expect(result.content).toContain("# Index"); }); + + test("should prepend prompt from config", async () => { + const result = await bundleOne( + "web", + { include: "src/index.ts", prompt: "Review this code." }, + fixturesDir, + ); + + expect(result.content).toStartWith("Review this code."); + expect(result.content).toContain("\n\n---\n\n"); + expect(result.content).toContain("# Index"); + }); + + test("should ignore empty prompt in config", async () => { + const result = await bundleOne( + "web", + { include: "src/index.ts", prompt: "" }, + fixturesDir, + ); + + expect(result.content).toStartWith("# Index"); + expect(result.content).not.toContain("---"); + }); + + test("should ignore undefined prompt in config", async () => { + const result = await bundleOne( + "web", + { include: "src/index.ts", prompt: undefined }, + fixturesDir, + ); + + expect(result.content).toStartWith("# Index"); + expect(result.content).not.toContain("---"); + }); + + test("should load prompt from file when path starts with ./", async () => { + const result = await bundleOne( + "web", + { include: "src/index.ts", prompt: "./prompts/review.md" }, + fixturesDir, + ); + + expect(result.content).toStartWith("Review this code for:"); + expect(result.content).toContain("- Security issues"); + expect(result.content).toContain("\n\n---\n\n"); + }); + + test("should attempt to load prompt from ~/ path", async () => { + // Verify ~/ paths are treated as file paths (throws for non-existent file) + await expect( + bundleOne( + "web", + { include: "src/index.ts", prompt: "~/non-existent-srcpack-test.md" }, + fixturesDir, + ), + ).rejects.toThrow("ENOENT"); + }); + + test("should use literal prompt when not a path", async () => { + const result = await bundleOne( + "web", + { include: "src/index.ts", prompt: "Check for bugs." }, + fixturesDir, + ); + + expect(result.content).toStartWith("Check for bugs."); + expect(result.content).not.toContain("./"); + }); }); diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts index 5a2ebcb..0541a7b 100644 --- a/tests/unit/config.test.ts +++ b/tests/unit/config.test.ts @@ -106,6 +106,41 @@ describe("parseConfig", () => { }); describe("default values", () => { + test("should default root to process.cwd()", () => { + const config = parseConfig({ + bundles: { web: "src/**/*" }, + }); + + expect(config.root).toBe(process.cwd()); + }); + + test("should resolve relative root to absolute path", () => { + const config = parseConfig({ + root: "./subdir", + bundles: { web: "src/**/*" }, + }); + + expect(config.root).toBe(join(process.cwd(), "subdir")); + }); + + test("should resolve tilde root to home directory", () => { + const config = parseConfig({ + root: "~/projects", + bundles: { web: "src/**/*" }, + }); + + expect(config.root).toBe(join(homedir(), "projects")); + }); + + test("should keep absolute root path unchanged", () => { + const config = parseConfig({ + root: "/absolute/path", + bundles: { web: "src/**/*" }, + }); + + expect(config.root).toBe("/absolute/path"); + }); + test("should default outDir to .srcpack", () => { const config = parseConfig({ bundles: { web: "src/**/*" }, @@ -122,6 +157,32 @@ describe("parseConfig", () => { expect(config.upload).toBeUndefined(); }); + test("should leave emptyOutDir undefined when not provided", () => { + const config = parseConfig({ + bundles: { web: "src/**/*" }, + }); + + expect(config.emptyOutDir).toBeUndefined(); + }); + + test("should accept emptyOutDir true", () => { + const config = parseConfig({ + bundles: { web: "src/**/*" }, + emptyOutDir: true, + }); + + expect(config.emptyOutDir).toBe(true); + }); + + test("should accept emptyOutDir false", () => { + const config = parseConfig({ + bundles: { web: "src/**/*" }, + emptyOutDir: false, + }); + + expect(config.emptyOutDir).toBe(false); + }); + test("should accept single upload config", () => { const config = parseConfig({ bundles: { web: "src/**/*" }, @@ -163,6 +224,36 @@ describe("parseConfig", () => { expect(Array.isArray(config.upload)).toBe(true); expect(config.upload).toHaveLength(2); }); + + test("should accept upload.exclude as array of bundle names", () => { + const config = parseConfig({ + bundles: { web: "src/**/*", local: "local/**/*" }, + upload: { + provider: "gdrive", + clientId: "id", + clientSecret: "secret", + exclude: ["local", "debug"], + }, + }); + + expect((config.upload as UploadConfig).exclude).toEqual([ + "local", + "debug", + ]); + }); + + test("should leave upload.exclude undefined when not provided", () => { + const config = parseConfig({ + bundles: { web: "src/**/*" }, + upload: { + provider: "gdrive", + clientId: "id", + clientSecret: "secret", + }, + }); + + expect((config.upload as UploadConfig).exclude).toBeUndefined(); + }); }); describe("path expansion", () => { @@ -281,6 +372,11 @@ describe("type inference", () => { }>().toMatchTypeOf(); }); + test("Config has required root after parsing", () => { + expectTypeOf().toHaveProperty("root"); + expectTypeOf().toEqualTypeOf(); + }); + test("Config has required outDir after parsing", () => { expectTypeOf().toHaveProperty("outDir"); expectTypeOf().toEqualTypeOf(); @@ -300,6 +396,18 @@ describe("type inference", () => { >(); }); + test("Config has optional emptyOutDir property", () => { + expectTypeOf().toHaveProperty("emptyOutDir"); + expectTypeOf().toEqualTypeOf(); + }); + + test("UploadConfig has optional exclude property", () => { + expectTypeOf().toHaveProperty("exclude"); + expectTypeOf().toEqualTypeOf< + string[] | undefined + >(); + }); + test("BundleConfig accepts string pattern", () => { expectTypeOf<"src/**/*">().toMatchTypeOf(); }); From 1f1bde8cc28df99d35ad83d4d4abef2ba062e9ce Mon Sep 17 00:00:00 2001 From: Konstantin Tarkus Date: Sat, 24 Jan 2026 10:13:27 +0100 Subject: [PATCH 2/2] test: skip manual tests when credentials missing --- src/bundle.ts | 8 ++++++-- src/config.ts | 4 +--- .../fixtures/sample-project/prompts/review.md | 1 + tests/manual/login.test.ts | 20 +++++-------------- tests/manual/upload.test.ts | 20 ++++++++----------- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index 0a2e0d9..94857a4 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -318,13 +318,17 @@ export async function createBundle( ? indexBlock : indexBlock + "\n\n" + contentParts.join("\n"); - const content = prompt ? `${prompt}\n\n---\n\n${bundleContent}` : bundleContent; + const content = prompt + ? `${prompt}\n\n---\n\n${bundleContent}` + : bundleContent; return { content, index }; } // No index: just join file content const bundleContent = contentParts.join("\n"); - const content = prompt ? `${prompt}\n\n---\n\n${bundleContent}` : bundleContent; + const content = prompt + ? `${prompt}\n\n---\n\n${bundleContent}` + : bundleContent; // Adjust line numbers for prompt offset (no index case) if (promptLines > 0) { diff --git a/src/config.ts b/src/config.ts index b81c8f2..097d5be 100644 --- a/src/config.ts +++ b/src/config.ts @@ -112,9 +112,7 @@ export function parseConfig(value: unknown): Config { const config = result.data; // Resolve root: absolute path, relative to CWD, or CWD if empty/unset - config.root = config.root - ? resolve(expandPath(config.root)) - : process.cwd(); + config.root = config.root ? resolve(expandPath(config.root)) : process.cwd(); config.outDir = expandPath(config.outDir); for (const bundle of Object.values(config.bundles)) { diff --git a/tests/fixtures/sample-project/prompts/review.md b/tests/fixtures/sample-project/prompts/review.md index f1217df..423826d 100644 --- a/tests/fixtures/sample-project/prompts/review.md +++ b/tests/fixtures/sample-project/prompts/review.md @@ -1,3 +1,4 @@ Review this code for: + - Security issues - Performance problems diff --git a/tests/manual/login.test.ts b/tests/manual/login.test.ts index eb67c7e..823be53 100644 --- a/tests/manual/login.test.ts +++ b/tests/manual/login.test.ts @@ -42,22 +42,12 @@ async function runCli( }; } -describe("real login flow", () => { - beforeAll(async () => { - // Validate required env vars - if (!process.env.GDRIVE_CLIENT_ID) { - throw new Error( - "GDRIVE_CLIENT_ID required. Run with:\n" + - "GDRIVE_CLIENT_ID=xxx GDRIVE_CLIENT_SECRET=xxx bun test:login", - ); - } - if (!process.env.GDRIVE_CLIENT_SECRET) { - throw new Error( - "GDRIVE_CLIENT_SECRET required. Run with:\n" + - "GDRIVE_CLIENT_ID=xxx GDRIVE_CLIENT_SECRET=xxx bun test:login", - ); - } +const hasCredentials = !!( + process.env.GDRIVE_CLIENT_ID && process.env.GDRIVE_CLIENT_SECRET +); +describe.skipIf(!hasCredentials)("real login flow", () => { + beforeAll(async () => { await mkdir(TEST_OUTPUT_DIR, { recursive: true }); }); diff --git a/tests/manual/upload.test.ts b/tests/manual/upload.test.ts index 536ae4d..a0ba489 100644 --- a/tests/manual/upload.test.ts +++ b/tests/manual/upload.test.ts @@ -1,31 +1,27 @@ import { existsSync } from "node:fs"; -import { readFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; import { describe, expect, test } from "bun:test"; -import { uploadFile, type UploadResult } from "../../src/gdrive.ts"; +import { uploadFile } from "../../src/gdrive.ts"; import type { UploadConfig } from "../../src/config.ts"; const FIXTURE_DIR = join(import.meta.dir, "fixtures/gdrive-project"); const CREDENTIALS_PATH = join(homedir(), ".config/srcpack/credentials.json"); -function getUploadConfig(): UploadConfig { - if (!process.env.GDRIVE_CLIENT_ID || !process.env.GDRIVE_CLIENT_SECRET) { - throw new Error( - "Required env vars missing. Run with:\n" + - "bun test:upload (requires .env.local with GDRIVE_CLIENT_ID and GDRIVE_CLIENT_SECRET)", - ); - } +const hasCredentials = !!( + process.env.GDRIVE_CLIENT_ID && process.env.GDRIVE_CLIENT_SECRET +); +function getUploadConfig(): UploadConfig { return { provider: "gdrive", - clientId: process.env.GDRIVE_CLIENT_ID, - clientSecret: process.env.GDRIVE_CLIENT_SECRET, + clientId: process.env.GDRIVE_CLIENT_ID!, + clientSecret: process.env.GDRIVE_CLIENT_SECRET!, folderId: process.env.GDRIVE_FOLDER_ID, }; } -describe("gdrive upload", () => { +describe.skipIf(!hasCredentials)("gdrive upload", () => { test("should upload a file to Google Drive", async () => { const config = getUploadConfig(); const filePath = join(FIXTURE_DIR, ".srcpack/main.txt");