diff --git a/examples/api/.dev.vars b/examples/api/.dev.vars new file mode 100644 index 00000000..17f2dcc2 --- /dev/null +++ b/examples/api/.dev.vars @@ -0,0 +1 @@ +NEXTJS_ENV=development \ No newline at end of file diff --git a/examples/api/.env.development b/examples/api/.env.development new file mode 100644 index 00000000..7eeb777a --- /dev/null +++ b/examples/api/.env.development @@ -0,0 +1 @@ +TEST_ENV_VAR=TEST_VALUE \ No newline at end of file diff --git a/examples/api/app/api/env/route.ts b/examples/api/app/api/env/route.ts new file mode 100644 index 00000000..24dfdc74 --- /dev/null +++ b/examples/api/app/api/env/route.ts @@ -0,0 +1,3 @@ +export async function GET() { + return new Response(JSON.stringify(process.env)); +} diff --git a/examples/api/e2e/base.spec.ts b/examples/api/e2e/base.spec.ts index a4f8811e..2cba2049 100644 --- a/examples/api/e2e/base.spec.ts +++ b/examples/api/e2e/base.spec.ts @@ -30,3 +30,8 @@ test("the hello-world api POST route works as intended", async ({ page }) => { expect(res.headers()["content-type"]).toContain("text/plain"); await expect(res.text()).resolves.toEqual("Hello post-World! body=some body"); }); + +test("sets environment variables from the Next.js env file", async ({ page }) => { + const res = await page.request.get("/api/env"); + await expect(res.json()).resolves.toEqual(expect.objectContaining({ TEST_ENV_VAR: "TEST_VALUE" })); +}); diff --git a/packages/cloudflare/env.d.ts b/packages/cloudflare/env.d.ts index 58204cd8..28761731 100644 --- a/packages/cloudflare/env.d.ts +++ b/packages/cloudflare/env.d.ts @@ -7,6 +7,8 @@ declare global { NEXT_PRIVATE_DEBUG_CACHE?: string; __OPENNEXT_KV_BINDING_NAME: string; OPEN_NEXT_ORIGIN: string; + NODE_ENV?: string; + __OPENNEXT_PROCESSED_ENV?: string; } } } diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 727bdb71..f23bc823 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -59,11 +59,14 @@ "tsup": "catalog:", "typescript": "catalog:", "typescript-eslint": "catalog:", - "vitest": "catalog:" + "vitest": "catalog:", + "mock-fs": "catalog:", + "@types/mock-fs": "catalog:" }, "dependencies": { "@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@678", - "ts-morph": "catalog:" + "ts-morph": "catalog:", + "@dotenvx/dotenvx": "catalog:" }, "peerDependencies": { "wrangler": "catalog:" diff --git a/packages/cloudflare/src/cli/build/index.ts b/packages/cloudflare/src/cli/build/index.ts index 5f01952b..8f10cfb9 100644 --- a/packages/cloudflare/src/cli/build/index.ts +++ b/packages/cloudflare/src/cli/build/index.ts @@ -15,6 +15,7 @@ import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import type { ProjectOptions } from "../config"; import { containsDotNextDir, getConfig } from "../config"; import { bundleServer } from "./bundle-server"; +import { compileEnvFiles } from "./open-next/compile-env-files"; import { createServerBundle } from "./open-next/createServerBundle"; /** @@ -71,6 +72,9 @@ export async function build(projectOpts: ProjectOptions): Promise { // Compile cache.ts compileCache(options); + // Compile .env files + compileEnvFiles(options); + // Compile middleware await createMiddleware(options, { forceOnlyBuildOnce: true }); diff --git a/packages/cloudflare/src/cli/build/open-next/compile-env-files.ts b/packages/cloudflare/src/cli/build/open-next/compile-env-files.ts new file mode 100644 index 00000000..ae4969fd --- /dev/null +++ b/packages/cloudflare/src/cli/build/open-next/compile-env-files.ts @@ -0,0 +1,18 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { BuildOptions } from "@opennextjs/aws/build/helper.js"; + +import { extractProjectEnvVars } from "../utils"; + +/** + * Compiles the values extracted from the project's env files to the output directory for use in the worker. + */ +export function compileEnvFiles(options: BuildOptions) { + ["production", "development", "test"].forEach((mode) => + fs.appendFileSync( + path.join(options.outputDir, `.env.mjs`), + `export const ${mode} = ${JSON.stringify(extractProjectEnvVars(mode, options))};\n` + ) + ); +} diff --git a/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts new file mode 100644 index 00000000..6aa9834d --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts @@ -0,0 +1,70 @@ +import { appendFileSync, writeFileSync } from "node:fs"; + +import { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import mockFs from "mock-fs"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { extractProjectEnvVars } from "./extract-project-env-vars"; + +const options = { monorepoRoot: "", appPath: "" } as BuildOptions; + +describe("extractProjectEnvVars", () => { + beforeEach(() => { + mockFs({ + ".env": "ENV_VAR=value", + ".env.local": "ENV_LOCAL_VAR=value", + ".env.development": "ENV_DEV_VAR=value", + ".env.development.local": "ENV_DEV_LOCAL_VAR=value", + ".env.production": "ENV_PROD_VAR=value", + ".env.production.local": "ENV_PROD_LOCAL_VAR=value", + }); + }); + + afterEach(() => mockFs.restore()); + + it("should extract production env vars", () => { + const result = extractProjectEnvVars("production", options); + expect(result).toEqual({ + ENV_LOCAL_VAR: "value", + ENV_PROD_LOCAL_VAR: "value", + ENV_PROD_VAR: "value", + ENV_VAR: "value", + }); + }); + + it("should extract development env vars", () => { + writeFileSync(".dev.vars", 'NEXTJS_ENV = "development"'); + + const result = extractProjectEnvVars("development", options); + expect(result).toEqual({ + ENV_LOCAL_VAR: "value", + ENV_DEV_LOCAL_VAR: "value", + ENV_DEV_VAR: "value", + ENV_VAR: "value", + }); + }); + + it("should override env vars with those in a local file", () => { + writeFileSync(".env.production.local", "ENV_PROD_VAR=overridden"); + + const result = extractProjectEnvVars("production", options); + expect(result).toEqual({ + ENV_LOCAL_VAR: "value", + ENV_PROD_VAR: "overridden", + ENV_VAR: "value", + }); + }); + + it("should support referencing variables", () => { + appendFileSync(".env.production.local", "\nENV_PROD_LOCAL_VAR_REF=$ENV_PROD_LOCAL_VAR"); + + const result = extractProjectEnvVars("production", options); + expect(result).toEqual({ + ENV_LOCAL_VAR: "value", + ENV_PROD_LOCAL_VAR: "value", + ENV_PROD_LOCAL_VAR_REF: "value", + ENV_PROD_VAR: "value", + ENV_VAR: "value", + }); + }); +}); diff --git a/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.ts b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.ts new file mode 100644 index 00000000..7440e6b1 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.ts @@ -0,0 +1,36 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; + +import { parse } from "@dotenvx/dotenvx"; +import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; + +function readEnvFile(filePath: string) { + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + return parse(fs.readFileSync(filePath).toString()); + } +} + +/** + * Extracts the environment variables defined in various .env files for a project. + * + * The `NEXTJS_ENV` environment variable in `.dev.vars` determines the mode. + * + * Merged variables respect the following priority order. + * 1. `.env.{mode}.local` + * 2. `.env.local` + * 3. `.env.{mode}` + * 4. `.env` + * + * https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#environment-variable-load-order + * + * In a monorepo, the env files in an app's directory will take precedence over + * the env files at the root of the monorepo. + */ +export function extractProjectEnvVars(mode: string, { monorepoRoot, appPath }: BuildOptions) { + return [".env", `.env.${mode}`, ".env.local", `.env.${mode}.local`] + .flatMap((fileName) => [ + ...(monorepoRoot !== appPath ? [readEnvFile(path.join(monorepoRoot, fileName))] : []), + readEnvFile(path.join(appPath, fileName)), + ]) + .reduce>((acc, overrides) => ({ ...acc, ...overrides }), {}); +} diff --git a/packages/cloudflare/src/cli/build/utils/index.ts b/packages/cloudflare/src/cli/build/utils/index.ts index 5b703b4d..e8f9a7d7 100644 --- a/packages/cloudflare/src/cli/build/utils/index.ts +++ b/packages/cloudflare/src/cli/build/utils/index.ts @@ -1,3 +1,4 @@ export * from "./copy-prerendered-routes"; +export * from "./extract-project-env-vars"; export * from "./normalize-path"; export * from "./ts-parse-file"; diff --git a/packages/cloudflare/src/cli/templates/worker.ts b/packages/cloudflare/src/cli/templates/worker.ts index a23da2e3..d4617b35 100644 --- a/packages/cloudflare/src/cli/templates/worker.ts +++ b/packages/cloudflare/src/cli/templates/worker.ts @@ -21,6 +21,21 @@ const cloudflareContextALS = new AsyncLocalStorage(); } ); +async function applyProjectEnvVars(mode: string) { + if (process.env.__OPENNEXT_PROCESSED_ENV === "1") return; + + // @ts-expect-error: resolved by wrangler build + const nextEnvVars = await import("./.env.mjs"); + + if (nextEnvVars[mode]) { + for (const key in nextEnvVars[mode]) { + process.env[key] = nextEnvVars[mode][key]; + } + } + + process.env.__OPENNEXT_PROCESSED_ENV = "1"; +} + export default { async fetch(request, env, ctx) { return cloudflareContextALS.run({ env, ctx, cf: request.cf }, async () => { @@ -34,6 +49,8 @@ export default { }, }); + await applyProjectEnvVars(env.NEXTJS_ENV ?? "production"); + // The Middleware handler can return either a `Response` or a `Request`: // - `Response`s should be returned early // - `Request`s are handled by the Next server @@ -46,4 +63,4 @@ export default { return serverHandler(reqOrResp, env, ctx); }); }, -} as ExportedHandler<{ ASSETS: Fetcher }>; +} as ExportedHandler<{ ASSETS: Fetcher; NEXTJS_ENV?: string }>; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80b286e2..8dfc9833 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,9 @@ catalogs: '@cloudflare/workers-types': specifier: ^4.20240925.0 version: 4.20240925.0 + '@dotenvx/dotenvx': + specifier: 1.31.0 + version: 1.31.0 '@eslint/js': specifier: ^9.11.1 version: 9.11.1 @@ -18,6 +21,9 @@ catalogs: '@tsconfig/strictest': specifier: ^2.0.5 version: 2.0.5 + '@types/mock-fs': + specifier: ^4.13.4 + version: 4.13.4 '@types/node': specifier: ^22.2.0 version: 22.2.0 @@ -42,6 +48,9 @@ catalogs: globals: specifier: ^15.9.0 version: 15.9.0 + mock-fs: + specifier: ^5.4.1 + version: 5.4.1 next: specifier: 14.2.11 version: 14.2.11 @@ -340,6 +349,9 @@ importers: packages/cloudflare: dependencies: + '@dotenvx/dotenvx': + specifier: 'catalog:' + version: 1.31.0 '@opennextjs/aws': specifier: https://pkg.pr.new/@opennextjs/aws@678 version: https://pkg.pr.new/@opennextjs/aws@678 @@ -359,6 +371,9 @@ importers: '@tsconfig/strictest': specifier: 'catalog:' version: 2.0.5 + '@types/mock-fs': + specifier: 'catalog:' + version: 4.13.4 '@types/node': specifier: 'catalog:' version: 22.2.0 @@ -383,6 +398,9 @@ importers: globals: specifier: 'catalog:' version: 15.9.0 + mock-fs: + specifier: 'catalog:' + version: 5.4.1 next: specifier: 'catalog:' version: 14.2.11(@playwright/test@1.47.0)(react-dom@19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-3208e73e-20240730))(react@19.0.0-rc-3208e73e-20240730) @@ -745,6 +763,16 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dotenvx/dotenvx@1.31.0': + resolution: {integrity: sha512-GeDxvtjiRuoyWVU9nQneId879zIyNdL05bS7RKiqMkfBSKpHMWHLoRyRqjYWLaXmX/llKO1hTlqHDmatkQAjPA==} + hasBin: true + + '@ecies/ciphers@0.2.2': + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + '@emnapi/runtime@1.2.0': resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} @@ -1665,6 +1693,22 @@ packages: cpu: [x64] os: [win32] + '@noble/ciphers@1.1.3': + resolution: {integrity: sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.1': + resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} + engines: {node: ^14.21.3 || >=16} + '@node-minify/core@8.0.6': resolution: {integrity: sha512-/vxN46ieWDLU67CmgbArEvOb41zlYFOkOtr9QW9CnTrBLuTyGgkyNWC2y5+khvRw3Br58p2B5ZVSx/PxCTru6g==} engines: {node: '>=16.0.0'} @@ -2141,6 +2185,9 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mock-fs@4.13.4': + resolution: {integrity: sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -2574,6 +2621,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2719,12 +2770,20 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + eciesjs@0.4.12: + resolution: {integrity: sha512-DGejvMCihsRAmKRFQiL6KZDE34vWVd0gvXlykFq1aEzJy/rD65AVyAIUZKZOvgvaP9ATQRcHGEZV5DfgrgjA4w==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + electron-to-chromium@1.5.29: resolution: {integrity: sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==} @@ -3474,6 +3533,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} @@ -3786,6 +3849,10 @@ packages: mnemonist@0.38.3: resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + mock-fs@5.4.1: + resolution: {integrity: sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==} + engines: {node: '>=12.0.0'} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -3889,6 +3956,10 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + object.assign@4.1.5: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} @@ -5001,6 +5072,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -5984,6 +6060,22 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dotenvx/dotenvx@1.31.0': + dependencies: + commander: 11.1.0 + dotenv: 16.4.7 + eciesjs: 0.4.12 + execa: 5.1.1 + fdir: 6.4.0(picomatch@4.0.2) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.2 + which: 4.0.0 + + '@ecies/ciphers@0.2.2(@noble/ciphers@1.1.3)': + dependencies: + '@noble/ciphers': 1.1.3 + '@emnapi/runtime@1.2.0': dependencies: tslib: 2.6.3 @@ -6592,6 +6684,16 @@ snapshots: '@next/swc-win32-x64-msvc@15.0.0-canary.113': optional: true + '@noble/ciphers@1.1.3': {} + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.6.1': {} + '@node-minify/core@8.0.6': dependencies: '@node-minify/utils': 8.0.6 @@ -7205,6 +7307,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mock-fs@4.13.4': + dependencies: + '@types/node': 20.14.12 + '@types/ms@0.7.34': {} '@types/node-forge@1.3.11': @@ -7735,6 +7841,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -7875,10 +7983,19 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.4.7: {} + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} + eciesjs@0.4.12: + dependencies: + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.1.3) + '@noble/ciphers': 1.1.3 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + electron-to-chromium@1.5.29: {} emoji-regex@10.4.0: {} @@ -8923,6 +9040,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 @@ -9342,6 +9461,8 @@ snapshots: dependencies: obliterator: 1.6.1 + mock-fs@5.4.1: {} + mri@1.2.0: {} ms@2.1.2: {} @@ -9470,6 +9591,8 @@ snapshots: object-keys@1.1.1: {} + object-treeify@1.1.33: {} + object.assign@4.1.5: dependencies: call-bind: 1.0.7 @@ -10652,6 +10775,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dea53fdd..341410d2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,3 +27,6 @@ catalog: "typescript-eslint": ^8.7.0 "vitest": ^2.1.1 "wrangler": ^3.95.0 + "@dotenvx/dotenvx": 1.31.0 + "mock-fs": ^5.4.1 + "@types/mock-fs": ^4.13.4