diff --git a/Cargo.lock b/Cargo.lock index 9ba6b3dab3..1be1a5a0e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,6 +801,7 @@ dependencies = [ "deno_graph", "deno_lint", "deno_net", + "deno_node", "deno_runtime", "deno_task_shell", "deno_url", @@ -1114,6 +1115,13 @@ dependencies = [ "trust-dns-resolver", ] +[[package]] +name = "deno_node" +version = "0.1.0" +dependencies = [ + "deno_core", +] + [[package]] name = "deno_ops" version = "0.24.0" @@ -1139,6 +1147,7 @@ dependencies = [ "deno_ffi", "deno_http", "deno_net", + "deno_node", "deno_tls", "deno_url", "deno_web", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e275e1d999..345260f11d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -31,6 +31,7 @@ deno_core = { version = "0.146.0", path = "../core" } deno_crypto = { version = "0.78.0", path = "../ext/crypto" } deno_fetch = { version = "0.87.0", path = "../ext/fetch" } deno_net = { version = "0.56.0", path = "../ext/net" } +deno_node = { version = "0.1.0", path = "../ext/node" } deno_url = { version = "0.64.0", path = "../ext/url" } deno_web = { version = "0.95.0", path = "../ext/web" } deno_webgpu = { version = "0.65.0", path = "../ext/webgpu" } diff --git a/cli/compat/mod.rs b/cli/compat/mod.rs index 98af2ea61b..df5174fd32 100644 --- a/cli/compat/mod.rs +++ b/cli/compat/mod.rs @@ -75,12 +75,18 @@ static NODE_COMPAT_URL: Lazy = Lazy::new(|| { static GLOBAL_URL_STR: Lazy = Lazy::new(|| format!("{}node/global.ts", NODE_COMPAT_URL.as_str())); +static PROCESS_URL_STR: Lazy = + Lazy::new(|| format!("{}node/process.ts", NODE_COMPAT_URL.as_str())); + pub static GLOBAL_URL: Lazy = Lazy::new(|| Url::parse(&GLOBAL_URL_STR).unwrap()); static MODULE_URL_STR: Lazy = Lazy::new(|| format!("{}node/module.ts", NODE_COMPAT_URL.as_str())); +static MODULE_ALL_URL_STR: Lazy = + Lazy::new(|| format!("{}node/module_all.ts", NODE_COMPAT_URL.as_str())); + pub static MODULE_URL: Lazy = Lazy::new(|| Url::parse(&MODULE_URL_STR).unwrap()); @@ -106,6 +112,46 @@ fn try_resolve_builtin_module(specifier: &str) -> Option { } } +#[allow(unused)] +pub async fn load_builtin_node_modules( + js_runtime: &mut JsRuntime, +) -> Result<(), AnyError> { + let source_code = &format!( + r#"(async function loadBuiltinNodeModules(moduleAllUrl, processUrl) {{ + const [moduleAll, processModule] = await Promise.all([ + import(moduleAllUrl), + import(processUrl) + ]); + Deno[Deno.internal].require.initializeCommonJs(moduleAll.default, processModule.default); + }})('{}', '{}');"#, + MODULE_ALL_URL_STR.as_str(), + PROCESS_URL_STR.as_str(), + ); + + let value = + js_runtime.execute_script(&located_script_name!(), source_code)?; + js_runtime.resolve_value(value).await?; + Ok(()) +} + +#[allow(unused)] +pub fn load_cjs_module_from_ext_node( + js_runtime: &mut JsRuntime, + module: &str, + main: bool, +) -> Result<(), AnyError> { + let source_code = &format!( + r#"(function loadCjsModule(module) {{ + Deno[Deno.internal].require.Module._load(module, null, {main}); + }})('{module}');"#, + main = main, + module = escape_for_single_quote_string(module), + ); + + js_runtime.execute_script(&located_script_name!(), source_code)?; + Ok(()) +} + pub fn load_cjs_module( js_runtime: &mut JsRuntime, module: &str, diff --git a/cli/tests/integration/compat_tests.rs b/cli/tests/integration/compat_tests.rs index ce89cc623f..e8e6316bff 100644 --- a/cli/tests/integration/compat_tests.rs +++ b/cli/tests/integration/compat_tests.rs @@ -172,3 +172,22 @@ fn native_modules_as_global_vars() { ); assert!(out.contains("true")); } + +#[test] +fn ext_node_cjs_execution() { + let (out, _err) = util::run_and_collect_output_with_args( + true, + vec![ + "run", + "-A", + "--unstable", + "--quiet", + "commonjs/init.js", + "./example.js", + ], + None, + Some(vec![("DENO_NODE_COMPAT_URL".to_string(), std_file_url())]), + false, + ); + assert!(out.contains("{ hello: \"world\" }")); +} diff --git a/cli/tests/testdata/commonjs/data.json b/cli/tests/testdata/commonjs/data.json new file mode 100644 index 0000000000..f2a886f39d --- /dev/null +++ b/cli/tests/testdata/commonjs/data.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} diff --git a/cli/tests/testdata/commonjs/example.js b/cli/tests/testdata/commonjs/example.js new file mode 100644 index 0000000000..7850780370 --- /dev/null +++ b/cli/tests/testdata/commonjs/example.js @@ -0,0 +1,11 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore no-undef +const processMod = require("process"); +const osMod = require("node:os"); +console.log("process.pid", processMod.pid); +console.log("os.EOL", osMod.EOL); +const leftPad = require("left-pad"); +const json = require("./data"); +console.log(json); +console.log(leftPad("foo", 5)); // => " foo" +console.log("main module", processMod.mainModule.filename); diff --git a/cli/tests/testdata/commonjs/init.js b/cli/tests/testdata/commonjs/init.js new file mode 100644 index 0000000000..142ae7c76f --- /dev/null +++ b/cli/tests/testdata/commonjs/init.js @@ -0,0 +1,17 @@ +import { fromFileUrl } from "../../../../test_util/std/path/mod.ts"; + +const DENO_NODE_COMPAT_URL = Deno.env.get("DENO_NODE_COMPAT_URL"); +const moduleAllUrl = `${DENO_NODE_COMPAT_URL}node/module_all.ts`; +const processUrl = `${DENO_NODE_COMPAT_URL}node/process.ts`; +let moduleName = import.meta.resolve(Deno.args[0]); +moduleName = fromFileUrl(moduleName); + +const [moduleAll, processModule] = await Promise.all([ + import(moduleAllUrl), + import(processUrl), +]); +Deno[Deno.internal].require.initializeCommonJs( + moduleAll.default, + processModule.default, +); +Deno[Deno.internal].require.Module._load(moduleName, null, true); diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs b/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs new file mode 100644 index 0000000000..0590d668d1 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs @@ -0,0 +1,74 @@ +// Copyright Jorge Bucaran. All rights reserved. MIT license. +let enabled = !("NO_COLOR" in process.env) && + ("FORCE_COLOR" in process.env || + process.platform === "win32" || + (process.stdout != null && + process.stdout.isTTY && + process.env.TERM && + process.env.TERM !== "dumb")); + +const raw = (open, close, searchRegex, replaceValue) => + (s) => + enabled + ? open + + (~(s += "").indexOf(close, 4) // skip opening \x1b[ + ? s.replace(searchRegex, replaceValue) + : s) + + close + : s; + +const init = (open, close) => { + return raw( + `\x1b[${open}m`, + `\x1b[${close}m`, + new RegExp(`\\x1b\\[${close}m`, "g"), + `\x1b[${open}m`, + ); +}; + +exports.options = Object.defineProperty({}, "enabled", { + get: () => enabled, + set: (value) => (enabled = value), +}); + +exports.reset = init(0, 0); +exports.bold = raw("\x1b[1m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[1m"); +exports.dim = raw("\x1b[2m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[2m"); +exports.italic = init(3, 23); +exports.underline = init(4, 24); +exports.inverse = init(7, 27); +exports.hidden = init(8, 28); +exports.strikethrough = init(9, 29); +exports.black = init(30, 39); +exports.red = init(31, 39); +exports.green = init(32, 39); +exports.yellow = init(33, 39); +exports.blue = init(34, 39); +exports.magenta = init(35, 39); +exports.cyan = init(36, 39); +exports.white = init(37, 39); +exports.gray = init(90, 39); +exports.bgBlack = init(40, 49); +exports.bgRed = init(41, 49); +exports.bgGreen = init(42, 49); +exports.bgYellow = init(43, 49); +exports.bgBlue = init(44, 49); +exports.bgMagenta = init(45, 49); +exports.bgCyan = init(46, 49); +exports.bgWhite = init(47, 49); +exports.blackBright = init(90, 39); +exports.redBright = init(91, 39); +exports.greenBright = init(92, 39); +exports.yellowBright = init(93, 39); +exports.blueBright = init(94, 39); +exports.magentaBright = init(95, 39); +exports.cyanBright = init(96, 39); +exports.whiteBright = init(97, 39); +exports.bgBlackBright = init(100, 49); +exports.bgRedBright = init(101, 49); +exports.bgGreenBright = init(102, 49); +exports.bgYellowBright = init(103, 49); +exports.bgBlueBright = init(104, 49); +exports.bgMagentaBright = init(105, 49); +exports.bgCyanBright = init(106, 49); +exports.bgWhiteBright = init(107, 49); diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/index.js b/cli/tests/testdata/commonjs/node_modules/colorette/index.js new file mode 100644 index 0000000000..6e83866f3a --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/colorette/index.js @@ -0,0 +1,75 @@ +// Copyright Jorge Bucaran. All rights reserved. MIT license. +// deno-lint-ignore-file no-undef no-control-regex +let enabled = !("NO_COLOR" in process.env) && + ("FORCE_COLOR" in process.env || + process.platform === "win32" || + (process.stdout != null && + process.stdout.isTTY && + process.env.TERM && + process.env.TERM !== "dumb")); + +const raw = (open, close, searchRegex, replaceValue) => + (s) => + enabled + ? open + + (~(s += "").indexOf(close, 4) // skip opening \x1b[ + ? s.replace(searchRegex, replaceValue) + : s) + + close + : s; + +const init = (open, close) => { + return raw( + `\x1b[${open}m`, + `\x1b[${close}m`, + new RegExp(`\\x1b\\[${close}m`, "g"), + `\x1b[${open}m`, + ); +}; + +export const options = Object.defineProperty({}, "enabled", { + get: () => enabled, + set: (value) => (enabled = value), +}); + +export const reset = init(0, 0); +export const bold = raw("\x1b[1m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[1m"); +export const dim = raw("\x1b[2m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[2m"); +export const italic = init(3, 23); +export const underline = init(4, 24); +export const inverse = init(7, 27); +export const hidden = init(8, 28); +export const strikethrough = init(9, 29); +export const black = init(30, 39); +export const red = init(31, 39); +export const green = init(32, 39); +export const yellow = init(33, 39); +export const blue = init(34, 39); +export const magenta = init(35, 39); +export const cyan = init(36, 39); +export const white = init(37, 39); +export const gray = init(90, 39); +export const bgBlack = init(40, 49); +export const bgRed = init(41, 49); +export const bgGreen = init(42, 49); +export const bgYellow = init(43, 49); +export const bgBlue = init(44, 49); +export const bgMagenta = init(45, 49); +export const bgCyan = init(46, 49); +export const bgWhite = init(47, 49); +export const blackBright = init(90, 39); +export const redBright = init(91, 39); +export const greenBright = init(92, 39); +export const yellowBright = init(93, 39); +export const blueBright = init(94, 39); +export const magentaBright = init(95, 39); +export const cyanBright = init(96, 39); +export const whiteBright = init(97, 39); +export const bgBlackBright = init(100, 49); +export const bgRedBright = init(101, 49); +export const bgGreenBright = init(102, 49); +export const bgYellowBright = init(103, 49); +export const bgBlueBright = init(104, 49); +export const bgMagentaBright = init(105, 49); +export const bgCyanBright = init(106, 49); +export const bgWhiteBright = init(107, 49); diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/package.json b/cli/tests/testdata/commonjs/node_modules/colorette/package.json new file mode 100644 index 0000000000..fdca1aa242 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/colorette/package.json @@ -0,0 +1,23 @@ +{ + "author": { + "name": "Jorge Bucaran" + }, + "description": "Color your terminal using pure idiomatic JavaScript.", + "exports": { + "./package.json": "./package.json", + ".": { + "require": "./index.cjs", + "import": "./index.js" + } + }, + "license": "MIT", + "main": "index.cjs", + "module": "index.js", + "name": "colorette", + "repository": { + "type": "git", + "url": "git+https://github.com/jorgebucaran/colorette.git" + }, + "type": "module", + "version": "1.2.1" +} diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js new file mode 100644 index 0000000000..3ebd222ea9 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js @@ -0,0 +1,6 @@ +import dep from "#dep"; + +export default { + bar: "bar", + dep, +}; diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js new file mode 100644 index 0000000000..76716a3ef4 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js @@ -0,0 +1,3 @@ +export default { + polyfill: "import", +}; diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json b/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json new file mode 100644 index 0000000000..5d26359db3 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0", + "name": "imports_exports", + "main": "./require_export.cjs", + "imports": { + "#dep": { + "import": "./import_polyfill.js", + "require": "./require_polyfill.js" + } + }, + "exports": { + ".": { + "import": "./import_export.js", + "require": "./require_export.cjs" + } + } +} diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs new file mode 100644 index 0000000000..48923b8d80 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs @@ -0,0 +1,6 @@ +const dep = require("#dep"); + +module.exports = { + foo: "foo", + dep, +}; diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js new file mode 100644 index 0000000000..1023fd65cb --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js @@ -0,0 +1,3 @@ +module.exports = { + polyfill: "require", +}; diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/README.md b/cli/tests/testdata/commonjs/node_modules/left-pad/README.md new file mode 100644 index 0000000000..0e70d461e2 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/left-pad/README.md @@ -0,0 +1,41 @@ +## left-pad + +String left pad + +[![Build Status][travis-image]][travis-url] + +## Install + +```bash +$ npm install left-pad +``` + +## Usage + +```js +const leftPad = require("left-pad"); + +leftPad("foo", 5); +// => " foo" + +leftPad("foobar", 6); +// => "foobar" + +leftPad(1, 2, "0"); +// => "01" + +leftPad(17, 5, 0); +// => "00017" +``` + +**NOTE:** The third argument should be a single `char`. However the module +doesn't throw an error if you supply more than one `char`s. See +[#28](https://github.com/stevemao/left-pad/pull/28). + +**NOTE:** Characters having code points outside of +[BMP plan](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane) +are considered a two distinct characters. See +[#58](https://github.com/stevemao/left-pad/issues/58). + +[travis-image]: https://travis-ci.org/stevemao/left-pad.svg?branch=master +[travis-url]: https://travis-ci.org/stevemao/left-pad diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/index.js b/cli/tests/testdata/commonjs/node_modules/left-pad/index.js new file mode 100644 index 0000000000..a439e91de5 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/left-pad/index.js @@ -0,0 +1,54 @@ +// deno-lint-ignore-file + +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://www.wtfpl.net/ for more details. */ +"use strict"; +module.exports = leftPad; + +var cache = [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +]; + +function leftPad(str, len, ch) { + // convert `str` to a `string` + str = str + ""; + // `len` is the `pad`'s length now + len = len - str.length; + // doesn't need to pad + if (len <= 0) return str; + // `ch` defaults to `' '` + if (!ch && ch !== 0) ch = " "; + // convert `ch` to a `string` cuz it could be a number + ch = ch + ""; + // cache common use cases + if (ch === " " && len < 10) return cache[len] + str; + // `pad` starts with an empty string + var pad = ""; + // loop + while (true) { + // add `ch` to `pad` if `len` is odd + if (len & 1) pad += ch; + // divide `len` by 2, ditch the remainder + len >>= 1; + // "double" the `ch` so this operation count grows logarithmically on `len` + // each time `ch` is "doubled", the `len` would need to be "doubled" too + // similar to finding a value in binary search tree, hence O(log(n)) + if (len) ch += ch; + // `len` is 0, exit the loop + else break; + } + // pad `str`! + return pad + str; +} diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/package.json b/cli/tests/testdata/commonjs/node_modules/left-pad/package.json new file mode 100644 index 0000000000..57be042717 --- /dev/null +++ b/cli/tests/testdata/commonjs/node_modules/left-pad/package.json @@ -0,0 +1,68 @@ +{ + "_from": "left-pad", + "_id": "left-pad@1.3.0", + "_inBundle": false, + "_integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "_location": "/left-pad", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "left-pad", + "name": "left-pad", + "escapedName": "left-pad", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "_shasum": "5b8a3a7765dfe001261dde915589e782f8c94d1e", + "_spec": "left-pad", + "_where": "/Users/kun/Projects/Deno/deno/std/node/tests", + "author": { + "name": "azer" + }, + "bugs": { + "url": "https://github.com/stevemao/left-pad/issues" + }, + "bundleDependencies": false, + "deprecated": "use String.prototype.padStart()", + "description": "String left pad", + "devDependencies": { + "benchmark": "^2.1.0", + "fast-check": "0.0.8", + "tape": "*" + }, + "homepage": "https://github.com/stevemao/left-pad#readme", + "keywords": [ + "leftpad", + "left", + "pad", + "padding", + "string", + "repeat" + ], + "license": "WTFPL", + "main": "index.js", + "maintainers": [ + { + "name": "Cameron Westland", + "email": "camwest@gmail.com" + } + ], + "name": "left-pad", + "repository": { + "url": "git+ssh://git@github.com/stevemao/left-pad.git", + "type": "git" + }, + "scripts": { + "bench": "node perf/perf.js", + "test": "node test" + }, + "types": "index.d.ts", + "version": "1.3.0" +} diff --git a/cli/tests/testdata/commonjs/package.json b/cli/tests/testdata/commonjs/package.json new file mode 100644 index 0000000000..9ce08a9003 --- /dev/null +++ b/cli/tests/testdata/commonjs/package.json @@ -0,0 +1,17 @@ +{ + "name": "example", + "version": "0.0.1", + "description": "", + "main": "example.js", + "dependencies": { + "colorette": "1.2.1", + "left-pad": "1.3.0", + "imports_exports": "1.0.0" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/ext/node/01_require.js b/ext/node/01_require.js new file mode 100644 index 0000000000..5a29617b1a --- /dev/null +++ b/ext/node/01_require.js @@ -0,0 +1,815 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file + +"use strict"; + +((window) => { + const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ObjectGetOwnPropertyDescriptor, + ObjectGetPrototypeOf, + ObjectEntries, + ObjectPrototypeHasOwnProperty, + ObjectSetPrototypeOf, + ObjectKeys, + ObjectCreate, + SafeMap, + SafeWeakMap, + JSONParse, + StringPrototypeEndsWith, + StringPrototypeIndexOf, + StringPrototypeSlice, + StringPrototypeStartsWith, + StringPrototypeCharCodeAt, + RegExpPrototypeTest, + } = window.__bootstrap.primordials; + const core = window.Deno.core; + + // Map used to store CJS parsing data. + const cjsParseCache = new SafeWeakMap(); + + function pathDirname(filepath) { + return core.opSync("op_require_path_dirname", filepath); + } + + function pathResolve(...args) { + return core.opSync("op_require_path_resolve", args); + } + + function assert(cond) { + if (!cond) { + throw Error("assert"); + } + } + + // TODO: + function isProxy() { + return false; + } + + // TODO(bartlomieju): verify in other parts of this file that + // we have initialized the system before making APIs work + let cjsInitialized = false; + let processGlobal = null; + const nativeModulePolyfill = new SafeMap(); + const nativeModuleExports = ObjectCreate(null); + + const relativeResolveCache = ObjectCreate(null); + let requireDepth = 0; + let statCache = null; + let isPreloading = false; + let mainModule = null; + + function stat(filename) { + // TODO: required only on windows + // filename = path.toNamespacedPath(filename); + if (statCache !== null) { + const result = statCache.get(filename); + if (result !== undefined) { + return result; + } + } + const result = core.opSync("op_require_stat", filename); + if (statCache !== null && result >= 0) { + statCache.set(filename, result); + } + + return result; + } + + function updateChildren(parent, child, scan) { + if (!parent) { + return; + } + + const children = parent.children; + if (children && !(scan && ArrayPrototypeIncludes(children, child))) { + ArrayPrototypePush(children, child); + } + } + + function tryFile(requestPath, _isMain) { + const rc = stat(requestPath); + if (rc !== 0) return; + return toRealPath(requestPath); + } + + function tryPackage(requestPath, exts, isMain, originalPath) { + // const pkg = readPackage(requestPath)?.main; + let pkg = false; + + if (!pkg) { + return tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, + ); + } + + const filename = path.resolve(requestPath, pkg); + let actual = tryFile(filename, isMain) || + tryExtensions(filename, exts, isMain) || + tryExtensions( + pathResolve(filename, "index"), + exts, + isMain, + ); + if (actual === false) { + actual = tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, + ); + if (!actual) { + // eslint-disable-next-line no-restricted-syntax + const err = new Error( + `Cannot find module '${filename}'. ` + + 'Please verify that the package.json has a valid "main" entry', + ); + err.code = "MODULE_NOT_FOUND"; + err.path = pathResolve( + requestPath, + "package.json", + ); + err.requestPath = originalPath; + throw err; + } else { + const jsonPath = pathResolve( + requestPath, + "package.json", + ); + process.emitWarning( + `Invalid 'main' field in '${jsonPath}' of '${pkg}'. ` + + "Please either fix that or report it to the module author", + "DeprecationWarning", + "DEP0128", + ); + } + } + return actual; + } + + const realpathCache = new SafeMap(); + function toRealPath(requestPath) { + const maybeCached = realpathCache.get(requestPath); + if (maybeCached) { + return maybeCached; + } + const rp = core.opSync("op_require_real_path", requestPath); + realpathCache.set(requestPath, rp); + return rp; + } + + function tryExtensions(p, exts, isMain) { + for (let i = 0; i < exts.length; i++) { + const filename = tryFile(p + exts[i], isMain); + + if (filename) { + return filename; + } + } + return false; + } + + // Find the longest (possibly multi-dot) extension registered in + // Module._extensions + function findLongestRegisteredExtension(filename) { + const name = core.opSync("op_require_path_basename", filename); + let currentExtension; + let index; + let startIndex = 0; + while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) { + startIndex = index + 1; + if (index === 0) continue; // Skip dotfiles like .gitignore + currentExtension = StringPrototypeSlice(name, index); + if (Module._extensions[currentExtension]) { + return currentExtension; + } + } + return ".js"; + } + + function getExportsForCircularRequire(module) { + if ( + module.exports && + !isProxy(module.exports) && + ObjectGetPrototypeOf(module.exports) === ObjectPrototype && + // Exclude transpiled ES6 modules / TypeScript code because those may + // employ unusual patterns for accessing 'module.exports'. That should + // be okay because ES6 modules have a different approach to circular + // dependencies anyway. + !module.exports.__esModule + ) { + // This is later unset once the module is done loading. + ObjectSetPrototypeOf( + module.exports, + CircularRequirePrototypeWarningProxy, + ); + } + + return module.exports; + } + + // A Proxy that can be used as the prototype of a module.exports object and + // warns when non-existent properties are accessed. + const CircularRequirePrototypeWarningProxy = new Proxy({}, { + get(target, prop) { + // Allow __esModule access in any case because it is used in the output + // of transpiled code to determine whether something comes from an + // ES module, and is not used as a regular key of `module.exports`. + if (prop in target || prop === "__esModule") return target[prop]; + // TODO: + // emitCircularRequireWarning(prop); + console.log("TODO: emitCircularRequireWarning"); + return undefined; + }, + + getOwnPropertyDescriptor(target, prop) { + if ( + ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" + ) { + return ObjectGetOwnPropertyDescriptor(target, prop); + } + // TODO: + // emitCircularRequireWarning(prop); + console.log("TODO: emitCircularRequireWarning"); + return undefined; + }, + }); + + const moduleParentCache = new SafeWeakMap(); + function Module(id = "", parent) { + this.id = id; + this.path = pathDirname(id); + this.exports = {}; + moduleParentCache.set(this, parent); + updateChildren(parent, this, false); + this.filename = null; + this.loaded = false; + this.children = []; + } + + const builtinModules = []; + // TODO(bartlomieju): handle adding native modules + Module.builtinModules = builtinModules; + + Module._extensions = Object.create(null); + Module._cache = Object.create(null); + Module._pathCache = Object.create(null); + let modulePaths = []; + Module.globalPaths = modulePaths; + + const CHAR_FORWARD_SLASH = 47; + const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/; + Module._findPath = function (request, paths, isMain) { + const absoluteRequest = core.opSync("op_require_path_is_absolute", request); + if (absoluteRequest) { + paths = [""]; + } else if (!paths || paths.length === 0) { + return false; + } + + const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00"); + const entry = Module._pathCache[cacheKey]; + if (entry) { + return entry; + } + + let exts; + let trailingSlash = request.length > 0 && + StringPrototypeCharCodeAt(request, request.length - 1) === + CHAR_FORWARD_SLASH; + if (!trailingSlash) { + trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request); + } + + // For each path + for (let i = 0; i < paths.length; i++) { + // Don't search further if path doesn't exist + const curPath = paths[i]; + if (curPath && stat(curPath) < 1) continue; + + if (!absoluteRequest) { + const exportsResolved = false; + // TODO: + console.log("TODO: Module._findPath resolveExports"); + // const exportsResolved = resolveExports(curPath, request); + if (exportsResolved) { + return exportsResolved; + } + } + + const basePath = pathResolve(curPath, request); + let filename; + + const rc = stat(basePath); + if (!trailingSlash) { + if (rc === 0) { // File. + if (!isMain) { + filename = toRealPath(basePath); + } else { + filename = toRealPath(basePath); + } + } + + if (!filename) { + // Try it with each of the extensions + if (exts === undefined) { + exts = ObjectKeys(Module._extensions); + } + filename = tryExtensions(basePath, exts, isMain); + } + } + + if (!filename && rc === 1) { // Directory. + // try it with each of the extensions at "index" + if (exts === undefined) { + exts = ObjectKeys(Module._extensions); + } + filename = tryPackage(basePath, exts, isMain, request); + } + + if (filename) { + Module._pathCache[cacheKey] = filename; + return filename; + } + } + + return false; + }; + + Module._nodeModulePaths = function (from) { + return core.opSync("op_require_node_module_paths", from); + }; + + Module._resolveLookupPaths = function (request, parent) { + return core.opSync( + "op_require_resolve_lookup_paths", + request, + parent?.paths, + parent?.filename ?? "", + ); + }; + + Module._load = function (request, parent, isMain) { + let relResolveCacheIdentifier; + if (parent) { + // Fast path for (lazy loaded) modules in the same directory. The indirect + // caching is required to allow cache invalidation without changing the old + // cache key names. + relResolveCacheIdentifier = `${parent.path}\x00${request}`; + const filename = relativeResolveCache[relResolveCacheIdentifier]; + if (filename !== undefined) { + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); + } + return cachedModule.exports; + } + delete relativeResolveCache[relResolveCacheIdentifier]; + } + } + + const filename = Module._resolveFilename(request, parent, isMain); + if (StringPrototypeStartsWith(filename, "node:")) { + // Slice 'node:' prefix + const id = StringPrototypeSlice(filename, 5); + + const module = loadNativeModule(id, id); + if (!module) { + // TODO: + // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); + throw new Error("Unknown built-in module"); + } + + return module.exports; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + const parseCachedModule = cjsParseCache.get(cachedModule); + if (!parseCachedModule || parseCachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); + } + parseCachedModule.loaded = true; + } else { + return cachedModule.exports; + } + } + + const mod = loadNativeModule(filename, request); + if ( + mod + ) { + return mod.exports; + } + + // Don't call updateChildren(), Module constructor already does. + const module = cachedModule || new Module(filename, parent); + + if (isMain) { + processGlobal.mainModule = module; + module.id = "."; + } + + Module._cache[filename] = module; + if (parent !== undefined) { + relativeResolveCache[relResolveCacheIdentifier] = filename; + } + + let threw = true; + try { + module.load(filename); + threw = false; + } finally { + if (threw) { + delete Module._cache[filename]; + if (parent !== undefined) { + delete relativeResolveCache[relResolveCacheIdentifier]; + const children = parent?.children; + if (ArrayIsArray(children)) { + const index = ArrayPrototypeIndexOf(children, module); + if (index !== -1) { + ArrayPrototypeSplice(children, index, 1); + } + } + } + } else if ( + module.exports && + !isProxy(module.exports) && + ObjectGetPrototypeOf(module.exports) === + CircularRequirePrototypeWarningProxy + ) { + ObjectSetPrototypeOf(module.exports, ObjectPrototype); + } + } + + return module.exports; + }; + + Module._resolveFilename = function ( + request, + parent, + isMain, + options, + ) { + if ( + StringPrototypeStartsWith(request, "node:") || + nativeModuleCanBeRequiredByUsers(request) + ) { + return request; + } + + let paths; + + if (typeof options === "object" && options !== null) { + if (ArrayIsArray(options.paths)) { + const isRelative = core.opSync( + "op_require_specifier_is_relative", + request, + ); + + if (isRelative) { + paths = options.paths; + } else { + const fakeParent = new Module("", null); + + paths = []; + + for (let i = 0; i < options.paths.length; i++) { + const path = options.paths[i]; + fakeParent.paths = Module._nodeModulePaths(path); + const lookupPaths = Module._resolveLookupPaths(request, fakeParent); + + for (let j = 0; j < lookupPaths.length; j++) { + if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) { + ArrayPrototypePush(paths, lookupPaths[j]); + } + } + } + } + } else if (options.paths === undefined) { + paths = Module._resolveLookupPaths(request, parent); + } else { + // TODO: + // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths); + throw new Error("Invalid arg value options.paths", options.path); + } + } else { + paths = Module._resolveLookupPaths(request, parent); + } + + if (parent?.filename) { + if (request[0] === "#") { + console.log("TODO: Module._resolveFilename with #specifier"); + // const pkg = readPackageScope(parent.filename) || {}; + // if (pkg.data?.imports != null) { + // try { + // return finalizeEsmResolution( + // packageImportsResolve( + // request, + // pathToFileURL(parent.filename), + // cjsConditions, + // ), + // parent.filename, + // pkg.path, + // ); + // } catch (e) { + // if (e.code === "ERR_MODULE_NOT_FOUND") { + // throw createEsmNotFoundErr(request); + // } + // throw e; + // } + // } + } + } + + // Try module self resolution first + // TODO(bartlomieju): make into a single op + const parentPath = core.opSync( + "op_require_try_self_parent_path", + !!parent, + parent?.filename, + parent?.id, + ); + // const selfResolved = core.opSync("op_require_try_self", parentPath, request); + const selfResolved = false; + if (selfResolved) { + const cacheKey = request + "\x00" + + (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00")); + Module._pathCache[cacheKey] = selfResolved; + return selfResolved; + } + + // Look up the filename first, since that's the cache key. + const filename = Module._findPath(request, paths, isMain, false); + if (filename) return filename; + const requireStack = []; + for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) { + ArrayPrototypePush(requireStack, cursor.filename || cursor.id); + } + let message = `Cannot find module '${request}'`; + if (requireStack.length > 0) { + message = message + "\nRequire stack:\n- " + + ArrayPrototypeJoin(requireStack, "\n- "); + } + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + err.code = "MODULE_NOT_FOUND"; + err.requireStack = requireStack; + throw err; + }; + + Module.prototype.load = function (filename) { + assert(!this.loaded); + this.filename = filename; + this.paths = Module._nodeModulePaths( + pathDirname(filename), + ); + const extension = findLongestRegisteredExtension(filename); + // allow .mjs to be overriden + if ( + StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"] + ) { + // TODO: use proper error class + throw new Error("require ESM", filename); + } + + Module._extensions[extension](this, filename); + this.loaded = true; + + // TODO: do caching + }; + + // Loads a module at the given file path. Returns that module's + // `exports` property. + Module.prototype.require = function (id) { + if (typeof id !== "string") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("Invalid argument type"); + } + + if (id === "") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("id must be non empty"); + } + requireDepth++; + try { + return Module._load(id, this, /* isMain */ false); + } finally { + requireDepth--; + } + }; + + Module.wrapper = [ + // TODO: + // We provide non standard timer APIs in the CommonJS wrapper + // to avoid exposing them in global namespace. + "(function (exports, require, module, __filename, __dirname, setTimeout, clearTimeout, setInterval, clearInterval) { (function (exports, require, module, __filename, __dirname) {", + "\n}).call(this, exports, require, module, __filename, __dirname); })", + ]; + Module.wrap = function (script) { + script = script.replace(/^#!.*?\n/, ""); + return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`; + }; + + function wrapSafe( + filename, + content, + _cjsModuleInstance, + ) { + const wrapper = Module.wrap(content); + const [f, err] = core.evalContext(wrapper, filename); + if (err) { + console.log("TODO: wrapSafe check if main module"); + throw err.thrown; + } + return f; + } + + Module.prototype._compile = function (content, filename) { + const compiledWrapper = wrapSafe(filename, content, this); + + const dirname = pathDirname(filename); + const require = makeRequireFunction(this); + const exports = this.exports; + const thisValue = exports; + const module = this; + if (requireDepth === 0) { + statCache = new SafeMap(); + } + const result = compiledWrapper.call( + thisValue, + exports, + require, + this, + filename, + dirname, + ); + if (requireDepth === 0) { + statCache = null; + } + return result; + }; + + Module._extensions[".js"] = function (module, filename) { + const content = core.opSync("op_require_read_file", filename); + + console.log(`TODO: Module._extensions[".js"] is ESM`); + + module._compile(content, filename); + }; + + function stripBOM(content) { + if (content.charCodeAt(0) === 0xfeff) { + content = content.slice(1); + } + return content; + } + + // Native extension for .json + Module._extensions[".json"] = function (module, filename) { + const content = core.opSync("op_require_read_file", filename); + + try { + module.exports = JSONParse(stripBOM(content)); + } catch (err) { + err.message = filename + ": " + err.message; + throw err; + } + }; + + // Native extension for .node + Module._extensions[".node"] = function (module, filename) { + throw new Error("not implemented loading .node files"); + }; + + function createRequireFromPath(filename) { + const proxyPath = core.opSync("op_require_proxy_path", filename); + const mod = new Module(proxyPath); + mod.filename = proxyPath; + mod.paths = Module._nodeModulePaths(mod.path); + return makeRequireFunction(mod); + } + + function makeRequireFunction(mod) { + const require = function require(path) { + return mod.require(path); + }; + + function resolve(request, options) { + return Module._resolveFilename(request, mod, false, options); + } + + require.resolve = resolve; + + function paths(request) { + return Module._resolveLookupPaths(request, mod); + } + + resolve.paths = paths; + require.main = mainModule; + // Enable support to add extra extension types. + require.extensions = Module._extensions; + require.cache = Module._cache; + + return require; + } + + function createRequire(filename) { + // FIXME: handle URLs and validation + return createRequireFromPath(filename); + } + + Module.createRequire = createRequire; + + Module._initPaths = function () { + const paths = core.opSync("op_require_init_paths"); + modulePaths = paths; + Module.globalPaths = ArrayPrototypeSlice(modulePaths); + }; + + Module.syncBuiltinESMExports = function syncBuiltinESMExports() { + throw new Error("not implemented"); + }; + + Module.Module = Module; + + const m = { + _cache: Module._cache, + _extensions: Module._extensions, + _findPath: Module._findPath, + _initPaths: Module._initPaths, + _load: Module._load, + _nodeModulePaths: Module._nodeModulePaths, + _pathCache: Module._pathCache, + _preloadModules: Module._preloadModules, + _resolveFilename: Module._resolveFilename, + _resolveLookupPaths: Module._resolveLookupPaths, + builtinModules: Module.builtinModules, + createRequire: Module.createRequire, + globalPaths: Module.globalPaths, + Module, + wrap: Module.wrap, + }; + + nativeModuleExports.module = m; + + function loadNativeModule(_id, request) { + if (nativeModulePolyfill.has(request)) { + return nativeModulePolyfill.get(request); + } + const modExports = nativeModuleExports[request]; + if (modExports) { + const nodeMod = new Module(request); + nodeMod.exports = modExports; + nodeMod.loaded = true; + nativeModulePolyfill.set(request, nodeMod); + return nodeMod; + } + return undefined; + } + + function nativeModuleCanBeRequiredByUsers(request) { + return !!nativeModuleExports[request]; + } + + function initializeCommonJs(nodeModules, process) { + assert(!cjsInitialized); + cjsInitialized = true; + for (const [name, exports] of ObjectEntries(nodeModules)) { + nativeModuleExports[name] = exports; + ArrayPrototypePush(Module.builtinModules, name); + } + processGlobal = process; + } + + function readPackageScope() { + throw new Error("not implemented"); + } + + window.__bootstrap.internals = { + ...window.__bootstrap.internals ?? {}, + require: { + Module, + wrapSafe, + toRealPath, + cjsParseCache, + readPackageScope, + initializeCommonJs, + }, + }; +})(globalThis); diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml new file mode 100644 index 0000000000..719de8032c --- /dev/null +++ b/ext/node/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_node" +version = "0.1.0" +authors = ["the Deno authors"] +edition = "2021" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "Node compatibility for Deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.146.0", path = "../../core" } diff --git a/ext/node/lib.rs b/ext/node/lib.rs new file mode 100644 index 0000000000..5d6542dc53 --- /dev/null +++ b/ext/node/lib.rs @@ -0,0 +1,317 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::include_js_files; +use deno_core::normalize_path; +use deno_core::op; +use deno_core::Extension; +use std::path::PathBuf; + +pub fn init() -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:ext/node", + "01_require.js", + )) + .ops(vec![ + op_require_init_paths::decl(), + op_require_node_module_paths::decl(), + op_require_proxy_path::decl(), + op_require_is_request_relative::decl(), + op_require_resolve_lookup_paths::decl(), + op_require_try_self_parent_path::decl(), + op_require_try_self::decl(), + op_require_real_path::decl(), + op_require_path_is_absolute::decl(), + op_require_path_dirname::decl(), + op_require_stat::decl(), + op_require_path_resolve::decl(), + op_require_path_basename::decl(), + op_require_read_file::decl(), + ]) + .build() +} + +#[op] +pub fn op_require_init_paths() -> Vec { + let (home_dir, node_path) = if cfg!(windows) { + ( + std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), + std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + ) + } else { + ( + std::env::var("HOME").unwrap_or_else(|_| "".into()), + std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + ) + }; + + let mut prefix_dir = std::env::current_exe().unwrap(); + if cfg!(windows) { + prefix_dir = prefix_dir.join("..").join("..") + } else { + prefix_dir = prefix_dir.join("..") + } + + let mut paths = vec![prefix_dir.join("lib").join("node")]; + + if !home_dir.is_empty() { + paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); + paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); + } + + let mut paths = paths + .into_iter() + .map(|p| p.to_string_lossy().to_string()) + .collect(); + + if !node_path.is_empty() { + let delimiter = if cfg!(windows) { ";" } else { ":" }; + let mut node_paths: Vec = node_path + .split(delimiter) + .filter(|e| !e.is_empty()) + .map(|s| s.to_string()) + .collect(); + node_paths.append(&mut paths); + paths = node_paths; + } + + paths +} + +#[op] +pub fn op_require_node_module_paths(from: String) -> Vec { + // Guarantee that "from" is absolute. + let from = deno_core::resolve_path(&from) + .unwrap() + .to_file_path() + .unwrap(); + + if cfg!(windows) { + // return root node_modules when path is 'D:\\'. + let from_str = from.to_str().unwrap(); + if from_str.len() >= 3 { + let bytes = from_str.as_bytes(); + if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' + { + let p = from_str.to_owned() + "node_modules"; + return vec![p]; + } + } + } else { + // Return early not only to avoid unnecessary work, but to *avoid* returning + // an array of two items for a root: [ '//node_modules', '/node_modules' ] + if from.to_string_lossy() == "/" { + return vec!["/node_modules".to_string()]; + } + } + + let mut paths = vec![]; + let mut current_path = from.as_path(); + let mut maybe_parent = Some(current_path); + while let Some(parent) = maybe_parent { + if !parent.ends_with("/node_modules") { + paths.push(parent.join("node_modules").to_string_lossy().to_string()); + current_path = parent; + maybe_parent = current_path.parent(); + } + } + + if !cfg!(windows) { + // Append /node_modules to handle root paths. + paths.push("/node_modules".to_string()); + } + + paths +} + +#[op] +fn op_require_proxy_path(filename: String) -> String { + // Allow a directory to be passed as the filename + let trailing_slash = if cfg!(windows) { + filename.ends_with('\\') + } else { + filename.ends_with('/') + }; + + if trailing_slash { + let p = PathBuf::from(filename); + p.join("noop.js").to_string_lossy().to_string() + } else { + filename + } +} + +#[op] +fn op_require_is_request_relative(request: String) -> bool { + if request.starts_with("./") { + return true; + } + + if request.starts_with("../") { + return true; + } + + if cfg!(windows) { + if request.starts_with(".\\") { + return true; + } + + if request.starts_with("..\\") { + return true; + } + } + + false +} + +#[op] +fn op_require_resolve_lookup_paths( + request: String, + maybe_parent_paths: Option>, + parent_filename: String, +) -> Option> { + if !request.starts_with('.') + || (request.len() > 1 + && !request.starts_with("..") + && !request.starts_with("./") + && (!cfg!(windows) || !request.starts_with(".\\"))) + { + let module_paths = vec![]; + let mut paths = module_paths; + if let Some(mut parent_paths) = maybe_parent_paths { + if !parent_paths.is_empty() { + paths.append(&mut parent_paths); + } + } + + if !paths.is_empty() { + return Some(paths); + } else { + return None; + } + } + + // In REPL, parent.filename is null. + // if (!parent || !parent.id || !parent.filename) { + // // Make require('./path/to/foo') work - normally the path is taken + // // from realpath(__filename) but in REPL there is no filename + // const mainPaths = ['.']; + + // debug('looking for %j in %j', request, mainPaths); + // return mainPaths; + // } + + let p = PathBuf::from(parent_filename); + Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) +} + +#[op] +fn op_require_path_is_absolute(p: String) -> bool { + PathBuf::from(p).is_absolute() +} + +#[op] +fn op_require_stat(filename: String) -> i32 { + if let Ok(metadata) = std::fs::metadata(&filename) { + if metadata.is_file() { + return 0; + } else { + return 1; + } + } + + -1 +} + +#[op] +fn op_require_real_path(request: String) -> Result { + let mut canonicalized_path = PathBuf::from(request).canonicalize()?; + if cfg!(windows) { + canonicalized_path = PathBuf::from( + canonicalized_path + .display() + .to_string() + .trim_start_matches("\\\\?\\"), + ); + } + Ok(canonicalized_path.to_string_lossy().to_string()) +} + +#[op] +fn op_require_path_resolve(parts: Vec) -> String { + assert!(!parts.is_empty()); + let mut p = PathBuf::from(&parts[0]); + if parts.len() > 1 { + for part in &parts[1..] { + p = p.join(part); + } + } + normalize_path(p).to_string_lossy().to_string() +} + +#[op] +fn op_require_path_dirname(request: String) -> String { + let p = PathBuf::from(request); + p.parent().unwrap().to_string_lossy().to_string() +} + +#[op] +fn op_require_path_basename(request: String) -> String { + let p = PathBuf::from(request); + p.file_name().unwrap().to_string_lossy().to_string() +} + +#[op] +fn op_require_try_self_parent_path( + has_parent: bool, + maybe_parent_filename: Option, + maybe_parent_id: Option, +) -> Option { + if !has_parent { + return None; + } + + if let Some(parent_filename) = maybe_parent_filename { + return Some(parent_filename); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + return Some(cwd.to_string_lossy().to_string()); + } + } + } + None +} + +#[op] +fn op_require_try_self( + has_parent: bool, + maybe_parent_filename: Option, + maybe_parent_id: Option, +) -> Option { + if !has_parent { + return None; + } + + if let Some(parent_filename) = maybe_parent_filename { + return Some(parent_filename); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + return Some(cwd.to_string_lossy().to_string()); + } + } + } + None +} + +#[op] +fn op_require_read_file(filename: String) -> Result { + let contents = std::fs::read_to_string(filename)?; + Ok(contents) +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 3c94e833a6..fb1b32846c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -30,6 +30,7 @@ deno_fetch = { version = "0.87.0", path = "../ext/fetch" } deno_ffi = { version = "0.51.0", path = "../ext/ffi" } deno_http = { version = "0.58.0", path = "../ext/http" } deno_net = { version = "0.56.0", path = "../ext/net" } +deno_node = { version = "0.1.0", path = "../ext/node" } deno_tls = { version = "0.51.0", path = "../ext/tls" } deno_url = { version = "0.64.0", path = "../ext/url" } deno_web = { version = "0.95.0", path = "../ext/web" } @@ -53,6 +54,7 @@ deno_fetch = { version = "0.87.0", path = "../ext/fetch" } deno_ffi = { version = "0.51.0", path = "../ext/ffi" } deno_http = { version = "0.58.0", path = "../ext/http" } deno_net = { version = "0.56.0", path = "../ext/net" } +deno_node = { version = "0.1.0", path = "../ext/node" } deno_tls = { version = "0.51.0", path = "../ext/tls" } deno_url = { version = "0.64.0", path = "../ext/url" } deno_web = { version = "0.95.0", path = "../ext/web" } diff --git a/runtime/build.rs b/runtime/build.rs index eea7a3602c..b389462b59 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -158,6 +158,7 @@ mod not_docs { deno_broadcast_channel::InMemoryBroadcastChannel::default(), false, // No --unstable. ), + deno_node::init(), deno_ffi::init::(false), deno_net::init::( None, false, // No --unstable. diff --git a/runtime/lib.rs b/runtime/lib.rs index 543d3a0a21..dfbfaafaa4 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -8,6 +8,7 @@ pub use deno_fetch; pub use deno_ffi; pub use deno_http; pub use deno_net; +pub use deno_node; pub use deno_tls; pub use deno_url; pub use deno_web; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 83ba380a6f..ec6cd50410 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -419,6 +419,7 @@ impl WebWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), + deno_node::init(), ops::os::init_for_worker(), ops::permissions::init(), ops::process::init(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 5100f42da1..c90129ab33 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -161,6 +161,7 @@ impl MainWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), + deno_node::init(), ops::os::init(exit_code.clone()), ops::permissions::init(), ops::process::init(),