Skip to content

Cloudflare media-provider *EnvVar options ignore process.env under Node adapters #2453

Description

@connorblack

Summary

The Cloudflare Images and Cloudflare Stream media-provider runtimes document *EnvVar options as runtime environment-variable names, but they resolve those names only from cloudflare:workers env. Under a Node adapter, exported process.env values are ignored and provider operations fail as missing credentials.

This is both an implementation defect for an advertised adapter-neutral config shape and a documentation ambiguity: the error says to set an environment variable, and the provider examples say the default/custom names are read at runtime, without saying that only Workers bindings are consulted.

Minimal reproduction (Node adapter)

// astro.config.mjs
import node from '@astrojs/node';
import { cloudflareImages } from '@emdash-cms/cloudflare';
import { defineConfig } from 'astro/config';
import emdash from 'emdash/astro';

export default defineConfig({
  adapter: node({ mode: 'standalone' }),
  integrations: [
    emdash({
      mediaProviders: [
        cloudflareImages({
          accountIdEnvVar: 'CF_ACCOUNT_ID',
          accountHashEnvVar: 'CF_IMAGES_ACCOUNT_HASH',
          apiTokenEnvVar: 'CF_IMAGES_TOKEN',
        }),
      ],
    }),
  ],
});

Export non-secret test values before starting Astro (real credentials are needed only to get past resolution):

CF_ACCOUNT_ID=test-account \
CF_IMAGES_ACCOUNT_HASH=test-hash \
CF_IMAGES_TOKEN=test-token \
astro dev

Open the media library or invoke a provider operation. Expected: the provider resolves the exported Node environment variables and reaches the API (which may reject these test values). Actual: resolution stops locally with Cloudflare Images: Missing CF_ACCOUNT_ID.

Cloudflare Stream has the same behavior for accountIdEnvVar and apiTokenEnvVar.

Current implementation

Both runtimes import env directly from cloudflare:workers and resolveEnvValue() reads only:

(env as Record<string, string | undefined>)[envVar]
  • packages/cloudflare/src/media/images-runtime.ts
  • packages/cloudflare/src/media/stream-runtime.ts

Direct config values work because they are checked first, but inlining an API token into the generated virtual:emdash/media-providers module is not an appropriate general workaround for secrets.

Expected behavior / candidate shape

Please preserve the existing precedence and Workers behavior:

  1. A direct config value remains authoritative when explicitly supplied.
  2. If a cloudflare:workers binding exists for the requested name, use it.
  3. Only when that Workers binding is absent, fall back to process.env[envVar] in runtimes where process is available.
  4. Otherwise throw the existing missing-variable error.

Conceptually:

const workersValue = (env as Record<string, string | undefined>)[envVar];
const nodeValue =
  typeof process !== 'undefined' ? process.env?.[envVar] : undefined;
const value = workersValue ?? nodeValue;

An adapter-generated env virtual module would also be reasonable if it avoids a direct Node-global reference. The key contract is that Workers bindings retain precedence and secrets remain runtime values rather than being serialized into generated source.

Documentation should explicitly distinguish Workers bindings from Node process.env and state the precedence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions