-
Notifications
You must be signed in to change notification settings - Fork 379
Description
Bug report
Describe the bug
When using a root-level workspace deno.json at supabase/functions/deno.json (instead of per-function deno.json files), supabase functions deploy fails to resolve the environment variable in .npmrc.
The .npmrc file uses ${NPM_AUTH_TOKEN} for private registry authentication. functions serve reads this variable correctly (from supabase/functions/.env), but functions deploy does not — resulting in a registry authentication failure.
.npmrc configuration
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_AUTH_TOKEN}
The NPM_AUTH_TOKEN is provided via supabase/functions/.env, which functions serve loads automatically.
Project structure
supabase/functions/
├── deno.json # Root workspace config
│ ├── "workspace": ["./*"]
│ └── "imports": {
│ "@my-private-pkg": "npm:@myorg/my-package@^1.0.0",
│ ...
│ }
├── .npmrc # Private registry auth
├── .env # NPM_AUTH_TOKEN=ghp_xxxxx
├── function-a/
│ ├── index.ts
│ └── deno.json # Function-specific imports only
├── function-b/
│ └── ...
└── _shared/ # Shared utilities
Why this structure?
The official docs recommend per-function deno.json, but this creates a dependency management problem with _shared/ code — shared utilities would need their npm dependencies duplicated across every function's deno.json. A root workspace deno.json solves this. This approach is also discussed as practical in Discussion #33595.
Steps to reproduce
- Set up the project structure above with a private npm package
- Configure
.npmrcwith${NPM_AUTH_TOKEN}environment variable - Set the token in
supabase/functions/.env - Run
supabase functions serve→ Works, private package resolves correctly - Run
supabase functions deploy→ Fails, authentication error (.npmrcenv var not resolved)
Expected behavior
functions deploy should resolve environment variables in .npmrc the same way functions serve does.
Workaround
Running functions serve before deploy makes it work. The serve process appears to cache the resolved npm packages, which deploy then picks up:
# In CI/CD pipeline:
- name: Start Supabase services
run: supabase start -x imgproxy,supavisor,realtime,storage-api,studio,mailpit,logflare,vector
- name: Warm up functions (workaround)
run: |
supabase functions serve --no-verify-jwt &
SERVE_PID=$!
sleep 3
# Must call a function that imports the private package
curl -i --fail http://127.0.0.1:54321/functions/v1/health || true
kill $SERVE_PID || true
wait $SERVE_PID 2>/dev/null || true
- name: Deploy Edge Functions
run: supabase functions deploy --no-verify-jwt --prune --yesImportant: The health endpoint must actually import the private package — simply running serve is not enough. The package needs to be loaded at least once.
// health/index.ts
import {} from "@my-private-pkg"; // Force package resolution
Deno.serve(() => new Response("ok", { status: 200 }));This was discovered empirically after multiple failed attempts and has been the only reliable CI/CD method.
Environment
- Supabase CLI: v2.75.0
- Deno: v2.7.1
- OS: Ubuntu 24.04 (GitHub Actions) / macOS (local)
Related issues
- Support for Deno workspaces #3047 — Deno workspace support
- deno monorepo support does not support using dependencies in workspace packages #4550 — Workspace package dependencies not resolved
- fail to deploy functions that use deno workspace packages supabase#35021 — Deploy fails with workspace packages