1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

Merge remote-tracking branch 'upstream/main' into update-imagebitmap

This commit is contained in:
Hajime-san 2024-09-08 20:38:46 +09:00
commit dbac50db8f
864 changed files with 8469 additions and 15627 deletions

View file

@ -65,7 +65,7 @@
"third_party"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.91.6.wasm",
"https://plugins.dprint.dev/typescript-0.91.7.wasm",
"https://plugins.dprint.dev/json-0.19.3.wasm",
"https://plugins.dprint.dev/markdown-0.17.8.wasm",
"https://plugins.dprint.dev/toml-0.6.2.wasm",

View file

@ -5,7 +5,7 @@ import { stringify } from "jsr:@std/yaml@^0.221/stringify";
// Bump this number when you want to purge the cache.
// Note: the tools/release/01_bump_crate_versions.ts script will update this version
// automatically via regex, so ensure that this line maintains this format.
const cacheVersion = 13;
const cacheVersion = 15;
const ubuntuX86Runner = "ubuntu-22.04";
const ubuntuX86XlRunner = "ubuntu-22.04-xl";

View file

@ -367,8 +367,8 @@ jobs:
path: |-
~/.cargo/registry/index
~/.cargo/registry/cache
key: '13-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}'
restore-keys: '13-cargo-home-${{ matrix.os }}-${{ matrix.arch }}'
key: '15-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}'
restore-keys: '15-cargo-home-${{ matrix.os }}-${{ matrix.arch }}'
if: '!(matrix.skip)'
- name: Restore cache build output (PR)
uses: actions/cache/restore@v4
@ -381,7 +381,7 @@ jobs:
!./target/*/*.zip
!./target/*/*.tar.gz
key: never_saved
restore-keys: '13-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-'
restore-keys: '15-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-'
- name: Apply and update mtime cache
if: '!(matrix.skip) && (!startsWith(github.ref, ''refs/tags/''))'
uses: ./.github/mtime_cache
@ -670,7 +670,7 @@ jobs:
!./target/*/gn_out
!./target/*/*.zip
!./target/*/*.tar.gz
key: '13-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
key: '15-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
publish-canary:
name: publish canary
runs-on: ubuntu-22.04

508
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -44,47 +44,48 @@ license = "MIT"
repository = "https://github.com/denoland/deno"
[workspace.dependencies]
deno_ast = { version = "=0.41.2", features = ["transpiling"] }
deno_core = { version = "0.306.0" }
deno_ast = { version = "=0.42.0", features = ["transpiling"] }
deno_core = { version = "0.307.0" }
deno_bench_util = { version = "0.160.0", path = "./bench_util" }
deno_lockfile = "=0.22.0"
deno_bench_util = { version = "0.162.0", path = "./bench_util" }
deno_lockfile = "=0.23.0"
deno_media_type = { version = "0.1.4", features = ["module_specifier"] }
deno_permissions = { version = "0.26.0", path = "./runtime/permissions" }
deno_runtime = { version = "0.175.0", path = "./runtime" }
deno_permissions = { version = "0.28.0", path = "./runtime/permissions" }
deno_runtime = { version = "0.177.0", path = "./runtime" }
deno_semver = "=0.5.13"
deno_terminal = "0.2.0"
napi_sym = { version = "0.96.0", path = "./cli/napi/sym" }
napi_sym = { version = "0.98.0", path = "./cli/napi/sym" }
test_util = { package = "test_server", path = "./tests/util/server" }
denokv_proto = "0.8.1"
denokv_remote = "0.8.1"
# denokv_sqlite brings in bundled sqlite if we don't disable the default features
denokv_sqlite = { default-features = false, version = "0.8.1" }
denokv_sqlite = { default-features = false, version = "0.8.2" }
# exts
deno_broadcast_channel = { version = "0.160.0", path = "./ext/broadcast_channel" }
deno_cache = { version = "0.98.0", path = "./ext/cache" }
deno_canvas = { version = "0.35.0", path = "./ext/canvas" }
deno_console = { version = "0.166.0", path = "./ext/console" }
deno_cron = { version = "0.46.0", path = "./ext/cron" }
deno_crypto = { version = "0.180.0", path = "./ext/crypto" }
deno_fetch = { version = "0.190.0", path = "./ext/fetch" }
deno_ffi = { version = "0.153.0", path = "./ext/ffi" }
deno_fs = { version = "0.76.0", path = "./ext/fs" }
deno_http = { version = "0.164.0", path = "./ext/http" }
deno_io = { version = "0.76.0", path = "./ext/io" }
deno_kv = { version = "0.74.0", path = "./ext/kv" }
deno_napi = { version = "0.97.0", path = "./ext/napi" }
deno_net = { version = "0.158.0", path = "./ext/net" }
deno_node = { version = "0.103.0", path = "./ext/node" }
deno_tls = { version = "0.153.0", path = "./ext/tls" }
deno_url = { version = "0.166.0", path = "./ext/url" }
deno_web = { version = "0.197.0", path = "./ext/web" }
deno_webgpu = { version = "0.133.0", path = "./ext/webgpu" }
deno_webidl = { version = "0.166.0", path = "./ext/webidl" }
deno_websocket = { version = "0.171.0", path = "./ext/websocket" }
deno_webstorage = { version = "0.161.0", path = "./ext/webstorage" }
node_resolver = { version = "0.5.0", path = "./ext/node_resolver" }
deno_broadcast_channel = { version = "0.162.0", path = "./ext/broadcast_channel" }
deno_cache = { version = "0.100.0", path = "./ext/cache" }
deno_canvas = { version = "0.37.0", path = "./ext/canvas" }
deno_console = { version = "0.168.0", path = "./ext/console" }
deno_cron = { version = "0.48.0", path = "./ext/cron" }
deno_crypto = { version = "0.182.0", path = "./ext/crypto" }
deno_fetch = { version = "0.192.0", path = "./ext/fetch" }
deno_ffi = { version = "0.155.0", path = "./ext/ffi" }
deno_fs = { version = "0.78.0", path = "./ext/fs" }
deno_http = { version = "0.166.0", path = "./ext/http" }
deno_io = { version = "0.78.0", path = "./ext/io" }
deno_kv = { version = "0.76.0", path = "./ext/kv" }
deno_napi = { version = "0.99.0", path = "./ext/napi" }
deno_net = { version = "0.160.0", path = "./ext/net" }
deno_node = { version = "0.105.0", path = "./ext/node" }
deno_tls = { version = "0.155.0", path = "./ext/tls" }
deno_url = { version = "0.168.0", path = "./ext/url" }
deno_web = { version = "0.199.0", path = "./ext/web" }
deno_webgpu = { version = "0.135.0", path = "./ext/webgpu" }
deno_webidl = { version = "0.168.0", path = "./ext/webidl" }
deno_websocket = { version = "0.173.0", path = "./ext/websocket" }
deno_webstorage = { version = "0.163.0", path = "./ext/webstorage" }
node_resolver = { version = "0.7.0", path = "./ext/node_resolver" }
aes = "=0.8.3"
anyhow = "1.0.57"
@ -102,11 +103,11 @@ chrono = { version = "0.4", default-features = false, features = ["std", "serde"
console_static_text = "=0.8.1"
data-encoding = "2.3.3"
data-url = "=0.3.0"
deno_cache_dir = "=0.11.0"
deno_cache_dir = "=0.11.1"
deno_package_json = { version = "=0.1.1", default-features = false }
dlopen2 = "0.6.1"
ecb = "=0.1.2"
elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] }
elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] }
encoding_rs = "=0.8.33"
fast-socks5 = "0.9.6"
faster-hex = "0.9"
@ -141,8 +142,8 @@ num-bigint = { version = "0.4", features = ["rand"] }
once_cell = "1.17.1"
os_pipe = { version = "=1.1.5", features = ["io_safety"] }
p224 = { version = "0.13.0", features = ["ecdh"] }
p256 = { version = "0.13.2", features = ["ecdh"] }
p384 = { version = "0.13.0", features = ["ecdh"] }
p256 = { version = "0.13.2", features = ["ecdh", "jwk"] }
p384 = { version = "0.13.0", features = ["ecdh", "jwk"] }
parking_lot = "0.12.0"
percent-encoding = "2.3.0"
phf = { version = "0.11", features = ["macros"] }
@ -154,7 +155,7 @@ rand = "=0.8.5"
regex = "^1.7.0"
reqwest = { version = "=0.12.5", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli", "socks", "json", "http2"] } # pinned because of https://github.com/seanmonstar/reqwest/pull/1955
ring = "^0.17.0"
rusqlite = { version = "=0.29.0", features = ["unlock_notify", "bundled"] }
rusqlite = { version = "0.32.0", features = ["unlock_notify", "bundled"] }
rustls = { version = "0.23.11", default-features = false, features = ["logging", "std", "tls12", "ring"] }
rustls-pemfile = "2"
rustls-tokio-stream = "=0.3.0"
@ -192,6 +193,7 @@ url = { version = "< 2.5.0", features = ["serde", "expose_internals"] }
uuid = { version = "1.3.0", features = ["v4"] }
webpki-roots = "0.26"
which = "4.2.5"
yoke = { version = "0.7.4", features = ["derive"] }
zeromq = { version = "=0.4.0", default-features = false, features = ["tcp-transport", "tokio-runtime"] }
zstd = "=0.12.4"

View file

@ -6,6 +6,50 @@ https://github.com/denoland/deno/releases
We also have one-line install commands at:
https://github.com/denoland/deno_install
### 1.46.3 / 2024.09.04
- feat(upgrade): print info links for Deno 2 RC releases (#25225)
- fix(cli): Map error kind to `PermissionDenied` when symlinking fails due to
permissions (#25398)
- fix(cli/tools): correct `deno init --serve` template behavior (#25318)
- fix(ext/node): session close during stream setup (#25170)
- fix(publish): ensure provenance is spec compliant (#25200)
- fix(upgrade): more informative information on invalid version (#25319)
- fix: fix jupyter display function type (#25326)
### 1.46.2 / 2024.08.29
- Revert "feat(fetch): accept async iterables for body" (#25207)
- fix(bench): Fix table column alignments and NO_COLOR=1 (#25190)
- fix(ext/crypto): throw DataError for invalid EC key import (#25181)
- fix(ext/fetch): percent decode userinfo when parsing proxies (#25229)
- fix(ext/node): emit `online` event after worker thread is initialized (#25243)
- fix(ext/node): export JWK public key (#25239)
- fix(ext/node): import EC JWK keys (#25266)
- fix(ext/node): import JWK octet key pairs (#25180)
- fix(ext/node): import RSA JWK keys (#25267)
- fix(ext/node): throw when loading `cpu-features` module (#25257)
- fix(ext/node): update aead-gcm-stream to 0.3 (#25261)
- fix(ext/webgpu): allow to build on unsupported platforms (#25202)
- fix(fmt): fix incorrect quotes in components (#25249)
- fix(fmt/markdown): fix regression with multi-line footnotes and inline math
(#25222)
- fix(install): Use relative symlinks in deno install (#25164)
- fix(lsp): panic on url_to_uri() (#25238)
- fix(napi): Don't run microtasks in napi_resolve_deferred (#25246)
- fix(napi): Fix worker threads importing already-loaded NAPI addon (#25245)
- fix(node/cluster): improve stubs to make log4js work (#25146)
- fix(runtime/web_worker): populate `SnapshotOptions` for `WebWorker` when
instantiated without snapshot (#25280)
- fix(task): support tasks with colons in name in `deno run` (#25233)
- fix: handle showing warnings while the progress bar is shown (#25187)
- fix: reland async context (#25140)
- fix: removed unstable-htttp from deno help (#25216)
- fix: replace `npm install` hint with `deno install` hint (#25244)
- fix: update deno_doc (#25290)
- fix: upgrade deno_core to 0.307.0 (#25287)
- perf(ext/node): reduce some allocations in require (#25197)
### 1.46.1 / 2024.08.22
- fix(ext/node): http2session ready state (#25143)

View file

@ -2,7 +2,7 @@
[package]
name = "deno_bench_util"
version = "0.160.0"
version = "0.162.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

View file

@ -2,7 +2,7 @@
[package]
name = "deno"
version = "1.46.1"
version = "2.0.0-rc.1"
authors.workspace = true
default-run = "deno"
edition.workspace = true
@ -65,20 +65,19 @@ winres.workspace = true
[dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_cache_dir = { workspace = true }
deno_config = { version = "=0.30.1", features = ["workspace", "sync"] }
deno_config = { version = "=0.33.2", features = ["workspace", "sync"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "0.146.0", features = ["html", "syntect"] }
deno_emit = "=0.44.0"
deno_graph = { version = "=0.81.3" }
deno_lint = { version = "=0.63.1", features = ["docs"] }
deno_doc = { version = "0.148.0", features = ["html", "syntect"] }
deno_graph = { version = "=0.82.1" }
deno_lint = { version = "=0.65.0", features = ["docs"] }
deno_lockfile.workspace = true
deno_npm = "=0.24.0"
deno_npm = "=0.25.0"
deno_package_json.workspace = true
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_semver = "=0.5.10"
deno_semver.workspace = true
deno_task_shell = "=0.17.0"
deno_terminal.workspace = true
eszip = "=0.76.0"
eszip = "=0.78.0"
libsui = "0.3.0"
napi_sym.workspace = true
node_resolver.workspace = true
@ -91,8 +90,8 @@ bincode = "=1.3.3"
bytes.workspace = true
cache_control.workspace = true
chrono = { workspace = true, features = ["now"] }
clap = { version = "=4.5.13", features = ["env", "string", "wrap_help"] }
clap_complete = "=4.5.12"
clap = { version = "=4.5.16", features = ["env", "string", "wrap_help", "error-context"] }
clap_complete = "=4.5.24"
clap_complete_fig = "=4.5.2"
color-print = "0.3.5"
console_static_text.workspace = true
@ -103,7 +102,7 @@ dotenvy = "0.15.7"
dprint-plugin-json = "=0.19.3"
dprint-plugin-jupyter = "=0.1.3"
dprint-plugin-markdown = "=0.17.8"
dprint-plugin-typescript = "=0.91.6"
dprint-plugin-typescript = "=0.91.7"
env_logger = "=0.10.0"
fancy-regex = "=0.10.0"
faster-hex.workspace = true
@ -115,7 +114,7 @@ http.workspace = true
http-body.workspace = true
http-body-util.workspace = true
hyper-util.workspace = true
import_map = { version = "=0.20.0", features = ["ext"] }
import_map = { version = "=0.20.1", features = ["ext"] }
indexmap.workspace = true
jsonc-parser.workspace = true
jupyter_runtime = { package = "runtimelib", version = "=0.14.0" }
@ -162,6 +161,7 @@ typed-arena = "=2.0.2"
uuid = { workspace = true, features = ["serde"] }
walkdir = "=2.3.2"
which.workspace = true
yoke.workspace = true
zeromq.workspace = true
zip = { version = "2.1.6", default-features = false, features = ["deflate-flate2"] }
zstd.workspace = true

View file

@ -2,6 +2,7 @@
use std::collections::HashSet;
use deno_config::deno_json::TsConfigForEmit;
use deno_core::serde_json;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::jsr::JsrPackageReqReference;
@ -105,3 +106,18 @@ fn values_to_set<'a>(
}
entries
}
pub fn check_warn_tsconfig(ts_config: &TsConfigForEmit) {
if let Some(ignored_options) = &ts_config.maybe_ignored_options {
log::warn!("{}", ignored_options);
}
let serde_json::Value::Object(obj) = &ts_config.ts_config.0 else {
return;
};
if obj.get("experimentalDecorators") == Some(&serde_json::Value::Bool(true)) {
log::warn!(
"{} experimentalDecorators compiler option is deprecated and may be removed at any time",
deno_runtime::colors::yellow("Warning"),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -50,7 +50,7 @@ pub fn parse(paths: Vec<String>) -> clap::error::Result<Vec<String>> {
out.push(format!("{}:{}", host, port.0));
}
} else {
host_and_port.parse::<NetDescriptor>().map_err(|e| {
NetDescriptor::parse(&host_and_port).map_err(|e| {
clap::Error::raw(clap::error::ErrorKind::InvalidValue, format!("{e:?}"))
})?;
out.push(host_and_port)

View file

@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::BTreeSet;
use std::collections::HashSet;
use std::path::PathBuf;
use deno_config::deno_json::ConfigFile;
@ -12,6 +12,7 @@ use deno_core::parking_lot::MutexGuard;
use deno_lockfile::WorkspaceMemberConfig;
use deno_package_json::PackageJsonDepValue;
use deno_runtime::deno_node::PackageJson;
use deno_semver::jsr::JsrDepPackageReq;
use crate::cache;
use crate::util::fs::atomic_write_file_with_retries;
@ -98,7 +99,9 @@ impl CliLockfile {
flags: &Flags,
workspace: &Workspace,
) -> Result<Option<CliLockfile>, AnyError> {
fn pkg_json_deps(maybe_pkg_json: Option<&PackageJson>) -> BTreeSet<String> {
fn pkg_json_deps(
maybe_pkg_json: Option<&PackageJson>,
) -> HashSet<JsrDepPackageReq> {
let Some(pkg_json) = maybe_pkg_json else {
return Default::default();
};
@ -107,21 +110,21 @@ impl CliLockfile {
.values()
.filter_map(|dep| dep.as_ref().ok())
.filter_map(|dep| match dep {
PackageJsonDepValue::Req(req) => Some(req),
PackageJsonDepValue::Req(req) => {
Some(JsrDepPackageReq::npm(req.clone()))
}
PackageJsonDepValue::Workspace(_) => None,
})
.map(|r| format!("npm:{}", r))
.collect()
}
fn deno_json_deps(
maybe_deno_json: Option<&ConfigFile>,
) -> BTreeSet<String> {
) -> HashSet<JsrDepPackageReq> {
maybe_deno_json
.map(|c| {
crate::args::deno_json::deno_json_deps(c)
.into_iter()
.map(|req| req.to_string())
.collect()
})
.unwrap_or_default()
@ -157,15 +160,7 @@ impl CliLockfile {
.unwrap_or(false)
});
let lockfile = if flags.lock_write {
log::warn!(
"{} \"--lock-write\" flag is deprecated and will be removed in Deno 2.",
crate::colors::yellow("Warning")
);
CliLockfile::new(Lockfile::new_empty(filename, true), frozen)
} else {
Self::read_from_path(filename, frozen)?
};
let lockfile = Self::read_from_path(filename, frozen)?;
// initialize the lockfile with the workspace's configuration
let root_url = workspace.root_dir();
@ -215,6 +210,7 @@ impl CliLockfile {
Ok(Some(lockfile))
}
pub fn read_from_path(
file_path: PathBuf,
frozen: bool,
@ -243,12 +239,6 @@ impl CliLockfile {
}
let lockfile = self.lockfile.lock();
if lockfile.has_content_changed {
let suggested = if *super::DENO_FUTURE {
"`deno cache --frozen=false`, `deno install --frozen=false`,"
} else {
"`deno cache --frozen=false`"
};
let contents =
std::fs::read_to_string(&lockfile.filename).unwrap_or_default();
let new_contents = lockfile.as_json_string();
@ -256,7 +246,7 @@ impl CliLockfile {
// has an extra newline at the end
let diff = diff.trim_end();
Err(deno_core::anyhow::anyhow!(
"The lockfile is out of date. Run {suggested} or rerun with `--frozen=false` to update it.\nchanges:\n{diff}"
"The lockfile is out of date. Run `deno cache --frozen=false`, `deno install --frozen=false`, or rerun with `--frozen=false` to update it.\nchanges:\n{diff}"
))
} else {
Ok(())

View file

@ -8,7 +8,9 @@ mod lockfile;
mod package_json;
use deno_ast::SourceMapOption;
use deno_config::deno_json::NodeModulesDirMode;
use deno_config::workspace::CreateResolverOptions;
use deno_config::workspace::FolderConfigs;
use deno_config::workspace::PackageJsonDepResolution;
use deno_config::workspace::VendorEnablement;
use deno_config::workspace::Workspace;
@ -40,9 +42,10 @@ pub use deno_config::deno_json::TsConfigForEmit;
pub use deno_config::deno_json::TsConfigType;
pub use deno_config::deno_json::TsTypeLib;
pub use deno_config::glob::FilePatterns;
pub use deno_json::check_warn_tsconfig;
pub use flags::*;
pub use lockfile::CliLockfile;
pub use package_json::PackageJsonInstallDepsProvider;
pub use package_json::NpmInstallDepsProvider;
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::bail;
@ -50,7 +53,6 @@ use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::url::Url;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_permissions::PermissionsOptions;
use deno_runtime::deno_tls::deno_native_certs::load_native_certs;
use deno_runtime::deno_tls::rustls;
@ -63,6 +65,7 @@ use dotenvy::from_filename;
use once_cell::sync::Lazy;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
use std::io::BufReader;
@ -116,9 +119,6 @@ pub static DENO_DISABLE_PEDANTIC_NODE_WARNINGS: Lazy<bool> = Lazy::new(|| {
.is_some()
});
pub static DENO_FUTURE: Lazy<bool> =
Lazy::new(|| std::env::var("DENO_FUTURE").ok().is_some());
pub fn jsr_url() -> &'static Url {
static JSR_URL: Lazy<Url> = Lazy::new(|| {
let env_var_name = "JSR_URL";
@ -370,7 +370,7 @@ pub struct WorkspaceTestOptions {
pub doc: bool,
pub no_run: bool,
pub fail_fast: Option<NonZeroUsize>,
pub allow_none: bool,
pub permit_no_files: bool,
pub filter: Option<String>,
pub shuffle: Option<u64>,
pub concurrent_jobs: NonZeroUsize,
@ -383,7 +383,7 @@ pub struct WorkspaceTestOptions {
impl WorkspaceTestOptions {
pub fn resolve(test_flags: &TestFlags) -> Self {
Self {
allow_none: test_flags.allow_none,
permit_no_files: test_flags.permit_no_files,
concurrent_jobs: test_flags
.concurrent_jobs
.unwrap_or_else(|| NonZeroUsize::new(1).unwrap()),
@ -781,8 +781,6 @@ pub struct CliOptions {
maybe_lockfile: Option<Arc<CliLockfile>>,
overrides: CliOptionOverrides,
pub start_dir: Arc<WorkspaceDirectory>,
pub disable_deprecated_api_warning: bool,
pub verbose_deprecated_api_warning: bool,
pub deno_dir_provider: Arc<DenoDirProvider>,
}
@ -813,27 +811,18 @@ impl CliOptions {
}
let maybe_lockfile = maybe_lockfile.filter(|_| !force_global_cache);
let root_folder = start_dir.workspace.root_folder_configs();
let deno_dir_provider =
Arc::new(DenoDirProvider::new(flags.cache_path.clone()));
let maybe_node_modules_folder = resolve_node_modules_folder(
&initial_cwd,
&flags,
root_folder.deno_json.as_deref(),
root_folder.pkg_json.as_deref(),
&start_dir.workspace,
&deno_dir_provider,
)
.with_context(|| "Resolving node_modules folder.")?;
load_env_variables_from_env_file(flags.env_file.as_ref());
let disable_deprecated_api_warning = flags.log_level
== Some(log::Level::Error)
|| std::env::var("DENO_NO_DEPRECATION_WARNINGS").ok().is_some();
let verbose_deprecated_api_warning =
std::env::var("DENO_VERBOSE_WARNINGS").ok().is_some();
Ok(Self {
flags,
initial_cwd,
@ -842,8 +831,6 @@ impl CliOptions {
maybe_node_modules_folder,
overrides: Default::default(),
start_dir,
disable_deprecated_api_warning,
verbose_deprecated_api_warning,
deno_dir_provider,
})
}
@ -1137,15 +1124,8 @@ impl CliOptions {
self.flags.env_file.as_ref()
}
pub fn enable_future_features(&self) -> bool {
*DENO_FUTURE
}
pub fn resolve_main_module(&self) -> Result<ModuleSpecifier, AnyError> {
let main_module = match &self.flags.subcommand {
DenoSubcommand::Bundle(bundle_flags) => {
resolve_url_or_path(&bundle_flags.source_file, self.initial_cwd())?
}
DenoSubcommand::Compile(compile_flags) => {
resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())?
}
@ -1227,11 +1207,6 @@ impl CliOptions {
NPM_PROCESS_STATE.is_some()
}
/// Overrides the import map specifier to use.
pub fn set_import_map_specifier(&mut self, path: Option<ModuleSpecifier>) {
self.overrides.import_map_specifier = Some(path);
}
pub fn has_node_modules_dir(&self) -> bool {
self.maybe_node_modules_folder.is_some()
}
@ -1240,26 +1215,13 @@ impl CliOptions {
self.maybe_node_modules_folder.as_ref()
}
pub fn with_node_modules_dir_path(&self, path: PathBuf) -> Self {
Self {
flags: self.flags.clone(),
initial_cwd: self.initial_cwd.clone(),
maybe_node_modules_folder: Some(path),
npmrc: self.npmrc.clone(),
maybe_lockfile: self.maybe_lockfile.clone(),
start_dir: self.start_dir.clone(),
overrides: self.overrides.clone(),
disable_deprecated_api_warning: self.disable_deprecated_api_warning,
verbose_deprecated_api_warning: self.verbose_deprecated_api_warning,
deno_dir_provider: self.deno_dir_provider.clone(),
pub fn node_modules_dir(
&self,
) -> Result<Option<NodeModulesDirMode>, AnyError> {
if let Some(flag) = self.flags.node_modules_dir {
return Ok(Some(flag));
}
}
pub fn node_modules_dir_enablement(&self) -> Option<bool> {
self
.flags
.node_modules_dir
.or_else(|| self.workspace().node_modules_dir())
self.workspace().node_modules_dir().map_err(Into::into)
}
pub fn vendor_dir_path(&self) -> Option<&PathBuf> {
@ -1270,23 +1232,7 @@ impl CliOptions {
&self,
config_type: TsConfigType,
) -> Result<TsConfigForEmit, AnyError> {
let result = self.workspace().resolve_ts_config_for_emit(config_type);
match result {
Ok(mut ts_config_for_emit) => {
if matches!(self.flags.subcommand, DenoSubcommand::Bundle(..)) {
// For backwards compatibility, force `experimentalDecorators` setting
// to true.
*ts_config_for_emit
.ts_config
.0
.get_mut("experimentalDecorators")
.unwrap() = serde_json::Value::Bool(true);
}
Ok(ts_config_for_emit)
}
Err(err) => Err(err),
}
self.workspace().resolve_ts_config_for_emit(config_type)
}
pub fn resolve_inspector_server(
@ -1611,9 +1557,18 @@ impl CliOptions {
|| self.workspace().has_unstable("bare-node-builtins")
}
fn byonm_enabled(&self) -> bool {
// check if enabled via unstable
self.node_modules_dir().ok().flatten() == Some(NodeModulesDirMode::Manual)
|| NPM_PROCESS_STATE
.as_ref()
.map(|s| matches!(s.kind, NpmProcessStateKind::Byonm))
.unwrap_or(false)
}
pub fn use_byonm(&self) -> bool {
if self.enable_future_features()
&& self.node_modules_dir_enablement().is_none()
if self.node_modules_dir().ok().flatten().is_none()
&& self.maybe_node_modules_folder.is_some()
&& self
.workspace()
.config_folders()
@ -1623,13 +1578,7 @@ impl CliOptions {
return true;
}
// check if enabled via unstable
self.flags.unstable_config.byonm
|| NPM_PROCESS_STATE
.as_ref()
.map(|s| matches!(s.kind, NpmProcessStateKind::Byonm))
.unwrap_or(false)
|| self.workspace().has_unstable("byonm")
self.byonm_enabled()
}
pub fn unstable_sloppy_imports(&self) -> bool {
@ -1651,18 +1600,17 @@ impl CliOptions {
}
});
if *DENO_FUTURE {
let future_features = [
deno_runtime::deno_ffi::UNSTABLE_FEATURE_NAME.to_string(),
deno_runtime::deno_fs::UNSTABLE_FEATURE_NAME.to_string(),
deno_runtime::deno_webgpu::UNSTABLE_FEATURE_NAME.to_string(),
];
future_features.iter().for_each(|future_feature| {
if !from_config_file.contains(future_feature) {
from_config_file.push(future_feature.to_string());
}
});
}
// TODO(2.0): remove this code and enable these features in `99_main.js` by default.
let future_features = [
deno_runtime::deno_ffi::UNSTABLE_FEATURE_NAME.to_string(),
deno_runtime::deno_fs::UNSTABLE_FEATURE_NAME.to_string(),
deno_runtime::deno_webgpu::UNSTABLE_FEATURE_NAME.to_string(),
];
future_features.iter().for_each(|future_feature| {
if !from_config_file.contains(future_feature) {
from_config_file.push(future_feature.to_string());
}
});
if !from_config_file.is_empty() {
// collect unstable granular flags
@ -1761,42 +1709,55 @@ impl CliOptions {
fn resolve_node_modules_folder(
cwd: &Path,
flags: &Flags,
maybe_config_file: Option<&ConfigFile>,
maybe_package_json: Option<&PackageJson>,
workspace: &Workspace,
deno_dir_provider: &Arc<DenoDirProvider>,
) -> Result<Option<PathBuf>, AnyError> {
let use_node_modules_dir = flags
.node_modules_dir
.or_else(|| maybe_config_file.and_then(|c| c.json.node_modules_dir))
.or(flags.vendor)
.or_else(|| maybe_config_file.and_then(|c| c.json.vendor));
fn resolve_from_root(root_folder: &FolderConfigs, cwd: &Path) -> PathBuf {
root_folder
.deno_json
.as_ref()
.map(|c| Cow::Owned(c.dir_path()))
.or_else(|| {
root_folder
.pkg_json
.as_ref()
.map(|c| Cow::Borrowed(c.dir_path()))
})
.unwrap_or(Cow::Borrowed(cwd))
.join("node_modules")
}
let root_folder = workspace.root_folder_configs();
let use_node_modules_dir = if let Some(mode) = flags.node_modules_dir {
Some(mode.uses_node_modules_dir())
} else {
workspace
.node_modules_dir()?
.map(|m| m.uses_node_modules_dir())
.or(flags.vendor)
.or_else(|| root_folder.deno_json.as_ref().and_then(|c| c.json.vendor))
};
let path = if use_node_modules_dir == Some(false) {
return Ok(None);
} else if let Some(state) = &*NPM_PROCESS_STATE {
return Ok(state.local_node_modules_path.as_ref().map(PathBuf::from));
} else if let Some(package_json_path) = maybe_package_json.map(|c| &c.path) {
} else if root_folder.pkg_json.is_some() {
let node_modules_dir = resolve_from_root(root_folder, cwd);
if let Ok(deno_dir) = deno_dir_provider.get_or_create() {
// `deno_dir.root` can be symlink in macOS
if let Ok(root) = canonicalize_path_maybe_not_exists(&deno_dir.root) {
if package_json_path.starts_with(root) {
if node_modules_dir.starts_with(root) {
// if the package.json is in deno_dir, then do not use node_modules
// next to it as local node_modules dir
return Ok(None);
}
}
}
// auto-discover the local_node_modules_folder when a package.json exists
// and it's not in deno_dir
package_json_path.parent().unwrap().join("node_modules")
node_modules_dir
} else if use_node_modules_dir.is_none() {
return Ok(None);
} else if let Some(config_path) = maybe_config_file
.as_ref()
.and_then(|c| c.specifier.to_file_path().ok())
{
config_path.parent().unwrap().join("node_modules")
} else {
cwd.join("node_modules")
resolve_from_root(root_folder, cwd)
};
Ok(Some(canonicalize_path_maybe_not_exists(&path)?))
}
@ -1886,19 +1847,18 @@ pub fn npm_pkg_req_ref_to_binary_command(
pub fn config_to_deno_graph_workspace_member(
config: &ConfigFile,
) -> Result<deno_graph::WorkspaceMember, AnyError> {
let nv = deno_semver::package::PackageNv {
name: match &config.json.name {
Some(name) => name.clone(),
None => bail!("Missing 'name' field in config file."),
},
version: match &config.json.version {
Some(name) => deno_semver::Version::parse_standard(name)?,
None => bail!("Missing 'version' field in config file."),
},
let name = match &config.json.name {
Some(name) => name.clone(),
None => bail!("Missing 'name' field in config file."),
};
let version = match &config.json.version {
Some(name) => Some(deno_semver::Version::parse_standard(name)?),
None => None,
};
Ok(deno_graph::WorkspaceMember {
base: config.specifier.join("./").unwrap(),
nv,
name,
version,
exports: config.to_exports_config()?.into_map(),
})
}

View file

@ -1,17 +1,20 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use deno_config::workspace::Workspace;
use deno_core::serde_json;
use deno_package_json::PackageJsonDepValue;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
use crate::util::path::is_banned_path_char;
#[derive(Debug)]
pub struct InstallNpmRemotePkg {
pub alias: String,
// todo(24419): use this when setting up the node_modules dir
#[allow(dead_code)]
pub base_dir: PathBuf,
pub req: PackageReq,
}
@ -19,74 +22,126 @@ pub struct InstallNpmRemotePkg {
#[derive(Debug)]
pub struct InstallNpmWorkspacePkg {
pub alias: String,
// todo(24419): use this when setting up the node_modules dir
#[allow(dead_code)]
pub base_dir: PathBuf,
pub target_dir: PathBuf,
}
#[derive(Debug, Default)]
pub struct PackageJsonInstallDepsProvider {
pub struct NpmInstallDepsProvider {
remote_pkgs: Vec<InstallNpmRemotePkg>,
workspace_pkgs: Vec<InstallNpmWorkspacePkg>,
}
impl PackageJsonInstallDepsProvider {
impl NpmInstallDepsProvider {
pub fn empty() -> Self {
Self::default()
}
pub fn from_workspace(workspace: &Arc<Workspace>) -> Self {
// todo(dsherret): estimate capacity?
let mut workspace_pkgs = Vec::new();
let mut remote_pkgs = Vec::new();
let workspace_npm_pkgs = workspace.npm_packages();
for pkg_json in workspace.package_jsons() {
let deps = pkg_json.resolve_local_package_json_deps();
let mut pkg_pkgs = Vec::with_capacity(deps.len());
for (alias, dep) in deps {
let Ok(dep) = dep else {
continue;
};
match dep {
PackageJsonDepValue::Req(pkg_req) => {
let workspace_pkg = workspace_npm_pkgs.iter().find(|pkg| {
pkg.matches_req(&pkg_req)
// do not resolve to the current package
&& pkg.pkg_json.path != pkg_json.path
});
for (_, folder) in workspace.config_folders() {
let mut deno_json_aliases = HashSet::new();
// deal with the deno.json first because it takes precedence during resolution
if let Some(deno_json) = &folder.deno_json {
// don't bother with externally referenced import maps as users
// should inline their import map to get this behaviour
if let Some(serde_json::Value::Object(obj)) = &deno_json.json.imports {
deno_json_aliases.reserve(obj.len());
let mut pkg_pkgs = Vec::with_capacity(obj.len());
for (alias, value) in obj {
let serde_json::Value::String(specifier) = value else {
continue;
};
let Ok(npm_req_ref) = NpmPackageReqReference::from_str(specifier)
else {
continue;
};
// skip any aliases with banned characters
if alias.chars().any(|c| c == '\\' || is_banned_path_char(c)) {
continue;
}
deno_json_aliases.insert(alias.to_lowercase());
let pkg_req = npm_req_ref.into_inner().req;
let workspace_pkg = workspace_npm_pkgs
.iter()
.find(|pkg| pkg.matches_req(&pkg_req));
if let Some(pkg) = workspace_pkg {
workspace_pkgs.push(InstallNpmWorkspacePkg {
alias,
base_dir: pkg_json.dir_path().to_path_buf(),
alias: alias.to_string(),
target_dir: pkg.pkg_json.dir_path().to_path_buf(),
});
} else {
pkg_pkgs.push(InstallNpmRemotePkg {
alias,
base_dir: pkg_json.dir_path().to_path_buf(),
alias: alias.to_string(),
base_dir: deno_json.dir_path(),
req: pkg_req,
});
}
}
PackageJsonDepValue::Workspace(version_req) => {
if let Some(pkg) = workspace_npm_pkgs.iter().find(|pkg| {
pkg.matches_name_and_version_req(&alias, &version_req)
}) {
workspace_pkgs.push(InstallNpmWorkspacePkg {
alias,
base_dir: pkg_json.dir_path().to_path_buf(),
target_dir: pkg.pkg_json.dir_path().to_path_buf(),
// sort within each package (more like npm resolution)
pkg_pkgs.sort_by(|a, b| a.alias.cmp(&b.alias));
remote_pkgs.extend(pkg_pkgs);
}
}
if let Some(pkg_json) = &folder.pkg_json {
let deps = pkg_json.resolve_local_package_json_deps();
let mut pkg_pkgs = Vec::with_capacity(deps.len());
for (alias, dep) in deps {
let Ok(dep) = dep else {
continue;
};
if deno_json_aliases.contains(&alias.to_lowercase()) {
// aliases in deno.json take precedence over package.json, so
// since this can't be resolved don't bother installing it
continue;
}
match dep {
PackageJsonDepValue::Req(pkg_req) => {
let workspace_pkg = workspace_npm_pkgs.iter().find(|pkg| {
pkg.matches_req(&pkg_req)
// do not resolve to the current package
&& pkg.pkg_json.path != pkg_json.path
});
if let Some(pkg) = workspace_pkg {
workspace_pkgs.push(InstallNpmWorkspacePkg {
alias,
target_dir: pkg.pkg_json.dir_path().to_path_buf(),
});
} else {
pkg_pkgs.push(InstallNpmRemotePkg {
alias,
base_dir: pkg_json.dir_path().to_path_buf(),
req: pkg_req,
});
}
}
PackageJsonDepValue::Workspace(version_req) => {
if let Some(pkg) = workspace_npm_pkgs.iter().find(|pkg| {
pkg.matches_name_and_version_req(&alias, &version_req)
}) {
workspace_pkgs.push(InstallNpmWorkspacePkg {
alias,
target_dir: pkg.pkg_json.dir_path().to_path_buf(),
});
}
}
}
}
}
// sort within each package
pkg_pkgs.sort_by(|a, b| a.alias.cmp(&b.alias));
remote_pkgs.extend(pkg_pkgs);
// sort within each package as npm does
pkg_pkgs.sort_by(|a, b| a.alias.cmp(&b.alias));
remote_pkgs.extend(pkg_pkgs);
}
}
remote_pkgs.shrink_to_fit();
workspace_pkgs.shrink_to_fit();
Self {

View file

@ -46,8 +46,7 @@ Deno.bench("b64_rt_short", { n: 1e6 }, () => {
const buf = new Uint8Array(100);
const file = Deno.openSync("/dev/zero");
Deno.bench("read_zero", { n: 5e5 }, () => {
// deno-lint-ignore no-deprecated-deno-api
Deno.readSync(file.rid, buf);
file.readSync(buf);
});
}

View file

@ -143,29 +143,6 @@ const EXEC_TIME_BENCHMARKS: &[(&str, &[&str], Option<i32>)] = &[
],
None,
),
(
"bundle",
&[
"bundle",
"--unstable",
"--config",
"tests/config/deno.json",
"tests/util/std/http/file_server_test.ts",
],
None,
),
(
"bundle_no_check",
&[
"bundle",
"--no-check",
"--unstable",
"--config",
"tests/config/deno.json",
"tests/util/std/http/file_server_test.ts",
],
None,
),
];
const RESULT_KEYS: &[&str] =
@ -314,40 +291,6 @@ fn get_binary_sizes(target_dir: &Path) -> Result<HashMap<String, i64>> {
Ok(sizes)
}
const BUNDLES: &[(&str, &str)] = &[
("file_server", "./tests/util/std/http/file_server.ts"),
("welcome", "./tests/testdata/welcome.ts"),
];
fn bundle_benchmark(deno_exe: &Path) -> Result<HashMap<String, i64>> {
let mut sizes = HashMap::<String, i64>::new();
for (name, url) in BUNDLES {
let path = format!("{name}.bundle.js");
test_util::run(
&[
deno_exe.to_str().unwrap(),
"bundle",
"--unstable",
"--config",
"tests/config/deno.json",
url,
&path,
],
None,
None,
None,
true,
);
let file = PathBuf::from(path);
assert!(file.is_file());
sizes.insert(name.to_string(), file.metadata()?.len() as i64);
let _ = fs::remove_file(file);
}
Ok(sizes)
}
fn run_max_mem_benchmark(deno_exe: &Path) -> Result<HashMap<String, i64>> {
let mut results = HashMap::<String, i64>::new();
@ -415,7 +358,6 @@ async fn main() -> Result<()> {
let mut args = env::args();
let mut benchmarks = vec![
"bundle",
"exec_time",
"binary_size",
"cargo_deps",
@ -465,11 +407,6 @@ async fn main() -> Result<()> {
..Default::default()
};
if benchmarks.contains(&"bundle") {
let bundle_size = bundle_benchmark(&deno_exe)?;
new_data.bundle_size = bundle_size;
}
if benchmarks.contains(&"exec_time") {
let exec_times = run_exec_time(&deno_exe, &target_dir)?;
new_data.benchmark = exec_times;

13
cli/cache/emit.rs vendored
View file

@ -82,19 +82,6 @@ impl EmitCache {
Ok(())
}
/// Gets the filepath which stores the emit.
pub fn get_emit_filepath(
&self,
specifier: &ModuleSpecifier,
) -> Option<PathBuf> {
Some(
self
.disk_cache
.location
.join(self.get_emit_filename(specifier)?),
)
}
fn get_emit_filename(&self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
self
.disk_cache

64
cli/cache/mod.rs vendored
View file

@ -8,6 +8,7 @@ use crate::file_fetcher::FileFetcher;
use crate::file_fetcher::FileOrRedirect;
use crate::npm::CliNpmResolver;
use crate::util::fs::atomic_write_file_with_retries;
use crate::util::path::specifier_has_extension;
use deno_ast::MediaType;
use deno_core::futures;
@ -74,6 +75,10 @@ impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv {
atomic_write_file_with_retries(path, bytes, CACHE_PERM)
}
fn remove_file(&self, path: &Path) -> std::io::Result<()> {
std::fs::remove_file(path)
}
fn modified(&self, path: &Path) -> std::io::Result<Option<SystemTime>> {
match std::fs::metadata(path) {
Ok(metadata) => Ok(Some(
@ -102,7 +107,6 @@ pub use deno_cache_dir::HttpCache;
/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides
/// a concise interface to the DENO_DIR when building module graphs.
pub struct FetchCacher {
emit_cache: Arc<EmitCache>,
file_fetcher: Arc<FileFetcher>,
file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
global_http_cache: Arc<GlobalHttpCache>,
@ -114,7 +118,6 @@ pub struct FetchCacher {
impl FetchCacher {
pub fn new(
emit_cache: Arc<EmitCache>,
file_fetcher: Arc<FileFetcher>,
file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>,
global_http_cache: Arc<GlobalHttpCache>,
@ -123,7 +126,6 @@ impl FetchCacher {
permissions: PermissionsContainer,
) -> Self {
Self {
emit_cache,
file_fetcher,
file_header_overrides,
global_http_cache,
@ -140,15 +142,7 @@ impl FetchCacher {
self.cache_info_enabled = true;
}
// DEPRECATED: Where the file is stored and how it's stored should be an implementation
// detail of the cache.
//
// todo(dsheret): remove once implementing
// * https://github.com/denoland/deno/issues/17707
// * https://github.com/denoland/deno/issues/17703
#[deprecated(
note = "There should not be a way to do this because the file may not be cached at a local path in the future."
)]
/// Only use this for `deno info`.
fn get_local_path(&self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
// TODO(@kitsonk) fix when deno_graph does not query cache for synthetic
// modules
@ -175,15 +169,7 @@ impl Loader for FetchCacher {
#[allow(deprecated)]
let local = self.get_local_path(specifier)?;
if local.is_file() {
let emit = self
.emit_cache
.get_emit_filepath(specifier)
.filter(|p| p.is_file());
Some(CacheInfo {
local: Some(local),
emit,
map: None,
})
Some(CacheInfo { local: Some(local) })
} else {
None
}
@ -196,19 +182,28 @@ impl Loader for FetchCacher {
) -> LoadFuture {
use deno_graph::source::CacheSetting as LoaderCacheSetting;
if specifier.scheme() == "file"
&& specifier.path().contains("/node_modules/")
{
// The specifier might be in a completely different symlinked tree than
// what the node_modules url is in (ex. `/my-project-1/node_modules`
// symlinked to `/my-project-2/node_modules`), so first we checked if the path
// is in a node_modules dir to avoid needlessly canonicalizing, then now compare
// against the canonicalized specifier.
let specifier =
crate::node::resolve_specifier_into_node_modules(specifier);
if self.npm_resolver.in_npm_package(&specifier) {
if specifier.scheme() == "file" {
if specifier.path().contains("/node_modules/") {
// The specifier might be in a completely different symlinked tree than
// what the node_modules url is in (ex. `/my-project-1/node_modules`
// symlinked to `/my-project-2/node_modules`), so first we checked if the path
// is in a node_modules dir to avoid needlessly canonicalizing, then now compare
// against the canonicalized specifier.
let specifier =
crate::node::resolve_specifier_into_node_modules(specifier);
if self.npm_resolver.in_npm_package(&specifier) {
return Box::pin(futures::future::ready(Ok(Some(
LoadResponse::External { specifier },
))));
}
}
// make local CJS modules external to the graph
if specifier_has_extension(specifier, "cjs") {
return Box::pin(futures::future::ready(Ok(Some(
LoadResponse::External { specifier },
LoadResponse::External {
specifier: specifier.clone(),
},
))));
}
}
@ -289,6 +284,7 @@ impl Loader for FetchCacher {
fn cache_module_info(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
source: &Arc<[u8]>,
module_info: &deno_graph::ModuleInfo,
) {
@ -296,7 +292,7 @@ impl Loader for FetchCacher {
let source_hash = CacheDBHash::from_source(source);
let result = self.module_info_cache.set_module_info(
specifier,
MediaType::from_specifier(specifier),
media_type,
source_hash,
module_info,
);

View file

@ -112,28 +112,26 @@ impl Emitter {
let parsed_source_cache = self.parsed_source_cache.clone();
let transpile_and_emit_options =
self.transpile_and_emit_options.clone();
let (should_cache, transpile_result) =
deno_core::unsync::spawn_blocking({
let specifier = specifier.clone();
let source = source.clone();
move || -> Result<_, AnyError> {
EmitParsedSourceHelper::transpile(
&parsed_source_cache,
&specifier,
source.clone(),
media_type,
&transpile_and_emit_options.0,
&transpile_and_emit_options.1,
)
}
})
.await
.unwrap()?;
let transpile_result = deno_core::unsync::spawn_blocking({
let specifier = specifier.clone();
let source = source.clone();
move || -> Result<_, AnyError> {
EmitParsedSourceHelper::transpile(
&parsed_source_cache,
&specifier,
source.clone(),
media_type,
&transpile_and_emit_options.0,
&transpile_and_emit_options.1,
)
}
})
.await
.unwrap()?;
Ok(helper.post_emit_parsed_source(
specifier,
transpile_result,
source_hash,
should_cache,
))
}
}
@ -150,20 +148,18 @@ impl Emitter {
match helper.pre_emit_parsed_source(specifier, source) {
PreEmitResult::Cached(emitted_text) => Ok(emitted_text),
PreEmitResult::NotCached { source_hash } => {
let (should_cache, transpile_result) =
EmitParsedSourceHelper::transpile(
&self.parsed_source_cache,
specifier,
source.clone(),
media_type,
&self.transpile_and_emit_options.0,
&self.transpile_and_emit_options.1,
)?;
let transpile_result = EmitParsedSourceHelper::transpile(
&self.parsed_source_cache,
specifier,
source.clone(),
media_type,
&self.transpile_and_emit_options.0,
&self.transpile_and_emit_options.1,
)?;
Ok(helper.post_emit_parsed_source(
specifier,
transpile_result,
source_hash,
should_cache,
))
}
}
@ -261,16 +257,13 @@ impl<'a> EmitParsedSourceHelper<'a> {
media_type: MediaType,
transpile_options: &deno_ast::TranspileOptions,
emit_options: &deno_ast::EmitOptions,
) -> Result<(bool, TranspileResult), AnyError> {
) -> Result<TranspileResult, AnyError> {
// nothing else needs the parsed source at this point, so remove from
// the cache in order to not transpile owned
let parsed_source = parsed_source_cache
.remove_or_parse_module(specifier, source, media_type)?;
let should_cache = !has_import_assertion(&parsed_source);
Ok((
should_cache,
parsed_source.transpile(transpile_options, emit_options)?,
))
ensure_no_import_assertion(&parsed_source)?;
Ok(parsed_source.transpile(transpile_options, emit_options)?)
}
pub fn post_emit_parsed_source(
@ -278,8 +271,6 @@ impl<'a> EmitParsedSourceHelper<'a> {
specifier: &ModuleSpecifier,
transpile_result: TranspileResult,
source_hash: u64,
// todo(dsherret): remove after Deno 2.0
should_cache: bool,
) -> ModuleCodeBytes {
let transpiled_source = match transpile_result {
TranspileResult::Owned(source) => source,
@ -289,44 +280,47 @@ impl<'a> EmitParsedSourceHelper<'a> {
}
};
debug_assert!(transpiled_source.source_map.is_none());
if should_cache {
self.0.emit_cache.set_emit_code(
specifier,
source_hash,
&transpiled_source.source,
);
}
self.0.emit_cache.set_emit_code(
specifier,
source_hash,
&transpiled_source.source,
);
transpiled_source.source.into_boxed_slice().into()
}
}
fn has_import_assertion(parsed_source: &deno_ast::ParsedSource) -> bool {
// todo(dsherret): this is a temporary measure until we have swc erroring for this
fn ensure_no_import_assertion(
parsed_source: &deno_ast::ParsedSource,
) -> Result<(), AnyError> {
fn has_import_assertion(text: &str) -> bool {
// good enough
text.contains(" assert ") && !text.contains(" with ")
}
fn warn_import_attribute(
fn create_err(
parsed_source: &deno_ast::ParsedSource,
range: SourceRange,
) {
) -> AnyError {
let text_info = parsed_source.text_info_lazy();
let loc = text_info.line_and_column_display(range.start);
deno_runtime::import_assertion_callback(
deno_core::ImportAssertionsSupportCustomCallbackArgs {
maybe_specifier: Some(parsed_source.specifier().to_string()),
maybe_line_number: Some(loc.line_number),
column_number: loc.column_number,
maybe_source_line: Some(range.text_fast(text_info).to_string()),
},
)
let mut msg = "Import assertions are deprecated. Use `with` keyword, instead of 'assert' keyword.".to_string();
msg.push_str("\n\n");
msg.push_str(range.text_fast(text_info));
msg.push_str("\n\n");
msg.push_str(&format!(
" at {}:{}:{}\n",
parsed_source.specifier(),
loc.line_number,
loc.column_number,
));
deno_core::anyhow::anyhow!("{}", msg)
}
let Some(module) = parsed_source.program_ref().as_module() else {
return false;
return Ok(());
};
let mut had_import_assertion = false;
for item in &module.body {
match item {
deno_ast::swc::ast::ModuleItem::ModuleDecl(decl) => match decl {
@ -334,24 +328,21 @@ fn has_import_assertion(parsed_source: &deno_ast::ParsedSource) -> bool {
if n.with.is_some()
&& has_import_assertion(n.text_fast(parsed_source.text_info_lazy()))
{
had_import_assertion = true;
warn_import_attribute(parsed_source, n.range());
return Err(create_err(parsed_source, n.range()));
}
}
deno_ast::swc::ast::ModuleDecl::ExportAll(n) => {
if n.with.is_some()
&& has_import_assertion(n.text_fast(parsed_source.text_info_lazy()))
{
had_import_assertion = true;
warn_import_attribute(parsed_source, n.range());
return Err(create_err(parsed_source, n.range()));
}
}
deno_ast::swc::ast::ModuleDecl::ExportNamed(n) => {
if n.with.is_some()
&& has_import_assertion(n.text_fast(parsed_source.text_info_lazy()))
{
had_import_assertion = true;
warn_import_attribute(parsed_source, n.range());
return Err(create_err(parsed_source, n.range()));
}
}
deno_ast::swc::ast::ModuleDecl::ExportDecl(_)
@ -364,5 +355,6 @@ fn has_import_assertion(parsed_source: &deno_ast::ParsedSource) -> bool {
deno_ast::swc::ast::ModuleItem::Stmt(_) => {}
}
}
had_import_assertion
Ok(())
}

View file

@ -1,11 +1,12 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::check_warn_tsconfig;
use crate::args::get_root_cert_store;
use crate::args::CaData;
use crate::args::CliOptions;
use crate::args::DenoSubcommand;
use crate::args::Flags;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::args::StorageKeyResolver;
use crate::args::TsConfigType;
use crate::cache::Caches;
@ -386,9 +387,7 @@ impl CliFactory {
cache_setting: cli_options.cache_setting(),
text_only_progress_bar: self.text_only_progress_bar().clone(),
maybe_node_modules_path: cli_options.node_modules_dir_path().cloned(),
package_json_deps_provider: Arc::new(PackageJsonInstallDepsProvider::from_workspace(
cli_options.workspace(),
)),
npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::from_workspace(cli_options.workspace())),
npm_system_info: cli_options.npm_system_info(),
npmrc: cli_options.npmrc().clone(),
lifecycle_scripts: cli_options.lifecycle_scripts_config(),
@ -522,9 +521,7 @@ impl CliFactory {
let cli_options = self.cli_options()?;
let ts_config_result =
cli_options.resolve_ts_config_for_emit(TsConfigType::Emit)?;
if let Some(ignored_options) = ts_config_result.maybe_ignored_options {
warn!("{}", ignored_options);
}
check_warn_tsconfig(&ts_config_result);
let (transpile_options, emit_options) =
crate::args::ts_config_to_transpile_and_emit_options(
ts_config_result.ts_config,
@ -619,7 +616,6 @@ impl CliFactory {
self.parsed_source_cache().clone(),
cli_options.maybe_lockfile().cloned(),
self.maybe_file_watcher_reporter().clone(),
self.emit_cache()?.clone(),
self.file_fetcher()?.clone(),
self.global_http_cache()?.clone(),
)))
@ -793,20 +789,12 @@ impl CliFactory {
self.maybe_inspector_server()?.clone(),
cli_options.maybe_lockfile().cloned(),
self.feature_checker()?.clone(),
self.create_cli_main_worker_options()?,
cli_options.node_ipc_fd(),
cli_options.serve_port(),
cli_options.serve_host(),
cli_options.enable_future_features(),
// TODO(bartlomieju): temporarily disabled
// cli_options.disable_deprecated_api_warning,
true,
cli_options.verbose_deprecated_api_warning,
if cli_options.code_cache_enabled() {
Some(self.code_cache()?.clone())
} else {
None
},
self.create_cli_main_worker_options()?,
))
}
@ -871,6 +859,9 @@ impl CliFactory {
unstable: cli_options.legacy_unstable_flag(),
create_hmr_runner,
create_coverage_collector,
node_ipc: cli_options.node_ipc_fd(),
serve_port: cli_options.serve_port(),
serve_host: cli_options.serve_host(),
})
}
}

View file

@ -20,13 +20,12 @@ use crate::tools::check::TypeChecker;
use crate::util::file_watcher::WatcherCommunicator;
use crate::util::fs::canonicalize_path;
use deno_config::workspace::JsrPackageConfig;
use deno_emit::LoaderChecksum;
use deno_graph::source::LoaderChecksum;
use deno_graph::JsrLoadError;
use deno_graph::ModuleLoadError;
use deno_graph::WorkspaceFastCheckOption;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_core::anyhow::bail;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@ -35,7 +34,6 @@ use deno_graph::source::Loader;
use deno_graph::source::ResolutionMode;
use deno_graph::source::ResolveError;
use deno_graph::GraphKind;
use deno_graph::Module;
use deno_graph::ModuleError;
use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError;
@ -44,10 +42,13 @@ use deno_graph::SpecifierError;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node;
use deno_runtime::deno_permissions::PermissionsContainer;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use deno_semver::Version;
use import_map::ImportMapError;
use std::collections::HashSet;
use std::error::Error;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
@ -110,7 +111,7 @@ pub fn graph_valid(
ModuleGraphError::ModuleError(error) => {
enhanced_lockfile_error_message(error)
.or_else(|| enhanced_sloppy_imports_error_message(fs, error))
.unwrap_or_else(|| format!("{}", error))
.unwrap_or_else(|| format_deno_graph_error(error))
}
};
@ -164,7 +165,10 @@ pub fn graph_valid(
} else {
// finally surface the npm resolution result
if let Err(err) = &graph.npm_dep_graph_result {
return Err(custom_error(get_error_class_name(err), format!("{}", err)));
return Err(custom_error(
get_error_class_name(err),
format_deno_graph_error(err.as_ref().deref()),
));
}
Ok(())
}
@ -364,7 +368,6 @@ pub struct ModuleGraphBuilder {
parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<CliLockfile>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: Arc<cache::EmitCache>,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
}
@ -381,7 +384,6 @@ impl ModuleGraphBuilder {
parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<CliLockfile>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: Arc<cache::EmitCache>,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
) -> Self {
@ -395,7 +397,6 @@ impl ModuleGraphBuilder {
parsed_source_cache,
lockfile,
maybe_file_watcher_reporter,
emit_cache,
file_fetcher,
global_http_cache,
}
@ -463,7 +464,7 @@ impl ModuleGraphBuilder {
.content
.packages
.jsr
.get(&package_nv.to_string())
.get(package_nv)
.map(|s| LoaderChecksum::new(s.integrity.clone()))
}
@ -477,7 +478,7 @@ impl ModuleGraphBuilder {
self
.0
.lock()
.insert_package(package_nv.to_string(), checksum.into_string());
.insert_package(package_nv.clone(), checksum.into_string());
}
}
@ -535,7 +536,12 @@ impl ModuleGraphBuilder {
) -> Result<(), AnyError> {
// ensure an "npm install" is done if the user has explicitly
// opted into using a node_modules directory
if self.options.node_modules_dir_enablement() == Some(true) {
if self
.options
.node_modules_dir()?
.map(|m| m.uses_node_modules_dir())
.unwrap_or(false)
{
if let Some(npm_resolver) = self.npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
}
@ -556,16 +562,21 @@ impl ModuleGraphBuilder {
}
}
}
for (key, value) in &lockfile.content.packages.specifiers {
if let Some(key) = key
.strip_prefix("jsr:")
.and_then(|key| PackageReq::from_str(key).ok())
{
if let Some(value) = value
.strip_prefix("jsr:")
.and_then(|value| PackageNv::from_str(value).ok())
{
graph.packages.add_nv(key, value);
for (req_dep, value) in &lockfile.content.packages.specifiers {
match req_dep.kind {
deno_semver::package::PackageKind::Jsr => {
if let Ok(version) = Version::parse_standard(value) {
graph.packages.add_nv(
req_dep.req.clone(),
PackageNv {
name: req_dep.req.name.clone(),
version,
},
);
}
}
deno_semver::package::PackageKind::Npm => {
// ignore
}
}
}
@ -603,16 +614,15 @@ impl ModuleGraphBuilder {
if has_jsr_package_mappings_changed {
for (from, to) in graph.packages.mappings() {
lockfile.insert_package_specifier(
format!("jsr:{}", from),
format!("jsr:{}", to),
JsrDepPackageReq::jsr(from.clone()),
to.version.to_string(),
);
}
}
// jsr packages
if has_jsr_package_deps_changed {
for (name, deps) in graph.packages.packages_with_deps() {
lockfile
.add_package_deps(&name.to_string(), deps.map(|s| s.to_string()));
for (nv, deps) in graph.packages.packages_with_deps() {
lockfile.add_package_deps(nv, deps.cloned());
}
}
}
@ -668,7 +678,6 @@ impl ModuleGraphBuilder {
permissions: PermissionsContainer,
) -> cache::FetchCacher {
cache::FetchCacher::new(
self.emit_cache.clone(),
self.file_fetcher.clone(),
self.options.resolve_file_header_overrides(),
self.global_http_cache.clone(),
@ -707,33 +716,29 @@ impl ModuleGraphBuilder {
}
}
pub fn error_for_any_npm_specifier(
graph: &ModuleGraph,
) -> Result<(), AnyError> {
for module in graph.modules() {
match module {
Module::Npm(module) => {
bail!("npm specifiers have not yet been implemented for this subcommand (https://github.com/denoland/deno/issues/15960). Found: {}", module.specifier)
}
Module::Node(module) => {
bail!("Node specifiers have not yet been implemented for this subcommand (https://github.com/denoland/deno/issues/15960). Found: node:{}", module.module_name)
}
Module::Js(_) | Module::Json(_) | Module::External(_) => {}
}
}
Ok(())
}
/// Adds more explanatory information to a resolution error.
pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String {
let mut message = format!("{error}");
let mut message = format_deno_graph_error(error);
if let Some(specifier) = get_resolution_error_bare_node_specifier(error) {
let maybe_hint = if let Some(specifier) =
get_resolution_error_bare_node_specifier(error)
{
if !*DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
message.push_str(&format!(
"\nIf you want to use a built-in Node module, add a \"node:\" prefix (ex. \"node:{specifier}\")."
));
Some(format!("If you want to use a built-in Node module, add a \"node:\" prefix (ex. \"node:{specifier}\")."))
} else {
None
}
} else {
get_import_prefix_missing_error(error).map(|specifier| {
format!(
"If you want to use a JSR or npm package, try running `deno add jsr:{}` or `deno add npm:{}`",
specifier, specifier
)
})
};
if let Some(hint) = maybe_hint {
message.push_str(&format!("\n {} {}", colors::cyan("hint:"), hint));
}
message
@ -868,6 +873,50 @@ fn get_resolution_error_bare_specifier(
}
}
fn get_import_prefix_missing_error(error: &ResolutionError) -> Option<&str> {
let mut maybe_specifier = None;
if let ResolutionError::InvalidSpecifier {
error: SpecifierError::ImportPrefixMissing { specifier, .. },
range,
} = error
{
if range.specifier.scheme() == "file" {
maybe_specifier = Some(specifier);
}
} else if let ResolutionError::ResolverError { error, range, .. } = error {
if range.specifier.scheme() == "file" {
match error.as_ref() {
ResolveError::Specifier(specifier_error) => {
if let SpecifierError::ImportPrefixMissing { specifier, .. } =
specifier_error
{
maybe_specifier = Some(specifier);
}
}
ResolveError::Other(other_error) => {
if let Some(SpecifierError::ImportPrefixMissing {
specifier, ..
}) = other_error.downcast_ref::<SpecifierError>()
{
maybe_specifier = Some(specifier);
}
}
}
}
}
// NOTE(bartlomieju): For now, return None if a specifier contains a dot or a space. This is because
// suggesting to `deno add bad-module.ts` makes no sense and is worse than not providing
// a suggestion at all. This should be improved further in the future
if let Some(specifier) = maybe_specifier {
if specifier.contains('.') || specifier.contains(' ') {
return None;
}
}
maybe_specifier.map(|s| s.as_str())
}
/// Gets if any of the specified root's "file:" dependents are in the
/// provided changed set.
pub fn has_graph_root_local_dependent_changed(
@ -1022,6 +1071,49 @@ impl deno_graph::source::JsrUrlProvider for CliJsrUrlProvider {
}
}
// todo(dsherret): We should change ModuleError to use thiserror so that
// we don't need to do this.
fn format_deno_graph_error(err: &dyn Error) -> String {
use std::fmt::Write;
let mut message = format!("{}", err);
let mut maybe_source = err.source();
if maybe_source.is_some() {
let mut past_message = message.clone();
let mut count = 0;
let mut display_count = 0;
while let Some(source) = maybe_source {
let current_message = format!("{}", source);
maybe_source = source.source();
// sometimes an error might be repeated due to
// being boxed multiple times in another AnyError
if current_message != past_message {
write!(message, "\n {}: ", display_count,).unwrap();
for (i, line) in current_message.lines().enumerate() {
if i > 0 {
write!(message, "\n {}", line).unwrap();
} else {
write!(message, "{}", line).unwrap();
}
}
display_count += 1;
}
if count > 8 {
write!(message, "\n {}: ...", count).unwrap();
break;
}
past_message = current_message;
count += 1;
}
}
message
}
#[cfg(test)]
mod test {
use std::sync::Arc;

View file

@ -104,12 +104,12 @@ function bench(
}
if (optionsOrFn.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, bench function is already provided as the third argument.",
"Unexpected 'fn' field in options, bench function is already provided as the third argument",
);
}
if (optionsOrFn.name != undefined) {
throw new TypeError(
"Unexpected 'name' field in options, bench name is already provided as the first argument.",
"Unexpected 'name' field in options, bench name is already provided as the first argument",
);
}
benchDesc = {
@ -141,7 +141,7 @@ function bench(
fn = optionsOrFn;
if (nameOrFnOrOptions.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, bench function is already provided as the second argument.",
"Unexpected 'fn' field in options, bench function is already provided as the second argument",
);
}
name = nameOrFnOrOptions.name ?? fn.name;
@ -150,7 +150,7 @@ function bench(
!nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
) {
throw new TypeError(
"Expected 'fn' field in the first argument to be a bench function.",
"Expected 'fn' field in the first argument to be a bench function",
);
}
fn = nameOrFnOrOptions.fn;
@ -385,12 +385,12 @@ function createBenchContext(desc) {
start() {
if (currentBenchId !== desc.id) {
throw new TypeError(
"The benchmark which this context belongs to is not being executed.",
"The benchmark which this context belongs to is not being executed",
);
}
if (currentBenchUserExplicitStart != null) {
throw new TypeError(
"BenchContext::start() has already been invoked.",
"BenchContext::start() has already been invoked",
);
}
currentBenchUserExplicitStart = benchNow();
@ -399,11 +399,11 @@ function createBenchContext(desc) {
const end = benchNow();
if (currentBenchId !== desc.id) {
throw new TypeError(
"The benchmark which this context belongs to is not being executed.",
"The benchmark which this context belongs to is not being executed",
);
}
if (currentBenchUserExplicitEnd != null) {
throw new TypeError("BenchContext::end() has already been invoked.");
throw new TypeError("BenchContext::end() has already been invoked");
}
currentBenchUserExplicitEnd = end;
},

View file

@ -113,7 +113,7 @@ function assertExit(fn, isTest) {
throw new Error(
`${
isTest ? "Test case" : "Bench"
} finished with exit code set to ${exitCode}.`,
} finished with exit code set to ${exitCode}`,
);
}
if (innerResult) {
@ -242,12 +242,12 @@ function testInner(
}
if (optionsOrFn.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, test function is already provided as the third argument.",
"Unexpected 'fn' field in options, test function is already provided as the third argument",
);
}
if (optionsOrFn.name != undefined) {
throw new TypeError(
"Unexpected 'name' field in options, test name is already provided as the first argument.",
"Unexpected 'name' field in options, test name is already provided as the first argument",
);
}
testDesc = {
@ -279,7 +279,7 @@ function testInner(
fn = optionsOrFn;
if (nameOrFnOrOptions.fn != undefined) {
throw new TypeError(
"Unexpected 'fn' field in options, test function is already provided as the second argument.",
"Unexpected 'fn' field in options, test function is already provided as the second argument",
);
}
name = nameOrFnOrOptions.name ?? fn.name;
@ -288,7 +288,7 @@ function testInner(
!nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
) {
throw new TypeError(
"Expected 'fn' field in the first argument to be a test function.",
"Expected 'fn' field in the first argument to be a test function",
);
}
fn = nameOrFnOrOptions.fn;
@ -426,7 +426,7 @@ function createTestContext(desc) {
let stepDesc;
if (typeof nameOrFnOrOptions === "string") {
if (typeof maybeFn !== "function") {
throw new TypeError("Expected function for second argument.");
throw new TypeError("Expected function for second argument");
}
stepDesc = {
name: nameOrFnOrOptions,
@ -434,7 +434,7 @@ function createTestContext(desc) {
};
} else if (typeof nameOrFnOrOptions === "function") {
if (!nameOrFnOrOptions.name) {
throw new TypeError("The step function must have a name.");
throw new TypeError("The step function must have a name");
}
if (maybeFn != undefined) {
throw new TypeError(
@ -449,7 +449,7 @@ function createTestContext(desc) {
stepDesc = nameOrFnOrOptions;
} else {
throw new TypeError(
"Expected a test definition or name and function.",
"Expected a test definition or name and function",
);
}
stepDesc.ignore ??= false;

View file

@ -751,7 +751,7 @@ impl CodeActionCollection {
.as_ref()
.and_then(|d| serde_json::from_value::<Vec<DataQuickFix>>(d.clone()).ok())
{
let uri = url_to_uri(specifier);
let uri = url_to_uri(specifier)?;
for quick_fix in data_quick_fixes {
let mut changes = HashMap::new();
changes.insert(
@ -797,7 +797,7 @@ impl CodeActionCollection {
maybe_text_info: Option<&SourceTextInfo>,
maybe_parsed_source: Option<&deno_ast::ParsedSource>,
) -> Result<(), AnyError> {
let uri = url_to_uri(specifier);
let uri = url_to_uri(specifier)?;
let code = diagnostic
.code
.as_ref()

View file

@ -8,6 +8,7 @@ use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::serde_json::json;
use deno_core::unsync::spawn;
use lsp_types::Uri;
use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::ConfigurationItem;
@ -17,7 +18,6 @@ use super::config::WorkspaceSettings;
use super::config::SETTINGS_SECTION;
use super::lsp_custom;
use super::testing::lsp_custom as testing_lsp_custom;
use super::urls::LspClientUrl;
#[derive(Debug)]
pub enum TestingNotification {
@ -52,14 +52,11 @@ impl Client {
pub async fn publish_diagnostics(
&self,
uri: LspClientUrl,
uri: Uri,
diags: Vec<lsp::Diagnostic>,
version: Option<i32>,
) {
self
.0
.publish_diagnostics(uri.to_uri(), diags, version)
.await;
self.0.publish_diagnostics(uri, diags, version).await;
}
pub fn send_registry_state_notification(

View file

@ -5,6 +5,7 @@ use deno_config::deno_json::DenoJsonCache;
use deno_config::deno_json::FmtConfig;
use deno_config::deno_json::FmtOptionsConfig;
use deno_config::deno_json::LintConfig;
use deno_config::deno_json::NodeModulesDirMode;
use deno_config::deno_json::TestConfig;
use deno_config::deno_json::TsConfig;
use deno_config::fs::DenoConfigFs;
@ -54,7 +55,6 @@ use crate::args::CliLockfile;
use crate::args::ConfigFile;
use crate::args::LintFlags;
use crate::args::LintOptions;
use crate::args::DENO_FUTURE;
use crate::cache::FastInsecureHasher;
use crate::file_fetcher::FileFetcher;
use crate::lsp::logging::lsp_warn;
@ -850,7 +850,7 @@ impl Config {
let mut config = Self::default();
let mut folders = vec![];
for root_url in root_urls {
let root_uri = url_to_uri(&root_url);
let root_uri = url_to_uri(&root_url).unwrap();
let name = root_url.path_segments().and_then(|s| s.last());
let name = name.unwrap_or_default().to_string();
folders.push((
@ -1084,7 +1084,6 @@ impl Default for LspTsConfig {
"strict": true,
"target": "esnext",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": false,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
@ -1387,11 +1386,12 @@ impl ConfigData {
}
}
let byonm = std::env::var("DENO_UNSTABLE_BYONM").is_ok()
|| member_dir.workspace.has_unstable("byonm")
|| (*DENO_FUTURE
&& member_dir.workspace.package_jsons().next().is_some()
&& member_dir.workspace.node_modules_dir().is_none());
let node_modules_dir =
member_dir.workspace.node_modules_dir().unwrap_or_default();
let byonm = match node_modules_dir {
Some(mode) => mode == NodeModulesDirMode::Manual,
None => member_dir.workspace.root_pkg_json().is_some(),
};
if byonm {
lsp_log!(" Enabled 'bring your own node_modules'.");
}
@ -1694,9 +1694,14 @@ impl ConfigTree {
}
pub fn is_watched_file(&self, specifier: &ModuleSpecifier) -> bool {
if specifier.path().ends_with("/deno.json")
|| specifier.path().ends_with("/deno.jsonc")
|| specifier.path().ends_with("/package.json")
let path = specifier.path();
if path.ends_with("/deno.json")
|| path.ends_with("/deno.jsonc")
|| path.ends_with("/package.json")
|| path.ends_with("/node_modules/.package-lock.json")
|| path.ends_with("/node_modules/.yarn-integrity.json")
|| path.ends_with("/node_modules/.modules.yaml")
|| path.ends_with("/node_modules/.deno/.setup-cache.bin")
{
return true;
}
@ -1865,13 +1870,17 @@ fn resolve_node_modules_dir(
// `nodeModulesDir: true` setting in the deno.json file. This is to
// reduce the chance of modifying someone's node_modules directory
// without them having asked us to do so.
let explicitly_disabled = workspace.node_modules_dir() == Some(false);
let node_modules_mode = workspace.node_modules_dir().ok().flatten();
let explicitly_disabled = node_modules_mode == Some(NodeModulesDirMode::None);
if explicitly_disabled {
return None;
}
let enabled = byonm
|| workspace.node_modules_dir() == Some(true)
|| node_modules_mode
.map(|m| m.uses_node_modules_dir())
.unwrap_or(false)
|| workspace.vendor_dir_path().is_some();
if !enabled {
return None;
}

View file

@ -13,7 +13,6 @@ use super::performance::Performance;
use super::tsc;
use super::tsc::TsServer;
use super::urls::url_to_uri;
use super::urls::LspClientUrl;
use super::urls::LspUrlMap;
use crate::graph_util;
@ -164,15 +163,14 @@ impl DiagnosticsPublisher {
.state
.update(&record.specifier, version, &all_specifier_diagnostics);
let file_referrer = documents.get_file_referrer(&record.specifier);
let Ok(uri) =
url_map.specifier_to_uri(&record.specifier, file_referrer.as_deref())
else {
continue;
};
self
.client
.publish_diagnostics(
url_map
.normalize_specifier(&record.specifier, file_referrer.as_deref())
.unwrap_or(LspClientUrl::new(record.specifier)),
all_specifier_diagnostics,
version,
)
.publish_diagnostics(uri, all_specifier_diagnostics, version)
.await;
messages_sent += 1;
}
@ -195,15 +193,14 @@ impl DiagnosticsPublisher {
// clear out any diagnostics for this specifier
self.state.update(specifier, removed_value.version, &[]);
let file_referrer = documents.get_file_referrer(specifier);
let Ok(uri) =
url_map.specifier_to_uri(specifier, file_referrer.as_deref())
else {
continue;
};
self
.client
.publish_diagnostics(
url_map
.normalize_specifier(specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(specifier.clone())),
Vec::new(),
removed_value.version,
)
.publish_diagnostics(uri, Vec::new(), removed_value.version)
.await;
messages_sent += 1;
}
@ -1074,7 +1071,7 @@ impl DenoDiagnostic {
diagnostics: Some(vec![diagnostic.clone()]),
edit: Some(lsp::WorkspaceEdit {
changes: Some(HashMap::from([(
url_to_uri(specifier),
url_to_uri(specifier)?,
vec![lsp::TextEdit {
new_text: format!("\"{to}\""),
range: diagnostic.range,
@ -1091,7 +1088,7 @@ impl DenoDiagnostic {
diagnostics: Some(vec![diagnostic.clone()]),
edit: Some(lsp::WorkspaceEdit {
changes: Some(HashMap::from([(
url_to_uri(specifier),
url_to_uri(specifier)?,
vec![lsp::TextEdit {
new_text: " with { type: \"json\" }".to_string(),
range: lsp::Range {
@ -1142,7 +1139,7 @@ impl DenoDiagnostic {
diagnostics: Some(vec![diagnostic.clone()]),
edit: Some(lsp::WorkspaceEdit {
changes: Some(HashMap::from([(
url_to_uri(specifier),
url_to_uri(specifier)?,
vec![lsp::TextEdit {
new_text: format!(
"\"{}\"",
@ -1168,7 +1165,7 @@ impl DenoDiagnostic {
diagnostics: Some(vec![diagnostic.clone()]),
edit: Some(lsp::WorkspaceEdit {
changes: Some(HashMap::from([(
url_to_uri(specifier),
url_to_uri(specifier)?,
vec![lsp::TextEdit {
new_text: format!(
"\"{}\"",
@ -1194,7 +1191,7 @@ impl DenoDiagnostic {
diagnostics: Some(vec![diagnostic.clone()]),
edit: Some(lsp::WorkspaceEdit {
changes: Some(HashMap::from([(
url_to_uri(specifier),
url_to_uri(specifier)?,
vec![lsp::TextEdit {
new_text: format!("\"node:{}\"", data.specifier),
range: diagnostic.range,
@ -1642,7 +1639,7 @@ mod tests {
fn mock_config() -> Config {
let root_url = resolve_url("file:///").unwrap();
let root_uri = url_to_uri(&root_url);
let root_uri = url_to_uri(&root_url).unwrap();
Config {
settings: Arc::new(Settings {
unscoped: Arc::new(WorkspaceSettings {

View file

@ -60,6 +60,9 @@ pub enum LanguageId {
Json,
JsonC,
Markdown,
Html,
Css,
Yaml,
Unknown,
}
@ -73,6 +76,9 @@ impl LanguageId {
LanguageId::Json => Some("json"),
LanguageId::JsonC => Some("jsonc"),
LanguageId::Markdown => Some("md"),
LanguageId::Html => Some("html"),
LanguageId::Css => Some("css"),
LanguageId::Yaml => Some("yaml"),
LanguageId::Unknown => None,
}
}
@ -85,6 +91,9 @@ impl LanguageId {
LanguageId::Tsx => Some("text/tsx"),
LanguageId::Json | LanguageId::JsonC => Some("application/json"),
LanguageId::Markdown => Some("text/markdown"),
LanguageId::Html => Some("text/html"),
LanguageId::Css => Some("text/css"),
LanguageId::Yaml => Some("application/yaml"),
LanguageId::Unknown => None,
}
}
@ -109,6 +118,9 @@ impl FromStr for LanguageId {
"json" => Ok(Self::Json),
"jsonc" => Ok(Self::JsonC),
"markdown" => Ok(Self::Markdown),
"html" => Ok(Self::Html),
"css" => Ok(Self::Css),
"yaml" => Ok(Self::Yaml),
_ => Ok(Self::Unknown),
}
}
@ -1239,7 +1251,7 @@ impl Documents {
/// tsc when type checking.
pub fn resolve(
&self,
specifiers: &[String],
raw_specifiers: &[String],
referrer: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
@ -1250,16 +1262,16 @@ impl Documents {
.or(file_referrer);
let dependencies = document.as_ref().map(|d| d.dependencies());
let mut results = Vec::new();
for specifier in specifiers {
if specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(specifier) {
for raw_specifier in raw_specifiers {
if raw_specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(raw_specifier) {
let media_type = MediaType::from_specifier(&specifier);
results.push(Some((specifier, media_type)));
} else {
results.push(None);
}
} else if let Some(dep) =
dependencies.as_ref().and_then(|d| d.get(specifier))
dependencies.as_ref().and_then(|d| d.get(raw_specifier))
{
if let Some(specifier) = dep.maybe_type.maybe_specifier() {
results.push(self.resolve_dependency(
@ -1278,7 +1290,7 @@ impl Documents {
}
} else if let Ok(specifier) =
self.resolver.as_graph_resolver(file_referrer).resolve(
specifier,
raw_specifier,
&deno_graph::Range {
specifier: referrer.clone(),
start: deno_graph::Position::zeroed(),
@ -1410,11 +1422,9 @@ impl Documents {
if let Some(lockfile) = config_data.lockfile.as_ref() {
let reqs = npm_reqs_by_scope.entry(Some(scope.clone())).or_default();
let lockfile = lockfile.lock();
for key in lockfile.content.packages.specifiers.keys() {
if let Some(key) = key.strip_prefix("npm:") {
if let Ok(req) = PackageReq::from_str(key) {
reqs.insert(req);
}
for dep_req in lockfile.content.packages.specifiers.keys() {
if dep_req.kind == deno_semver::package::PackageKind::Npm {
reqs.insert(dep_req.req.clone());
}
}
}
@ -1514,12 +1524,16 @@ impl<'a> deno_graph::source::Loader for OpenDocumentsGraphLoader<'a> {
fn cache_module_info(
&self,
specifier: &deno_ast::ModuleSpecifier,
media_type: MediaType,
source: &Arc<[u8]>,
module_info: &deno_graph::ModuleInfo,
) {
self
.inner_loader
.cache_module_info(specifier, source, module_info)
self.inner_loader.cache_module_info(
specifier,
media_type,
source,
module_info,
)
}
}

View file

@ -92,20 +92,23 @@ impl JsrCacheResolver {
}
}
if let Some(lockfile) = config_data.and_then(|d| d.lockfile.as_ref()) {
for (req_url, nv_url) in &lockfile.lock().content.packages.specifiers {
let Some(req) = req_url.strip_prefix("jsr:") else {
for (dep_req, version) in &lockfile.lock().content.packages.specifiers {
let req = match dep_req.kind {
deno_semver::package::PackageKind::Jsr => &dep_req.req,
deno_semver::package::PackageKind::Npm => {
continue;
}
};
let Ok(version) = Version::parse_standard(version) else {
continue;
};
let Some(nv) = nv_url.strip_prefix("jsr:") else {
continue;
};
let Ok(req) = PackageReq::from_str(req) else {
continue;
};
let Ok(nv) = PackageNv::from_str(nv) else {
continue;
};
nv_by_req.insert(req, Some(nv));
nv_by_req.insert(
req.clone(),
Some(PackageNv {
name: req.name.clone(),
version,
}),
);
}
}
Self {

View file

@ -32,6 +32,7 @@ use std::collections::VecDeque;
use std::env;
use std::fmt::Write as _;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::mpsc::unbounded_channel;
use tokio::sync::mpsc::UnboundedReceiver;
@ -723,7 +724,9 @@ impl Inner {
.into_iter()
.map(|folder| {
(
self.url_map.normalize_url(&folder.uri, LspUrlKind::Folder),
self
.url_map
.uri_to_specifier(&folder.uri, LspUrlKind::Folder),
folder,
)
})
@ -735,7 +738,7 @@ impl Inner {
if let Some(root_uri) = params.root_uri {
if !workspace_folders.iter().any(|(_, f)| f.uri == root_uri) {
let root_url =
self.url_map.normalize_url(&root_uri, LspUrlKind::Folder);
self.url_map.uri_to_specifier(&root_uri, LspUrlKind::Folder);
let name = root_url.path_segments().and_then(|s| s.last());
let name = name.unwrap_or_default().to_string();
workspace_folders.insert(
@ -963,9 +966,8 @@ impl Inner {
.await;
for config_file in self.config.tree.config_files() {
(|| {
let compiler_options = config_file.to_compiler_options().ok()?.0;
let compiler_options_obj = compiler_options.as_object()?;
let jsx_import_source = compiler_options_obj.get("jsxImportSource")?;
let compiler_options = config_file.to_compiler_options().ok()?.options;
let jsx_import_source = compiler_options.get("jsxImportSource")?;
let jsx_import_source = jsx_import_source.as_str()?;
let referrer = config_file.specifier.clone();
let specifier = Url::parse(&format!(
@ -1043,7 +1045,7 @@ impl Inner {
.filter(|s| self.documents.is_valid_file_referrer(s));
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
let document = self.documents.open(
specifier.clone(),
params.text_document.version,
@ -1065,7 +1067,7 @@ impl Inner {
let mark = self.performance.mark_with_args("lsp.did_change", &params);
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
match self.documents.change(
&specifier,
params.text_document.version,
@ -1102,7 +1104,7 @@ impl Inner {
let _mark = self.performance.measure_scope("lsp.did_save");
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
self.documents.save(&specifier);
if !self
.config
@ -1148,7 +1150,7 @@ impl Inner {
}
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
self.diagnostics_state.clear(&specifier);
if self.is_diagnosable(&specifier) {
self.refresh_npm_specifiers().await;
@ -1202,7 +1204,7 @@ impl Inner {
let changes = params
.changes
.into_iter()
.map(|e| (self.url_map.normalize_url(&e.uri, LspUrlKind::File), e))
.map(|e| (self.url_map.uri_to_specifier(&e.uri, LspUrlKind::File), e))
.collect::<Vec<_>>();
if changes
.iter()
@ -1221,7 +1223,7 @@ impl Inner {
_ => return None,
};
Some(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: url_to_uri(t.0),
scope_uri: url_to_uri(t.0).ok()?,
file_uri: e.uri.clone(),
typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
e.typ,
@ -1256,7 +1258,7 @@ impl Inner {
_ => return None,
};
Some(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: url_to_uri(t.0),
scope_uri: url_to_uri(t.0).ok()?,
file_uri: e.uri.clone(),
typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type(
e.typ,
@ -1282,7 +1284,7 @@ impl Inner {
) -> LspResult<Option<DocumentSymbolResponse>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -1326,7 +1328,7 @@ impl Inner {
.filter(|s| self.documents.is_valid_file_referrer(s));
let mut specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
// skip formatting any files ignored by the config file
if !self
.config
@ -1441,7 +1443,7 @@ impl Inner {
}
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -1574,7 +1576,7 @@ impl Inner {
) -> LspResult<Option<CodeActionResponse>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -1918,7 +1920,7 @@ impl Inner {
) -> LspResult<Option<Vec<CodeLens>>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -2004,7 +2006,7 @@ impl Inner {
&self,
params: DocumentHighlightParams,
) -> LspResult<Option<Vec<DocumentHighlight>>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2048,7 +2050,7 @@ impl Inner {
&self,
params: ReferenceParams,
) -> LspResult<Option<Vec<Location>>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position.text_document.uri,
LspUrlKind::File,
);
@ -2104,7 +2106,7 @@ impl Inner {
&self,
params: GotoDefinitionParams,
) -> LspResult<Option<GotoDefinitionResponse>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2143,7 +2145,7 @@ impl Inner {
&self,
params: GotoTypeDefinitionParams,
) -> LspResult<Option<GotoTypeDefinitionResponse>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2189,7 +2191,7 @@ impl Inner {
&self,
params: CompletionParams,
) -> LspResult<Option<CompletionResponse>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position.text_document.uri,
LspUrlKind::File,
);
@ -2378,7 +2380,7 @@ impl Inner {
&self,
params: GotoImplementationParams,
) -> LspResult<Option<GotoImplementationResponse>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2429,7 +2431,7 @@ impl Inner {
) -> LspResult<Option<Vec<FoldingRange>>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -2476,7 +2478,7 @@ impl Inner {
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
let specifier = self
.url_map
.normalize_url(&params.item.uri, LspUrlKind::File);
.uri_to_specifier(&params.item.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -2525,7 +2527,7 @@ impl Inner {
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
let specifier = self
.url_map
.normalize_url(&params.item.uri, LspUrlKind::File);
.uri_to_specifier(&params.item.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -2570,7 +2572,7 @@ impl Inner {
&self,
params: CallHierarchyPrepareParams,
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2634,7 +2636,7 @@ impl Inner {
&self,
params: RenameParams,
) -> LspResult<Option<WorkspaceEdit>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position.text_document.uri,
LspUrlKind::File,
);
@ -2683,7 +2685,7 @@ impl Inner {
) -> LspResult<Option<Vec<SelectionRange>>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
{
@ -2721,7 +2723,7 @@ impl Inner {
) -> LspResult<Option<SemanticTokensResult>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) {
return Ok(None);
}
@ -2774,7 +2776,7 @@ impl Inner {
) -> LspResult<Option<SemanticTokensRangeResult>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) {
return Ok(None);
}
@ -2823,7 +2825,7 @@ impl Inner {
&self,
params: SignatureHelpParams,
) -> LspResult<Option<SignatureHelp>> {
let specifier = self.url_map.normalize_url(
let specifier = self.url_map.uri_to_specifier(
&params.text_document_position_params.text_document.uri,
LspUrlKind::File,
);
@ -2877,8 +2879,8 @@ impl Inner {
) -> LspResult<Option<WorkspaceEdit>> {
let mut changes = vec![];
for rename in params.files {
let old_specifier = self.url_map.normalize_url(
&url_to_uri(&resolve_url(&rename.old_uri).unwrap()),
let old_specifier = self.url_map.uri_to_specifier(
&Uri::from_str(&rename.old_uri).unwrap(),
LspUrlKind::File,
);
let options = self
@ -2903,8 +2905,8 @@ impl Inner {
.get_edits_for_file_rename(
self.snapshot(),
old_specifier,
self.url_map.normalize_url(
&url_to_uri(&resolve_url(&rename.new_uri).unwrap()),
self.url_map.uri_to_specifier(
&Uri::from_str(&rename.new_uri).unwrap(),
LspUrlKind::File,
),
format_code_settings,
@ -3503,22 +3505,28 @@ impl Inner {
let mut config_events = vec![];
for (scope_url, config_data) in self.config.tree.data_by_scope().iter() {
let scope_uri = url_to_uri(scope_url);
let Ok(scope_uri) = url_to_uri(scope_url) else {
continue;
};
if let Some(config_file) = config_data.maybe_deno_json() {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: scope_uri.clone(),
file_uri: url_to_uri(&config_file.specifier),
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::DenoJson,
});
if let Ok(file_uri) = url_to_uri(&config_file.specifier) {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
scope_uri: scope_uri.clone(),
file_uri,
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::DenoJson,
});
}
}
if let Some(package_json) = config_data.maybe_pkg_json() {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
scope_uri,
file_uri: url_to_uri(&package_json.specifier()),
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::PackageJson,
});
if let Ok(file_uri) = url_to_uri(&package_json.specifier()) {
config_events.push(lsp_custom::DenoConfigurationChangeEvent {
scope_uri,
file_uri,
typ: lsp_custom::DenoConfigurationChangeType::Added,
configuration_type: lsp_custom::DenoConfigurationType::PackageJson,
});
}
}
}
if !config_events.is_empty() {
@ -3602,11 +3610,6 @@ impl Inner {
.as_ref()
.map(|url| url.to_string())
}),
node_modules_dir: Some(
config_data
.and_then(|d| d.node_modules_dir.as_ref())
.is_some(),
),
// bit of a hack to force the lsp to cache the @types/node package
type_check_mode: crate::args::TypeCheckMode::Local,
..Default::default()
@ -3648,7 +3651,9 @@ impl Inner {
.into_iter()
.map(|folder| {
(
self.url_map.normalize_url(&folder.uri, LspUrlKind::Folder),
self
.url_map
.uri_to_specifier(&folder.uri, LspUrlKind::Folder),
folder,
)
})
@ -3724,7 +3729,8 @@ impl Inner {
result.push(TaskDefinition {
name: name.clone(),
command: command.to_string(),
source_uri: url_to_uri(&config_file.specifier),
source_uri: url_to_uri(&config_file.specifier)
.map_err(|_| LspError::internal_error())?,
});
}
};
@ -3735,7 +3741,8 @@ impl Inner {
result.push(TaskDefinition {
name: name.clone(),
command: command.clone(),
source_uri: url_to_uri(&package_json.specifier()),
source_uri: url_to_uri(&package_json.specifier())
.map_err(|_| LspError::internal_error())?,
});
}
}
@ -3750,7 +3757,7 @@ impl Inner {
) -> LspResult<Option<Vec<InlayHint>>> {
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !self.config.enabled_inlay_hints_for_specifier(&specifier)
@ -3813,7 +3820,7 @@ impl Inner {
.mark_with_args("lsp.virtual_text_document", &params);
let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
let contents = if specifier.scheme() == "deno"
&& specifier.path() == "/status.md"
{

View file

@ -76,7 +76,7 @@ impl ReplLanguageServer {
.initialize(InitializeParams {
process_id: None,
root_path: None,
root_uri: Some(url_to_uri(&cwd_uri)),
root_uri: Some(url_to_uri(&cwd_uri).unwrap()),
initialization_options: Some(
serde_json::to_value(get_repl_workspace_settings()).unwrap(),
),

View file

@ -3,7 +3,7 @@
use crate::args::create_default_npmrc;
use crate::args::CacheSetting;
use crate::args::CliLockfile;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::graph_util::CliJsrUrlProvider;
use crate::http_util::HttpClientProvider;
use crate::lsp::config::Config;
@ -474,9 +474,7 @@ async fn create_npm_resolver(
maybe_node_modules_path: config_data
.and_then(|d| d.node_modules_dir.clone()),
// only used for top level install, so we can ignore this
package_json_deps_provider: Arc::new(
PackageJsonInstallDepsProvider::empty(),
),
npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()),
npmrc: config_data
.and_then(|d| d.npmrc.clone())
.unwrap_or_else(create_default_npmrc),

View file

@ -10,6 +10,7 @@ use crate::tools::test::TestDescription;
use crate::tools::test::TestStepDescription;
use crate::util::checksum;
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use lsp::Range;
use std::collections::HashMap;
@ -144,21 +145,23 @@ impl TestModule {
pub fn as_replace_notification(
&self,
maybe_root_uri: Option<&ModuleSpecifier>,
) -> TestingNotification {
) -> Result<TestingNotification, AnyError> {
let label = self.label(maybe_root_uri);
TestingNotification::Module(lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&self.specifier),
Ok(TestingNotification::Module(
lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&self.specifier)?,
},
kind: lsp_custom::TestModuleNotificationKind::Replace,
label,
tests: self
.defs
.iter()
.filter(|(_, def)| def.parent_id.is_none())
.map(|(id, _)| self.get_test_data(id))
.collect(),
},
kind: lsp_custom::TestModuleNotificationKind::Replace,
label,
tests: self
.defs
.iter()
.filter(|(_, def)| def.parent_id.is_none())
.map(|(id, _)| self.get_test_data(id))
.collect(),
})
))
}
pub fn label(&self, maybe_root_uri: Option<&ModuleSpecifier>) -> String {

View file

@ -186,7 +186,7 @@ impl TestRun {
self
.queue
.iter()
.map(|s| {
.filter_map(|s| {
let ids = if let Some((test_module, _)) = tests.get(s) {
if let Some(filter) = self.filters.get(s) {
filter.as_ids(test_module)
@ -196,10 +196,12 @@ impl TestRun {
} else {
Vec::new()
};
lsp_custom::EnqueuedTestModule {
text_document: lsp::TextDocumentIdentifier { uri: url_to_uri(s) },
Some(lsp_custom::EnqueuedTestModule {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(s).ok()?,
},
ids,
}
})
})
.collect()
}
@ -591,6 +593,9 @@ impl LspTestReporter {
let (test_module, _) = files
.entry(specifier.clone())
.or_insert_with(|| (TestModule::new(specifier), "1".to_string()));
let Ok(uri) = url_to_uri(&test_module.specifier) else {
return;
};
let (static_id, is_new) = test_module.register_dynamic(desc);
self.tests.insert(
desc.id,
@ -601,9 +606,7 @@ impl LspTestReporter {
.client
.send_test_notification(TestingNotification::Module(
lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&test_module.specifier),
},
text_document: lsp::TextDocumentIdentifier { uri },
kind: lsp_custom::TestModuleNotificationKind::Insert,
label: test_module.label(self.maybe_root_uri.as_ref()),
tests: vec![test_module.get_test_data(&static_id)],
@ -701,6 +704,9 @@ impl LspTestReporter {
let (test_module, _) = files
.entry(specifier.clone())
.or_insert_with(|| (TestModule::new(specifier), "1".to_string()));
let Ok(uri) = url_to_uri(&test_module.specifier) else {
return;
};
let (static_id, is_new) = test_module.register_step_dynamic(
desc,
self.tests.get(&desc.parent_id).unwrap().static_id(),
@ -714,9 +720,7 @@ impl LspTestReporter {
.client
.send_test_notification(TestingNotification::Module(
lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&test_module.specifier),
},
text_document: lsp::TextDocumentIdentifier { uri },
kind: lsp_custom::TestModuleNotificationKind::Insert,
label: test_module.label(self.maybe_root_uri.as_ref()),
tests: vec![test_module.get_test_data(&static_id)],
@ -800,14 +804,14 @@ mod tests {
include: Some(vec![
lsp_custom::TestIdentifier {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&specifier),
uri: url_to_uri(&specifier).unwrap(),
},
id: None,
step_id: None,
},
lsp_custom::TestIdentifier {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&non_test_specifier),
uri: url_to_uri(&non_test_specifier).unwrap(),
},
id: None,
step_id: None,
@ -815,7 +819,7 @@ mod tests {
]),
exclude: vec![lsp_custom::TestIdentifier {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&specifier),
uri: url_to_uri(&specifier).unwrap(),
},
id: Some(
"69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f"

View file

@ -27,14 +27,16 @@ use tower_lsp::jsonrpc::Error as LspError;
use tower_lsp::jsonrpc::Result as LspResult;
use tower_lsp::lsp_types as lsp;
fn as_delete_notification(url: ModuleSpecifier) -> TestingNotification {
TestingNotification::DeleteModule(
fn as_delete_notification(
url: &ModuleSpecifier,
) -> Result<TestingNotification, AnyError> {
Ok(TestingNotification::DeleteModule(
lsp_custom::TestModuleDeleteNotificationParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_to_uri(&url),
uri: url_to_uri(url)?,
},
},
)
))
}
pub type TestServerTests =
@ -126,20 +128,24 @@ impl TestServer {
.map(|tm| tm.as_ref().clone())
.unwrap_or_else(|| TestModule::new(specifier.clone()));
if !test_module.is_empty() {
client.send_test_notification(
test_module.as_replace_notification(mru.as_ref()),
);
if let Ok(params) =
test_module.as_replace_notification(mru.as_ref())
{
client.send_test_notification(params);
}
} else if !was_empty {
client.send_test_notification(as_delete_notification(
specifier.clone(),
));
if let Ok(params) = as_delete_notification(specifier) {
client.send_test_notification(params);
}
}
tests
.insert(specifier.clone(), (test_module, script_version));
}
}
for key in keys {
client.send_test_notification(as_delete_notification(key));
for key in &keys {
if let Ok(params) = as_delete_notification(key) {
client.send_test_notification(params);
}
}
performance.measure(mark);
}

View file

@ -19,9 +19,10 @@ use super::refactor::EXTRACT_TYPE;
use super::semantic_tokens;
use super::semantic_tokens::SemanticTokensBuilder;
use super::text::LineIndex;
use super::urls::uri_to_url;
use super::urls::url_to_uri;
use super::urls::LspClientUrl;
use super::urls::INVALID_SPECIFIER;
use super::urls::INVALID_URI;
use crate::args::jsr_url;
use crate::args::FmtOptionsConfig;
@ -2047,7 +2048,7 @@ impl DocumentSpan {
let file_referrer = target_asset_or_doc.file_referrer();
let target_uri = language_server
.url_map
.normalize_specifier(&target_specifier, file_referrer)
.specifier_to_uri(&target_specifier, file_referrer)
.ok()?;
let (target_range, target_selection_range) =
if let Some(context_span) = &self.context_span {
@ -2072,7 +2073,7 @@ impl DocumentSpan {
};
let link = lsp::LocationLink {
origin_selection_range,
target_uri: target_uri.to_uri(),
target_uri,
target_range,
target_selection_range,
};
@ -2092,11 +2093,11 @@ impl DocumentSpan {
let line_index = asset_or_doc.line_index();
let range = self.text_span.to_range(line_index);
let file_referrer = asset_or_doc.file_referrer();
let mut target = language_server
let target_uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer)
.ok()?
.into_url();
.specifier_to_uri(&specifier, file_referrer)
.ok()?;
let mut target = uri_to_url(&target_uri);
target.set_fragment(Some(&format!(
"L{},{}",
range.start.line + 1,
@ -2155,13 +2156,10 @@ impl NavigateToItem {
let file_referrer = asset_or_doc.file_referrer();
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer)
.specifier_to_uri(&specifier, file_referrer)
.ok()?;
let range = self.text_span.to_range(line_index);
let location = lsp::Location {
uri: uri.to_uri(),
range,
};
let location = lsp::Location { uri, range };
let mut tags: Option<Vec<lsp::SymbolTag>> = None;
let kind_modifiers = parse_kind_modifier(&self.kind_modifiers);
@ -2414,12 +2412,10 @@ impl ImplementationLocation {
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| {
LspClientUrl::new(ModuleSpecifier::parse("deno://invalid").unwrap())
});
.specifier_to_uri(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| INVALID_URI.clone());
lsp::Location {
uri: uri.to_uri(),
uri,
range: self.document_span.text_span.to_range(line_index),
}
}
@ -2475,7 +2471,7 @@ impl RenameLocations {
language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())?;
.specifier_to_uri(&specifier, file_referrer.as_deref())?;
let asset_or_doc = language_server.get_asset_or_document(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`.
@ -2484,7 +2480,7 @@ impl RenameLocations {
uri.clone(),
lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: uri.to_uri(),
uri: uri.clone(),
version: asset_or_doc.document_lsp_version(),
},
edits:
@ -2686,7 +2682,7 @@ impl FileTextChanges {
.collect();
Ok(lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: url_to_uri(&specifier),
uri: url_to_uri(&specifier)?,
version: asset_or_doc.document_lsp_version(),
},
edits,
@ -2713,7 +2709,7 @@ impl FileTextChanges {
if self.is_new_file.unwrap_or(false) {
ops.push(lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(
lsp::CreateFile {
uri: url_to_uri(&specifier),
uri: url_to_uri(&specifier)?,
options: Some(lsp::CreateFileOptions {
ignore_if_exists: Some(true),
overwrite: None,
@ -2730,7 +2726,7 @@ impl FileTextChanges {
.collect();
ops.push(lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
text_document: lsp::OptionalVersionedTextDocumentIdentifier {
uri: url_to_uri(&specifier),
uri: url_to_uri(&specifier)?,
version: maybe_asset_or_document.and_then(|d| d.document_lsp_version()),
},
edits,
@ -3128,10 +3124,10 @@ impl ReferenceEntry {
let file_referrer = language_server.documents.get_file_referrer(&specifier);
let uri = language_server
.url_map
.normalize_specifier(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
.specifier_to_uri(&specifier, file_referrer.as_deref())
.unwrap_or_else(|_| INVALID_URI.clone());
lsp::Location {
uri: uri.to_uri(),
uri,
range: self.document_span.text_span.to_range(line_index),
}
}
@ -3189,12 +3185,13 @@ impl CallHierarchyItem {
.get_file_referrer(&target_specifier);
let uri = language_server
.url_map
.normalize_specifier(&target_specifier, file_referrer.as_deref())
.unwrap_or_else(|_| LspClientUrl::new(INVALID_SPECIFIER.clone()));
.specifier_to_uri(&target_specifier, file_referrer.as_deref())
.unwrap_or_else(|_| INVALID_URI.clone());
let use_file_name = self.is_source_file_item();
let maybe_file_path = if uri.as_url().scheme() == "file" {
specifier_to_file_path(uri.as_url()).ok()
let maybe_file_path = if uri.scheme().is_some_and(|s| s.as_str() == "file")
{
specifier_to_file_path(&uri_to_url(&uri)).ok()
} else {
None
};
@ -3238,7 +3235,7 @@ impl CallHierarchyItem {
lsp::CallHierarchyItem {
name,
tags,
uri: uri.to_uri(),
uri,
detail: Some(detail),
kind: self.kind.clone().into(),
range: self.span.to_range(line_index.clone()),

View file

@ -13,12 +13,18 @@ use std::str::FromStr;
use std::sync::Arc;
use super::cache::LspCache;
use super::logging::lsp_warn;
/// Used in situations where a default URL needs to be used where otherwise a
/// panic is undesired.
pub static INVALID_SPECIFIER: Lazy<ModuleSpecifier> =
Lazy::new(|| ModuleSpecifier::parse("deno://invalid").unwrap());
/// Used in situations where a default URL needs to be used where otherwise a
/// panic is undesired.
pub static INVALID_URI: Lazy<Uri> =
Lazy::new(|| Uri::from_str("deno://invalid").unwrap());
/// Matches the `encodeURIComponent()` encoding from JavaScript, which matches
/// the component percent encoding set.
///
@ -58,7 +64,7 @@ fn hash_data_specifier(specifier: &ModuleSpecifier) -> String {
crate::util::checksum::gen(&[file_name_str.as_bytes()])
}
fn to_deno_url(specifier: &Url) -> String {
fn to_deno_uri(specifier: &Url) -> String {
let mut string = String::with_capacity(specifier.as_str().len() + 6);
string.push_str("deno:/");
string.push_str(specifier.scheme());
@ -95,64 +101,31 @@ fn from_deno_url(url: &Url) -> Option<Url> {
Url::parse(&string).ok()
}
/// This exists to make it a little bit harder to accidentally use a `Url`
/// in the wrong place where a client url should be used.
#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
pub struct LspClientUrl(Url);
impl LspClientUrl {
pub fn new(url: Url) -> Self {
Self(url)
}
pub fn as_url(&self) -> &Url {
&self.0
}
pub fn into_url(self) -> Url {
self.0
}
pub fn to_uri(&self) -> Uri {
url_to_uri(&self.0)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl std::fmt::Display for LspClientUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Default)]
struct LspUrlMapInner {
specifier_to_url: HashMap<ModuleSpecifier, LspClientUrl>,
url_to_specifier: HashMap<Url, ModuleSpecifier>,
specifier_to_uri: HashMap<ModuleSpecifier, Uri>,
uri_to_specifier: HashMap<Uri, ModuleSpecifier>,
}
impl LspUrlMapInner {
fn put(&mut self, specifier: ModuleSpecifier, url: LspClientUrl) {
self
.url_to_specifier
.insert(url.as_url().clone(), specifier.clone());
self.specifier_to_url.insert(specifier, url);
fn put(&mut self, specifier: ModuleSpecifier, uri: Uri) {
self.uri_to_specifier.insert(uri.clone(), specifier.clone());
self.specifier_to_uri.insert(specifier, uri);
}
fn get_url(&self, specifier: &ModuleSpecifier) -> Option<&LspClientUrl> {
self.specifier_to_url.get(specifier)
fn get_uri(&self, specifier: &ModuleSpecifier) -> Option<&Uri> {
self.specifier_to_uri.get(specifier)
}
fn get_specifier(&self, url: &Url) -> Option<&ModuleSpecifier> {
self.url_to_specifier.get(url)
fn get_specifier(&self, uri: &Uri) -> Option<&ModuleSpecifier> {
self.uri_to_specifier.get(uri)
}
}
pub fn url_to_uri(url: &Url) -> Uri {
Uri::from_str(url.as_str()).unwrap()
pub fn url_to_uri(url: &Url) -> Result<Uri, AnyError> {
Ok(Uri::from_str(url.as_str()).inspect_err(|err| {
lsp_warn!("Could not convert URL \"{url}\" to URI: {err}")
})?)
}
pub fn uri_to_url(uri: &Uri) -> Url {
@ -181,24 +154,24 @@ impl LspUrlMap {
/// Normalize a specifier that is used internally within Deno (or tsc) to a
/// URL that can be handled as a "virtual" document by an LSP client.
pub fn normalize_specifier(
pub fn specifier_to_uri(
&self,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Result<LspClientUrl, AnyError> {
) -> Result<Uri, AnyError> {
if let Some(file_url) =
self.cache.vendored_specifier(specifier, file_referrer)
{
return Ok(LspClientUrl(file_url));
return url_to_uri(&file_url);
}
let mut inner = self.inner.lock();
if let Some(url) = inner.get_url(specifier).cloned() {
Ok(url)
if let Some(uri) = inner.get_uri(specifier).cloned() {
Ok(uri)
} else {
let url = if specifier.scheme() == "file" {
LspClientUrl(specifier.clone())
let uri = if specifier.scheme() == "file" {
url_to_uri(specifier)?
} else {
let specifier_str = if specifier.scheme() == "asset" {
let uri_str = if specifier.scheme() == "asset" {
format!("deno:/asset{}", specifier.path())
} else if specifier.scheme() == "data" {
let data_url = deno_graph::source::RawDataUrl::parse(specifier)?;
@ -214,13 +187,13 @@ impl LspUrlMap {
extension
)
} else {
to_deno_url(specifier)
to_deno_uri(specifier)
};
let url = LspClientUrl(Url::parse(&specifier_str)?);
inner.put(specifier.clone(), url.clone());
url
let uri = Uri::from_str(&uri_str)?;
inner.put(specifier.clone(), uri.clone());
uri
};
Ok(url)
Ok(uri)
}
}
@ -232,13 +205,17 @@ impl LspUrlMap {
/// Note: Sometimes the url provided by the client may not have a trailing slash,
/// so we need to force it to in the mapping and nee to explicitly state whether
/// this is a file or directory url.
pub fn normalize_url(&self, uri: &Uri, kind: LspUrlKind) -> ModuleSpecifier {
pub fn uri_to_specifier(
&self,
uri: &Uri,
kind: LspUrlKind,
) -> ModuleSpecifier {
let url = uri_to_url(uri);
if let Some(remote_url) = self.cache.unvendored_specifier(&url) {
return remote_url;
}
let mut inner = self.inner.lock();
if let Some(specifier) = inner.get_specifier(&url).cloned() {
if let Some(specifier) = inner.get_specifier(uri).cloned() {
return specifier;
}
let mut specifier = None;
@ -255,7 +232,7 @@ impl LspUrlMap {
specifier = Some(s);
}
let specifier = specifier.unwrap_or_else(|| url.clone());
inner.put(specifier.clone(), LspClientUrl(url));
inner.put(specifier.clone(), uri.clone());
specifier
}
}
@ -303,15 +280,14 @@ mod tests {
fn test_lsp_url_map() {
let map = LspUrlMap::default();
let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture, None)
let actual_uri = map
.specifier_to_uri(&fixture, None)
.expect("could not handle specifier");
let expected_url =
Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
let actual_specifier =
map.normalize_url(&actual_url.to_uri(), LspUrlKind::File);
assert_eq!(
actual_uri.as_str(),
"deno:/https/deno.land/x/pkg%401.0.0/mod.ts"
);
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
assert_eq!(actual_specifier, fixture);
}
@ -320,17 +296,13 @@ mod tests {
let map = LspUrlMap::default();
let fixture =
Uri::from_str("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap();
let actual_specifier = map.normalize_url(&fixture, LspUrlKind::File);
let actual_specifier = map.uri_to_specifier(&fixture, LspUrlKind::File);
let expected_specifier =
Url::parse("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
assert_eq!(&actual_specifier, &expected_specifier);
let actual_url = map
.normalize_specifier(&actual_specifier, None)
.unwrap()
.to_uri()
.clone();
assert_eq!(actual_url, fixture);
let actual_uri = map.specifier_to_uri(&actual_specifier, None).unwrap();
assert_eq!(actual_uri, fixture);
}
#[test]
@ -338,14 +310,11 @@ mod tests {
// Test fix for #9741 - not properly encoding certain URLs
let map = LspUrlMap::default();
let fixture = resolve_url("https://cdn.skypack.dev/-/postcss@v8.2.9-E4SktPp9c0AtxrJHp8iV/dist=es2020,mode=types/lib/postcss.d.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture, None)
let actual_uri = map
.specifier_to_uri(&fixture, None)
.expect("could not handle specifier");
let expected_url = Url::parse("deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
let actual_specifier =
map.normalize_url(&actual_url.to_uri(), LspUrlKind::File);
assert_eq!(actual_uri.as_str(), "deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts");
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
assert_eq!(actual_specifier, fixture);
}
@ -353,14 +322,13 @@ mod tests {
fn test_lsp_url_map_data() {
let map = LspUrlMap::default();
let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
let actual_url = map
.normalize_specifier(&fixture, None)
let actual_uri = map
.specifier_to_uri(&fixture, None)
.expect("could not handle specifier");
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
assert_eq!(&uri_to_url(&actual_uri), &expected_url);
let actual_specifier =
map.normalize_url(&actual_url.to_uri(), LspUrlKind::File);
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
assert_eq!(actual_specifier, fixture);
}
@ -368,15 +336,11 @@ mod tests {
fn test_lsp_url_map_host_with_port() {
let map = LspUrlMap::default();
let fixture = resolve_url("http://localhost:8000/mod.ts").unwrap();
let actual_url = map
.normalize_specifier(&fixture, None)
let actual_uri = map
.specifier_to_uri(&fixture, None)
.expect("could not handle specifier");
let expected_url =
Url::parse("deno:/http/localhost%3A8000/mod.ts").unwrap();
assert_eq!(actual_url.as_url(), &expected_url);
let actual_specifier =
map.normalize_url(&actual_url.to_uri(), LspUrlKind::File);
assert_eq!(actual_uri.as_str(), "deno:/http/localhost%3A8000/mod.ts");
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
assert_eq!(actual_specifier, fixture);
}
@ -388,7 +352,7 @@ mod tests {
"file:///c%3A/Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
)
.unwrap();
let actual = map.normalize_url(&fixture, LspUrlKind::File);
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
let expected =
Url::parse("file:///C:/Users/deno/Desktop/file with spaces in name.txt")
.unwrap();
@ -403,7 +367,7 @@ mod tests {
"file:///Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
)
.unwrap();
let actual = map.normalize_url(&fixture, LspUrlKind::File);
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
let expected =
Url::parse("file:///Users/deno/Desktop/file with spaces in name.txt")
.unwrap();
@ -414,7 +378,7 @@ mod tests {
fn test_normalize_deno_status() {
let map = LspUrlMap::default();
let fixture = Uri::from_str("deno:/status.md").unwrap();
let actual = map.normalize_url(&fixture, LspUrlKind::File);
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
assert_eq!(actual.as_str(), fixture.as_str());
}

View file

@ -32,8 +32,6 @@ mod worker;
use crate::args::flags_from_vec;
use crate::args::DenoSubcommand;
use crate::args::Flags;
use crate::args::DENO_FUTURE;
use crate::graph_container::ModuleGraphContainer;
use crate::util::display;
use crate::util::v8::get_v8_flags_from_env;
use crate::util::v8::init_v8_flags;
@ -48,7 +46,8 @@ use deno_core::error::JsError;
use deno_core::futures::FutureExt;
use deno_core::unsync::JoinHandle;
use deno_npm::resolution::SnapshotFromLockfileError;
use deno_runtime::fmt_errors::format_js_error;
use deno_runtime::fmt_errors::format_js_error_with_suggestions;
use deno_runtime::fmt_errors::FixSuggestion;
use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics;
use deno_terminal::colors;
use factory::CliFactory;
@ -111,9 +110,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
tools::bench::run_benchmarks(flags, bench_flags).await
}
}),
DenoSubcommand::Bundle(bundle_flags) => spawn_subcommand(async {
tools::bundle::bundle(flags, bundle_flags).await
}),
DenoSubcommand::Bundle => exit_with_message("⚠️ `deno bundle` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1),
DenoSubcommand::Doc(doc_flags) => {
spawn_subcommand(async { tools::doc::doc(flags, doc_flags).await })
}
@ -121,14 +118,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
tools::run::eval_command(flags, eval_flags).await
}),
DenoSubcommand::Cache(cache_flags) => spawn_subcommand(async move {
let factory = CliFactory::from_flags(flags);
let emitter = factory.emitter()?;
let main_graph_container =
factory.main_module_graph_container().await?;
main_graph_container
.load_and_type_check_files(&cache_flags.files)
.await?;
emitter.cache_module_emits(&main_graph_container.graph()).await
tools::installer::install_from_entrypoints(flags, &cache_flags.files).await
}),
DenoSubcommand::Check(check_flags) => spawn_subcommand(async move {
let factory = CliFactory::from_flags(flags);
@ -172,7 +162,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
tools::jupyter::kernel(flags, jupyter_flags).await
}),
DenoSubcommand::Uninstall(uninstall_flags) => spawn_subcommand(async {
tools::installer::uninstall(uninstall_flags)
tools::installer::uninstall(flags, uninstall_flags).await
}),
DenoSubcommand::Lsp => spawn_subcommand(async { lsp::start().await }),
DenoSubcommand::Lint(lint_flags) => spawn_subcommand(async {
@ -286,9 +276,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
"This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.",
1,
),
DenoSubcommand::Vendor(vendor_flags) => spawn_subcommand(async {
tools::vendor::vendor(flags, vendor_flags).await
}),
DenoSubcommand::Vendor => exit_with_message("⚠️ `deno vendor` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations", 1),
DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async {
tools::registry::publish(flags, publish_flags).await
}),
@ -349,12 +337,30 @@ fn exit_with_message(message: &str, code: i32) -> ! {
std::process::exit(code);
}
fn get_suggestions_for_commonjs_error(e: &JsError) -> Vec<FixSuggestion> {
if e.name.as_deref() == Some("ReferenceError") {
if let Some(msg) = &e.message {
if msg.contains("module is not defined")
|| msg.contains("exports is not defined")
{
return vec![
FixSuggestion::info("Deno does not support CommonJS modules without `.cjs` extension."),
FixSuggestion::hint("Rewrite this module to ESM or change the file extension to `.cjs`."),
];
}
}
}
vec![]
}
fn exit_for_error(error: AnyError) -> ! {
let mut error_string = format!("{error:?}");
let mut error_code = 1;
if let Some(e) = error.downcast_ref::<JsError>() {
error_string = format_js_error(e);
let suggestions = get_suggestions_for_commonjs_error(e);
error_string = format_js_error_with_suggestions(e, suggestions);
} else if let Some(SnapshotFromLockfileError::IntegrityCheckFailed(e)) =
error.downcast_ref::<SnapshotFromLockfileError>()
{
@ -454,30 +460,19 @@ fn resolve_flags_and_init(
// https://github.com/microsoft/vscode/blob/48d4ba271686e8072fc6674137415bc80d936bc7/extensions/typescript-language-features/src/configuration/configuration.ts#L213-L214
DenoSubcommand::Lsp => vec!["--max-old-space-size=3072".to_string()],
_ => {
if *DENO_FUTURE {
// TODO(bartlomieju): I think this can be removed as it's handled by `deno_core`
// and its settings.
// deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript
// TODO(petamoriken): Need to check TypeScript `assert` keywords in deno_ast
vec!["--no-harmony-import-assertions".to_string()]
} else {
vec![
// TODO(bartlomieju): I think this can be removed as it's handled by `deno_core`
// and its settings.
// If we're still in v1.X version we want to support import assertions.
// V8 12.6 unshipped the support by default, so force it by passing a
// flag.
"--harmony-import-assertions".to_string(),
// Verify with DENO_FUTURE for now.
"--no-maglev".to_string(),
]
}
// TODO(bartlomieju): I think this can be removed as it's handled by `deno_core`
// and its settings.
// deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript
// TODO(petamoriken): Need to check TypeScript `assert` keywords in deno_ast
vec!["--no-harmony-import-assertions".to_string()]
}
};
init_v8_flags(&default_v8_flags, &flags.v8_flags, get_v8_flags_from_env());
// TODO(bartlomieju): remove last argument in Deno 2.
deno_core::JsRuntime::init_platform(None, !*DENO_FUTURE);
deno_core::JsRuntime::init_platform(
None, /* import assertions enabled */ false,
);
util::logger::init(flags.log_level);
Ok(flags)

View file

@ -401,7 +401,7 @@ impl<TGraphContainer: ModuleGraphContainer>
fn inner_resolve(
&self,
specifier: &str,
raw_specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
if self.shared.node_resolver.in_npm_package(referrer) {
@ -409,7 +409,7 @@ impl<TGraphContainer: ModuleGraphContainer>
self
.shared
.node_resolver
.resolve(specifier, referrer, NodeResolutionMode::Execution)?
.resolve(raw_specifier, referrer, NodeResolutionMode::Execution)?
.into_url(),
);
}
@ -418,7 +418,7 @@ impl<TGraphContainer: ModuleGraphContainer>
let resolution = match graph.get(referrer) {
Some(Module::Js(module)) => module
.dependencies
.get(specifier)
.get(raw_specifier)
.map(|d| &d.maybe_code)
.unwrap_or(&Resolution::None),
_ => &Resolution::None,
@ -433,7 +433,7 @@ impl<TGraphContainer: ModuleGraphContainer>
));
}
Resolution::None => Cow::Owned(self.shared.resolver.resolve(
specifier,
raw_specifier,
&deno_graph::Range {
specifier: referrer.clone(),
start: deno_graph::Position::zeroed(),

View file

@ -3307,19 +3307,30 @@ fn napi_resolve_deferred(
check_arg!(env, result);
check_arg!(env, deferred);
// Make sure microtasks don't run and call back into JS
env
.scope()
.set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
let deferred_ptr =
unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) };
let global = unsafe { v8::Global::from_raw(env.isolate(), deferred_ptr) };
let resolver = v8::Local::new(&mut env.scope(), global);
if !resolver
let success = resolver
.resolve(&mut env.scope(), result.unwrap())
.unwrap_or(false)
{
return napi_generic_failure;
}
.unwrap_or(false);
napi_ok
// Restore policy
env
.scope()
.set_microtasks_policy(v8::MicrotasksPolicy::Auto);
if success {
napi_ok
} else {
napi_generic_failure
}
}
#[napi_sym]

View file

@ -2,7 +2,7 @@
[package]
name = "napi_sym"
version = "0.96.0"
version = "0.98.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

View file

@ -17,6 +17,7 @@ use deno_runtime::deno_node::NodeRequireResolver;
use deno_runtime::deno_node::NpmProcessStateProvider;
use deno_runtime::deno_node::PackageJson;
use deno_semver::package::PackageReq;
use deno_semver::Version;
use node_resolver::errors::PackageFolderResolveError;
use node_resolver::errors::PackageFolderResolveIoError;
use node_resolver::errors::PackageJsonLoadError;
@ -29,6 +30,7 @@ use crate::args::NpmProcessStateKind;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
use deno_runtime::fs_util::specifier_to_file_path;
use super::managed::normalize_pkg_name_for_node_modules_deno_folder;
use super::CliNpmResolver;
use super::InnerCliNpmResolverRef;
@ -60,9 +62,7 @@ impl ByonmCliNpmResolver {
) -> Result<Option<Arc<PackageJson>>, PackageJsonLoadError> {
load_pkg_json(&DenoPkgJsonFsAdapter(self.fs.as_ref()), path)
}
}
impl ByonmCliNpmResolver {
/// Finds the ancestor package.json that contains the specified dependency.
pub fn find_ancestor_package_json_with_dep(
&self,
@ -98,7 +98,7 @@ impl ByonmCliNpmResolver {
&self,
req: &PackageReq,
referrer: &ModuleSpecifier,
) -> Result<(Arc<PackageJson>, String), AnyError> {
) -> Result<Option<(Arc<PackageJson>, String)>, AnyError> {
fn resolve_alias_from_pkg_json(
req: &PackageReq,
pkg_json: &PackageJson,
@ -134,7 +134,7 @@ impl ByonmCliNpmResolver {
if let Some(alias) =
resolve_alias_from_pkg_json(req, pkg_json.as_ref())
{
return Ok((pkg_json, alias));
return Ok(Some((pkg_json, alias)));
}
}
current_path = dir_path;
@ -148,19 +148,65 @@ impl ByonmCliNpmResolver {
if let Some(pkg_json) = self.load_pkg_json(&root_pkg_json_path)? {
if let Some(alias) = resolve_alias_from_pkg_json(req, pkg_json.as_ref())
{
return Ok((pkg_json, alias));
return Ok(Some((pkg_json, alias)));
}
}
}
bail!(
concat!(
"Could not find a matching package for 'npm:{}' in a package.json file. ",
"You must specify this as a package.json dependency when the ",
"node_modules folder is not managed by Deno.",
),
req,
Ok(None)
}
fn resolve_folder_in_root_node_modules(
&self,
req: &PackageReq,
) -> Option<PathBuf> {
// now check if node_modules/.deno/ matches this constraint
let root_node_modules_dir = self.root_node_modules_dir.as_ref()?;
let node_modules_deno_dir = root_node_modules_dir.join(".deno");
let Ok(entries) = self.fs.read_dir_sync(&node_modules_deno_dir) else {
return None;
};
let search_prefix = format!(
"{}@",
normalize_pkg_name_for_node_modules_deno_folder(&req.name)
);
let mut best_version = None;
// example entries:
// - @denotest+add@1.0.0
// - @denotest+add@1.0.0_1
for entry in entries {
if !entry.is_directory {
continue;
}
let Some(version_and_copy_idx) = entry.name.strip_prefix(&search_prefix)
else {
continue;
};
let version = version_and_copy_idx
.rsplit_once('_')
.map(|(v, _)| v)
.unwrap_or(version_and_copy_idx);
let Ok(version) = Version::parse_from_npm(version) else {
continue;
};
if req.version_req.matches(&version) {
if let Some((best_version_version, _)) = &best_version {
if version > *best_version_version {
best_version = Some((version, entry.name));
}
} else {
best_version = Some((version, entry.name));
}
}
}
best_version.map(|(_version, entry_name)| {
join_package_name(
&node_modules_deno_dir.join(entry_name).join("node_modules"),
&req.name,
)
})
}
}
@ -288,34 +334,62 @@ impl CliNpmResolver for ByonmCliNpmResolver {
req: &PackageReq,
referrer: &ModuleSpecifier,
) -> Result<PathBuf, AnyError> {
// resolve the pkg json and alias
let (pkg_json, alias) =
self.resolve_pkg_json_and_alias_for_req(req, referrer)?;
// now try node resolution
for ancestor in pkg_json.path.parent().unwrap().ancestors() {
let node_modules_folder = ancestor.join("node_modules");
let sub_dir = join_package_name(&node_modules_folder, &alias);
if self.fs.is_dir_sync(&sub_dir) {
return Ok(canonicalize_path_maybe_not_exists_with_fs(
&sub_dir,
self.fs.as_ref(),
)?);
fn node_resolve_dir(
fs: &dyn FileSystem,
alias: &str,
start_dir: &Path,
) -> Result<Option<PathBuf>, AnyError> {
for ancestor in start_dir.ancestors() {
let node_modules_folder = ancestor.join("node_modules");
let sub_dir = join_package_name(&node_modules_folder, alias);
if fs.is_dir_sync(&sub_dir) {
return Ok(Some(canonicalize_path_maybe_not_exists_with_fs(
&sub_dir, fs,
)?));
}
}
Ok(None)
}
bail!(
concat!(
"Could not find \"{}\" in a node_modules folder. ",
"Deno expects the node_modules/ directory to be up to date. ",
"Did you forget to run `{}`?"
),
alias,
if *crate::args::DENO_FUTURE {
"deno install"
} else {
"npm install"
// now attempt to resolve if it's found in any package.json
let maybe_pkg_json_and_alias =
self.resolve_pkg_json_and_alias_for_req(req, referrer)?;
match maybe_pkg_json_and_alias {
Some((pkg_json, alias)) => {
// now try node resolution
if let Some(resolved) =
node_resolve_dir(self.fs.as_ref(), &alias, pkg_json.dir_path())?
{
return Ok(resolved);
}
bail!(
concat!(
"Could not find \"{}\" in a node_modules folder. ",
"Deno expects the node_modules/ directory to be up to date. ",
"Did you forget to run `deno install`?"
),
alias,
);
}
);
None => {
// now check if node_modules/.deno/ matches this constraint
if let Some(folder) = self.resolve_folder_in_root_node_modules(req) {
return Ok(folder);
}
bail!(
concat!(
"Could not find a matching package for 'npm:{}' in the node_modules ",
"directory. Ensure you have all your JSR and npm dependencies listed ",
"in your deno.json or package.json, then run `deno install`. Alternatively, ",
r#"turn on auto-install by specifying `"nodeModulesDir": "auto"` in your "#,
"deno.json file."
),
req,
);
}
}
}
fn check_state_hash(&self) -> Option<u64> {

View file

@ -32,9 +32,9 @@ use resolution::AddPkgReqsResult;
use crate::args::CliLockfile;
use crate::args::LifecycleScriptsConfig;
use crate::args::NpmInstallDepsProvider;
use crate::args::NpmProcessState;
use crate::args::NpmProcessStateKind;
use crate::args::PackageJsonInstallDepsProvider;
use crate::cache::FastInsecureHasher;
use crate::http_util::HttpClientProvider;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
@ -45,6 +45,7 @@ use self::cache::NpmCache;
use self::registry::CliNpmRegistryApi;
use self::resolution::NpmResolution;
use self::resolvers::create_npm_fs_resolver;
pub use self::resolvers::normalize_pkg_name_for_node_modules_deno_folder;
use self::resolvers::NpmPackageFsResolver;
use super::CliNpmResolver;
@ -71,7 +72,7 @@ pub struct CliNpmResolverManagedCreateOptions {
pub text_only_progress_bar: crate::util::progress_bar::ProgressBar,
pub maybe_node_modules_path: Option<PathBuf>,
pub npm_system_info: NpmSystemInfo,
pub package_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
pub npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
pub npmrc: Arc<ResolvedNpmRc>,
pub lifecycle_scripts: LifecycleScriptsConfig,
}
@ -97,7 +98,7 @@ pub async fn create_managed_npm_resolver_for_lsp(
npm_api,
npm_cache,
options.npmrc,
options.package_json_deps_provider,
options.npm_install_deps_provider,
options.text_only_progress_bar,
options.maybe_node_modules_path,
options.npm_system_info,
@ -122,7 +123,7 @@ pub async fn create_managed_npm_resolver(
npm_api,
npm_cache,
options.npmrc,
options.package_json_deps_provider,
options.npm_install_deps_provider,
options.text_only_progress_bar,
options.maybe_node_modules_path,
options.npm_system_info,
@ -139,7 +140,7 @@ fn create_inner(
npm_api: Arc<CliNpmRegistryApi>,
npm_cache: Arc<NpmCache>,
npm_rc: Arc<ResolvedNpmRc>,
package_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
text_only_progress_bar: crate::util::progress_bar::ProgressBar,
node_modules_dir_path: Option<PathBuf>,
npm_system_info: NpmSystemInfo,
@ -161,7 +162,7 @@ fn create_inner(
let fs_resolver = create_npm_fs_resolver(
fs.clone(),
npm_cache.clone(),
&package_json_deps_provider,
&npm_install_deps_provider,
&text_only_progress_bar,
resolution.clone(),
tarball_cache.clone(),
@ -175,7 +176,7 @@ fn create_inner(
maybe_lockfile,
npm_api,
npm_cache,
package_json_deps_provider,
npm_install_deps_provider,
resolution,
tarball_cache,
text_only_progress_bar,
@ -261,7 +262,7 @@ pub struct ManagedCliNpmResolver {
maybe_lockfile: Option<Arc<CliLockfile>>,
npm_api: Arc<CliNpmRegistryApi>,
npm_cache: Arc<NpmCache>,
package_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
resolution: Arc<NpmResolution>,
tarball_cache: Arc<TarballCache>,
text_only_progress_bar: ProgressBar,
@ -286,7 +287,7 @@ impl ManagedCliNpmResolver {
maybe_lockfile: Option<Arc<CliLockfile>>,
npm_api: Arc<CliNpmRegistryApi>,
npm_cache: Arc<NpmCache>,
package_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
resolution: Arc<NpmResolution>,
tarball_cache: Arc<TarballCache>,
text_only_progress_bar: ProgressBar,
@ -299,7 +300,7 @@ impl ManagedCliNpmResolver {
maybe_lockfile,
npm_api,
npm_cache,
package_json_deps_provider,
npm_install_deps_provider,
text_only_progress_bar,
resolution,
tarball_cache,
@ -406,8 +407,7 @@ impl ManagedCliNpmResolver {
}
}
if result.dependencies_result.is_ok() {
result.dependencies_result =
self.cache_packages().await.map_err(AnyError::from);
result.dependencies_result = self.cache_packages().await;
}
result
@ -477,7 +477,7 @@ impl ManagedCliNpmResolver {
if !self.top_level_install_flag.raise() {
return Ok(false); // already did this
}
let pkg_json_remote_pkgs = self.package_json_deps_provider.remote_pkgs();
let pkg_json_remote_pkgs = self.npm_install_deps_provider.remote_pkgs();
if pkg_json_remote_pkgs.is_empty() {
return Ok(false);
}
@ -606,7 +606,7 @@ impl CliNpmResolver for ManagedCliNpmResolver {
create_npm_fs_resolver(
self.fs.clone(),
self.npm_cache.clone(),
&self.package_json_deps_provider,
&self.npm_install_deps_provider,
&self.text_only_progress_bar,
npm_resolution.clone(),
self.tarball_cache.clone(),
@ -617,7 +617,7 @@ impl CliNpmResolver for ManagedCliNpmResolver {
self.maybe_lockfile.clone(),
self.npm_api.clone(),
self.npm_cache.clone(),
self.package_json_deps_provider.clone(),
self.npm_install_deps_provider.clone(),
npm_resolution,
self.tarball_cache.clone(),
self.text_only_progress_bar.clone(),

View file

@ -22,6 +22,7 @@ use deno_npm::NpmPackageCacheFolderId;
use deno_npm::NpmPackageId;
use deno_npm::NpmResolutionPackage;
use deno_npm::NpmSystemInfo;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use deno_semver::VersionReq;
@ -329,16 +330,10 @@ fn populate_lockfile_from_snapshot(
) {
let mut lockfile = lockfile.lock();
for (package_req, nv) in snapshot.package_reqs() {
let id = &snapshot.resolve_package_from_deno_module(nv).unwrap().id;
lockfile.insert_package_specifier(
format!("npm:{}", package_req),
format!(
"npm:{}",
snapshot
.resolve_package_from_deno_module(nv)
.unwrap()
.id
.as_serialized()
),
JsrDepPackageReq::npm(package_req.clone()),
format!("{}{}", id.nv.version, id.peer_deps_serialized()),
);
}
for package in snapshot.all_packages_for_every_system() {

View file

@ -10,6 +10,7 @@ use std::cmp::Ordering;
use std::collections::hash_map::Entry;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
@ -41,7 +42,7 @@ use node_resolver::errors::ReferrerNotFoundError;
use serde::Deserialize;
use serde::Serialize;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::cache::CACHE_PERM;
use crate::npm::cache_dir::mixed_case_package_name_decode;
use crate::npm::cache_dir::mixed_case_package_name_encode;
@ -65,7 +66,7 @@ use super::common::RegistryReadPermissionChecker;
pub struct LocalNpmPackageResolver {
cache: Arc<NpmCache>,
fs: Arc<dyn deno_fs::FileSystem>,
pkg_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
progress_bar: ProgressBar,
resolution: Arc<NpmResolution>,
tarball_cache: Arc<TarballCache>,
@ -81,7 +82,7 @@ impl LocalNpmPackageResolver {
pub fn new(
cache: Arc<NpmCache>,
fs: Arc<dyn deno_fs::FileSystem>,
pkg_json_deps_provider: Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: Arc<NpmInstallDepsProvider>,
progress_bar: ProgressBar,
resolution: Arc<NpmResolution>,
tarball_cache: Arc<TarballCache>,
@ -92,7 +93,7 @@ impl LocalNpmPackageResolver {
Self {
cache,
fs: fs.clone(),
pkg_json_deps_provider,
npm_install_deps_provider,
progress_bar,
resolution,
tarball_cache,
@ -248,7 +249,7 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver {
sync_resolution_with_fs(
&self.resolution.snapshot(),
&self.cache,
&self.pkg_json_deps_provider,
&self.npm_install_deps_provider,
&self.progress_bar,
&self.tarball_cache,
&self.root_node_modules_path,
@ -412,14 +413,16 @@ fn has_lifecycle_scripts(
async fn sync_resolution_with_fs(
snapshot: &NpmResolutionSnapshot,
cache: &Arc<NpmCache>,
pkg_json_deps_provider: &PackageJsonInstallDepsProvider,
npm_install_deps_provider: &NpmInstallDepsProvider,
progress_bar: &ProgressBar,
tarball_cache: &Arc<TarballCache>,
root_node_modules_dir_path: &Path,
system_info: &NpmSystemInfo,
lifecycle_scripts: &LifecycleScriptsConfig,
) -> Result<(), AnyError> {
if snapshot.is_empty() && pkg_json_deps_provider.workspace_pkgs().is_empty() {
if snapshot.is_empty()
&& npm_install_deps_provider.workspace_pkgs().is_empty()
{
return Ok(()); // don't create the directory
}
@ -618,9 +621,12 @@ async fn sync_resolution_with_fs(
let mut found_names: HashMap<&String, &PackageNv> = HashMap::new();
// set of node_modules in workspace packages that we've already ensured exist
let mut existing_child_node_modules_dirs: HashSet<PathBuf> = HashSet::new();
// 4. Create symlinks for package json dependencies
{
for remote in pkg_json_deps_provider.remote_pkgs() {
for remote in npm_install_deps_provider.remote_pkgs() {
let remote_pkg = if let Ok(remote_pkg) =
snapshot.resolve_pkg_from_pkg_req(&remote.req)
{
@ -665,8 +671,15 @@ async fn sync_resolution_with_fs(
);
if install_in_child {
// symlink the dep into the package's child node_modules folder
let dest_path =
remote.base_dir.join("node_modules").join(&remote.alias);
let dest_node_modules = remote.base_dir.join("node_modules");
if !existing_child_node_modules_dirs.contains(&dest_node_modules) {
fs::create_dir_all(&dest_node_modules).with_context(|| {
format!("Creating '{}'", dest_node_modules.display())
})?;
existing_child_node_modules_dirs.insert(dest_node_modules.clone());
}
let mut dest_path = dest_node_modules;
dest_path.push(&remote.alias);
symlink_package_dir(&local_registry_package_path, &dest_path)?;
} else {
@ -684,7 +697,7 @@ async fn sync_resolution_with_fs(
}
// 5. Create symlinks for the remaining top level packages in the node_modules folder.
// (These may be present if they are not in the package.json dependencies, such as )
// (These may be present if they are not in the package.json dependencies)
// Symlink node_modules/.deno/<package_id>/node_modules/<package_name> to
// node_modules/<package_name>
let mut ids = snapshot
@ -757,10 +770,10 @@ async fn sync_resolution_with_fs(
// 8. Create symlinks for the workspace packages
{
// todo(#24419): this is not exactly correct because it should
// todo(dsherret): this is not exactly correct because it should
// install correctly for a workspace (potentially in sub directories),
// but this is good enough for a first pass
for workspace in pkg_json_deps_provider.workspace_pkgs() {
for workspace in npm_install_deps_provider.workspace_pkgs() {
symlink_package_dir(
&workspace.target_dir,
&root_node_modules_dir_path.join(&workspace.alias),
@ -831,22 +844,14 @@ async fn sync_resolution_with_fs(
}
if !packages_with_scripts_not_run.is_empty() {
let (maybe_install, maybe_install_example) = if *crate::args::DENO_FUTURE {
(
" or `deno install`",
" or `deno install --allow-scripts=pkg1,pkg2`",
)
} else {
("", "")
};
let packages = packages_with_scripts_not_run
.iter()
.map(|(_, p)| format!("npm:{p}"))
.collect::<Vec<_>>()
.join(", ");
log::warn!("{}: Packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed.
This may cause the packages to not work correctly. To run them, use the `--allow-scripts` flag with `deno cache`{maybe_install}
(e.g. `deno cache --allow-scripts=pkg1,pkg2 <entrypoint>`{maybe_install_example}):\n {packages}", crate::colors::yellow("warning"));
log::warn!("{} Packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed.
This may cause the packages to not work correctly. To run them, use the `--allow-scripts` flag with `deno cache` or `deno install`
(e.g. `deno cache --allow-scripts=pkg1,pkg2 <entrypoint>` or `deno install --allow-scripts=pkg1,pkg2`):\n {packages}", crate::colors::yellow("Warning"));
for (scripts_warned_path, _) in packages_with_scripts_not_run {
let _ignore_err = fs::write(scripts_warned_path, "");
}
@ -993,21 +998,31 @@ impl SetupCache {
}
}
/// Normalizes a package name for use at `node_modules/.deno/<pkg-name>@<version>[_<copy_index>]`
pub fn normalize_pkg_name_for_node_modules_deno_folder(name: &str) -> Cow<str> {
let name = if name.to_lowercase() == name {
Cow::Borrowed(name)
} else {
Cow::Owned(format!("_{}", mixed_case_package_name_encode(name)))
};
if name.starts_with('@') {
name.replace('/', "+").into()
} else {
name
}
}
fn get_package_folder_id_folder_name(
folder_id: &NpmPackageCacheFolderId,
) -> String {
let copy_str = if folder_id.copy_index == 0 {
"".to_string()
Cow::Borrowed("")
} else {
format!("_{}", folder_id.copy_index)
Cow::Owned(format!("_{}", folder_id.copy_index))
};
let nv = &folder_id.nv;
let name = if nv.name.to_lowercase() == nv.name {
Cow::Borrowed(&nv.name)
} else {
Cow::Owned(format!("_{}", mixed_case_package_name_encode(&nv.name)))
};
format!("{}@{}{}", name, nv.version, copy_str).replace('/', "+")
let name = normalize_pkg_name_for_node_modules_deno_folder(&nv.name);
format!("{}@{}{}", name, nv.version, copy_str)
}
fn get_package_folder_id_from_folder_name(

View file

@ -11,10 +11,11 @@ use deno_npm::NpmSystemInfo;
use deno_runtime::deno_fs::FileSystem;
use crate::args::LifecycleScriptsConfig;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::util::progress_bar::ProgressBar;
pub use self::common::NpmPackageFsResolver;
pub use self::local::normalize_pkg_name_for_node_modules_deno_folder;
use self::global::GlobalNpmPackageResolver;
use self::local::LocalNpmPackageResolver;
@ -27,7 +28,7 @@ use super::resolution::NpmResolution;
pub fn create_npm_fs_resolver(
fs: Arc<dyn FileSystem>,
npm_cache: Arc<NpmCache>,
pkg_json_deps_provider: &Arc<PackageJsonInstallDepsProvider>,
npm_install_deps_provider: &Arc<NpmInstallDepsProvider>,
progress_bar: &ProgressBar,
resolution: Arc<NpmResolution>,
tarball_cache: Arc<TarballCache>,
@ -39,7 +40,7 @@ pub fn create_npm_fs_resolver(
Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new(
npm_cache,
fs,
pkg_json_deps_provider.clone(),
npm_install_deps_provider.clone(),
progress_bar.clone(),
resolution,
tarball_cache,

View file

@ -146,7 +146,7 @@ impl CliNodeResolver {
concat!(
"Could not resolve \"{}\", but found it in a package.json. ",
"Deno expects the node_modules/ directory to be up to date. ",
"Did you forget to run `npm install`?"
"Did you forget to run `deno install`?"
),
specifier
));
@ -225,13 +225,8 @@ impl CliNodeResolver {
let package_json_path = package_folder.join("package.json");
if !self.fs.exists_sync(&package_json_path) {
return Err(anyhow!(
"Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `{}`?",
"Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?",
package_json_path.display(),
if *crate::args::DENO_FUTURE {
"deno install"
} else {
"npm install"
},
));
}
}
@ -332,7 +327,9 @@ impl NpmModuleLoader {
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
) -> Option<Result<ModuleCodeStringSource, AnyError>> {
if self.node_resolver.in_npm_package(specifier) {
if self.node_resolver.in_npm_package(specifier)
|| (specifier.scheme() == "file" && specifier.path().ends_with(".cjs"))
{
Some(self.load(specifier, maybe_referrer).await)
} else {
None
@ -381,7 +378,9 @@ impl NpmModuleLoader {
}
})?;
let code = if self.cjs_resolutions.contains(specifier) {
let code = if self.cjs_resolutions.contains(specifier)
|| (specifier.scheme() == "file" && specifier.path().ends_with(".cjs"))
{
// translate cjs to esm if it's cjs and inject node globals
let code = match String::from_utf8_lossy(&code) {
Cow::Owned(code) => code,
@ -507,7 +506,7 @@ impl Resolver for CliGraphResolver {
fn resolve(
&self,
specifier: &str,
raw_specifier: &str,
referrer_range: &deno_graph::Range,
mode: ResolutionMode,
) -> Result<ModuleSpecifier, ResolveError> {
@ -524,7 +523,7 @@ impl Resolver for CliGraphResolver {
if let Some(node_resolver) = self.node_resolver.as_ref() {
if referrer.scheme() == "file" && node_resolver.in_npm_package(referrer) {
return node_resolver
.resolve(specifier, referrer, to_node_mode(mode))
.resolve(raw_specifier, referrer, to_node_mode(mode))
.map(|res| res.into_url())
.map_err(|e| ResolveError::Other(e.into()));
}
@ -533,7 +532,7 @@ impl Resolver for CliGraphResolver {
// Attempt to resolve with the workspace resolver
let result: Result<_, ResolveError> = self
.workspace_resolver
.resolve(specifier, referrer)
.resolve(raw_specifier, referrer)
.map_err(|err| match err {
MappedResolutionError::Specifier(err) => ResolveError::Specifier(err),
MappedResolutionError::ImportMap(err) => {
@ -705,7 +704,7 @@ impl Resolver for CliGraphResolver {
// If byonm, check if the bare specifier resolves to an npm package
if is_byonm && referrer.scheme() == "file" {
let maybe_resolution = node_resolver
.resolve_if_for_npm_pkg(specifier, referrer, to_node_mode(mode))
.resolve_if_for_npm_pkg(raw_specifier, referrer, to_node_mode(mode))
.map_err(ResolveError::Other)?;
if let Some(res) = maybe_resolution {
return Ok(res.into_url());
@ -754,7 +753,7 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> {
let line = start.line + 1;
let column = start.character + 1;
if !*DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
log::warn!("Warning: Resolving \"{module_name}\" as \"node:{module_name}\" at {specifier}:{line}:{column}. If you want to use a built-in Node module, add a \"node:\" prefix.")
log::warn!("{} Resolving \"{module_name}\" as \"node:{module_name}\" at {specifier}:{line}:{column}. If you want to use a built-in Node module, add a \"node:\" prefix.", colors::yellow("Warning"))
}
}

View file

@ -223,7 +223,7 @@
"useUnknownInCatchVariables": {
"description": "Default catch clause variables as `unknown` instead of `any`.",
"type": "boolean",
"default": false,
"default": true,
"markdownDescription": "Default catch clause variables as `unknown` instead of `any`.\n\nSee more: https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables"
}
}
@ -622,6 +622,11 @@
{
"type": "object",
"description": "A map of package exports to files in this JSR package.",
"propertyNames": {
"description": "Package export name",
"examples": [".", "./foo", "./bar"],
"pattern": "^\\.(/.*)?$"
},
"patternProperties": {
"^\\.(/.*)?$": {
"type": "string",

View file

@ -83,14 +83,6 @@
"type": "string",
"description": "The checksum of the local source file. This can be used to validate if the current on disk version matches the version described here."
},
"emit": {
"type": "string",
"description": "The path to an emitted version of the module, if the module requires transpilation to be loaded into the Deno runtime."
},
"map": {
"type": "string",
"description": "The path to an optionally emitted source map between the original and emitted version of the file."
},
"error": {
"type": "string",
"description": "If when resolving the module, Deno encountered an error and the module is unavailable, the text of that error will be indicated here."

View file

@ -45,7 +45,7 @@ use serde::Serialize;
use crate::args::CaData;
use crate::args::CliOptions;
use crate::args::CompileFlags;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::args::PermissionFlags;
use crate::args::UnstableConfig;
use crate::cache::DenoDir;
@ -117,7 +117,6 @@ pub struct Metadata {
pub workspace_resolver: SerializedWorkspaceResolver,
pub entrypoint_key: String,
pub node_modules: Option<NodeModules>,
pub disable_deprecated_api_warning: bool,
pub unstable_config: UnstableConfig,
}
@ -624,12 +623,9 @@ impl<'a> DenoCompileBinaryWriter<'a> {
pkg_json_resolution: self.workspace_resolver.pkg_json_dep_resolution(),
},
node_modules,
disable_deprecated_api_warning: cli_options
.disable_deprecated_api_warning,
unstable_config: UnstableConfig {
legacy_flag_enabled: cli_options.legacy_unstable_flag(),
bare_node_builtins: cli_options.unstable_bare_node_builtins(),
byonm: cli_options.use_byonm(),
sloppy_imports: cli_options.unstable_sloppy_imports(),
features: cli_options.unstable_features(),
},

View file

@ -48,7 +48,7 @@ use crate::args::get_root_cert_store;
use crate::args::npm_pkg_req_ref_to_binary_command;
use crate::args::CaData;
use crate::args::CacheSetting;
use crate::args::PackageJsonInstallDepsProvider;
use crate::args::NpmInstallDepsProvider;
use crate::args::StorageKeyResolver;
use crate::cache::Caches;
use crate::cache::DenoDirProvider;
@ -138,7 +138,7 @@ pub const UNSUPPORTED_SCHEME: &str = "Unsupported scheme";
impl ModuleLoader for EmbeddedModuleLoader {
fn resolve(
&self,
specifier: &str,
raw_specifier: &str,
referrer: &str,
kind: ResolutionKind,
) -> Result<ModuleSpecifier, AnyError> {
@ -162,13 +162,15 @@ impl ModuleLoader for EmbeddedModuleLoader {
self
.shared
.node_resolver
.resolve(specifier, &referrer, NodeResolutionMode::Execution)?
.resolve(raw_specifier, &referrer, NodeResolutionMode::Execution)?
.into_url(),
);
}
let mapped_resolution =
self.shared.workspace_resolver.resolve(specifier, &referrer);
let mapped_resolution = self
.shared
.workspace_resolver
.resolve(raw_specifier, &referrer);
match mapped_resolution {
Ok(MappedResolution::WorkspaceJsrPackage { specifier, .. }) => {
@ -262,7 +264,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" =>
{
let maybe_res = self.shared.node_resolver.resolve_if_for_npm_pkg(
specifier,
raw_specifier,
&referrer,
NodeResolutionMode::Execution,
)?;
@ -502,9 +504,9 @@ pub async fn run(
text_only_progress_bar: progress_bar,
maybe_node_modules_path,
npm_system_info: Default::default(),
package_json_deps_provider: Arc::new(
npm_install_deps_provider: Arc::new(
// this is only used for installing packages, which isn't necessary with deno compile
PackageJsonInstallDepsProvider::empty(),
NpmInstallDepsProvider::empty(),
),
// create an npmrc that uses the fake npm_registry_url to resolve packages
npmrc: Arc::new(ResolvedNpmRc {
@ -554,9 +556,9 @@ pub async fn run(
text_only_progress_bar: progress_bar,
maybe_node_modules_path: None,
npm_system_info: Default::default(),
package_json_deps_provider: Arc::new(
npm_install_deps_provider: Arc::new(
// this is only used for installing packages, which isn't necessary with deno compile
PackageJsonInstallDepsProvider::empty(),
NpmInstallDepsProvider::empty(),
),
// Packages from different registries are already inlined in the ESZip,
// so no need to create actual `.npmrc` configuration.
@ -706,6 +708,8 @@ pub async fn run(
None,
None,
feature_checker,
// Code cache is not supported for standalone binary yet.
None,
CliMainWorkerOptions {
argv: metadata.argv,
log_level: WorkerLogLevel::Info,
@ -732,17 +736,10 @@ pub async fn run(
unstable: metadata.unstable_config.legacy_flag_enabled,
create_hmr_runner: None,
create_coverage_collector: None,
node_ipc: None,
serve_port: None,
serve_host: None,
},
None,
None,
None,
false,
// TODO(bartlomieju): temporarily disabled
// metadata.disable_deprecated_api_warning,
true,
false,
// Code cache is not supported for standalone binary yet.
None,
);
// Initialize v8 once from the main thread.

View file

@ -213,8 +213,8 @@ impl ShellCommand for NodeGypCommand {
) -> LocalBoxFuture<'static, ExecuteResult> {
// at the moment this shell command is just to give a warning if node-gyp is not found
// in the future, we could try to run/install node-gyp for the user with deno
if which::which("node-gyp").is_err() {
log::warn!("{}: node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`)", crate::colors::yellow("warning"));
if context.state.resolve_command_path("node-gyp").is_err() {
log::warn!("{} node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`)", crate::colors::yellow("Warning"));
}
ExecutableCommand::new(
"node-gyp".to_string(),

View file

@ -18,8 +18,11 @@ pub trait BenchReporter {
fn report_uncaught_error(&mut self, origin: &str, error: Box<JsError>);
}
const JSON_SCHEMA_VERSION: u8 = 1;
#[derive(Debug, Serialize)]
struct JsonReporterOutput {
version: u8,
runtime: String,
cpu: String,
benches: Vec<JsonReporterBench>,
@ -28,6 +31,7 @@ struct JsonReporterOutput {
impl Default for JsonReporterOutput {
fn default() -> Self {
Self {
version: JSON_SCHEMA_VERSION,
runtime: format!(
"{} {}",
version::DENO_VERSION_INFO.user_agent,

View file

@ -1,164 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::path::PathBuf;
use std::sync::Arc;
use deno_core::error::AnyError;
use deno_graph::Module;
use deno_terminal::colors;
use crate::args::BundleFlags;
use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::TsConfigType;
use crate::factory::CliFactory;
use crate::graph_util::error_for_any_npm_specifier;
use crate::util;
use crate::util::display;
pub async fn bundle(
flags: Arc<Flags>,
bundle_flags: BundleFlags,
) -> Result<(), AnyError> {
log::info!(
"{}",
colors::yellow("⚠️ Warning: `deno bundle` is deprecated and will be removed in Deno 2.0.\nUse an alternative bundler like \"deno_emit\", \"esbuild\" or \"rollup\" instead."),
);
if let Some(watch_flags) = &bundle_flags.watch {
util::file_watcher::watch_func(
flags,
util::file_watcher::PrintConfig::new(
"Bundle",
!watch_flags.no_clear_screen,
),
move |flags, watcher_communicator, _changed_paths| {
let bundle_flags = bundle_flags.clone();
Ok(async move {
let factory = CliFactory::from_flags_for_watcher(
flags,
watcher_communicator.clone(),
);
let cli_options = factory.cli_options()?;
let _ = watcher_communicator.watch_paths(cli_options.watch_paths());
bundle_action(factory, &bundle_flags).await?;
Ok(())
})
},
)
.await?;
} else {
let factory = CliFactory::from_flags(flags);
bundle_action(factory, &bundle_flags).await?;
}
Ok(())
}
async fn bundle_action(
factory: CliFactory,
bundle_flags: &BundleFlags,
) -> Result<(), AnyError> {
let cli_options = factory.cli_options()?;
let module_specifier = cli_options.resolve_main_module()?;
log::debug!(">>>>> bundle START");
let module_graph_creator = factory.module_graph_creator().await?;
let cli_options = factory.cli_options()?;
let graph = module_graph_creator
.create_graph_and_maybe_check(vec![module_specifier.clone()])
.await?;
let mut paths_to_watch: Vec<PathBuf> = graph
.specifiers()
.filter_map(|(_, r)| {
r.ok().and_then(|module| match module {
Module::Js(m) => m.specifier.to_file_path().ok(),
Module::Json(m) => m.specifier.to_file_path().ok(),
// nothing to watch
Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
})
})
.collect();
if let Ok(Some(import_map_path)) = cli_options
.resolve_specified_import_map_specifier()
.map(|ms| ms.and_then(|ref s| s.to_file_path().ok()))
{
paths_to_watch.push(import_map_path);
}
// at the moment, we don't support npm specifiers in deno bundle, so show an error
error_for_any_npm_specifier(&graph)?;
let bundle_output = bundle_module_graph(graph.as_ref(), cli_options)?;
log::debug!(">>>>> bundle END");
let out_file = &bundle_flags.out_file;
if let Some(out_file) = out_file {
let out_file = cli_options.initial_cwd().join(out_file);
let output_bytes = bundle_output.code.as_bytes();
let output_len = output_bytes.len();
util::fs::write_file(&out_file, output_bytes, 0o644)?;
log::info!(
"{} {:?} ({})",
colors::green("Emit"),
out_file,
colors::gray(display::human_size(output_len as f64))
);
if let Some(bundle_map) = bundle_output.maybe_map {
let map_bytes = bundle_map.as_bytes();
let map_len = map_bytes.len();
let ext = if let Some(curr_ext) = out_file.extension() {
format!("{}.map", curr_ext.to_string_lossy())
} else {
"map".to_string()
};
let map_out_file = out_file.with_extension(ext);
util::fs::write_file(&map_out_file, map_bytes, 0o644)?;
log::info!(
"{} {:?} ({})",
colors::green("Emit"),
map_out_file,
colors::gray(display::human_size(map_len as f64))
);
}
} else {
#[allow(clippy::print_stdout)]
{
println!("{}", bundle_output.code);
}
}
Ok(())
}
fn bundle_module_graph(
graph: &deno_graph::ModuleGraph,
cli_options: &CliOptions,
) -> Result<deno_emit::BundleEmit, AnyError> {
log::info!("{} {}", colors::green("Bundle"), graph.roots[0]);
let ts_config_result =
cli_options.resolve_ts_config_for_emit(TsConfigType::Bundle)?;
if !cli_options.type_check_mode().is_true() {
if let Some(ignored_options) = ts_config_result.maybe_ignored_options {
log::warn!("{}", ignored_options);
}
}
let (transpile_options, emit_options) =
crate::args::ts_config_to_transpile_and_emit_options(
ts_config_result.ts_config,
)?;
deno_emit::bundle_graph(
graph,
deno_emit::BundleOptions {
minify: false,
bundle_type: deno_emit::BundleType::Module,
emit_options,
emit_ignore_directives: true,
transpile_options,
},
)
}

View file

@ -14,6 +14,7 @@ use deno_terminal::colors;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::args::check_warn_tsconfig;
use crate::args::CliOptions;
use crate::args::TsConfig;
use crate::args::TsConfigType;
@ -118,9 +119,7 @@ impl TypeChecker {
.cli_options
.resolve_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?;
if options.log_ignored_options {
if let Some(ignored_options) = ts_config_result.maybe_ignored_options {
log::warn!("{}", ignored_options);
}
check_warn_tsconfig(&ts_config_result);
}
let type_check_mode = options.type_check_mode;
@ -427,7 +426,7 @@ fn get_tsc_roots(
// now walk the graph that only includes the fast check dependencies
while let Some(specifier) = pending.pop_front() {
let Some(module) = graph.get(&specifier) else {
let Some(module) = graph.get(specifier) else {
continue;
};
if let Some(entry) = maybe_get_check_entry(module, check_js) {

View file

@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::check_warn_tsconfig;
use crate::args::CompileFlags;
use crate::args::Flags;
use crate::factory::CliFactory;
@ -79,6 +80,7 @@ pub async fn compile(
let ts_config_for_emit = cli_options
.resolve_ts_config_for_emit(deno_config::deno_json::TsConfigType::Emit)?;
check_warn_tsconfig(&ts_config_for_emit);
let (transpile_options, emit_options) =
crate::args::ts_config_to_transpile_and_emit_options(
ts_config_for_emit.ts_config,

View file

@ -5,8 +5,7 @@ use crate::args::DocHtmlFlag;
use crate::args::DocSourceFileFlag;
use crate::args::Flags;
use crate::colors;
use crate::display::write_json_to_stdout;
use crate::display::write_to_stdout_ignore_sigpipe;
use crate::display;
use crate::factory::CliFactory;
use crate::graph_util::graph_exit_lock_errors;
use crate::tsc::get_types_declaration_file_text;
@ -17,6 +16,7 @@ use deno_config::glob::PathOrPatternSet;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_doc as doc;
use deno_doc::html::UrlResolveKind;
use deno_graph::source::NullFileSystem;
@ -31,6 +31,8 @@ use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::Arc;
const JSON_SCHEMA_VERSION: u8 = 1;
async fn generate_doc_nodes_for_builtin_types(
doc_flags: DocFlags,
parser: &dyn ModuleParser,
@ -228,7 +230,11 @@ pub async fn doc(
doc_nodes_by_url.into_values().flatten().collect::<Vec<_>>();
if doc_flags.json {
write_json_to_stdout(&doc_nodes)
let json_output = serde_json::json!({
"version": JSON_SCHEMA_VERSION,
"nodes": &doc_nodes
});
display::write_json_to_stdout(&json_output)
} else if doc_flags.lint {
// don't output docs if running with only the --lint flag
log::info!(
@ -553,7 +559,8 @@ fn print_docs_to_stdout(
)
};
write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(AnyError::from)
display::write_to_stdout_ignore_sigpipe(details.as_bytes())
.map_err(AnyError::from)
}
fn check_diagnostics(diagnostics: &[DocDiagnostic]) -> Result<(), AnyError> {

View file

@ -403,15 +403,6 @@ pub fn format_html(
let mut typescript_config =
get_resolved_typescript_config(fmt_options);
typescript_config.line_width = hints.print_width as u32;
if hints.attr {
typescript_config.quote_style = if let Some(true) =
fmt_options.single_quote
{
dprint_plugin_typescript::configuration::QuoteStyle::AlwaysDouble
} else {
dprint_plugin_typescript::configuration::QuoteStyle::AlwaysSingle
};
}
dprint_plugin_typescript::format_text(
&path,
text.to_string(),

View file

@ -11,7 +11,6 @@ use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::resolve_url_or_path;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_graph::Dependency;
use deno_graph::GraphKind;
use deno_graph::Module;
@ -35,6 +34,8 @@ use crate::npm::CliNpmResolver;
use crate::npm::ManagedCliNpmResolver;
use crate::util::checksum;
const JSON_SCHEMA_VERSION: u8 = 1;
pub async fn info(
flags: Arc<Flags>,
info_flags: InfoFlags,
@ -79,7 +80,10 @@ pub async fn info(
}
if info_flags.json {
let mut json_graph = json!(graph);
let mut json_graph = serde_json::json!(graph);
if let Some(output) = json_graph.as_object_mut() {
output.insert("version".to_string(), JSON_SCHEMA_VERSION.into());
}
add_npm_packages_to_json(&mut json_graph, npm_resolver.as_ref());
display::write_json_to_stdout(&json_graph)?;
} else {
@ -121,7 +125,8 @@ fn print_cache_info(
let local_storage_dir = origin_dir.join("local_storage");
if json {
let mut output = json!({
let mut json_output = serde_json::json!({
"version": JSON_SCHEMA_VERSION,
"denoDir": deno_dir,
"modulesCache": modules_cache,
"npmCache": npm_cache,
@ -131,10 +136,10 @@ fn print_cache_info(
});
if location.is_some() {
output["localStorage"] = serde_json::to_value(local_storage_dir)?;
json_output["localStorage"] = serde_json::to_value(local_storage_dir)?;
}
display::write_json_to_stdout(&output)
display::write_json_to_stdout(&json_output)
} else {
println!("{} {}", colors::bold("DENO_DIR location:"), deno_dir);
println!(
@ -440,7 +445,7 @@ impl<'a> GraphDisplayContext<'a> {
}
let root_specifier = self.graph.resolve(&self.graph.roots[0]);
match self.graph.try_get(&root_specifier) {
match self.graph.try_get(root_specifier) {
Ok(Some(root)) => {
let maybe_cache_info = match root {
Module::Js(module) => module.maybe_cache_info.as_ref(),
@ -456,22 +461,6 @@ impl<'a> GraphDisplayContext<'a> {
local.to_string_lossy()
)?;
}
if let Some(emit) = &cache_info.emit {
writeln!(
writer,
"{} {}",
colors::bold("emit:"),
emit.to_string_lossy()
)?;
}
if let Some(map) = &cache_info.map {
writeln!(
writer,
"{} {}",
colors::bold("map:"),
map.to_string_lossy()
)?;
}
}
if let Some(module) = root.js() {
writeln!(writer, "{} {}", colors::bold("type:"), module.media_type)?;
@ -694,9 +683,9 @@ impl<'a> GraphDisplayContext<'a> {
Resolution::Ok(resolved) => {
let specifier = &resolved.specifier;
let resolved_specifier = self.graph.resolve(specifier);
Some(match self.graph.try_get(&resolved_specifier) {
Some(match self.graph.try_get(resolved_specifier) {
Ok(Some(module)) => self.build_module_info(module, type_dep),
Err(err) => self.build_error_info(err, &resolved_specifier),
Err(err) => self.build_error_info(err, resolved_specifier),
Ok(None) => TreeNode::from_text(format!(
"{} {}",
colors::red(specifier),

View file

@ -37,7 +37,7 @@ const routes: Route[] = [
},
{
pattern: new URLPattern({ pathname: "/static/*" }),
handler: (req) => serveDir(req, { urlRoot: "./" }),
handler: (req) => serveDir(req),
},
];
@ -52,7 +52,6 @@ export default {
return handler(req);
},
} satisfies Deno.ServeDefaultExport;
"#,
)?;
create_file(
@ -80,13 +79,23 @@ Deno.test(async function serverFetchUsers() {
});
Deno.test(async function serverFetchStatic() {
const req = new Request("https://deno.land/static/main.ts");
const req = new Request("https://deno.land/static/hello.js");
const res = await server.fetch(req);
assertEquals(res.headers.get("content-type"), "text/plain;charset=UTF-8");
assertEquals(await res.text(), 'console.log("Hello, world!");\n');
assertEquals(res.headers.get("content-type"), "text/javascript; charset=UTF-8");
});
"#,
)?;
let static_dir = dir.join("static");
std::fs::create_dir_all(&static_dir)?;
create_file(
&static_dir,
"hello.js",
r#"console.log("Hello, world!");
"#,
)?;
create_json_file(
&dir,
"deno.json",
@ -203,7 +212,7 @@ Deno.test(function addTest() {
info!(" deno task dev");
info!("");
info!(" {}", colors::gray("# Run the tests"));
info!(" deno -R test");
info!(" deno test -R");
} else if init_flags.lib {
info!(" {}", colors::gray("# Run the tests"));
info!(" deno test");

View file

@ -7,14 +7,17 @@ use crate::args::ConfigFlag;
use crate::args::Flags;
use crate::args::InstallFlags;
use crate::args::InstallFlagsGlobal;
use crate::args::InstallFlagsLocal;
use crate::args::InstallKind;
use crate::args::TypeCheckMode;
use crate::args::UninstallFlags;
use crate::args::UninstallKind;
use crate::factory::CliFactory;
use crate::graph_container::ModuleGraphContainer;
use crate::http_util::HttpClientProvider;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
@ -195,14 +198,15 @@ pub async fn infer_name_from_url(
Some(stem.to_string())
}
pub fn uninstall(uninstall_flags: UninstallFlags) -> Result<(), AnyError> {
if !uninstall_flags.global {
log::warn!("⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.");
}
pub async fn uninstall(
flags: Arc<Flags>,
uninstall_flags: UninstallFlags,
) -> Result<(), AnyError> {
let uninstall_flags = match uninstall_flags.kind {
UninstallKind::Global(flags) => flags,
UninstallKind::Local => unreachable!(),
UninstallKind::Local(remove_flags) => {
return super::registry::remove(flags, remove_flags).await;
}
};
let cwd = std::env::current_dir().context("Unable to get CWD")?;
@ -261,26 +265,65 @@ pub fn uninstall(uninstall_flags: UninstallFlags) -> Result<(), AnyError> {
Ok(())
}
pub(crate) async fn install_from_entrypoints(
flags: Arc<Flags>,
entrypoints: &[String],
) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags.clone());
let emitter = factory.emitter()?;
let main_graph_container = factory.main_module_graph_container().await?;
main_graph_container
.load_and_type_check_files(entrypoints)
.await?;
emitter
.cache_module_emits(&main_graph_container.graph())
.await
}
async fn install_local(
flags: Arc<Flags>,
maybe_add_flags: Option<AddFlags>,
install_flags: InstallFlagsLocal,
) -> Result<(), AnyError> {
if let Some(add_flags) = maybe_add_flags {
return super::registry::add(
flags,
add_flags,
super::registry::AddCommandName::Install,
)
.await;
match install_flags {
InstallFlagsLocal::Add(add_flags) => {
super::registry::add(
flags,
add_flags,
super::registry::AddCommandName::Install,
)
.await
}
InstallFlagsLocal::Entrypoints(entrypoints) => {
install_from_entrypoints(flags, &entrypoints).await
}
InstallFlagsLocal::TopLevel => {
let factory = CliFactory::from_flags(flags);
crate::tools::registry::cache_top_level_deps(&factory, None).await?;
if let Some(lockfile) = factory.cli_options()?.maybe_lockfile() {
lockfile.write_if_changed()?;
}
Ok(())
}
}
}
let factory = CliFactory::from_flags(flags);
crate::tools::registry::cache_top_level_deps(&factory, None).await?;
if let Some(lockfile) = factory.cli_options()?.maybe_lockfile() {
lockfile.write_if_changed()?;
fn check_if_installs_a_single_package_globally(
maybe_add_flags: Option<&AddFlags>,
) -> Result<(), AnyError> {
let Some(add_flags) = maybe_add_flags else {
return Ok(());
};
if add_flags.packages.len() != 1 {
return Ok(());
}
let Ok(url) = Url::parse(&add_flags.packages[0]) else {
return Ok(());
};
if matches!(url.scheme(), "http" | "https") {
bail!("Failed to install \"{}\" specifier. If you are trying to install {} globally, run again with `-g` flag:\n deno install -g {}", url.scheme(), url.as_str(), url.as_str());
}
Ok(())
}
@ -290,14 +333,13 @@ pub async fn install_command(
) -> Result<(), AnyError> {
match install_flags.kind {
InstallKind::Global(global_flags) => {
if !install_flags.global {
log::warn!("⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.");
}
install_global(flags, global_flags).await
}
InstallKind::Local(maybe_add_flags) => {
install_local(flags, maybe_add_flags).await
InstallKind::Local(local_flags) => {
if let InstallFlagsLocal::Add(add_flags) = &local_flags {
check_if_installs_a_single_package_globally(Some(add_flags))?;
}
install_local(flags, local_flags).await
}
}
}
@ -464,10 +506,6 @@ async fn resolve_shim_data(
executable_args.push("--no-npm".to_string());
}
if flags.lock_write {
executable_args.push("--lock-write".to_string());
}
if flags.cached_only {
executable_args.push("--cached-only".to_string());
}
@ -1471,8 +1509,8 @@ mod tests {
assert!(content.contains(&expected_string));
}
#[test]
fn uninstall_basic() {
#[tokio::test]
async fn uninstall_basic() {
let temp_dir = TempDir::new();
let bin_dir = temp_dir.path().join("bin");
std::fs::create_dir(&bin_dir).unwrap();
@ -1499,13 +1537,16 @@ mod tests {
File::create(file_path).unwrap();
}
uninstall(UninstallFlags {
kind: UninstallKind::Global(UninstallFlagsGlobal {
name: "echo_test".to_string(),
root: Some(temp_dir.path().to_string()),
}),
global: false,
})
uninstall(
Default::default(),
UninstallFlags {
kind: UninstallKind::Global(UninstallFlagsGlobal {
name: "echo_test".to_string(),
root: Some(temp_dir.path().to_string()),
}),
},
)
.await
.unwrap();
assert!(!file_path.exists());

View file

@ -45,6 +45,7 @@ use crate::colors;
use crate::factory::CliFactory;
use crate::graph_util::ModuleGraphCreator;
use crate::tools::fmt::run_parallelized;
use crate::util::display;
use crate::util::file_watcher;
use crate::util::fs::canonicalize_path;
use crate::util::path::is_script_ext;
@ -60,6 +61,8 @@ pub use rules::collect_no_slow_type_diagnostics;
pub use rules::ConfiguredRules;
pub use rules::LintRuleProvider;
const JSON_SCHEMA_VERSION: u8 = 1;
static STDIN_FILE_NAME: &str = "$deno$stdin.ts";
pub async fn lint(
@ -440,18 +443,20 @@ pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
.rules;
if json {
let json_rules: Vec<serde_json::Value> = lint_rules
.iter()
.map(|rule| {
serde_json::json!({
"code": rule.code(),
"tags": rule.tags(),
"docs": rule.docs(),
let json_output = serde_json::json!({
"version": JSON_SCHEMA_VERSION,
"rules": lint_rules
.iter()
.map(|rule| {
serde_json::json!({
"code": rule.code(),
"tags": rule.tags(),
"docs": rule.docs(),
})
})
})
.collect();
let json_str = serde_json::to_string_pretty(&json_rules).unwrap();
println!("{json_str}");
.collect::<Vec<serde_json::Value>>(),
});
display::write_json_to_stdout(&json_output).unwrap();
} else {
// The rules should still be printed even if `--quiet` option is enabled,
// so use `println!` here instead of `info!`.

View file

@ -12,6 +12,8 @@ use crate::args::LintReporterKind;
use super::LintError;
const JSON_SCHEMA_VERSION: u8 = 1;
pub fn create_reporter(kind: LintReporterKind) -> Box<dyn LintReporter + Send> {
match kind {
LintReporterKind::Pretty => Box::new(PrettyLintReporter::new()),
@ -170,6 +172,7 @@ struct JsonLintDiagnostic {
#[derive(Serialize)]
struct JsonLintReporter {
version: u8,
diagnostics: Vec<JsonLintDiagnostic>,
errors: Vec<LintError>,
}
@ -177,6 +180,7 @@ struct JsonLintReporter {
impl JsonLintReporter {
fn new() -> JsonLintReporter {
JsonLintReporter {
version: JSON_SCHEMA_VERSION,
diagnostics: Vec::new(),
errors: Vec::new(),
}

View file

@ -1,7 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
pub mod bench;
pub mod bundle;
pub mod check;
pub mod clean;
pub mod compile;
@ -20,4 +19,3 @@ pub mod serve;
pub mod task;
pub mod test;
pub mod upgrade;
pub mod vendor;

View file

@ -1049,7 +1049,8 @@ async fn publish_package(
sha256: faster_hex::hex_string(&sha2::Sha256::digest(&meta_bytes)),
},
};
let bundle = provenance::generate_provenance(http_client, subject).await?;
let bundle =
provenance::generate_provenance(http_client, vec![subject]).await?;
let tlog_entry = &bundle.verification_material.tlog_entries[0];
log::info!("{}",

View file

@ -3,9 +3,10 @@
mod cache_deps;
pub use cache_deps::cache_top_level_deps;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@ -24,9 +25,11 @@ use deno_semver::package::PackageReq;
use indexmap::IndexMap;
use jsonc_parser::ast::ObjectProp;
use jsonc_parser::ast::Value;
use yoke::Yoke;
use crate::args::AddFlags;
use crate::args::CacheSetting;
use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::RemoveFlags;
use crate::factory::CliFactory;
@ -54,115 +57,303 @@ impl DenoConfigFormat {
}
}
struct DenoConfig {
config: Arc<deno_config::deno_json::ConfigFile>,
format: DenoConfigFormat,
imports: IndexMap<String, String>,
}
fn deno_json_imports(
config: &deno_config::deno_json::ConfigFile,
) -> Result<IndexMap<String, String>, AnyError> {
Ok(
config
.json
.imports
.clone()
.map(|imports| {
serde_json::from_value(imports)
.map_err(|err| anyhow!("Malformed \"imports\" configuration: {err}"))
})
.transpose()?
.unwrap_or_default(),
)
}
impl DenoConfig {
fn from_options(options: &CliOptions) -> Result<Option<Self>, AnyError> {
let start_dir = &options.start_dir;
if let Some(config) = start_dir.maybe_deno_json() {
Ok(Some(Self {
imports: deno_json_imports(config)?,
config: config.clone(),
format: DenoConfigFormat::from_specifier(&config.specifier)?,
}))
} else {
Ok(None)
}
}
fn add(&mut self, selected: SelectedPackage) {
self.imports.insert(
selected.import_name,
format!("{}@{}", selected.package_name, selected.version_req),
);
}
fn remove(&mut self, package: &str) -> bool {
self.imports.shift_remove(package).is_some()
}
fn take_import_fields(
&mut self,
) -> Vec<(&'static str, IndexMap<String, String>)> {
vec![("imports", std::mem::take(&mut self.imports))]
}
}
impl NpmConfig {
fn from_options(options: &CliOptions) -> Result<Option<Self>, AnyError> {
let start_dir = &options.start_dir;
if let Some(pkg_json) = start_dir.maybe_pkg_json() {
Ok(Some(Self {
dependencies: pkg_json.dependencies.clone().unwrap_or_default(),
dev_dependencies: pkg_json.dev_dependencies.clone().unwrap_or_default(),
config: pkg_json.clone(),
fmt_options: None,
}))
} else {
Ok(None)
}
}
fn add(&mut self, selected: SelectedPackage, dev: bool) {
let (name, version) = package_json_dependency_entry(selected);
if dev {
self.dev_dependencies.insert(name, version);
} else {
self.dependencies.insert(name, version);
}
}
fn remove(&mut self, package: &str) -> bool {
let in_deps = self.dependencies.shift_remove(package).is_some();
let in_dev_deps = self.dev_dependencies.shift_remove(package).is_some();
in_deps || in_dev_deps
}
fn take_import_fields(
&mut self,
) -> Vec<(&'static str, IndexMap<String, String>)> {
vec![
("dependencies", std::mem::take(&mut self.dependencies)),
(
"devDependencies",
std::mem::take(&mut self.dev_dependencies),
),
]
}
}
struct NpmConfig {
config: Arc<deno_node::PackageJson>,
fmt_options: Option<FmtOptionsConfig>,
dependencies: IndexMap<String, String>,
dev_dependencies: IndexMap<String, String>,
}
enum DenoOrPackageJson {
Deno(Arc<deno_config::deno_json::ConfigFile>, DenoConfigFormat),
Npm(Arc<deno_node::PackageJson>, Option<FmtOptionsConfig>),
Deno(DenoConfig),
Npm(NpmConfig),
}
impl From<DenoConfig> for DenoOrPackageJson {
fn from(config: DenoConfig) -> Self {
Self::Deno(config)
}
}
impl From<NpmConfig> for DenoOrPackageJson {
fn from(config: NpmConfig) -> Self {
Self::Npm(config)
}
}
/// Wrapper around `jsonc_parser::ast::Object` that can be stored in a `Yoke`
#[derive(yoke::Yokeable)]
struct JsoncObjectView<'a>(jsonc_parser::ast::Object<'a>);
struct ConfigUpdater {
config: DenoOrPackageJson,
// the `Yoke` is so we can carry the parsed object (which borrows from
// the source) along with the source itself
ast: Yoke<JsoncObjectView<'static>, String>,
path: PathBuf,
modified: bool,
}
impl ConfigUpdater {
fn obj(&self) -> &jsonc_parser::ast::Object<'_> {
&self.ast.get().0
}
fn contents(&self) -> &str {
self.ast.backing_cart()
}
async fn maybe_new(
config: Option<impl Into<DenoOrPackageJson>>,
) -> Result<Option<Self>, AnyError> {
if let Some(config) = config {
Ok(Some(Self::new(config.into()).await?))
} else {
Ok(None)
}
}
async fn new(config: DenoOrPackageJson) -> Result<Self, AnyError> {
let specifier = config.specifier();
if specifier.scheme() != "file" {
bail!("Can't update a remote configuration file");
}
let config_file_path = specifier.to_file_path().map_err(|_| {
anyhow!("Specifier {specifier:?} is an invalid file path")
})?;
let config_file_contents = {
let contents = tokio::fs::read_to_string(&config_file_path)
.await
.with_context(|| {
format!("Reading config file at: {}", config_file_path.display())
})?;
if contents.trim().is_empty() {
"{}\n".into()
} else {
contents
}
};
let ast = Yoke::try_attach_to_cart(config_file_contents, |contents| {
let ast = jsonc_parser::parse_to_ast(
contents,
&Default::default(),
&Default::default(),
)
.with_context(|| {
format!("Failed to parse config file at {}", specifier)
})?;
let obj = match ast.value {
Some(Value::Object(obj)) => obj,
_ => bail!(
"Failed to update config file at {}, expected an object",
specifier
),
};
Ok(JsoncObjectView(obj))
})?;
Ok(Self {
config,
ast,
path: config_file_path,
modified: false,
})
}
fn add(&mut self, selected: SelectedPackage, dev: bool) {
match &mut self.config {
DenoOrPackageJson::Deno(deno) => deno.add(selected),
DenoOrPackageJson::Npm(npm) => npm.add(selected, dev),
}
self.modified = true;
}
fn remove(&mut self, package: &str) -> bool {
let removed = match &mut self.config {
DenoOrPackageJson::Deno(deno) => deno.remove(package),
DenoOrPackageJson::Npm(npm) => npm.remove(package),
};
if removed {
self.modified = true;
}
removed
}
async fn commit(mut self) -> Result<(), AnyError> {
if !self.modified {
return Ok(());
}
let import_fields = self.config.take_import_fields();
let fmt_config_options = self.config.fmt_options();
let new_text = update_config_file_content(
self.obj(),
self.contents(),
fmt_config_options,
import_fields.into_iter().map(|(k, v)| {
(
k,
if v.is_empty() {
None
} else {
Some(generate_imports(v.into_iter().collect()))
},
)
}),
self.config.file_name(),
);
tokio::fs::write(&self.path, new_text).await?;
Ok(())
}
}
impl DenoOrPackageJson {
fn specifier(&self) -> Cow<ModuleSpecifier> {
match self {
Self::Deno(d, ..) => Cow::Borrowed(&d.specifier),
Self::Npm(n, ..) => Cow::Owned(n.specifier()),
}
}
/// Returns the existing imports/dependencies from the config.
fn existing_imports(&self) -> Result<IndexMap<String, String>, AnyError> {
match self {
DenoOrPackageJson::Deno(deno, ..) => {
if let Some(imports) = deno.json.imports.clone() {
match serde_json::from_value(imports) {
Ok(map) => Ok(map),
Err(err) => {
bail!("Malformed \"imports\" configuration: {err}")
}
}
} else {
Ok(Default::default())
}
}
DenoOrPackageJson::Npm(npm, ..) => {
Ok(npm.dependencies.clone().unwrap_or_default())
}
Self::Deno(d, ..) => Cow::Borrowed(&d.config.specifier),
Self::Npm(n, ..) => Cow::Owned(n.config.specifier()),
}
}
fn fmt_options(&self) -> FmtOptionsConfig {
match self {
DenoOrPackageJson::Deno(deno, ..) => deno
.config
.to_fmt_config()
.ok()
.map(|f| f.options)
.unwrap_or_default(),
DenoOrPackageJson::Npm(_, config) => config.clone().unwrap_or_default(),
DenoOrPackageJson::Npm(config) => {
config.fmt_options.clone().unwrap_or_default()
}
}
}
fn imports_key(&self) -> &'static str {
fn take_import_fields(
&mut self,
) -> Vec<(&'static str, IndexMap<String, String>)> {
match self {
DenoOrPackageJson::Deno(..) => "imports",
DenoOrPackageJson::Npm(..) => "dependencies",
Self::Deno(d) => d.take_import_fields(),
Self::Npm(n) => n.take_import_fields(),
}
}
fn file_name(&self) -> &'static str {
match self {
DenoOrPackageJson::Deno(_, format) => match format {
DenoOrPackageJson::Deno(config) => match config.format {
DenoConfigFormat::Json => "deno.json",
DenoConfigFormat::Jsonc => "deno.jsonc",
},
DenoOrPackageJson::Npm(..) => "package.json",
}
}
}
fn is_npm(&self) -> bool {
matches!(self, Self::Npm(..))
}
/// Get the preferred config file to operate on
/// given the flags. If no config file is present,
/// creates a `deno.json` file - in this case
/// we also return a new `CliFactory` that knows about
/// the new config
fn from_flags(flags: Arc<Flags>) -> Result<(Self, CliFactory), AnyError> {
let factory = CliFactory::from_flags(flags.clone());
let options = factory.cli_options()?;
let start_dir = &options.start_dir;
match (start_dir.maybe_deno_json(), start_dir.maybe_pkg_json()) {
// when both are present, for now,
// default to deno.json
(Some(deno), Some(_) | None) => Ok((
DenoOrPackageJson::Deno(
deno.clone(),
DenoConfigFormat::from_specifier(&deno.specifier)?,
),
factory,
)),
(None, Some(package_json)) if options.enable_future_features() => {
Ok((DenoOrPackageJson::Npm(package_json.clone(), None), factory))
}
(None, Some(_) | None) => {
std::fs::write(options.initial_cwd().join("deno.json"), "{}\n")
.context("Failed to create deno.json file")?;
drop(factory); // drop to prevent use
log::info!("Created deno.json configuration file.");
let factory = CliFactory::from_flags(flags.clone());
let options = factory.cli_options()?.clone();
let start_dir = &options.start_dir;
Ok((
DenoOrPackageJson::Deno(
start_dir.maybe_deno_json().cloned().ok_or_else(|| {
anyhow!("config not found, but it was just created")
})?,
DenoConfigFormat::Json,
),
factory,
))
}
}
}
fn create_deno_json(
flags: &Arc<Flags>,
options: &CliOptions,
) -> Result<CliFactory, AnyError> {
std::fs::write(options.initial_cwd().join("deno.json"), "{}\n")
.context("Failed to create deno.json file")?;
log::info!("Created deno.json configuration file.");
let factory = CliFactory::from_flags(flags.clone());
Ok(factory)
}
fn package_json_dependency_entry(
@ -197,19 +388,53 @@ impl std::fmt::Display for AddCommandName {
}
}
fn load_configs(
flags: &Arc<Flags>,
) -> Result<(CliFactory, Option<NpmConfig>, Option<DenoConfig>), AnyError> {
let cli_factory = CliFactory::from_flags(flags.clone());
let options = cli_factory.cli_options()?;
let npm_config = NpmConfig::from_options(options)?;
let (cli_factory, deno_config) = match DenoConfig::from_options(options)? {
Some(config) => (cli_factory, Some(config)),
None if npm_config.is_some() => (cli_factory, None),
None => {
let factory = create_deno_json(flags, options)?;
let options = factory.cli_options()?.clone();
(
factory,
Some(
DenoConfig::from_options(&options)?.expect("Just created deno.json"),
),
)
}
};
assert!(deno_config.is_some() || npm_config.is_some());
Ok((cli_factory, npm_config, deno_config))
}
pub async fn add(
flags: Arc<Flags>,
add_flags: AddFlags,
cmd_name: AddCommandName,
) -> Result<(), AnyError> {
let (config_file, cli_factory) =
DenoOrPackageJson::from_flags(flags.clone())?;
let (cli_factory, npm_config, deno_config) = load_configs(&flags)?;
let mut npm_config = ConfigUpdater::maybe_new(npm_config).await?;
let mut deno_config = ConfigUpdater::maybe_new(deno_config).await?;
let config_specifier = config_file.specifier();
if config_specifier.scheme() != "file" {
bail!("Can't add dependencies to a remote configuration file");
if let Some(deno) = &deno_config {
let specifier = deno.config.specifier();
if deno.obj().get_string("importMap").is_some() {
bail!(
concat!(
"`deno {}` is not supported when configuration file contains an \"importMap\" field. ",
"Inline the import map into the Deno configuration file.\n",
" at {}",
),
cmd_name,
specifier
);
}
}
let config_file_path = config_specifier.to_file_path().unwrap();
let http_client = cli_factory.http_client_provider();
@ -277,39 +502,7 @@ pub async fn add(
}
}
let config_file_contents = {
let contents = tokio::fs::read_to_string(&config_file_path).await.unwrap();
if contents.trim().is_empty() {
"{}\n".into()
} else {
contents
}
};
let ast = jsonc_parser::parse_to_ast(
&config_file_contents,
&Default::default(),
&Default::default(),
)?;
let obj = match ast.value {
Some(Value::Object(obj)) => obj,
_ => bail!("Failed updating config file due to no object."),
};
if obj.get_string("importMap").is_some() {
bail!(
concat!(
"`deno add` is not supported when configuration file contains an \"importMap\" field. ",
"Inline the import map into the Deno configuration file.\n",
" at {}",
),
config_specifier
);
}
let mut existing_imports = config_file.existing_imports()?;
let is_npm = config_file.is_npm();
let dev = add_flags.dev;
for selected_package in selected_packages {
log::info!(
"Add {}{}{}",
@ -318,39 +511,32 @@ pub async fn add(
selected_package.selected_version
);
if is_npm {
let (name, version) = package_json_dependency_entry(selected_package);
existing_imports.insert(name, version)
if selected_package.package_name.starts_with("npm:") {
if let Some(npm) = &mut npm_config {
npm.add(selected_package, dev);
} else {
deno_config.as_mut().unwrap().add(selected_package, dev);
}
} else if let Some(deno) = &mut deno_config {
deno.add(selected_package, dev);
} else {
existing_imports.insert(
selected_package.import_name,
format!(
"{}@{}",
selected_package.package_name, selected_package.version_req
),
)
};
npm_config.as_mut().unwrap().add(selected_package, dev);
}
}
let mut import_list: Vec<(String, String)> =
existing_imports.into_iter().collect();
import_list.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
let generated_imports = generate_imports(import_list);
let mut commit_futures = vec![];
if let Some(npm) = npm_config {
commit_futures.push(npm.commit());
}
if let Some(deno) = deno_config {
commit_futures.push(deno.commit());
}
let commit_futures =
deno_core::futures::future::join_all(commit_futures).await;
let fmt_config_options = config_file.fmt_options();
let new_text = update_config_file_content(
obj,
&config_file_contents,
generated_imports,
fmt_config_options,
config_file.imports_key(),
config_file.file_name(),
);
tokio::fs::write(&config_file_path, new_text)
.await
.context("Failed to update configuration file")?;
for result in commit_futures {
result.context("Failed to update configuration file")?;
}
// clear the previously cached package.json from memory before reloading it
node_resolver::PackageJsonThreadLocalCache::clear();
@ -501,14 +687,18 @@ impl AddPackageReq {
match prefix {
Prefix::Jsr => {
let package_req = PackageReq::from_str(entry_text)?;
let req_ref =
JsrPackageReqReference::from_str(&format!("jsr:{}", entry_text))?;
let package_req = req_ref.into_inner().req;
Ok(AddPackageReq {
alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()),
value: AddPackageReqValue::Jsr(package_req),
})
}
Prefix::Npm => {
let package_req = PackageReq::from_str(entry_text)?;
let req_ref =
NpmPackageReqReference::from_str(&format!("npm:{}", entry_text))?;
let package_req = req_ref.into_inner().req;
Ok(AddPackageReq {
alias: maybe_alias.unwrap_or_else(|| package_req.name.to_string()),
value: AddPackageReqValue::Npm(package_req),
@ -518,7 +708,8 @@ impl AddPackageReq {
}
}
fn generate_imports(packages_to_version: Vec<(String, String)>) -> String {
fn generate_imports(mut packages_to_version: Vec<(String, String)>) -> String {
packages_to_version.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
let mut contents = vec![];
let len = packages_to_version.len();
for (index, (package, version)) in packages_to_version.iter().enumerate() {
@ -531,68 +722,27 @@ fn generate_imports(packages_to_version: Vec<(String, String)>) -> String {
contents.join("\n")
}
fn remove_from_config(
config_path: &Path,
keys: &[&'static str],
packages_to_remove: &[String],
removed_packages: &mut Vec<String>,
fmt_options: &FmtOptionsConfig,
) -> Result<(), AnyError> {
let mut json: serde_json::Value =
serde_json::from_slice(&std::fs::read(config_path)?)?;
for key in keys {
let Some(obj) = json.get_mut(*key).and_then(|v| v.as_object_mut()) else {
continue;
};
for package in packages_to_remove {
if obj.shift_remove(package).is_some() {
removed_packages.push(package.clone());
}
}
}
let config = serde_json::to_string_pretty(&json)?;
let config =
crate::tools::fmt::format_json(config_path, &config, fmt_options)
.ok()
.flatten()
.unwrap_or(config);
std::fs::write(config_path, config)
.context("Failed to update configuration file")?;
Ok(())
}
pub async fn remove(
flags: Arc<Flags>,
remove_flags: RemoveFlags,
) -> Result<(), AnyError> {
let (config_file, factory) = DenoOrPackageJson::from_flags(flags.clone())?;
let options = factory.cli_options()?;
let start_dir = &options.start_dir;
let fmt_config_options = config_file.fmt_options();
let (_, npm_config, deno_config) = load_configs(&flags)?;
let mut removed_packages = Vec::new();
let mut configs = [
ConfigUpdater::maybe_new(npm_config).await?,
ConfigUpdater::maybe_new(deno_config).await?,
];
if let Some(deno_json) = start_dir.maybe_deno_json() {
remove_from_config(
&deno_json.specifier.to_file_path().unwrap(),
&["imports"],
&remove_flags.packages,
&mut removed_packages,
&fmt_config_options,
)?;
}
let mut removed_packages = vec![];
if let Some(pkg_json) = start_dir.maybe_pkg_json() {
remove_from_config(
&pkg_json.path,
&["dependencies", "devDependencies"],
&remove_flags.packages,
&mut removed_packages,
&fmt_config_options,
)?;
for package in &remove_flags.packages {
let mut removed = false;
for config in configs.iter_mut().flatten() {
removed |= config.remove(package);
}
if removed {
removed_packages.push(package.clone());
}
}
if removed_packages.is_empty() {
@ -601,6 +751,10 @@ pub async fn remove(
for package in &removed_packages {
log::info!("Removed {}", crate::colors::green(package));
}
for config in configs.into_iter().flatten() {
config.commit().await?;
}
// Update deno.lock
node_resolver::PackageJsonThreadLocalCache::clear();
let cli_factory = CliFactory::from_flags(flags);
@ -610,41 +764,72 @@ pub async fn remove(
Ok(())
}
fn update_config_file_content(
obj: jsonc_parser::ast::Object,
fn update_config_file_content<
I: IntoIterator<Item = (&'static str, Option<String>)>,
>(
obj: &jsonc_parser::ast::Object,
config_file_contents: &str,
generated_imports: String,
fmt_options: FmtOptionsConfig,
imports_key: &str,
entries: I,
file_name: &str,
) -> String {
let mut text_changes = vec![];
for (key, value) in entries {
match obj.properties.iter().enumerate().find_map(|(idx, k)| {
if k.name.as_str() == key {
Some((idx, k))
} else {
None
}
}) {
Some((
idx,
ObjectProp {
value: Value::Object(lit),
range,
..
},
)) => {
if let Some(value) = value {
text_changes.push(TextChange {
range: (lit.range.start + 1)..(lit.range.end - 1),
new_text: value,
})
} else {
text_changes.push(TextChange {
// remove field entirely, making sure to
// remove the comma if it's not the last field
range: range.start..(if idx == obj.properties.len() - 1 {
range.end
} else {
obj.properties[idx + 1].range.start
}),
new_text: "".to_string(),
})
}
}
match obj.get(imports_key) {
Some(ObjectProp {
value: Value::Object(lit),
..
}) => text_changes.push(TextChange {
range: (lit.range.start + 1)..(lit.range.end - 1),
new_text: generated_imports,
}),
None => {
let insert_position = obj.range.end - 1;
text_changes.push(TextChange {
range: insert_position..insert_position,
// NOTE(bartlomieju): adding `\n` here to force the formatter to always
// produce a config file that is multiline, like so:
// ```
// {
// "imports": {
// "<package_name>": "<registry>:<package_name>@<semver>"
// }
// }
new_text: format!("\"{imports_key}\": {{\n {generated_imports} }}"),
})
// need to add field
None => {
if let Some(value) = value {
let insert_position = obj.range.end - 1;
text_changes.push(TextChange {
range: insert_position..insert_position,
// NOTE(bartlomieju): adding `\n` here to force the formatter to always
// produce a config file that is multiline, like so:
// ```
// {
// "imports": {
// "<package_name>": "<registry>:<package_name>@<semver>"
// }
// }
new_text: format!("\"{key}\": {{\n {value} }}"),
})
}
}
// we verified the shape of `imports`/`dependencies` above
Some(_) => unreachable!(),
}
// we verified the shape of `imports`/`dependencies` above
Some(_) => unreachable!(),
}
let new_text =

View file

@ -229,16 +229,16 @@ impl Predicate {
struct ProvenanceAttestation {
#[serde(rename = "type")]
_type: &'static str,
subject: Subject,
subject: Vec<Subject>,
predicate_type: &'static str,
predicate: Predicate,
}
impl ProvenanceAttestation {
pub fn new_github_actions(subject: Subject) -> Self {
pub fn new_github_actions(subjects: Vec<Subject>) -> Self {
Self {
_type: INTOTO_STATEMENT_TYPE,
subject,
subject: subjects,
predicate_type: SLSA_PREDICATE_TYPE,
predicate: Predicate::new_github_actions(),
}
@ -296,7 +296,7 @@ pub struct ProvenanceBundle {
pub async fn generate_provenance(
http_client: &HttpClient,
subject: Subject,
subjects: Vec<Subject>,
) -> Result<ProvenanceBundle, AnyError> {
if !is_gha() {
bail!("Automatic provenance is only available in GitHub Actions");
@ -308,7 +308,7 @@ pub async fn generate_provenance(
);
};
let slsa = ProvenanceAttestation::new_github_actions(subject);
let slsa = ProvenanceAttestation::new_github_actions(subjects);
let attestation = serde_json::to_string(&slsa)?;
let bundle = attest(http_client, &attestation, INTOTO_PAYLOAD_TYPE).await?;
@ -738,8 +738,13 @@ mod tests {
sha256: "yourmom".to_string(),
},
};
let slsa = ProvenanceAttestation::new_github_actions(subject);
assert_eq!(slsa.subject.name, "jsr:@divy/sdl2@0.0.1");
assert_eq!(slsa.subject.digest.sha256, "yourmom");
let slsa = ProvenanceAttestation::new_github_actions(vec![subject]);
assert_eq!(
slsa.subject.len(),
1,
"Subject should be an array per the in-toto specification"
);
assert_eq!(slsa.subject[0].name, "jsr:@divy/sdl2@0.0.1");
assert_eq!(slsa.subject[0].digest.sha256, "yourmom");
}
}

View file

@ -3,6 +3,7 @@
use std::io::Read;
use std::sync::Arc;
use deno_config::deno_json::NodeModulesDirMode;
use deno_core::error::AnyError;
use deno_runtime::deno_permissions::Permissions;
use deno_runtime::deno_permissions::PermissionsContainer;
@ -194,7 +195,9 @@ pub async fn eval_command(
pub async fn maybe_npm_install(factory: &CliFactory) -> Result<(), AnyError> {
// ensure an "npm install" is done if the user has explicitly
// opted into using a managed node_modules directory
if factory.cli_options()?.node_modules_dir_enablement() == Some(true) {
if factory.cli_options()?.node_modules_dir()?
== Some(NodeModulesDirMode::Auto)
{
if let Some(npm_resolver) = factory.npm_resolver().await?.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
}

View file

@ -1,13 +1,13 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::TaskFlags;
use crate::colors;
use crate::factory::CliFactory;
use crate::npm::CliNpmResolver;
use crate::task_runner;
use crate::util::fs::canonicalize_path;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use deno_config::deno_json::Task;
use deno_config::workspace::TaskOrScript;
use deno_config::workspace::WorkspaceDirectory;
@ -18,13 +18,15 @@ use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::normalize_path;
use deno_task_shell::ShellCommand;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::TaskFlags;
use crate::colors;
use crate::factory::CliFactory;
use crate::npm::CliNpmResolver;
use crate::task_runner;
use crate::util::fs::canonicalize_path;
pub async fn execute_script(
flags: Arc<Flags>,
@ -106,12 +108,9 @@ See https://docs.deno.com/go/config"#
.await
}
TaskOrScript::Script(scripts, _script) => {
// ensure the npm packages are installed if using a node_modules
// directory and managed resolver
if cli_options.has_node_modules_dir() {
if let Some(npm_resolver) = npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
}
// ensure the npm packages are installed if using a managed resolver
if let Some(npm_resolver) = npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
}
let cwd = match task_flags.cwd {

View file

@ -319,14 +319,11 @@ pub const OP_DETAILS: phf::Map<&'static str, [&'static str; 2]> = phf_map! {
"op_fs_copy_file_async" => ["copy a file", "awaiting the result of a `Deno.copyFile` call"],
"op_fs_events_poll" => ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"],
"op_fs_fdatasync_async" => ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` or `Deno.FsFile.syncData` call"],
"op_fs_file_stat_async" => ["get file metadata", "awaiting the result of a `Deno.fstat` or `Deno.FsFile.stat` call"],
"op_fs_flock_async_unstable" => ["lock a file", "awaiting the result of a `Deno.flock` call"],
"op_fs_file_stat_async" => ["get file metadata", "awaiting the result of a `Deno.FsFile.prototype.stat` call"],
"op_fs_flock_async" => ["lock a file", "awaiting the result of a `Deno.FsFile.lock` call"],
"op_fs_fsync_async" => ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` or `Deno.FsFile.sync` call"],
"op_fs_ftruncate_async" => ["truncate a file", "awaiting the result of a `Deno.ftruncate` or `Deno.FsFile.truncate` call"],
"op_fs_funlock_async_unstable" => ["unlock a file", "awaiting the result of a `Deno.funlock` call"],
"op_fs_file_truncate_async" => ["truncate a file", "awaiting the result of a `Deno.FsFile.prototype.truncate` call"],
"op_fs_funlock_async" => ["unlock a file", "awaiting the result of a `Deno.FsFile.unlock` call"],
"op_fs_futime_async" => ["change file timestamps", "awaiting the result of a `Deno.futime` or `Deno.FsFile.utime` call"],
"op_fs_link_async" => ["create a hard link", "awaiting the result of a `Deno.link` call"],
"op_fs_lstat_async" => ["get file metadata", "awaiting the result of a `Deno.lstat` call"],
"op_fs_make_temp_dir_async" => ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"],
@ -340,7 +337,7 @@ pub const OP_DETAILS: phf::Map<&'static str, [&'static str; 2]> = phf_map! {
"op_fs_realpath_async" => ["resolve a path", "awaiting the result of a `Deno.realpath` call"],
"op_fs_remove_async" => ["remove a file or directory", "awaiting the result of a `Deno.remove` call"],
"op_fs_rename_async" => ["rename a file or directory", "awaiting the result of a `Deno.rename` call"],
"op_fs_seek_async" => ["seek in a file", "awaiting the result of a `Deno.seek` or `Deno.FsFile.seek` call"],
"op_fs_seek_async" => ["seek in a file", "awaiting the result of a `Deno.FsFile.prototype.seek` call"],
"op_fs_stat_async" => ["get file metadata", "awaiting the result of a `Deno.stat` call"],
"op_fs_symlink_async" => ["create a symlink", "awaiting the result of a `Deno.symlink` call"],
"op_fs_truncate_async" => ["truncate a file", "awaiting the result of a `Deno.truncate` call"],

View file

@ -1778,7 +1778,8 @@ pub async fn run_tests(
)
.await?;
if !workspace_test_options.allow_none && specifiers_with_mode.is_empty() {
if !workspace_test_options.permit_no_files && specifiers_with_mode.is_empty()
{
return Err(generic_error("No test modules found"));
}

View file

@ -15,6 +15,7 @@ use crate::util::progress_bar::ProgressBarStyle;
use crate::version;
use async_trait::async_trait;
use color_print::cstr;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
@ -37,6 +38,8 @@ const RELEASE_URL: &str = "https://github.com/denoland/deno/releases";
const CANARY_URL: &str = "https://dl.deno.land/canary";
const RC_URL: &str = "https://dl.deno.land/release";
static EXAMPLE_USAGE: &str = cstr!("Example usage:\n <p(245)>deno upgrade | deno upgrade 1.46 | deno upgrade canary</>");
pub static ARCHIVE_NAME: Lazy<String> =
Lazy::new(|| format!("deno-{}.zip", env!("TARGET")));
@ -226,15 +229,70 @@ impl<
}
}
fn get_minor_version(version: &str) -> &str {
version.rsplitn(2, '.').collect::<Vec<&str>>()[1]
fn get_minor_version_blog_post_url(semver: &Version) -> String {
format!("https://deno.com/blog/v{}.{}", semver.major, semver.minor)
}
fn print_release_notes(current_version: &str, new_version: &str) {
// TODO(bartlomieju): we might want to reconsider this one for RC releases.
// TODO(bartlomieju): also maybe just parse using `Version::standard` instead
// of using `get_minor_version`?
if get_minor_version(current_version) == get_minor_version(new_version) {
fn get_rc_version_blog_post_url(semver: &Version) -> String {
format!(
"https://deno.com/blog/v{}.{}-rc-{}",
semver.major, semver.minor, semver.pre[1]
)
}
async fn print_release_notes(
current_version: &str,
new_version: &str,
client: &HttpClient,
) {
let Ok(current_semver) = Version::parse_standard(current_version) else {
return;
};
let Ok(new_semver) = Version::parse_standard(new_version) else {
return;
};
let is_switching_from_deno1_to_deno2 =
new_semver.major == 2 && current_semver.major == 1;
let is_deno_2_rc = new_semver.major == 2
&& new_semver.minor == 0
&& new_semver.patch == 0
&& new_semver.pre.first() == Some(&"rc".to_string());
if is_deno_2_rc || is_switching_from_deno1_to_deno2 {
log::info!(
"{}\n\n {}\n",
colors::gray("Migration guide:"),
colors::bold(
"https://docs.deno.com/runtime/manual/advanced/migrate_deprecations"
)
);
}
if is_deno_2_rc {
log::info!(
"{}\n\n {}\n",
colors::gray("If you find a bug, please report to:"),
colors::bold("https://github.com/denoland/deno/issues/new")
);
// Check if there's blog post entry for this release
let blog_url_str = get_rc_version_blog_post_url(&new_semver);
let blog_url = Url::parse(&blog_url_str).unwrap();
if client.download(blog_url).await.is_ok() {
log::info!(
"{}\n\n {}\n",
colors::gray("Blog post:"),
colors::bold(blog_url_str)
);
}
return;
}
let should_print = current_semver.major != new_semver.major
|| current_semver.minor != new_semver.minor;
if !should_print {
return;
}
@ -249,10 +307,7 @@ fn print_release_notes(current_version: &str, new_version: &str) {
log::info!(
"{}\n\n {}\n",
colors::gray("Blog post:"),
colors::bold(format!(
"https://deno.com/blog/v{}",
get_minor_version(new_version)
))
colors::bold(get_minor_version_blog_post_url(&new_semver))
);
}
@ -320,14 +375,14 @@ pub fn check_for_upgrades(
log::info!(
"{} {}",
colors::green("A new canary release of Deno is available."),
colors::italic_gray("Run `deno upgrade --canary` to install it.")
colors::italic_gray("Run `deno upgrade canary` to install it.")
);
}
ReleaseChannel::Rc => {
log::info!(
"{} {}",
colors::green("A new release candidate of Deno is available."),
colors::italic_gray("Run `deno upgrade --rc` to install it.")
colors::italic_gray("Run `deno upgrade rc` to install it.")
);
}
// TODO(bartlomieju)
@ -512,7 +567,9 @@ pub async fn upgrade(
print_release_notes(
version::DENO_VERSION_INFO.deno,
&selected_version_to_upgrade.version_or_hash,
);
&client,
)
.await;
}
drop(temp_dir);
return Ok(());
@ -540,7 +597,9 @@ pub async fn upgrade(
print_release_notes(
version::DENO_VERSION_INFO.deno,
&selected_version_to_upgrade.version_or_hash,
);
&client,
)
.await;
}
drop(temp_dir); // delete the temp dir
@ -591,12 +650,20 @@ impl RequestedVersion {
let (channel, passed_version) = if is_canary {
if !re_hash.is_match(&passed_version) {
bail!("Invalid commit hash passed");
bail!(
"Invalid commit hash passed ({})\n\n{}",
colors::gray(passed_version),
EXAMPLE_USAGE
);
}
(ReleaseChannel::Canary, passed_version)
} else {
let Ok(semver) = Version::parse_standard(&passed_version) else {
bail!("Invalid version passed");
bail!(
"Invalid version passed ({})\n\n{}",
colors::gray(passed_version),
EXAMPLE_USAGE
);
};
if semver.pre.contains(&"rc".to_string()) {
@ -1692,4 +1759,31 @@ mod test {
);
}
}
#[test]
fn blog_post_links() {
let version = Version::parse_standard("1.46.0").unwrap();
assert_eq!(
get_minor_version_blog_post_url(&version),
"https://deno.com/blog/v1.46"
);
let version = Version::parse_standard("2.1.1").unwrap();
assert_eq!(
get_minor_version_blog_post_url(&version),
"https://deno.com/blog/v2.1"
);
let version = Version::parse_standard("2.0.0-rc.0").unwrap();
assert_eq!(
get_rc_version_blog_post_url(&version),
"https://deno.com/blog/v2.0-rc-0"
);
let version = Version::parse_standard("2.0.0-rc.2").unwrap();
assert_eq!(
get_rc_version_blog_post_url(&version),
"https://deno.com/blog/v2.0-rc-2"
);
}
}

View file

@ -1,113 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_ast::swc::ast::ExportDefaultDecl;
use deno_ast::swc::ast::ExportSpecifier;
use deno_ast::swc::ast::ModuleExportName;
use deno_ast::swc::ast::NamedExport;
use deno_ast::swc::ast::Program;
use deno_ast::swc::visit::noop_visit_type;
use deno_ast::swc::visit::Visit;
use deno_ast::swc::visit::VisitWith;
use deno_ast::ParsedSource;
/// Gets if the parsed source has a default export.
pub fn has_default_export(source: &ParsedSource) -> bool {
let mut visitor = DefaultExportFinder {
has_default_export: false,
};
let program = source.program();
let program: &Program = &program;
program.visit_with(&mut visitor);
visitor.has_default_export
}
struct DefaultExportFinder {
has_default_export: bool,
}
impl Visit for DefaultExportFinder {
noop_visit_type!();
fn visit_export_default_decl(&mut self, _: &ExportDefaultDecl) {
self.has_default_export = true;
}
fn visit_named_export(&mut self, named_export: &NamedExport) {
if named_export
.specifiers
.iter()
.any(export_specifier_has_default)
{
self.has_default_export = true;
}
}
}
fn export_specifier_has_default(s: &ExportSpecifier) -> bool {
match s {
ExportSpecifier::Default(_) => true,
ExportSpecifier::Namespace(_) => false,
ExportSpecifier::Named(named) => {
let export_name = named.exported.as_ref().unwrap_or(&named.orig);
match export_name {
ModuleExportName::Str(_) => false,
ModuleExportName::Ident(ident) => &*ident.sym == "default",
}
}
}
}
#[cfg(test)]
mod test {
use deno_ast::MediaType;
use deno_ast::ModuleSpecifier;
use deno_ast::ParseParams;
use deno_ast::ParsedSource;
use super::has_default_export;
#[test]
fn has_default_when_export_default_decl() {
let parsed_source = parse_module("export default class Class {}");
assert!(has_default_export(&parsed_source));
}
#[test]
fn has_default_when_named_export() {
let parsed_source = parse_module("export {default} from './test.ts';");
assert!(has_default_export(&parsed_source));
}
#[test]
fn has_default_when_named_export_alias() {
let parsed_source =
parse_module("export {test as default} from './test.ts';");
assert!(has_default_export(&parsed_source));
}
#[test]
fn not_has_default_when_named_export_not_exported() {
let parsed_source =
parse_module("export {default as test} from './test.ts';");
assert!(!has_default_export(&parsed_source));
}
#[test]
fn not_has_default_when_not() {
let parsed_source = parse_module("export {test} from './test.ts'; export class Test{} export * from './test';");
assert!(!has_default_export(&parsed_source));
}
fn parse_module(text: &str) -> ParsedSource {
deno_ast::parse_module(ParseParams {
specifier: ModuleSpecifier::parse("file:///mod.ts").unwrap(),
capture_tokens: false,
maybe_syntax: None,
media_type: MediaType::TypeScript,
scope_analysis: false,
text: text.into(),
})
.unwrap()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,508 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_ast::LineAndColumnIndex;
use deno_ast::ModuleSpecifier;
use deno_ast::SourceTextInfo;
use deno_core::error::AnyError;
use deno_graph::source::ResolutionMode;
use deno_graph::Module;
use deno_graph::ModuleGraph;
use deno_graph::Position;
use deno_graph::Range;
use deno_graph::Resolution;
use import_map::ImportMap;
use import_map::SpecifierMap;
use indexmap::IndexMap;
use log::warn;
use crate::args::JsxImportSourceConfig;
use crate::cache::ParsedSourceCache;
use super::mappings::Mappings;
use super::specifiers::is_remote_specifier;
use super::specifiers::is_remote_specifier_text;
struct ImportMapBuilder<'a> {
base_dir: &'a ModuleSpecifier,
mappings: &'a Mappings,
imports: ImportsBuilder<'a>,
scopes: IndexMap<String, ImportsBuilder<'a>>,
}
impl<'a> ImportMapBuilder<'a> {
pub fn new(base_dir: &'a ModuleSpecifier, mappings: &'a Mappings) -> Self {
ImportMapBuilder {
base_dir,
mappings,
imports: ImportsBuilder::new(base_dir, mappings),
scopes: Default::default(),
}
}
pub fn base_dir(&self) -> &ModuleSpecifier {
self.base_dir
}
pub fn scope(
&mut self,
base_specifier: &ModuleSpecifier,
) -> &mut ImportsBuilder<'a> {
self
.scopes
.entry(
self
.mappings
.relative_specifier_text(self.base_dir, base_specifier),
)
.or_insert_with(|| ImportsBuilder::new(self.base_dir, self.mappings))
}
pub fn into_import_map(
self,
maybe_original_import_map: Option<&ImportMap>,
) -> ImportMap {
fn get_local_imports(
new_relative_path: &str,
original_imports: &SpecifierMap,
) -> Vec<(String, String)> {
let mut result = Vec::new();
for entry in original_imports.entries() {
if let Some(raw_value) = entry.raw_value {
if raw_value.starts_with("./") || raw_value.starts_with("../") {
let sub_index = raw_value.find('/').unwrap() + 1;
result.push((
entry.raw_key.to_string(),
format!("{}{}", new_relative_path, &raw_value[sub_index..]),
));
}
}
}
result
}
fn add_local_imports<'a>(
new_relative_path: &str,
original_imports: &SpecifierMap,
get_new_imports: impl FnOnce() -> &'a mut SpecifierMap,
) {
let local_imports =
get_local_imports(new_relative_path, original_imports);
if !local_imports.is_empty() {
let new_imports = get_new_imports();
for (key, value) in local_imports {
if let Err(warning) = new_imports.append(key, value) {
warn!("{}", warning);
}
}
}
}
let mut import_map = ImportMap::new(self.base_dir.clone());
if let Some(original_im) = maybe_original_import_map {
let original_base_dir = ModuleSpecifier::from_directory_path(
original_im
.base_url()
.to_file_path()
.unwrap()
.parent()
.unwrap(),
)
.unwrap();
let new_relative_path = self
.mappings
.relative_specifier_text(self.base_dir, &original_base_dir);
// add the imports
add_local_imports(&new_relative_path, original_im.imports(), || {
import_map.imports_mut()
});
for scope in original_im.scopes() {
if scope.raw_key.starts_with("./") || scope.raw_key.starts_with("../") {
let sub_index = scope.raw_key.find('/').unwrap() + 1;
let new_key =
format!("{}{}", new_relative_path, &scope.raw_key[sub_index..]);
add_local_imports(&new_relative_path, scope.imports, || {
import_map.get_or_append_scope_mut(&new_key).unwrap()
});
}
}
}
let imports = import_map.imports_mut();
for (key, value) in self.imports.imports {
if !imports.contains(&key) {
imports.append(key, value).unwrap();
}
}
for (scope_key, scope_value) in self.scopes {
if !scope_value.imports.is_empty() {
let imports = import_map.get_or_append_scope_mut(&scope_key).unwrap();
for (key, value) in scope_value.imports {
if !imports.contains(&key) {
imports.append(key, value).unwrap();
}
}
}
}
import_map
}
}
struct ImportsBuilder<'a> {
base_dir: &'a ModuleSpecifier,
mappings: &'a Mappings,
imports: IndexMap<String, String>,
}
impl<'a> ImportsBuilder<'a> {
pub fn new(base_dir: &'a ModuleSpecifier, mappings: &'a Mappings) -> Self {
Self {
base_dir,
mappings,
imports: Default::default(),
}
}
pub fn add(&mut self, key: String, specifier: &ModuleSpecifier) {
let value = self
.mappings
.relative_specifier_text(self.base_dir, specifier);
// skip creating identity entries
if key != value {
self.imports.insert(key, value);
}
}
}
pub struct BuildImportMapInput<'a> {
pub base_dir: &'a ModuleSpecifier,
pub modules: &'a [&'a Module],
pub graph: &'a ModuleGraph,
pub mappings: &'a Mappings,
pub maybe_original_import_map: Option<&'a ImportMap>,
pub maybe_jsx_import_source: Option<&'a JsxImportSourceConfig>,
pub resolver: &'a dyn deno_graph::source::Resolver,
pub parsed_source_cache: &'a ParsedSourceCache,
}
pub fn build_import_map(
input: BuildImportMapInput<'_>,
) -> Result<String, AnyError> {
let BuildImportMapInput {
base_dir,
modules,
graph,
mappings,
maybe_original_import_map,
maybe_jsx_import_source,
resolver,
parsed_source_cache,
} = input;
let mut builder = ImportMapBuilder::new(base_dir, mappings);
visit_modules(graph, modules, mappings, &mut builder, parsed_source_cache)?;
for base_specifier in mappings.base_specifiers() {
builder
.imports
.add(base_specifier.to_string(), base_specifier);
}
// add the jsx import source to the destination import map, if mapped in the original import map
if let Some(jsx_import_source) = maybe_jsx_import_source {
if let Some(specifier_text) = jsx_import_source.maybe_specifier_text() {
if let Ok(resolved_url) = resolver.resolve(
&specifier_text,
&deno_graph::Range {
specifier: jsx_import_source.base_url.clone(),
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
},
ResolutionMode::Execution,
) {
builder.imports.add(specifier_text, &resolved_url);
}
}
}
Ok(builder.into_import_map(maybe_original_import_map).to_json())
}
fn visit_modules(
graph: &ModuleGraph,
modules: &[&Module],
mappings: &Mappings,
import_map: &mut ImportMapBuilder,
parsed_source_cache: &ParsedSourceCache,
) -> Result<(), AnyError> {
for module in modules {
let module = match module {
Module::Js(module) => module,
// skip visiting Json modules as they are leaves
Module::Json(_)
| Module::Npm(_)
| Module::Node(_)
| Module::External(_) => continue,
};
let parsed_source =
parsed_source_cache.get_parsed_source_from_js_module(module)?;
let text_info = parsed_source.text_info_lazy().clone();
for dep in module.dependencies.values() {
visit_resolution(
&dep.maybe_code,
graph,
import_map,
&module.specifier,
mappings,
&text_info,
&module.source,
);
visit_resolution(
&dep.maybe_type,
graph,
import_map,
&module.specifier,
mappings,
&text_info,
&module.source,
);
}
if let Some(types_dep) = &module.maybe_types_dependency {
visit_resolution(
&types_dep.dependency,
graph,
import_map,
&module.specifier,
mappings,
&text_info,
&module.source,
);
}
}
Ok(())
}
fn visit_resolution(
resolution: &Resolution,
graph: &ModuleGraph,
import_map: &mut ImportMapBuilder,
referrer: &ModuleSpecifier,
mappings: &Mappings,
text_info: &SourceTextInfo,
source_text: &str,
) {
if let Some(resolved) = resolution.ok() {
let text = text_from_range(text_info, source_text, &resolved.range);
// if the text is empty then it's probably an x-TypeScript-types
if !text.is_empty() {
handle_dep_specifier(
text,
&resolved.specifier,
graph,
import_map,
referrer,
mappings,
);
}
}
}
fn handle_dep_specifier(
text: &str,
unresolved_specifier: &ModuleSpecifier,
graph: &ModuleGraph,
import_map: &mut ImportMapBuilder,
referrer: &ModuleSpecifier,
mappings: &Mappings,
) {
let specifier = match graph.get(unresolved_specifier) {
Some(module) => module.specifier().clone(),
// Ignore when None. The graph was previous validated so this is a
// dynamic import that was missing and is ignored for vendoring
None => return,
};
// check if it's referencing a remote module
if is_remote_specifier(&specifier) {
handle_remote_dep_specifier(
text,
unresolved_specifier,
&specifier,
import_map,
referrer,
mappings,
)
} else if specifier.scheme() == "file" {
handle_local_dep_specifier(
text,
unresolved_specifier,
&specifier,
import_map,
referrer,
mappings,
);
}
}
fn handle_remote_dep_specifier(
text: &str,
unresolved_specifier: &ModuleSpecifier,
specifier: &ModuleSpecifier,
import_map: &mut ImportMapBuilder,
referrer: &ModuleSpecifier,
mappings: &Mappings,
) {
if is_remote_specifier_text(text) {
let base_specifier = mappings.base_specifier(specifier);
if text.starts_with(base_specifier.as_str()) {
let sub_path = &text[base_specifier.as_str().len()..];
let relative_text =
mappings.relative_specifier_text(base_specifier, specifier);
let expected_sub_path = relative_text.trim_start_matches("./");
if expected_sub_path != sub_path {
import_map.imports.add(text.to_string(), specifier);
}
} else {
// it's probably a redirect. Add it explicitly to the import map
import_map.imports.add(text.to_string(), specifier);
}
} else {
let expected_relative_specifier_text =
mappings.relative_specifier_text(referrer, specifier);
if expected_relative_specifier_text == text {
return;
}
if !is_remote_specifier(referrer) {
// local module referencing a remote module using
// non-remote specifier text means it was something in
// the original import map, so add a mapping to it
import_map.imports.add(text.to_string(), specifier);
return;
}
let base_referrer = mappings.base_specifier(referrer);
let base_dir = import_map.base_dir().clone();
let imports = import_map.scope(base_referrer);
if text.starts_with("./") || text.starts_with("../") {
// resolve relative specifier key
let mut local_base_specifier = mappings.local_uri(base_referrer);
local_base_specifier = local_base_specifier
// path includes "/" so make it relative
.join(&format!(".{}", unresolved_specifier.path()))
.unwrap_or_else(|_| {
panic!(
"Error joining {} to {}",
unresolved_specifier.path(),
local_base_specifier
)
});
local_base_specifier.set_query(unresolved_specifier.query());
imports.add(
mappings.relative_specifier_text(&base_dir, &local_base_specifier),
specifier,
);
// add a mapping that uses the local directory name and the remote
// filename in order to support files importing this relatively
imports.add(
{
let local_path = mappings.local_path(specifier);
let mut value =
ModuleSpecifier::from_directory_path(local_path.parent().unwrap())
.unwrap();
value.set_query(specifier.query());
value.set_path(&format!(
"{}{}",
value.path(),
specifier.path_segments().unwrap().last().unwrap(),
));
mappings.relative_specifier_text(&base_dir, &value)
},
specifier,
);
} else {
// absolute (`/`) or bare specifier should be left as-is
imports.add(text.to_string(), specifier);
}
}
}
fn handle_local_dep_specifier(
text: &str,
unresolved_specifier: &ModuleSpecifier,
specifier: &ModuleSpecifier,
import_map: &mut ImportMapBuilder,
referrer: &ModuleSpecifier,
mappings: &Mappings,
) {
if !is_remote_specifier(referrer) {
// do not handle local modules referencing local modules
return;
}
// The remote module is referencing a local file. This could occur via an
// existing import map. In this case, we'll have to add an import map
// entry in order to map the path back to the local path once vendored.
let base_dir = import_map.base_dir().clone();
let base_specifier = mappings.base_specifier(referrer);
let imports = import_map.scope(base_specifier);
if text.starts_with("./") || text.starts_with("../") {
let referrer_local_uri = mappings.local_uri(referrer);
let mut specifier_local_uri =
referrer_local_uri.join(text).unwrap_or_else(|_| {
panic!(
"Error joining {} to {}",
unresolved_specifier.path(),
referrer_local_uri
)
});
specifier_local_uri.set_query(unresolved_specifier.query());
imports.add(
mappings.relative_specifier_text(&base_dir, &specifier_local_uri),
specifier,
);
} else {
imports.add(text.to_string(), specifier);
}
}
fn text_from_range<'a>(
text_info: &SourceTextInfo,
text: &'a str,
range: &Range,
) -> &'a str {
let result = &text[byte_range(text_info, range)];
if result.starts_with('"') || result.starts_with('\'') {
// remove the quotes
&result[1..result.len() - 1]
} else {
result
}
}
fn byte_range(
text_info: &SourceTextInfo,
range: &Range,
) -> std::ops::Range<usize> {
let start = byte_index(text_info, &range.start);
let end = byte_index(text_info, &range.end);
start..end
}
fn byte_index(text_info: &SourceTextInfo, pos: &Position) -> usize {
// todo(https://github.com/denoland/deno_graph/issues/79): use byte indexes all the way down
text_info.loc_to_source_pos(LineAndColumnIndex {
line_index: pos.line,
column_index: pos.character,
}) - text_info.range().start
}

View file

@ -1,255 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use deno_ast::MediaType;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_graph::Module;
use deno_graph::ModuleGraph;
use deno_graph::Position;
use crate::util::path::path_with_stem_suffix;
use crate::util::path::relative_specifier;
use super::specifiers::dir_name_for_root;
use super::specifiers::get_unique_path;
use super::specifiers::make_url_relative;
use super::specifiers::partition_by_root_specifiers;
use super::specifiers::sanitize_filepath;
pub struct ProxiedModule {
pub output_path: PathBuf,
pub declaration_specifier: ModuleSpecifier,
}
/// Constructs and holds the remote specifier to local path mappings.
pub struct Mappings {
mappings: HashMap<ModuleSpecifier, PathBuf>,
base_specifiers: Vec<ModuleSpecifier>,
proxies: HashMap<ModuleSpecifier, ProxiedModule>,
}
impl Mappings {
pub fn from_remote_modules(
graph: &ModuleGraph,
remote_modules: &[&Module],
output_dir: &Path,
) -> Result<Self, AnyError> {
let partitioned_specifiers = partition_by_root_specifiers(
remote_modules.iter().map(|m| m.specifier()),
);
let mut mapped_paths = HashSet::new();
let mut mappings = HashMap::new();
let mut proxies = HashMap::new();
let mut base_specifiers = Vec::new();
for (root, specifiers) in partitioned_specifiers.into_iter() {
let base_dir = get_unique_path(
output_dir.join(dir_name_for_root(&root)),
&mut mapped_paths,
);
for specifier in specifiers {
let module = graph.get(&specifier).unwrap();
let media_type = match module {
Module::Js(module) => module.media_type,
Module::Json(_) => MediaType::Json,
Module::Node(_) | Module::Npm(_) | Module::External(_) => continue,
};
let sub_path = sanitize_filepath(&make_url_relative(&root, &{
let mut specifier = specifier.clone();
specifier.set_query(None);
specifier
})?);
let new_path = path_with_extension(
&base_dir.join(if cfg!(windows) {
sub_path.replace('/', "\\")
} else {
sub_path
}),
&media_type.as_ts_extension()[1..],
);
mappings
.insert(specifier, get_unique_path(new_path, &mut mapped_paths));
}
base_specifiers.push(root.clone());
mappings.insert(root, base_dir);
}
// resolve all the "proxy" paths to use for when an x-typescript-types header is specified
for module in remote_modules {
if let Some(module) = module.js() {
if let Some(resolved) = &module
.maybe_types_dependency
.as_ref()
.and_then(|d| d.dependency.ok())
{
let range = &resolved.range;
// hack to tell if it's an x-typescript-types header
let is_ts_types_header = range.start == Position::zeroed()
&& range.end == Position::zeroed();
if is_ts_types_header {
let module_path = mappings.get(&module.specifier).unwrap();
let proxied_path = get_unique_path(
path_with_stem_suffix(module_path, ".proxied"),
&mut mapped_paths,
);
proxies.insert(
module.specifier.clone(),
ProxiedModule {
output_path: proxied_path,
declaration_specifier: resolved.specifier.clone(),
},
);
}
}
}
}
Ok(Self {
mappings,
base_specifiers,
proxies,
})
}
pub fn local_uri(&self, specifier: &ModuleSpecifier) -> ModuleSpecifier {
if specifier.scheme() == "file" {
specifier.clone()
} else {
let local_path = self.local_path(specifier);
if specifier.path().ends_with('/') {
ModuleSpecifier::from_directory_path(&local_path)
} else {
ModuleSpecifier::from_file_path(&local_path)
}
.unwrap_or_else(|_| {
panic!("Could not convert {} to uri.", local_path.display())
})
}
}
pub fn local_path(&self, specifier: &ModuleSpecifier) -> PathBuf {
if specifier.scheme() == "file" {
specifier.to_file_path().unwrap()
} else {
self
.mappings
.get(specifier)
.unwrap_or_else(|| panic!("Could not find local path for {specifier}"))
.to_path_buf()
}
}
pub fn relative_specifier_text(
&self,
from: &ModuleSpecifier,
to: &ModuleSpecifier,
) -> String {
let from = self.local_uri(from);
let to = self.local_uri(to);
relative_specifier(&from, &to).unwrap()
}
pub fn base_specifiers(&self) -> &Vec<ModuleSpecifier> {
&self.base_specifiers
}
pub fn base_specifier(
&self,
child_specifier: &ModuleSpecifier,
) -> &ModuleSpecifier {
self
.base_specifiers
.iter()
.find(|s| child_specifier.as_str().starts_with(s.as_str()))
.unwrap_or_else(|| {
panic!("Could not find base specifier for {child_specifier}")
})
}
pub fn proxied_path(&self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
self.proxies.get(specifier).map(|s| s.output_path.clone())
}
pub fn proxied_modules(
&self,
) -> std::collections::hash_map::Iter<'_, ModuleSpecifier, ProxiedModule> {
self.proxies.iter()
}
}
fn path_with_extension(path: &Path, new_ext: &str) -> PathBuf {
if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) {
if let Some(old_ext) = path.extension().map(|f| f.to_string_lossy()) {
if file_stem.to_lowercase().ends_with(".d") {
if new_ext.to_lowercase() == format!("d.{}", old_ext.to_lowercase()) {
// maintain casing
return path.to_path_buf();
}
return path.with_file_name(format!(
"{}.{}",
&file_stem[..file_stem.len() - ".d".len()],
new_ext
));
}
if new_ext.to_lowercase() == old_ext.to_lowercase() {
// maintain casing
return path.to_path_buf();
}
let media_type = MediaType::from_path(path);
if media_type == MediaType::Unknown {
return path.with_file_name(format!(
"{}.{}",
path.file_name().unwrap().to_string_lossy(),
new_ext
));
}
}
}
path.with_extension(new_ext)
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_path_with_extension() {
assert_eq!(
path_with_extension(&PathBuf::from("/test.D.TS"), "ts"),
PathBuf::from("/test.ts")
);
assert_eq!(
path_with_extension(&PathBuf::from("/test.D.MTS"), "js"),
PathBuf::from("/test.js")
);
assert_eq!(
path_with_extension(&PathBuf::from("/test.D.TS"), "d.ts"),
// maintains casing
PathBuf::from("/test.D.TS"),
);
assert_eq!(
path_with_extension(&PathBuf::from("/test.TS"), "ts"),
// maintains casing
PathBuf::from("/test.TS"),
);
assert_eq!(
path_with_extension(&PathBuf::from("/test.ts"), "js"),
PathBuf::from("/test.js")
);
assert_eq!(
path_with_extension(&PathBuf::from("/test.js"), "js"),
PathBuf::from("/test.js")
);
assert_eq!(
path_with_extension(&PathBuf::from("/chai@1.2.3"), "js"),
PathBuf::from("/chai@1.2.3.js")
);
}
}

View file

@ -1,575 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
use deno_ast::TextChange;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::resolve_url_or_path;
use deno_graph::GraphKind;
use deno_runtime::colors;
use log::warn;
use crate::args::CliOptions;
use crate::args::ConfigFile;
use crate::args::Flags;
use crate::args::FmtOptionsConfig;
use crate::args::VendorFlags;
use crate::factory::CliFactory;
use crate::tools::fmt::format_json;
use crate::util::fs::canonicalize_path;
use crate::util::fs::resolve_from_cwd;
use crate::util::path::relative_specifier;
use deno_runtime::fs_util::specifier_to_file_path;
mod analyze;
mod build;
mod import_map;
mod mappings;
mod specifiers;
#[cfg(test)]
mod test;
pub async fn vendor(
flags: Arc<Flags>,
vendor_flags: VendorFlags,
) -> Result<(), AnyError> {
log::info!(
"{}",
colors::yellow("⚠️ Warning: `deno vendor` is deprecated and will be removed in Deno 2.0.\nAdd `\"vendor\": true` to your `deno.json` or use the `--vendor` flag instead."),
);
let mut cli_options = CliOptions::from_flags(flags)?;
let raw_output_dir = match &vendor_flags.output_path {
Some(output_path) => PathBuf::from(output_path).to_owned(),
None => PathBuf::from("vendor/"),
};
let output_dir = resolve_from_cwd(&raw_output_dir)?;
validate_output_dir(&output_dir, &vendor_flags)?;
validate_options(&mut cli_options, &output_dir)?;
let factory = CliFactory::from_cli_options(Arc::new(cli_options));
let cli_options = factory.cli_options()?;
if cli_options.workspace().config_folders().len() > 1 {
bail!("deno vendor is not supported in a workspace. Set `\"vendor\": true` in the workspace deno.json file instead");
}
let entry_points =
resolve_entry_points(&vendor_flags, cli_options.initial_cwd())?;
let jsx_import_source = cli_options
.workspace()
.to_maybe_jsx_import_source_config()?;
let module_graph_creator = factory.module_graph_creator().await?.clone();
let workspace_resolver = factory.workspace_resolver().await?;
let root_folder = cli_options.workspace().root_folder_configs();
let maybe_config_file = root_folder.deno_json.as_ref();
let output = build::build(build::BuildInput {
entry_points,
build_graph: move |entry_points| {
async move {
module_graph_creator
.create_graph(GraphKind::All, entry_points)
.await
}
.boxed_local()
},
parsed_source_cache: factory.parsed_source_cache(),
output_dir: &output_dir,
maybe_original_import_map: workspace_resolver.maybe_import_map(),
maybe_jsx_import_source: jsx_import_source.as_ref(),
resolver: factory.resolver().await?.as_graph_resolver(),
environment: &build::RealVendorEnvironment,
})
.await?;
let vendored_count = output.vendored_count;
let graph = output.graph;
let npm_package_count = graph.npm_packages.len();
let try_add_node_modules_dir = npm_package_count > 0
&& cli_options.node_modules_dir_enablement().unwrap_or(true);
log::info!(
concat!("Vendored {} {} into {} directory.",),
vendored_count,
if vendored_count == 1 {
"module"
} else {
"modules"
},
raw_output_dir.display(),
);
let try_add_import_map = vendored_count > 0;
let modified_result = maybe_update_config_file(
&output_dir,
maybe_config_file,
try_add_import_map,
try_add_node_modules_dir,
);
// cache the node_modules folder when it's been added to the config file
if modified_result.added_node_modules_dir {
let node_modules_path =
cli_options.node_modules_dir_path().cloned().or_else(|| {
maybe_config_file
.as_ref()
.map(|d| &d.specifier)
.filter(|c| c.scheme() == "file")
.and_then(|c| c.to_file_path().ok())
.map(|config_path| config_path.parent().unwrap().join("node_modules"))
});
if let Some(node_modules_path) = node_modules_path {
let cli_options =
cli_options.with_node_modules_dir_path(node_modules_path);
let factory = CliFactory::from_cli_options(Arc::new(cli_options));
if let Some(managed) = factory.npm_resolver().await?.as_managed() {
managed.cache_packages().await?;
}
}
log::info!(
concat!(
"Vendored {} npm {} into node_modules directory. Set `nodeModulesDir: false` ",
"in the Deno configuration file to disable vendoring npm packages in the future.",
),
npm_package_count,
if npm_package_count == 1 {
"package"
} else {
"packages"
},
);
}
if vendored_count > 0 {
let import_map_path = raw_output_dir.join("import_map.json");
if modified_result.updated_import_map {
log::info!(
concat!(
"\nUpdated your local Deno configuration file with a reference to the ",
"new vendored import map at {}. Invoking Deno subcommands will now ",
"automatically resolve using the vendored modules. You may override ",
"this by providing the `--import-map <other-import-map>` flag or by ",
"manually editing your Deno configuration file.",
),
import_map_path.display(),
);
} else {
log::info!(
concat!(
"\nTo use vendored modules, specify the `--import-map {}` flag when ",
r#"invoking Deno subcommands or add an `"importMap": "<path_to_vendored_import_map>"` "#,
"entry to a deno.json file.",
),
import_map_path.display(),
);
}
}
Ok(())
}
fn validate_output_dir(
output_dir: &Path,
flags: &VendorFlags,
) -> Result<(), AnyError> {
if !flags.force && !is_dir_empty(output_dir)? {
bail!(concat!(
"Output directory was not empty. Please specify an empty directory or use ",
"--force to ignore this error and potentially overwrite its contents.",
));
}
Ok(())
}
fn validate_options(
options: &mut CliOptions,
output_dir: &Path,
) -> Result<(), AnyError> {
let import_map_specifier = options
.resolve_specified_import_map_specifier()?
.or_else(|| {
let config_file = options.workspace().root_deno_json()?;
config_file
.to_import_map_specifier()
.ok()
.flatten()
.or_else(|| {
if config_file.is_an_import_map() {
Some(config_file.specifier.clone())
} else {
None
}
})
});
// check the import map
if let Some(import_map_path) = import_map_specifier
.and_then(|p| specifier_to_file_path(&p).ok())
.and_then(|p| canonicalize_path(&p).ok())
{
// make the output directory in order to canonicalize it for the check below
std::fs::create_dir_all(output_dir)?;
let output_dir = canonicalize_path(output_dir).with_context(|| {
format!("Failed to canonicalize: {}", output_dir.display())
})?;
if import_map_path.starts_with(output_dir) {
// canonicalize to make the test for this pass on the CI
let cwd = canonicalize_path(&std::env::current_dir()?)?;
// We don't allow using the output directory to help generate the
// new state because this may lead to cryptic error messages.
log::warn!(
concat!(
"Ignoring import map. Specifying an import map file ({}) in the ",
"deno vendor output directory is not supported. If you wish to use ",
"an import map while vendoring, please specify one located outside ",
"this directory."
),
import_map_path
.strip_prefix(&cwd)
.unwrap_or(&import_map_path)
.display()
.to_string(),
);
// don't use an import map in the config
options.set_import_map_specifier(None);
}
}
Ok(())
}
fn maybe_update_config_file(
output_dir: &Path,
maybe_config_file: Option<&Arc<ConfigFile>>,
try_add_import_map: bool,
try_add_node_modules_dir: bool,
) -> ModifiedResult {
assert!(output_dir.is_absolute());
let config_file = match maybe_config_file {
Some(config_file) => config_file,
None => return ModifiedResult::default(),
};
if config_file.specifier.scheme() != "file" {
return ModifiedResult::default();
}
let fmt_config_options = config_file
.to_fmt_config()
.ok()
.map(|config| config.options)
.unwrap_or_default();
let result = update_config_file(
config_file,
&fmt_config_options,
if try_add_import_map {
Some(
ModuleSpecifier::from_file_path(output_dir.join("import_map.json"))
.unwrap(),
)
} else {
None
},
try_add_node_modules_dir,
);
match result {
Ok(modified_result) => modified_result,
Err(err) => {
warn!("Error updating config file. {:#}", err);
ModifiedResult::default()
}
}
}
fn update_config_file(
config_file: &ConfigFile,
fmt_options: &FmtOptionsConfig,
import_map_specifier: Option<ModuleSpecifier>,
try_add_node_modules_dir: bool,
) -> Result<ModifiedResult, AnyError> {
let config_path = specifier_to_file_path(&config_file.specifier)?;
let config_text = std::fs::read_to_string(&config_path)?;
let import_map_specifier =
import_map_specifier.and_then(|import_map_specifier| {
relative_specifier(&config_file.specifier, &import_map_specifier)
});
let modified_result = update_config_text(
&config_text,
fmt_options,
import_map_specifier.as_deref(),
try_add_node_modules_dir,
)?;
if let Some(new_text) = &modified_result.new_text {
std::fs::write(config_path, new_text)?;
}
Ok(modified_result)
}
#[derive(Default)]
struct ModifiedResult {
updated_import_map: bool,
added_node_modules_dir: bool,
new_text: Option<String>,
}
fn update_config_text(
text: &str,
fmt_options: &FmtOptionsConfig,
import_map_specifier: Option<&str>,
try_add_node_modules_dir: bool,
) -> Result<ModifiedResult, AnyError> {
use jsonc_parser::ast::ObjectProp;
use jsonc_parser::ast::Value;
let text = if text.trim().is_empty() { "{}\n" } else { text };
let ast =
jsonc_parser::parse_to_ast(text, &Default::default(), &Default::default())?;
let obj = match ast.value {
Some(Value::Object(obj)) => obj,
_ => bail!("Failed updating config file due to no object."),
};
let mut modified_result = ModifiedResult::default();
let mut text_changes = Vec::new();
let mut should_format = false;
if try_add_node_modules_dir {
// Only modify the nodeModulesDir property if it's not set
// as this allows people to opt-out of this when vendoring
// by specifying `nodeModulesDir: false`
if obj.get("nodeModulesDir").is_none() {
let insert_position = obj.range.end - 1;
text_changes.push(TextChange {
range: insert_position..insert_position,
new_text: r#""nodeModulesDir": true"#.to_string(),
});
should_format = true;
modified_result.added_node_modules_dir = true;
}
}
if let Some(import_map_specifier) = import_map_specifier {
let import_map_specifier = import_map_specifier.replace('\"', "\\\"");
match obj.get("importMap") {
Some(ObjectProp {
value: Value::StringLit(lit),
..
}) => {
text_changes.push(TextChange {
range: lit.range.start..lit.range.end,
new_text: format!("\"{}\"", import_map_specifier),
});
modified_result.updated_import_map = true;
}
None => {
// insert it crudely at a position that won't cause any issues
// with comments and format after to make it look nice
let insert_position = obj.range.end - 1;
text_changes.push(TextChange {
range: insert_position..insert_position,
new_text: format!(r#""importMap": "{}""#, import_map_specifier),
});
should_format = true;
modified_result.updated_import_map = true;
}
// shouldn't happen
Some(_) => {
bail!("Failed updating importMap in config file due to invalid type.")
}
}
}
if text_changes.is_empty() {
return Ok(modified_result);
}
let new_text = deno_ast::apply_text_changes(text, text_changes);
modified_result.new_text = if should_format {
format_json(&PathBuf::from("deno.json"), &new_text, fmt_options)
.ok()
.map(|formatted_text| formatted_text.unwrap_or(new_text))
} else {
Some(new_text)
};
Ok(modified_result)
}
fn is_dir_empty(dir_path: &Path) -> Result<bool, AnyError> {
match std::fs::read_dir(dir_path) {
Ok(mut dir) => Ok(dir.next().is_none()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(true),
Err(err) => {
bail!("Error reading directory {}: {}", dir_path.display(), err)
}
}
}
fn resolve_entry_points(
flags: &VendorFlags,
initial_cwd: &Path,
) -> Result<Vec<ModuleSpecifier>, AnyError> {
flags
.specifiers
.iter()
.map(|p| resolve_url_or_path(p, initial_cwd).map_err(|e| e.into()))
.collect::<Result<Vec<_>, _>>()
}
#[cfg(test)]
mod internal_test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn update_config_text_no_existing_props_add_prop() {
let result = update_config_text(
"{\n}",
&Default::default(),
Some("./vendor/import_map.json"),
false,
)
.unwrap();
assert!(result.updated_import_map);
assert!(!result.added_node_modules_dir);
assert_eq!(
result.new_text.unwrap(),
r#"{
"importMap": "./vendor/import_map.json"
}
"#
);
let result = update_config_text(
"{\n}",
&Default::default(),
Some("./vendor/import_map.json"),
true,
)
.unwrap();
assert!(result.updated_import_map);
assert!(result.added_node_modules_dir);
assert_eq!(
result.new_text.unwrap(),
r#"{
"nodeModulesDir": true,
"importMap": "./vendor/import_map.json"
}
"#
);
let result =
update_config_text("{\n}", &Default::default(), None, true).unwrap();
assert!(!result.updated_import_map);
assert!(result.added_node_modules_dir);
assert_eq!(
result.new_text.unwrap(),
r#"{
"nodeModulesDir": true
}
"#
);
}
#[test]
fn update_config_text_existing_props_add_prop() {
let result = update_config_text(
r#"{
"tasks": {
"task1": "other"
}
}
"#,
&Default::default(),
Some("./vendor/import_map.json"),
false,
)
.unwrap();
assert_eq!(
result.new_text.unwrap(),
r#"{
"tasks": {
"task1": "other"
},
"importMap": "./vendor/import_map.json"
}
"#
);
// trailing comma
let result = update_config_text(
r#"{
"tasks": {
"task1": "other"
},
}
"#,
&Default::default(),
Some("./vendor/import_map.json"),
false,
)
.unwrap();
assert_eq!(
result.new_text.unwrap(),
r#"{
"tasks": {
"task1": "other"
},
"importMap": "./vendor/import_map.json"
}
"#
);
}
#[test]
fn update_config_text_update_prop() {
let result = update_config_text(
r#"{
"importMap": "./local.json"
}
"#,
&Default::default(),
Some("./vendor/import_map.json"),
false,
)
.unwrap();
assert_eq!(
result.new_text.unwrap(),
r#"{
"importMap": "./vendor/import_map.json"
}
"#
);
}
#[test]
fn no_update_node_modules_dir() {
// will not update if this is already set (even if it's false)
let result = update_config_text(
r#"{
"nodeModulesDir": false
}
"#,
&Default::default(),
None,
true,
)
.unwrap();
assert!(!result.added_node_modules_dir);
assert!(!result.updated_import_map);
assert_eq!(result.new_text, None);
let result = update_config_text(
r#"{
"nodeModulesDir": true
}
"#,
&Default::default(),
None,
true,
)
.unwrap();
assert!(!result.added_node_modules_dir);
assert!(!result.updated_import_map);
assert_eq!(result.new_text, None);
}
}

View file

@ -1,208 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::path::PathBuf;
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use crate::util::path::is_banned_path_char;
use crate::util::path::path_with_stem_suffix;
use crate::util::path::root_url_to_safe_local_dirname;
/// Partitions the provided specifiers by the non-path and non-query parts of a specifier.
pub fn partition_by_root_specifiers<'a>(
specifiers: impl Iterator<Item = &'a ModuleSpecifier>,
) -> BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> {
let mut root_specifiers: BTreeMap<ModuleSpecifier, Vec<ModuleSpecifier>> =
Default::default();
for remote_specifier in specifiers {
let mut root_specifier = remote_specifier.clone();
root_specifier.set_query(None);
root_specifier.set_path("/");
let specifiers = root_specifiers.entry(root_specifier).or_default();
specifiers.push(remote_specifier.clone());
}
root_specifiers
}
/// Gets the directory name to use for the provided root.
pub fn dir_name_for_root(root: &ModuleSpecifier) -> PathBuf {
root_url_to_safe_local_dirname(root)
}
/// Gets a unique file path given the provided file path
/// and the set of existing file paths. Inserts to the
/// set when finding a unique path.
pub fn get_unique_path(
mut path: PathBuf,
unique_set: &mut HashSet<String>,
) -> PathBuf {
let original_path = path.clone();
let mut count = 2;
// case insensitive comparison so the output works on case insensitive file systems
while !unique_set.insert(path.to_string_lossy().to_lowercase()) {
path = path_with_stem_suffix(&original_path, &format!("_{count}"));
count += 1;
}
path
}
pub fn make_url_relative(
root: &ModuleSpecifier,
url: &ModuleSpecifier,
) -> Result<String, AnyError> {
root.make_relative(url).ok_or_else(|| {
anyhow!(
"Error making url ({}) relative to root: {}",
url.to_string(),
root.to_string()
)
})
}
pub fn is_remote_specifier(specifier: &ModuleSpecifier) -> bool {
matches!(specifier.scheme().to_lowercase().as_str(), "http" | "https")
}
pub fn is_remote_specifier_text(text: &str) -> bool {
let text = text.trim_start().to_lowercase();
text.starts_with("http:") || text.starts_with("https:")
}
pub fn sanitize_filepath(text: &str) -> String {
text
.chars()
.map(|c| if is_banned_path_char(c) { '_' } else { c })
.collect()
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn partition_by_root_specifiers_same_sub_folder() {
run_partition_by_root_specifiers_test(
vec![
"https://deno.land/x/mod/A.ts",
"https://deno.land/x/mod/other/A.ts",
],
vec![(
"https://deno.land/",
vec![
"https://deno.land/x/mod/A.ts",
"https://deno.land/x/mod/other/A.ts",
],
)],
);
}
#[test]
fn partition_by_root_specifiers_different_sub_folder() {
run_partition_by_root_specifiers_test(
vec![
"https://deno.land/x/mod/A.ts",
"https://deno.land/x/other/A.ts",
],
vec![(
"https://deno.land/",
vec![
"https://deno.land/x/mod/A.ts",
"https://deno.land/x/other/A.ts",
],
)],
);
}
#[test]
fn partition_by_root_specifiers_different_hosts() {
run_partition_by_root_specifiers_test(
vec![
"https://deno.land/mod/A.ts",
"http://deno.land/B.ts",
"https://deno.land:8080/C.ts",
"https://localhost/mod/A.ts",
"https://other/A.ts",
],
vec![
("http://deno.land/", vec!["http://deno.land/B.ts"]),
("https://deno.land/", vec!["https://deno.land/mod/A.ts"]),
(
"https://deno.land:8080/",
vec!["https://deno.land:8080/C.ts"],
),
("https://localhost/", vec!["https://localhost/mod/A.ts"]),
("https://other/", vec!["https://other/A.ts"]),
],
);
}
fn run_partition_by_root_specifiers_test(
input: Vec<&str>,
expected: Vec<(&str, Vec<&str>)>,
) {
let input = input
.iter()
.map(|s| ModuleSpecifier::parse(s).unwrap())
.collect::<Vec<_>>();
let output = partition_by_root_specifiers(input.iter());
// the assertion is much easier to compare when everything is strings
let output = output
.into_iter()
.map(|(s, vec)| {
(
s.to_string(),
vec.into_iter().map(|s| s.to_string()).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
let expected = expected
.into_iter()
.map(|(s, vec)| {
(
s.to_string(),
vec.into_iter().map(|s| s.to_string()).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
assert_eq!(output, expected);
}
#[test]
fn test_unique_path() {
let mut paths = HashSet::new();
assert_eq!(
get_unique_path(PathBuf::from("/test"), &mut paths),
PathBuf::from("/test")
);
assert_eq!(
get_unique_path(PathBuf::from("/test"), &mut paths),
PathBuf::from("/test_2")
);
assert_eq!(
get_unique_path(PathBuf::from("/test"), &mut paths),
PathBuf::from("/test_3")
);
assert_eq!(
get_unique_path(PathBuf::from("/TEST"), &mut paths),
PathBuf::from("/TEST_4")
);
assert_eq!(
get_unique_path(PathBuf::from("/test.txt"), &mut paths),
PathBuf::from("/test.txt")
);
assert_eq!(
get_unique_path(PathBuf::from("/test.txt"), &mut paths),
PathBuf::from("/test_2.txt")
);
assert_eq!(
get_unique_path(PathBuf::from("/TEST.TXT"), &mut paths),
PathBuf::from("/TEST_3.TXT")
);
}
}

View file

@ -1,357 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
use deno_config::workspace::WorkspaceResolver;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::futures::FutureExt;
use deno_core::serde_json;
use deno_graph::source::LoadFuture;
use deno_graph::source::LoadResponse;
use deno_graph::source::Loader;
use deno_graph::DefaultModuleAnalyzer;
use deno_graph::GraphKind;
use deno_graph::ModuleGraph;
use import_map::ImportMap;
use crate::args::JsxImportSourceConfig;
use crate::cache::ParsedSourceCache;
use crate::resolver::CliGraphResolver;
use crate::resolver::CliGraphResolverOptions;
use super::build::VendorEnvironment;
// Utilities that help `deno vendor` get tested in memory.
type RemoteFileText = String;
type RemoteFileHeaders = Option<HashMap<String, String>>;
type RemoteFileResult = Result<(RemoteFileText, RemoteFileHeaders), String>;
#[derive(Clone, Default)]
pub struct TestLoader {
files: HashMap<ModuleSpecifier, RemoteFileResult>,
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
}
impl TestLoader {
pub fn add(
&mut self,
path_or_specifier: impl AsRef<str>,
text: impl AsRef<str>,
) -> &mut Self {
self.add_result(path_or_specifier, Ok((text.as_ref().to_string(), None)))
}
pub fn add_failure(
&mut self,
path_or_specifier: impl AsRef<str>,
message: impl AsRef<str>,
) -> &mut Self {
self.add_result(path_or_specifier, Err(message.as_ref().to_string()))
}
fn add_result(
&mut self,
path_or_specifier: impl AsRef<str>,
result: RemoteFileResult,
) -> &mut Self {
if path_or_specifier
.as_ref()
.to_lowercase()
.starts_with("http")
{
self.files.insert(
ModuleSpecifier::parse(path_or_specifier.as_ref()).unwrap(),
result,
);
} else {
let path = make_path(path_or_specifier.as_ref());
let specifier = ModuleSpecifier::from_file_path(path).unwrap();
self.files.insert(specifier, result);
}
self
}
pub fn add_with_headers(
&mut self,
specifier: impl AsRef<str>,
text: impl AsRef<str>,
headers: &[(&str, &str)],
) -> &mut Self {
let headers = headers
.iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect();
self.files.insert(
ModuleSpecifier::parse(specifier.as_ref()).unwrap(),
Ok((text.as_ref().to_string(), Some(headers))),
);
self
}
pub fn add_redirect(
&mut self,
from: impl AsRef<str>,
to: impl AsRef<str>,
) -> &mut Self {
self.redirects.insert(
ModuleSpecifier::parse(from.as_ref()).unwrap(),
ModuleSpecifier::parse(to.as_ref()).unwrap(),
);
self
}
}
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: deno_graph::source::LoadOptions,
) -> LoadFuture {
if let Some(redirect) = self.redirects.get(specifier) {
return Box::pin(futures::future::ready(Ok(Some(
LoadResponse::Redirect {
specifier: redirect.clone(),
},
))));
}
let result = self.files.get(specifier).map(|result| match result {
Ok(result) => Ok(LoadResponse::Module {
specifier: specifier.clone(),
content: result.0.clone().into_bytes().into(),
maybe_headers: result.1.clone(),
}),
Err(err) => Err(err),
});
let result = match result {
Some(Ok(result)) => Ok(Some(result)),
Some(Err(err)) => Err(anyhow!("{}", err)),
None if specifier.scheme() == "data" => {
deno_graph::source::load_data_url(specifier)
}
None => Ok(None),
};
Box::pin(futures::future::ready(result))
}
}
#[derive(Default)]
struct TestVendorEnvironment {
directories: RefCell<HashSet<PathBuf>>,
files: RefCell<HashMap<PathBuf, String>>,
}
impl VendorEnvironment for TestVendorEnvironment {
fn create_dir_all(&self, dir_path: &Path) -> Result<(), AnyError> {
let mut directories = self.directories.borrow_mut();
for path in dir_path.ancestors() {
if !directories.insert(path.to_path_buf()) {
break;
}
}
Ok(())
}
fn write_file(&self, file_path: &Path, text: &[u8]) -> Result<(), AnyError> {
let parent = file_path.parent().unwrap();
if !self.directories.borrow().contains(parent) {
bail!("Directory not found: {}", parent.display());
}
self.files.borrow_mut().insert(
file_path.to_path_buf(),
String::from_utf8(text.to_vec()).unwrap(),
);
Ok(())
}
}
pub struct VendorOutput {
pub files: Vec<(String, String)>,
pub import_map: Option<serde_json::Value>,
}
#[derive(Default)]
pub struct VendorTestBuilder {
entry_points: Vec<ModuleSpecifier>,
loader: TestLoader,
maybe_original_import_map: Option<ImportMap>,
environment: TestVendorEnvironment,
jsx_import_source_config: Option<JsxImportSourceConfig>,
}
impl VendorTestBuilder {
pub fn with_default_setup() -> Self {
let mut builder = VendorTestBuilder::default();
builder.add_entry_point("/mod.ts");
builder
}
pub fn resolve_to_url(&self, path: &str) -> ModuleSpecifier {
ModuleSpecifier::from_file_path(make_path(path)).unwrap()
}
pub fn new_import_map(&self, base_path: &str) -> ImportMap {
let base = self.resolve_to_url(base_path);
ImportMap::new(base)
}
pub fn set_original_import_map(
&mut self,
import_map: ImportMap,
) -> &mut Self {
self.maybe_original_import_map = Some(import_map);
self
}
pub fn add_entry_point(&mut self, entry_point: impl AsRef<str>) -> &mut Self {
let entry_point = make_path(entry_point.as_ref());
self
.entry_points
.push(ModuleSpecifier::from_file_path(entry_point).unwrap());
self
}
pub fn set_jsx_import_source_config(
&mut self,
jsx_import_source_config: JsxImportSourceConfig,
) -> &mut Self {
self.jsx_import_source_config = Some(jsx_import_source_config);
self
}
pub async fn build(&mut self) -> Result<VendorOutput, AnyError> {
let output_dir = make_path("/vendor");
let entry_points = self.entry_points.clone();
let loader = self.loader.clone();
let parsed_source_cache = ParsedSourceCache::default();
let resolver = Arc::new(build_resolver(
output_dir.parent().unwrap(),
self.jsx_import_source_config.clone(),
self.maybe_original_import_map.clone(),
));
super::build::build(super::build::BuildInput {
entry_points,
build_graph: {
let resolver = resolver.clone();
move |entry_points| {
async move {
Ok(
build_test_graph(
entry_points,
loader,
resolver.as_graph_resolver(),
&DefaultModuleAnalyzer,
)
.await,
)
}
.boxed_local()
}
},
parsed_source_cache: &parsed_source_cache,
output_dir: &output_dir,
maybe_original_import_map: self.maybe_original_import_map.as_ref(),
maybe_jsx_import_source: self.jsx_import_source_config.as_ref(),
resolver: resolver.as_graph_resolver(),
environment: &self.environment,
})
.await?;
let mut files = self.environment.files.borrow_mut();
let import_map = files.remove(&output_dir.join("import_map.json"));
let mut files = files
.iter()
.map(|(path, text)| (path_to_string(path), text.to_string()))
.collect::<Vec<_>>();
files.sort_by(|a, b| a.0.cmp(&b.0));
Ok(VendorOutput {
import_map: import_map.map(|text| serde_json::from_str(&text).unwrap()),
files,
})
}
pub fn with_loader(&mut self, action: impl Fn(&mut TestLoader)) -> &mut Self {
action(&mut self.loader);
self
}
}
fn build_resolver(
root_dir: &Path,
maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
maybe_original_import_map: Option<ImportMap>,
) -> CliGraphResolver {
CliGraphResolver::new(CliGraphResolverOptions {
node_resolver: None,
npm_resolver: None,
sloppy_imports_resolver: None,
workspace_resolver: Arc::new(WorkspaceResolver::new_raw(
Arc::new(ModuleSpecifier::from_directory_path(root_dir).unwrap()),
maybe_original_import_map,
Vec::new(),
Vec::new(),
deno_config::workspace::PackageJsonDepResolution::Enabled,
)),
maybe_jsx_import_source_config,
maybe_vendor_dir: None,
bare_node_builtins_enabled: false,
})
}
async fn build_test_graph(
roots: Vec<ModuleSpecifier>,
loader: TestLoader,
resolver: &dyn deno_graph::source::Resolver,
analyzer: &dyn deno_graph::ModuleAnalyzer,
) -> ModuleGraph {
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
roots,
&loader,
deno_graph::BuildOptions {
resolver: Some(resolver),
module_analyzer: analyzer,
..Default::default()
},
)
.await;
graph
}
fn make_path(text: &str) -> PathBuf {
// This should work all in memory. We're waiting on
// https://github.com/servo/rust-url/issues/730 to provide
// a cross platform path here
assert!(text.starts_with('/'));
if cfg!(windows) {
PathBuf::from(format!("C:{}", text.replace('/', "\\")))
} else {
PathBuf::from(text)
}
}
fn path_to_string<P>(path: P) -> String
where
P: AsRef<Path>,
{
let path = path.as_ref();
// inverse of the function above
let path = path.to_string_lossy();
if cfg!(windows) {
path.replace("C:\\", "\\").replace('\\', "/")
} else {
path.to_string()
}
}

View file

@ -46,10 +46,6 @@ delete Object.prototype.__proto__;
"UnixListenOptions",
"createHttpClient",
"dlopen",
"flock",
"flockSync",
"funlock",
"funlockSync",
"listen",
"listenDatagram",
"openKv",
@ -1156,7 +1152,6 @@ delete Object.prototype.__proto__;
"strict": true,
"target": "esnext",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": false,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",

File diff suppressed because it is too large Load diff

View file

@ -593,16 +593,12 @@ declare interface Performance extends EventTarget {
endMark?: string,
): PerformanceMeasure;
/** Returns a current time from Deno's start in milliseconds.
*
* Use the permission flag `--allow-hrtime` to return a precise value.
/** Returns a current time from Deno's start in fractional milliseconds.
*
* ```ts
* const t = performance.now();
* console.log(`${t} ms since start!`);
* ```
*
* @tags allow-hrtime
*/
now(): number;

View file

@ -10,18 +10,6 @@
declare namespace Deno {
export {}; // stop default export type behavior
/** Information for a HTTP request.
*
* @category HTTP Server
* @experimental
*/
export interface ServeHandlerInfo {
/** The remote address of the connection. */
remoteAddr: Deno.NetAddr;
/** The completion promise */
completed: Promise<void>;
}
/** **UNSTABLE**: New API, yet to be vetted.
*
* Retrieve the process umask. If `mask` is provided, sets the process umask.
@ -852,80 +840,6 @@ declare namespace Deno {
present(): void;
}
/** **UNSTABLE**: New API, yet to be vetted.
*
* These are unstable options which can be used with {@linkcode Deno.run}.
*
* @category Subprocess
* @experimental
*/
export interface UnstableRunOptions extends RunOptions {
/** If `true`, clears the environment variables before executing the
* sub-process.
*
* @default {false} */
clearEnv?: boolean;
/** For POSIX systems, sets the group ID for the sub process. */
gid?: number;
/** For POSIX systems, sets the user ID for the sub process. */
uid?: number;
}
/** **UNSTABLE**: New API, yet to be vetted.
*
* Spawns new subprocess. RunOptions must contain at a minimum the `opt.cmd`,
* an array of program arguments, the first of which is the binary.
*
* ```ts
* const p = Deno.run({
* cmd: ["curl", "https://example.com"],
* });
* const status = await p.status();
* ```
*
* Subprocess uses same working directory as parent process unless `opt.cwd`
* is specified.
*
* Environmental variables from parent process can be cleared using `opt.clearEnv`.
* Doesn't guarantee that only `opt.env` variables are present,
* as the OS may set environmental variables for processes.
*
* Environmental variables for subprocess can be specified using `opt.env`
* mapping.
*
* `opt.uid` sets the child processs user ID. This translates to a setuid call
* in the child process. Failure in the setuid call will cause the spawn to fail.
*
* `opt.gid` is similar to `opt.uid`, but sets the group ID of the child process.
* This has the same semantics as the uid field.
*
* By default subprocess inherits stdio of parent process. To change
* this this, `opt.stdin`, `opt.stdout`, and `opt.stderr` can be set
* independently to a resource ID (_rid_) of an open file, `"inherit"`,
* `"piped"`, or `"null"`:
*
* - _number_: the resource ID of an open file/resource. This allows you to
* read or write to a file.
* - `"inherit"`: The default if unspecified. The subprocess inherits from the
* parent.
* - `"piped"`: A new pipe should be arranged to connect the parent and child
* sub-process.
* - `"null"`: This stream will be ignored. This is the equivalent of attaching
* the stream to `/dev/null`.
*
* Details of the spawned process are returned as an instance of
* {@linkcode Deno.Process}.
*
* Requires `allow-run` permission.
*
* @tags allow-run
* @category Subprocess
* @experimental
*/
export function run<T extends UnstableRunOptions = UnstableRunOptions>(
opt: T,
): Process<T>;
/** **UNSTABLE**: New API, yet to be vetted.
*
* A custom `HttpClient` for use with {@linkcode fetch} function. This is
@ -1216,44 +1130,6 @@ declare namespace Deno {
options: UnixListenOptions & { transport: "unixpacket" },
): DatagramConn;
/** **UNSTABLE**: New API, yet to be vetted.
*
* Acquire an advisory file-system lock for the provided file.
*
* @param [exclusive=false]
* @category File System
* @experimental
*/
export function flock(rid: number, exclusive?: boolean): Promise<void>;
/** **UNSTABLE**: New API, yet to be vetted.
*
* Acquire an advisory file-system lock synchronously for the provided file.
*
* @param [exclusive=false]
* @category File System
* @experimental
*/
export function flockSync(rid: number, exclusive?: boolean): void;
/** **UNSTABLE**: New API, yet to be vetted.
*
* Release an advisory file-system lock for the provided file.
*
* @category File System
* @experimental
*/
export function funlock(rid: number): Promise<void>;
/** **UNSTABLE**: New API, yet to be vetted.
*
* Release an advisory file-system lock for the provided file synchronously.
*
* @category File System
* @experimental
*/
export function funlockSync(rid: number): void;
/** **UNSTABLE**: New API, yet to be vetted.
*
* Open a new {@linkcode Deno.Kv} connection to persist data.
@ -2163,7 +2039,10 @@ declare namespace Deno {
* @category Jupyter
* @experimental
*/
export function display(obj: unknown, options?: DisplayOptions): void;
export function display(
obj: unknown,
options?: DisplayOptions,
): Promise<void>;
/**
* Show Markdown in Jupyter frontends with a tagged template function.
@ -2236,12 +2115,12 @@ declare namespace Deno {
* Format an object for displaying in Deno
*
* @param obj - The object to be displayed
* @returns MediaBundle
* @returns Promise<MediaBundle>
*
* @category Jupyter
* @experimental
*/
export function format(obj: unknown): MediaBundle;
export function format(obj: unknown): Promise<MediaBundle>;
/**
* Broadcast a message on IO pub channel.

View file

@ -17550,7 +17550,7 @@ declare var PerformanceServerTiming: {
};
/**
* A legacy interface kept for backwards compatibility and contains properties that offer performance timing information for various events which occur during the loading and use of the current page. You get a PerformanceTiming object describing your page using the window.performance.timing property.
* A legacy interface kept for backwards compatibility and contains properties that offer performance timing information for various events which occur during the loading and use of the current page. You get a PerformanceTiming object describing your page using the globalThis.performance.timing property.
* @deprecated This interface is deprecated in the Navigation Timing Level 2 specification. Please use the PerformanceNavigationTiming interface instead.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceTiming)
@ -17833,7 +17833,7 @@ declare var Plugin: {
};
/**
* Used to store a list of Plugin objects describing the available plugins; it's returned by the window.navigator.plugins property. The PluginArray is not a JavaScript array, but has the length property and supports accessing individual items using bracket notation (plugins[2]), as well as via item(index) and namedItem("name") methods.
* Used to store a list of Plugin objects describing the available plugins; it's returned by the globalThis.navigator.plugins property. The PluginArray is not a JavaScript array, but has the length property and supports accessing individual items using bracket notation (plugins[2]), as well as via item(index) and namedItem("name") methods.
* @deprecated
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PluginArray)
@ -22155,7 +22155,7 @@ declare var SubmitEvent: {
};
/**
* This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto).
* This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via globalThis.crypto).
* Available only in secure contexts.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)

View file

@ -5407,7 +5407,7 @@ declare var StylePropertyMapReadOnly: {
};
/**
* This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto).
* This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via globalThis.crypto).
* Available only in secure contexts.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)
@ -8641,7 +8641,7 @@ declare var WorkerLocation: {
};
/**
* A subset of the Navigator interface allowed to be accessed from a Worker. Such an object is initialized for each worker and is available via the WorkerGlobalScope.navigator property obtained by calling window.self.navigator.
* A subset of the Navigator interface allowed to be accessed from a Worker. Such an object is initialized for each worker and is available via the WorkerGlobalScope.navigator property obtained by calling globalThis.self.navigator.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WorkerNavigator)
*/

View file

@ -795,7 +795,7 @@ fn resolve_graph_specifier_types(
}
fn resolve_non_graph_specifier_types(
specifier: &str,
raw_specifier: &str,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
state: &State,
@ -810,14 +810,16 @@ fn resolve_non_graph_specifier_types(
Ok(Some(NodeResolution::into_specifier_and_media_type(
node_resolver
.resolve(
specifier,
raw_specifier,
referrer,
referrer_kind,
NodeResolutionMode::Types,
)
.ok(),
)))
} else if let Ok(npm_req_ref) = NpmPackageReqReference::from_str(specifier) {
} else if let Ok(npm_req_ref) =
NpmPackageReqReference::from_str(raw_specifier)
{
debug_assert_eq!(referrer_kind, NodeModuleKind::Esm);
// todo(dsherret): add support for injecting this in the graph so
// we don't need this special code here.

View file

@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::env::current_dir;
use std::fs::OpenOptions;
use std::io::Error;
use std::io::ErrorKind;
@ -18,7 +17,6 @@ use deno_config::glob::WalkEntry;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
pub use deno_core::normalize_path;
use deno_core::unsync::spawn_blocking;
use deno_core::ModuleSpecifier;
use deno_runtime::deno_fs::FileSystem;
@ -255,18 +253,6 @@ fn canonicalize_path_maybe_not_exists_with_custom_fn(
}
}
pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
let resolved_path = if path.is_absolute() {
path.to_owned()
} else {
let cwd =
current_dir().context("Failed to get current working directory")?;
cwd.join(path)
};
Ok(normalize_path(resolved_path))
}
/// Collects module specifiers that satisfy the given predicate as a file path, by recursively walking `include`.
/// Specifiers that start with http and https are left intact.
/// Note: This ignores all .git and node_modules folders.
@ -510,9 +496,9 @@ pub fn hard_link_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> {
}
pub fn symlink_dir(oldpath: &Path, newpath: &Path) -> Result<(), Error> {
let err_mapper = |err: Error| {
let err_mapper = |err: Error, kind: Option<ErrorKind>| {
Error::new(
err.kind(),
kind.unwrap_or_else(|| err.kind()),
format!(
"{}, symlink '{}' -> '{}'",
err,
@ -524,12 +510,19 @@ pub fn symlink_dir(oldpath: &Path, newpath: &Path) -> Result<(), Error> {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
symlink(oldpath, newpath).map_err(err_mapper)?;
symlink(oldpath, newpath).map_err(|e| err_mapper(e, None))?;
}
#[cfg(not(unix))]
{
use std::os::windows::fs::symlink_dir;
symlink_dir(oldpath, newpath).map_err(err_mapper)?;
symlink_dir(oldpath, newpath).map_err(|err| {
if let Some(code) = err.raw_os_error() {
if code as u32 == winapi::shared::winerror::ERROR_PRIVILEGE_NOT_HELD {
return err_mapper(err, Some(ErrorKind::PermissionDenied));
}
}
err_mapper(err, None)
})?;
}
Ok(())
}
@ -715,30 +708,13 @@ pub fn specifier_from_file_path(
mod tests {
use super::*;
use deno_core::futures;
use deno_core::normalize_path;
use deno_core::parking_lot::Mutex;
use pretty_assertions::assert_eq;
use test_util::PathRef;
use test_util::TempDir;
use tokio::sync::Notify;
#[test]
fn resolve_from_cwd_child() {
let cwd = current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a"));
}
#[test]
fn resolve_from_cwd_dot() {
let cwd = current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd);
}
#[test]
fn resolve_from_cwd_parent() {
let cwd = current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd);
}
#[test]
fn test_normalize_path() {
assert_eq!(normalize_path(Path::new("a/../b")), PathBuf::from("b"));
@ -756,14 +732,6 @@ mod tests {
}
}
#[test]
fn resolve_from_cwd_absolute() {
let expected = Path::new("a");
let cwd = current_dir().unwrap();
let absolute_expected = cwd.join(expected);
assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected);
}
#[test]
fn test_collect_specifiers() {
fn create_files(dir_path: &PathRef, files: &[&str]) {

View file

@ -41,8 +41,10 @@ impl log::Log for CliLogger {
pub fn init(maybe_level: Option<log::Level>) {
let log_level = maybe_level.unwrap_or(log::Level::Info);
let logger = env_logger::Builder::from_env(
env_logger::Env::default()
.default_filter_or(log_level.to_level_filter().to_string()),
env_logger::Env::new()
// Use `DENO_LOG` and `DENO_LOG_STYLE` instead of `RUST_` prefix
.filter_or("DENO_LOG", log_level.to_level_filter().to_string())
.write_style("DENO_LOG_STYLE"),
)
// https://github.com/denoland/deno/issues/6641
.filter_module("rustyline", log::LevelFilter::Off)

View file

@ -42,6 +42,21 @@ pub fn get_extension(file_path: &Path) -> Option<String> {
.map(|e| e.to_lowercase());
}
pub fn specifier_has_extension(
specifier: &ModuleSpecifier,
searching_ext: &str,
) -> bool {
let Some((_, ext)) = specifier.path().rsplit_once('.') else {
return false;
};
let searching_ext = searching_ext.strip_prefix('.').unwrap_or(searching_ext);
debug_assert!(!searching_ext.contains('.')); // exts like .d.ts are not implemented here
if ext.len() != searching_ext.len() {
return false;
}
ext.eq_ignore_ascii_case(searching_ext)
}
pub fn get_atomic_dir_path(file_path: &Path) -> PathBuf {
let rand = gen_rand_path_component();
let new_file_name = format!(
@ -145,34 +160,6 @@ pub fn relative_specifier(
Some(to_percent_decoded_str(&text))
}
/// Gets a path with the specified file stem suffix.
///
/// Ex. `file.ts` with suffix `_2` returns `file_2.ts`
pub fn path_with_stem_suffix(path: &Path, suffix: &str) -> PathBuf {
if let Some(file_name) = path.file_name().map(|f| f.to_string_lossy()) {
if let Some(file_stem) = path.file_stem().map(|f| f.to_string_lossy()) {
if let Some(ext) = path.extension().map(|f| f.to_string_lossy()) {
return if file_stem.to_lowercase().ends_with(".d") {
path.with_file_name(format!(
"{}{}.{}.{}",
&file_stem[..file_stem.len() - ".d".len()],
suffix,
// maintain casing
&file_stem[file_stem.len() - "d".len()..],
ext
))
} else {
path.with_file_name(format!("{file_stem}{suffix}.{ext}"))
};
}
}
path.with_file_name(format!("{file_name}{suffix}"))
} else {
path.with_file_name(suffix)
}
}
#[cfg_attr(windows, allow(dead_code))]
pub fn relative_path(from: &Path, to: &Path) -> Option<PathBuf> {
pathdiff::diff_paths(to, from)
@ -406,43 +393,15 @@ mod test {
}
#[test]
fn test_path_with_stem_suffix() {
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/"), "_2"),
PathBuf::from("/_2")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test"), "_2"),
PathBuf::from("/test_2")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test.txt"), "_2"),
PathBuf::from("/test_2.txt")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test/subdir"), "_2"),
PathBuf::from("/test/subdir_2")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test/subdir.other.txt"), "_2"),
PathBuf::from("/test/subdir.other_2.txt")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test.d.ts"), "_2"),
PathBuf::from("/test_2.d.ts")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test.D.TS"), "_2"),
PathBuf::from("/test_2.D.TS")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test.d.mts"), "_2"),
PathBuf::from("/test_2.d.mts")
);
assert_eq!(
path_with_stem_suffix(&PathBuf::from("/test.d.cts"), "_2"),
PathBuf::from("/test_2.d.cts")
);
fn test_specifier_has_extension() {
fn get(specifier: &str, ext: &str) -> bool {
specifier_has_extension(&ModuleSpecifier::parse(specifier).unwrap(), ext)
}
assert!(get("file:///a/b/c.ts", "ts"));
assert!(get("file:///a/b/c.ts", ".ts"));
assert!(!get("file:///a/b/c.ts", ".cts"));
assert!(get("file:///a/b/c.CtS", ".cts"));
}
#[test]

View file

@ -9,6 +9,8 @@ const TYPESCRIPT: &str = env!("TS_VERSION");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
// TODO(bartlomieju): ideally we could remove this const.
const IS_CANARY: bool = option_env!("DENO_CANARY").is_some();
// TODO(bartlomieju): this is temporary, to allow Homebrew to cut RC releases as well
const IS_RC: bool = option_env!("DENO_RC").is_some();
pub static DENO_VERSION_INFO: Lazy<DenoVersionInfo> = Lazy::new(|| {
let release_channel = libsui::find_section("denover")
@ -17,6 +19,8 @@ pub static DENO_VERSION_INFO: Lazy<DenoVersionInfo> = Lazy::new(|| {
.unwrap_or({
if IS_CANARY {
ReleaseChannel::Canary
} else if IS_RC {
ReleaseChannel::Rc
} else {
ReleaseChannel::Stable
}

View file

@ -116,6 +116,9 @@ pub struct CliMainWorkerOptions {
pub skip_op_registration: bool,
pub create_hmr_runner: Option<CreateHmrRunnerCb>,
pub create_coverage_collector: Option<CreateCoverageCollectorCb>,
pub node_ipc: Option<i64>,
pub serve_port: Option<u16>,
pub serve_host: Option<String>,
}
struct SharedWorkerState {
@ -135,13 +138,7 @@ struct SharedWorkerState {
maybe_inspector_server: Option<Arc<InspectorServer>>,
maybe_lockfile: Option<Arc<CliLockfile>>,
feature_checker: Arc<FeatureChecker>,
node_ipc: Option<i64>,
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
serve_port: Option<u16>,
serve_host: Option<String>,
}
impl SharedWorkerState {
@ -434,14 +431,8 @@ impl CliMainWorkerFactory {
maybe_inspector_server: Option<Arc<InspectorServer>>,
maybe_lockfile: Option<Arc<CliLockfile>>,
feature_checker: Arc<FeatureChecker>,
options: CliMainWorkerOptions,
node_ipc: Option<i64>,
serve_port: Option<u16>,
serve_host: Option<String>,
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
options: CliMainWorkerOptions,
) -> Self {
Self {
shared: Arc::new(SharedWorkerState {
@ -461,12 +452,6 @@ impl CliMainWorkerFactory {
maybe_inspector_server,
maybe_lockfile,
feature_checker,
node_ipc,
serve_port,
serve_host,
enable_future_features,
disable_deprecated_api_warning,
verbose_deprecated_api_warning,
code_cache,
}),
}
@ -602,13 +587,10 @@ impl CliMainWorkerFactory {
has_node_modules_dir: shared.options.has_node_modules_dir,
argv0: shared.options.argv0.clone(),
node_debug: shared.options.node_debug.clone(),
node_ipc_fd: shared.node_ipc,
disable_deprecated_api_warning: shared.disable_deprecated_api_warning,
verbose_deprecated_api_warning: shared.verbose_deprecated_api_warning,
future: shared.enable_future_features,
node_ipc_fd: shared.options.node_ipc,
mode,
serve_port: shared.serve_port,
serve_host: shared.serve_host.clone(),
serve_port: shared.options.serve_port,
serve_host: shared.options.serve_host.clone(),
},
extensions: custom_extensions,
startup_snapshot: crate::js::deno_isolate_init(),
@ -801,12 +783,9 @@ fn create_web_worker_callback(
argv0: shared.options.argv0.clone(),
node_debug: shared.options.node_debug.clone(),
node_ipc_fd: None,
disable_deprecated_api_warning: shared.disable_deprecated_api_warning,
verbose_deprecated_api_warning: shared.verbose_deprecated_api_warning,
future: shared.enable_future_features,
mode: WorkerExecutionMode::Worker,
serve_port: shared.serve_port,
serve_host: shared.serve_host.clone(),
serve_port: shared.options.serve_port,
serve_host: shared.options.serve_host.clone(),
},
extensions: vec![],
startup_snapshot: crate::js::deno_isolate_init(),

View file

@ -2,7 +2,7 @@
[package]
name = "deno_broadcast_channel"
version = "0.160.0"
version = "0.162.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

View file

@ -105,7 +105,7 @@ class Cache {
const reqUrl = new URL(innerRequest.url());
if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") {
throw new TypeError(
"Request url protocol must be 'http:' or 'https:'",
`Request url protocol must be 'http:' or 'https:': received '${reqUrl.protocol}'`,
);
}
if (innerRequest.method !== "GET") {

View file

@ -2,7 +2,7 @@
[package]
name = "deno_cache"
version = "0.98.0"
version = "0.100.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

2
ext/cache/lib.rs vendored
View file

@ -211,7 +211,7 @@ where
state.put(cache);
Ok(state.borrow::<CA>().clone())
} else {
Err(type_error("CacheStorage is not available in this context."))
Err(type_error("CacheStorage is not available in this context"))
}
}

View file

@ -2,7 +2,7 @@
[package]
name = "deno_canvas"
version = "0.35.0"
version = "0.37.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

View file

@ -179,7 +179,7 @@ class AssertionError extends Error {
}
}
function assert(cond, msg = "Assertion failed.") {
function assert(cond, msg = "Assertion failed") {
if (!cond) {
throw new AssertionError(msg);
}
@ -260,6 +260,7 @@ const colors = {
function defineColorAlias(target, alias) {
ObjectDefineProperty(colors, alias, {
__proto__: null,
get() {
return this[target];
},
@ -3236,8 +3237,8 @@ class Console {
table = (data = undefined, properties = undefined) => {
if (properties !== undefined && !ArrayIsArray(properties)) {
throw new Error(
"The 'properties' argument must be of type Array. " +
"Received type " + typeof properties,
"The 'properties' argument must be of type Array: " +
"received type " + typeof properties,
);
}
@ -3447,7 +3448,10 @@ function inspect(
function createFilteredInspectProxy({ object, keys, evaluate }) {
const obj = class {};
if (object.constructor?.name) {
ObjectDefineProperty(obj, "name", { value: object.constructor.name });
ObjectDefineProperty(obj, "name", {
__proto__: null,
value: object.constructor.name,
});
}
return new Proxy(new obj(), {

View file

@ -2,7 +2,7 @@
[package]
name = "deno_console"
version = "0.166.0"
version = "0.168.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

View file

@ -41,7 +41,9 @@ export function formatToCronSchedule(
} else if (end === undefined && every !== undefined) {
return "*/" + every;
} else {
throw new TypeError("Invalid cron schedule");
throw new TypeError(
`Invalid cron schedule: start=${start}, end=${end}, every=${every}`,
);
}
} else {
if (typeof exact === "number") {
@ -103,10 +105,14 @@ function cron(
handler2?: () => Promise<void> | void,
) {
if (name === undefined) {
throw new TypeError("Deno.cron requires a unique name");
throw new TypeError(
"Cannot create cron job, a unique name is required: received 'undefined'",
);
}
if (schedule === undefined) {
throw new TypeError("Deno.cron requires a valid schedule");
throw new TypeError(
"Cannot create cron job, a schedule is required: received 'undefined'",
);
}
schedule = parseScheduleToString(schedule);
@ -119,13 +125,15 @@ function cron(
if (typeof handlerOrOptions1 === "function") {
handler = handlerOrOptions1;
if (handler2 !== undefined) {
throw new TypeError("Deno.cron requires a single handler");
throw new TypeError(
"Cannot create cron job, a single handler is required: two handlers were specified",
);
}
} else if (typeof handler2 === "function") {
handler = handler2;
options = handlerOrOptions1;
} else {
throw new TypeError("Deno.cron requires a handler");
throw new TypeError("Cannot create cron job: a handler is required");
}
const rid = op_cron_create(

View file

@ -2,7 +2,7 @@
[package]
name = "deno_cron"
version = "0.46.0"
version = "0.48.0"
authors.workspace = true
edition.workspace = true
license.workspace = true

Some files were not shown because too many files have changed in this diff Show more