Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ members = [
"crates/perry-runtime",
"crates/perry-ffi",
"crates/perry-native-registration",
"crates/perry-ext-dotenv",
"crates/perry-ext-nanoid",
"crates/perry-ext-bcrypt",
"crates/perry-ext-argon2",
Expand Down Expand Up @@ -472,7 +471,6 @@ perry-dispatch = { path = "crates/perry-dispatch" }
perry-runtime = { path = "crates/perry-runtime", version = "0.5.1011", default-features = false }
perry-ffi = { path = "crates/perry-ffi", version = "0.5.1011" }
perry-native-registration = { path = "crates/perry-native-registration", version = "0.5.1534" }
perry-ext-dotenv = { path = "crates/perry-ext-dotenv" }
perry-ext-nanoid = { path = "crates/perry-ext-nanoid" }
perry-ext-bcrypt = { path = "crates/perry-ext-bcrypt" }
perry-ext-argon2 = { path = "crates/perry-ext-argon2" }
Expand Down
16 changes: 16 additions & 0 deletions changelog.d/10691-dotenv-native-binding-removal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Removed the native `dotenv` binding: `dotenv.parse(Buffer)` (the idiomatic
`dotenv.parse(fs.readFileSync(...))`) returned 0 keys, and `config()`
reported no error but populated neither `result.parsed` nor `process.env` —
a silent total no-op. `import dotenv from "dotenv"` (no
`perry.compilePackages` entry) now compiles the real npm package from
source, matching Node exactly.

Deleted both duplicate hand-written implementations
(`crates/perry-ext-dotenv` and `crates/perry-stdlib/src/dotenv.rs`, which
independently exported the same `js_dotenv_*` symbols — #10678) and removed
`"dotenv"` from `PERRY_NATIVE_EXTENSION_PACKAGES` so the real package's
source (including the `dotenv/config` auto-load subpath) reaches the module
walker instead of being skipped as "handled by native stdlib". Also fixed a
standalone-workspace release fixture
(`tests/release/packages/next-app-route/provider/stdlib/Cargo.toml`) that
referenced the now-deleted `bundled-dotenv` feature.
2 changes: 0 additions & 2 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ pub const NATIVE_MODULES: &[&str] = &[
"ws", // WebSocket client/server
"zlib", // (Node builtin) gzip/deflate/brotli/zstd compression
"crypto", // (Node builtin) hashing, HMAC, cipher, sign/verify, WebCrypto
"dotenv", // .env file loader
"dotenv/config", // dotenv's auto-load-on-import subpath
"nanoid", // compact URL-safe ID generation
"ethers", // Ethereum library (utils/wallet/ABI)
"mongodb", // MongoDB driver
Expand Down
22 changes: 0 additions & 22 deletions crates/perry-api-manifest/src/entries/part_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,28 +1111,6 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
),
method("nodemailer", "sendMail", true, None),
method("nodemailer", "verify", true, None),
method_sig("dotenv", "config", false, None, &[], TypeSpec::Any),
// `dotenv.parse(src)` — the native impl (`js_dotenv_parse`) has shipped
// since the module was added, but the manifest never registered the
// symbol, so the #463 gate compiled every call site to a deferred
// throw-on-reach error. Callers that wrap config loading in
// `try { … } catch {}` swallowed that throw and silently got no config
// at all, which is why this is registered as a data-loss fix, not a
// missing-feature one. The extern returns a JSON string; the dispatch
// row's `NR_OBJ_FROM_JSON_STR` pipes it through `js_json_parse` so the
// user-visible value is a real object.
method_sig(
"dotenv",
"parse",
false,
None,
&[ParamSpec::Named {
name: "src",
ty: TypeSpec::String,
optional: false,
}],
TypeSpec::Any,
),
method_sig(
"nanoid",
"nanoid",
Expand Down
42 changes: 0 additions & 42 deletions crates/perry-api-manifest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,48 +597,6 @@ mod tests {
assert!(matches!(arena.kind, ApiKind::Property));
}

/// `dotenv.parse` regression guard.
///
/// `js_dotenv_parse` has always been implemented and declared to codegen,
/// but the manifest only ever registered `dotenv.config`. That gap made
/// the #463 unimplemented-API gate fire for every `dotenv.parse(...)`
/// call site, which under the default (defer) policy compiles to a
/// throw-on-reach runtime error rather than a build failure. Callers that
/// load config inside `try { … } catch {}` — the common shape — swallowed
/// the throw and silently ran with no configuration at all.
#[test]
fn dotenv_parse_is_registered() {
let entry = module_has_symbol("dotenv", "parse")
.expect("dotenv.parse must be in the manifest — see js_dotenv_parse");
assert!(
matches!(
entry.kind,
ApiKind::Method {
has_receiver: false,
class_filter: None
}
),
"dotenv.parse must be a static module method, got {:?}",
entry.kind
);
assert_eq!(
entry.params.len(),
1,
"dotenv.parse takes exactly the source text"
);
assert!(
matches!(
entry.params[0],
ParamSpec::Named {
ty: TypeSpec::String,
..
}
),
"dotenv.parse's argument is the .env source string, got {:?}",
entry.params[0]
);
}

#[test]
fn buffer_inspect_max_bytes_is_manifest_property() {
let entry = module_has_symbol("node:buffer", "INSPECT_MAX_BYTES")
Expand Down
59 changes: 0 additions & 59 deletions crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,6 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[
args: &[],
ret: NR_GCPTR,
},
// ========== dotenv ==========
NativeModSig {
module: "dotenv",
has_receiver: false,
method: "config",
class_filter: None,
runtime: "js_dotenv_config",
args: &[],
ret: NR_F64,
},
// `dotenv.parse(src)` → the JSON string `js_dotenv_parse` builds, piped
// through `js_json_parse` by NR_OBJ_FROM_JSON_STR so TypeScript sees a
// real object (`{ FOO: "bar" }`), not the encoded string. Without this
// row the symbol fell through the #463 gate to a deferred runtime throw
// even though the native implementation was already linked in.
NativeModSig {
module: "dotenv",
has_receiver: false,
method: "parse",
class_filter: None,
runtime: "js_dotenv_parse",
args: &[NA_STR],
ret: NR_OBJ_FROM_JSON_STR,
},
// ========== nanoid ==========
// js_nanoid_sized(NaN) → size=0 → falls back to js_nanoid() (21-char default),
// so nanoid() and nanoid(N) both route through the same entry safely.
Expand Down Expand Up @@ -243,38 +219,3 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[
ret: NR_VOID,
},
];

#[cfg(test)]
mod tests {
use super::*;

/// `dotenv.parse` must dispatch to the native implementation and return a
/// real object.
///
/// `js_dotenv_parse` was declared to codegen and linked into every binary,
/// but had no dispatch row, so the #463 gate compiled each call site to a
/// deferred throw-on-reach error. `readConfigFile()`-shaped callers wrap
/// the call in `try { … } catch {}`, so the throw was swallowed and the
/// `.env` config silently never loaded.
///
/// The return kind matters as much as the row: `js_dotenv_parse` hands back
/// a JSON *string*, so only `NR_OBJ_FROM_JSON_STR` (which pipes it through
/// `js_json_parse`) makes `dotenv.parse(src).FOO` read a property instead
/// of indexing a string.
#[test]
fn dotenv_parse_dispatches_to_native_impl_as_an_object() {
let row = UTILS_CRYPTO_ROWS
.iter()
.find(|r| r.module == "dotenv" && r.method == "parse")
.expect("dotenv.parse needs a dispatch row");
assert_eq!(row.runtime, "js_dotenv_parse");
assert!(!row.has_receiver);
assert_eq!(row.class_filter, None);
assert!(matches!(row.args, [NativeArgKind::StrPtr]));
assert!(
matches!(row.ret, NativeRetKind::ObjFromJsonStr),
"dotenv.parse must be JSON-decoded into an object, got {:?}",
row.ret
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ pub(crate) fn declare_utilities(module: &mut LlModule) {
module.declare_function("js_commander_required_option", I64, &[I64, I64, I64, I64]);
module.declare_function("js_commander_version", I64, &[I64, I64]);

// ========== Dotenv ==========
module.declare_function("js_dotenv_config", DOUBLE, &[]);
module.declare_function("js_dotenv_config_path", DOUBLE, &[I64]);
module.declare_function("js_dotenv_parse", I64, &[I64]);

// ========== Date libs (dayjs/datefns/moment) ==========
module.declare_function("js_datefns_add_days", DOUBLE, &[DOUBLE, DOUBLE]);
module.declare_function("js_datefns_add_months", DOUBLE, &[DOUBLE, DOUBLE]);
Expand Down
4 changes: 2 additions & 2 deletions crates/perry-codegen/tests/manifest_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ fn every_native_module_has_at_least_one_manifest_entry() {
/// allowed list documents the exception so a future module that
/// genuinely lacks coverage doesn't sneak past CI by being added
/// here.
const SIDE_EFFECT_ONLY: &[&str] = &["dotenv/config"];
const SIDE_EFFECT_ONLY: &[&str] = &[];

let mut missing: Vec<&'static str> = Vec::new();
for &module in perry_api_manifest::NATIVE_MODULES {
Expand Down Expand Up @@ -267,7 +267,7 @@ fn cjs_style_node_builtins_have_default_entries() {
/// the sibling test above and excluded here too.
#[test]
fn every_well_known_binding_has_manifest_entry() {
const SIDE_EFFECT_ONLY: &[&str] = &["dotenv/config"];
const SIDE_EFFECT_ONLY: &[&str] = &[];

// Inline parse of well_known_bindings.toml — small enough that
// pulling in `toml` as a dev-dep just for this test would be
Expand Down
19 changes: 0 additions & 19 deletions crates/perry-ext-dotenv/Cargo.toml

This file was deleted.

Loading
Loading