mirror of
https://github.com/denoland/deno.git
synced 2025-01-06 22:35:51 -05:00
refactor(core): InternalModuleLoader checks if all files were used (#18005)
This commit changes "InternalModuleLoader" from "deno_core" to store a list of used modules during snapshotting. If a module was not used during snapshotting "InternalModuleLoader" will panic in its "Drop" handler signaling to the embedder that they made a mistake somewhere.
This commit is contained in:
parent
4fbc51ca46
commit
b6a4b1c794
10 changed files with 37 additions and 1196 deletions
|
@ -328,6 +328,7 @@ pub type InternalModuleLoaderCb =
|
||||||
pub struct InternalModuleLoader {
|
pub struct InternalModuleLoader {
|
||||||
module_loader: Rc<dyn ModuleLoader>,
|
module_loader: Rc<dyn ModuleLoader>,
|
||||||
esm_sources: Vec<ExtensionFileSource>,
|
esm_sources: Vec<ExtensionFileSource>,
|
||||||
|
used_esm_sources: RefCell<HashMap<String, bool>>,
|
||||||
maybe_load_callback: Option<InternalModuleLoaderCb>,
|
maybe_load_callback: Option<InternalModuleLoaderCb>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,6 +337,7 @@ impl Default for InternalModuleLoader {
|
||||||
Self {
|
Self {
|
||||||
module_loader: Rc::new(NoopModuleLoader),
|
module_loader: Rc::new(NoopModuleLoader),
|
||||||
esm_sources: vec![],
|
esm_sources: vec![],
|
||||||
|
used_esm_sources: RefCell::new(HashMap::default()),
|
||||||
maybe_load_callback: None,
|
maybe_load_callback: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,14 +349,43 @@ impl InternalModuleLoader {
|
||||||
esm_sources: Vec<ExtensionFileSource>,
|
esm_sources: Vec<ExtensionFileSource>,
|
||||||
maybe_load_callback: Option<InternalModuleLoaderCb>,
|
maybe_load_callback: Option<InternalModuleLoaderCb>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let used_esm_sources: HashMap<String, bool> = esm_sources
|
||||||
|
.iter()
|
||||||
|
.map(|file_source| (file_source.specifier.to_string(), false))
|
||||||
|
.collect();
|
||||||
|
|
||||||
InternalModuleLoader {
|
InternalModuleLoader {
|
||||||
module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)),
|
module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)),
|
||||||
esm_sources,
|
esm_sources,
|
||||||
|
used_esm_sources: RefCell::new(used_esm_sources),
|
||||||
maybe_load_callback,
|
maybe_load_callback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for InternalModuleLoader {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let used_esm_sources = self.used_esm_sources.get_mut();
|
||||||
|
let unused_modules: Vec<_> = used_esm_sources
|
||||||
|
.iter()
|
||||||
|
.filter(|(_s, v)| !*v)
|
||||||
|
.map(|(s, _)| s)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !unused_modules.is_empty() {
|
||||||
|
let mut msg =
|
||||||
|
"Following modules were passed to InternalModuleLoader but never used:\n"
|
||||||
|
.to_string();
|
||||||
|
for m in unused_modules {
|
||||||
|
msg.push_str(" - ");
|
||||||
|
msg.push_str(m);
|
||||||
|
msg.push('\n');
|
||||||
|
}
|
||||||
|
panic!("{}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ModuleLoader for InternalModuleLoader {
|
impl ModuleLoader for InternalModuleLoader {
|
||||||
fn resolve(
|
fn resolve(
|
||||||
&self,
|
&self,
|
||||||
|
@ -400,6 +431,12 @@ impl ModuleLoader for InternalModuleLoader {
|
||||||
.find(|file_source| file_source.specifier == module_specifier.as_str());
|
.find(|file_source| file_source.specifier == module_specifier.as_str());
|
||||||
|
|
||||||
if let Some(file_source) = maybe_file_source {
|
if let Some(file_source) = maybe_file_source {
|
||||||
|
{
|
||||||
|
let mut used_esm_sources = self.used_esm_sources.borrow_mut();
|
||||||
|
let used = used_esm_sources.get_mut(&file_source.specifier).unwrap();
|
||||||
|
*used = true;
|
||||||
|
}
|
||||||
|
|
||||||
let result = if let Some(load_callback) = &self.maybe_load_callback {
|
let result = if let Some(load_callback) = &self.maybe_load_callback {
|
||||||
load_callback(file_source)
|
load_callback(file_source)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -179,7 +179,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"events.ts",
|
"events.ts",
|
||||||
"fs.ts",
|
"fs.ts",
|
||||||
"fs/promises.ts",
|
"fs/promises.ts",
|
||||||
"global.ts",
|
|
||||||
"http.ts",
|
"http.ts",
|
||||||
"http2.ts",
|
"http2.ts",
|
||||||
"https.ts",
|
"https.ts",
|
||||||
|
@ -211,7 +210,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"internal_binding/uv.ts",
|
"internal_binding/uv.ts",
|
||||||
"internal/assert.mjs",
|
"internal/assert.mjs",
|
||||||
"internal/async_hooks.ts",
|
"internal/async_hooks.ts",
|
||||||
"internal/blob.mjs",
|
|
||||||
"internal/buffer.mjs",
|
"internal/buffer.mjs",
|
||||||
"internal/child_process.ts",
|
"internal/child_process.ts",
|
||||||
"internal/cli_table.ts",
|
"internal/cli_table.ts",
|
||||||
|
@ -234,7 +232,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"internal/crypto/random.ts",
|
"internal/crypto/random.ts",
|
||||||
"internal/crypto/scrypt.ts",
|
"internal/crypto/scrypt.ts",
|
||||||
"internal/crypto/sig.ts",
|
"internal/crypto/sig.ts",
|
||||||
"internal/crypto/types.ts",
|
|
||||||
"internal/crypto/util.ts",
|
"internal/crypto/util.ts",
|
||||||
"internal/crypto/x509.ts",
|
"internal/crypto/x509.ts",
|
||||||
"internal/dgram.ts",
|
"internal/dgram.ts",
|
||||||
|
@ -245,7 +242,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"internal/errors.ts",
|
"internal/errors.ts",
|
||||||
"internal/event_target.mjs",
|
"internal/event_target.mjs",
|
||||||
"internal/fixed_queue.ts",
|
"internal/fixed_queue.ts",
|
||||||
"internal/freelist.ts",
|
|
||||||
"internal/fs/streams.mjs",
|
"internal/fs/streams.mjs",
|
||||||
"internal/fs/utils.mjs",
|
"internal/fs/utils.mjs",
|
||||||
"internal/hide_stack_frames.ts",
|
"internal/hide_stack_frames.ts",
|
||||||
|
@ -270,7 +266,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"internal/streams/duplex.mjs",
|
"internal/streams/duplex.mjs",
|
||||||
"internal/streams/end-of-stream.mjs",
|
"internal/streams/end-of-stream.mjs",
|
||||||
"internal/streams/lazy_transform.mjs",
|
"internal/streams/lazy_transform.mjs",
|
||||||
"internal/streams/legacy.mjs",
|
|
||||||
"internal/streams/passthrough.mjs",
|
"internal/streams/passthrough.mjs",
|
||||||
"internal/streams/readable.mjs",
|
"internal/streams/readable.mjs",
|
||||||
"internal/streams/state.mjs",
|
"internal/streams/state.mjs",
|
||||||
|
@ -287,8 +282,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"internal/util/types.ts",
|
"internal/util/types.ts",
|
||||||
"internal/validators.mjs",
|
"internal/validators.mjs",
|
||||||
"module_all.ts",
|
"module_all.ts",
|
||||||
"module_esm.ts",
|
|
||||||
"module.js",
|
|
||||||
"net.ts",
|
"net.ts",
|
||||||
"os.ts",
|
"os.ts",
|
||||||
"path.ts",
|
"path.ts",
|
||||||
|
@ -318,7 +311,6 @@ pub fn init_polyfill() -> Extension {
|
||||||
"timers/promises.ts",
|
"timers/promises.ts",
|
||||||
"tls.ts",
|
"tls.ts",
|
||||||
"tty.ts",
|
"tty.ts",
|
||||||
"upstream_modules.ts",
|
|
||||||
"url.ts",
|
"url.ts",
|
||||||
"util.ts",
|
"util.ts",
|
||||||
"util/types.ts",
|
"util/types.ts",
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// deno-lint-ignore-file no-var
|
|
||||||
import processModule from "internal:deno_node/process.ts";
|
|
||||||
import { Buffer as bufferModule } from "internal:deno_node/buffer.ts";
|
|
||||||
import {
|
|
||||||
clearInterval,
|
|
||||||
clearTimeout,
|
|
||||||
setInterval,
|
|
||||||
setTimeout,
|
|
||||||
} from "internal:deno_node/timers.ts";
|
|
||||||
import timers from "internal:deno_node/timers.ts";
|
|
||||||
|
|
||||||
type GlobalType = {
|
|
||||||
process: typeof processModule;
|
|
||||||
Buffer: typeof bufferModule;
|
|
||||||
setImmediate: typeof timers.setImmediate;
|
|
||||||
clearImmediate: typeof timers.clearImmediate;
|
|
||||||
setTimeout: typeof timers.setTimeout;
|
|
||||||
clearTimeout: typeof timers.clearTimeout;
|
|
||||||
setInterval: typeof timers.setInterval;
|
|
||||||
clearInterval: typeof timers.clearInterval;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
global: GlobalType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface globalThis {
|
|
||||||
global: GlobalType;
|
|
||||||
}
|
|
||||||
|
|
||||||
var global: GlobalType;
|
|
||||||
var process: typeof processModule;
|
|
||||||
var Buffer: typeof bufferModule;
|
|
||||||
type Buffer = bufferModule;
|
|
||||||
var setImmediate: typeof timers.setImmediate;
|
|
||||||
var clearImmediate: typeof timers.clearImmediate;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(globalThis, "global", {
|
|
||||||
value: new Proxy(globalThis, {
|
|
||||||
get(target, prop, receiver) {
|
|
||||||
switch (prop) {
|
|
||||||
case "setInterval":
|
|
||||||
return setInterval;
|
|
||||||
case "setTimeout":
|
|
||||||
return setTimeout;
|
|
||||||
case "clearInterval":
|
|
||||||
return clearInterval;
|
|
||||||
case "clearTimeout":
|
|
||||||
return clearTimeout;
|
|
||||||
default:
|
|
||||||
return Reflect.get(target, prop, receiver);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
writable: false,
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(globalThis, "process", {
|
|
||||||
value: processModule,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(globalThis, "Buffer", {
|
|
||||||
value: bufferModule,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(globalThis, "setImmediate", {
|
|
||||||
value: timers.setImmediate,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(globalThis, "clearImmediate", {
|
|
||||||
value: timers.clearImmediate,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export {};
|
|
|
@ -1,7 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Node's implementation checks for a symbol they put in the blob prototype
|
|
||||||
// Since the implementation of Blob is Deno's, the only option is to check the
|
|
||||||
// objects constructor
|
|
||||||
export function isBlob(object) {
|
|
||||||
return object instanceof Blob;
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
import { Buffer } from "internal:deno_node/buffer.ts";
|
|
||||||
|
|
||||||
export type HASH_DATA = string | ArrayBufferView | Buffer;
|
|
||||||
|
|
||||||
export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary";
|
|
||||||
|
|
||||||
export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "latin1";
|
|
||||||
|
|
||||||
export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2";
|
|
||||||
|
|
||||||
export type Encoding =
|
|
||||||
| BinaryToTextEncoding
|
|
||||||
| CharacterEncoding
|
|
||||||
| LegacyCharacterEncoding;
|
|
||||||
|
|
||||||
export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid";
|
|
||||||
|
|
||||||
export type BinaryLike = string | ArrayBufferView;
|
|
||||||
|
|
||||||
export type KeyFormat = "pem" | "der";
|
|
||||||
|
|
||||||
export type KeyType =
|
|
||||||
| "rsa"
|
|
||||||
| "rsa-pss"
|
|
||||||
| "dsa"
|
|
||||||
| "ec"
|
|
||||||
| "ed25519"
|
|
||||||
| "ed448"
|
|
||||||
| "x25519"
|
|
||||||
| "x448";
|
|
||||||
|
|
||||||
export interface PrivateKeyInput {
|
|
||||||
key: string | Buffer;
|
|
||||||
format?: KeyFormat | undefined;
|
|
||||||
type?: "pkcs1" | "pkcs8" | "sec1" | undefined;
|
|
||||||
passphrase?: string | Buffer | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PublicKeyInput {
|
|
||||||
key: string | Buffer;
|
|
||||||
format?: KeyFormat | undefined;
|
|
||||||
type?: "pkcs1" | "spki" | undefined;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
type Fn<T> = (...args: unknown[]) => T;
|
|
||||||
export class FreeList<T> {
|
|
||||||
name: string;
|
|
||||||
ctor: Fn<T>;
|
|
||||||
max: number;
|
|
||||||
list: Array<T>;
|
|
||||||
constructor(name: string, max: number, ctor: Fn<T>) {
|
|
||||||
this.name = name;
|
|
||||||
this.ctor = ctor;
|
|
||||||
this.max = max;
|
|
||||||
this.list = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
alloc(): T {
|
|
||||||
return this.list.length > 0
|
|
||||||
? this.list.pop()
|
|
||||||
: Reflect.apply(this.ctor, this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(obj: T) {
|
|
||||||
if (this.list.length < this.max) {
|
|
||||||
this.list.push(obj);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
|
||||||
// deno-lint-ignore-file
|
|
||||||
|
|
||||||
import EE from "internal:deno_node/events.ts";
|
|
||||||
|
|
||||||
function Stream(opts) {
|
|
||||||
EE.call(this, opts);
|
|
||||||
}
|
|
||||||
Object.setPrototypeOf(Stream.prototype, EE.prototype);
|
|
||||||
Object.setPrototypeOf(Stream, EE);
|
|
||||||
|
|
||||||
Stream.prototype.pipe = function (dest, options) {
|
|
||||||
// deno-lint-ignore no-this-alias
|
|
||||||
const source = this;
|
|
||||||
|
|
||||||
function ondata(chunk) {
|
|
||||||
if (dest.writable && dest.write(chunk) === false && source.pause) {
|
|
||||||
source.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
source.on("data", ondata);
|
|
||||||
|
|
||||||
function ondrain() {
|
|
||||||
if (source.readable && source.resume) {
|
|
||||||
source.resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dest.on("drain", ondrain);
|
|
||||||
|
|
||||||
// If the 'end' option is not supplied, dest.end() will be called when
|
|
||||||
// source gets the 'end' or 'close' events. Only dest.end() once.
|
|
||||||
if (!dest._isStdio && (!options || options.end !== false)) {
|
|
||||||
source.on("end", onend);
|
|
||||||
source.on("close", onclose);
|
|
||||||
}
|
|
||||||
|
|
||||||
let didOnEnd = false;
|
|
||||||
function onend() {
|
|
||||||
if (didOnEnd) return;
|
|
||||||
didOnEnd = true;
|
|
||||||
|
|
||||||
dest.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onclose() {
|
|
||||||
if (didOnEnd) return;
|
|
||||||
didOnEnd = true;
|
|
||||||
|
|
||||||
if (typeof dest.destroy === "function") dest.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't leave dangling pipes when there are errors.
|
|
||||||
function onerror(er) {
|
|
||||||
cleanup();
|
|
||||||
if (EE.listenerCount(this, "error") === 0) {
|
|
||||||
this.emit("error", er);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prependListener(source, "error", onerror);
|
|
||||||
prependListener(dest, "error", onerror);
|
|
||||||
|
|
||||||
// Remove all the event listeners that were added.
|
|
||||||
function cleanup() {
|
|
||||||
source.removeListener("data", ondata);
|
|
||||||
dest.removeListener("drain", ondrain);
|
|
||||||
|
|
||||||
source.removeListener("end", onend);
|
|
||||||
source.removeListener("close", onclose);
|
|
||||||
|
|
||||||
source.removeListener("error", onerror);
|
|
||||||
dest.removeListener("error", onerror);
|
|
||||||
|
|
||||||
source.removeListener("end", cleanup);
|
|
||||||
source.removeListener("close", cleanup);
|
|
||||||
|
|
||||||
dest.removeListener("close", cleanup);
|
|
||||||
}
|
|
||||||
|
|
||||||
source.on("end", cleanup);
|
|
||||||
source.on("close", cleanup);
|
|
||||||
|
|
||||||
dest.on("close", cleanup);
|
|
||||||
dest.emit("pipe", source);
|
|
||||||
|
|
||||||
// Allow for unix-like usage: A.pipe(B).pipe(C)
|
|
||||||
return dest;
|
|
||||||
};
|
|
||||||
|
|
||||||
function prependListener(emitter, event, fn) {
|
|
||||||
// Sadly this is not cacheable as some libraries bundle their own
|
|
||||||
// event emitter implementation with them.
|
|
||||||
if (typeof emitter.prependListener === "function") {
|
|
||||||
return emitter.prependListener(event, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a hack to make sure that our error handler is attached before any
|
|
||||||
// userland ones. NEVER DO THIS. This is here only because this code needs
|
|
||||||
// to continue to work with older versions of Node.js that do not include
|
|
||||||
// the prependListener() method. The goal is to eventually remove this hack.
|
|
||||||
if (!emitter._events || !emitter._events[event]) {
|
|
||||||
emitter.on(event, fn);
|
|
||||||
} else if (Array.isArray(emitter._events[event])) {
|
|
||||||
emitter._events[event].unshift(fn);
|
|
||||||
} else {
|
|
||||||
emitter._events[event] = [fn, emitter._events[event]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { prependListener, Stream };
|
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
const internals = globalThis.__bootstrap.internals;
|
|
||||||
const m = internals.require.Module;
|
|
||||||
export const _cache = m._cache;
|
|
||||||
export const _extensions = m._extensions;
|
|
||||||
export const _findPath = m._findPath;
|
|
||||||
export const _initPaths = m._initPaths;
|
|
||||||
export const _load = m._load;
|
|
||||||
export const _nodeModulePaths = m._nodeModulePaths;
|
|
||||||
export const _pathCache = m._pathCache;
|
|
||||||
export const _preloadModules = m._preloadModules;
|
|
||||||
export const _resolveFilename = m._resolveFilename;
|
|
||||||
export const _resolveLookupPaths = m._resolveLookupPaths;
|
|
||||||
export const builtinModules = m.builtinModules;
|
|
||||||
export const createRequire = m.createRequire;
|
|
||||||
export const globalPaths = m.globalPaths;
|
|
||||||
export const Module = m.Module;
|
|
||||||
export const wrap = m.wrap;
|
|
||||||
export default m;
|
|
|
@ -1,842 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Copyright Joyent, Inc. and other Node contributors.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
// copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
|
||||||
// following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included
|
|
||||||
// in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE(bartlomieju):
|
|
||||||
* Functionality of this file is ported in Rust in `cli/compat/esm_resolver.ts`.
|
|
||||||
* Unfortunately we have no way to call ESM resolution in Rust from TypeScript code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { fileURLToPath, pathToFileURL } from "internal:deno_node/url.ts";
|
|
||||||
import {
|
|
||||||
ERR_INVALID_MODULE_SPECIFIER,
|
|
||||||
ERR_INVALID_PACKAGE_CONFIG,
|
|
||||||
ERR_INVALID_PACKAGE_TARGET,
|
|
||||||
ERR_MODULE_NOT_FOUND,
|
|
||||||
ERR_PACKAGE_IMPORT_NOT_DEFINED,
|
|
||||||
ERR_PACKAGE_PATH_NOT_EXPORTED,
|
|
||||||
NodeError,
|
|
||||||
} from "internal:deno_node/internal/errors.ts";
|
|
||||||
|
|
||||||
const { hasOwn } = Object;
|
|
||||||
|
|
||||||
export const encodedSepRegEx = /%2F|%2C/i;
|
|
||||||
|
|
||||||
function throwInvalidSubpath(
|
|
||||||
subpath: string,
|
|
||||||
packageJSONUrl: string,
|
|
||||||
internal: boolean,
|
|
||||||
base: string,
|
|
||||||
) {
|
|
||||||
const reason = `request is not a valid subpath for the "${
|
|
||||||
internal ? "imports" : "exports"
|
|
||||||
}" resolution of ${fileURLToPath(packageJSONUrl)}`;
|
|
||||||
throw new ERR_INVALID_MODULE_SPECIFIER(
|
|
||||||
subpath,
|
|
||||||
reason,
|
|
||||||
base && fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwInvalidPackageTarget(
|
|
||||||
subpath: string,
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
target: any,
|
|
||||||
packageJSONUrl: string,
|
|
||||||
internal: boolean,
|
|
||||||
base: string,
|
|
||||||
) {
|
|
||||||
if (typeof target === "object" && target !== null) {
|
|
||||||
target = JSON.stringify(target, null, "");
|
|
||||||
} else {
|
|
||||||
target = `${target}`;
|
|
||||||
}
|
|
||||||
throw new ERR_INVALID_PACKAGE_TARGET(
|
|
||||||
fileURLToPath(new URL(".", packageJSONUrl)),
|
|
||||||
subpath,
|
|
||||||
target,
|
|
||||||
internal,
|
|
||||||
base && fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwImportNotDefined(
|
|
||||||
specifier: string,
|
|
||||||
packageJSONUrl: URL | undefined,
|
|
||||||
base: string | URL,
|
|
||||||
): TypeError & { code: string } {
|
|
||||||
throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(
|
|
||||||
specifier,
|
|
||||||
packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)),
|
|
||||||
fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwExportsNotFound(
|
|
||||||
subpath: string,
|
|
||||||
packageJSONUrl: string,
|
|
||||||
base?: string,
|
|
||||||
): Error & { code: string } {
|
|
||||||
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
|
|
||||||
subpath,
|
|
||||||
fileURLToPath(new URL(".", packageJSONUrl)),
|
|
||||||
base && fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function patternKeyCompare(a: string, b: string): number {
|
|
||||||
const aPatternIndex = a.indexOf("*");
|
|
||||||
const bPatternIndex = b.indexOf("*");
|
|
||||||
const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
|
|
||||||
const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
|
|
||||||
if (baseLenA > baseLenB) return -1;
|
|
||||||
if (baseLenB > baseLenA) return 1;
|
|
||||||
if (aPatternIndex === -1) return 1;
|
|
||||||
if (bPatternIndex === -1) return -1;
|
|
||||||
if (a.length > b.length) return -1;
|
|
||||||
if (b.length > a.length) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileExists(url: string | URL): boolean {
|
|
||||||
try {
|
|
||||||
const info = Deno.statSync(url);
|
|
||||||
return info.isFile;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryStatSync(path: string): { isDirectory: boolean } {
|
|
||||||
try {
|
|
||||||
const info = Deno.statSync(path);
|
|
||||||
return { isDirectory: info.isDirectory };
|
|
||||||
} catch {
|
|
||||||
return { isDirectory: false };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy CommonJS main resolution:
|
|
||||||
* 1. let M = pkg_url + (json main field)
|
|
||||||
* 2. TRY(M, M.js, M.json, M.node)
|
|
||||||
* 3. TRY(M/index.js, M/index.json, M/index.node)
|
|
||||||
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
|
|
||||||
* 5. NOT_FOUND
|
|
||||||
*/
|
|
||||||
function legacyMainResolve(
|
|
||||||
packageJSONUrl: URL,
|
|
||||||
packageConfig: PackageConfig,
|
|
||||||
base: string | URL,
|
|
||||||
): URL {
|
|
||||||
let guess;
|
|
||||||
if (packageConfig.main !== undefined) {
|
|
||||||
// Note: fs check redundances will be handled by Descriptor cache here.
|
|
||||||
if (
|
|
||||||
fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl))
|
|
||||||
) {
|
|
||||||
return guess;
|
|
||||||
} else if (
|
|
||||||
fileExists(guess = new URL(`./${packageConfig.main}.js`, packageJSONUrl))
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else if (
|
|
||||||
fileExists(
|
|
||||||
guess = new URL(`./${packageConfig.main}.json`, packageJSONUrl),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else if (
|
|
||||||
fileExists(
|
|
||||||
guess = new URL(`./${packageConfig.main}.node`, packageJSONUrl),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else if (
|
|
||||||
fileExists(
|
|
||||||
guess = new URL(`./${packageConfig.main}/index.js`, packageJSONUrl),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else if (
|
|
||||||
fileExists(
|
|
||||||
guess = new URL(`./${packageConfig.main}/index.json`, packageJSONUrl),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else if (
|
|
||||||
fileExists(
|
|
||||||
guess = new URL(`./${packageConfig.main}/index.node`, packageJSONUrl),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// pass
|
|
||||||
} else guess = undefined;
|
|
||||||
if (guess) {
|
|
||||||
// TODO(bartlomieju):
|
|
||||||
// emitLegacyIndexDeprecation(guess, packageJSONUrl, base,
|
|
||||||
// packageConfig.main);
|
|
||||||
return guess;
|
|
||||||
}
|
|
||||||
// Fallthrough.
|
|
||||||
}
|
|
||||||
if (fileExists(guess = new URL("./index.js", packageJSONUrl))) {
|
|
||||||
// pass
|
|
||||||
} // So fs.
|
|
||||||
else if (fileExists(guess = new URL("./index.json", packageJSONUrl))) {
|
|
||||||
// pass
|
|
||||||
} else if (fileExists(guess = new URL("./index.node", packageJSONUrl))) {
|
|
||||||
// pass
|
|
||||||
} else guess = undefined;
|
|
||||||
if (guess) {
|
|
||||||
// TODO(bartlomieju):
|
|
||||||
// emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main);
|
|
||||||
return guess;
|
|
||||||
}
|
|
||||||
// Not found.
|
|
||||||
throw new ERR_MODULE_NOT_FOUND(
|
|
||||||
fileURLToPath(new URL(".", packageJSONUrl)),
|
|
||||||
fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePackageName(
|
|
||||||
specifier: string,
|
|
||||||
base: string | URL,
|
|
||||||
): { packageName: string; packageSubpath: string; isScoped: boolean } {
|
|
||||||
let separatorIndex = specifier.indexOf("/");
|
|
||||||
let validPackageName = true;
|
|
||||||
let isScoped = false;
|
|
||||||
if (specifier[0] === "@") {
|
|
||||||
isScoped = true;
|
|
||||||
if (separatorIndex === -1 || specifier.length === 0) {
|
|
||||||
validPackageName = false;
|
|
||||||
} else {
|
|
||||||
separatorIndex = specifier.indexOf("/", separatorIndex + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageName = separatorIndex === -1
|
|
||||||
? specifier
|
|
||||||
: specifier.slice(0, separatorIndex);
|
|
||||||
|
|
||||||
// Package name cannot have leading . and cannot have percent-encoding or
|
|
||||||
// separators.
|
|
||||||
for (let i = 0; i < packageName.length; i++) {
|
|
||||||
if (packageName[i] === "%" || packageName[i] === "\\") {
|
|
||||||
validPackageName = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validPackageName) {
|
|
||||||
throw new ERR_INVALID_MODULE_SPECIFIER(
|
|
||||||
specifier,
|
|
||||||
"is not a valid package name",
|
|
||||||
fileURLToPath(base),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageSubpath = "." +
|
|
||||||
(separatorIndex === -1 ? "" : specifier.slice(separatorIndex));
|
|
||||||
|
|
||||||
return { packageName, packageSubpath, isScoped };
|
|
||||||
}
|
|
||||||
|
|
||||||
function packageResolve(
|
|
||||||
specifier: string,
|
|
||||||
base: string,
|
|
||||||
conditions: Set<string>,
|
|
||||||
): URL | undefined {
|
|
||||||
const { packageName, packageSubpath, isScoped } = parsePackageName(
|
|
||||||
specifier,
|
|
||||||
base,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ResolveSelf
|
|
||||||
const packageConfig = getPackageScopeConfig(base);
|
|
||||||
if (packageConfig.exists) {
|
|
||||||
const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
|
|
||||||
if (
|
|
||||||
packageConfig.name === packageName &&
|
|
||||||
packageConfig.exports !== undefined && packageConfig.exports !== null
|
|
||||||
) {
|
|
||||||
return packageExportsResolve(
|
|
||||||
packageJSONUrl.toString(),
|
|
||||||
packageSubpath,
|
|
||||||
packageConfig,
|
|
||||||
base,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let packageJSONUrl = new URL(
|
|
||||||
"./node_modules/" + packageName + "/package.json",
|
|
||||||
base,
|
|
||||||
);
|
|
||||||
let packageJSONPath = fileURLToPath(packageJSONUrl);
|
|
||||||
let lastPath;
|
|
||||||
do {
|
|
||||||
const stat = tryStatSync(
|
|
||||||
packageJSONPath.slice(0, packageJSONPath.length - 13),
|
|
||||||
);
|
|
||||||
if (!stat.isDirectory) {
|
|
||||||
lastPath = packageJSONPath;
|
|
||||||
packageJSONUrl = new URL(
|
|
||||||
(isScoped ? "../../../../node_modules/" : "../../../node_modules/") +
|
|
||||||
packageName + "/package.json",
|
|
||||||
packageJSONUrl,
|
|
||||||
);
|
|
||||||
packageJSONPath = fileURLToPath(packageJSONUrl);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package match.
|
|
||||||
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
|
|
||||||
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
|
|
||||||
return packageExportsResolve(
|
|
||||||
packageJSONUrl.toString(),
|
|
||||||
packageSubpath,
|
|
||||||
packageConfig,
|
|
||||||
base,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (packageSubpath === ".") {
|
|
||||||
return legacyMainResolve(packageJSONUrl, packageConfig, base);
|
|
||||||
}
|
|
||||||
return new URL(packageSubpath, packageJSONUrl);
|
|
||||||
// Cross-platform root check.
|
|
||||||
} while (packageJSONPath.length !== lastPath.length);
|
|
||||||
|
|
||||||
// TODO(bartlomieju): this is false positive
|
|
||||||
// deno-lint-ignore no-unreachable
|
|
||||||
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/;
|
|
||||||
const patternRegEx = /\*/g;
|
|
||||||
|
|
||||||
function resolvePackageTargetString(
|
|
||||||
target: string,
|
|
||||||
subpath: string,
|
|
||||||
match: string,
|
|
||||||
packageJSONUrl: string,
|
|
||||||
base: string,
|
|
||||||
pattern: boolean,
|
|
||||||
internal: boolean,
|
|
||||||
conditions: Set<string>,
|
|
||||||
): URL | undefined {
|
|
||||||
if (subpath !== "" && !pattern && target[target.length - 1] !== "/") {
|
|
||||||
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target.startsWith("./")) {
|
|
||||||
if (
|
|
||||||
internal && !target.startsWith("../") &&
|
|
||||||
!target.startsWith("/")
|
|
||||||
) {
|
|
||||||
let isURL = false;
|
|
||||||
try {
|
|
||||||
new URL(target);
|
|
||||||
isURL = true;
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
if (!isURL) {
|
|
||||||
const exportTarget = pattern
|
|
||||||
? target.replace(patternRegEx, () => subpath)
|
|
||||||
: target + subpath;
|
|
||||||
return packageResolve(exportTarget, packageJSONUrl, conditions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalidSegmentRegEx.test(target.slice(2))) {
|
|
||||||
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolved = new URL(target, packageJSONUrl);
|
|
||||||
const resolvedPath = resolved.pathname;
|
|
||||||
const packagePath = new URL(".", packageJSONUrl).pathname;
|
|
||||||
|
|
||||||
if (!resolvedPath.startsWith(packagePath)) {
|
|
||||||
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subpath === "") return resolved;
|
|
||||||
|
|
||||||
if (invalidSegmentRegEx.test(subpath)) {
|
|
||||||
const request = pattern
|
|
||||||
? match.replace("*", () => subpath)
|
|
||||||
: match + subpath;
|
|
||||||
throwInvalidSubpath(request, packageJSONUrl, internal, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern) {
|
|
||||||
return new URL(resolved.href.replace(patternRegEx, () => subpath));
|
|
||||||
}
|
|
||||||
return new URL(subpath, resolved);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isArrayIndex(key: string): boolean {
|
|
||||||
const keyNum = +key;
|
|
||||||
if (`${keyNum}` !== key) return false;
|
|
||||||
return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePackageTarget(
|
|
||||||
packageJSONUrl: string,
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
target: any,
|
|
||||||
subpath: string,
|
|
||||||
packageSubpath: string,
|
|
||||||
base: string,
|
|
||||||
pattern: boolean,
|
|
||||||
internal: boolean,
|
|
||||||
conditions: Set<string>,
|
|
||||||
): URL | undefined {
|
|
||||||
if (typeof target === "string") {
|
|
||||||
return resolvePackageTargetString(
|
|
||||||
target,
|
|
||||||
subpath,
|
|
||||||
packageSubpath,
|
|
||||||
packageJSONUrl,
|
|
||||||
base,
|
|
||||||
pattern,
|
|
||||||
internal,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
} else if (Array.isArray(target)) {
|
|
||||||
if (target.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastException;
|
|
||||||
for (let i = 0; i < target.length; i++) {
|
|
||||||
const targetItem = target[i];
|
|
||||||
let resolved;
|
|
||||||
try {
|
|
||||||
resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl,
|
|
||||||
targetItem,
|
|
||||||
subpath,
|
|
||||||
packageSubpath,
|
|
||||||
base,
|
|
||||||
pattern,
|
|
||||||
internal,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
lastException = e;
|
|
||||||
if (e instanceof NodeError && e.code === "ERR_INVALID_PACKAGE_TARGET") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
if (resolved === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (resolved === null) {
|
|
||||||
lastException = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
if (lastException === undefined || lastException === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
throw lastException;
|
|
||||||
} else if (typeof target === "object" && target !== null) {
|
|
||||||
const keys = Object.getOwnPropertyNames(target);
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
if (isArrayIndex(key)) {
|
|
||||||
throw new ERR_INVALID_PACKAGE_CONFIG(
|
|
||||||
fileURLToPath(packageJSONUrl),
|
|
||||||
base,
|
|
||||||
'"exports" cannot contain numeric property keys.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
if (key === "default" || conditions.has(key)) {
|
|
||||||
const conditionalTarget = target[key];
|
|
||||||
const resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl,
|
|
||||||
conditionalTarget,
|
|
||||||
subpath,
|
|
||||||
packageSubpath,
|
|
||||||
base,
|
|
||||||
pattern,
|
|
||||||
internal,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
if (resolved === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
} else if (target === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
throwInvalidPackageTarget(
|
|
||||||
packageSubpath,
|
|
||||||
target,
|
|
||||||
packageJSONUrl,
|
|
||||||
internal,
|
|
||||||
base,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function packageExportsResolve(
|
|
||||||
packageJSONUrl: string,
|
|
||||||
packageSubpath: string,
|
|
||||||
packageConfig: PackageConfig,
|
|
||||||
base: string,
|
|
||||||
conditions: Set<string>,
|
|
||||||
// @ts-ignore `URL` needs to be forced due to control flow
|
|
||||||
): URL {
|
|
||||||
let exports = packageConfig.exports;
|
|
||||||
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
|
|
||||||
exports = { ".": exports };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
hasOwn(exports, packageSubpath) &&
|
|
||||||
!packageSubpath.includes("*") &&
|
|
||||||
!packageSubpath.endsWith("/")
|
|
||||||
) {
|
|
||||||
const target = exports[packageSubpath];
|
|
||||||
const resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl,
|
|
||||||
target,
|
|
||||||
"",
|
|
||||||
packageSubpath,
|
|
||||||
base,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
if (resolved === null || resolved === undefined) {
|
|
||||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
|
||||||
}
|
|
||||||
return resolved!;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bestMatch = "";
|
|
||||||
let bestMatchSubpath = "";
|
|
||||||
const keys = Object.getOwnPropertyNames(exports);
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
const patternIndex = key.indexOf("*");
|
|
||||||
if (
|
|
||||||
patternIndex !== -1 &&
|
|
||||||
packageSubpath.startsWith(key.slice(0, patternIndex))
|
|
||||||
) {
|
|
||||||
// When this reaches EOL, this can throw at the top of the whole function:
|
|
||||||
//
|
|
||||||
// if (StringPrototypeEndsWith(packageSubpath, '/'))
|
|
||||||
// throwInvalidSubpath(packageSubpath)
|
|
||||||
//
|
|
||||||
// To match "imports" and the spec.
|
|
||||||
if (packageSubpath.endsWith("/")) {
|
|
||||||
// TODO(@bartlomieju):
|
|
||||||
// emitTrailingSlashPatternDeprecation(
|
|
||||||
// packageSubpath,
|
|
||||||
// packageJSONUrl,
|
|
||||||
// base,
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
const patternTrailer = key.slice(patternIndex + 1);
|
|
||||||
if (
|
|
||||||
packageSubpath.length >= key.length &&
|
|
||||||
packageSubpath.endsWith(patternTrailer) &&
|
|
||||||
patternKeyCompare(bestMatch, key) === 1 &&
|
|
||||||
key.lastIndexOf("*") === patternIndex
|
|
||||||
) {
|
|
||||||
bestMatch = key;
|
|
||||||
bestMatchSubpath = packageSubpath.slice(
|
|
||||||
patternIndex,
|
|
||||||
packageSubpath.length - patternTrailer.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestMatch) {
|
|
||||||
const target = exports[bestMatch];
|
|
||||||
const resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl,
|
|
||||||
target,
|
|
||||||
bestMatchSubpath,
|
|
||||||
bestMatch,
|
|
||||||
base,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
if (resolved === null || resolved === undefined) {
|
|
||||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
|
||||||
}
|
|
||||||
return resolved!;
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PackageConfig {
|
|
||||||
pjsonPath: string;
|
|
||||||
exists: boolean;
|
|
||||||
name?: string;
|
|
||||||
main?: string;
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
exports?: any;
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
imports?: any;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageJSONCache = new Map(); /* string -> PackageConfig */
|
|
||||||
|
|
||||||
function getPackageConfig(
|
|
||||||
path: string,
|
|
||||||
specifier: string | URL,
|
|
||||||
base?: string | URL,
|
|
||||||
): PackageConfig {
|
|
||||||
const existing = packageJSONCache.get(path);
|
|
||||||
if (existing !== undefined) {
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
let source: string | undefined;
|
|
||||||
try {
|
|
||||||
source = new TextDecoder().decode(
|
|
||||||
Deno.readFileSync(path),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source === undefined) {
|
|
||||||
const packageConfig = {
|
|
||||||
pjsonPath: path,
|
|
||||||
exists: false,
|
|
||||||
main: undefined,
|
|
||||||
name: undefined,
|
|
||||||
type: "none",
|
|
||||||
exports: undefined,
|
|
||||||
imports: undefined,
|
|
||||||
};
|
|
||||||
packageJSONCache.set(path, packageConfig);
|
|
||||||
return packageConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
let packageJSON;
|
|
||||||
try {
|
|
||||||
packageJSON = JSON.parse(source);
|
|
||||||
} catch (error) {
|
|
||||||
throw new ERR_INVALID_PACKAGE_CONFIG(
|
|
||||||
path,
|
|
||||||
(base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier),
|
|
||||||
// @ts-ignore there's no assertion for type and `error` is thus `unknown`
|
|
||||||
error.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let { imports, main, name, type } = packageJSON;
|
|
||||||
const { exports } = packageJSON;
|
|
||||||
if (typeof imports !== "object" || imports === null) imports = undefined;
|
|
||||||
if (typeof main !== "string") main = undefined;
|
|
||||||
if (typeof name !== "string") name = undefined;
|
|
||||||
// Ignore unknown types for forwards compatibility
|
|
||||||
if (type !== "module" && type !== "commonjs") type = "none";
|
|
||||||
|
|
||||||
const packageConfig = {
|
|
||||||
pjsonPath: path,
|
|
||||||
exists: true,
|
|
||||||
main,
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
exports,
|
|
||||||
imports,
|
|
||||||
};
|
|
||||||
packageJSONCache.set(path, packageConfig);
|
|
||||||
return packageConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPackageScopeConfig(resolved: URL | string): PackageConfig {
|
|
||||||
let packageJSONUrl = new URL("./package.json", resolved);
|
|
||||||
while (true) {
|
|
||||||
const packageJSONPath = packageJSONUrl.pathname;
|
|
||||||
if (packageJSONPath.endsWith("node_modules/package.json")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const packageConfig = getPackageConfig(
|
|
||||||
fileURLToPath(packageJSONUrl),
|
|
||||||
resolved,
|
|
||||||
);
|
|
||||||
if (packageConfig.exists) return packageConfig;
|
|
||||||
|
|
||||||
const lastPackageJSONUrl = packageJSONUrl;
|
|
||||||
packageJSONUrl = new URL("../package.json", packageJSONUrl);
|
|
||||||
|
|
||||||
// Terminates at root where ../package.json equals ../../package.json
|
|
||||||
// (can't just check "/package.json" for Windows support).
|
|
||||||
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break;
|
|
||||||
}
|
|
||||||
const packageJSONPath = fileURLToPath(packageJSONUrl);
|
|
||||||
const packageConfig = {
|
|
||||||
pjsonPath: packageJSONPath,
|
|
||||||
exists: false,
|
|
||||||
main: undefined,
|
|
||||||
name: undefined,
|
|
||||||
type: "none",
|
|
||||||
exports: undefined,
|
|
||||||
imports: undefined,
|
|
||||||
};
|
|
||||||
packageJSONCache.set(packageJSONPath, packageConfig);
|
|
||||||
return packageConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function packageImportsResolve(
|
|
||||||
name: string,
|
|
||||||
base: string,
|
|
||||||
conditions: Set<string>,
|
|
||||||
// @ts-ignore `URL` needs to be forced due to control flow
|
|
||||||
): URL {
|
|
||||||
if (
|
|
||||||
name === "#" || name.startsWith("#/") ||
|
|
||||||
name.startsWith("/")
|
|
||||||
) {
|
|
||||||
const reason = "is not a valid internal imports specifier name";
|
|
||||||
throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
|
|
||||||
}
|
|
||||||
let packageJSONUrl;
|
|
||||||
const packageConfig = getPackageScopeConfig(base);
|
|
||||||
if (packageConfig.exists) {
|
|
||||||
packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
|
|
||||||
const imports = packageConfig.imports;
|
|
||||||
if (imports) {
|
|
||||||
if (
|
|
||||||
hasOwn(imports, name) &&
|
|
||||||
!name.includes("*")
|
|
||||||
) {
|
|
||||||
const resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl.toString(),
|
|
||||||
imports[name],
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
base,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
if (resolved !== null && resolved !== undefined) {
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let bestMatch = "";
|
|
||||||
let bestMatchSubpath = "";
|
|
||||||
const keys = Object.getOwnPropertyNames(imports);
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
const key = keys[i];
|
|
||||||
const patternIndex = key.indexOf("*");
|
|
||||||
if (
|
|
||||||
patternIndex !== -1 &&
|
|
||||||
name.startsWith(
|
|
||||||
key.slice(0, patternIndex),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const patternTrailer = key.slice(patternIndex + 1);
|
|
||||||
if (
|
|
||||||
name.length >= key.length &&
|
|
||||||
name.endsWith(patternTrailer) &&
|
|
||||||
patternKeyCompare(bestMatch, key) === 1 &&
|
|
||||||
key.lastIndexOf("*") === patternIndex
|
|
||||||
) {
|
|
||||||
bestMatch = key;
|
|
||||||
bestMatchSubpath = name.slice(
|
|
||||||
patternIndex,
|
|
||||||
name.length - patternTrailer.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestMatch) {
|
|
||||||
const target = imports[bestMatch];
|
|
||||||
const resolved = resolvePackageTarget(
|
|
||||||
packageJSONUrl.toString(),
|
|
||||||
target,
|
|
||||||
bestMatchSubpath,
|
|
||||||
bestMatch,
|
|
||||||
base,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
conditions,
|
|
||||||
);
|
|
||||||
if (resolved !== null && resolved !== undefined) {
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throwImportNotDefined(name, packageJSONUrl, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isConditionalExportsMainSugar(
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
exports: any,
|
|
||||||
packageJSONUrl: string,
|
|
||||||
base: string,
|
|
||||||
): boolean {
|
|
||||||
if (typeof exports === "string" || Array.isArray(exports)) return true;
|
|
||||||
if (typeof exports !== "object" || exports === null) return false;
|
|
||||||
|
|
||||||
const keys = Object.getOwnPropertyNames(exports);
|
|
||||||
let isConditionalSugar = false;
|
|
||||||
let i = 0;
|
|
||||||
for (let j = 0; j < keys.length; j++) {
|
|
||||||
const key = keys[j];
|
|
||||||
const curIsConditionalSugar = key === "" || key[0] !== ".";
|
|
||||||
if (i++ === 0) {
|
|
||||||
isConditionalSugar = curIsConditionalSugar;
|
|
||||||
} else if (isConditionalSugar !== curIsConditionalSugar) {
|
|
||||||
const message =
|
|
||||||
"\"exports\" cannot contain some keys starting with '.' and some not." +
|
|
||||||
" The exports object must either be an object of package subpath keys" +
|
|
||||||
" or an object of main entry condition name keys only.";
|
|
||||||
throw new ERR_INVALID_PACKAGE_CONFIG(
|
|
||||||
fileURLToPath(packageJSONUrl),
|
|
||||||
base,
|
|
||||||
message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isConditionalSugar;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// Upstream modules
|
|
||||||
const callerPath = `const callerCallsite = require("caller-callsite");
|
|
||||||
const re = /^file:/;
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
const fileUrl = callerCallsite().getFileName();
|
|
||||||
return fileUrl.replace(re, "");
|
|
||||||
};
|
|
||||||
`;
|
|
||||||
|
|
||||||
// From: https://github.com/stefanpenner/get-caller-file/blob/2383bf9e98ed3c568ff69d7586cf59c0f1dcb9d3/index.ts
|
|
||||||
const getCallerFile = `
|
|
||||||
const re = /^file:\\/\\//;
|
|
||||||
|
|
||||||
module.exports = function getCallerFile(position = 2) {
|
|
||||||
if (position >= Error.stackTraceLimit) {
|
|
||||||
throw new TypeError('getCallerFile(position) requires position be less then Error.stackTraceLimit but position was: "' + position + '" and Error.stackTraceLimit was: "' + Error.stackTraceLimit + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldPrepareStackTrace = Error.prepareStackTrace;
|
|
||||||
Error.prepareStackTrace = (_, stack) => stack;
|
|
||||||
const stack = new Error().stack;
|
|
||||||
Error.prepareStackTrace = oldPrepareStackTrace;
|
|
||||||
|
|
||||||
|
|
||||||
if (stack !== null && typeof stack === 'object') {
|
|
||||||
// stack[0] holds this file
|
|
||||||
// stack[1] holds where this function was called
|
|
||||||
// stack[2] holds the file we're interested in
|
|
||||||
return stack[position] ? stack[position].getFileName().replace(re, "") : undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
"caller-path": callerPath,
|
|
||||||
"get-caller-file": getCallerFile,
|
|
||||||
} as Record<string, string>;
|
|
Loading…
Reference in a new issue