Add postinstall optimizer with native binlink support and script skipping (#24283)

## Summary

This PR introduces a new postinstall optimization system that
significantly reduces the need to run lifecycle scripts for certain
packages by intelligently handling their requirements at install time.

## Key Features

### 1. Native Binlink Optimization

When packages like `esbuild` ship platform-specific binaries as optional
dependencies, we now:
- Detect the native binlink pattern (enabled by default for `esbuild`)
- Find the matching platform-specific dependency based on target CPU/OS
- Link binaries directly from the platform-specific package (e.g.,
`@esbuild/darwin-arm64`)
- Fall back gracefully if the platform-specific package isn't found

**Result**: No postinstall scripts needed for esbuild and similar
packages.

### 2. Lifecycle Script Skipping

For packages like `sharp` that run heavy postinstall scripts:
- Skip lifecycle scripts entirely (enabled by default for `sharp`)
- Prevents downloading large binaries or compiling native code
unnecessarily
- Reduces install time and potential failures in restricted environments

## Configuration

Both features can be configured via `package.json`:

```json
{
  "nativeDependencies": ["esbuild", "my-custom-package"],
  "ignoreScripts": ["sharp", "another-package"]
}
```

Set to empty arrays to disable defaults:
```json
{
  "nativeDependencies": [],
  "ignoreScripts": []
}
```

Environment variable overrides:
- `BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER=1` - disable native
binlink
- `BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1` - disable script ignoring

## Implementation Details

### Core Components

- **`postinstall_optimizer.zig`**: New file containing the optimizer
logic
- `PostinstallOptimizer` enum with `native_binlink` and `ignore`
variants
  - `List` type to track optimization strategies per package hash
  - Defaults for `esbuild` (native binlink) and `sharp` (ignore)
  
- **`Bin.Linker` changes**: Extended to support separate target paths
  - `target_node_modules_path`: Where to find the actual binary
  - `target_package_name`: Name of the package containing the binary
  - Fallback logic when native binlink optimization fails

### Modified Components

- **PackageInstaller.zig**: Checks optimizer before:
  - Enqueueing lifecycle scripts
  - Linking binaries (with platform-specific package resolution)
  
- **isolated_install/Installer.zig**: Similar checks for isolated linker
mode
  - `maybeReplaceNodeModulesPath()` resolves platform-specific packages
  - Retry logic without optimization on failure

- **Lockfile**: Added `postinstall_optimizer` field to persist
configuration

## Changes Included

- Updated `esbuild` from 0.21.5 to 0.25.11 (testing with latest)
- VS Code launch config updates for debugging install with new flags
- New feature flags in `env_var.zig`

## Test Plan

- [x] Existing install tests pass
- [ ] Test esbuild install without postinstall scripts running
- [ ] Test sharp install with scripts skipped
- [ ] Test custom package.json configuration
- [ ] Test fallback when platform-specific package not found
- [ ] Test feature flag overrides

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Native binlink optimization: installs platform-specific binaries when
available, with a safe retry fallback and verbose logging option.
* Per-package postinstall controls to optionally skip lifecycle scripts.
* New feature flags to disable native binlink optimization and to
disable lifecycle-script ignoring.

* **Tests**
* End-to-end tests and test packages added to validate native binlink
behavior across install scenarios and linker modes.

* **Documentation**
  * Bench README and sample app migrated to a Next.js-based setup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
This commit is contained in:
Jarred Sumner
2025-11-03 20:36:22 -08:00
committed by GitHub
parent 7197fb1f04
commit 528620e9ae
31 changed files with 1454 additions and 360 deletions

View File

@@ -1,40 +1,29 @@
# `install` benchmark # Create T3 App
Requires [`hyperfine`](https://github.com/sharkdp/hyperfine). The goal of this benchmark is to compare installation performance of Bun with other package managers _when caches are hot_. This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
### With lockfile, online mode ## What's next? How do I make an app with this?
To run the benchmark with the standard "install" command for each package manager: We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
```sh If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
$ hyperfine --prepare 'rm -rf node_modules' --warmup 1 --runs 3 'bun install' 'pnpm install' 'yarn' 'npm install'
```
### With lockfile, offline mode - [Next.js](https://nextjs.org)
- [NextAuth.js](https://next-auth.js.org)
- [Prisma](https://prisma.io)
- [Drizzle](https://orm.drizzle.team)
- [Tailwind CSS](https://tailwindcss.com)
- [tRPC](https://trpc.io)
Even though all packages are cached, some tools may hit the npm API during the version resolution step. (This is not the same as re-downloading a package.) To entirely avoid network calls, the other package managers require `--prefer-offline/--offline` flag. To run the benchmark using "offline" mode: ## Learn More
```sh To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
$ hyperfine --prepare 'rm -rf node_modules' --runs 1 'bun install' 'pnpm install --prefer-offline' 'yarn --offline' 'npm install --prefer-offline'
```
### Without lockfile, offline mode - [Documentation](https://create.t3.gg/)
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
To run the benchmark with offline mode but without lockfiles: You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
```sh ## How do I deploy this?
$ hyperfine --prepare 'rm -rf node_modules' --warmup 1 'rm bun.lock && bun install' 'rm pnpm-lock.yaml && pnpm install --prefer-offline' 'rm yarn.lock && yarn --offline' 'rm package-lock.json && npm install --prefer-offline'
```
## Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.
To check that the app is working as expected:
```
$ bun run dev
$ npm run dev
$ yarn dev
$ pnpm dev
```
Then visit [http://localhost:3000](http://localhost:3000).

View File

@@ -1,18 +0,0 @@
/**
* By default, Remix will handle hydrating your app on the client for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/docs/en/main/file-conventions/entry.client
*/
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>,
);
});

View File

@@ -1,101 +0,0 @@
/**
* By default, Remix will handle generating the HTTP Response for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/docs/en/main/file-conventions/entry.server
*/
import type { EntryContext } from "@remix-run/node";
import { Response } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import isbot from "isbot";
import { PassThrough } from "node:stream";
import { renderToPipeableStream } from "react-dom/server";
const ABORT_DELAY = 5_000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
return isbot(request.headers.get("user-agent"))
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
}
function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
return new Promise((resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
{
onAllReady() {
const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
}),
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
console.error(error);
},
},
);
setTimeout(abort, ABORT_DELAY);
});
}
function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
return new Promise((resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
{
onShellReady() {
const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
}),
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
console.error(error);
responseStatusCode = 500;
},
},
);
setTimeout(abort, ABORT_DELAY);
});
}

View File

@@ -1,20 +0,0 @@
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}

View File

@@ -1,30 +0,0 @@
import type { V2_MetaFunction } from "@remix-run/node";
export const meta: V2_MetaFunction = () => {
return [{ title: "New Remix App" }];
};
export default function Index() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
<h1>Welcome to Remix</h1>
<ul>
<li>
<a target="_blank" href="https://remix.run/tutorials/blog" rel="noreferrer">
15m Quickstart Blog Tutorial
</a>
</li>
<li>
<a target="_blank" href="https://remix.run/tutorials/jokes" rel="noreferrer">
Deep Dive Jokes App Tutorial
</a>
</li>
<li>
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
Remix Docs
</a>
</li>
</ul>
</div>
);
}

488
bench/install/bun.lock Normal file
View File

@@ -0,0 +1,488 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "installbench",
"dependencies": {
"@auth/drizzle-adapter": "^1.7.2",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.69.0",
"@trpc/client": "^11.0.0",
"@trpc/react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"postgres": "^3.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.24.2",
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/postcss": "^4.0.15",
"@types/node": "^20.14.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"drizzle-kit": "^0.30.5",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",
"typescript": "^5.8.2",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@auth/core": ["@auth/core@0.41.1", "", { "dependencies": { "@panva/hkdf": "1.2.1", "jose": "6.1.0", "oauth4webapi": "3.8.2", "preact": "10.24.3", "preact-render-to-string": "6.5.11" } }, "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw=="],
"@auth/drizzle-adapter": ["@auth/drizzle-adapter@1.11.1", "", { "dependencies": { "@auth/core": "0.41.1" } }, "sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA=="],
"@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "0.18.20", "source-map-support": "0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "3.3.2", "get-tsconfig": "4.13.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "1.6.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="],
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
"@t3-oss/env-core": ["@t3-oss/env-core@0.12.0", "", { "optionalDependencies": { "typescript": "5.9.3", "zod": "3.25.76" } }, "sha512-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw=="],
"@t3-oss/env-nextjs": ["@t3-oss/env-nextjs@0.12.0", "", { "dependencies": { "@t3-oss/env-core": "0.12.0" }, "optionalDependencies": { "typescript": "5.9.3", "zod": "3.25.76" } }, "sha512-rFnvYk1049RnNVUPvY8iQ55AuQh1Rr+qZzQBh3t++RttCGK4COpXGNxS4+45afuQq02lu+QAOy/5955aU8hRKw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "enhanced-resolve": "5.18.3", "jiti": "2.6.1", "lightningcss": "1.30.2", "magic-string": "0.30.21", "source-map-js": "1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.16", "", { "dependencies": { "@alloc/quick-lru": "5.2.0", "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "postcss": "8.5.6", "tailwindcss": "4.1.16" } }, "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.5", "", {}, "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.5", "", { "dependencies": { "@tanstack/query-core": "5.90.5" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q=="],
"@trpc/client": ["@trpc/client@11.7.1", "", { "peerDependencies": { "@trpc/server": "11.7.1", "typescript": "5.9.3" } }, "sha512-uOnAjElKI892/U6aQMcBHYs3x7mme3Cvv1F87ytBL56rBvs7+DyK7r43zgaXKf13+GtPEI6ex5xjVUfyDW8XcQ=="],
"@trpc/react-query": ["@trpc/react-query@11.7.1", "", { "peerDependencies": { "@tanstack/react-query": "5.90.5", "@trpc/client": "11.7.1", "@trpc/server": "11.7.1", "react": "19.2.0", "react-dom": "19.2.0", "typescript": "5.9.3" } }, "sha512-dEHDjIqSTzO8nLlCbtiFBMBwhbSkK1QP7aYVo3nP3sYBna0b+iCtrPXdxVPCSopr9/aIqDTEh+dMRZa7yBgjfQ=="],
"@trpc/server": ["@trpc/server@11.7.1", "", { "peerDependencies": { "typescript": "5.9.3" } }, "sha512-N3U8LNLIP4g9C7LJ/sLkjuPHwqlvE3bnspzC4DEFVdvx2+usbn70P80E3wj5cjOTLhmhRiwJCSXhlB+MHfGeCw=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/node": ["@types/node@20.19.24", "", { "dependencies": { "undici-types": "6.21.0" } }, "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "19.2.2" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001752", "", {}, "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "5.5.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "0.10.2", "@esbuild-kit/esm-loader": "2.6.5", "esbuild": "0.19.12", "esbuild-register": "3.6.0", "gel": "2.1.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="],
"drizzle-orm": ["drizzle-orm@0.41.0", "", { "optionalDependencies": { "gel": "2.1.1", "postgres": "3.4.7" } }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="],
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "4.2.11", "tapable": "2.3.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "4.4.3" }, "peerDependencies": { "esbuild": "0.19.12" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
"gel": ["gel@2.1.1", "", { "dependencies": { "@petamoriken/float16": "3.9.3", "debug": "4.4.3", "env-paths": "3.0.0", "semver": "7.7.3", "shell-quote": "1.8.3", "which": "4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-Newg9X7mRYskoBjSw70l1YnJ/ZGbq64VPyR821H5WVkTGpHG2O0mQILxCeUhxdYERLFY9B4tUyKLyf3uMTjtKw=="],
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="],
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"jose": ["jose@6.1.0", "", {}, "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="],
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "next": "15.5.6", "react": "19.2.0" } }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],
"oauth4webapi": ["oauth4webapi@3.8.2", "", {}, "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
"preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="],
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": "10.24.3" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
"pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
"sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "1.0.0", "detect-libc": "2.1.2", "semver": "7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "1.1.2", "source-map": "0.6.1" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"superjson": ["superjson@2.2.5", "", { "dependencies": { "copy-anything": "4.0.5" } }, "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w=="],
"tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"drizzle-kit/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"next-auth/@auth/core": ["@auth/core@0.37.2", "", { "dependencies": { "@panva/hkdf": "1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "5.10.0", "oauth4webapi": "3.8.2", "preact": "10.11.3", "preact-render-to-string": "5.2.3" } }, "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
"drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
"drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="],
"drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="],
"drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="],
"drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="],
"drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="],
"drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="],
"drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="],
"drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="],
"drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="],
"drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="],
"drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="],
"drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="],
"drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="],
"drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="],
"drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="],
"drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="],
"drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="],
"drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="],
"drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="],
"drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
"drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
"next-auth/@auth/core/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"next-auth/@auth/core/preact": ["preact@10.11.3", "", {}, "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="],
"next-auth/@auth/core/preact-render-to-string": ["preact-render-to-string@5.2.3", "", { "dependencies": { "pretty-format": "3.8.0" }, "peerDependencies": { "preact": "10.11.3" } }, "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA=="],
}
}

5
bench/install/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,10 @@
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
import "./src/env.js";
/** @type {import("next").NextConfig} */
const config = {};
export default config;

View File

@@ -1,31 +1,52 @@
{ {
"name": "installbench",
"version": "0.1.0",
"private": true, "private": true,
"sideEffects": false, "type": "module",
"scripts": { "scripts": {
"build": "remix build", "build": "next build",
"dev": "remix dev", "check": "biome check .",
"start": "remix-serve build", "check:unsafe": "biome check --write --unsafe .",
"typecheck": "tsc", "check:write": "biome check --write .",
"clean": "rm -rf node_modules", "db:generate": "drizzle-kit generate",
"bench": "hyperfine --prepare 'rm -rf node_modules' --warmup 1 --runs 3 'bun install' 'pnpm install' 'yarn' 'npm install'" "db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"dev": "next dev --turbo",
"preview": "next build && next start",
"start": "next start",
"typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@remix-run/node": "^1.15.0", "@auth/drizzle-adapter": "^1.7.2",
"@remix-run/react": "^1.15.0", "@t3-oss/env-nextjs": "^0.12.0",
"@remix-run/serve": "^1.15.0", "@tanstack/react-query": "^5.69.0",
"isbot": "^3.6.5", "@trpc/client": "^11.0.0",
"react": "^18.2.0", "@trpc/react-query": "^11.0.0",
"react-dom": "^18.2.0" "@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"postgres": "^3.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.15.0", "@biomejs/biome": "1.9.4",
"@remix-run/eslint-config": "^1.15.0", "@tailwindcss/postcss": "^4.0.15",
"@types/react": "^18.0.25", "@types/node": "^20.14.10",
"@types/react-dom": "^18.0.8", "@types/react": "^19.0.0",
"eslint": "^8.27.0", "@types/react-dom": "^19.0.0",
"typescript": "^4.8.4" "drizzle-kit": "^0.30.5",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",
"typescript": "^5.8.2"
}, },
"engines": { "ct3aMetadata": {
"node": ">=14" "initVersion": "7.39.3"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,14 +0,0 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
ignoredRouteFiles: ["**/.*"],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",
future: {
v2_errorBoundary: true,
v2_meta: true,
v2_normalizeFormMethod: true,
v2_routeConvention: true,
},
};

View File

@@ -1,2 +0,0 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node" />

View File

@@ -1,22 +0,0 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
// Remix takes care of building everything in `remix build`.
"noEmit": true
}
}

View File

@@ -141,6 +141,8 @@ fn link(ctx: Command.Context) !void {
.bin = package.bin, .bin = package.bin,
.node_modules_path = &node_modules_path, .node_modules_path = &node_modules_path,
.global_bin_path = manager.options.bin_path, .global_bin_path = manager.options.bin_path,
.target_node_modules_path = &node_modules_path,
.target_package_name = strings.StringOrTinyString.init(name),
// .destination_dir_subpath = destination_dir_subpath, // .destination_dir_subpath = destination_dir_subpath,
.package_name = strings.StringOrTinyString.init(name), .package_name = strings.StringOrTinyString.init(name),

View File

@@ -101,6 +101,8 @@ fn unlink(ctx: Command.Context) !void {
defer node_modules_path.deinit(); defer node_modules_path.deinit();
var bin_linker = Bin.Linker{ var bin_linker = Bin.Linker{
.target_node_modules_path = &node_modules_path,
.target_package_name = strings.StringOrTinyString.init(name),
.bin = package.bin, .bin = package.bin,
.node_modules_path = &node_modules_path, .node_modules_path = &node_modules_path,
.global_bin_path = manager.options.bin_path, .global_bin_path = manager.options.bin_path,

View File

@@ -137,6 +137,13 @@ pub const feature_flag = struct {
pub const BUN_BE_BUN = newFeatureFlag("BUN_BE_BUN", .{}); pub const BUN_BE_BUN = newFeatureFlag("BUN_BE_BUN", .{});
pub const BUN_DEBUG_NO_DUMP = newFeatureFlag("BUN_DEBUG_NO_DUMP", .{}); pub const BUN_DEBUG_NO_DUMP = newFeatureFlag("BUN_DEBUG_NO_DUMP", .{});
pub const BUN_DESTRUCT_VM_ON_EXIT = newFeatureFlag("BUN_DESTRUCT_VM_ON_EXIT", .{}); pub const BUN_DESTRUCT_VM_ON_EXIT = newFeatureFlag("BUN_DESTRUCT_VM_ON_EXIT", .{});
/// Disable "nativeDependencies"
pub const BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER", .{});
/// Disable "ignoreScripts" in package.json
pub const BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS", .{});
pub const BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG", .{}); pub const BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG", .{});
pub const BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER", .{}); pub const BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER", .{});
pub const BUN_FEATURE_FLAG_DISABLE_DNS_CACHE = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_DNS_CACHE", .{}); pub const BUN_FEATURE_FLAG_DISABLE_DNS_CACHE = newFeatureFlag("BUN_FEATURE_FLAG_DISABLE_DNS_CACHE", .{});

View File

@@ -256,10 +256,18 @@ pub const PackageInstaller = struct {
log_level: Options.LogLevel, log_level: Options.LogLevel,
) void { ) void {
const lockfile = this.lockfile; const lockfile = this.lockfile;
const manager = this.manager;
const string_buf = lockfile.buffers.string_bytes.items; const string_buf = lockfile.buffers.string_bytes.items;
var node_modules_path: bun.AbsPath(.{}) = .from(this.node_modules.path.items); var node_modules_path: bun.AbsPath(.{}) = .from(this.node_modules.path.items);
defer node_modules_path.deinit(); defer node_modules_path.deinit();
const pkgs = lockfile.packages.slice();
const pkg_name_hashes = pkgs.items(.name_hash);
const pkg_metas = pkgs.items(.meta);
const pkg_resolutions_lists = pkgs.items(.resolutions);
const pkg_resolutions_buffer = lockfile.buffers.resolutions.items;
const pkg_names = pkgs.items(.name);
while (tree.binaries.removeOrNull()) |dep_id| { while (tree.binaries.removeOrNull()) |dep_id| {
bun.assertWithLocation(dep_id < lockfile.buffers.dependencies.items.len, @src()); bun.assertWithLocation(dep_id < lockfile.buffers.dependencies.items.len, @src());
const package_id = lockfile.buffers.resolutions.items[dep_id]; const package_id = lockfile.buffers.resolutions.items[dep_id];
@@ -268,28 +276,50 @@ pub const PackageInstaller = struct {
bun.assertWithLocation(bin.tag != .none, @src()); bun.assertWithLocation(bin.tag != .none, @src());
const alias = lockfile.buffers.dependencies.items[dep_id].name.slice(string_buf); const alias = lockfile.buffers.dependencies.items[dep_id].name.slice(string_buf);
const package_name_ = strings.StringOrTinyString.init(alias);
var target_package_name = package_name_;
var can_retry_without_native_binlink_optimization = false;
var target_node_modules_path_opt: ?bun.AbsPath(.{}) = null;
defer if (target_node_modules_path_opt) |*path| path.deinit();
var bin_linker: Bin.Linker = .{ if (manager.postinstall_optimizer.isNativeBinlinkEnabled()) native_binlink_optimization: {
.bin = bin, // Check for native binlink optimization
.global_bin_path = this.options.bin_path, const name_hash = pkg_name_hashes[package_id];
.package_name = strings.StringOrTinyString.init(alias), if (manager.postinstall_optimizer.get(name_hash)) |optimizer| {
.string_buf = string_buf, switch (optimizer) {
.extern_string_buf = lockfile.buffers.extern_strings.items, .native_binlink => {
.seen = &this.seen_bin_links, const target_cpu = manager.options.cpu;
.node_modules_path = &node_modules_path, const target_os = manager.options.os;
.abs_target_buf = link_target_buf, if (PostinstallOptimizer.getNativeBinlinkReplacementPackageID(
.abs_dest_buf = link_dest_buf, pkg_resolutions_lists[package_id].get(pkg_resolutions_buffer),
.rel_buf = link_rel_buf, pkg_metas,
}; target_cpu,
target_os,
)) |replacement_pkg_id| {
if (tree_id != 0) {
// TODO: support this optimization in nested node_modules
// It's tricky to get the hoisting right.
// So we leave this out for now.
break :native_binlink_optimization;
}
const replacement_name = pkg_names[replacement_pkg_id].slice(string_buf);
target_package_name = strings.StringOrTinyString.init(replacement_name);
can_retry_without_native_binlink_optimization = true;
}
},
.ignore => {},
}
}
}
// globally linked packages shouls always belong to the root // globally linked packages shouls always belong to the root
// tree (0). // tree (0).
const global = if (!this.manager.options.global) const global = if (!manager.options.global)
false false
else if (tree_id != 0) else if (tree_id != 0)
false false
else global: { else global: {
for (this.manager.update_requests) |request| { for (manager.update_requests) |request| {
if (request.package_id == package_id) { if (request.package_id == package_id) {
break :global true; break :global true;
} }
@@ -298,21 +328,52 @@ pub const PackageInstaller = struct {
break :global false; break :global false;
}; };
bin_linker.link(global); while (true) {
var bin_linker: Bin.Linker = .{
.bin = bin,
.global_bin_path = this.options.bin_path,
.package_name = package_name_,
.target_package_name = target_package_name,
.string_buf = string_buf,
.extern_string_buf = lockfile.buffers.extern_strings.items,
.seen = &this.seen_bin_links,
.node_modules_path = &node_modules_path,
.target_node_modules_path = if (target_node_modules_path_opt) |*path| path else &node_modules_path,
.abs_target_buf = link_target_buf,
.abs_dest_buf = link_dest_buf,
.rel_buf = link_rel_buf,
};
if (bin_linker.err) |err| { bin_linker.link(global);
if (log_level != .silent) {
this.manager.log.addErrorFmtOpts( if (can_retry_without_native_binlink_optimization and (bin_linker.skipped_due_to_missing_bin or bin_linker.err != null)) {
this.manager.allocator, can_retry_without_native_binlink_optimization = false;
"Failed to link <b>{s}<r>: {s}", if (PackageManager.verbose_install) {
.{ alias, @errorName(err) }, Output.prettyErrorln("<d>[Bin Linker]<r> {s} -> {s} retrying without native bin link", .{
.{}, package_name_.slice(),
) catch |e| bun.handleOom(e); target_package_name.slice(),
});
}
target_package_name = package_name_;
continue;
} }
if (this.options.enable.fail_early) { if (bin_linker.err) |err| {
this.manager.crash(); if (log_level != .silent) {
manager.log.addErrorFmtOpts(
manager.allocator,
"Failed to link <b>{s}<r>: {s}",
.{ alias, @errorName(err) },
.{},
) catch |e| bun.handleOom(e);
}
if (this.options.enable.fail_early) {
manager.crash();
}
} }
break;
} }
} }
} }
@@ -357,6 +418,7 @@ pub const PackageInstaller = struct {
if (this.canRunScripts(tree_id)) { if (this.canRunScripts(tree_id)) {
_ = this.pending_lifecycle_scripts.swapRemove(i); _ = this.pending_lifecycle_scripts.swapRemove(i);
const output_in_foreground = false; const output_in_foreground = false;
this.manager.spawnPackageLifecycleScripts( this.manager.spawnPackageLifecycleScripts(
this.command_ctx, this.command_ctx,
entry.list, entry.list,
@@ -1100,22 +1162,40 @@ pub const PackageInstaller = struct {
defer folder_path.deinit(); defer folder_path.deinit();
folder_path.append(alias.slice(this.lockfile.buffers.string_bytes.items)); folder_path.append(alias.slice(this.lockfile.buffers.string_bytes.items));
if (this.enqueueLifecycleScripts( enqueueLifecycleScripts: {
alias.slice(this.lockfile.buffers.string_bytes.items), if (this.manager.postinstall_optimizer.shouldIgnoreLifecycleScripts(
log_level, pkg_name_hash,
&folder_path, this.lockfile.packages.items(.resolutions)[package_id].get(this.lockfile.buffers.resolutions.items),
package_id, this.lockfile.packages.items(.meta),
dep.behavior.optional, this.manager.options.cpu,
resolution, this.manager.options.os,
)) { this.current_tree_id,
if (is_trusted_through_update_request) { )) {
this.manager.trusted_deps_to_add_to_package_json.append( if (PackageManager.verbose_install) {
this.manager.allocator, Output.prettyErrorln("<d>[Lifecycle Scripts]<r> ignoring {s} lifecycle scripts", .{
bun.handleOom(this.manager.allocator.dupe(u8, alias.slice(this.lockfile.buffers.string_bytes.items))), pkg_name.slice(this.lockfile.buffers.string_bytes.items),
) catch |err| bun.handleOom(err); });
}
break :enqueueLifecycleScripts;
}
if (this.lockfile.trusted_dependencies == null) this.lockfile.trusted_dependencies = .{}; if (this.enqueueLifecycleScripts(
this.lockfile.trusted_dependencies.?.put(this.manager.allocator, truncated_dep_name_hash, {}) catch |err| bun.handleOom(err); alias.slice(this.lockfile.buffers.string_bytes.items),
log_level,
&folder_path,
package_id,
dep.behavior.optional,
resolution,
)) {
if (is_trusted_through_update_request) {
this.manager.trusted_deps_to_add_to_package_json.append(
this.manager.allocator,
bun.handleOom(this.manager.allocator.dupe(u8, alias.slice(this.lockfile.buffers.string_bytes.items))),
) catch |err| bun.handleOom(err);
if (this.lockfile.trusted_dependencies == null) this.lockfile.trusted_dependencies = .{};
this.lockfile.trusted_dependencies.?.put(this.manager.allocator, truncated_dep_name_hash, {}) catch |err| bun.handleOom(err);
}
} }
} }
} }
@@ -1272,24 +1352,42 @@ pub const PackageInstaller = struct {
defer folder_path.deinit(); defer folder_path.deinit();
folder_path.append(alias.slice(this.lockfile.buffers.string_bytes.items)); folder_path.append(alias.slice(this.lockfile.buffers.string_bytes.items));
if (this.enqueueLifecycleScripts( enqueueLifecycleScripts: {
alias.slice(this.lockfile.buffers.string_bytes.items), if (this.manager.postinstall_optimizer.shouldIgnoreLifecycleScripts(
log_level, pkg_name_hash,
&folder_path, this.lockfile.packages.items(.resolutions)[package_id].get(this.lockfile.buffers.resolutions.items),
package_id, this.lockfile.packages.items(.meta),
dep.behavior.optional, this.manager.options.cpu,
resolution, this.manager.options.os,
)) { this.current_tree_id,
if (is_trusted_through_update_request) { )) {
this.manager.trusted_deps_to_add_to_package_json.append( if (PackageManager.verbose_install) {
this.manager.allocator, Output.prettyErrorln("<d>[Lifecycle Scripts]<r> ignoring {s} lifecycle scripts", .{
bun.handleOom(this.manager.allocator.dupe(u8, alias.slice(this.lockfile.buffers.string_bytes.items))), pkg_name.slice(this.lockfile.buffers.string_bytes.items),
) catch |err| bun.handleOom(err); });
}
break :enqueueLifecycleScripts;
} }
if (add_to_lockfile) { if (this.enqueueLifecycleScripts(
if (this.lockfile.trusted_dependencies == null) this.lockfile.trusted_dependencies = .{}; alias.slice(this.lockfile.buffers.string_bytes.items),
this.lockfile.trusted_dependencies.?.put(this.manager.allocator, truncated_dep_name_hash, {}) catch |err| bun.handleOom(err); log_level,
&folder_path,
package_id,
dep.behavior.optional,
resolution,
)) {
if (is_trusted_through_update_request) {
this.manager.trusted_deps_to_add_to_package_json.append(
this.manager.allocator,
bun.handleOom(this.manager.allocator.dupe(u8, alias.slice(this.lockfile.buffers.string_bytes.items))),
) catch |err| bun.handleOom(err);
}
if (add_to_lockfile) {
if (this.lockfile.trusted_dependencies == null) this.lockfile.trusted_dependencies = .{};
this.lockfile.trusted_dependencies.?.put(this.manager.allocator, truncated_dep_name_hash, {}) catch |err| bun.handleOom(err);
}
} }
} }
} }
@@ -1424,6 +1522,7 @@ const PackageID = install.PackageID;
const PackageInstall = install.PackageInstall; const PackageInstall = install.PackageInstall;
const PackageNameHash = install.PackageNameHash; const PackageNameHash = install.PackageNameHash;
const PatchTask = install.PatchTask; const PatchTask = install.PatchTask;
const PostinstallOptimizer = install.PostinstallOptimizer;
const Resolution = install.Resolution; const Resolution = install.Resolution;
const Task = install.Task; const Task = install.Task;
const TaskCallbackContext = install.TaskCallbackContext; const TaskCallbackContext = install.TaskCallbackContext;

View File

@@ -89,6 +89,7 @@ lockfile: *Lockfile = undefined,
options: Options, options: Options,
preinstall_state: std.ArrayListUnmanaged(PreinstallState) = .{}, preinstall_state: std.ArrayListUnmanaged(PreinstallState) = .{},
postinstall_optimizer: PostinstallOptimizer.List = .{},
global_link_dir: ?std.fs.Dir = null, global_link_dir: ?std.fs.Dir = null,
global_dir: ?std.fs.Dir = null, global_dir: ?std.fs.Dir = null,
@@ -1314,6 +1315,7 @@ const PackageManifestMap = bun.install.PackageManifestMap;
const PackageNameAndVersionHash = bun.install.PackageNameAndVersionHash; const PackageNameAndVersionHash = bun.install.PackageNameAndVersionHash;
const PackageNameHash = bun.install.PackageNameHash; const PackageNameHash = bun.install.PackageNameHash;
const PatchTask = bun.install.PatchTask; const PatchTask = bun.install.PatchTask;
const PostinstallOptimizer = bun.install.PostinstallOptimizer;
const PreinstallState = bun.install.PreinstallState; const PreinstallState = bun.install.PreinstallState;
const Task = bun.install.Task; const Task = bun.install.Task;
const TaskCallbackContext = bun.install.TaskCallbackContext; const TaskCallbackContext = bun.install.TaskCallbackContext;

View File

@@ -504,6 +504,14 @@ pub const Bin = extern struct {
pub const Linker = struct { pub const Linker = struct {
bin: Bin, bin: Bin,
/// Usually will be the same as `node_modules_path`.
/// Used to support native bin linking.
target_node_modules_path: *bun.AbsPath(.{}),
/// Usually will be the same as `package_name`.
/// Used to support native bin linking.
target_package_name: strings.StringOrTinyString,
// Hash map of seen destination paths for this `node_modules/.bin` folder. PackageInstaller will reset it before // Hash map of seen destination paths for this `node_modules/.bin` folder. PackageInstaller will reset it before
// linking each tree. // linking each tree.
seen: ?*bun.StringHashMap(void), seen: ?*bun.StringHashMap(void),
@@ -523,6 +531,7 @@ pub const Bin = extern struct {
rel_buf: []u8, rel_buf: []u8,
err: ?anyerror = null, err: ?anyerror = null,
skipped_due_to_missing_bin: bool = false,
pub var umask: bun.Mode = 0; pub var umask: bun.Mode = 0;
@@ -570,6 +579,7 @@ pub const Bin = extern struct {
// Skip if the target does not exist. This is important because placing a dangling // Skip if the target does not exist. This is important because placing a dangling
// shim in path might break a postinstall // shim in path might break a postinstall
if (!bun.sys.exists(abs_target)) { if (!bun.sys.exists(abs_target)) {
this.skipped_due_to_missing_bin = true;
return; return;
} }
@@ -838,7 +848,7 @@ pub const Bin = extern struct {
/// uses `this.abs_target_buf` /// uses `this.abs_target_buf`
pub fn buildTargetPackageDir(this: *const Linker) []const u8 { pub fn buildTargetPackageDir(this: *const Linker) []const u8 {
const dest_dir_without_trailing_slash = strings.withoutTrailingSlash(this.node_modules_path.slice()); const dest_dir_without_trailing_slash = strings.withoutTrailingSlash(this.target_node_modules_path.slice());
var remain = this.abs_target_buf; var remain = this.abs_target_buf;
@@ -847,7 +857,7 @@ pub const Bin = extern struct {
remain[0] = std.fs.path.sep; remain[0] = std.fs.path.sep;
remain = remain[1..]; remain = remain[1..];
const package_name = this.package_name.slice(); const package_name = this.target_package_name.slice();
@memcpy(remain[0..package_name.len], package_name); @memcpy(remain[0..package_name.len], package_name);
remain = remain[package_name.len..]; remain = remain[package_name.len..];
remain[0] = std.fs.path.sep; remain[0] = std.fs.path.sep;

View File

@@ -257,6 +257,7 @@ pub const Resolution = @import("./resolution.zig").Resolution;
pub const Store = @import("./isolated_install/Store.zig").Store; pub const Store = @import("./isolated_install/Store.zig").Store;
pub const FileCopier = @import("./isolated_install/FileCopier.zig").FileCopier; pub const FileCopier = @import("./isolated_install/FileCopier.zig").FileCopier;
pub const PnpmMatcher = @import("./PnpmMatcher.zig"); pub const PnpmMatcher = @import("./PnpmMatcher.zig");
pub const PostinstallOptimizer = @import("./postinstall_optimizer.zig").PostinstallOptimizer;
pub const ArrayIdentityContext = @import("../identity_context.zig").ArrayIdentityContext; pub const ArrayIdentityContext = @import("../identity_context.zig").ArrayIdentityContext;
pub const IdentityContext = @import("../identity_context.zig").IdentityContext; pub const IdentityContext = @import("../identity_context.zig").IdentityContext;

View File

@@ -435,6 +435,8 @@ pub const Installer = struct {
const pkg_names = pkgs.items(.name); const pkg_names = pkgs.items(.name);
const pkg_name_hashes = pkgs.items(.name_hash); const pkg_name_hashes = pkgs.items(.name_hash);
const pkg_resolutions = pkgs.items(.resolution); const pkg_resolutions = pkgs.items(.resolution);
const pkg_resolutions_lists = pkgs.items(.resolutions);
const pkg_metas: []const Lockfile.Package.Meta = pkgs.items(.meta);
const pkg_bins = pkgs.items(.bin); const pkg_bins = pkgs.items(.bin);
const pkg_script_lists = pkgs.items(.scripts); const pkg_script_lists = pkgs.items(.scripts);
@@ -925,8 +927,18 @@ pub const Installer = struct {
installer.appendStorePath(&pkg_cwd, this.entry_id); installer.appendStorePath(&pkg_cwd, this.entry_id);
if (pkg_res.tag != .root and (pkg_res.tag == .workspace or is_trusted)) { if (pkg_res.tag != .root and (pkg_res.tag == .workspace or is_trusted)) enqueue_lifecycle_scripts: {
var pkg_scripts: Package.Scripts = pkg_script_lists[pkg_id]; var pkg_scripts: Package.Scripts = pkg_script_lists[pkg_id];
if (is_trusted and manager.postinstall_optimizer.shouldIgnoreLifecycleScripts(
pkg_name_hashes[pkg_id],
installer.lockfile.buffers.resolutions.items,
pkg_metas,
manager.options.cpu,
manager.options.os,
null,
)) {
break :enqueue_lifecycle_scripts;
}
var log = bun.logger.Log.init(bun.default_allocator); var log = bun.logger.Log.init(bun.default_allocator);
defer log.deinit(); defer log.deinit();
@@ -999,16 +1011,39 @@ pub const Installer = struct {
var node_modules_path: bun.AbsPath(.{}) = .initTopLevelDir(); var node_modules_path: bun.AbsPath(.{}) = .initTopLevelDir();
defer node_modules_path.deinit(); defer node_modules_path.deinit();
installer.appendStoreNodeModulesPath(&node_modules_path, this.entry_id); installer.appendStoreNodeModulesPath(&node_modules_path, this.entry_id);
var target_node_modules_path: ?bun.AbsPath(.{}) = null;
defer if (target_node_modules_path) |*path| path.deinit();
var target_package_name: strings.StringOrTinyString = strings.StringOrTinyString.init(dep_name);
if (installer.maybeReplaceNodeModulesPath(
entry_node_ids,
node_pkg_ids,
pkg_name_hashes,
pkg_resolutions_lists,
installer.lockfile.buffers.resolutions.items,
installer.lockfile.packages.items(.meta),
pkg_id,
)) |replacement_entry_id| {
target_node_modules_path = bun.AbsPath(.{}).initTopLevelDir();
installer.appendStoreNodeModulesPath(&target_node_modules_path.?, replacement_entry_id);
const replacement_node_id = entry_node_ids[replacement_entry_id.get()];
const replacement_pkg_id = node_pkg_ids[replacement_node_id.get()];
target_package_name = strings.StringOrTinyString.init(installer.lockfile.str(&pkg_names[replacement_pkg_id]));
}
var bin_linker: Bin.Linker = .{ var bin_linker: Bin.Linker = .{
.bin = bin, .bin = bin,
.global_bin_path = installer.manager.options.bin_path, .global_bin_path = installer.manager.options.bin_path,
.package_name = strings.StringOrTinyString.init(dep_name), .package_name = strings.StringOrTinyString.init(dep_name),
.target_package_name = target_package_name,
.string_buf = string_buf, .string_buf = string_buf,
.extern_string_buf = installer.lockfile.buffers.extern_strings.items, .extern_string_buf = installer.lockfile.buffers.extern_strings.items,
.seen = &seen, .seen = &seen,
.target_node_modules_path = if (target_node_modules_path) |*path| path else &node_modules_path,
.node_modules_path = &node_modules_path, .node_modules_path = &node_modules_path,
.abs_target_buf = abs_target_buf, .abs_target_buf = abs_target_buf,
.abs_dest_buf = abs_dest_buf, .abs_dest_buf = abs_dest_buf,
@@ -1017,6 +1052,23 @@ pub const Installer = struct {
bin_linker.link(false); bin_linker.link(false);
if (target_node_modules_path != null and (bin_linker.skipped_due_to_missing_bin or bin_linker.err != null)) {
target_node_modules_path.?.deinit();
target_node_modules_path = null;
bin_linker.target_node_modules_path = &node_modules_path;
bin_linker.target_package_name = strings.StringOrTinyString.init(dep_name);
if (this.installer.manager.options.log_level.isVerbose()) {
Output.prettyErrorln("<d>[Bin Linker]<r> {s} -> {s} retrying without native bin link", .{
dep_name,
bin_linker.target_package_name.slice(),
});
}
bin_linker.link(false);
}
if (bin_linker.err) |err| { if (bin_linker.err) |err| {
return .failure(.{ .binaries = err }); return .failure(.{ .binaries = err });
} }
@@ -1243,6 +1295,49 @@ pub const Installer = struct {
_ = symlinker.ensureSymlink(link_strategy); _ = symlinker.ensureSymlink(link_strategy);
} }
fn maybeReplaceNodeModulesPath(
this: *const Installer,
entry_node_ids: []const Store.Node.Id,
node_pkg_ids: []const PackageID,
name_hashes: []const PackageNameHash,
pkg_resolutions_lists: []const Lockfile.PackageIDSlice,
pkg_resolutions_buffer: []const PackageID,
pkg_metas: []const Package.Meta,
pkg_id: PackageID,
) ?Store.Entry.Id {
const postinstall_optimizer = &this.manager.postinstall_optimizer;
if (!postinstall_optimizer.isNativeBinlinkEnabled()) {
return null;
}
const name_hash = name_hashes[pkg_id];
if (postinstall_optimizer.get(name_hash)) |optimizer| {
switch (optimizer) {
.native_binlink => {
const manager = this.manager;
const target_cpu = manager.options.cpu;
const target_os = manager.options.os;
if (PostinstallOptimizer.getNativeBinlinkReplacementPackageID(
pkg_resolutions_lists[pkg_id].get(pkg_resolutions_buffer),
pkg_metas,
target_cpu,
target_os,
)) |replacement_pkg_id| {
for (entry_node_ids, 0..) |new_node_id, new_entry_id| {
if (node_pkg_ids[new_node_id.get()] == replacement_pkg_id) {
debug("native bin link {d} -> {d}", .{ pkg_id, replacement_pkg_id });
return .from(@intCast(new_entry_id));
}
}
}
},
.ignore => {},
}
}
return null;
}
pub fn linkDependencyBins(this: *const Installer, parent_entry_id: Store.Entry.Id) !void { pub fn linkDependencyBins(this: *const Installer, parent_entry_id: Store.Entry.Id) !void {
const lockfile = this.lockfile; const lockfile = this.lockfile;
const store = this.store; const store = this.store;
@@ -1251,7 +1346,7 @@ pub const Installer = struct {
const extern_string_buf = lockfile.buffers.extern_strings.items; const extern_string_buf = lockfile.buffers.extern_strings.items;
const entries = store.entries.slice(); const entries = store.entries.slice();
const entry_node_ids = entries.items(.node_id); const entry_node_ids: []const Store.Node.Id = entries.items(.node_id);
const entry_deps = entries.items(.dependencies); const entry_deps = entries.items(.dependencies);
const nodes = store.nodes.slice(); const nodes = store.nodes.slice();
@@ -1259,6 +1354,10 @@ pub const Installer = struct {
const node_dep_ids = nodes.items(.dep_id); const node_dep_ids = nodes.items(.dep_id);
const pkgs = lockfile.packages.slice(); const pkgs = lockfile.packages.slice();
const pkg_name_hashes = pkgs.items(.name_hash);
const pkg_metas = pkgs.items(.meta);
const pkg_resolutions_lists = pkgs.items(.resolutions);
const pkg_resolutions_buffer = lockfile.buffers.resolutions.items;
const pkg_bins = pkgs.items(.bin); const pkg_bins = pkgs.items(.bin);
const link_target_buf = bun.path_buffer_pool.get(); const link_target_buf = bun.path_buffer_pool.get();
@@ -1284,17 +1383,42 @@ pub const Installer = struct {
if (bin.tag == .none) { if (bin.tag == .none) {
continue; continue;
} }
const alias = lockfile.buffers.dependencies.items[dep_id].name; const alias = lockfile.buffers.dependencies.items[dep_id].name;
var target_node_modules_path: ?bun.AbsPath(.{}) = null;
defer if (target_node_modules_path) |*path| path.deinit();
const package_name = strings.StringOrTinyString.init(alias.slice(string_buf));
var target_package_name = package_name;
if (this.maybeReplaceNodeModulesPath(
entry_node_ids,
node_pkg_ids,
pkg_name_hashes,
pkg_resolutions_lists,
pkg_resolutions_buffer,
pkg_metas,
pkg_id,
)) |replacement_entry_id| {
target_node_modules_path = bun.AbsPath(.{}).initTopLevelDir();
this.appendStoreNodeModulesPath(&target_node_modules_path.?, replacement_entry_id);
const replacement_node_id = entry_node_ids[replacement_entry_id.get()];
const replacement_pkg_id = node_pkg_ids[replacement_node_id.get()];
const pkg_names = pkgs.items(.name);
target_package_name = strings.StringOrTinyString.init(this.lockfile.str(&pkg_names[replacement_pkg_id]));
}
var bin_linker: Bin.Linker = .{ var bin_linker: Bin.Linker = .{
.bin = bin, .bin = bin,
.global_bin_path = this.manager.options.bin_path, .global_bin_path = this.manager.options.bin_path,
.package_name = strings.StringOrTinyString.init(alias.slice(string_buf)), .package_name = package_name,
.string_buf = string_buf, .string_buf = string_buf,
.extern_string_buf = extern_string_buf, .extern_string_buf = extern_string_buf,
.seen = &seen, .seen = &seen,
.node_modules_path = &node_modules_path, .node_modules_path = &node_modules_path,
.target_node_modules_path = if (target_node_modules_path) |*path| path else &node_modules_path,
.target_package_name = if (target_node_modules_path != null) target_package_name else package_name,
.abs_target_buf = link_target_buf, .abs_target_buf = link_target_buf,
.abs_dest_buf = link_dest_buf, .abs_dest_buf = link_dest_buf,
.rel_buf = link_rel_buf, .rel_buf = link_rel_buf,
@@ -1302,6 +1426,23 @@ pub const Installer = struct {
bin_linker.link(false); bin_linker.link(false);
if (target_node_modules_path != null and (bin_linker.skipped_due_to_missing_bin or bin_linker.err != null)) {
target_node_modules_path.?.deinit();
target_node_modules_path = null;
bin_linker.target_node_modules_path = &node_modules_path;
bin_linker.target_package_name = package_name;
if (this.manager.options.log_level.isVerbose()) {
Output.prettyErrorln("<d>[Bin Linker]<r> {s} -> {s} retrying without native bin link", .{
package_name.slice(),
target_package_name.slice(),
});
}
bin_linker.link(false);
}
if (bin_linker.err) |err| { if (bin_linker.err) |err| {
return err; return err;
} }
@@ -1436,6 +1577,8 @@ pub const Installer = struct {
const string = []const u8; const string = []const u8;
const debug = Output.scoped(.IsolatedInstaller, .hidden);
const FileCloner = @import("./FileCloner.zig"); const FileCloner = @import("./FileCloner.zig");
const Hardlinker = @import("./Hardlinker.zig"); const Hardlinker = @import("./Hardlinker.zig");
const std = @import("std"); const std = @import("std");
@@ -1464,6 +1607,7 @@ const PackageID = install.PackageID;
const PackageInstall = install.PackageInstall; const PackageInstall = install.PackageInstall;
const PackageManager = install.PackageManager; const PackageManager = install.PackageManager;
const PackageNameHash = install.PackageNameHash; const PackageNameHash = install.PackageNameHash;
const PostinstallOptimizer = install.PostinstallOptimizer;
const Resolution = install.Resolution; const Resolution = install.Resolution;
const Store = install.Store; const Store = install.Store;
const TruncatedPackageNameHash = install.TruncatedPackageNameHash; const TruncatedPackageNameHash = install.TruncatedPackageNameHash;

View File

@@ -1583,6 +1583,8 @@ pub fn Package(comptime SemverIntType: type) type {
// Count catalog strings in top-level package.json as well, since parseAppend // Count catalog strings in top-level package.json as well, since parseAppend
// might process them later if no catalogs were found in workspaces // might process them later if no catalogs were found in workspaces
lockfile.catalogs.parseCount(lockfile, json, &string_builder); lockfile.catalogs.parseCount(lockfile, json, &string_builder);
try install.PostinstallOptimizer.fromPackageJSON(&pm.postinstall_optimizer, &json, allocator);
} }
try string_builder.allocate(); try string_builder.allocate();

View File

@@ -0,0 +1,170 @@
pub const PostinstallOptimizer = enum {
native_binlink,
ignore,
const default_native_binlinks_name_hashes = &[_]PackageNameHash{
bun.Semver.String.Builder.stringHash("esbuild"),
};
const default_ignore_name_hashes = &[_]PackageNameHash{
bun.Semver.String.Builder.stringHash("sharp"),
};
fn fromStringArrayGroup(list: *List, expr: *const ast.Expr, allocator: std.mem.Allocator, value: PostinstallOptimizer) !bool {
var array = expr.asArray() orelse return false;
if (array.array.items.len == 0) {
return true;
}
while (array.next()) |entry| {
if (entry.isString()) {
const str = entry.asString(allocator) orelse continue;
if (str.len == 0) continue;
const hash = bun.Semver.String.Builder.stringHash(str);
try list.dynamic.put(allocator, hash, value);
}
}
return true;
}
pub fn fromPackageJSON(list: *List, expr: *const ast.Expr, allocator: std.mem.Allocator) !void {
if (expr.get("nativeDependencies")) |*native_deps_expr| {
list.disable_default_native_binlinks = try fromStringArrayGroup(list, native_deps_expr, allocator, .native_binlink);
}
if (expr.get("ignoreScripts")) |*ignored_scripts_expr| {
list.disable_default_ignore = try fromStringArrayGroup(list, ignored_scripts_expr, allocator, .ignore);
}
}
pub fn getNativeBinlinkReplacementPackageID(
resolutions: []const PackageID,
metas: []const Meta,
target_cpu: Npm.Architecture,
target_os: Npm.OperatingSystem,
) ?PackageID {
// Windows needs file extensions.
if (target_os.isMatch(@enumFromInt(Npm.OperatingSystem.win32))) {
return null;
}
// Loop through the list of optional dependencies with platform-specific constraints
// Find a matching target-specific dependency.
for (resolutions) |resolution| {
if (resolution > metas.len) continue;
const meta: *const Meta = &metas[resolution];
if (meta.arch == .all or meta.os == .all) continue;
if (meta.arch.isMatch(target_cpu) and meta.os.isMatch(target_os)) {
return resolution;
}
}
return null;
}
pub const List = struct {
dynamic: Map = .{},
disable_default_native_binlinks: bool = false,
disable_default_ignore: bool = false,
pub const Map = std.ArrayHashMapUnmanaged(PackageNameHash, PostinstallOptimizer, install.ArrayIdentityContext.U64, false);
pub fn isNativeBinlinkEnabled(this: *const @This()) bool {
if (this.dynamic.count() == 0) {
if (this.disable_default_native_binlinks) {
return true;
}
}
if (bun.env_var.feature_flag.BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER.get()) {
return false;
}
return true;
}
pub fn shouldIgnoreLifecycleScripts(
this: *const @This(),
name_hash: PackageNameHash,
resolutions: []const PackageID,
metas: []const Meta,
target_cpu: Npm.Architecture,
target_os: Npm.OperatingSystem,
tree_id: ?Lockfile.Tree.Id,
) bool {
if (bun.env_var.feature_flag.BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS.get()) {
return false;
}
const mode = this.get(name_hash) orelse return false;
return switch (mode) {
.native_binlink =>
// TODO: support hoisted.
(tree_id == null or tree_id.? == 0) and
// It's not as simple as checking `get(name_hash) != null` because if the
// specific versions of the package do not have optional
// dependencies then we cannot do this optimization without
// breaking the code.
//
// This shows up in test/integration/esbuild/esbuild.test.ts
getNativeBinlinkReplacementPackageID(resolutions, metas, target_cpu, target_os) != null,
.ignore => true,
};
}
fn fromDefault(name_hash: PackageNameHash) ?PostinstallOptimizer {
for (default_native_binlinks_name_hashes) |hash| {
if (hash == name_hash) {
return .native_binlink;
}
}
for (default_ignore_name_hashes) |hash| {
if (hash == name_hash) {
return .ignore;
}
}
return null;
}
pub fn get(this: *const @This(), name_hash: PackageNameHash) ?PostinstallOptimizer {
if (this.dynamic.get(name_hash)) |optimize| {
return optimize;
}
const default = fromDefault(name_hash) orelse {
return null;
};
switch (default) {
.native_binlink => {
if (!this.disable_default_native_binlinks) {
return .native_binlink;
}
},
.ignore => {
if (!this.disable_default_ignore) {
return .ignore;
}
},
}
return null;
}
};
};
const std = @import("std");
const bun = @import("bun");
const ast = bun.ast;
const install = bun.install;
const ArrayIdentityContext = install.ArrayIdentityContext;
const Lockfile = install.Lockfile;
const Npm = install.Npm;
const PackageID = install.PackageID;
const PackageNameHash = install.PackageNameHash;
const Meta = Lockfile.Package.Meta;

View File

@@ -0,0 +1,152 @@
import { spawn } from "bun";
import { afterAll, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
import { rm, writeFile } from "fs/promises";
import { bunEnv, bunExe, isWindows, VerdaccioRegistry } from "harness";
import { join } from "path";
let verdaccio: VerdaccioRegistry;
beforeAll(async () => {
setDefaultTimeout(1000 * 60 * 5);
verdaccio = new VerdaccioRegistry();
await verdaccio.start();
});
afterAll(() => {
verdaccio.stop();
});
describe.skipIf(isWindows).concurrent("native binlink optimization", () => {
for (const linker of ["hoisted", "isolated"]) {
test(`uses platform-specific bin instead of main package bin with linker ${linker}`, async () => {
let env = { ...bunEnv };
const { packageDir, packageJson } = await verdaccio.createTestDir();
env.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache");
env.BUN_TMPDIR = env.TMPDIR = env.TEMP = join(packageDir, ".bun-tmp");
// Create bunfig
await writeFile(
join(packageDir, "bunfig.toml"),
`
[install]
cache = "${join(packageDir, ".bun-cache").replaceAll("\\", "\\\\")}"
registry = "${verdaccio.registryUrl()}"
linker = "${linker}"
`,
);
// Install the main package
await writeFile(
packageJson,
JSON.stringify({
name: "test-app",
version: "1.0.0",
dependencies: {
"test-native-binlink": "1.0.0",
},
nativeDependencies: ["test-native-binlink"],
trustedDependencies: ["test-native-binlink"],
}),
);
const installProc = spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
stdout: "inherit",
stdin: "ignore",
stderr: "inherit",
env,
});
expect(await installProc.exited).toBe(0);
// Run the bin - it should use the platform-specific one (exit code 0)
// not the main package one (exit code 1)
const binProc = spawn({
cmd: [join(packageDir, "node_modules", ".bin", "test-binlink-cmd")],
cwd: packageDir,
stdout: "pipe",
stdin: "ignore",
stderr: "inherit",
env,
});
const [binStdout, binExitCode] = await Promise.all([binProc.stdout.text(), binProc.exited]);
// Should exit with 0 (platform-specific) not 1 (main package)
expect(binExitCode).toBe(0);
expect(binStdout).toContain("SUCCESS: Using platform-specific bin");
// Now delete the node_modules folder, keep the bun.lock, re-install
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
const installProc2 = spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
stdout: "inherit",
stdin: "ignore",
stderr: "inherit",
env,
});
expect(await installProc2.exited).toBe(0);
const binProc2 = spawn({
cmd: [join(packageDir, "node_modules", ".bin", "test-binlink-cmd")],
cwd: packageDir,
stdout: "pipe",
stdin: "ignore",
stderr: "inherit",
env,
});
const [binStdout2, binExitCode2] = await Promise.all([binProc2.stdout.text(), binProc2.exited]);
expect(binStdout2).toContain("SUCCESS: Using platform-specific bin");
expect(binExitCode2).toBe(0);
// Now do a no-op re-install.
const installProc3 = spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
stdout: "inherit",
stdin: "ignore",
stderr: "inherit",
env,
});
expect(await installProc3.exited).toBe(0);
const binProc3 = spawn({
cmd: [join(packageDir, "node_modules", ".bin", "test-binlink-cmd")],
cwd: packageDir,
stdout: "pipe",
stdin: "ignore",
stderr: "inherit",
env,
});
const [binStdout3, binExitCode3] = await Promise.all([binProc3.stdout.text(), binProc3.exited]);
expect(binStdout3).toContain("SUCCESS: Using platform-specific bin");
expect(binExitCode3).toBe(0);
// Now do an install with the .bin folder gone
await rm(join(packageDir, "node_modules", ".bin"), { recursive: true, force: true });
const installProc4 = spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
stdout: "inherit",
stdin: "ignore",
stderr: "inherit",
env,
});
expect(await installProc4.exited).toBe(0);
const binProc4 = spawn({
cmd: [join(packageDir, "node_modules", ".bin", "test-binlink-cmd")],
cwd: packageDir,
stdout: "pipe",
stdin: "ignore",
stderr: "inherit",
env,
});
const [binStdout4, binExitCode4] = await Promise.all([binProc4.stdout.text(), binProc4.exited]);
expect(binStdout4).toContain("SUCCESS: Using platform-specific bin");
expect(binExitCode4).toBe(0);
});
}
});

View File

@@ -0,0 +1,155 @@
#!/usr/bin/env bun
/**
* This script creates test packages for native binlink optimization testing.
* It creates:
* - test-native-binlink: main package with a bin that exits with code 1
* - test-native-binlink-target: platform-specific package with bin that exits with code 0
*/
import { $ } from "bun";
import { mkdir, writeFile } from "fs/promises";
import { join } from "path";
const packagesDir = import.meta.dir;
// Main package that should NOT be used
const mainPkgDir = join(packagesDir, "test-native-binlink-tmp");
await mkdir(mainPkgDir, { recursive: true });
await mkdir(join(mainPkgDir, "bin"), { recursive: true });
await writeFile(
join(mainPkgDir, "package.json"),
JSON.stringify(
{
name: "test-native-binlink",
version: "1.0.0",
bin: {
"test-binlink-cmd": "./bin/main.js",
},
optionalDependencies: {
"test-native-binlink-target": "1.0.0",
},
},
null,
2,
),
);
await writeFile(
join(mainPkgDir, "bin", "main.js"),
`#!/usr/bin/env node
console.log("ERROR: Using main package bin, not platform-specific!");
process.exit(1);
`,
);
// Create package structure for tarball
const mainTarDir = join(mainPkgDir, "package");
await mkdir(mainTarDir, { recursive: true });
await mkdir(join(mainTarDir, "bin"), { recursive: true });
await $`cp ${join(mainPkgDir, "package.json")} ${mainTarDir}/`;
await $`cp ${join(mainPkgDir, "bin", "main.js")} ${join(mainTarDir, "bin")}/`;
// Create tarball
await mkdir(join(packagesDir, "test-native-binlink"), { recursive: true });
await $`cd ${mainPkgDir} && tar -czf ${join(packagesDir, "test-native-binlink", "test-native-binlink-1.0.0.tgz")} package`;
// Platform-specific package
const targetPkgDir = join(packagesDir, "test-native-binlink-target-tmp");
await mkdir(targetPkgDir, { recursive: true });
await mkdir(join(targetPkgDir, "bin"), { recursive: true });
await writeFile(
join(targetPkgDir, "package.json"),
JSON.stringify(
{
name: "test-native-binlink-target",
version: "1.0.0",
os: ["darwin", "linux", "win32"],
cpu: ["arm64", "x64"],
},
null,
2,
),
);
// Use the SAME filename as the main package!
await writeFile(
join(targetPkgDir, "bin", "main.js"),
`#!/usr/bin/env node
console.log("SUCCESS: Using platform-specific bin (test-native-binlink-target)");
process.exit(0);
`,
);
// Create package structure for tarball
const targetTarDir = join(targetPkgDir, "package");
await mkdir(targetTarDir, { recursive: true });
await mkdir(join(targetTarDir, "bin"), { recursive: true });
await $`cp ${join(targetPkgDir, "package.json")} ${targetTarDir}/`;
await $`cp ${join(targetPkgDir, "bin", "main.js")} ${join(targetTarDir, "bin")}/`;
// Create tarball
await mkdir(join(packagesDir, "test-native-binlink-target"), { recursive: true });
await $`cd ${targetPkgDir} && tar -czf ${join(packagesDir, "test-native-binlink-target", "test-native-binlink-target-1.0.0.tgz")} package`;
// Create package.json for verdaccio registry with proper integrity hashes
for (const pkgName of ["test-native-binlink", "test-native-binlink-target"]) {
const version = "1.0.0";
const tarballName = `${pkgName}-${version}.tgz`;
const tarballPath = join(packagesDir, pkgName, tarballName);
// Calculate SHA512 integrity hash
const tarballFile = Bun.file(tarballPath);
const tarballBytes = await tarballFile.arrayBuffer();
const hash = new Bun.CryptoHasher("sha512");
hash.update(tarballBytes);
const integrity = `sha512-${Buffer.from(hash.digest()).toString("base64")}`;
// Calculate SHA1 shasum
const sha1Hash = new Bun.CryptoHasher("sha1");
sha1Hash.update(tarballBytes);
const shasum = Buffer.from(sha1Hash.digest()).toString("hex");
await writeFile(
join(packagesDir, pkgName, "package.json"),
JSON.stringify(
{
_id: pkgName,
name: pkgName,
"dist-tags": {
latest: version,
},
versions: {
[version]: {
name: pkgName,
version,
_id: `${pkgName}@${version}`,
bin: pkgName === "test-native-binlink" ? { "test-binlink-cmd": "./bin/main.js" } : undefined,
optionalDependencies:
pkgName === "test-native-binlink"
? {
"test-native-binlink-target": "1.0.0",
}
: undefined,
os: pkgName === "test-native-binlink-target" ? ["darwin", "linux", "win32"] : undefined,
cpu: pkgName === "test-native-binlink-target" ? ["arm64", "x64"] : undefined,
dist: {
integrity,
shasum,
tarball: `http://localhost:4873/${pkgName}/-/${tarballName}`,
},
},
},
},
null,
2,
),
);
}
// Clean up temp directories
await $`rm -rf ${mainPkgDir}`;
await $`rm -rf ${targetPkgDir}`;
console.log("✅ Created native binlink test packages");

View File

@@ -0,0 +1,28 @@
{
"_id": "test-native-binlink-target",
"name": "test-native-binlink-target",
"dist-tags": {
"latest": "1.0.0"
},
"versions": {
"1.0.0": {
"name": "test-native-binlink-target",
"version": "1.0.0",
"_id": "test-native-binlink-target@1.0.0",
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"arm64",
"x64"
],
"dist": {
"integrity": "sha512-CVsB3TKHbqr8o3/S5L4EnpFTZjlhGvdry2mF6mZvaUHYvZ8wRUMaJJObkDAXN1+zvuPAUdK4585DBrOpPe/vwA==",
"shasum": "d436fc61b25745d517ddfd117461fb5da7406679",
"tarball": "http://localhost:4873/test-native-binlink-target/-/test-native-binlink-target-1.0.0.tgz"
}
}
}
}

View File

@@ -0,0 +1,25 @@
{
"_id": "test-native-binlink",
"name": "test-native-binlink",
"dist-tags": {
"latest": "1.0.0"
},
"versions": {
"1.0.0": {
"name": "test-native-binlink",
"version": "1.0.0",
"_id": "test-native-binlink@1.0.0",
"bin": {
"test-binlink-cmd": "./bin/main.js"
},
"optionalDependencies": {
"test-native-binlink-target": "1.0.0"
},
"dist": {
"integrity": "sha512-1PqDobvLsa0lFqxrns17u0yfmjgLQeuST8PWNxov09gs+yEm2xoBc5yXx5B6NFcXOY6lu5A62cRgFW8FSOgpJQ==",
"shasum": "da15dad1eae537c1341ca87b04d8ae7a52be5e4a",
"tarball": "http://localhost:4873/test-native-binlink/-/test-native-binlink-1.0.0.tgz"
}
}
}
}

View File

@@ -1,24 +1,22 @@
import { spawn } from "bun"; import { spawn } from "bun";
import { beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test"; import { beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
import { cp, rm, writeFile } from "fs/promises"; import { cp, rm, writeFile } from "fs/promises";
import { bunExe, bunEnv as env, tmpdirSync } from "harness"; import { bunExe, bunEnv as env, tempDir } from "harness";
import { join } from "path"; import { join } from "path";
beforeAll(() => { beforeAll(() => {
setDefaultTimeout(1000 * 60 * 5); setDefaultTimeout(1000 * 60 * 5);
}); });
describe("esbuild integration test", () => { describe.concurrent("esbuild integration test", () => {
test("install and use esbuild", async () => { test("install and use esbuild", async () => {
const packageDir = tmpdirSync(); using dir = tempDir("esbuild-test", {
"package.json": JSON.stringify({
await writeFile(
join(packageDir, "package.json"),
JSON.stringify({
name: "bun-esbuild-test", name: "bun-esbuild-test",
version: "1.0.0", version: "1.0.0",
}), }),
); });
const packageDir = dir + "";
var { stdout, stderr, exited } = spawn({ var { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "esbuild@0.19.8"], cmd: [bunExe(), "install", "esbuild@0.19.8"],
@@ -52,17 +50,15 @@ describe("esbuild integration test", () => {
}); });
test("install and use estrella", async () => { test("install and use estrella", async () => {
const packageDir = tmpdirSync(); using dir = tempDir("esbuild-estrella-test", {
"package.json": JSON.stringify({
await writeFile(
join(packageDir, "package.json"),
JSON.stringify({
name: "bun-esbuild-estrella-test", name: "bun-esbuild-estrella-test",
version: "1.0.0", version: "1.0.0",
}), }),
); });
const packageDir = dir + "";
var { stdout, stderr, exited } = spawn({ let { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "estrella@1.4.1"], cmd: [bunExe(), "install", "estrella@1.4.1"],
cwd: packageDir, cwd: packageDir,
stdout: "pipe", stdout: "pipe",
@@ -70,12 +66,14 @@ describe("esbuild integration test", () => {
stderr: "pipe", stderr: "pipe",
env, env,
}); });
let exitCode = 0;
let err = "";
let out = "";
var err = await stderr.text(); [err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
var out = await stdout.text();
expect(err).toContain("Saved lockfile"); expect(err).toContain("Saved lockfile");
expect(out).toContain("estrella@1.4.1"); expect(out).toContain("estrella@1.4.1");
expect(await exited).toBe(0); expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({ ({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "--estrella-version"], cmd: [bunExe(), "estrella", "--estrella-version"],
@@ -86,11 +84,10 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
out = await stdout.text();
expect(err).toBe(""); expect(err).toBe("");
expect(out).toContain("1.4.1"); expect(out).toContain("1.4.1");
expect(await exited).toBe(0); expect(exitCode).toBe(0);
await cp(join(import.meta.dir, "build-file.js"), join(packageDir, "build-file.js")); await cp(join(import.meta.dir, "build-file.js"), join(packageDir, "build-file.js"));
@@ -103,11 +100,7 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toBe("");
expect(out).toBe('console.log("hello"),console.log("estrella");\n');
expect(await exited).toBe(0);
await rm(join(packageDir, "node_modules"), { recursive: true, force: true }); await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
await rm(join(packageDir, "bun.lockb"), { force: true }); await rm(join(packageDir, "bun.lockb"), { force: true });
@@ -134,12 +127,11 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toContain("Saved lockfile"); expect(err).toContain("Saved lockfile");
expect(out).toContain("estrella@1.4.1"); expect(out).toContain("estrella@1.4.1");
expect(out).toContain("esbuild@0.19.8"); expect(out).toContain("esbuild@0.19.8");
expect(await exited).toBe(0); expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({ ({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "--estrella-version"], cmd: [bunExe(), "estrella", "--estrella-version"],
@@ -150,11 +142,10 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toBe(""); expect(err).toBe("");
expect(out).toContain("1.4.1"); expect(out).toContain("1.4.1");
expect(await exited).toBe(0); expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({ ({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "esbuild", "--version"], cmd: [bunExe(), "esbuild", "--version"],
@@ -165,11 +156,10 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toBe(""); expect(err).toBe("");
expect(out).toContain("0.19.8"); expect(out).toContain("0.19.8");
expect(await exited).toBe(0); expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({ ({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "esbuild", "--version"], cmd: [bunExe(), "esbuild", "--version"],
@@ -180,10 +170,10 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toBe(""); expect(err).toBe("");
expect(out).toContain("0.11.23"); expect(out).toContain("0.11.23");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({ ({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "build-file.js"], cmd: [bunExe(), "estrella", "build-file.js"],
@@ -194,10 +184,9 @@ describe("esbuild integration test", () => {
env, env,
})); }));
err = await stderr.text(); [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
out = await stdout.text();
expect(err).toBe(""); expect(err).toBe("");
expect(out).toBe('console.log("hello"),console.log("estrella");\n'); expect(out).toBe('console.log("hello"),console.log("estrella");\n');
expect(await exited).toBe(0); expect(exitCode).toBe(0);
}); });
}); });