[VERY EXTREMELY CURSED] How can I access process.env (at start time)

Ahoy! I need .env to instantiate my jwt server. I've spent so much time of my life reading minified code and dealing with build tools, BUT I HAVE NO IDEA WHATS GOING ON!!! Solid start somehow uses vite, rollup, esbuild, vinxi, and nitro. and then I suppose cf is doing its own polyfills Has javascript gone too far???? But anyway My first attempt is
export const authPlugin = new Elysia({ name: "authPlugin" }).use(
jwt({
secret: process.env.JWT_SECRET!,
// ...
})
);
export const authPlugin = new Elysia({ name: "authPlugin" }).use(
jwt({
secret: process.env.JWT_SECRET!,
// ...
})
);
but this didn't work, process.env is undefined despite node.js compat being enabled. Looking inside the bundled code, I see .use(jwt({secret:J.env.JWT_SECRET, what is J? const J=H.process. Ok lets zoom out
N.title="unenv";const U=Object.create(null),W=globalThis.process?.env,_getEnv=s=>W||globalThis.__env__||(s?U:globalThis);function noop$1(){return N}N.env=new Proxy(U,{get:(s,c)=>_getEnv()[c]??U[c],has:(s,c)=>c in _getEnv()||c in U,set:(s,c,u)=>(_getEnv(!0)[c]=u,!0),deleteProperty(s,c){delete _getEnv(!0)[c]},ownKeys(){const s=_getEnv();return Object.keys(s)}}),N.argv=[],N.version="",N.versions={},N.on=noop$1,N.addListener=noop$1,N.once=noop$1,N.off=noop$1,N.removeListener=noop$1,N.removeAllListeners=noop$1,N.emit=noop$1,N.prependListener=noop$1,N.prependOnceListener=noop$1,N.listeners=function(s){return[]},N.binding=function(s){throw new Error("[unenv] process.binding is not supported")};let V="/";N.cwd=function(){return V},N.chdir=function(s){V=s},N.umask=function(){return 0};const H="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};H.process=H.process||N;const J=H.process
N.title="unenv";const U=Object.create(null),W=globalThis.process?.env,_getEnv=s=>W||globalThis.__env__||(s?U:globalThis);function noop$1(){return N}N.env=new Proxy(U,{get:(s,c)=>_getEnv()[c]??U[c],has:(s,c)=>c in _getEnv()||c in U,set:(s,c,u)=>(_getEnv(!0)[c]=u,!0),deleteProperty(s,c){delete _getEnv(!0)[c]},ownKeys(){const s=_getEnv();return Object.keys(s)}}),N.argv=[],N.version="",N.versions={},N.on=noop$1,N.addListener=noop$1,N.once=noop$1,N.off=noop$1,N.removeListener=noop$1,N.removeAllListeners=noop$1,N.emit=noop$1,N.prependListener=noop$1,N.prependOnceListener=noop$1,N.listeners=function(s){return[]},N.binding=function(s){throw new Error("[unenv] process.binding is not supported")};let V="/";N.cwd=function(){return V},N.chdir=function(s){V=s},N.umask=function(){return 0};const H="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};H.process=H.process||N;const J=H.process
ok so nitro is being "helpful" and injecting unenv into the game. Ok so its looking at globalThis. Whats in globalThis? console.log(globalThis) OH LAWDY WHAT IS THIS (hahah globalThis get it puns?) 1/?
48 Replies
Ping for toast
Ping for toast4mo ago
<Formatting threw (Error: Error: Error: Disallowed operation called within global scope. Asynchronous I/O (ex: fetch() or connect()), setting a timeout, and generating random values are not allowed within global scope. To fix this error, perform this operation within a handler. https://developers.cloudflare.com/workers/runtime-apis/handlers/
<Formatting threw (Error: Error: Error: Disallowed operation called within global scope. Asynchronous I/O (ex: fetch() or connect()), setting a timeout, and generating random values are not allowed within global scope. To fix this error, perform this operation within a handler. https://developers.cloudflare.com/workers/runtime-apis/handlers/
Ok so it should be console.log(globalThis.process?.env || globalThis.__env__) maybe like unenv does right? Well that just gives me [Object: null prototype] {} [Object: null prototype] {} [Object: null prototype] {}, so there's obviously nothing there, right? Ok so I saw https://developers.cloudflare.com/workers/runtime-apis/nodejs/process/ Which says
In the Workers implementation, there is no process-level environment, so env is an empty object
So maybe we can directly acess it, but I think this is saying that I don't get this :( Still, lets try: console.log(globalThis.process?.env.JWT_SECRET) gives undefined when the server starts. Not looking good. But wait! When I do a request, I see it! Wait, so what if I directly acess process?.env.JWT_SECRET? OH LAWD WOULD YOU LOOK AT THAT! IT WORKS! But it doesn't work outside of that request. When the server boots up, it throws an error because process.env.JWT is undefined and it needs that to start. Ok, so what other ways do we have? Well you can see import { env, nextTick } from "node:process"; in that doc page above. Does anything change?
.vinxi/build/server-fns/_server/index8.mjs (1:476): "env" is not exported by "node_modules/.pnpm/unenv@1.9.0/node_modules/unenv/runtime/node/process/index.mjs", imported by ".vinxi/build/server-fns/_server/index8.mjs".
.vinxi/build/server-fns/_server/index8.mjs (1:476): "env" is not exported by "node_modules/.pnpm/unenv@1.9.0/node_modules/unenv/runtime/node/process/index.mjs", imported by ".vinxi/build/server-fns/_server/index8.mjs".
Ow. Ok, Solid provides getRequestEvent()?.nativeEvent.context.cloudflare.env.JWT_SECRET, and again this is undefined in the initialization as expected because there's no request event when initializing.
Cloudflare Docs
process · Cloudflare Workers docs
To use Node.js APIs in your Worker, add the nodejs_compat compatibility flag to your wrangler.toml file.
Ping for toast
Ping for toast4mo ago
next-on-cloudlare How does next-on-cloudflare do it with getRequestContext().env?
const cloudflareRequestContextSymbol = Symbol.for(
'__cloudflare-request-context__',
);

export function getOptionalRequestContext<
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
Context = ExecutionContext,
>(): undefined | RequestContext<CfProperties, Context> {
const cloudflareRequestContext = (
globalThis as unknown as {
[cloudflareRequestContextSymbol]:
| RequestContext<CfProperties, Context>
| undefined;
}
)[cloudflareRequestContextSymbol];

if (inferRuntime() === 'nodejs') {
throw new Error(/*trim*/`);
}

return cloudflareRequestContext;
}
const cloudflareRequestContextSymbol = Symbol.for(
'__cloudflare-request-context__',
);

export function getOptionalRequestContext<
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
Context = ExecutionContext,
>(): undefined | RequestContext<CfProperties, Context> {
const cloudflareRequestContext = (
globalThis as unknown as {
[cloudflareRequestContextSymbol]:
| RequestContext<CfProperties, Context>
| undefined;
}
)[cloudflareRequestContextSymbol];

if (inferRuntime() === 'nodejs') {
throw new Error(/*trim*/`);
}

return cloudflareRequestContext;
}
Woah, __cloudflare-request-context__, we're in some deep stuff, but why is it undefined? Oooh, next on pages does... I don't even want to know: Now this is JavaScript
export function generateGlobalJs(): string {
return `
import('node:buffer').then(({ Buffer }) => {
globalThis.Buffer = Buffer;
})
.catch(() => null);

const __ALSes_PROMISE__ = import('node:async_hooks').then(({ AsyncLocalStorage }) => {
globalThis.AsyncLocalStorage = AsyncLocalStorage;

const envAsyncLocalStorage = new AsyncLocalStorage();
const requestContextAsyncLocalStorage = new AsyncLocalStorage();

globalThis.process = {
env: new Proxy(
{},
{
ownKeys: () => Reflect.ownKeys(envAsyncLocalStorage.getStore()),
getOwnPropertyDescriptor: (_, ...args) =>
Reflect.getOwnPropertyDescriptor(envAsyncLocalStorage.getStore(), ...args),
get: (_, property) => Reflect.get(envAsyncLocalStorage.getStore(), property),
set: (_, property, value) => Reflect.set(envAsyncLocalStorage.getStore(), property, value),
}),
};

globalThis[Symbol.for('__cloudflare-request-context__')] = new Proxy(
{},
{
ownKeys: () => Reflect.ownKeys(requestContextAsyncLocalStorage.getStore()),
getOwnPropertyDescriptor: (_, ...args) =>
Reflect.getOwnPropertyDescriptor(requestContextAsyncLocalStorage.getStore(), ...args),
get: (_, property) => Reflect.get(requestContextAsyncLocalStorage.getStore(), property),
set: (_, property, value) => Reflect.set(requestContextAsyncLocalStorage.getStore(), property, value),
}
);

return { envAsyncLocalStorage, requestContextAsyncLocalStorage };
})
.catch(() => null);
`;
}
export function generateGlobalJs(): string {
return `
import('node:buffer').then(({ Buffer }) => {
globalThis.Buffer = Buffer;
})
.catch(() => null);

const __ALSes_PROMISE__ = import('node:async_hooks').then(({ AsyncLocalStorage }) => {
globalThis.AsyncLocalStorage = AsyncLocalStorage;

const envAsyncLocalStorage = new AsyncLocalStorage();
const requestContextAsyncLocalStorage = new AsyncLocalStorage();

globalThis.process = {
env: new Proxy(
{},
{
ownKeys: () => Reflect.ownKeys(envAsyncLocalStorage.getStore()),
getOwnPropertyDescriptor: (_, ...args) =>
Reflect.getOwnPropertyDescriptor(envAsyncLocalStorage.getStore(), ...args),
get: (_, property) => Reflect.get(envAsyncLocalStorage.getStore(), property),
set: (_, property, value) => Reflect.set(envAsyncLocalStorage.getStore(), property, value),
}),
};

globalThis[Symbol.for('__cloudflare-request-context__')] = new Proxy(
{},
{
ownKeys: () => Reflect.ownKeys(requestContextAsyncLocalStorage.getStore()),
getOwnPropertyDescriptor: (_, ...args) =>
Reflect.getOwnPropertyDescriptor(requestContextAsyncLocalStorage.getStore(), ...args),
get: (_, property) => Reflect.get(requestContextAsyncLocalStorage.getStore(), property),
set: (_, property, value) => Reflect.set(requestContextAsyncLocalStorage.getStore(), property, value),
}
);

return { envAsyncLocalStorage, requestContextAsyncLocalStorage };
})
.catch(() => null);
`;
}
Ping for toast
Ping for toast4mo ago
So we're looking in globalThis for __cloudflare-request-context__. But this still doesn't explain how env is being populated - https://github.com/cloudflare/next-on-pages/blob/main/internal-packages/next-dev/src/shared.ts But tbh maybe it doesn't even matter, I don't have any reason to believe this works outside of a request, given that its... requestContext. --- So yeah, the ultimate question is is it possible to access env outside of a request context?
GitHub
next-on-pages/internal-packages/next-dev/src/shared.ts at main · cl...
CLI to build and develop Next.js apps for Cloudflare Pages - cloudflare/next-on-pages
Isaac McFadyen
Isaac McFadyen4mo ago
So no, I don't believe that you can get environment variables outside of a request context because they're only passed into the actual request handler by the runtime itself (i.e. global code can't access them). Frameworks like next-on-pages use a somewhat-hacky method of putting the environment variables into the global context but I believe it's very much next-on-pages-specific and has a lot of moving parts (i.e. probably not something that would be easy to do yourself for Solid Start). @Better James (hope the ping is OK) would probably have more info on how next-on-pages do it specifically, I'm not well-versed with the internals of that framework.
Hello, I’m Allie!
I believe too that only works because those scripts are loaded when a Request Context has already been initialized
James
James4mo ago
next-on-pages runs your requests inside an AyncLocalStorage that holds the values for the env vars, and process.env is overridden with a proxy object to interact with the ALS. And request context is done in a similar way. Outside of a request context is not possible. How does code run outside of a request context? It doesn't, everything should be run within the context of a request if it is supposed to depend on that request's context. Maybe you're trying to do something build-time related, in which case it will be up to how your framework does things at build-time. But run-time, everything runs within the lifecycle of a request.
Ping for toast
Ping for toast4mo ago
How does code run outside of a request context?
This would make sense, but when I start miniflare it crashes instantly. The command run was pnpm build && wrangler pages dev dist/ --compatibility-flags nodejs_compat
No description
Ping for toast
Ping for toast4mo ago
so maybe there's a bug with miniflare, because I agree that it makes sense that code shouldn't be running outside of a request context
Hello, I’m Allie!
How are you ensuring that this code isn't running outside of the request context?
Ping for toast
Ping for toast4mo ago
Well 1. Wouldn't all code technically be running inside of a request context as cloudflare doesn't have long running processes? but more importantly 2. The specific answer is I'm not ensuring this. Its plainly imported import client from "../../../server/queries/index"; which causes this code to run on import. But the thing is that its running again whenever I get a req, as I would expect because workers isn't long running
Ping for toast
Ping for toast4mo ago
No description
Ping for toast
Ping for toast4mo ago
for all I know this is a miniflare problem and doesn't happen in prod
Hello, I’m Allie!
A Worker is given a few milliseconds to boot up before an event is actually passed in. In this case, the outcome is expected, as the variables are not available in the bootup phase
Ping for toast
Ping for toast4mo ago
but the miliseconds to bootup you describe never happened. When the server started, this code ran without a request, and caused the entire server to crash. Subsequent requests, despite theoretically running before being passed an event, correctly have the env set
Hello, I’m Allie!
That sounds right. The bootup runs once, so it fails on the first request, but on subsequent ones it passes
Want results from more Discord servers?
Add your server