🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

env-runner

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

env-runner - npm Package Compare versions

Comparing version
0.1.13
to
0.1.14
+32
-1
dist/_chunks/miniflare-runner.d.mts

@@ -5,2 +5,4 @@ import { WorkerHooks } from "./types.mjs";

import { Socket } from "node:net";
/** Raw (snake_case) Wrangler config object, mirroring `wrangler.json` contents. */
type WranglerInlineConfig = Record<string, unknown>;
/** Result from a module transform (compatible with Vite's `TransformResult`). */

@@ -56,2 +58,31 @@ interface TransformResult {

exportConditions?: string[];
/**
* Load a Cloudflare `wrangler` config to populate Miniflare options
* (compatibility date/flags and bindings: `vars`, KV, R2, D1, Durable
* Objects, queues).
*
* - `true` — auto-discover `wrangler.{json,jsonc,toml}` next to the entry
* file, then in the current working directory.
* - `string` — explicit path to a wrangler config file.
* - `object` — an inline raw (snake_case) wrangler config, as you would
* write in `wrangler.json` (no file needed). A config file is still
* auto-discovered (next to the entry, then cwd) and the inline config is
* merged on top of it (inline wins per key, binding records merge,
* `compatibilityFlags` are unioned).
*
* The installed `wrangler` package is preferred (full fidelity: TOML,
* `env` inheritance, `.dev.vars`, every binding type; an inline config is
* normalized through a short-lived temp file). When `wrangler` is not
* installed, a built-in minimal reader handles plain JSON files and inline
* objects (common fields only) and a one-time warning is logged. JSONC and
* TOML files without `wrangler` are skipped with a warning. Values from
* `miniflareOptions` always win over config-derived ones; binding records
* (e.g. `bindings`) merge per key and `compatibilityFlags` are unioned.
*/
wrangler?: boolean | string | WranglerInlineConfig;
/**
* Wrangler environment (`--env`) to select when loading the config.
* Defaults to the `CLOUDFLARE_ENV` environment variable.
*/
wranglerEnv?: string;
}

@@ -98,2 +129,2 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {

}
export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult };
export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, WranglerInlineConfig };

@@ -9,5 +9,6 @@ import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";

import { fileURLToPath } from "node:url";
import { readFileSync } from "node:fs";
import { basename, dirname, isAbsolute, resolve } from "node:path";
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
import { resolveModulePath } from "exsolve";
import { tmpdir } from "node:os";
const IPC_PATH = "/__env_runner_ipc";

@@ -183,2 +184,176 @@ const IPC_BINDING = "__ENV_RUNNER_IPC";

}
let _warnedNoWrangler = false;
const WRANGLER_CONFIG_FILENAMES = [
"wrangler.json",
"wrangler.jsonc",
"wrangler.toml"
];
const WRANGLER_OPTION_DENYLIST = new Set([
"name",
"script",
"scriptPath",
"modules",
"modulesRoot",
"modulesRules",
"unsafeDirectSockets",
"unsafeEvalBinding",
"unsafeModuleFallbackService",
"unsafeUseModuleFallbackService"
]);
function isPlainObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isInlineWranglerConfig(opt) {
return isPlainObject(opt);
}
async function loadWranglerConfig(opt, env, entryPath) {
if (!opt) return;
const inline = isInlineWranglerConfig(opt) ? opt : void 0;
let configPath;
if (typeof opt === "string") {
configPath = resolve(opt);
if (!existsSync(configPath)) {
console.warn(`[env-runner] wrangler config requested but not found at "${configPath}"`);
return;
}
} else if (opt === true) {
configPath = findWranglerConfig(entryPath);
if (!configPath) {
console.warn("[env-runner] wrangler config requested but none found near the entry or cwd");
return;
}
} else configPath = findWranglerConfig(entryPath);
let wrangler;
try {
wrangler = await import("wrangler");
} catch {
if (!_warnedNoWrangler) {
_warnedNoWrangler = true;
console.warn("[env-runner] 'wrangler' is not installed; using the built-in minimal config reader (plain JSON, common fields only). Install 'wrangler' for full fidelity (JSONC, TOML, env inheritance, all binding types).");
}
return mergeWranglerMiniflareOptions(configPath ? readWranglerConfigMinimal(configPath, env) : void 0, inline ? mapWranglerConfigToMiniflare(applyWranglerEnv(inline, env)) : void 0);
}
try {
return mergeWranglerMiniflareOptions(configPath ? pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(wrangler.unstable_readConfig({
config: configPath,
env
}, { hideWarnings: true }), env).workerOptions) : void 0, inline ? pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(readInlineWranglerConfig(wrangler, inline, env), env).workerOptions) : void 0);
} catch (error) {
const desc = [configPath && `"${configPath}"`, inline && "(inline)"].filter(Boolean).join(" + ");
console.warn(`[env-runner] failed to load wrangler config ${desc}: ${error.message}`);
return;
}
}
function mergeWranglerMiniflareOptions(base, override) {
if (!base) return override;
if (!override) return base;
const out = { ...base };
for (const [key, value] of Object.entries(override)) {
const prev = out[key];
if (Array.isArray(value) && Array.isArray(prev)) out[key] = [...new Set([...prev, ...value])];
else if (isPlainObject(value) && isPlainObject(prev)) out[key] = {
...prev,
...value
};
else out[key] = value;
}
return out;
}
function readInlineWranglerConfig(wrangler, inline, env) {
const dir = mkdtempSync(join(tmpdir(), "env-runner-wrangler-"));
const file = join(dir, "wrangler.json");
try {
writeFileSync(file, JSON.stringify(inline));
return wrangler.unstable_readConfig({
config: file,
env
}, { hideWarnings: true });
} finally {
rmSync(dir, {
recursive: true,
force: true
});
}
}
function findWranglerConfig(entryPath) {
const dirs = [];
if (entryPath) {
const resolved = isAbsolute(entryPath) ? entryPath : resolve(entryPath);
dirs.push(dirname(resolved));
}
dirs.push(process.cwd());
for (const dir of dirs) for (const name of WRANGLER_CONFIG_FILENAMES) {
const candidate = join(dir, name);
if (existsSync(candidate)) return candidate;
}
}
function pickWranglerMiniflareOptions(workerOptions) {
const out = {};
for (const [key, value] of Object.entries(workerOptions)) {
if (value === void 0 || WRANGLER_OPTION_DENYLIST.has(key)) continue;
out[key] = value;
}
return Object.keys(out).length > 0 ? out : void 0;
}
function readWranglerConfigMinimal(configPath, env) {
if (extname(configPath).toLowerCase() !== ".json") {
console.warn(`[env-runner] reading "${basename(configPath)}" requires the 'wrangler' package; the built-in reader supports plain JSON only (install 'wrangler' for JSONC/TOML).`);
return;
}
let raw;
try {
raw = readFileSync(configPath, "utf8");
} catch {
return;
}
let config;
try {
config = JSON.parse(raw);
} catch (error) {
console.warn(`[env-runner] failed to parse wrangler config "${configPath}": ${error.message}`);
return;
}
return mapWranglerConfigToMiniflare(applyWranglerEnv(config, env));
}
function applyWranglerEnv(config, env) {
return env && config.env?.[env] ? {
...config,
...config.env[env]
} : config;
}
function mapWranglerConfigToMiniflare(config) {
const out = {};
if (typeof config.compatibility_date === "string") out.compatibilityDate = config.compatibility_date;
if (Array.isArray(config.compatibility_flags)) out.compatibilityFlags = config.compatibility_flags;
if (config.vars && typeof config.vars === "object") out.bindings = { ...config.vars };
const kv = mapBindingArray(config.kv_namespaces, "binding", (n) => n.id ?? n.binding);
if (kv) out.kvNamespaces = kv;
const r2 = mapBindingArray(config.r2_buckets, "binding", (n) => n.bucket_name ?? n.binding);
if (r2) out.r2Buckets = r2;
const d1 = mapBindingArray(config.d1_databases, "binding", (n) => n.database_id ?? n.preview_database_id ?? n.binding);
if (d1) out.d1Databases = d1;
const queues = mapBindingArray(config.queues?.producers, "binding", (n) => n.queue);
if (queues) out.queueProducers = queues;
if (Array.isArray(config.durable_objects?.bindings)) {
const dos = {};
for (const b of config.durable_objects.bindings) {
if (!b?.name || !b?.class_name) continue;
dos[b.name] = b.script_name ? {
className: b.class_name,
scriptName: b.script_name
} : b.class_name;
}
if (Object.keys(dos).length > 0) out.durableObjects = dos;
}
return Object.keys(out).length > 0 ? out : void 0;
}
function mapBindingArray(arr, keyField, value) {
if (!Array.isArray(arr)) return;
const out = {};
for (const entry of arr) {
const key = entry?.[keyField];
if (typeof key === "string") out[key] = value(entry);
}
return Object.keys(out).length > 0 ? out : void 0;
}
const _miniflareCache = /* @__PURE__ */ new Map();

@@ -199,2 +374,4 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {

#exportConditions;
#wrangler;
#wranglerEnv;
constructor(opts) {

@@ -211,2 +388,4 @@ super({

this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
this.#wrangler = opts.wrangler ?? false;
this.#wranglerEnv = opts.wranglerEnv ?? process.env.CLOUDFLARE_ENV;
this._initWithVirtualData(() => this.#init());

@@ -342,13 +521,20 @@ }

async #initAsync() {
const { Miniflare } = await import("miniflare");
const { Miniflare, supportedCompatibilityDate } = await import("miniflare");
const entryPath = this._data?.entry;
const virtual = await this.#prepareVirtualModules();
this.#virtual = virtual;
const wranglerOptions = await loadWranglerConfig(this.#wrangler, this.#wranglerEnv, entryPath);
const userFlags = this.#miniflareOptions.compatibilityFlags || [];
const wranglerFlags = wranglerOptions?.compatibilityFlags || [];
const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
const options = {
compatibilityDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
compatibilityDate: supportedCompatibilityDate,
modules: true,
...wranglerOptions,
...this.#miniflareOptions,
compatibilityFlags: [...new Set(["nodejs_compat", ...userFlags])],
compatibilityFlags: [...new Set([
"nodejs_compat",
...wranglerFlags,
...userFlags
])],
unsafeDirectSockets: [{

@@ -359,2 +545,9 @@ host: "127.0.0.1",

};
if (wranglerOptions) for (const [key, wValue] of Object.entries(wranglerOptions)) {
const uValue = this.#miniflareOptions[key];
if (isPlainObject(wValue) && isPlainObject(uValue)) options[key] = {
...wValue,
...uValue
};
}
if (entryPath && !options.script && !options.scriptPath) {

@@ -361,0 +554,0 @@ const entryIsVirtual = isVirtualSpecifier(entryPath, virtual);

+2
-2
import { EnvRunnerData } from "../../_chunks/common-base-runner.mjs";
import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult } from "../../_chunks/miniflare-runner.mjs";
export { MiniflareEnvRunner, type EnvRunnerData as MiniflareEnvRunnerData, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult };
import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, WranglerInlineConfig } from "../../_chunks/miniflare-runner.mjs";
export { MiniflareEnvRunner, type EnvRunnerData as MiniflareEnvRunnerData, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult, type WranglerInlineConfig };
{
"name": "env-runner",
"version": "0.1.13",
"version": "0.1.14",
"description": "Generic environment runner for JavaScript runtimes.",

@@ -67,3 +67,4 @@ "license": "MIT",

"typescript": "^6.0.3",
"vitest": "^4.1.8"
"vitest": "^4.1.8",
"wrangler": "^4.99.0"
},

@@ -73,3 +74,4 @@ "peerDependencies": {

"@vercel/queue": "^0.2.0",
"miniflare": "^4.20260515.0"
"miniflare": "^4.20260515.0",
"wrangler": "^4.0.0"
},

@@ -85,2 +87,5 @@ "peerDependenciesMeta": {

"optional": true
},
"wrangler": {
"optional": true
}

@@ -87,0 +92,0 @@ },

@@ -313,2 +313,41 @@ # env-runner

When you don't set a `compatibilityDate` (via `miniflareOptions` or a wrangler config), it defaults to the date supported by the installed `workerd` binary rather than today's date — the binary always lags the calendar slightly, and pinning a future date makes `workerd` refuse to start.
#### Wrangler Config
Set the `wrangler` option to load a Cloudflare [Wrangler config](https://developers.cloudflare.com/workers/wrangler/configuration/) (`wrangler.json` / `wrangler.jsonc` / `wrangler.toml`) into the Miniflare options — compatibility date/flags and bindings (`vars`, KV, R2, D1, Durable Objects, queues):
```ts
import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
await using runner = new MiniflareEnvRunner({
name: "my-worker",
data: { entry: "./worker.ts" },
wrangler: true, // auto-discover wrangler.{json,jsonc,toml} next to the entry, then cwd
// wrangler: "./config/wrangler.toml", // or an explicit path
// wranglerEnv: "production", // select a `[env.production]` block
});
```
`wranglerEnv` selects a named Wrangler environment (`--env`). When omitted, it defaults to the `CLOUDFLARE_ENV` environment variable, so `CLOUDFLARE_ENV=production` selects the `production` env without passing the option.
You can also pass an **inline** config object (raw `wrangler.json` shape) instead of (or in addition to) a file — handy for programmatic setups:
```ts
await using runner = new MiniflareEnvRunner({
name: "my-worker",
data: { entry: "./worker.ts" },
wrangler: {
compatibility_date: "2024-09-01",
compatibility_flags: ["nodejs_compat"],
vars: { GREETING: "hello" },
kv_namespaces: [{ binding: "MY_KV", id: "..." }],
},
});
```
When an inline config is passed, a `wrangler.{json,jsonc,toml}` file is still auto-discovered (next to the entry, then cwd) and loaded, and the inline config is **merged on top of it** — inline values win per key, binding records (e.g. `vars`) merge, and `compatibilityFlags` are unioned. This lets you keep a committed `wrangler` file and override a few fields programmatically.
When the [`wrangler`](https://www.npmjs.com/package/wrangler) package is installed (an optional peer dependency), it is used for full fidelity — TOML, `env` inheritance, `.dev.vars`, and every binding type. When `wrangler` is **not** installed, a built-in minimal reader handles plain JSON files and inline objects (common fields only) and logs a one-time warning; JSONC and TOML files are skipped with a warning (they need `wrangler` to parse). Values you pass in `miniflareOptions` always take precedence over config-derived ones — binding records (e.g. `bindings`) merge per key, and `compatibilityFlags` are merged.
#### Module Transform Pipeline

@@ -315,0 +354,0 @@