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

Merge branch 'main' into lint_skip_minified_files

This commit is contained in:
Bartek Iwańczuk 2024-11-30 02:19:30 +01:00
commit ae87b37215
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750
2093 changed files with 21808 additions and 16360 deletions

View file

@ -31,6 +31,8 @@
"cli/tsc/dts/lib.scripthost.d.ts", "cli/tsc/dts/lib.scripthost.d.ts",
"cli/tsc/dts/lib.webworker*.d.ts", "cli/tsc/dts/lib.webworker*.d.ts",
"cli/tsc/dts/typescript.d.ts", "cli/tsc/dts/typescript.d.ts",
"cli/tools/doc/prism.css",
"cli/tools/doc/prism.js",
"ext/websocket/autobahn/reports", "ext/websocket/autobahn/reports",
"gh-pages", "gh-pages",
"target", "target",

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. // Bump this number when you want to purge the cache.
// Note: the tools/release/01_bump_crate_versions.ts script will update this version // 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. // automatically via regex, so ensure that this line maintains this format.
const cacheVersion = 25; const cacheVersion = 28;
const ubuntuX86Runner = "ubuntu-24.04"; const ubuntuX86Runner = "ubuntu-24.04";
const ubuntuX86XlRunner = "ubuntu-24.04-xl"; const ubuntuX86XlRunner = "ubuntu-24.04-xl";

View file

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

821
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@ members = [
"ext/napi/sym", "ext/napi/sym",
"ext/net", "ext/net",
"ext/node", "ext/node",
"ext/telemetry",
"ext/url", "ext/url",
"ext/web", "ext/web",
"ext/webgpu", "ext/webgpu",
@ -45,19 +46,20 @@ license = "MIT"
repository = "https://github.com/denoland/deno" repository = "https://github.com/denoland/deno"
[workspace.dependencies] [workspace.dependencies]
deno_ast = { version = "=0.43.3", features = ["transpiling"] } deno_ast = { version = "=0.44.0", features = ["transpiling"] }
deno_core = { version = "0.319.0" } deno_core = { version = "0.323.0" }
deno_bench_util = { version = "0.171.0", path = "./bench_util" } deno_bench_util = { version = "0.174.0", path = "./bench_util" }
deno_lockfile = "=0.23.1" deno_config = { version = "=0.39.3", features = ["workspace", "sync"] }
deno_lockfile = "=0.23.2"
deno_media_type = { version = "0.2.0", features = ["module_specifier"] } deno_media_type = { version = "0.2.0", features = ["module_specifier"] }
deno_npm = "=0.25.4" deno_npm = "=0.25.5"
deno_path_util = "=0.2.1" deno_path_util = "=0.2.1"
deno_permissions = { version = "0.37.0", path = "./runtime/permissions" } deno_permissions = { version = "0.40.0", path = "./runtime/permissions" }
deno_runtime = { version = "0.186.0", path = "./runtime" } deno_runtime = { version = "0.189.0", path = "./runtime" }
deno_semver = "=0.5.16" deno_semver = "=0.6.0"
deno_terminal = "0.2.0" deno_terminal = "0.2.0"
napi_sym = { version = "0.107.0", path = "./ext/napi/sym" } napi_sym = { version = "0.110.0", path = "./ext/napi/sym" }
test_util = { package = "test_server", path = "./tests/util/server" } test_util = { package = "test_server", path = "./tests/util/server" }
denokv_proto = "0.8.4" denokv_proto = "0.8.4"
@ -66,32 +68,33 @@ denokv_remote = "0.8.4"
denokv_sqlite = { default-features = false, version = "0.8.4" } denokv_sqlite = { default-features = false, version = "0.8.4" }
# exts # exts
deno_broadcast_channel = { version = "0.171.0", path = "./ext/broadcast_channel" } deno_broadcast_channel = { version = "0.174.0", path = "./ext/broadcast_channel" }
deno_cache = { version = "0.109.0", path = "./ext/cache" } deno_cache = { version = "0.112.0", path = "./ext/cache" }
deno_canvas = { version = "0.46.0", path = "./ext/canvas" } deno_canvas = { version = "0.49.0", path = "./ext/canvas" }
deno_console = { version = "0.177.0", path = "./ext/console" } deno_console = { version = "0.180.0", path = "./ext/console" }
deno_cron = { version = "0.57.0", path = "./ext/cron" } deno_cron = { version = "0.60.0", path = "./ext/cron" }
deno_crypto = { version = "0.191.0", path = "./ext/crypto" } deno_crypto = { version = "0.194.0", path = "./ext/crypto" }
deno_fetch = { version = "0.201.0", path = "./ext/fetch" } deno_fetch = { version = "0.204.0", path = "./ext/fetch" }
deno_ffi = { version = "0.164.0", path = "./ext/ffi" } deno_ffi = { version = "0.167.0", path = "./ext/ffi" }
deno_fs = { version = "0.87.0", path = "./ext/fs" } deno_fs = { version = "0.90.0", path = "./ext/fs" }
deno_http = { version = "0.175.0", path = "./ext/http" } deno_http = { version = "0.178.0", path = "./ext/http" }
deno_io = { version = "0.87.0", path = "./ext/io" } deno_io = { version = "0.90.0", path = "./ext/io" }
deno_kv = { version = "0.85.0", path = "./ext/kv" } deno_kv = { version = "0.88.0", path = "./ext/kv" }
deno_napi = { version = "0.108.0", path = "./ext/napi" } deno_napi = { version = "0.111.0", path = "./ext/napi" }
deno_net = { version = "0.169.0", path = "./ext/net" } deno_net = { version = "0.172.0", path = "./ext/net" }
deno_node = { version = "0.114.0", path = "./ext/node" } deno_node = { version = "0.117.0", path = "./ext/node" }
deno_tls = { version = "0.164.0", path = "./ext/tls" } deno_telemetry = { version = "0.2.0", path = "./ext/telemetry" }
deno_url = { version = "0.177.0", path = "./ext/url" } deno_tls = { version = "0.167.0", path = "./ext/tls" }
deno_web = { version = "0.208.0", path = "./ext/web" } deno_url = { version = "0.180.0", path = "./ext/url" }
deno_webgpu = { version = "0.144.0", path = "./ext/webgpu" } deno_web = { version = "0.211.0", path = "./ext/web" }
deno_webidl = { version = "0.177.0", path = "./ext/webidl" } deno_webgpu = { version = "0.147.0", path = "./ext/webgpu" }
deno_websocket = { version = "0.182.0", path = "./ext/websocket" } deno_webidl = { version = "0.180.0", path = "./ext/webidl" }
deno_webstorage = { version = "0.172.0", path = "./ext/webstorage" } deno_websocket = { version = "0.185.0", path = "./ext/websocket" }
deno_webstorage = { version = "0.175.0", path = "./ext/webstorage" }
# resolvers # resolvers
deno_resolver = { version = "0.9.0", path = "./resolvers/deno" } deno_resolver = { version = "0.12.0", path = "./resolvers/deno" }
node_resolver = { version = "0.16.0", path = "./resolvers/node" } node_resolver = { version = "0.19.0", path = "./resolvers/node" }
aes = "=0.8.3" aes = "=0.8.3"
anyhow = "1.0.57" anyhow = "1.0.57"
@ -99,6 +102,7 @@ async-trait = "0.1.73"
base32 = "=0.5.1" base32 = "=0.5.1"
base64 = "0.21.7" base64 = "0.21.7"
bencher = "0.1" bencher = "0.1"
boxed_error = "0.2.2"
brotli = "6.0.0" brotli = "6.0.0"
bytes = "1.4.0" bytes = "1.4.0"
cache_control = "=0.2.0" cache_control = "=0.2.0"
@ -111,8 +115,8 @@ console_static_text = "=0.8.1"
dashmap = "5.5.3" dashmap = "5.5.3"
data-encoding = "2.3.3" data-encoding = "2.3.3"
data-url = "=0.3.0" data-url = "=0.3.0"
deno_cache_dir = "=0.13.2" deno_cache_dir = "=0.14.0"
deno_package_json = { version = "0.1.2", default-features = false } deno_package_json = { version = "0.2.1", default-features = false }
dlopen2 = "0.6.1" dlopen2 = "0.6.1"
ecb = "=0.1.2" ecb = "=0.1.2"
elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] } elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] }
@ -126,6 +130,7 @@ fs3 = "0.5.0"
futures = "0.3.21" futures = "0.3.21"
glob = "0.3.1" glob = "0.3.1"
h2 = "0.4.4" h2 = "0.4.4"
hickory-resolver = { version = "0.24", features = ["tokio-runtime", "serde-config"] }
http = "1.0" http = "1.0"
http-body = "1.0" http-body = "1.0"
http-body-util = "0.1.2" http-body-util = "0.1.2"
@ -141,7 +146,7 @@ jsonc-parser = { version = "=0.26.2", features = ["serde"] }
lazy-regex = "3" lazy-regex = "3"
libc = "0.2.126" libc = "0.2.126"
libz-sys = { version = "1.1.20", default-features = false } libz-sys = { version = "1.1.20", default-features = false }
log = "0.4.20" log = { version = "0.4.20", features = ["kv"] }
lsp-types = "=0.97.0" # used by tower-lsp and "proposed" feature is unstable in patch releases lsp-types = "=0.97.0" # used by tower-lsp and "proposed" feature is unstable in patch releases
memmem = "0.1.1" memmem = "0.1.1"
monch = "=0.5.0" monch = "=0.5.0"
@ -197,8 +202,7 @@ tower-http = { version = "0.6.1", features = ["decompression-br", "decompression
tower-lsp = { package = "deno_tower_lsp", version = "0.1.0", features = ["proposed"] } tower-lsp = { package = "deno_tower_lsp", version = "0.1.0", features = ["proposed"] }
tower-service = "0.3.2" tower-service = "0.3.2"
twox-hash = "=1.6.3" twox-hash = "=1.6.3"
# Upgrading past 2.4.1 may cause WPT failures url = { version = "2.5", features = ["serde", "expose_internals"] }
url = { version = "< 2.5.0", features = ["serde", "expose_internals"] }
uuid = { version = "1.3.0", features = ["v4"] } uuid = { version = "1.3.0", features = ["v4"] }
webpki-root-certs = "0.26.5" webpki-root-certs = "0.26.5"
webpki-roots = "0.26" webpki-roots = "0.26"

View file

@ -6,6 +6,130 @@ https://github.com/denoland/deno/releases
We also have one-line install commands at: We also have one-line install commands at:
https://github.com/denoland/deno_install https://github.com/denoland/deno_install
### 2.1.2 / 2024.11.28
- feat(unstable): Instrument Deno.serve (#26964)
- feat(unstable): Instrument fetch (#27057)
- feat(unstable): repurpose `--unstable-detect-cjs` to attempt loading more
modules as cjs (#27094)
- fix(check): support jsdoc `@import` tag (#26991)
- fix(compile): correct buffered reading of assets and files (#27008)
- fix(compile): do not error embedding same symlink via multiple methods
(#27015)
- fix(compile): handle TypeScript file included as asset (#27032)
- fix(ext/fetch): don't throw when `bodyUsed` inspect after upgrade (#27088)
- fix(ext/node): `tls.connect` socket upgrades (#27125)
- fix(ext/node): add `fs.promises.fstat` and `FileHandle#stat` (#26719)
- fix(ext/webgpu): normalize limits to number (#27072)
- fix(ext/webgpu): use correct variable name (#27108)
- fix(ext/websocket): don't throw exception when sending to closed socket
(#26932)
- fix(fmt): return `None` if sql fmt result is the same (#27014)
- fix(info): resolve bare specifier pointing to workspace member (#27020)
- fix(init): always force managed node modules (#27047)
- fix(init): support scoped npm packages (#27128)
- fix(install): don't re-set up node_modules if running lifecycle script
(#26984)
- fix(lsp): remove stray debug output (#27010)
- fix(lsp): support task object notation for tasks request (#27076)
- fix(lsp): wasm file import completions (#27018)
- fix(node): correct resolution of dynamic import of esm from cjs (#27071)
- fix(node/fs): add missing stat path argument validation (#27086)
- fix(node/fs): missing uv error context for readFile (#27011)
- fix(node/http): casing ignored in ServerResponse.hasHeader() (#27105)
- fix(node/timers): error when passing id to clearTimeout/clearInterval (#27130)
- fix(runtime/ops): Fix watchfs remove event (#27041)
- fix(streams): reject `string` in `ReadableStream.from` type (#25116)
- fix(task): handle carriage return in task description (#27099)
- fix(task): handle multiline descriptions properly (#27069)
- fix(task): strip ansi codes and control chars when printing tasks (#27100)
- fix(tools/doc): HTML resolve main entrypoint from config file (#27103)
- fix: support bun specifiers in JSR publish (#24588)
- fix: support non-function exports in Wasm modules (#26992)
- perf(compile): read embedded files as static references when UTF-8 and reading
as strings (#27033)
- perf(ext/webstorage): use object wrap for `Storage` (#26931)
### 2.1.1 / 2024.11.21
- docs(add): clarification to add command (#26968)
- docs(doc): fix typo in doc subcommand help output (#26321)
- fix(node): regression where ts files were sometimes resolved instead of js
(#26971)
- fix(task): ensure root config always looks up dependencies in root (#26959)
- fix(watch): don't panic if there's no path provided (#26972)
- fix: Buffer global in --unstable-node-globals (#26973)
### 2.1.0 / 2024.11.21
- feat(cli): add `--unstable-node-globals` flag (#26617)
- feat(cli): support multiple env file argument (#26527)
- feat(compile): ability to embed directory in executable (#26939)
- feat(compile): ability to embed local data files (#26934)
- feat(ext/fetch): Make fetch client parameters configurable (#26909)
- feat(ext/fetch): allow embedders to use `hickory_dns_resolver` instead of
default `GaiResolver` (#26740)
- feat(ext/fs): add ctime to Deno.stats and use it in node compat layer (#24801)
- feat(ext/http): Make http server parameters configurable (#26785)
- feat(ext/node): perf_hooks.monitorEventLoopDelay() (#26905)
- feat(fetch): accept async iterables for body (#26882)
- feat(fmt): support SQL (#26750)
- feat(info): show location for Web Cache (#26205)
- feat(init): add --npm flag to initialize npm projects (#26896)
- feat(jupyter): Add `Deno.jupyter.image` API (#26284)
- feat(lint): Add checked files list to the JSON output(#26936)
- feat(lsp): auto-imports with @deno-types directives (#26821)
- feat(node): stabilize detecting if CJS via `"type": "commonjs"` in a
package.json (#26439)
- feat(permission): support suffix wildcards in `--allow-env` flag (#25255)
- feat(publish): add `--set-version <version>` flag (#26141)
- feat(runtime): remove public OTEL trace API (#26854)
- feat(task): add --eval flag (#26943)
- feat(task): dependencies (#26467)
- feat(task): support object notation, remove support for JSDocs (#26886)
- feat(task): workspace support with --filter and --recursive (#26949)
- feat(watch): log which file changed on HMR or watch change (#25801)
- feat: OpenTelemetry Tracing API and Exporting (#26710)
- feat: Wasm module support (#26668)
- feat: fmt and lint respect .gitignore file (#26897)
- feat: permission stack traces in ops (#26938)
- feat: subcommand to view and update outdated dependencies (#26942)
- feat: upgrade V8 to 13.0 (#26851)
- fix(cli): preserve comments in doc tests (#26828)
- fix(cli): show prefix hint when installing a package globally (#26629)
- fix(ext/cache): gracefully error when cache creation failed (#26895)
- fix(ext/http): prefer brotli for `accept-encoding: gzip, deflate, br, zstd`
(#26814)
- fix(ext/node): New async setInterval function to improve the nodejs
compatibility (#26703)
- fix(ext/node): add autoSelectFamily option to net.createConnection (#26661)
- fix(ext/node): handle `--allow-sys=inspector` (#26836)
- fix(ext/node): increase tolerance for interval test (#26899)
- fix(ext/node): process.getBuiltinModule (#26833)
- fix(ext/node): use ERR_NOT_IMPLEMENTED for notImplemented (#26853)
- fix(ext/node): zlib.crc32() (#26856)
- fix(ext/webgpu): Create GPUQuerySet converter before usage (#26883)
- fix(ext/websocket): initialize `error` attribute of WebSocket ErrorEvent
(#26796)
- fix(ext/webstorage): use error class for sqlite error case (#26806)
- fix(fmt): error instead of panic on unstable format (#26859)
- fix(fmt): formatting of .svelte files (#26948)
- fix(install): percent encodings in interactive progress bar (#26600)
- fix(install): re-setup bin entries after running lifecycle scripts (#26752)
- fix(lockfile): track dependencies specified in TypeScript compiler options
(#26551)
- fix(lsp): ignore editor indent settings if deno.json is present (#26912)
- fix(lsp): skip code action edits that can't be converted (#26831)
- fix(node): handle resolving ".//<something>" in npm packages (#26920)
- fix(node/crypto): support promisify on generateKeyPair (#26913)
- fix(permissions): say to use --allow-run instead of --allow-all (#26842)
- fix(publish): improve error message when missing exports (#26945)
- fix: otel resiliency (#26857)
- fix: update message for unsupported schemes with npm and jsr (#26884)
- perf(compile): code cache (#26528)
- perf(windows): delay load webgpu and some other dlls (#26917)
- perf: use available system memory for v8 isolate memory limit (#26868)
### 2.0.6 / 2024.11.10 ### 2.0.6 / 2024.11.10
- feat(ext/http): abort event when request is cancelled (#26781) - feat(ext/http): abort event when request is cancelled (#26781)

View file

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

View file

@ -2,7 +2,7 @@
[package] [package]
name = "deno" name = "deno"
version = "2.0.6" version = "2.1.2"
authors.workspace = true authors.workspace = true
default-run = "deno" default-run = "deno"
edition.workspace = true edition.workspace = true
@ -69,12 +69,12 @@ winres.workspace = true
[dependencies] [dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_cache_dir = { workspace = true } deno_cache_dir.workspace = true
deno_config = { version = "=0.38.2", features = ["workspace", "sync"] } deno_config.workspace = true
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "0.156.0", default-features = false, features = ["rust", "html", "syntect"] } deno_doc = { version = "=0.161.2", features = ["rust", "comrak"] }
deno_graph = { version = "=0.84.1" } deno_graph = { version = "=0.86.3" }
deno_lint = { version = "=0.68.0", features = ["docs"] } deno_lint = { version = "=0.68.1", features = ["docs"] }
deno_lockfile.workspace = true deno_lockfile.workspace = true
deno_npm.workspace = true deno_npm.workspace = true
deno_package_json.workspace = true deno_package_json.workspace = true
@ -82,7 +82,8 @@ deno_path_util.workspace = true
deno_resolver.workspace = true deno_resolver.workspace = true
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_semver.workspace = true deno_semver.workspace = true
deno_task_shell = "=0.18.1" deno_task_shell = "=0.20.1"
deno_telemetry.workspace = true
deno_terminal.workspace = true deno_terminal.workspace = true
libsui = "0.5.0" libsui = "0.5.0"
node_resolver.workspace = true node_resolver.workspace = true
@ -107,7 +108,7 @@ dotenvy = "0.15.7"
dprint-plugin-json = "=0.19.4" dprint-plugin-json = "=0.19.4"
dprint-plugin-jupyter = "=0.1.5" dprint-plugin-jupyter = "=0.1.5"
dprint-plugin-markdown = "=0.17.8" dprint-plugin-markdown = "=0.17.8"
dprint-plugin-typescript = "=0.93.2" dprint-plugin-typescript = "=0.93.3"
env_logger = "=0.10.0" env_logger = "=0.10.0"
fancy-regex = "=0.10.0" fancy-regex = "=0.10.0"
faster-hex.workspace = true faster-hex.workspace = true
@ -129,7 +130,7 @@ libz-sys.workspace = true
log = { workspace = true, features = ["serde"] } log = { workspace = true, features = ["serde"] }
lsp-types.workspace = true lsp-types.workspace = true
malva = "=0.11.0" malva = "=0.11.0"
markup_fmt = "=0.15.0" markup_fmt = "=0.16.0"
memmem.workspace = true memmem.workspace = true
monch.workspace = true monch.workspace = true
notify.workspace = true notify.workspace = true
@ -151,6 +152,7 @@ serde_repr.workspace = true
sha2.workspace = true sha2.workspace = true
shell-escape = "=0.1.5" shell-escape = "=0.1.5"
spki = { version = "0.7", features = ["pem"] } spki = { version = "0.7", features = ["pem"] }
sqlformat = "=0.3.2"
strsim = "0.11.1" strsim = "0.11.1"
tar.workspace = true tar.workspace = true
tempfile.workspace = true tempfile.workspace = true

View file

@ -18,7 +18,7 @@ impl<'a> deno_config::fs::DenoConfigFs for DenoConfigFsAdapter<'a> {
fn read_to_string_lossy( fn read_to_string_lossy(
&self, &self,
path: &std::path::Path, path: &std::path::Path,
) -> Result<String, std::io::Error> { ) -> Result<std::borrow::Cow<'static, str>, std::io::Error> {
self self
.0 .0
.read_text_file_lossy_sync(path, None) .read_text_file_lossy_sync(path, None)
@ -70,7 +70,41 @@ pub fn deno_json_deps(
let values = imports_values(config.json.imports.as_ref()) let values = imports_values(config.json.imports.as_ref())
.into_iter() .into_iter()
.chain(scope_values(config.json.scopes.as_ref())); .chain(scope_values(config.json.scopes.as_ref()));
values_to_set(values) let mut set = values_to_set(values);
if let Some(serde_json::Value::Object(compiler_options)) =
&config.json.compiler_options
{
// add jsxImportSource
if let Some(serde_json::Value::String(value)) =
compiler_options.get("jsxImportSource")
{
if let Some(dep_req) = value_to_dep_req(value) {
set.insert(dep_req);
}
}
// add jsxImportSourceTypes
if let Some(serde_json::Value::String(value)) =
compiler_options.get("jsxImportSourceTypes")
{
if let Some(dep_req) = value_to_dep_req(value) {
set.insert(dep_req);
}
}
// add the dependencies in the types array
if let Some(serde_json::Value::Array(types)) = compiler_options.get("types")
{
for value in types {
if let serde_json::Value::String(value) = value {
if let Some(dep_req) = value_to_dep_req(value) {
set.insert(dep_req);
}
}
}
}
}
set
} }
fn imports_values(value: Option<&serde_json::Value>) -> Vec<&String> { fn imports_values(value: Option<&serde_json::Value>) -> Vec<&String> {
@ -98,15 +132,23 @@ fn values_to_set<'a>(
) -> HashSet<JsrDepPackageReq> { ) -> HashSet<JsrDepPackageReq> {
let mut entries = HashSet::new(); let mut entries = HashSet::new();
for value in values { for value in values {
if let Ok(req_ref) = JsrPackageReqReference::from_str(value) { if let Some(dep_req) = value_to_dep_req(value) {
entries.insert(JsrDepPackageReq::jsr(req_ref.into_inner().req)); entries.insert(dep_req);
} else if let Ok(req_ref) = NpmPackageReqReference::from_str(value) {
entries.insert(JsrDepPackageReq::npm(req_ref.into_inner().req));
} }
} }
entries entries
} }
fn value_to_dep_req(value: &str) -> Option<JsrDepPackageReq> {
if let Ok(req_ref) = JsrPackageReqReference::from_str(value) {
Some(JsrDepPackageReq::jsr(req_ref.into_inner().req))
} else if let Ok(req_ref) = NpmPackageReqReference::from_str(value) {
Some(JsrDepPackageReq::npm(req_ref.into_inner().req))
} else {
None
}
}
pub fn check_warn_tsconfig(ts_config: &TsConfigForEmit) { pub fn check_warn_tsconfig(ts_config: &TsConfigForEmit) {
if let Some(ignored_options) = &ts_config.maybe_ignored_options { if let Some(ignored_options) = &ts_config.maybe_ignored_options {
log::warn!("{}", ignored_options); log::warn!("{}", ignored_options);

File diff suppressed because it is too large Load diff

View file

@ -109,9 +109,12 @@ impl CliLockfile {
let Some(pkg_json) = maybe_pkg_json else { let Some(pkg_json) = maybe_pkg_json else {
return Default::default(); return Default::default();
}; };
pkg_json let deps = pkg_json.resolve_local_package_json_deps();
.resolve_local_package_json_deps()
deps
.dependencies
.values() .values()
.chain(deps.dev_dependencies.values())
.filter_map(|dep| dep.as_ref().ok()) .filter_map(|dep| dep.as_ref().ok())
.filter_map(|dep| match dep { .filter_map(|dep| match dep {
PackageJsonDepValue::Req(req) => { PackageJsonDepValue::Req(req) => {
@ -126,11 +129,7 @@ impl CliLockfile {
maybe_deno_json: Option<&ConfigFile>, maybe_deno_json: Option<&ConfigFile>,
) -> HashSet<JsrDepPackageReq> { ) -> HashSet<JsrDepPackageReq> {
maybe_deno_json maybe_deno_json
.map(|c| { .map(crate::args::deno_json::deno_json_deps)
crate::args::deno_json::deno_json_deps(c)
.into_iter()
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -28,8 +28,8 @@ use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
use deno_npm::NpmSystemInfo; use deno_npm::NpmSystemInfo;
use deno_path_util::normalize_path; use deno_path_util::normalize_path;
use deno_runtime::ops::otel::OtelConfig;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use deno_telemetry::OtelConfig;
use import_map::resolve_import_map_value_from_specifier; use import_map::resolve_import_map_value_from_specifier;
pub use deno_config::deno_json::BenchConfig; pub use deno_config::deno_json::BenchConfig;
@ -289,6 +289,7 @@ impl BenchOptions {
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct UnstableFmtOptions { pub struct UnstableFmtOptions {
pub component: bool, pub component: bool,
pub sql: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -322,6 +323,7 @@ impl FmtOptions {
options: resolve_fmt_options(fmt_flags, fmt_config.options), options: resolve_fmt_options(fmt_flags, fmt_config.options),
unstable: UnstableFmtOptions { unstable: UnstableFmtOptions {
component: unstable.component || fmt_flags.unstable_component, component: unstable.component || fmt_flags.unstable_component,
sql: unstable.sql || fmt_flags.unstable_sql,
}, },
files: fmt_config.files, files: fmt_config.files,
} }
@ -823,10 +825,8 @@ impl CliOptions {
}; };
let msg = let msg =
format!("DANGER: TLS certificate validation is disabled {}", domains); format!("DANGER: TLS certificate validation is disabled {}", domains);
#[allow(clippy::print_stderr)]
{ {
// use eprintln instead of log::warn so this always gets shown log::error!("{}", colors::yellow(msg));
eprintln!("{}", colors::yellow(msg));
} }
} }
@ -870,12 +870,8 @@ impl CliOptions {
} else { } else {
&[] &[]
}; };
let config_parse_options = deno_config::deno_json::ConfigParseOptions { let config_parse_options =
include_task_comments: matches!( deno_config::deno_json::ConfigParseOptions::default();
flags.subcommand,
DenoSubcommand::Task(..)
),
};
let discover_pkg_json = flags.config_flag != ConfigFlag::Disabled let discover_pkg_json = flags.config_flag != ConfigFlag::Disabled
&& !flags.no_npm && !flags.no_npm
&& !has_flag_env_var("DENO_NO_PACKAGE_JSON"); && !has_flag_env_var("DENO_NO_PACKAGE_JSON");
@ -1131,23 +1127,10 @@ impl CliOptions {
} }
pub fn otel_config(&self) -> Option<OtelConfig> { pub fn otel_config(&self) -> Option<OtelConfig> {
if self self.flags.otel_config()
.flags
.unstable_config
.features
.contains(&String::from("otel"))
{
Some(OtelConfig {
runtime_name: Cow::Borrowed("deno"),
runtime_version: Cow::Borrowed(crate::version::DENO_VERSION_INFO.deno),
..Default::default()
})
} else {
None
}
} }
pub fn env_file_name(&self) -> Option<&String> { pub fn env_file_name(&self) -> Option<&Vec<String>> {
self.flags.env_file.as_ref() self.flags.env_file.as_ref()
} }
@ -1338,6 +1321,7 @@ impl CliOptions {
let workspace = self.workspace(); let workspace = self.workspace();
UnstableFmtOptions { UnstableFmtOptions {
component: workspace.has_unstable("fmt-component"), component: workspace.has_unstable("fmt-component"),
sql: workspace.has_unstable("fmt-sql"),
} }
} }
@ -1564,6 +1548,10 @@ impl CliOptions {
}) => Url::parse(&flags.module_url) }) => Url::parse(&flags.module_url)
.ok() .ok()
.map(|url| vec![Cow::Owned(url)]), .map(|url| vec![Cow::Owned(url)]),
DenoSubcommand::Doc(DocFlags {
source_files: DocSourceFileFlag::Paths(paths),
..
}) => Some(files_to_urls(paths)),
_ => None, _ => None,
}) })
.unwrap_or_default(); .unwrap_or_default();
@ -1618,6 +1606,11 @@ impl CliOptions {
|| self.workspace().has_unstable("bare-node-builtins") || self.workspace().has_unstable("bare-node-builtins")
} }
pub fn unstable_detect_cjs(&self) -> bool {
self.flags.unstable_config.detect_cjs
|| self.workspace().has_unstable("detect-cjs")
}
pub fn detect_cjs(&self) -> bool { pub fn detect_cjs(&self) -> bool {
// only enabled when there's a package.json in order to not have a // only enabled when there's a package.json in order to not have a
// perf penalty for non-npm Deno projects of searching for the closest // perf penalty for non-npm Deno projects of searching for the closest
@ -1640,8 +1633,10 @@ impl CliOptions {
DenoSubcommand::Install(_) DenoSubcommand::Install(_)
| DenoSubcommand::Add(_) | DenoSubcommand::Add(_)
| DenoSubcommand::Remove(_) | DenoSubcommand::Remove(_)
| DenoSubcommand::Init(_)
| DenoSubcommand::Outdated(_)
) { ) {
// For `deno install/add/remove` we want to force the managed resolver so it can set up `node_modules/` directory. // For `deno install/add/remove/init` we want to force the managed resolver so it can set up `node_modules/` directory.
return false; return false;
} }
if self.node_modules_dir().ok().flatten().is_none() if self.node_modules_dir().ok().flatten().is_none()
@ -1685,7 +1680,9 @@ impl CliOptions {
"sloppy-imports", "sloppy-imports",
"byonm", "byonm",
"bare-node-builtins", "bare-node-builtins",
"detect-cjs",
"fmt-component", "fmt-component",
"fmt-sql",
]) ])
.collect(); .collect();
@ -1923,6 +1920,10 @@ pub fn resolve_no_prompt(flags: &PermissionFlags) -> bool {
flags.no_prompt || has_flag_env_var("DENO_NO_PROMPT") flags.no_prompt || has_flag_env_var("DENO_NO_PROMPT")
} }
pub fn has_trace_permissions_enabled() -> bool {
has_flag_env_var("DENO_TRACE_PERMISSIONS")
}
pub fn has_flag_env_var(name: &str) -> bool { pub fn has_flag_env_var(name: &str) -> bool {
let value = env::var(name); let value = env::var(name);
matches!(value.as_ref().map(|s| s.as_str()), Ok("1")) matches!(value.as_ref().map(|s| s.as_str()), Ok("1"))
@ -1954,19 +1955,22 @@ pub fn config_to_deno_graph_workspace_member(
}) })
} }
fn load_env_variables_from_env_file(filename: Option<&String>) { fn load_env_variables_from_env_file(filename: Option<&Vec<String>>) {
let Some(env_file_name) = filename else { let Some(env_file_names) = filename else {
return; return;
}; };
match from_filename(env_file_name) {
Ok(_) => (), for env_file_name in env_file_names.iter().rev() {
Err(error) => { match from_filename(env_file_name) {
match error { Ok(_) => (),
Err(error) => {
match error {
dotenvy::Error::LineParse(line, index)=> log::info!("{} Parsing failed within the specified environment file: {} at index: {} of the value: {}",colors::yellow("Warning"), env_file_name, index, line), dotenvy::Error::LineParse(line, index)=> log::info!("{} Parsing failed within the specified environment file: {} at index: {} of the value: {}",colors::yellow("Warning"), env_file_name, index, line),
dotenvy::Error::Io(_)=> log::info!("{} The `--env-file` flag was used, but the environment file specified '{}' was not found.",colors::yellow("Warning"),env_file_name), dotenvy::Error::Io(_)=> log::info!("{} The `--env-file` flag was used, but the environment file specified '{}' was not found.",colors::yellow("Warning"),env_file_name),
dotenvy::Error::EnvVar(_)=> log::info!("{} One or more of the environment variables isn't present or not unicode within the specified environment file: {}",colors::yellow("Warning"),env_file_name), dotenvy::Error::EnvVar(_)=> log::info!("{} One or more of the environment variables isn't present or not unicode within the specified environment file: {}",colors::yellow("Warning"),env_file_name),
_ => log::info!("{} Unknown failure occurred with the specified environment file: {}", colors::yellow("Warning"), env_file_name), _ => log::info!("{} Unknown failure occurred with the specified environment file: {}", colors::yellow("Warning"), env_file_name),
} }
}
} }
} }
} }

View file

@ -8,8 +8,10 @@ use deno_core::serde_json;
use deno_core::url::Url; use deno_core::url::Url;
use deno_package_json::PackageJsonDepValue; use deno_package_json::PackageJsonDepValue;
use deno_package_json::PackageJsonDepValueParseError; use deno_package_json::PackageJsonDepValueParseError;
use deno_package_json::PackageJsonDepWorkspaceReq;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use deno_semver::VersionReq;
use thiserror::Error; use thiserror::Error;
#[derive(Debug)] #[derive(Debug)]
@ -95,8 +97,14 @@ impl NpmInstallDepsProvider {
if let Some(pkg_json) = &folder.pkg_json { if let Some(pkg_json) = &folder.pkg_json {
let deps = pkg_json.resolve_local_package_json_deps(); let deps = pkg_json.resolve_local_package_json_deps();
let mut pkg_pkgs = Vec::with_capacity(deps.len()); let mut pkg_pkgs = Vec::with_capacity(
for (alias, dep) in deps { deps.dependencies.len() + deps.dev_dependencies.len(),
);
for (alias, dep) in deps
.dependencies
.into_iter()
.chain(deps.dev_dependencies.into_iter())
{
let dep = match dep { let dep = match dep {
Ok(dep) => dep, Ok(dep) => dep,
Err(err) => { Err(err) => {
@ -131,7 +139,16 @@ impl NpmInstallDepsProvider {
}); });
} }
} }
PackageJsonDepValue::Workspace(version_req) => { PackageJsonDepValue::Workspace(workspace_version_req) => {
let version_req = match workspace_version_req {
PackageJsonDepWorkspaceReq::VersionReq(version_req) => {
version_req
}
PackageJsonDepWorkspaceReq::Tilde
| PackageJsonDepWorkspaceReq::Caret => {
VersionReq::parse_from_npm("*").unwrap()
}
};
if let Some(pkg) = workspace_npm_pkgs.iter().find(|pkg| { if let Some(pkg) = workspace_npm_pkgs.iter().find(|pkg| {
pkg.matches_name_and_version_req(&alias, &version_req) pkg.matches_name_and_version_req(&alias, &version_req)
}) { }) {

View file

@ -400,6 +400,24 @@ fn main() {
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap()); println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap()); println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
if cfg!(windows) {
// these dls load slowly, so delay loading them
let dlls = [
// webgpu
"d3dcompiler_47",
"OPENGL32",
// network related functions
"iphlpapi",
];
for dll in dlls {
println!("cargo:rustc-link-arg-bin=deno=/delayload:{dll}.dll");
println!("cargo:rustc-link-arg-bin=denort=/delayload:{dll}.dll");
}
// enable delay loading
println!("cargo:rustc-link-arg-bin=deno=delayimp.lib");
println!("cargo:rustc-link-arg-bin=denort=delayimp.lib");
}
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());

View file

@ -1,10 +1,14 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::sync::Arc;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_runtime::code_cache; use deno_runtime::code_cache;
use deno_runtime::deno_webstorage::rusqlite::params; use deno_runtime::deno_webstorage::rusqlite::params;
use crate::worker::CliCodeCache;
use super::cache_db::CacheDB; use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration; use super::cache_db::CacheDBConfiguration;
use super::cache_db::CacheDBHash; use super::cache_db::CacheDBHash;
@ -82,6 +86,12 @@ impl CodeCache {
} }
} }
impl CliCodeCache for CodeCache {
fn as_code_cache(self: Arc<Self>) -> Arc<dyn code_cache::CodeCache> {
self
}
}
impl code_cache::CodeCache for CodeCache { impl code_cache::CodeCache for CodeCache {
fn get_sync( fn get_sync(
&self, &self,

13
cli/cache/mod.rs vendored
View file

@ -23,6 +23,7 @@ use deno_graph::source::Loader;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_permissions::PermissionsContainer;
use node_resolver::InNpmPackageChecker; use node_resolver::InNpmPackageChecker;
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -67,8 +68,11 @@ pub const CACHE_PERM: u32 = 0o644;
pub struct RealDenoCacheEnv; pub struct RealDenoCacheEnv;
impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv { impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv {
fn read_file_bytes(&self, path: &Path) -> std::io::Result<Vec<u8>> { fn read_file_bytes(
std::fs::read(path) &self,
path: &Path,
) -> std::io::Result<Cow<'static, [u8]>> {
std::fs::read(path).map(Cow::Owned)
} }
fn atomic_write_file( fn atomic_write_file(
@ -112,7 +116,10 @@ pub struct DenoCacheEnvFsAdapter<'a>(
); );
impl<'a> deno_cache_dir::DenoCacheEnv for DenoCacheEnvFsAdapter<'a> { impl<'a> deno_cache_dir::DenoCacheEnv for DenoCacheEnvFsAdapter<'a> {
fn read_file_bytes(&self, path: &Path) -> std::io::Result<Vec<u8>> { fn read_file_bytes(
&self,
path: &Path,
) -> std::io::Result<Cow<'static, [u8]>> {
self self
.0 .0
.read_file_sync(path, None) .read_file_sync(path, None)

View file

@ -284,6 +284,7 @@ fn serialize_media_type(media_type: MediaType) -> i64 {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use deno_graph::JsDocImportInfo;
use deno_graph::PositionRange; use deno_graph::PositionRange;
use deno_graph::SpecifierWithRange; use deno_graph::SpecifierWithRange;
@ -308,18 +309,21 @@ mod test {
); );
let mut module_info = ModuleInfo::default(); let mut module_info = ModuleInfo::default();
module_info.jsdoc_imports.push(SpecifierWithRange { module_info.jsdoc_imports.push(JsDocImportInfo {
range: PositionRange { specifier: SpecifierWithRange {
start: deno_graph::Position { range: PositionRange {
line: 0, start: deno_graph::Position {
character: 3, line: 0,
}, character: 3,
end: deno_graph::Position { },
line: 1, end: deno_graph::Position {
character: 2, line: 1,
character: 2,
},
}, },
text: "test".to_string(),
}, },
text: "test".to_string(), resolution_mode: None,
}); });
cache cache
.set_module_info( .set_module_info(

View file

@ -1,5 +1,6 @@
disallowed-methods = [ disallowed-methods = [
{ path = "reqwest::Client::new", reason = "create an HttpClient via an HttpClientProvider instead" }, { path = "reqwest::Client::new", reason = "create an HttpClient via an HttpClientProvider instead" },
{ path = "std::process::exit", reason = "use deno_runtime::exit instead" },
] ]
disallowed-types = [ disallowed-types = [
{ path = "reqwest::Client", reason = "use crate::http_util::HttpClient instead" }, { path = "reqwest::Client", reason = "use crate::http_util::HttpClient instead" },

View file

@ -38,6 +38,7 @@ fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str {
ModuleGraphError::ModuleError(err) => match err { ModuleGraphError::ModuleError(err) => match err {
ModuleError::InvalidTypeAssertion { .. } => "SyntaxError", ModuleError::InvalidTypeAssertion { .. } => "SyntaxError",
ModuleError::ParseErr(_, diagnostic) => get_diagnostic_class(diagnostic), ModuleError::ParseErr(_, diagnostic) => get_diagnostic_class(diagnostic),
ModuleError::WasmParseErr(..) => "SyntaxError",
ModuleError::UnsupportedMediaType { .. } ModuleError::UnsupportedMediaType { .. }
| ModuleError::UnsupportedImportAttributeType { .. } => "TypeError", | ModuleError::UnsupportedImportAttributeType { .. } => "TypeError",
ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => { ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => {

View file

@ -42,12 +42,12 @@ use crate::npm::CliNpmResolverCreateOptions;
use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::CreateInNpmPkgCheckerOptions; use crate::npm::CreateInNpmPkgCheckerOptions;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use crate::resolver::CliDenoResolver;
use crate::resolver::CliDenoResolverFs; use crate::resolver::CliDenoResolverFs;
use crate::resolver::CliNodeResolver; use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver; use crate::resolver::CliResolver;
use crate::resolver::CliResolverOptions; use crate::resolver::CliResolverOptions;
use crate::resolver::CliSloppyImportsResolver; use crate::resolver::CliSloppyImportsResolver;
use crate::resolver::IsCjsResolverOptions;
use crate::resolver::NpmModuleLoader; use crate::resolver::NpmModuleLoader;
use crate::resolver::SloppyImportsCachedFs; use crate::resolver::SloppyImportsCachedFs;
use crate::standalone::DenoCompileBinaryWriter; use crate::standalone::DenoCompileBinaryWriter;
@ -71,6 +71,10 @@ use deno_core::error::AnyError;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::FeatureChecker; use deno_core::FeatureChecker;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_resolver::DenoResolverOptions;
use deno_resolver::NodeAndNpmReqResolver;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node::DenoFsNodeResolverEnv; use deno_runtime::deno_node::DenoFsNodeResolverEnv;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
@ -126,7 +130,7 @@ impl RootCertStoreProvider for CliRootCertStoreProvider {
} }
} }
struct Deferred<T>(once_cell::unsync::OnceCell<T>); pub struct Deferred<T>(once_cell::unsync::OnceCell<T>);
impl<T> Default for Deferred<T> { impl<T> Default for Deferred<T> {
fn default() -> Self { fn default() -> Self {
@ -175,9 +179,9 @@ struct CliFactoryServices {
blob_store: Deferred<Arc<BlobStore>>, blob_store: Deferred<Arc<BlobStore>>,
caches: Deferred<Arc<Caches>>, caches: Deferred<Arc<Caches>>,
cjs_tracker: Deferred<Arc<CjsTracker>>, cjs_tracker: Deferred<Arc<CjsTracker>>,
cli_node_resolver: Deferred<Arc<CliNodeResolver>>,
cli_options: Deferred<Arc<CliOptions>>, cli_options: Deferred<Arc<CliOptions>>,
code_cache: Deferred<Arc<CodeCache>>, code_cache: Deferred<Arc<CodeCache>>,
deno_resolver: Deferred<Arc<CliDenoResolver>>,
emit_cache: Deferred<Arc<EmitCache>>, emit_cache: Deferred<Arc<EmitCache>>,
emitter: Deferred<Arc<Emitter>>, emitter: Deferred<Arc<Emitter>>,
feature_checker: Deferred<Arc<FeatureChecker>>, feature_checker: Deferred<Arc<FeatureChecker>>,
@ -197,6 +201,7 @@ struct CliFactoryServices {
node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>, node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>,
node_resolver: Deferred<Arc<NodeResolver>>, node_resolver: Deferred<Arc<NodeResolver>>,
npm_cache_dir: Deferred<Arc<NpmCacheDir>>, npm_cache_dir: Deferred<Arc<NpmCacheDir>>,
npm_req_resolver: Deferred<Arc<CliNpmReqResolver>>,
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>, npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
parsed_source_cache: Deferred<Arc<ParsedSourceCache>>, parsed_source_cache: Deferred<Arc<ParsedSourceCache>>,
permission_desc_parser: Deferred<Arc<RuntimePermissionDescriptorParser>>, permission_desc_parser: Deferred<Arc<RuntimePermissionDescriptorParser>>,
@ -499,7 +504,12 @@ impl CliFactory {
let resolver = cli_options let resolver = cli_options
.create_workspace_resolver( .create_workspace_resolver(
self.file_fetcher()?, self.file_fetcher()?,
if cli_options.use_byonm() { if cli_options.use_byonm()
&& !matches!(
cli_options.sub_command(),
DenoSubcommand::Publish(_)
)
{
PackageJsonDepResolution::Disabled PackageJsonDepResolution::Disabled
} else { } else {
// todo(dsherret): this should be false for nodeModulesDir: true // todo(dsherret): this should be false for nodeModulesDir: true
@ -523,6 +533,31 @@ impl CliFactory {
.await .await
} }
pub async fn deno_resolver(&self) -> Result<&Arc<CliDenoResolver>, AnyError> {
self
.services
.deno_resolver
.get_or_try_init_async(async {
let cli_options = self.cli_options()?;
Ok(Arc::new(CliDenoResolver::new(DenoResolverOptions {
in_npm_pkg_checker: self.in_npm_pkg_checker()?.clone(),
node_and_req_resolver: if cli_options.no_npm() {
None
} else {
Some(NodeAndNpmReqResolver {
node_resolver: self.node_resolver().await?.clone(),
npm_req_resolver: self.npm_req_resolver().await?.clone(),
})
},
sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(),
workspace_resolver: self.workspace_resolver().await?.clone(),
is_byonm: cli_options.use_byonm(),
maybe_vendor_dir: cli_options.vendor_dir_path(),
})))
})
.await
}
pub async fn resolver(&self) -> Result<&Arc<CliResolver>, AnyError> { pub async fn resolver(&self) -> Result<&Arc<CliResolver>, AnyError> {
self self
.services .services
@ -531,17 +566,14 @@ impl CliFactory {
async { async {
let cli_options = self.cli_options()?; let cli_options = self.cli_options()?;
Ok(Arc::new(CliResolver::new(CliResolverOptions { Ok(Arc::new(CliResolver::new(CliResolverOptions {
sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(),
node_resolver: Some(self.cli_node_resolver().await?.clone()),
npm_resolver: if cli_options.no_npm() { npm_resolver: if cli_options.no_npm() {
None None
} else { } else {
Some(self.npm_resolver().await?.clone()) Some(self.npm_resolver().await?.clone())
}, },
workspace_resolver: self.workspace_resolver().await?.clone(),
bare_node_builtins_enabled: cli_options bare_node_builtins_enabled: cli_options
.unstable_bare_node_builtins(), .unstable_bare_node_builtins(),
maybe_vendor_dir: cli_options.vendor_dir_path(), deno_resolver: self.deno_resolver().await?.clone(),
}))) })))
} }
.boxed_local(), .boxed_local(),
@ -624,7 +656,11 @@ impl CliFactory {
Ok(Arc::new(NodeResolver::new( Ok(Arc::new(NodeResolver::new(
DenoFsNodeResolverEnv::new(self.fs().clone()), DenoFsNodeResolverEnv::new(self.fs().clone()),
self.in_npm_pkg_checker()?.clone(), self.in_npm_pkg_checker()?.clone(),
self.npm_resolver().await?.clone().into_npm_resolver(), self
.npm_resolver()
.await?
.clone()
.into_npm_pkg_folder_resolver(),
self.pkg_json_resolver().clone(), self.pkg_json_resolver().clone(),
))) )))
} }
@ -656,13 +692,36 @@ impl CliFactory {
DenoFsNodeResolverEnv::new(self.fs().clone()), DenoFsNodeResolverEnv::new(self.fs().clone()),
self.in_npm_pkg_checker()?.clone(), self.in_npm_pkg_checker()?.clone(),
node_resolver, node_resolver,
self.npm_resolver().await?.clone().into_npm_resolver(), self
.npm_resolver()
.await?
.clone()
.into_npm_pkg_folder_resolver(),
self.pkg_json_resolver().clone(), self.pkg_json_resolver().clone(),
))) )))
}) })
.await .await
} }
pub async fn npm_req_resolver(
&self,
) -> Result<&Arc<CliNpmReqResolver>, AnyError> {
self
.services
.npm_req_resolver
.get_or_try_init_async(async {
let npm_resolver = self.npm_resolver().await?;
Ok(Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions {
byonm_resolver: (npm_resolver.clone()).into_maybe_byonm(),
fs: CliDenoResolverFs(self.fs().clone()),
in_npm_pkg_checker: self.in_npm_pkg_checker()?.clone(),
node_resolver: self.node_resolver().await?.clone(),
npm_req_resolver: npm_resolver.clone().into_npm_req_resolver(),
})))
})
.await
}
pub fn pkg_json_resolver(&self) -> &Arc<PackageJsonResolver> { pub fn pkg_json_resolver(&self) -> &Arc<PackageJsonResolver> {
self.services.pkg_json_resolver.get_or_init(|| { self.services.pkg_json_resolver.get_or_init(|| {
Arc::new(PackageJsonResolver::new(DenoFsNodeResolverEnv::new( Arc::new(PackageJsonResolver::new(DenoFsNodeResolverEnv::new(
@ -791,31 +850,17 @@ impl CliFactory {
Ok(Arc::new(CjsTracker::new( Ok(Arc::new(CjsTracker::new(
self.in_npm_pkg_checker()?.clone(), self.in_npm_pkg_checker()?.clone(),
self.pkg_json_resolver().clone(), self.pkg_json_resolver().clone(),
IsCjsResolverOptions { if options.is_node_main() || options.unstable_detect_cjs() {
detect_cjs: options.detect_cjs(), IsCjsResolutionMode::ImplicitTypeCommonJs
is_node_main: options.is_node_main(), } else if options.detect_cjs() {
IsCjsResolutionMode::ExplicitTypeCommonJs
} else {
IsCjsResolutionMode::Disabled
}, },
))) )))
}) })
} }
pub async fn cli_node_resolver(
&self,
) -> Result<&Arc<CliNodeResolver>, AnyError> {
self
.services
.cli_node_resolver
.get_or_try_init_async(async {
Ok(Arc::new(CliNodeResolver::new(
self.fs().clone(),
self.in_npm_pkg_checker()?.clone(),
self.node_resolver().await?.clone(),
self.npm_resolver().await?.clone(),
)))
})
.await
}
pub fn permission_desc_parser( pub fn permission_desc_parser(
&self, &self,
) -> Result<&Arc<RuntimePermissionDescriptorParser>, AnyError> { ) -> Result<&Arc<RuntimePermissionDescriptorParser>, AnyError> {
@ -847,6 +892,7 @@ impl CliFactory {
let cli_options = self.cli_options()?; let cli_options = self.cli_options()?;
Ok(DenoCompileBinaryWriter::new( Ok(DenoCompileBinaryWriter::new(
self.cjs_tracker()?, self.cjs_tracker()?,
self.cli_options()?,
self.deno_dir()?, self.deno_dir()?,
self.emitter()?, self.emitter()?,
self.file_fetcher()?, self.file_fetcher()?,
@ -880,7 +926,6 @@ impl CliFactory {
let fs = self.fs(); let fs = self.fs();
let node_resolver = self.node_resolver().await?; let node_resolver = self.node_resolver().await?;
let npm_resolver = self.npm_resolver().await?; let npm_resolver = self.npm_resolver().await?;
let cli_node_resolver = self.cli_node_resolver().await?;
let cli_npm_resolver = self.npm_resolver().await?.clone(); let cli_npm_resolver = self.npm_resolver().await?.clone();
let in_npm_pkg_checker = self.in_npm_pkg_checker()?; let in_npm_pkg_checker = self.in_npm_pkg_checker()?;
let maybe_file_watcher_communicator = if cli_options.has_hmr() { let maybe_file_watcher_communicator = if cli_options.has_hmr() {
@ -891,6 +936,7 @@ impl CliFactory {
let node_code_translator = self.node_code_translator().await?; let node_code_translator = self.node_code_translator().await?;
let cjs_tracker = self.cjs_tracker()?.clone(); let cjs_tracker = self.cjs_tracker()?.clone();
let pkg_json_resolver = self.pkg_json_resolver().clone(); let pkg_json_resolver = self.pkg_json_resolver().clone();
let npm_req_resolver = self.npm_req_resolver().await?;
Ok(CliMainWorkerFactory::new( Ok(CliMainWorkerFactory::new(
self.blob_store().clone(), self.blob_store().clone(),
@ -918,7 +964,8 @@ impl CliFactory {
self.main_module_graph_container().await?.clone(), self.main_module_graph_container().await?.clone(),
self.module_load_preparer().await?.clone(), self.module_load_preparer().await?.clone(),
node_code_translator.clone(), node_code_translator.clone(),
cli_node_resolver.clone(), node_resolver.clone(),
npm_req_resolver.clone(),
cli_npm_resolver.clone(), cli_npm_resolver.clone(),
NpmModuleLoader::new( NpmModuleLoader::new(
self.cjs_tracker()?.clone(), self.cjs_tracker()?.clone(),

View file

@ -164,8 +164,19 @@ fn get_validated_scheme(
) -> Result<String, AnyError> { ) -> Result<String, AnyError> {
let scheme = specifier.scheme(); let scheme = specifier.scheme();
if !SUPPORTED_SCHEMES.contains(&scheme) { if !SUPPORTED_SCHEMES.contains(&scheme) {
// NOTE(bartlomieju): this message list additional `npm` and `jsr` schemes, but they should actually be handled
// before `file_fetcher.rs` APIs are even hit.
let mut all_supported_schemes = SUPPORTED_SCHEMES.to_vec();
all_supported_schemes.extend_from_slice(&["npm", "jsr"]);
all_supported_schemes.sort();
let scheme_list = all_supported_schemes
.iter()
.map(|scheme| format!(" - \"{}\"", scheme))
.collect::<Vec<_>>()
.join("\n");
Err(generic_error(format!( Err(generic_error(format!(
"Unsupported scheme \"{scheme}\" for module \"{specifier}\". Supported schemes: {SUPPORTED_SCHEMES:#?}" "Unsupported scheme \"{scheme}\" for module \"{specifier}\". Supported schemes:\n{}",
scheme_list
))) )))
} else { } else {
Ok(scheme.to_string()) Ok(scheme.to_string())
@ -1529,7 +1540,7 @@ mod tests {
.unwrap() .unwrap()
.unwrap() .unwrap()
.content; .content;
String::from_utf8(bytes).unwrap() String::from_utf8(bytes.into_owned()).unwrap()
} }
#[track_caller] #[track_caller]

View file

@ -6,6 +6,7 @@ use crate::args::CliLockfile;
use crate::args::CliOptions; use crate::args::CliOptions;
use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS;
use crate::cache; use crate::cache;
use crate::cache::FetchCacher;
use crate::cache::GlobalHttpCache; use crate::cache::GlobalHttpCache;
use crate::cache::ModuleInfoCache; use crate::cache::ModuleInfoCache;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
@ -25,7 +26,7 @@ use deno_config::deno_json::JsxImportSourceConfig;
use deno_config::workspace::JsrPackageConfig; use deno_config::workspace::JsrPackageConfig;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_graph::source::LoaderChecksum; use deno_graph::source::LoaderChecksum;
use deno_graph::source::ResolutionMode; use deno_graph::source::ResolutionKind;
use deno_graph::FillFromLockfileOptions; use deno_graph::FillFromLockfileOptions;
use deno_graph::JsrLoadError; use deno_graph::JsrLoadError;
use deno_graph::ModuleLoadError; use deno_graph::ModuleLoadError;
@ -44,7 +45,7 @@ use deno_graph::ModuleGraphError;
use deno_graph::ResolutionError; use deno_graph::ResolutionError;
use deno_graph::SpecifierError; use deno_graph::SpecifierError;
use deno_path_util::url_to_file_path; use deno_path_util::url_to_file_path;
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node; use deno_runtime::deno_node;
use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_permissions::PermissionsContainer;
@ -188,7 +189,7 @@ pub fn graph_exit_integrity_errors(graph: &ModuleGraph) {
fn exit_for_integrity_error(err: &ModuleError) { fn exit_for_integrity_error(err: &ModuleError) {
if let Some(err_message) = enhanced_integrity_error_message(err) { if let Some(err_message) = enhanced_integrity_error_message(err) {
log::error!("{} {}", colors::red("error:"), err_message); log::error!("{} {}", colors::red("error:"), err_message);
std::process::exit(10); deno_runtime::exit(10);
} }
} }
@ -254,6 +255,23 @@ impl ModuleGraphCreator {
package_configs: &[JsrPackageConfig], package_configs: &[JsrPackageConfig],
build_fast_check_graph: bool, build_fast_check_graph: bool,
) -> Result<ModuleGraph, AnyError> { ) -> Result<ModuleGraph, AnyError> {
struct PublishLoader(FetchCacher);
impl Loader for PublishLoader {
fn load(
&self,
specifier: &deno_ast::ModuleSpecifier,
options: deno_graph::source::LoadOptions,
) -> deno_graph::source::LoadFuture {
if specifier.scheme() == "bun" {
return Box::pin(std::future::ready(Ok(Some(
deno_graph::source::LoadResponse::External {
specifier: specifier.clone(),
},
))));
}
self.0.load(specifier, options)
}
}
fn graph_has_external_remote(graph: &ModuleGraph) -> bool { fn graph_has_external_remote(graph: &ModuleGraph) -> bool {
// Earlier on, we marked external non-JSR modules as external. // Earlier on, we marked external non-JSR modules as external.
// If the graph contains any of those, it would cause type checking // If the graph contains any of those, it would cause type checking
@ -271,12 +289,15 @@ impl ModuleGraphCreator {
for package_config in package_configs { for package_config in package_configs {
roots.extend(package_config.config_file.resolve_export_value_urls()?); roots.extend(package_config.config_file.resolve_export_value_urls()?);
} }
let loader = self.module_graph_builder.create_graph_loader();
let mut publish_loader = PublishLoader(loader);
let mut graph = self let mut graph = self
.create_graph_with_options(CreateGraphOptions { .create_graph_with_options(CreateGraphOptions {
is_dynamic: false, is_dynamic: false,
graph_kind: deno_graph::GraphKind::All, graph_kind: deno_graph::GraphKind::All,
roots, roots,
loader: None, loader: Some(&mut publish_loader),
}) })
.await?; .await?;
self.graph_valid(&graph)?; self.graph_valid(&graph)?;
@ -795,7 +816,7 @@ fn enhanced_sloppy_imports_error_message(
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
| ModuleError::Missing(specifier, _) => { | ModuleError::Missing(specifier, _) => {
let additional_message = CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(fs.clone())) let additional_message = CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(fs.clone()))
.resolve(specifier, SloppyImportsResolutionMode::Execution)? .resolve(specifier, SloppyImportsResolutionKind::Execution)?
.as_suggestion_message(); .as_suggestion_message();
Some(format!( Some(format!(
"{} {} or run with --unstable-sloppy-imports", "{} {} or run with --unstable-sloppy-imports",
@ -1100,12 +1121,12 @@ impl<'a> deno_graph::source::FileSystem for DenoGraphFsAdapter<'a> {
} }
} }
pub fn format_range_with_colors(range: &deno_graph::Range) -> String { pub fn format_range_with_colors(referrer: &deno_graph::Range) -> String {
format!( format!(
"{}:{}:{}", "{}:{}:{}",
colors::cyan(range.specifier.as_str()), colors::cyan(referrer.specifier.as_str()),
colors::yellow(&(range.start.line + 1).to_string()), colors::yellow(&(referrer.range.start.line + 1).to_string()),
colors::yellow(&(range.start.character + 1).to_string()) colors::yellow(&(referrer.range.start.character + 1).to_string())
) )
} }
@ -1195,26 +1216,54 @@ impl<'a> deno_graph::source::Resolver for CliGraphResolver<'a> {
&self, &self,
raw_specifier: &str, raw_specifier: &str,
referrer_range: &deno_graph::Range, referrer_range: &deno_graph::Range,
mode: ResolutionMode, resolution_kind: ResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> { ) -> Result<ModuleSpecifier, ResolveError> {
self.resolver.resolve( self.resolver.resolve(
raw_specifier, raw_specifier,
referrer_range, &referrer_range.specifier,
self referrer_range.range.start,
.cjs_tracker referrer_range
.get_referrer_kind(&referrer_range.specifier), .resolution_mode
mode, .map(to_node_resolution_mode)
.unwrap_or_else(|| {
self
.cjs_tracker
.get_referrer_kind(&referrer_range.specifier)
}),
to_node_resolution_kind(resolution_kind),
) )
} }
} }
pub fn to_node_resolution_kind(
kind: ResolutionKind,
) -> node_resolver::NodeResolutionKind {
match kind {
ResolutionKind::Execution => node_resolver::NodeResolutionKind::Execution,
ResolutionKind::Types => node_resolver::NodeResolutionKind::Types,
}
}
pub fn to_node_resolution_mode(
mode: deno_graph::source::ResolutionMode,
) -> node_resolver::ResolutionMode {
match mode {
deno_graph::source::ResolutionMode::Import => {
node_resolver::ResolutionMode::Import
}
deno_graph::source::ResolutionMode::Require => {
node_resolver::ResolutionMode::Require
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::sync::Arc; use std::sync::Arc;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_graph::source::ResolveError; use deno_graph::source::ResolveError;
use deno_graph::Position; use deno_graph::PositionRange;
use deno_graph::Range; use deno_graph::Range;
use deno_graph::ResolutionError; use deno_graph::ResolutionError;
use deno_graph::SpecifierError; use deno_graph::SpecifierError;
@ -1235,8 +1284,8 @@ mod test {
specifier: input.to_string(), specifier: input.to_string(),
range: Range { range: Range {
specifier, specifier,
start: Position::zeroed(), resolution_mode: None,
end: Position::zeroed(), range: PositionRange::zeroed(),
}, },
}; };
assert_eq!(get_resolution_error_bare_node_specifier(&err), output); assert_eq!(get_resolution_error_bare_node_specifier(&err), output);
@ -1251,8 +1300,8 @@ mod test {
let err = ResolutionError::InvalidSpecifier { let err = ResolutionError::InvalidSpecifier {
range: Range { range: Range {
specifier, specifier,
start: Position::zeroed(), resolution_mode: None,
end: Position::zeroed(), range: PositionRange::zeroed(),
}, },
error: SpecifierError::ImportPrefixMissing { error: SpecifierError::ImportPrefixMissing {
specifier: input.to_string(), specifier: input.to_string(),

View file

@ -177,6 +177,52 @@ function isCanvasLike(obj) {
return obj !== null && typeof obj === "object" && "toDataURL" in obj; return obj !== null && typeof obj === "object" && "toDataURL" in obj;
} }
function isJpg(obj) {
// Check if obj is a Uint8Array
if (!(obj instanceof Uint8Array)) {
return false;
}
// JPG files start with the magic bytes FF D8
if (obj.length < 2 || obj[0] !== 0xFF || obj[1] !== 0xD8) {
return false;
}
// JPG files end with the magic bytes FF D9
if (
obj.length < 2 || obj[obj.length - 2] !== 0xFF ||
obj[obj.length - 1] !== 0xD9
) {
return false;
}
return true;
}
function isPng(obj) {
// Check if obj is a Uint8Array
if (!(obj instanceof Uint8Array)) {
return false;
}
// PNG files start with a specific 8-byte signature
const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
// Check if the array is at least as long as the signature
if (obj.length < pngSignature.length) {
return false;
}
// Check each byte of the signature
for (let i = 0; i < pngSignature.length; i++) {
if (obj[i] !== pngSignature[i]) {
return false;
}
}
return true;
}
/** Possible HTML and SVG Elements */ /** Possible HTML and SVG Elements */
function isSVGElementLike(obj) { function isSVGElementLike(obj) {
return obj !== null && typeof obj === "object" && "outerHTML" in obj && return obj !== null && typeof obj === "object" && "outerHTML" in obj &&
@ -233,6 +279,16 @@ async function format(obj) {
if (isDataFrameLike(obj)) { if (isDataFrameLike(obj)) {
return extractDataFrame(obj); return extractDataFrame(obj);
} }
if (isJpg(obj)) {
return {
"image/jpeg": core.ops.op_base64_encode(obj),
};
}
if (isPng(obj)) {
return {
"image/png": core.ops.op_base64_encode(obj),
};
}
if (isSVGElementLike(obj)) { if (isSVGElementLike(obj)) {
return { return {
"image/svg+xml": obj.outerHTML, "image/svg+xml": obj.outerHTML,
@ -314,6 +370,28 @@ const html = createTaggedTemplateDisplayable("text/html");
*/ */
const svg = createTaggedTemplateDisplayable("image/svg+xml"); const svg = createTaggedTemplateDisplayable("image/svg+xml");
function image(obj) {
if (typeof obj === "string") {
try {
obj = Deno.readFileSync(obj);
} catch {
// pass
}
}
if (isJpg(obj)) {
return makeDisplayable({ "image/jpeg": core.ops.op_base64_encode(obj) });
}
if (isPng(obj)) {
return makeDisplayable({ "image/png": core.ops.op_base64_encode(obj) });
}
throw new TypeError(
"Object is not a valid image or a path to an image. `Deno.jupyter.image` supports displaying JPG or PNG images.",
);
}
function isMediaBundle(obj) { function isMediaBundle(obj) {
if (obj == null || typeof obj !== "object" || Array.isArray(obj)) { if (obj == null || typeof obj !== "object" || Array.isArray(obj)) {
return false; return false;
@ -465,6 +543,7 @@ function enableJupyter() {
md, md,
html, html,
svg, svg,
image,
$display, $display,
}; };
} }

View file

@ -15,7 +15,6 @@ use crate::lsp::search::PackageSearchApi;
use crate::tools::lint::CliLinter; use crate::tools::lint::CliLinter;
use crate::util::path::relative_specifier; use crate::util::path::relative_specifier;
use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolution;
use deno_graph::source::ResolutionMode;
use deno_lint::diagnostic::LintDiagnosticRange; use deno_lint::diagnostic::LintDiagnosticRange;
use deno_ast::SourceRange; use deno_ast::SourceRange;
@ -39,7 +38,8 @@ use deno_semver::package::PackageReq;
use deno_semver::package::PackageReqReference; use deno_semver::package::PackageReqReference;
use deno_semver::Version; use deno_semver::Version;
use import_map::ImportMap; use import_map::ImportMap;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::borrow::Cow; use std::borrow::Cow;
@ -344,9 +344,8 @@ impl<'a> TsResponseImportMapper<'a> {
{ {
let in_npm_pkg = self let in_npm_pkg = self
.resolver .resolver
.maybe_node_resolver(Some(&self.file_referrer)) .in_npm_pkg_checker(Some(&self.file_referrer))
.map(|n| n.in_npm_package(specifier)) .in_npm_package(specifier);
.unwrap_or(false);
if in_npm_pkg { if in_npm_pkg {
if let Ok(Some(pkg_id)) = if let Ok(Some(pkg_id)) =
npm_resolver.resolve_pkg_id_from_specifier(specifier) npm_resolver.resolve_pkg_id_from_specifier(specifier)
@ -468,7 +467,7 @@ impl<'a> TsResponseImportMapper<'a> {
&self, &self,
specifier: &str, specifier: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
) -> Option<String> { ) -> Option<String> {
let specifier_stem = specifier.strip_suffix(".js").unwrap_or(specifier); let specifier_stem = specifier.strip_suffix(".js").unwrap_or(specifier);
let specifiers = std::iter::once(Cow::Borrowed(specifier)).chain( let specifiers = std::iter::once(Cow::Borrowed(specifier)).chain(
@ -482,13 +481,10 @@ impl<'a> TsResponseImportMapper<'a> {
.as_cli_resolver(Some(&self.file_referrer)) .as_cli_resolver(Some(&self.file_referrer))
.resolve( .resolve(
&specifier, &specifier,
&deno_graph::Range { referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), resolution_mode,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Types,
},
referrer_kind,
ResolutionMode::Types,
) )
.ok() .ok()
.and_then(|s| self.tsc_specifier_map.normalize(s.as_str()).ok()) .and_then(|s| self.tsc_specifier_map.normalize(s.as_str()).ok())
@ -510,20 +506,17 @@ impl<'a> TsResponseImportMapper<'a> {
&self, &self,
specifier_text: &str, specifier_text: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
) -> bool { ) -> bool {
self self
.resolver .resolver
.as_cli_resolver(Some(&self.file_referrer)) .as_cli_resolver(Some(&self.file_referrer))
.resolve( .resolve(
specifier_text, specifier_text,
&deno_graph::Range { referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), resolution_mode,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Types,
},
referrer_kind,
deno_graph::source::ResolutionMode::Types,
) )
.is_ok() .is_ok()
} }
@ -591,7 +584,7 @@ fn try_reverse_map_package_json_exports(
/// like an import and rewrite the import specifier to include the extension /// like an import and rewrite the import specifier to include the extension
pub fn fix_ts_import_changes( pub fn fix_ts_import_changes(
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
changes: &[tsc::FileTextChanges], changes: &[tsc::FileTextChanges],
language_server: &language_server::Inner, language_server: &language_server::Inner,
) -> Result<Vec<tsc::FileTextChanges>, AnyError> { ) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
@ -609,7 +602,7 @@ pub fn fix_ts_import_changes(
let specifier = let specifier =
captures.iter().skip(1).find_map(|s| s).unwrap().as_str(); captures.iter().skip(1).find_map(|s| s).unwrap().as_str();
if let Some(new_specifier) = import_mapper if let Some(new_specifier) = import_mapper
.check_unresolved_specifier(specifier, referrer, referrer_kind) .check_unresolved_specifier(specifier, referrer, resolution_mode)
{ {
line.replace(specifier, &new_specifier) line.replace(specifier, &new_specifier)
} else { } else {
@ -639,7 +632,7 @@ pub fn fix_ts_import_changes(
/// resolution by Deno (includes the extension). /// resolution by Deno (includes the extension).
fn fix_ts_import_action<'a>( fn fix_ts_import_action<'a>(
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
action: &'a tsc::CodeFixAction, action: &'a tsc::CodeFixAction,
language_server: &language_server::Inner, language_server: &language_server::Inner,
) -> Option<Cow<'a, tsc::CodeFixAction>> { ) -> Option<Cow<'a, tsc::CodeFixAction>> {
@ -658,9 +651,11 @@ fn fix_ts_import_action<'a>(
return Some(Cow::Borrowed(action)); return Some(Cow::Borrowed(action));
}; };
let import_mapper = language_server.get_ts_response_import_mapper(referrer); let import_mapper = language_server.get_ts_response_import_mapper(referrer);
if let Some(new_specifier) = if let Some(new_specifier) = import_mapper.check_unresolved_specifier(
import_mapper.check_unresolved_specifier(specifier, referrer, referrer_kind) specifier,
{ referrer,
resolution_mode,
) {
let description = action.description.replace(specifier, &new_specifier); let description = action.description.replace(specifier, &new_specifier);
let changes = action let changes = action
.changes .changes
@ -690,7 +685,8 @@ fn fix_ts_import_action<'a>(
fix_id: None, fix_id: None,
fix_all_description: None, fix_all_description: None,
})) }))
} else if !import_mapper.is_valid_import(specifier, referrer, referrer_kind) { } else if !import_mapper.is_valid_import(specifier, referrer, resolution_mode)
{
None None
} else { } else {
Some(Cow::Borrowed(action)) Some(Cow::Borrowed(action))
@ -1024,7 +1020,7 @@ impl CodeActionCollection {
pub fn add_ts_fix_action( pub fn add_ts_fix_action(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
specifier_kind: NodeModuleKind, resolution_mode: ResolutionMode,
action: &tsc::CodeFixAction, action: &tsc::CodeFixAction,
diagnostic: &lsp::Diagnostic, diagnostic: &lsp::Diagnostic,
language_server: &language_server::Inner, language_server: &language_server::Inner,
@ -1043,7 +1039,7 @@ impl CodeActionCollection {
)); ));
} }
let Some(action) = let Some(action) =
fix_ts_import_action(specifier, specifier_kind, action, language_server) fix_ts_import_action(specifier, resolution_mode, action, language_server)
else { else {
return Ok(()); return Ok(());
}; };
@ -1238,12 +1234,12 @@ impl CodeActionCollection {
let text_info = parsed_source.text_info_lazy(); let text_info = parsed_source.text_info_lazy();
let specifier_range = SourceRange::new( let specifier_range = SourceRange::new(
text_info.loc_to_source_pos(LineAndColumnIndex { text_info.loc_to_source_pos(LineAndColumnIndex {
line_index: import.specifier_range.start.line, line_index: import.specifier_range.range.start.line,
column_index: import.specifier_range.start.character, column_index: import.specifier_range.range.start.character,
}), }),
text_info.loc_to_source_pos(LineAndColumnIndex { text_info.loc_to_source_pos(LineAndColumnIndex {
line_index: import.specifier_range.end.line, line_index: import.specifier_range.range.end.line,
column_index: import.specifier_range.end.character, column_index: import.specifier_range.range.end.character,
}), }),
); );
@ -1278,16 +1274,14 @@ impl CodeActionCollection {
if json!(i.kind) != json!("es") && json!(i.kind) != json!("tsType") { if json!(i.kind) != json!("es") && json!(i.kind) != json!("tsType") {
return None; return None;
} }
if !i.specifier_range.includes(&position) { if !i.specifier_range.includes(position) {
return None; return None;
} }
import_start_from_specifier(document, i) import_start_from_specifier(document, i)
})?; })?;
let referrer = document.specifier(); let referrer = document.specifier();
let referrer_kind = language_server let resolution_mode = document.resolution_mode();
.is_cjs_resolver
.get_doc_module_kind(document);
let file_referrer = document.file_referrer(); let file_referrer = document.file_referrer();
let config_data = language_server let config_data = language_server
.config .config
@ -1313,7 +1307,7 @@ impl CodeActionCollection {
if !language_server.resolver.is_bare_package_json_dep( if !language_server.resolver.is_bare_package_json_dep(
&dep_key, &dep_key,
referrer, referrer,
referrer_kind, resolution_mode,
) { ) {
return None; return None;
} }
@ -1333,7 +1327,7 @@ impl CodeActionCollection {
} }
if language_server if language_server
.resolver .resolver
.npm_to_file_url(&npm_ref, referrer, referrer_kind, file_referrer) .npm_to_file_url(&npm_ref, referrer, resolution_mode, file_referrer)
.is_some() .is_some()
{ {
// The package import has types. // The package import has types.

View file

@ -9,16 +9,14 @@ use super::jsr::CliJsrSearchApi;
use super::lsp_custom; use super::lsp_custom;
use super::npm::CliNpmSearchApi; use super::npm::CliNpmSearchApi;
use super::registries::ModuleRegistry; use super::registries::ModuleRegistry;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver; use super::resolver::LspResolver;
use super::search::PackageSearchApi; use super::search::PackageSearchApi;
use super::tsc; use super::tsc;
use crate::graph_util::to_node_resolution_mode;
use crate::jsr::JsrFetchResolver; use crate::jsr::JsrFetchResolver;
use crate::util::path::is_importable_ext; use crate::util::path::is_importable_ext;
use crate::util::path::relative_specifier; use crate::util::path::relative_specifier;
use deno_graph::source::ResolutionMode;
use deno_graph::Range;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES; use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use deno_ast::LineAndColumnIndex; use deno_ast::LineAndColumnIndex;
@ -36,7 +34,8 @@ use deno_semver::package::PackageNv;
use import_map::ImportMap; use import_map::ImportMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use lsp_types::CompletionList; use lsp_types::CompletionList;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
@ -113,7 +112,7 @@ async fn check_auto_config_registry(
/// which we want to ignore when replacing text. /// which we want to ignore when replacing text.
fn to_narrow_lsp_range( fn to_narrow_lsp_range(
text_info: &SourceTextInfo, text_info: &SourceTextInfo,
range: &deno_graph::Range, range: deno_graph::PositionRange,
) -> lsp::Range { ) -> lsp::Range {
let end_byte_index = text_info let end_byte_index = text_info
.loc_to_source_pos(LineAndColumnIndex { .loc_to_source_pos(LineAndColumnIndex {
@ -161,26 +160,25 @@ pub async fn get_import_completions(
jsr_search_api: &CliJsrSearchApi, jsr_search_api: &CliJsrSearchApi,
npm_search_api: &CliNpmSearchApi, npm_search_api: &CliNpmSearchApi,
documents: &Documents, documents: &Documents,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver, resolver: &LspResolver,
maybe_import_map: Option<&ImportMap>, maybe_import_map: Option<&ImportMap>,
) -> Option<lsp::CompletionResponse> { ) -> Option<lsp::CompletionResponse> {
let document = documents.get(specifier)?; let document = documents.get(specifier)?;
let specifier_kind = is_cjs_resolver.get_doc_module_kind(&document);
let file_referrer = document.file_referrer(); let file_referrer = document.file_referrer();
let (text, _, range) = document.get_maybe_dependency(position)?; let (text, _, graph_range) = document.get_maybe_dependency(position)?;
let range = to_narrow_lsp_range(document.text_info(), &range); let resolution_mode = graph_range
.resolution_mode
.map(to_node_resolution_mode)
.unwrap_or_else(|| document.resolution_mode());
let range = to_narrow_lsp_range(document.text_info(), graph_range.range);
let resolved = resolver let resolved = resolver
.as_cli_resolver(file_referrer) .as_cli_resolver(file_referrer)
.resolve( .resolve(
&text, &text,
&Range { specifier,
specifier: specifier.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), resolution_mode,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Execution,
},
specifier_kind,
ResolutionMode::Execution,
) )
.ok(); .ok();
if let Some(completion_list) = get_jsr_completions( if let Some(completion_list) = get_jsr_completions(
@ -206,7 +204,7 @@ pub async fn get_import_completions(
// completions for import map specifiers // completions for import map specifiers
Some(lsp::CompletionResponse::List(completion_list)) Some(lsp::CompletionResponse::List(completion_list))
} else if let Some(completion_list) = } else if let Some(completion_list) =
get_local_completions(specifier, specifier_kind, &text, &range, resolver) get_local_completions(specifier, resolution_mode, &text, &range, resolver)
{ {
// completions for local relative modules // completions for local relative modules
Some(lsp::CompletionResponse::List(completion_list)) Some(lsp::CompletionResponse::List(completion_list))
@ -361,7 +359,7 @@ fn get_import_map_completions(
/// Return local completions that are relative to the base specifier. /// Return local completions that are relative to the base specifier.
fn get_local_completions( fn get_local_completions(
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
text: &str, text: &str,
range: &lsp::Range, range: &lsp::Range,
resolver: &LspResolver, resolver: &LspResolver,
@ -374,13 +372,10 @@ fn get_local_completions(
.as_cli_resolver(Some(referrer)) .as_cli_resolver(Some(referrer))
.resolve( .resolve(
parent, parent,
&Range { referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), resolution_mode,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Execution,
},
referrer_kind,
ResolutionMode::Execution,
) )
.ok()?; .ok()?;
let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?; let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?;
@ -831,7 +826,6 @@ mod tests {
use crate::lsp::documents::LanguageId; use crate::lsp::documents::LanguageId;
use crate::lsp::search::tests::TestPackageSearchApi; use crate::lsp::search::tests::TestPackageSearchApi;
use deno_core::resolve_url; use deno_core::resolve_url;
use deno_graph::Range;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::collections::HashMap; use std::collections::HashMap;
use test_util::TempDir; use test_util::TempDir;
@ -912,7 +906,7 @@ mod tests {
ModuleSpecifier::from_file_path(file_c).expect("could not create"); ModuleSpecifier::from_file_path(file_c).expect("could not create");
let actual = get_local_completions( let actual = get_local_completions(
&specifier, &specifier,
NodeModuleKind::Esm, ResolutionMode::Import,
"./", "./",
&lsp::Range { &lsp::Range {
start: lsp::Position { start: lsp::Position {
@ -1608,8 +1602,7 @@ mod tests {
let text_info = SourceTextInfo::from_string(r#""te""#.to_string()); let text_info = SourceTextInfo::from_string(r#""te""#.to_string());
let range = to_narrow_lsp_range( let range = to_narrow_lsp_range(
&text_info, &text_info,
&Range { deno_graph::PositionRange {
specifier: ModuleSpecifier::parse("https://deno.land").unwrap(),
start: deno_graph::Position { start: deno_graph::Position {
line: 0, line: 0,
character: 0, character: 0,
@ -1632,8 +1625,7 @@ mod tests {
let text_info = SourceTextInfo::from_string(r#""te"#.to_string()); let text_info = SourceTextInfo::from_string(r#""te"#.to_string());
let range = to_narrow_lsp_range( let range = to_narrow_lsp_range(
&text_info, &text_info,
&Range { deno_graph::PositionRange {
specifier: ModuleSpecifier::parse("https://deno.land").unwrap(),
start: deno_graph::Position { start: deno_graph::Position {
line: 0, line: 0,
character: 0, character: 0,

View file

@ -41,6 +41,7 @@ use deno_path_util::url_to_file_path;
use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PackageJson;
use indexmap::IndexSet; use indexmap::IndexSet;
use lsp_types::ClientCapabilities; use lsp_types::ClientCapabilities;
use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::collections::HashMap; use std::collections::HashMap;
@ -2092,7 +2093,7 @@ impl<T: Clone> CachedFsItems<T> {
#[derive(Default)] #[derive(Default)]
struct InnerData { struct InnerData {
stat_calls: CachedFsItems<deno_config::fs::FsMetadata>, stat_calls: CachedFsItems<deno_config::fs::FsMetadata>,
read_to_string_calls: CachedFsItems<String>, read_to_string_calls: CachedFsItems<Cow<'static, str>>,
} }
#[derive(Default)] #[derive(Default)]
@ -2113,7 +2114,7 @@ impl DenoConfigFs for CachedDenoConfigFs {
fn read_to_string_lossy( fn read_to_string_lossy(
&self, &self,
path: &Path, path: &Path,
) -> Result<String, std::io::Error> { ) -> Result<Cow<'static, str>, std::io::Error> {
self self
.0 .0
.lock() .lock()

View file

@ -45,7 +45,7 @@ use deno_graph::Resolution;
use deno_graph::ResolutionError; use deno_graph::ResolutionError;
use deno_graph::SpecifierError; use deno_graph::SpecifierError;
use deno_resolver::sloppy_imports::SloppyImportsResolution; use deno_resolver::sloppy_imports::SloppyImportsResolution;
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node; use deno_runtime::deno_node;
use deno_runtime::tokio_util::create_basic_runtime; use deno_runtime::tokio_util::create_basic_runtime;
@ -1266,7 +1266,7 @@ impl DenoDiagnostic {
Self::NoLocal(specifier) => { Self::NoLocal(specifier) => {
let maybe_sloppy_resolution = CliSloppyImportsResolver::new( let maybe_sloppy_resolution = CliSloppyImportsResolver::new(
SloppyImportsCachedFs::new(Arc::new(deno_fs::RealFs)) SloppyImportsCachedFs::new(Arc::new(deno_fs::RealFs))
).resolve(specifier, SloppyImportsResolutionMode::Execution); ).resolve(specifier, SloppyImportsResolutionKind::Execution);
let data = maybe_sloppy_resolution.as_ref().map(|res| { let data = maybe_sloppy_resolution.as_ref().map(|res| {
json!({ json!({
"specifier": specifier, "specifier": specifier,
@ -1531,7 +1531,7 @@ fn diagnose_dependency(
&& !dependency.imports.iter().any(|i| { && !dependency.imports.iter().any(|i| {
dependency dependency
.maybe_type .maybe_type
.includes(&i.specifier_range.start) .includes(i.specifier_range.range.start)
.is_some() .is_some()
}); });
@ -1707,7 +1707,6 @@ mod tests {
documents: Arc::new(documents), documents: Arc::new(documents),
assets: Default::default(), assets: Default::default(),
config: Arc::new(config), config: Arc::new(config),
is_cjs_resolver: Default::default(),
resolver, resolver,
}, },
) )

View file

@ -3,8 +3,8 @@
use super::cache::calculate_fs_version; use super::cache::calculate_fs_version;
use super::cache::LspCache; use super::cache::LspCache;
use super::config::Config; use super::config::Config;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver; use super::resolver::LspResolver;
use super::resolver::ScopeDepInfo;
use super::resolver::SingleReferrerGraphResolver; use super::resolver::SingleReferrerGraphResolver;
use super::testing::TestCollector; use super::testing::TestCollector;
use super::testing::TestModule; use super::testing::TestModule;
@ -26,7 +26,6 @@ use deno_core::futures::future::Shared;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::source::ResolutionMode;
use deno_graph::Resolution; use deno_graph::Resolution;
use deno_path_util::url_to_file_path; use deno_path_util::url_to_file_path;
use deno_runtime::deno_node; use deno_runtime::deno_node;
@ -35,10 +34,10 @@ use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use indexmap::IndexMap; use indexmap::IndexMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;
@ -313,6 +312,7 @@ pub struct Document {
media_type: MediaType, media_type: MediaType,
/// Present if and only if this is an open document. /// Present if and only if this is an open document.
open_data: Option<DocumentOpenData>, open_data: Option<DocumentOpenData>,
resolution_mode: ResolutionMode,
resolver: Arc<LspResolver>, resolver: Arc<LspResolver>,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
text: Arc<str>, text: Arc<str>,
@ -328,7 +328,6 @@ impl Document {
maybe_lsp_version: Option<i32>, maybe_lsp_version: Option<i32>,
maybe_language_id: Option<LanguageId>, maybe_language_id: Option<LanguageId>,
maybe_headers: Option<HashMap<String, String>>, maybe_headers: Option<HashMap<String, String>>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>, resolver: Arc<LspResolver>,
config: Arc<Config>, config: Arc<Config>,
cache: &Arc<LspCache>, cache: &Arc<LspCache>,
@ -340,7 +339,7 @@ impl Document {
.or(file_referrer); .or(file_referrer);
let media_type = let media_type =
resolve_media_type(&specifier, maybe_headers.as_ref(), maybe_language_id); resolve_media_type(&specifier, maybe_headers.as_ref(), maybe_language_id);
let (maybe_parsed_source, maybe_module) = let (maybe_parsed_source, maybe_module, resolution_mode) =
if media_type_is_diagnosable(media_type) { if media_type_is_diagnosable(media_type) {
parse_and_analyze_module( parse_and_analyze_module(
specifier.clone(), specifier.clone(),
@ -348,11 +347,10 @@ impl Document {
maybe_headers.as_ref(), maybe_headers.as_ref(),
media_type, media_type,
file_referrer.as_ref(), file_referrer.as_ref(),
is_cjs_resolver,
&resolver, &resolver,
) )
} else { } else {
(None, None) (None, None, ResolutionMode::Import)
}; };
let maybe_module = maybe_module.and_then(Result::ok); let maybe_module = maybe_module.and_then(Result::ok);
let dependencies = maybe_module let dependencies = maybe_module
@ -387,6 +385,7 @@ impl Document {
maybe_parsed_source, maybe_parsed_source,
maybe_semantic_tokens: Default::default(), maybe_semantic_tokens: Default::default(),
}), }),
resolution_mode,
resolver, resolver,
specifier, specifier,
text, text,
@ -396,7 +395,6 @@ impl Document {
fn with_new_config( fn with_new_config(
&self, &self,
is_cjs_resolver: &LspIsCjsResolver,
resolver: Arc<LspResolver>, resolver: Arc<LspResolver>,
config: Arc<Config>, config: Arc<Config>,
) -> Arc<Self> { ) -> Arc<Self> {
@ -408,20 +406,20 @@ impl Document {
let dependencies; let dependencies;
let maybe_types_dependency; let maybe_types_dependency;
let maybe_parsed_source; let maybe_parsed_source;
let found_resolution_mode;
let is_script; let is_script;
let maybe_test_module_fut; let maybe_test_module_fut;
if media_type != self.media_type { if media_type != self.media_type {
let parsed_source_result = let parsed_source_result =
parse_source(self.specifier.clone(), self.text.clone(), media_type); parse_source(self.specifier.clone(), self.text.clone(), media_type);
let maybe_module = analyze_module( let (maybe_module_result, resolution_mode) = analyze_module(
self.specifier.clone(), self.specifier.clone(),
&parsed_source_result, &parsed_source_result,
self.maybe_headers.as_ref(), self.maybe_headers.as_ref(),
self.file_referrer.as_ref(), self.file_referrer.as_ref(),
is_cjs_resolver,
&resolver, &resolver,
) );
.ok(); let maybe_module = maybe_module_result.ok();
dependencies = maybe_module dependencies = maybe_module
.as_ref() .as_ref()
.map(|m| Arc::new(m.dependencies.clone())) .map(|m| Arc::new(m.dependencies.clone()))
@ -433,17 +431,21 @@ impl Document {
maybe_parsed_source = Some(parsed_source_result); maybe_parsed_source = Some(parsed_source_result);
maybe_test_module_fut = maybe_test_module_fut =
get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config); get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config);
found_resolution_mode = resolution_mode;
} else { } else {
let cli_resolver = resolver.as_cli_resolver(self.file_referrer.as_ref()); let cli_resolver = resolver.as_cli_resolver(self.file_referrer.as_ref());
let is_cjs_resolver =
resolver.as_is_cjs_resolver(self.file_referrer.as_ref());
let npm_resolver = let npm_resolver =
resolver.create_graph_npm_resolver(self.file_referrer.as_ref()); resolver.create_graph_npm_resolver(self.file_referrer.as_ref());
let config_data = resolver.as_config_data(self.file_referrer.as_ref()); let config_data = resolver.as_config_data(self.file_referrer.as_ref());
let jsx_import_source_config = let jsx_import_source_config =
config_data.and_then(|d| d.maybe_jsx_import_source_config()); config_data.and_then(|d| d.maybe_jsx_import_source_config());
found_resolution_mode = is_cjs_resolver
.get_lsp_resolution_mode(&self.specifier, self.is_script);
let resolver = SingleReferrerGraphResolver { let resolver = SingleReferrerGraphResolver {
valid_referrer: &self.specifier, valid_referrer: &self.specifier,
referrer_kind: is_cjs_resolver module_resolution_mode: found_resolution_mode,
.get_lsp_referrer_kind(&self.specifier, self.is_script),
cli_resolver, cli_resolver,
jsx_import_source_config: jsx_import_source_config.as_ref(), jsx_import_source_config: jsx_import_source_config.as_ref(),
}; };
@ -493,6 +495,7 @@ impl Document {
maybe_language_id: self.maybe_language_id, maybe_language_id: self.maybe_language_id,
maybe_test_module_fut, maybe_test_module_fut,
media_type, media_type,
resolution_mode: found_resolution_mode,
open_data: self.open_data.as_ref().map(|d| DocumentOpenData { open_data: self.open_data.as_ref().map(|d| DocumentOpenData {
lsp_version: d.lsp_version, lsp_version: d.lsp_version,
maybe_parsed_source, maybe_parsed_source,
@ -508,7 +511,6 @@ impl Document {
fn with_change( fn with_change(
&self, &self,
is_cjs_resolver: &LspIsCjsResolver,
version: i32, version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>, changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<Arc<Self>, AnyError> { ) -> Result<Arc<Self>, AnyError> {
@ -530,7 +532,7 @@ impl Document {
} }
let text: Arc<str> = content.into(); let text: Arc<str> = content.into();
let media_type = self.media_type; let media_type = self.media_type;
let (maybe_parsed_source, maybe_module) = if self let (maybe_parsed_source, maybe_module, resolution_mode) = if self
.maybe_language_id .maybe_language_id
.as_ref() .as_ref()
.map(|li| li.is_diagnosable()) .map(|li| li.is_diagnosable())
@ -542,11 +544,10 @@ impl Document {
self.maybe_headers.as_ref(), self.maybe_headers.as_ref(),
media_type, media_type,
self.file_referrer.as_ref(), self.file_referrer.as_ref(),
is_cjs_resolver,
self.resolver.as_ref(), self.resolver.as_ref(),
) )
} else { } else {
(None, None) (None, None, ResolutionMode::Import)
}; };
let maybe_module = maybe_module.and_then(Result::ok); let maybe_module = maybe_module.and_then(Result::ok);
let dependencies = maybe_module let dependencies = maybe_module
@ -580,6 +581,7 @@ impl Document {
maybe_navigation_tree: Mutex::new(None), maybe_navigation_tree: Mutex::new(None),
maybe_test_module_fut, maybe_test_module_fut,
media_type, media_type,
resolution_mode,
open_data: self.open_data.is_some().then_some(DocumentOpenData { open_data: self.open_data.is_some().then_some(DocumentOpenData {
lsp_version: version, lsp_version: version,
maybe_parsed_source, maybe_parsed_source,
@ -613,6 +615,7 @@ impl Document {
maybe_test_module_fut: self.maybe_test_module_fut.clone(), maybe_test_module_fut: self.maybe_test_module_fut.clone(),
media_type: self.media_type, media_type: self.media_type,
open_data: None, open_data: None,
resolution_mode: self.resolution_mode,
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
}) })
} }
@ -641,6 +644,7 @@ impl Document {
maybe_test_module_fut: self.maybe_test_module_fut.clone(), maybe_test_module_fut: self.maybe_test_module_fut.clone(),
media_type: self.media_type, media_type: self.media_type,
open_data: self.open_data.clone(), open_data: self.open_data.clone(),
resolution_mode: self.resolution_mode,
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
}) })
} }
@ -664,6 +668,10 @@ impl Document {
&self.text &self.text
} }
pub fn resolution_mode(&self) -> ResolutionMode {
self.resolution_mode
}
pub fn text_info(&self) -> &SourceTextInfo { pub fn text_info(&self) -> &SourceTextInfo {
// try to get the text info from the parsed source and if // try to get the text info from the parsed source and if
// not then create one in the cell // not then create one in the cell
@ -677,14 +685,6 @@ impl Document {
.get_or_init(|| SourceTextInfo::new(self.text.clone())) .get_or_init(|| SourceTextInfo::new(self.text.clone()))
}) })
} }
/// If this is maybe a CJS script and maybe not an ES module.
///
/// Use `LspIsCjsResolver` to determine for sure.
pub fn is_script(&self) -> Option<bool> {
self.is_script
}
pub fn line_index(&self) -> Arc<LineIndex> { pub fn line_index(&self) -> Arc<LineIndex> {
self.line_index.clone() self.line_index.clone()
} }
@ -768,7 +768,7 @@ impl Document {
}; };
self.dependencies().iter().find_map(|(s, dep)| { self.dependencies().iter().find_map(|(s, dep)| {
dep dep
.includes(&position) .includes(position)
.map(|r| (s.clone(), dep.clone(), r.clone())) .map(|r| (s.clone(), dep.clone(), r.clone()))
}) })
} }
@ -809,15 +809,15 @@ fn resolve_media_type(
MediaType::from_specifier(specifier) MediaType::from_specifier(specifier)
} }
pub fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range { pub fn to_lsp_range(referrer: &deno_graph::Range) -> lsp::Range {
lsp::Range { lsp::Range {
start: lsp::Position { start: lsp::Position {
line: range.start.line as u32, line: referrer.range.start.line as u32,
character: range.start.character as u32, character: referrer.range.start.character as u32,
}, },
end: lsp::Position { end: lsp::Position {
line: range.end.line as u32, line: referrer.range.end.line as u32,
character: range.end.character as u32, character: referrer.range.end.character as u32,
}, },
} }
} }
@ -832,7 +832,6 @@ impl FileSystemDocuments {
pub fn get( pub fn get(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>, resolver: &Arc<LspResolver>,
config: &Arc<Config>, config: &Arc<Config>,
cache: &Arc<LspCache>, cache: &Arc<LspCache>,
@ -856,14 +855,7 @@ impl FileSystemDocuments {
}; };
if dirty { if dirty {
// attempt to update the file on the file system // attempt to update the file on the file system
self.refresh_document( self.refresh_document(specifier, resolver, config, cache, file_referrer)
specifier,
is_cjs_resolver,
resolver,
config,
cache,
file_referrer,
)
} else { } else {
old_doc old_doc
} }
@ -874,7 +866,6 @@ impl FileSystemDocuments {
fn refresh_document( fn refresh_document(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &Arc<LspResolver>, resolver: &Arc<LspResolver>,
config: &Arc<Config>, config: &Arc<Config>,
cache: &Arc<LspCache>, cache: &Arc<LspCache>,
@ -883,15 +874,19 @@ impl FileSystemDocuments {
let doc = if specifier.scheme() == "file" { let doc = if specifier.scheme() == "file" {
let path = url_to_file_path(specifier).ok()?; let path = url_to_file_path(specifier).ok()?;
let bytes = fs::read(path).ok()?; let bytes = fs::read(path).ok()?;
let content = let content = bytes_to_content(
deno_graph::source::decode_owned_source(specifier, bytes, None).ok()?; specifier,
MediaType::from_specifier(specifier),
bytes,
None,
)
.ok()?;
Document::new( Document::new(
specifier.clone(), specifier.clone(),
content.into(), content.into(),
None, None,
None, None,
None, None,
is_cjs_resolver,
resolver.clone(), resolver.clone(),
config.clone(), config.clone(),
cache, cache,
@ -908,7 +903,6 @@ impl FileSystemDocuments {
None, None,
None, None,
None, None,
is_cjs_resolver,
resolver.clone(), resolver.clone(),
config.clone(), config.clone(),
cache, cache,
@ -923,20 +917,24 @@ impl FileSystemDocuments {
specifier, specifier,
Some(&cached_file.metadata.headers), Some(&cached_file.metadata.headers),
); );
let content = deno_graph::source::decode_owned_source( let media_type = resolve_media_type(
specifier, specifier,
cached_file.content, Some(&cached_file.metadata.headers),
None,
);
let content = bytes_to_content(
specifier,
media_type,
cached_file.content.into_owned(),
maybe_charset, maybe_charset,
) )
.ok()?; .ok()?;
let maybe_headers = Some(cached_file.metadata.headers);
Document::new( Document::new(
specifier.clone(), specifier.clone(),
content.into(), content.into(),
None, None,
None, None,
maybe_headers, Some(cached_file.metadata.headers),
is_cjs_resolver,
resolver.clone(), resolver.clone(),
config.clone(), config.clone(),
cache, cache,
@ -977,8 +975,6 @@ pub struct Documents {
/// The DENO_DIR that the documents looks for non-file based modules. /// The DENO_DIR that the documents looks for non-file based modules.
cache: Arc<LspCache>, cache: Arc<LspCache>,
config: Arc<Config>, config: Arc<Config>,
/// Resolver for detecting if a document is CJS or ESM.
is_cjs_resolver: Arc<LspIsCjsResolver>,
/// A resolver that takes into account currently loaded import map and JSX /// A resolver that takes into account currently loaded import map and JSX
/// settings. /// settings.
resolver: Arc<LspResolver>, resolver: Arc<LspResolver>,
@ -989,12 +985,7 @@ pub struct Documents {
open_docs: HashMap<ModuleSpecifier, Arc<Document>>, open_docs: HashMap<ModuleSpecifier, Arc<Document>>,
/// Documents stored on the file system. /// Documents stored on the file system.
file_system_docs: Arc<FileSystemDocuments>, file_system_docs: Arc<FileSystemDocuments>,
/// The npm package requirements found in npm specifiers. dep_info_by_scope: Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>>,
npm_reqs_by_scope:
Arc<BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>>,
/// Config scopes that contain a node: specifier such that a @types/node
/// package should be injected.
scopes_with_node_specifier: Arc<HashSet<Option<ModuleSpecifier>>>,
} }
impl Documents { impl Documents {
@ -1019,7 +1010,6 @@ impl Documents {
// the cache for remote modules here in order to get the // the cache for remote modules here in order to get the
// x-typescript-types? // x-typescript-types?
None, None,
&self.is_cjs_resolver,
self.resolver.clone(), self.resolver.clone(),
self.config.clone(), self.config.clone(),
&self.cache, &self.cache,
@ -1054,7 +1044,7 @@ impl Documents {
)) ))
})?; })?;
self.dirty = true; self.dirty = true;
let doc = doc.with_change(&self.is_cjs_resolver, version, changes)?; let doc = doc.with_change(version, changes)?;
self.open_docs.insert(doc.specifier().clone(), doc.clone()); self.open_docs.insert(doc.specifier().clone(), doc.clone());
Ok(doc) Ok(doc)
} }
@ -1157,17 +1147,20 @@ impl Documents {
false false
} }
pub fn npm_reqs_by_scope( pub fn dep_info_by_scope(
&mut self, &mut self,
) -> Arc<BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>> { ) -> Arc<BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>> {
self.calculate_npm_reqs_if_dirty(); self.calculate_dep_info_if_dirty();
self.npm_reqs_by_scope.clone() self.dep_info_by_scope.clone()
} }
pub fn scopes_with_node_specifier( pub fn scopes_with_node_specifier(&self) -> HashSet<Option<ModuleSpecifier>> {
&self, self
) -> &Arc<HashSet<Option<ModuleSpecifier>>> { .dep_info_by_scope
&self.scopes_with_node_specifier .iter()
.filter(|(_, i)| i.has_node_specifier)
.map(|(s, _)| s.clone())
.collect::<HashSet<_>>()
} }
/// Return a document for the specifier. /// Return a document for the specifier.
@ -1183,7 +1176,6 @@ impl Documents {
if let Some(old_doc) = old_doc { if let Some(old_doc) = old_doc {
self.file_system_docs.get( self.file_system_docs.get(
specifier, specifier,
&self.is_cjs_resolver,
&self.resolver, &self.resolver,
&self.config, &self.config,
&self.cache, &self.cache,
@ -1208,7 +1200,6 @@ impl Documents {
} else { } else {
self.file_system_docs.get( self.file_system_docs.get(
&specifier, &specifier,
&self.is_cjs_resolver,
&self.resolver, &self.resolver,
&self.config, &self.config,
&self.cache, &self.cache,
@ -1263,7 +1254,8 @@ impl Documents {
/// tsc when type checking. /// tsc when type checking.
pub fn resolve( pub fn resolve(
&self, &self,
raw_specifiers: &[String], // (is_cjs: bool, raw_specifier: String)
raw_specifiers: &[(bool, String)],
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
) -> Vec<Option<(ModuleSpecifier, MediaType)>> { ) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
@ -1273,11 +1265,12 @@ impl Documents {
.and_then(|d| d.file_referrer()) .and_then(|d| d.file_referrer())
.or(file_referrer); .or(file_referrer);
let dependencies = referrer_doc.as_ref().map(|d| d.dependencies()); let dependencies = referrer_doc.as_ref().map(|d| d.dependencies());
let referrer_kind = self
.is_cjs_resolver
.get_maybe_doc_module_kind(referrer, referrer_doc.as_deref());
let mut results = Vec::new(); let mut results = Vec::new();
for raw_specifier in raw_specifiers { for (is_cjs, raw_specifier) in raw_specifiers {
let resolution_mode = match is_cjs {
true => ResolutionMode::Require,
false => ResolutionMode::Import,
};
if raw_specifier.starts_with("asset:") { if raw_specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(raw_specifier) { if let Ok(specifier) = ModuleSpecifier::parse(raw_specifier) {
let media_type = MediaType::from_specifier(&specifier); let media_type = MediaType::from_specifier(&specifier);
@ -1292,14 +1285,14 @@ impl Documents {
results.push(self.resolve_dependency( results.push(self.resolve_dependency(
specifier, specifier,
referrer, referrer,
referrer_kind, resolution_mode,
file_referrer, file_referrer,
)); ));
} else if let Some(specifier) = dep.maybe_code.maybe_specifier() { } else if let Some(specifier) = dep.maybe_code.maybe_specifier() {
results.push(self.resolve_dependency( results.push(self.resolve_dependency(
specifier, specifier,
referrer, referrer,
referrer_kind, resolution_mode,
file_referrer, file_referrer,
)); ));
} else { } else {
@ -1308,19 +1301,16 @@ impl Documents {
} else if let Ok(specifier) = } else if let Ok(specifier) =
self.resolver.as_cli_resolver(file_referrer).resolve( self.resolver.as_cli_resolver(file_referrer).resolve(
raw_specifier, raw_specifier,
&deno_graph::Range { referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), resolution_mode,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Types,
},
referrer_kind,
ResolutionMode::Types,
) )
{ {
results.push(self.resolve_dependency( results.push(self.resolve_dependency(
&specifier, &specifier,
referrer, referrer,
referrer_kind, resolution_mode,
file_referrer, file_referrer,
)); ));
} else { } else {
@ -1339,7 +1329,6 @@ impl Documents {
) { ) {
self.config = Arc::new(config.clone()); self.config = Arc::new(config.clone());
self.cache = Arc::new(cache.clone()); self.cache = Arc::new(cache.clone());
self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(cache));
self.resolver = resolver.clone(); self.resolver = resolver.clone();
node_resolver::PackageJsonThreadLocalCache::clear(); node_resolver::PackageJsonThreadLocalCache::clear();
@ -1363,21 +1352,14 @@ impl Documents {
if !config.specifier_enabled(doc.specifier()) { if !config.specifier_enabled(doc.specifier()) {
continue; continue;
} }
*doc = doc.with_new_config( *doc = doc.with_new_config(self.resolver.clone(), self.config.clone());
&self.is_cjs_resolver,
self.resolver.clone(),
self.config.clone(),
);
} }
for mut doc in self.file_system_docs.docs.iter_mut() { for mut doc in self.file_system_docs.docs.iter_mut() {
if !config.specifier_enabled(doc.specifier()) { if !config.specifier_enabled(doc.specifier()) {
continue; continue;
} }
*doc.value_mut() = doc.with_new_config( *doc.value_mut() =
&self.is_cjs_resolver, doc.with_new_config(self.resolver.clone(), self.config.clone());
self.resolver.clone(),
self.config.clone(),
);
} }
self.open_docs = open_docs; self.open_docs = open_docs;
let mut preload_count = 0; let mut preload_count = 0;
@ -1394,7 +1376,6 @@ impl Documents {
{ {
fs_docs.refresh_document( fs_docs.refresh_document(
specifier, specifier,
&self.is_cjs_resolver,
&self.resolver, &self.resolver,
&self.config, &self.config,
&self.cache, &self.cache,
@ -1410,34 +1391,46 @@ impl Documents {
/// Iterate through the documents, building a map where the key is a unique /// Iterate through the documents, building a map where the key is a unique
/// document and the value is a set of specifiers that depend on that /// document and the value is a set of specifiers that depend on that
/// document. /// document.
fn calculate_npm_reqs_if_dirty(&mut self) { fn calculate_dep_info_if_dirty(&mut self) {
let mut npm_reqs_by_scope: BTreeMap<_, BTreeSet<_>> = Default::default(); let mut dep_info_by_scope: BTreeMap<_, ScopeDepInfo> = Default::default();
let mut scopes_with_specifier = HashSet::new();
let is_fs_docs_dirty = self.file_system_docs.set_dirty(false); let is_fs_docs_dirty = self.file_system_docs.set_dirty(false);
if !is_fs_docs_dirty && !self.dirty { if !is_fs_docs_dirty && !self.dirty {
return; return;
} }
let mut visit_doc = |doc: &Arc<Document>| { let mut visit_doc = |doc: &Arc<Document>| {
let scope = doc.scope(); let scope = doc.scope();
let reqs = npm_reqs_by_scope.entry(scope.cloned()).or_default(); let dep_info = dep_info_by_scope.entry(scope.cloned()).or_default();
for dependency in doc.dependencies().values() { for dependency in doc.dependencies().values() {
if let Some(dep) = dependency.get_code() { let code_specifier = dependency.get_code();
let type_specifier = dependency.get_type();
if let Some(dep) = code_specifier {
if dep.scheme() == "node" { if dep.scheme() == "node" {
scopes_with_specifier.insert(scope.cloned()); dep_info.has_node_specifier = true;
} }
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) { if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
reqs.insert(reference.into_inner().req); dep_info.npm_reqs.insert(reference.into_inner().req);
} }
} }
if let Some(dep) = dependency.get_type() { if let Some(dep) = type_specifier {
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) { if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
reqs.insert(reference.into_inner().req); dep_info.npm_reqs.insert(reference.into_inner().req);
}
}
if dependency.maybe_deno_types_specifier.is_some() {
if let (Some(code_specifier), Some(type_specifier)) =
(code_specifier, type_specifier)
{
if MediaType::from_specifier(type_specifier).is_declaration() {
dep_info
.deno_types_to_code_resolutions
.insert(type_specifier.clone(), code_specifier.clone());
}
} }
} }
} }
if let Some(dep) = doc.maybe_types_dependency().maybe_specifier() { if let Some(dep) = doc.maybe_types_dependency().maybe_specifier() {
if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) { if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) {
reqs.insert(reference.into_inner().req); dep_info.npm_reqs.insert(reference.into_inner().req);
} }
} }
}; };
@ -1448,14 +1441,46 @@ impl Documents {
visit_doc(doc); visit_doc(doc);
} }
// fill the reqs from the lockfile
for (scope, config_data) in self.config.tree.data_by_scope().as_ref() { for (scope, config_data) in self.config.tree.data_by_scope().as_ref() {
let dep_info = dep_info_by_scope.entry(Some(scope.clone())).or_default();
(|| {
let config_file = config_data.maybe_deno_json()?;
let jsx_config =
config_file.to_maybe_jsx_import_source_config().ok()??;
let type_specifier = jsx_config.default_types_specifier.as_ref()?;
let code_specifier = jsx_config.default_specifier.as_ref()?;
let cli_resolver = self.resolver.as_cli_resolver(Some(scope));
let type_specifier = cli_resolver
.resolve(
type_specifier,
&jsx_config.base_url,
deno_graph::Position::zeroed(),
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
ResolutionMode::Import,
NodeResolutionKind::Types,
)
.ok()?;
let code_specifier = cli_resolver
.resolve(
code_specifier,
&jsx_config.base_url,
deno_graph::Position::zeroed(),
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
ResolutionMode::Import,
NodeResolutionKind::Execution,
)
.ok()?;
dep_info
.deno_types_to_code_resolutions
.insert(type_specifier, code_specifier);
Some(())
})();
// fill the reqs from the lockfile
if let Some(lockfile) = config_data.lockfile.as_ref() { 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(); let lockfile = lockfile.lock();
for dep_req in lockfile.content.packages.specifiers.keys() { for dep_req in lockfile.content.packages.specifiers.keys() {
if dep_req.kind == deno_semver::package::PackageKind::Npm { if dep_req.kind == deno_semver::package::PackageKind::Npm {
reqs.insert(dep_req.req.clone()); dep_info.npm_reqs.insert(dep_req.req.clone());
} }
} }
} }
@ -1464,15 +1489,22 @@ impl Documents {
// Ensure a @types/node package exists when any module uses a node: specifier. // Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package // Unlike on the command line, here we just add @types/node to the npm package
// requirements since this won't end up in the lockfile. // requirements since this won't end up in the lockfile.
for scope in &scopes_with_specifier { for dep_info in dep_info_by_scope.values_mut() {
let reqs = npm_reqs_by_scope.entry(scope.clone()).or_default(); if dep_info.has_node_specifier
if !reqs.iter().any(|r| r.name == "@types/node") { && !dep_info.npm_reqs.iter().any(|r| r.name == "@types/node")
reqs.insert(PackageReq::from_str("@types/node").unwrap()); {
dep_info
.npm_reqs
.insert(PackageReq::from_str("@types/node").unwrap());
} }
} }
self.npm_reqs_by_scope = Arc::new(npm_reqs_by_scope); self.dep_info_by_scope = Arc::new(
self.scopes_with_node_specifier = Arc::new(scopes_with_specifier); dep_info_by_scope
.into_iter()
.map(|(s, i)| (s, Arc::new(i)))
.collect(),
);
self.dirty = false; self.dirty = false;
} }
@ -1480,7 +1512,7 @@ impl Documents {
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
) -> Option<(ModuleSpecifier, MediaType)> { ) -> Option<(ModuleSpecifier, MediaType)> {
if let Some(module_name) = specifier.as_str().strip_prefix("node:") { if let Some(module_name) = specifier.as_str().strip_prefix("node:") {
@ -1497,7 +1529,7 @@ impl Documents {
let (s, mt) = self.resolver.npm_to_file_url( let (s, mt) = self.resolver.npm_to_file_url(
&npm_ref, &npm_ref,
referrer, referrer,
referrer_kind, resolution_mode,
file_referrer, file_referrer,
)?; )?;
specifier = s; specifier = s;
@ -1509,8 +1541,12 @@ impl Documents {
return Some((specifier, media_type)); return Some((specifier, media_type));
}; };
if let Some(types) = doc.maybe_types_dependency().maybe_specifier() { if let Some(types) = doc.maybe_types_dependency().maybe_specifier() {
let specifier_kind = self.is_cjs_resolver.get_doc_module_kind(&doc); self.resolve_dependency(
self.resolve_dependency(types, &specifier, specifier_kind, file_referrer) types,
&specifier,
doc.resolution_mode(),
file_referrer,
)
} else { } else {
Some((doc.specifier().clone(), doc.media_type())) Some((doc.specifier().clone(), doc.media_type()))
} }
@ -1578,19 +1614,25 @@ fn parse_and_analyze_module(
maybe_headers: Option<&HashMap<String, String>>, maybe_headers: Option<&HashMap<String, String>>,
media_type: MediaType, media_type: MediaType,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver, resolver: &LspResolver,
) -> (Option<ParsedSourceResult>, Option<ModuleResult>) { ) -> (
Option<ParsedSourceResult>,
Option<ModuleResult>,
ResolutionMode,
) {
let parsed_source_result = parse_source(specifier.clone(), text, media_type); let parsed_source_result = parse_source(specifier.clone(), text, media_type);
let module_result = analyze_module( let (module_result, resolution_mode) = analyze_module(
specifier, specifier,
&parsed_source_result, &parsed_source_result,
maybe_headers, maybe_headers,
file_referrer, file_referrer,
is_cjs_resolver,
resolver, resolver,
); );
(Some(parsed_source_result), Some(module_result)) (
Some(parsed_source_result),
Some(module_result),
resolution_mode,
)
} }
fn parse_source( fn parse_source(
@ -1613,44 +1655,69 @@ fn analyze_module(
parsed_source_result: &ParsedSourceResult, parsed_source_result: &ParsedSourceResult,
maybe_headers: Option<&HashMap<String, String>>, maybe_headers: Option<&HashMap<String, String>>,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
is_cjs_resolver: &LspIsCjsResolver,
resolver: &LspResolver, resolver: &LspResolver,
) -> ModuleResult { ) -> (ModuleResult, ResolutionMode) {
match parsed_source_result { match parsed_source_result {
Ok(parsed_source) => { Ok(parsed_source) => {
let npm_resolver = resolver.create_graph_npm_resolver(file_referrer); let npm_resolver = resolver.create_graph_npm_resolver(file_referrer);
let cli_resolver = resolver.as_cli_resolver(file_referrer); let cli_resolver = resolver.as_cli_resolver(file_referrer);
let is_cjs_resolver = resolver.as_is_cjs_resolver(file_referrer);
let config_data = resolver.as_config_data(file_referrer); let config_data = resolver.as_config_data(file_referrer);
let valid_referrer = specifier.clone(); let valid_referrer = specifier.clone();
let jsx_import_source_config = let jsx_import_source_config =
config_data.and_then(|d| d.maybe_jsx_import_source_config()); config_data.and_then(|d| d.maybe_jsx_import_source_config());
let module_resolution_mode = is_cjs_resolver.get_lsp_resolution_mode(
&specifier,
Some(parsed_source.compute_is_script()),
);
let resolver = SingleReferrerGraphResolver { let resolver = SingleReferrerGraphResolver {
valid_referrer: &valid_referrer, valid_referrer: &valid_referrer,
referrer_kind: is_cjs_resolver.get_lsp_referrer_kind( module_resolution_mode,
&specifier,
Some(parsed_source.compute_is_script()),
),
cli_resolver, cli_resolver,
jsx_import_source_config: jsx_import_source_config.as_ref(), jsx_import_source_config: jsx_import_source_config.as_ref(),
}; };
Ok(deno_graph::parse_module_from_ast( (
deno_graph::ParseModuleFromAstOptions { Ok(deno_graph::parse_module_from_ast(
graph_kind: deno_graph::GraphKind::TypesOnly, deno_graph::ParseModuleFromAstOptions {
specifier, graph_kind: deno_graph::GraphKind::TypesOnly,
maybe_headers, specifier,
parsed_source, maybe_headers,
// use a null file system because there's no need to bother resolving parsed_source,
// dynamic imports like import(`./dir/${something}`) in the LSP // use a null file system because there's no need to bother resolving
file_system: &deno_graph::source::NullFileSystem, // dynamic imports like import(`./dir/${something}`) in the LSP
jsr_url_provider: &CliJsrUrlProvider, file_system: &deno_graph::source::NullFileSystem,
maybe_resolver: Some(&resolver), jsr_url_provider: &CliJsrUrlProvider,
maybe_npm_resolver: Some(&npm_resolver), maybe_resolver: Some(&resolver),
}, maybe_npm_resolver: Some(&npm_resolver),
)) },
)),
module_resolution_mode,
)
} }
Err(err) => Err(deno_graph::ModuleGraphError::ModuleError( Err(err) => (
deno_graph::ModuleError::ParseErr(specifier, err.clone()), Err(deno_graph::ModuleGraphError::ModuleError(
)), deno_graph::ModuleError::ParseErr(specifier, err.clone()),
)),
ResolutionMode::Import,
),
}
}
fn bytes_to_content(
specifier: &ModuleSpecifier,
media_type: MediaType,
bytes: Vec<u8>,
maybe_charset: Option<&str>,
) -> Result<String, AnyError> {
if media_type == MediaType::Wasm {
// we use the dts representation for Wasm modules
Ok(deno_graph::source::wasm::wasm_module_to_dts(&bytes)?)
} else {
Ok(deno_graph::source::decode_owned_source(
specifier,
bytes,
maybe_charset,
)?)
} }
} }

View file

@ -262,7 +262,7 @@ fn read_cached_url(
cache cache
.get(&cache.cache_item_key(url).ok()?, None) .get(&cache.cache_item_key(url).ok()?, None)
.ok()? .ok()?
.map(|f| f.content) .map(|f| f.content.into_owned())
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -22,7 +22,8 @@ use deno_semver::jsr::JsrPackageReqReference;
use indexmap::Equivalent; use indexmap::Equivalent;
use indexmap::IndexSet; use indexmap::IndexSet;
use log::error; use log::error;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use serde::Deserialize; use serde::Deserialize;
use serde_json::from_value; use serde_json::from_value;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -78,7 +79,6 @@ use super::parent_process_checker;
use super::performance::Performance; use super::performance::Performance;
use super::refactor; use super::refactor;
use super::registries::ModuleRegistry; use super::registries::ModuleRegistry;
use super::resolver::LspIsCjsResolver;
use super::resolver::LspResolver; use super::resolver::LspResolver;
use super::testing; use super::testing;
use super::text; use super::text;
@ -146,7 +146,6 @@ pub struct StateSnapshot {
pub project_version: usize, pub project_version: usize,
pub assets: AssetsSnapshot, pub assets: AssetsSnapshot,
pub config: Arc<Config>, pub config: Arc<Config>,
pub is_cjs_resolver: Arc<LspIsCjsResolver>,
pub documents: Arc<Documents>, pub documents: Arc<Documents>,
pub resolver: Arc<LspResolver>, pub resolver: Arc<LspResolver>,
} }
@ -206,7 +205,6 @@ pub struct Inner {
pub documents: Documents, pub documents: Documents,
http_client_provider: Arc<HttpClientProvider>, http_client_provider: Arc<HttpClientProvider>,
initial_cwd: PathBuf, initial_cwd: PathBuf,
pub is_cjs_resolver: Arc<LspIsCjsResolver>,
jsr_search_api: CliJsrSearchApi, jsr_search_api: CliJsrSearchApi,
/// Handles module registries, which allow discovery of modules /// Handles module registries, which allow discovery of modules
module_registry: ModuleRegistry, module_registry: ModuleRegistry,
@ -484,7 +482,6 @@ impl Inner {
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| { let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory") panic!("Could not resolve current working directory")
}); });
let is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&cache));
Self { Self {
assets, assets,
@ -496,7 +493,6 @@ impl Inner {
documents, documents,
http_client_provider, http_client_provider,
initial_cwd: initial_cwd.clone(), initial_cwd: initial_cwd.clone(),
is_cjs_resolver,
jsr_search_api, jsr_search_api,
project_version: 0, project_version: 0,
task_queue: Default::default(), task_queue: Default::default(),
@ -607,7 +603,6 @@ impl Inner {
project_version: self.project_version, project_version: self.project_version,
assets: self.assets.snapshot(), assets: self.assets.snapshot(),
config: Arc::new(self.config.clone()), config: Arc::new(self.config.clone()),
is_cjs_resolver: self.is_cjs_resolver.clone(),
documents: Arc::new(self.documents.clone()), documents: Arc::new(self.documents.clone()),
resolver: self.resolver.snapshot(), resolver: self.resolver.snapshot(),
}) })
@ -629,7 +624,6 @@ impl Inner {
} }
}); });
self.cache = LspCache::new(global_cache_url); self.cache = LspCache::new(global_cache_url);
self.is_cjs_resolver = Arc::new(LspIsCjsResolver::new(&self.cache));
let deno_dir = self.cache.deno_dir(); let deno_dir = self.cache.deno_dir();
let workspace_settings = self.config.workspace_settings(); let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self let maybe_root_path = self
@ -993,13 +987,10 @@ impl Inner {
let resolver = inner.resolver.as_cli_resolver(Some(&referrer)); let resolver = inner.resolver.as_cli_resolver(Some(&referrer));
let Ok(specifier) = resolver.resolve( let Ok(specifier) = resolver.resolve(
&specifier, &specifier,
&deno_graph::Range { &referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), ResolutionMode::Import,
end: deno_graph::Position::zeroed(), NodeResolutionKind::Types,
},
NodeModuleKind::Esm,
deno_graph::source::ResolutionMode::Types,
) else { ) else {
return; return;
}; };
@ -1036,7 +1027,7 @@ impl Inner {
// refresh the npm specifiers because it might have discovered // refresh the npm specifiers because it might have discovered
// a @types/node package and now's a good time to do that anyway // a @types/node package and now's a good time to do that anyway
self.refresh_npm_specifiers().await; self.refresh_dep_info().await;
self.project_changed([], true); self.project_changed([], true);
} }
@ -1082,7 +1073,7 @@ impl Inner {
); );
if document.is_diagnosable() { if document.is_diagnosable() {
self.project_changed([(document.specifier(), ChangeKind::Opened)], false); self.project_changed([(document.specifier(), ChangeKind::Opened)], false);
self.refresh_npm_specifiers().await; self.refresh_dep_info().await;
self.diagnostics_server.invalidate(&[specifier]); self.diagnostics_server.invalidate(&[specifier]);
self.send_diagnostics_update(); self.send_diagnostics_update();
self.send_testing_update(); self.send_testing_update();
@ -1103,8 +1094,8 @@ impl Inner {
Ok(document) => { Ok(document) => {
if document.is_diagnosable() { if document.is_diagnosable() {
let old_scopes_with_node_specifier = let old_scopes_with_node_specifier =
self.documents.scopes_with_node_specifier().clone(); self.documents.scopes_with_node_specifier();
self.refresh_npm_specifiers().await; self.refresh_dep_info().await;
let mut config_changed = false; let mut config_changed = false;
if !self if !self
.documents .documents
@ -1155,13 +1146,15 @@ impl Inner {
})); }));
} }
async fn refresh_npm_specifiers(&mut self) { async fn refresh_dep_info(&mut self) {
let package_reqs = self.documents.npm_reqs_by_scope(); let dep_info_by_scope = self.documents.dep_info_by_scope();
let resolver = self.resolver.clone(); let resolver = self.resolver.clone();
// spawn due to the lsp's `Send` requirement // spawn due to the lsp's `Send` requirement
spawn(async move { resolver.set_npm_reqs(&package_reqs).await }) spawn(
.await async move { resolver.set_dep_info_by_scope(&dep_info_by_scope).await },
.ok(); )
.await
.ok();
} }
async fn did_close(&mut self, params: DidCloseTextDocumentParams) { async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
@ -1180,7 +1173,7 @@ impl Inner {
.uri_to_specifier(&params.text_document.uri, LspUrlKind::File); .uri_to_specifier(&params.text_document.uri, LspUrlKind::File);
self.diagnostics_state.clear(&specifier); self.diagnostics_state.clear(&specifier);
if self.is_diagnosable(&specifier) { if self.is_diagnosable(&specifier) {
self.refresh_npm_specifiers().await; self.refresh_dep_info().await;
self.diagnostics_server.invalidate(&[specifier.clone()]); self.diagnostics_server.invalidate(&[specifier.clone()]);
self.send_diagnostics_update(); self.send_diagnostics_update();
self.send_testing_update(); self.send_testing_update();
@ -1394,13 +1387,18 @@ impl Inner {
.fmt_config_for_specifier(&specifier) .fmt_config_for_specifier(&specifier)
.options .options
.clone(); .clone();
fmt_options.use_tabs = Some(!params.options.insert_spaces);
fmt_options.indent_width = Some(params.options.tab_size as u8);
let config_data = self.config.tree.data_for_specifier(&specifier); let config_data = self.config.tree.data_for_specifier(&specifier);
if !config_data.is_some_and(|d| d.maybe_deno_json().is_some()) {
fmt_options.use_tabs = Some(!params.options.insert_spaces);
fmt_options.indent_width = Some(params.options.tab_size as u8);
}
let unstable_options = UnstableFmtOptions { let unstable_options = UnstableFmtOptions {
component: config_data component: config_data
.map(|d| d.unstable.contains("fmt-component")) .map(|d| d.unstable.contains("fmt-component"))
.unwrap_or(false), .unwrap_or(false),
sql: config_data
.map(|d| d.unstable.contains("fmt-sql"))
.unwrap_or(false),
}; };
let document = document.clone(); let document = document.clone();
move || { move || {
@ -1633,8 +1631,8 @@ impl Inner {
.get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version()); .get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version());
let specifier_kind = asset_or_doc let specifier_kind = asset_or_doc
.document() .document()
.map(|d| self.is_cjs_resolver.get_doc_module_kind(d)) .map(|d| d.resolution_mode())
.unwrap_or(NodeModuleKind::Esm); .unwrap_or(ResolutionMode::Import);
let mut includes_no_cache = false; let mut includes_no_cache = false;
for diagnostic in &fixable_diagnostics { for diagnostic in &fixable_diagnostics {
match diagnostic.source.as_deref() { match diagnostic.source.as_deref() {
@ -1857,8 +1855,8 @@ impl Inner {
maybe_asset_or_doc maybe_asset_or_doc
.as_ref() .as_ref()
.and_then(|d| d.document()) .and_then(|d| d.document())
.map(|d| self.is_cjs_resolver.get_doc_module_kind(d)) .map(|d| d.resolution_mode())
.unwrap_or(NodeModuleKind::Esm), .unwrap_or(ResolutionMode::Import),
&combined_code_actions.changes, &combined_code_actions.changes,
self, self,
) )
@ -1914,8 +1912,8 @@ impl Inner {
&action_data.specifier, &action_data.specifier,
asset_or_doc asset_or_doc
.document() .document()
.map(|d| self.is_cjs_resolver.get_doc_module_kind(d)) .map(|d| d.resolution_mode())
.unwrap_or(NodeModuleKind::Esm), .unwrap_or(ResolutionMode::Import),
&refactor_edit_info.edits, &refactor_edit_info.edits,
self, self,
) )
@ -2265,7 +2263,6 @@ impl Inner {
&self.jsr_search_api, &self.jsr_search_api,
&self.npm_search_api, &self.npm_search_api,
&self.documents, &self.documents,
&self.is_cjs_resolver,
self.resolver.as_ref(), self.resolver.as_ref(),
self self
.config .config
@ -3600,15 +3597,16 @@ impl Inner {
if byonm { if byonm {
roots.retain(|s| s.scheme() != "npm"); roots.retain(|s| s.scheme() != "npm");
} else if let Some(npm_reqs) = self } else if let Some(dep_info) = self
.documents .documents
.npm_reqs_by_scope() .dep_info_by_scope()
.get(&config_data.map(|d| d.scope.as_ref().clone())) .get(&config_data.map(|d| d.scope.as_ref().clone()))
{ {
// always include the npm packages since resolution of one npm package // always include the npm packages since resolution of one npm package
// might affect the resolution of other npm packages // might affect the resolution of other npm packages
roots.extend( roots.extend(
npm_reqs dep_info
.npm_reqs
.iter() .iter()
.map(|req| ModuleSpecifier::parse(&format!("npm:{}", req)).unwrap()), .map(|req| ModuleSpecifier::parse(&format!("npm:{}", req)).unwrap()),
); );
@ -3629,9 +3627,8 @@ impl Inner {
deno_json_cache: None, deno_json_cache: None,
pkg_json_cache: None, pkg_json_cache: None,
workspace_cache: None, workspace_cache: None,
config_parse_options: deno_config::deno_json::ConfigParseOptions { config_parse_options:
include_task_comments: false, deno_config::deno_json::ConfigParseOptions::default(),
},
additional_config_file_names: &[], additional_config_file_names: &[],
discover_pkg_json: !has_flag_env_var("DENO_NO_PACKAGE_JSON"), discover_pkg_json: !has_flag_env_var("DENO_NO_PACKAGE_JSON"),
maybe_vendor_override: if force_global_cache { maybe_vendor_override: if force_global_cache {
@ -3686,7 +3683,7 @@ impl Inner {
async fn post_cache(&mut self) { async fn post_cache(&mut self) {
self.resolver.did_cache(); self.resolver.did_cache();
self.refresh_npm_specifiers().await; self.refresh_dep_info().await;
self.diagnostics_server.invalidate_all(); self.diagnostics_server.invalidate_all();
self.project_changed([], true); self.project_changed([], true);
self.ts_server.cleanup_semantic_cache(self.snapshot()).await; self.ts_server.cleanup_semantic_cache(self.snapshot()).await;
@ -3774,14 +3771,11 @@ impl Inner {
fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> { fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> {
let mut result = vec![]; let mut result = vec![];
for config_file in self.config.tree.config_files() { for config_file in self.config.tree.config_files() {
if let Some(tasks) = json!(&config_file.json.tasks).as_object() { if let Some(tasks) = config_file.to_tasks_config().ok().flatten() {
for (name, value) in tasks { for (name, def) in tasks {
let Some(command) = value.as_str() else {
continue;
};
result.push(TaskDefinition { result.push(TaskDefinition {
name: name.clone(), name: name.clone(),
command: command.to_string(), command: def.command.clone(),
source_uri: url_to_uri(&config_file.specifier) source_uri: url_to_uri(&config_file.specifier)
.map_err(|_| LspError::internal_error())?, .map_err(|_| LspError::internal_error())?,
}); });

View file

@ -14,8 +14,6 @@ pub const LATEST_DIAGNOSTIC_BATCH_INDEX: &str =
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TaskDefinition { pub struct TaskDefinition {
pub name: String, pub name: String,
// TODO(nayeemrmn): Rename this to `command` in vscode_deno.
#[serde(rename = "detail")]
pub command: String, pub command: String,
pub source_uri: lsp::Uri, pub source_uri: lsp::Uri,
} }

View file

@ -56,9 +56,6 @@ pub async fn start() -> Result<(), AnyError> {
LanguageServer::performance_request, LanguageServer::performance_request,
) )
.custom_method(lsp_custom::TASK_REQUEST, LanguageServer::task_definitions) .custom_method(lsp_custom::TASK_REQUEST, LanguageServer::task_definitions)
// TODO(nayeemrmn): Rename this to `deno/taskDefinitions` in vscode_deno and
// remove this alias.
.custom_method("deno/task", LanguageServer::task_definitions)
.custom_method(testing::TEST_RUN_REQUEST, LanguageServer::test_run_request) .custom_method(testing::TEST_RUN_REQUEST, LanguageServer::test_run_request)
.custom_method( .custom_method(
testing::TEST_RUN_CANCEL_REQUEST, testing::TEST_RUN_CANCEL_REQUEST,

View file

@ -11,7 +11,7 @@ pub fn start(parent_process_id: u32) {
std::thread::sleep(Duration::from_secs(10)); std::thread::sleep(Duration::from_secs(10));
if !is_process_active(parent_process_id) { if !is_process_active(parent_process_id) {
std::process::exit(1); deno_runtime::exit(1);
} }
}); });
} }

View file

@ -7,14 +7,17 @@ use deno_cache_dir::HttpCache;
use deno_config::deno_json::JsxImportSourceConfig; use deno_config::deno_json::JsxImportSourceConfig;
use deno_config::workspace::PackageJsonDepResolution; use deno_config::workspace::PackageJsonDepResolution;
use deno_config::workspace::WorkspaceResolver; use deno_config::workspace::WorkspaceResolver;
use deno_core::parking_lot::Mutex;
use deno_core::url::Url; use deno_core::url::Url;
use deno_graph::source::ResolutionMode;
use deno_graph::GraphImport; use deno_graph::GraphImport;
use deno_graph::ModuleSpecifier; use deno_graph::ModuleSpecifier;
use deno_graph::Range; use deno_graph::Range;
use deno_npm::NpmSystemInfo; use deno_npm::NpmSystemInfo;
use deno_path_util::url_from_directory_path;
use deno_path_util::url_to_file_path; use deno_path_util::url_to_file_path;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_resolver::DenoResolverOptions;
use deno_resolver::NodeAndNpmReqResolver;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PackageJson;
@ -26,8 +29,8 @@ use deno_semver::package::PackageReq;
use indexmap::IndexMap; use indexmap::IndexMap;
use node_resolver::errors::ClosestPkgJsonError; use node_resolver::errors::ClosestPkgJsonError;
use node_resolver::InNpmPackageChecker; use node_resolver::InNpmPackageChecker;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolutionMode; use node_resolver::ResolutionMode;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
@ -36,13 +39,15 @@ use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use super::cache::LspCache; use super::cache::LspCache;
use super::documents::Document;
use super::jsr::JsrCacheResolver; use super::jsr::JsrCacheResolver;
use crate::args::create_default_npmrc; use crate::args::create_default_npmrc;
use crate::args::CacheSetting; use crate::args::CacheSetting;
use crate::args::CliLockfile; use crate::args::CliLockfile;
use crate::args::NpmInstallDepsProvider; use crate::args::NpmInstallDepsProvider;
use crate::cache::DenoCacheEnvFsAdapter; use crate::cache::DenoCacheEnvFsAdapter;
use crate::factory::Deferred;
use crate::graph_util::to_node_resolution_kind;
use crate::graph_util::to_node_resolution_mode;
use crate::graph_util::CliJsrUrlProvider; use crate::graph_util::CliJsrUrlProvider;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
use crate::lsp::config::Config; use crate::lsp::config::Config;
@ -57,40 +62,49 @@ use crate::npm::CliNpmResolverCreateOptions;
use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::CreateInNpmPkgCheckerOptions; use crate::npm::CreateInNpmPkgCheckerOptions;
use crate::npm::ManagedCliNpmResolver; use crate::npm::ManagedCliNpmResolver;
use crate::resolver::CliDenoResolver;
use crate::resolver::CliDenoResolverFs; use crate::resolver::CliDenoResolverFs;
use crate::resolver::CliNodeResolver; use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver; use crate::resolver::CliResolver;
use crate::resolver::CliResolverOptions; use crate::resolver::CliResolverOptions;
use crate::resolver::IsCjsResolver; use crate::resolver::IsCjsResolver;
use crate::resolver::WorkerCliNpmGraphResolver; use crate::resolver::WorkerCliNpmGraphResolver;
use crate::tsc::into_specifier_and_media_type; use crate::tsc::into_specifier_and_media_type;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressBarStyle;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct LspScopeResolver { struct LspScopeResolver {
resolver: Arc<CliResolver>, resolver: Arc<CliResolver>,
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
is_cjs_resolver: Arc<IsCjsResolver>,
jsr_resolver: Option<Arc<JsrCacheResolver>>, jsr_resolver: Option<Arc<JsrCacheResolver>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>, npm_resolver: Option<Arc<dyn CliNpmResolver>>,
node_resolver: Option<Arc<CliNodeResolver>>, node_resolver: Option<Arc<NodeResolver>>,
pkg_json_resolver: Option<Arc<PackageJsonResolver>>, npm_pkg_req_resolver: Option<Arc<CliNpmReqResolver>>,
pkg_json_resolver: Arc<PackageJsonResolver>,
redirect_resolver: Option<Arc<RedirectResolver>>, redirect_resolver: Option<Arc<RedirectResolver>>,
graph_imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>, graph_imports: Arc<IndexMap<ModuleSpecifier, GraphImport>>,
dep_info: Arc<Mutex<Arc<ScopeDepInfo>>>,
package_json_deps_by_resolution: Arc<IndexMap<ModuleSpecifier, String>>, package_json_deps_by_resolution: Arc<IndexMap<ModuleSpecifier, String>>,
config_data: Option<Arc<ConfigData>>, config_data: Option<Arc<ConfigData>>,
} }
impl Default for LspScopeResolver { impl Default for LspScopeResolver {
fn default() -> Self { fn default() -> Self {
let factory = ResolverFactory::new(None);
Self { Self {
resolver: create_cli_resolver(None, None, None), resolver: factory.cli_resolver().clone(),
in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver: None, jsr_resolver: None,
npm_resolver: None, npm_resolver: None,
node_resolver: None, node_resolver: None,
pkg_json_resolver: None, npm_pkg_req_resolver: None,
pkg_json_resolver: factory.pkg_json_resolver().clone(),
redirect_resolver: None, redirect_resolver: None,
graph_imports: Default::default(), graph_imports: Default::default(),
dep_info: Default::default(),
package_json_deps_by_resolution: Default::default(), package_json_deps_by_resolution: Default::default(),
config_data: None, config_data: None,
} }
@ -103,35 +117,16 @@ impl LspScopeResolver {
cache: &LspCache, cache: &LspCache,
http_client_provider: Option<&Arc<HttpClientProvider>>, http_client_provider: Option<&Arc<HttpClientProvider>>,
) -> Self { ) -> Self {
let mut npm_resolver = None; let mut factory = ResolverFactory::new(config_data);
let mut node_resolver = None; if let Some(http_client_provider) = http_client_provider {
let fs = Arc::new(deno_fs::RealFs); factory.init_npm_resolver(http_client_provider, cache).await;
let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
));
if let Some(http_client) = http_client_provider {
npm_resolver = create_npm_resolver(
config_data.map(|d| d.as_ref()),
cache,
http_client,
&pkg_json_resolver,
)
.await;
if let Some(npm_resolver) = &npm_resolver {
let in_npm_pkg_checker = create_in_npm_pkg_checker(npm_resolver);
node_resolver = Some(create_node_resolver(
fs.clone(),
in_npm_pkg_checker,
npm_resolver,
pkg_json_resolver.clone(),
));
}
} }
let cli_resolver = create_cli_resolver( let in_npm_pkg_checker = factory.in_npm_pkg_checker().clone();
config_data.map(|d| d.as_ref()), let npm_resolver = factory.npm_resolver().cloned();
npm_resolver.as_ref(), let node_resolver = factory.node_resolver().cloned();
node_resolver.as_ref(), let npm_pkg_req_resolver = factory.npm_pkg_req_resolver().cloned();
); let cli_resolver = factory.cli_resolver().clone();
let pkg_json_resolver = factory.pkg_json_resolver().clone();
let jsr_resolver = Some(Arc::new(JsrCacheResolver::new( let jsr_resolver = Some(Arc::new(JsrCacheResolver::new(
cache.for_specifier(config_data.map(|d| d.scope.as_ref())), cache.for_specifier(config_data.map(|d| d.scope.as_ref())),
config_data.map(|d| d.as_ref()), config_data.map(|d| d.as_ref()),
@ -152,7 +147,7 @@ impl LspScopeResolver {
.map(|(referrer, imports)| { .map(|(referrer, imports)| {
let resolver = SingleReferrerGraphResolver { let resolver = SingleReferrerGraphResolver {
valid_referrer: &referrer, valid_referrer: &referrer,
referrer_kind: NodeModuleKind::Esm, module_resolution_mode: ResolutionMode::Import,
cli_resolver: &cli_resolver, cli_resolver: &cli_resolver,
jsx_import_source_config: maybe_jsx_import_source_config jsx_import_source_config: maybe_jsx_import_source_config
.as_ref(), .as_ref(),
@ -171,7 +166,7 @@ impl LspScopeResolver {
}) })
.unwrap_or_default(); .unwrap_or_default();
let package_json_deps_by_resolution = (|| { let package_json_deps_by_resolution = (|| {
let node_resolver = node_resolver.as_ref()?; let npm_pkg_req_resolver = npm_pkg_req_resolver.as_ref()?;
let package_json = config_data?.maybe_pkg_json()?; let package_json = config_data?.maybe_pkg_json()?;
let referrer = package_json.specifier(); let referrer = package_json.specifier();
let dependencies = package_json.dependencies.as_ref()?; let dependencies = package_json.dependencies.as_ref()?;
@ -181,14 +176,23 @@ impl LspScopeResolver {
let req_ref = let req_ref =
NpmPackageReqReference::from_str(&format!("npm:{name}")).ok()?; NpmPackageReqReference::from_str(&format!("npm:{name}")).ok()?;
let specifier = into_specifier_and_media_type(Some( let specifier = into_specifier_and_media_type(Some(
node_resolver npm_pkg_req_resolver
.resolve_req_reference( .resolve_req_reference(
&req_ref, &req_ref,
&referrer, &referrer,
// todo(dsherret): this is wrong because it doesn't consider CJS referrers // todo(dsherret): this is wrong because it doesn't consider CJS referrers
NodeModuleKind::Esm, ResolutionMode::Import,
NodeResolutionMode::Types, NodeResolutionKind::Types,
) )
.or_else(|_| {
npm_pkg_req_resolver.resolve_req_reference(
&req_ref,
&referrer,
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
ResolutionMode::Import,
NodeResolutionKind::Execution,
)
})
.ok()?, .ok()?,
)) ))
.0; .0;
@ -201,47 +205,40 @@ impl LspScopeResolver {
Arc::new(package_json_deps_by_resolution.unwrap_or_default()); Arc::new(package_json_deps_by_resolution.unwrap_or_default());
Self { Self {
resolver: cli_resolver, resolver: cli_resolver,
in_npm_pkg_checker,
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver, jsr_resolver,
npm_pkg_req_resolver,
npm_resolver, npm_resolver,
node_resolver, node_resolver,
pkg_json_resolver: Some(pkg_json_resolver), pkg_json_resolver,
redirect_resolver, redirect_resolver,
graph_imports, graph_imports,
dep_info: Default::default(),
package_json_deps_by_resolution, package_json_deps_by_resolution,
config_data: config_data.cloned(), config_data: config_data.cloned(),
} }
} }
fn snapshot(&self) -> Arc<Self> { fn snapshot(&self) -> Arc<Self> {
let mut factory = ResolverFactory::new(self.config_data.as_ref());
let npm_resolver = let npm_resolver =
self.npm_resolver.as_ref().map(|r| r.clone_snapshotted()); self.npm_resolver.as_ref().map(|r| r.clone_snapshotted());
let fs = Arc::new(deno_fs::RealFs);
let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
));
let mut node_resolver = None;
if let Some(npm_resolver) = &npm_resolver { if let Some(npm_resolver) = &npm_resolver {
let in_npm_pkg_checker = create_in_npm_pkg_checker(npm_resolver); factory.set_npm_resolver(npm_resolver.clone());
node_resolver = Some(create_node_resolver(
fs,
in_npm_pkg_checker,
npm_resolver,
pkg_json_resolver.clone(),
));
} }
let graph_resolver = create_cli_resolver(
self.config_data.as_deref(),
npm_resolver.as_ref(),
node_resolver.as_ref(),
);
Arc::new(Self { Arc::new(Self {
resolver: graph_resolver, resolver: factory.cli_resolver().clone(),
in_npm_pkg_checker: factory.in_npm_pkg_checker().clone(),
is_cjs_resolver: factory.is_cjs_resolver().clone(),
jsr_resolver: self.jsr_resolver.clone(), jsr_resolver: self.jsr_resolver.clone(),
npm_resolver, npm_pkg_req_resolver: factory.npm_pkg_req_resolver().cloned(),
node_resolver, npm_resolver: factory.npm_resolver().cloned(),
node_resolver: factory.node_resolver().cloned(),
redirect_resolver: self.redirect_resolver.clone(), redirect_resolver: self.redirect_resolver.clone(),
pkg_json_resolver: Some(pkg_json_resolver), pkg_json_resolver: factory.pkg_json_resolver().clone(),
graph_imports: self.graph_imports.clone(), graph_imports: self.graph_imports.clone(),
dep_info: self.dep_info.clone(),
package_json_deps_by_resolution: self package_json_deps_by_resolution: self
.package_json_deps_by_resolution .package_json_deps_by_resolution
.clone(), .clone(),
@ -308,19 +305,24 @@ impl LspResolver {
} }
} }
pub async fn set_npm_reqs( pub async fn set_dep_info_by_scope(
&self, &self,
reqs: &BTreeMap<Option<ModuleSpecifier>, BTreeSet<PackageReq>>, dep_info_by_scope: &Arc<
BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>,
>,
) { ) {
for (scope, resolver) in [(None, &self.unscoped)] for (scope, resolver) in [(None, &self.unscoped)]
.into_iter() .into_iter()
.chain(self.by_scope.iter().map(|(s, r)| (Some(s), r))) .chain(self.by_scope.iter().map(|(s, r)| (Some(s), r)))
{ {
let dep_info = dep_info_by_scope.get(&scope.cloned());
if let Some(dep_info) = dep_info {
*resolver.dep_info.lock() = dep_info.clone();
}
if let Some(npm_resolver) = resolver.npm_resolver.as_ref() { if let Some(npm_resolver) = resolver.npm_resolver.as_ref() {
if let Some(npm_resolver) = npm_resolver.as_managed() { if let Some(npm_resolver) = npm_resolver.as_managed() {
let reqs = reqs let reqs = dep_info
.get(&scope.cloned()) .map(|i| i.npm_reqs.iter().cloned().collect::<Vec<_>>())
.map(|reqs| reqs.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default(); .unwrap_or_default();
if let Err(err) = npm_resolver.set_package_reqs(&reqs).await { if let Err(err) = npm_resolver.set_package_reqs(&reqs).await {
lsp_warn!("Could not set npm package requirements: {:#}", err); lsp_warn!("Could not set npm package requirements: {:#}", err);
@ -346,6 +348,14 @@ impl LspResolver {
resolver.resolver.create_graph_npm_resolver() resolver.resolver.create_graph_npm_resolver()
} }
pub fn as_is_cjs_resolver(
&self,
file_referrer: Option<&ModuleSpecifier>,
) -> &IsCjsResolver {
let resolver = self.get_scope_resolver(file_referrer);
resolver.is_cjs_resolver.as_ref()
}
pub fn as_config_data( pub fn as_config_data(
&self, &self,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
@ -354,12 +364,12 @@ impl LspResolver {
resolver.config_data.as_ref() resolver.config_data.as_ref()
} }
pub fn maybe_node_resolver( pub fn in_npm_pkg_checker(
&self, &self,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
) -> Option<&Arc<CliNodeResolver>> { ) -> &Arc<dyn InNpmPackageChecker> {
let resolver = self.get_scope_resolver(file_referrer); let resolver = self.get_scope_resolver(file_referrer);
resolver.node_resolver.as_ref() &resolver.in_npm_pkg_checker
} }
pub fn maybe_managed_npm_resolver( pub fn maybe_managed_npm_resolver(
@ -425,18 +435,18 @@ impl LspResolver {
&self, &self,
req_ref: &NpmPackageReqReference, req_ref: &NpmPackageReqReference,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
file_referrer: Option<&ModuleSpecifier>, file_referrer: Option<&ModuleSpecifier>,
) -> Option<(ModuleSpecifier, MediaType)> { ) -> Option<(ModuleSpecifier, MediaType)> {
let resolver = self.get_scope_resolver(file_referrer); let resolver = self.get_scope_resolver(file_referrer);
let node_resolver = resolver.node_resolver.as_ref()?; let npm_pkg_req_resolver = resolver.npm_pkg_req_resolver.as_ref()?;
Some(into_specifier_and_media_type(Some( Some(into_specifier_and_media_type(Some(
node_resolver npm_pkg_req_resolver
.resolve_req_reference( .resolve_req_reference(
req_ref, req_ref,
referrer, referrer,
referrer_kind, resolution_mode,
NodeResolutionMode::Types, NodeResolutionKind::Types,
) )
.ok()?, .ok()?,
))) )))
@ -454,6 +464,19 @@ impl LspResolver {
.cloned() .cloned()
} }
pub fn deno_types_to_code_resolution(
&self,
specifier: &ModuleSpecifier,
file_referrer: Option<&ModuleSpecifier>,
) -> Option<ModuleSpecifier> {
let resolver = self.get_scope_resolver(file_referrer);
let dep_info = resolver.dep_info.lock().clone();
dep_info
.deno_types_to_code_resolutions
.get(specifier)
.cloned()
}
pub fn in_node_modules(&self, specifier: &ModuleSpecifier) -> bool { pub fn in_node_modules(&self, specifier: &ModuleSpecifier) -> bool {
fn has_node_modules_dir(specifier: &ModuleSpecifier) -> bool { fn has_node_modules_dir(specifier: &ModuleSpecifier) -> bool {
// consider any /node_modules/ directory as being in the node_modules // consider any /node_modules/ directory as being in the node_modules
@ -480,18 +503,19 @@ impl LspResolver {
&self, &self,
specifier_text: &str, specifier_text: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
) -> bool { ) -> bool {
let resolver = self.get_scope_resolver(Some(referrer)); let resolver = self.get_scope_resolver(Some(referrer));
let Some(node_resolver) = resolver.node_resolver.as_ref() else { let Some(npm_pkg_req_resolver) = resolver.npm_pkg_req_resolver.as_ref()
else {
return false; return false;
}; };
node_resolver npm_pkg_req_resolver
.resolve_if_for_npm_pkg( .resolve_if_for_npm_pkg(
specifier_text, specifier_text,
referrer, referrer,
referrer_kind, resolution_mode,
NodeResolutionMode::Types, NodeResolutionKind::Types,
) )
.ok() .ok()
.flatten() .flatten()
@ -503,10 +527,9 @@ impl LspResolver {
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> Result<Option<Arc<PackageJson>>, ClosestPkgJsonError> { ) -> Result<Option<Arc<PackageJson>>, ClosestPkgJsonError> {
let resolver = self.get_scope_resolver(Some(referrer)); let resolver = self.get_scope_resolver(Some(referrer));
let Some(pkg_json_resolver) = resolver.pkg_json_resolver.as_ref() else { resolver
return Ok(None); .pkg_json_resolver
}; .get_closest_package_json(referrer)
pkg_json_resolver.get_closest_package_json(referrer)
} }
pub fn resolve_redirects( pub fn resolve_redirects(
@ -558,131 +581,231 @@ impl LspResolver {
} }
} }
async fn create_npm_resolver( #[derive(Debug, Default, Clone)]
config_data: Option<&ConfigData>, pub struct ScopeDepInfo {
cache: &LspCache, pub deno_types_to_code_resolutions: HashMap<ModuleSpecifier, ModuleSpecifier>,
http_client_provider: &Arc<HttpClientProvider>, pub npm_reqs: BTreeSet<PackageReq>,
pkg_json_resolver: &Arc<PackageJsonResolver>, pub has_node_specifier: bool,
) -> Option<Arc<dyn CliNpmResolver>> {
let enable_byonm = config_data.map(|d| d.byonm).unwrap_or(false);
let options = if enable_byonm {
CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions {
fs: CliDenoResolverFs(Arc::new(deno_fs::RealFs)),
pkg_json_resolver: pkg_json_resolver.clone(),
root_node_modules_dir: config_data.and_then(|config_data| {
config_data.node_modules_dir.clone().or_else(|| {
url_to_file_path(&config_data.scope)
.ok()
.map(|p| p.join("node_modules/"))
})
}),
})
} else {
let npmrc = config_data
.and_then(|d| d.npmrc.clone())
.unwrap_or_else(create_default_npmrc);
let npm_cache_dir = Arc::new(NpmCacheDir::new(
&DenoCacheEnvFsAdapter(&deno_fs::RealFs),
cache.deno_dir().npm_folder_path(),
npmrc.get_all_known_registries_urls(),
));
CliNpmResolverCreateOptions::Managed(CliManagedNpmResolverCreateOptions {
http_client_provider: http_client_provider.clone(),
snapshot: match config_data.and_then(|d| d.lockfile.as_ref()) {
Some(lockfile) => {
CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(
lockfile.clone(),
)
}
None => CliNpmResolverManagedSnapshotOption::Specified(None),
},
// Don't provide the lockfile. We don't want these resolvers
// updating it. Only the cache request should update the lockfile.
maybe_lockfile: None,
fs: Arc::new(deno_fs::RealFs),
npm_cache_dir,
// Use an "only" cache setting in order to make the
// user do an explicit "cache" command and prevent
// the cache from being filled with lots of packages while
// the user is typing.
cache_setting: CacheSetting::Only,
text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly),
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
npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()),
npmrc,
npm_system_info: NpmSystemInfo::default(),
lifecycle_scripts: Default::default(),
})
};
Some(create_cli_npm_resolver_for_lsp(options).await)
} }
fn create_in_npm_pkg_checker( #[derive(Default)]
npm_resolver: &Arc<dyn CliNpmResolver>, struct ResolverFactoryServices {
) -> Arc<dyn InNpmPackageChecker> { cli_resolver: Deferred<Arc<CliResolver>>,
crate::npm::create_in_npm_pkg_checker(match npm_resolver.as_inner() { in_npm_pkg_checker: Deferred<Arc<dyn InNpmPackageChecker>>,
crate::npm::InnerCliNpmResolverRef::Byonm(_) => { is_cjs_resolver: Deferred<Arc<IsCjsResolver>>,
CreateInNpmPkgCheckerOptions::Byonm node_resolver: Deferred<Option<Arc<NodeResolver>>>,
npm_pkg_req_resolver: Deferred<Option<Arc<CliNpmReqResolver>>>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
}
struct ResolverFactory<'a> {
config_data: Option<&'a Arc<ConfigData>>,
fs: Arc<dyn deno_fs::FileSystem>,
pkg_json_resolver: Arc<PackageJsonResolver>,
services: ResolverFactoryServices,
}
impl<'a> ResolverFactory<'a> {
pub fn new(config_data: Option<&'a Arc<ConfigData>>) -> Self {
let fs = Arc::new(deno_fs::RealFs);
let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
));
Self {
config_data,
fs,
pkg_json_resolver,
services: Default::default(),
} }
crate::npm::InnerCliNpmResolverRef::Managed(m) => { }
CreateInNpmPkgCheckerOptions::Managed(
CliManagedInNpmPkgCheckerCreateOptions { async fn init_npm_resolver(
root_cache_dir_url: m.global_cache_root_url(), &mut self,
maybe_node_modules_path: m.maybe_node_modules_path(), http_client_provider: &Arc<HttpClientProvider>,
cache: &LspCache,
) {
let enable_byonm = self.config_data.map(|d| d.byonm).unwrap_or(false);
let options = if enable_byonm {
CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions {
fs: CliDenoResolverFs(Arc::new(deno_fs::RealFs)),
pkg_json_resolver: self.pkg_json_resolver.clone(),
root_node_modules_dir: self.config_data.and_then(|config_data| {
config_data.node_modules_dir.clone().or_else(|| {
url_to_file_path(&config_data.scope)
.ok()
.map(|p| p.join("node_modules/"))
})
}),
})
} else {
let npmrc = self
.config_data
.and_then(|d| d.npmrc.clone())
.unwrap_or_else(create_default_npmrc);
let npm_cache_dir = Arc::new(NpmCacheDir::new(
&DenoCacheEnvFsAdapter(self.fs.as_ref()),
cache.deno_dir().npm_folder_path(),
npmrc.get_all_known_registries_urls(),
));
CliNpmResolverCreateOptions::Managed(CliManagedNpmResolverCreateOptions {
http_client_provider: http_client_provider.clone(),
snapshot: match self.config_data.and_then(|d| d.lockfile.as_ref()) {
Some(lockfile) => {
CliNpmResolverManagedSnapshotOption::ResolveFromLockfile(
lockfile.clone(),
)
}
None => CliNpmResolverManagedSnapshotOption::Specified(None),
},
// Don't provide the lockfile. We don't want these resolvers
// updating it. Only the cache request should update the lockfile.
maybe_lockfile: None,
fs: Arc::new(deno_fs::RealFs),
npm_cache_dir,
// Use an "only" cache setting in order to make the
// user do an explicit "cache" command and prevent
// the cache from being filled with lots of packages while
// the user is typing.
cache_setting: CacheSetting::Only,
text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly),
maybe_node_modules_path: self
.config_data
.and_then(|d| d.node_modules_dir.clone()),
// only used for top level install, so we can ignore this
npm_install_deps_provider: Arc::new(NpmInstallDepsProvider::empty()),
npmrc,
npm_system_info: NpmSystemInfo::default(),
lifecycle_scripts: Default::default(),
})
};
self.set_npm_resolver(create_cli_npm_resolver_for_lsp(options).await);
}
pub fn set_npm_resolver(&mut self, npm_resolver: Arc<dyn CliNpmResolver>) {
self.services.npm_resolver = Some(npm_resolver);
}
pub fn npm_resolver(&self) -> Option<&Arc<dyn CliNpmResolver>> {
self.services.npm_resolver.as_ref()
}
pub fn cli_resolver(&self) -> &Arc<CliResolver> {
self.services.cli_resolver.get_or_init(|| {
let npm_req_resolver = self.npm_pkg_req_resolver().cloned();
let deno_resolver = Arc::new(CliDenoResolver::new(DenoResolverOptions {
in_npm_pkg_checker: self.in_npm_pkg_checker().clone(),
node_and_req_resolver: match (self.node_resolver(), npm_req_resolver) {
(Some(node_resolver), Some(npm_req_resolver)) => {
Some(NodeAndNpmReqResolver {
node_resolver: node_resolver.clone(),
npm_req_resolver,
})
}
_ => None,
},
sloppy_imports_resolver: self
.config_data
.and_then(|d| d.sloppy_imports_resolver.clone()),
workspace_resolver: self
.config_data
.map(|d| d.resolver.clone())
.unwrap_or_else(|| {
Arc::new(WorkspaceResolver::new_raw(
// this is fine because this is only used before initialization
Arc::new(ModuleSpecifier::parse("file:///").unwrap()),
None,
Vec::new(),
Vec::new(),
PackageJsonDepResolution::Disabled,
))
}),
is_byonm: self.config_data.map(|d| d.byonm).unwrap_or(false),
maybe_vendor_dir: self.config_data.and_then(|d| d.vendor_dir.as_ref()),
}));
Arc::new(CliResolver::new(CliResolverOptions {
deno_resolver,
npm_resolver: self.npm_resolver().cloned(),
bare_node_builtins_enabled: self
.config_data
.is_some_and(|d| d.unstable.contains("bare-node-builtins")),
}))
})
}
pub fn pkg_json_resolver(&self) -> &Arc<PackageJsonResolver> {
&self.pkg_json_resolver
}
pub fn in_npm_pkg_checker(&self) -> &Arc<dyn InNpmPackageChecker> {
self.services.in_npm_pkg_checker.get_or_init(|| {
crate::npm::create_in_npm_pkg_checker(
match self.services.npm_resolver.as_ref().map(|r| r.as_inner()) {
Some(crate::npm::InnerCliNpmResolverRef::Byonm(_)) | None => {
CreateInNpmPkgCheckerOptions::Byonm
}
Some(crate::npm::InnerCliNpmResolverRef::Managed(m)) => {
CreateInNpmPkgCheckerOptions::Managed(
CliManagedInNpmPkgCheckerCreateOptions {
root_cache_dir_url: m.global_cache_root_url(),
maybe_node_modules_path: m.maybe_node_modules_path(),
},
)
}
}, },
) )
} })
}) }
}
fn create_node_resolver( pub fn is_cjs_resolver(&self) -> &Arc<IsCjsResolver> {
fs: Arc<dyn deno_fs::FileSystem>, self.services.is_cjs_resolver.get_or_init(|| {
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, Arc::new(IsCjsResolver::new(
npm_resolver: &Arc<dyn CliNpmResolver>, self.in_npm_pkg_checker().clone(),
pkg_json_resolver: Arc<PackageJsonResolver>, self.pkg_json_resolver().clone(),
) -> Arc<CliNodeResolver> { if self
let node_resolver_inner = Arc::new(NodeResolver::new( .config_data
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), .is_some_and(|d| d.unstable.contains("detect-cjs"))
in_npm_pkg_checker.clone(), {
npm_resolver.clone().into_npm_resolver(), IsCjsResolutionMode::ImplicitTypeCommonJs
pkg_json_resolver.clone(), } else {
)); IsCjsResolutionMode::ExplicitTypeCommonJs
Arc::new(CliNodeResolver::new( },
fs, ))
in_npm_pkg_checker, })
node_resolver_inner, }
npm_resolver.clone(),
))
}
fn create_cli_resolver( pub fn node_resolver(&self) -> Option<&Arc<NodeResolver>> {
config_data: Option<&ConfigData>, self
npm_resolver: Option<&Arc<dyn CliNpmResolver>>, .services
node_resolver: Option<&Arc<CliNodeResolver>>, .node_resolver
) -> Arc<CliResolver> { .get_or_init(|| {
Arc::new(CliResolver::new(CliResolverOptions { let npm_resolver = self.services.npm_resolver.as_ref()?;
node_resolver: node_resolver.cloned(), Some(Arc::new(NodeResolver::new(
npm_resolver: npm_resolver.cloned(), deno_runtime::deno_node::DenoFsNodeResolverEnv::new(self.fs.clone()),
workspace_resolver: config_data.map(|d| d.resolver.clone()).unwrap_or_else( self.in_npm_pkg_checker().clone(),
|| { npm_resolver.clone().into_npm_pkg_folder_resolver(),
Arc::new(WorkspaceResolver::new_raw( self.pkg_json_resolver.clone(),
// this is fine because this is only used before initialization )))
Arc::new(ModuleSpecifier::parse("file:///").unwrap()), })
None, .as_ref()
Vec::new(), }
Vec::new(),
PackageJsonDepResolution::Disabled, pub fn npm_pkg_req_resolver(&self) -> Option<&Arc<CliNpmReqResolver>> {
)) self
}, .services
), .npm_pkg_req_resolver
maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()), .get_or_init(|| {
bare_node_builtins_enabled: config_data let node_resolver = self.node_resolver()?;
.is_some_and(|d| d.unstable.contains("bare-node-builtins")), let npm_resolver = self.npm_resolver()?;
sloppy_imports_resolver: config_data Some(Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions {
.and_then(|d| d.sloppy_imports_resolver.clone()), byonm_resolver: (npm_resolver.clone()).into_maybe_byonm(),
})) fs: CliDenoResolverFs(self.fs.clone()),
in_npm_pkg_checker: self.in_npm_pkg_checker().clone(),
node_resolver: node_resolver.clone(),
npm_req_resolver: npm_resolver.clone().into_npm_req_resolver(),
})))
})
.as_ref()
}
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -709,99 +832,10 @@ impl std::fmt::Debug for RedirectResolver {
} }
} }
#[derive(Debug)]
pub struct LspIsCjsResolver {
inner: IsCjsResolver,
}
impl Default for LspIsCjsResolver {
fn default() -> Self {
LspIsCjsResolver::new(&Default::default())
}
}
impl LspIsCjsResolver {
pub fn new(cache: &LspCache) -> Self {
#[derive(Debug)]
struct LspInNpmPackageChecker {
global_cache_dir: ModuleSpecifier,
}
impl LspInNpmPackageChecker {
pub fn new(cache: &LspCache) -> Self {
let npm_folder_path = cache.deno_dir().npm_folder_path();
Self {
global_cache_dir: url_from_directory_path(
&canonicalize_path_maybe_not_exists(&npm_folder_path)
.unwrap_or(npm_folder_path),
)
.unwrap_or_else(|_| {
ModuleSpecifier::parse("file:///invalid/").unwrap()
}),
}
}
}
impl InNpmPackageChecker for LspInNpmPackageChecker {
fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
if specifier.scheme() != "file" {
return false;
}
if specifier
.as_str()
.starts_with(self.global_cache_dir.as_str())
{
return true;
}
specifier.as_str().contains("/node_modules/")
}
}
let fs = Arc::new(deno_fs::RealFs);
let pkg_json_resolver = Arc::new(PackageJsonResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
));
LspIsCjsResolver {
inner: IsCjsResolver::new(
Arc::new(LspInNpmPackageChecker::new(cache)),
pkg_json_resolver,
crate::resolver::IsCjsResolverOptions {
detect_cjs: true,
is_node_main: false,
},
),
}
}
pub fn get_maybe_doc_module_kind(
&self,
specifier: &ModuleSpecifier,
maybe_document: Option<&Document>,
) -> NodeModuleKind {
self.get_lsp_referrer_kind(
specifier,
maybe_document.and_then(|d| d.is_script()),
)
}
pub fn get_doc_module_kind(&self, document: &Document) -> NodeModuleKind {
self.get_lsp_referrer_kind(document.specifier(), document.is_script())
}
pub fn get_lsp_referrer_kind(
&self,
specifier: &ModuleSpecifier,
is_script: Option<bool>,
) -> NodeModuleKind {
self.inner.get_lsp_referrer_kind(specifier, is_script)
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct SingleReferrerGraphResolver<'a> { pub struct SingleReferrerGraphResolver<'a> {
pub valid_referrer: &'a ModuleSpecifier, pub valid_referrer: &'a ModuleSpecifier,
pub referrer_kind: NodeModuleKind, pub module_resolution_mode: ResolutionMode,
pub cli_resolver: &'a CliResolver, pub cli_resolver: &'a CliResolver,
pub jsx_import_source_config: Option<&'a JsxImportSourceConfig>, pub jsx_import_source_config: Option<&'a JsxImportSourceConfig>,
} }
@ -830,16 +864,20 @@ impl<'a> deno_graph::source::Resolver for SingleReferrerGraphResolver<'a> {
&self, &self,
specifier_text: &str, specifier_text: &str,
referrer_range: &Range, referrer_range: &Range,
mode: ResolutionMode, resolution_kind: deno_graph::source::ResolutionKind,
) -> Result<ModuleSpecifier, deno_graph::source::ResolveError> { ) -> Result<ModuleSpecifier, deno_graph::source::ResolveError> {
// this resolver assumes it will only be used with a single referrer // this resolver assumes it will only be used with a single referrer
// with the provided referrer kind // with the provided referrer kind
debug_assert_eq!(referrer_range.specifier, *self.valid_referrer); debug_assert_eq!(referrer_range.specifier, *self.valid_referrer);
self.cli_resolver.resolve( self.cli_resolver.resolve(
specifier_text, specifier_text,
referrer_range, &referrer_range.specifier,
self.referrer_kind, referrer_range.range.start,
mode, referrer_range
.resolution_mode
.map(to_node_resolution_mode)
.unwrap_or(self.module_resolution_mode),
to_node_resolution_kind(resolution_kind),
) )
} }
} }

View file

@ -34,6 +34,7 @@ use crate::util::path::relative_specifier;
use crate::util::path::to_percent_decoded_str; use crate::util::path::to_percent_decoded_str;
use crate::util::result::InfallibleResultExt; use crate::util::result::InfallibleResultExt;
use crate::util::v8::convert; use crate::util::v8::convert;
use crate::worker::create_isolate_create_params;
use deno_core::convert::Smi; use deno_core::convert::Smi;
use deno_core::convert::ToV8; use deno_core::convert::ToV8;
use deno_core::error::StdAnyError; use deno_core::error::StdAnyError;
@ -69,7 +70,7 @@ use indexmap::IndexMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use lazy_regex::lazy_regex; use lazy_regex::lazy_regex;
use log::error; use log::error;
use node_resolver::NodeModuleKind; use node_resolver::ResolutionMode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Captures; use regex::Captures;
use regex::Regex; use regex::Regex;
@ -1296,16 +1297,10 @@ impl TsServer {
{ {
// When an LSP request is cancelled by the client, the future this is being // When an LSP request is cancelled by the client, the future this is being
// executed under and any local variables here will be dropped at the next // executed under and any local variables here will be dropped at the next
// await point. To pass on that cancellation to the TS thread, we make this // await point. To pass on that cancellation to the TS thread, we use drop_guard
// wrapper which cancels the request's token on drop. // which cancels the request's token on drop.
struct DroppableToken(CancellationToken);
impl Drop for DroppableToken {
fn drop(&mut self) {
self.0.cancel();
}
}
let token = token.child_token(); let token = token.child_token();
let droppable_token = DroppableToken(token.clone()); let droppable_token = token.clone().drop_guard();
let (tx, mut rx) = oneshot::channel::<Result<String, AnyError>>(); let (tx, mut rx) = oneshot::channel::<Result<String, AnyError>>();
let change = self.pending_change.lock().take(); let change = self.pending_change.lock().take();
@ -1319,7 +1314,7 @@ impl TsServer {
tokio::select! { tokio::select! {
value = &mut rx => { value = &mut rx => {
let value = value??; let value = value??;
drop(droppable_token); droppable_token.disarm();
Ok(serde_json::from_str(&value)?) Ok(serde_json::from_str(&value)?)
} }
_ = token.cancelled() => { _ = token.cancelled() => {
@ -3416,9 +3411,18 @@ fn parse_code_actions(
additional_text_edits.extend(change.text_changes.iter().map(|tc| { additional_text_edits.extend(change.text_changes.iter().map(|tc| {
let mut text_edit = tc.as_text_edit(asset_or_doc.line_index()); let mut text_edit = tc.as_text_edit(asset_or_doc.line_index());
if let Some(specifier_rewrite) = &data.specifier_rewrite { if let Some(specifier_rewrite) = &data.specifier_rewrite {
text_edit.new_text = text_edit text_edit.new_text = text_edit.new_text.replace(
.new_text &specifier_rewrite.old_specifier,
.replace(&specifier_rewrite.0, &specifier_rewrite.1); &specifier_rewrite.new_specifier,
);
if let Some(deno_types_specifier) =
&specifier_rewrite.new_deno_types_specifier
{
text_edit.new_text = format!(
"// @deno-types=\"{}\"\n{}",
deno_types_specifier, &text_edit.new_text
);
}
} }
text_edit text_edit
})); }));
@ -3577,17 +3581,23 @@ impl CompletionEntryDetails {
let mut text_edit = original_item.text_edit.clone(); let mut text_edit = original_item.text_edit.clone();
if let Some(specifier_rewrite) = &data.specifier_rewrite { if let Some(specifier_rewrite) = &data.specifier_rewrite {
if let Some(text_edit) = &mut text_edit { if let Some(text_edit) = &mut text_edit {
match text_edit { let new_text = match text_edit {
lsp::CompletionTextEdit::Edit(text_edit) => { lsp::CompletionTextEdit::Edit(text_edit) => &mut text_edit.new_text,
text_edit.new_text = text_edit
.new_text
.replace(&specifier_rewrite.0, &specifier_rewrite.1);
}
lsp::CompletionTextEdit::InsertAndReplace(insert_replace_edit) => { lsp::CompletionTextEdit::InsertAndReplace(insert_replace_edit) => {
insert_replace_edit.new_text = insert_replace_edit &mut insert_replace_edit.new_text
.new_text
.replace(&specifier_rewrite.0, &specifier_rewrite.1);
} }
};
*new_text = new_text.replace(
&specifier_rewrite.old_specifier,
&specifier_rewrite.new_specifier,
);
if let Some(deno_types_specifier) =
&specifier_rewrite.new_deno_types_specifier
{
*new_text = format!(
"// @deno-types=\"{}\"\n{}",
deno_types_specifier, new_text
);
} }
} }
} }
@ -3692,6 +3702,13 @@ impl CompletionInfo {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CompletionSpecifierRewrite {
old_specifier: String,
new_specifier: String,
new_deno_types_specifier: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CompletionItemData { pub struct CompletionItemData {
@ -3704,7 +3721,7 @@ pub struct CompletionItemData {
/// be rewritten by replacing the first string with the second. Intended for /// be rewritten by replacing the first string with the second. Intended for
/// auto-import specifiers to be reverse-import-mapped. /// auto-import specifiers to be reverse-import-mapped.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub specifier_rewrite: Option<(String, String)>, pub specifier_rewrite: Option<CompletionSpecifierRewrite>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>, pub data: Option<Value>,
pub use_code_snippet: bool, pub use_code_snippet: bool,
@ -3926,20 +3943,40 @@ impl CompletionEntry {
if let Some(source) = &self.source { if let Some(source) = &self.source {
let mut display_source = source.clone(); let mut display_source = source.clone();
if let Some(import_data) = &self.auto_import_data { if let Some(import_data) = &self.auto_import_data {
if let Some(new_module_specifier) = language_server let import_mapper =
.get_ts_response_import_mapper(specifier) language_server.get_ts_response_import_mapper(specifier);
if let Some(mut new_specifier) = import_mapper
.check_specifier(&import_data.normalized, specifier) .check_specifier(&import_data.normalized, specifier)
.or_else(|| relative_specifier(specifier, &import_data.normalized)) .or_else(|| relative_specifier(specifier, &import_data.normalized))
{ {
if new_module_specifier.contains("/node_modules/") { if new_specifier.contains("/node_modules/") {
return None; return None;
} }
display_source.clone_from(&new_module_specifier); let mut new_deno_types_specifier = None;
if new_module_specifier != import_data.raw.module_specifier { if let Some(code_specifier) = language_server
specifier_rewrite = Some(( .resolver
import_data.raw.module_specifier.clone(), .deno_types_to_code_resolution(
new_module_specifier, &import_data.normalized,
)); Some(specifier),
)
.and_then(|s| {
import_mapper
.check_specifier(&s, specifier)
.or_else(|| relative_specifier(specifier, &s))
})
{
new_deno_types_specifier =
Some(std::mem::replace(&mut new_specifier, code_specifier));
}
display_source.clone_from(&new_specifier);
if new_specifier != import_data.raw.module_specifier
|| new_deno_types_specifier.is_some()
{
specifier_rewrite = Some(CompletionSpecifierRewrite {
old_specifier: import_data.raw.module_specifier.clone(),
new_specifier,
new_deno_types_specifier,
});
} }
} else if source.starts_with(jsr_url().as_str()) { } else if source.starts_with(jsr_url().as_str()) {
return None; return None;
@ -4245,9 +4282,7 @@ impl TscSpecifierMap {
return specifier.to_string(); return specifier.to_string();
} }
let mut specifier = original.to_string(); let mut specifier = original.to_string();
if specifier.contains("/node_modules/.deno/") if !specifier.contains("/node_modules/@types/node/") {
&& !specifier.contains("/node_modules/@types/node/")
{
// The ts server doesn't give completions from files in // The ts server doesn't give completions from files in
// `node_modules/.deno/`. We work around it like this. // `node_modules/.deno/`. We work around it like this.
specifier = specifier.replace("/node_modules/", "/$node_modules/"); specifier = specifier.replace("/node_modules/", "/$node_modules/");
@ -4408,14 +4443,12 @@ fn op_load<'s>(
version: state.script_version(&specifier), version: state.script_version(&specifier),
is_cjs: doc is_cjs: doc
.document() .document()
.map(|d| state.state_snapshot.is_cjs_resolver.get_doc_module_kind(d)) .map(|d| d.resolution_mode())
.unwrap_or(NodeModuleKind::Esm) .unwrap_or(ResolutionMode::Import)
== NodeModuleKind::Cjs, == ResolutionMode::Require,
}) })
}; };
let serialized = serde_v8::to_v8(scope, maybe_load_response)?; let serialized = serde_v8::to_v8(scope, maybe_load_response)?;
state.performance.measure(mark); state.performance.measure(mark);
Ok(serialized) Ok(serialized)
} }
@ -4440,17 +4473,9 @@ fn op_release(
fn op_resolve( fn op_resolve(
state: &mut OpState, state: &mut OpState,
#[string] base: String, #[string] base: String,
is_base_cjs: bool, #[serde] specifiers: Vec<(bool, String)>,
#[serde] specifiers: Vec<String>,
) -> Result<Vec<Option<(String, String)>>, AnyError> { ) -> Result<Vec<Option<(String, String)>>, AnyError> {
op_resolve_inner( op_resolve_inner(state, ResolveArgs { base, specifiers })
state,
ResolveArgs {
base,
is_base_cjs,
specifiers,
},
)
} }
struct TscRequestArray { struct TscRequestArray {
@ -4653,10 +4678,7 @@ fn op_script_names(state: &mut OpState) -> ScriptNames {
let (types, _) = documents.resolve_dependency( let (types, _) = documents.resolve_dependency(
types, types,
specifier, specifier,
state doc.resolution_mode(),
.state_snapshot
.is_cjs_resolver
.get_doc_module_kind(doc),
doc.file_referrer(), doc.file_referrer(),
)?; )?;
let types_doc = documents.get_or_load(&types, doc.file_referrer())?; let types_doc = documents.get_or_load(&types, doc.file_referrer())?;
@ -4760,6 +4782,7 @@ fn run_tsc_thread(
specifier_map, specifier_map,
request_rx, request_rx,
)], )],
create_params: create_isolate_create_params(),
startup_snapshot: Some(tsc::compiler_snapshot()), startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: has_inspector_server, inspector: has_inspector_server,
..Default::default() ..Default::default()
@ -5539,7 +5562,6 @@ mod tests {
documents: Arc::new(documents), documents: Arc::new(documents),
assets: Default::default(), assets: Default::default(),
config: Arc::new(config), config: Arc::new(config),
is_cjs_resolver: Default::default(),
resolver, resolver,
}); });
let performance = Arc::new(Performance::default()); let performance = Arc::new(Performance::default());
@ -5565,7 +5587,7 @@ mod tests {
let (_tx, rx) = mpsc::unbounded_channel(); let (_tx, rx) = mpsc::unbounded_channel();
let state = let state =
State::new(state_snapshot, Default::default(), Default::default(), rx); State::new(state_snapshot, Default::default(), Default::default(), rx);
let mut op_state = OpState::new(None); let mut op_state = OpState::new(None, None);
op_state.put(state); op_state.put(state);
op_state op_state
} }
@ -6390,8 +6412,7 @@ mod tests {
&mut state, &mut state,
ResolveArgs { ResolveArgs {
base: temp_dir.url().join("a.ts").unwrap().to_string(), base: temp_dir.url().join("a.ts").unwrap().to_string(),
is_base_cjs: false, specifiers: vec![(false, "./b.ts".to_string())],
specifiers: vec!["./b.ts".to_string()],
}, },
) )
.unwrap(); .unwrap();

View file

@ -37,6 +37,7 @@ use crate::util::v8::init_v8_flags;
use args::TaskFlags; use args::TaskFlags;
use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError; use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError;
use deno_resolver::npm::ResolvePkgFolderFromDenoReqError;
use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerExecutionMode;
pub use deno_runtime::UNSTABLE_GRANULAR_FLAGS; pub use deno_runtime::UNSTABLE_GRANULAR_FLAGS;
@ -50,7 +51,6 @@ use deno_runtime::fmt_errors::format_js_error;
use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics; use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics;
use deno_terminal::colors; use deno_terminal::colors;
use factory::CliFactory; use factory::CliFactory;
use npm::ResolvePkgFolderFromDenoReqError;
use standalone::MODULE_NOT_FOUND; use standalone::MODULE_NOT_FOUND;
use standalone::UNSUPPORTED_SCHEME; use standalone::UNSUPPORTED_SCHEME;
use std::env; use std::env;
@ -144,9 +144,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
} }
DenoSubcommand::Init(init_flags) => { DenoSubcommand::Init(init_flags) => {
spawn_subcommand(async { spawn_subcommand(async {
// make compiler happy since init_project is sync tools::init::init_project(init_flags).await
tokio::task::yield_now().await;
tools::init::init_project(init_flags)
}) })
} }
DenoSubcommand::Info(info_flags) => { DenoSubcommand::Info(info_flags) => {
@ -188,6 +186,11 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
tools::lint::lint(flags, lint_flags).await tools::lint::lint(flags, lint_flags).await
} }
}), }),
DenoSubcommand::Outdated(update_flags) => {
spawn_subcommand(async move {
tools::registry::outdated(flags, update_flags).await
})
}
DenoSubcommand::Repl(repl_flags) => { DenoSubcommand::Repl(repl_flags) => {
spawn_subcommand(async move { tools::repl::run(flags, repl_flags).await }) spawn_subcommand(async move { tools::repl::run(flags, repl_flags).await })
} }
@ -238,6 +241,9 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
cwd: None, cwd: None,
task: Some(run_flags.script.clone()), task: Some(run_flags.script.clone()),
is_run: true, is_run: true,
recursive: false,
filter: None,
eval: false,
}; };
new_flags.subcommand = DenoSubcommand::Task(task_flags.clone()); new_flags.subcommand = DenoSubcommand::Task(task_flags.clone());
let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await; let result = tools::task::execute_script(Arc::new(new_flags), task_flags.clone()).await;
@ -350,18 +356,17 @@ fn setup_panic_hook() {
eprintln!("Args: {:?}", env::args().collect::<Vec<_>>()); eprintln!("Args: {:?}", env::args().collect::<Vec<_>>());
eprintln!(); eprintln!();
orig_hook(panic_info); orig_hook(panic_info);
std::process::exit(1); deno_runtime::exit(1);
})); }));
} }
#[allow(clippy::print_stderr)]
fn exit_with_message(message: &str, code: i32) -> ! { fn exit_with_message(message: &str, code: i32) -> ! {
eprintln!( log::error!(
"{}: {}", "{}: {}",
colors::red_bold("error"), colors::red_bold("error"),
message.trim_start_matches("error: ") message.trim_start_matches("error: ")
); );
std::process::exit(code); deno_runtime::exit(code);
} }
fn exit_for_error(error: AnyError) -> ! { fn exit_for_error(error: AnyError) -> ! {
@ -380,13 +385,12 @@ fn exit_for_error(error: AnyError) -> ! {
exit_with_message(&error_string, error_code); exit_with_message(&error_string, error_code);
} }
#[allow(clippy::print_stderr)]
pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) { pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) {
eprintln!( log::error!(
"Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.", "Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.",
feature feature
); );
std::process::exit(70); deno_runtime::exit(70);
} }
pub fn main() { pub fn main() {
@ -419,7 +423,7 @@ pub fn main() {
drop(profiler); drop(profiler);
match result { match result {
Ok(exit_code) => std::process::exit(exit_code), Ok(exit_code) => deno_runtime::exit(exit_code),
Err(err) => exit_for_error(err), Err(err) => exit_for_error(err),
} }
} }
@ -433,12 +437,21 @@ fn resolve_flags_and_init(
if err.kind() == clap::error::ErrorKind::DisplayVersion => if err.kind() == clap::error::ErrorKind::DisplayVersion =>
{ {
// Ignore results to avoid BrokenPipe errors. // Ignore results to avoid BrokenPipe errors.
util::logger::init(None);
let _ = err.print(); let _ = err.print();
std::process::exit(0); deno_runtime::exit(0);
}
Err(err) => {
util::logger::init(None);
exit_for_error(AnyError::from(err))
} }
Err(err) => exit_for_error(AnyError::from(err)),
}; };
if let Some(otel_config) = flags.otel_config() {
deno_telemetry::init(otel_config)?;
}
util::logger::init(flags.log_level);
// TODO(bartlomieju): remove in Deno v2.5 and hard error then. // TODO(bartlomieju): remove in Deno v2.5 and hard error then.
if flags.unstable_config.legacy_flag_enabled { if flags.unstable_config.legacy_flag_enabled {
log::warn!( log::warn!(
@ -467,7 +480,6 @@ fn resolve_flags_and_init(
deno_core::JsRuntime::init_platform( deno_core::JsRuntime::init_platform(
None, /* import assertions enabled */ false, None, /* import assertions enabled */ false,
); );
util::logger::init(flags.log_level);
Ok(flags) Ok(flags)
} }

View file

@ -40,23 +40,21 @@ use std::env::current_exe;
use crate::args::Flags; use crate::args::Flags;
#[allow(clippy::print_stderr)]
pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) { pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) {
eprintln!( log::error!(
"Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.", "Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.",
feature feature
); );
std::process::exit(70); deno_runtime::exit(70);
} }
#[allow(clippy::print_stderr)]
fn exit_with_message(message: &str, code: i32) -> ! { fn exit_with_message(message: &str, code: i32) -> ! {
eprintln!( log::error!(
"{}: {}", "{}: {}",
colors::red_bold("error"), colors::red_bold("error"),
message.trim_start_matches("error: ") message.trim_start_matches("error: ")
); );
std::process::exit(code); deno_runtime::exit(code);
} }
fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T { fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T {
@ -89,13 +87,19 @@ fn main() {
let future = async move { let future = async move {
match standalone { match standalone {
Ok(Some(data)) => { Ok(Some(data)) => {
if let Some(otel_config) = data.metadata.otel_config.clone() {
deno_telemetry::init(otel_config)?;
}
util::logger::init(data.metadata.log_level); util::logger::init(data.metadata.log_level);
load_env_vars(&data.metadata.env_vars_from_env_file); load_env_vars(&data.metadata.env_vars_from_env_file);
let exit_code = standalone::run(data).await?; let exit_code = standalone::run(data).await?;
std::process::exit(exit_code); deno_runtime::exit(exit_code);
} }
Ok(None) => Ok(()), Ok(None) => Ok(()),
Err(err) => Err(err), Err(err) => {
util::logger::init(None);
Err(err)
}
} }
}; };

View file

@ -27,7 +27,7 @@ use crate::node;
use crate::node::CliNodeCodeTranslator; use crate::node::CliNodeCodeTranslator;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use crate::resolver::CliNodeResolver; use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver; use crate::resolver::CliResolver;
use crate::resolver::ModuleCodeStringSource; use crate::resolver::ModuleCodeStringSource;
use crate::resolver::NotSupportedKindInNpmError; use crate::resolver::NotSupportedKindInNpmError;
@ -57,24 +57,25 @@ use deno_core::ModuleSourceCode;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::ModuleType; use deno_core::ModuleType;
use deno_core::RequestedModuleType; use deno_core::RequestedModuleType;
use deno_core::ResolutionKind;
use deno_core::SourceCodeCacheInfo; use deno_core::SourceCodeCacheInfo;
use deno_graph::source::ResolutionMode;
use deno_graph::GraphKind; use deno_graph::GraphKind;
use deno_graph::JsModule; use deno_graph::JsModule;
use deno_graph::JsonModule; use deno_graph::JsonModule;
use deno_graph::Module; use deno_graph::Module;
use deno_graph::ModuleGraph; use deno_graph::ModuleGraph;
use deno_graph::Resolution; use deno_graph::Resolution;
use deno_graph::WasmModule;
use deno_runtime::code_cache; use deno_runtime::code_cache;
use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::create_host_defined_options; use deno_runtime::deno_node::create_host_defined_options;
use deno_runtime::deno_node::NodeRequireLoader; use deno_runtime::deno_node::NodeRequireLoader;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use node_resolver::errors::ClosestPkgJsonError; use node_resolver::errors::ClosestPkgJsonError;
use node_resolver::InNpmPackageChecker; use node_resolver::InNpmPackageChecker;
use node_resolver::NodeResolutionMode; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
pub struct ModuleLoadPreparer { pub struct ModuleLoadPreparer {
options: Arc<CliOptions>, options: Arc<CliOptions>,
@ -215,7 +216,8 @@ struct SharedCliModuleLoaderState {
main_module_graph_container: Arc<MainModuleGraphContainer>, main_module_graph_container: Arc<MainModuleGraphContainer>,
module_load_preparer: Arc<ModuleLoadPreparer>, module_load_preparer: Arc<ModuleLoadPreparer>,
node_code_translator: Arc<CliNodeCodeTranslator>, node_code_translator: Arc<CliNodeCodeTranslator>,
node_resolver: Arc<CliNodeResolver>, node_resolver: Arc<NodeResolver>,
npm_req_resolver: Arc<CliNpmReqResolver>,
npm_resolver: Arc<dyn CliNpmResolver>, npm_resolver: Arc<dyn CliNpmResolver>,
npm_module_loader: NpmModuleLoader, npm_module_loader: NpmModuleLoader,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
@ -238,7 +240,8 @@ impl CliModuleLoaderFactory {
main_module_graph_container: Arc<MainModuleGraphContainer>, main_module_graph_container: Arc<MainModuleGraphContainer>,
module_load_preparer: Arc<ModuleLoadPreparer>, module_load_preparer: Arc<ModuleLoadPreparer>,
node_code_translator: Arc<CliNodeCodeTranslator>, node_code_translator: Arc<CliNodeCodeTranslator>,
node_resolver: Arc<CliNodeResolver>, node_resolver: Arc<NodeResolver>,
npm_req_resolver: Arc<CliNpmReqResolver>,
npm_resolver: Arc<dyn CliNpmResolver>, npm_resolver: Arc<dyn CliNpmResolver>,
npm_module_loader: NpmModuleLoader, npm_module_loader: NpmModuleLoader,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
@ -264,6 +267,7 @@ impl CliModuleLoaderFactory {
module_load_preparer, module_load_preparer,
node_code_translator, node_code_translator,
node_resolver, node_resolver,
npm_req_resolver,
npm_resolver, npm_resolver,
npm_module_loader, npm_module_loader,
parsed_source_cache, parsed_source_cache,
@ -364,7 +368,9 @@ impl<TGraphContainer: ModuleGraphContainer>
requested_module_type: RequestedModuleType, requested_module_type: RequestedModuleType,
) -> Result<ModuleSource, AnyError> { ) -> Result<ModuleSource, AnyError> {
let code_source = self.load_code_source(specifier, maybe_referrer).await?; let code_source = self.load_code_source(specifier, maybe_referrer).await?;
let code = if self.shared.is_inspecting { let code = if self.shared.is_inspecting
|| code_source.media_type == MediaType::Wasm
{
// we need the code with the source map in order for // we need the code with the source map in order for
// it to work with --inspect or --inspect-brk // it to work with --inspect or --inspect-brk
code_source.code code_source.code
@ -374,6 +380,7 @@ impl<TGraphContainer: ModuleGraphContainer>
}; };
let module_type = match code_source.media_type { let module_type = match code_source.media_type {
MediaType::Json => ModuleType::Json, MediaType::Json => ModuleType::Json,
MediaType::Wasm => ModuleType::Wasm,
_ => ModuleType::JavaScript, _ => ModuleType::JavaScript,
}; };
@ -425,7 +432,7 @@ impl<TGraphContainer: ModuleGraphContainer>
if let Some(code_source) = self.load_prepared_module(specifier).await? { if let Some(code_source) = self.load_prepared_module(specifier).await? {
return Ok(code_source); return Ok(code_source);
} }
if self.shared.node_resolver.in_npm_package(specifier) { if self.shared.in_npm_pkg_checker.in_npm_package(specifier) {
return self return self
.shared .shared
.npm_module_loader .npm_module_loader
@ -470,21 +477,6 @@ impl<TGraphContainer: ModuleGraphContainer>
raw_specifier: &str, raw_specifier: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<ModuleSpecifier, AnyError> {
if self.shared.node_resolver.in_npm_package(referrer) {
return Ok(
self
.shared
.node_resolver
.resolve(
raw_specifier,
referrer,
self.shared.cjs_tracker.get_referrer_kind(referrer),
NodeResolutionMode::Execution,
)?
.into_url(),
);
}
let graph = self.graph_container.graph(); let graph = self.graph_container.graph();
let resolution = match graph.get(referrer) { let resolution = match graph.get(referrer) {
Some(Module::Js(module)) => module Some(Module::Js(module)) => module
@ -505,25 +497,27 @@ impl<TGraphContainer: ModuleGraphContainer>
} }
Resolution::None => Cow::Owned(self.shared.resolver.resolve( Resolution::None => Cow::Owned(self.shared.resolver.resolve(
raw_specifier, raw_specifier,
&deno_graph::Range { referrer,
specifier: referrer.clone(), deno_graph::Position::zeroed(),
start: deno_graph::Position::zeroed(), // if we're here, that means it's resolving a dynamic import
end: deno_graph::Position::zeroed(), ResolutionMode::Import,
}, NodeResolutionKind::Execution,
self.shared.cjs_tracker.get_referrer_kind(referrer),
ResolutionMode::Execution,
)?), )?),
}; };
if self.shared.is_repl { if self.shared.is_repl {
if let Ok(reference) = NpmPackageReqReference::from_specifier(&specifier) if let Ok(reference) = NpmPackageReqReference::from_specifier(&specifier)
{ {
return self.shared.node_resolver.resolve_req_reference( return self
&reference, .shared
referrer, .npm_req_resolver
self.shared.cjs_tracker.get_referrer_kind(referrer), .resolve_req_reference(
NodeResolutionMode::Execution, &reference,
); referrer,
ResolutionMode::Import,
NodeResolutionKind::Execution,
)
.map_err(AnyError::from);
} }
} }
@ -538,12 +532,12 @@ impl<TGraphContainer: ModuleGraphContainer>
self self
.shared .shared
.node_resolver .node_resolver
.resolve_package_sub_path_from_deno_module( .resolve_package_subpath_from_deno_module(
&package_folder, &package_folder,
module.nv_reference.sub_path(), module.nv_reference.sub_path(),
Some(referrer), Some(referrer),
self.shared.cjs_tracker.get_referrer_kind(referrer), ResolutionMode::Import,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
) )
.with_context(|| { .with_context(|| {
format!("Could not resolve '{}'.", module.nv_reference) format!("Could not resolve '{}'.", module.nv_reference)
@ -552,6 +546,7 @@ impl<TGraphContainer: ModuleGraphContainer>
Some(Module::Node(module)) => module.specifier.clone(), Some(Module::Node(module)) => module.specifier.clone(),
Some(Module::Js(module)) => module.specifier.clone(), Some(Module::Js(module)) => module.specifier.clone(),
Some(Module::Json(module)) => module.specifier.clone(), Some(Module::Json(module)) => module.specifier.clone(),
Some(Module::Wasm(module)) => module.specifier.clone(),
Some(Module::External(module)) => { Some(Module::External(module)) => {
node::resolve_specifier_into_node_modules( node::resolve_specifier_into_node_modules(
&module.specifier, &module.specifier,
@ -723,6 +718,13 @@ impl<TGraphContainer: ModuleGraphContainer>
media_type: *media_type, media_type: *media_type,
}))) })))
} }
Some(deno_graph::Module::Wasm(WasmModule {
source, specifier, ..
})) => Ok(Some(CodeOrDeferredEmit::Code(ModuleCodeStringSource {
code: ModuleSourceCode::Bytes(source.clone().into()),
found_url: specifier.clone(),
media_type: MediaType::Wasm,
}))),
Some( Some(
deno_graph::Module::External(_) deno_graph::Module::External(_)
| deno_graph::Module::Node(_) | deno_graph::Module::Node(_)
@ -801,7 +803,7 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
&self, &self,
specifier: &str, specifier: &str,
referrer: &str, referrer: &str,
_kind: ResolutionKind, _kind: deno_core::ResolutionKind,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<ModuleSpecifier, AnyError> {
fn ensure_not_jsr_non_jsr_remote_import( fn ensure_not_jsr_non_jsr_remote_import(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -828,7 +830,7 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
name: &str, name: &str,
) -> Option<deno_core::v8::Local<'s, deno_core::v8::Data>> { ) -> Option<deno_core::v8::Local<'s, deno_core::v8::Data>> {
let name = deno_core::ModuleSpecifier::parse(name).ok()?; let name = deno_core::ModuleSpecifier::parse(name).ok()?;
if self.0.shared.node_resolver.in_npm_package(&name) { if self.0.shared.in_npm_pkg_checker.in_npm_package(&name) {
Some(create_host_defined_options(scope)) Some(create_host_defined_options(scope))
} else { } else {
None None
@ -865,7 +867,7 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
_maybe_referrer: Option<String>, _maybe_referrer: Option<String>,
is_dynamic: bool, is_dynamic: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> { ) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
if self.0.shared.node_resolver.in_npm_package(specifier) { if self.0.shared.in_npm_pkg_checker.in_npm_package(specifier) {
return Box::pin(deno_core::futures::future::ready(Ok(()))); return Box::pin(deno_core::futures::future::ready(Ok(())));
} }
@ -1058,7 +1060,10 @@ impl<TGraphContainer: ModuleGraphContainer> NodeRequireLoader
self.npm_resolver.ensure_read_permission(permissions, path) self.npm_resolver.ensure_read_permission(permissions, path)
} }
fn load_text_file_lossy(&self, path: &Path) -> Result<String, AnyError> { fn load_text_file_lossy(
&self,
path: &Path,
) -> Result<Cow<'static, str>, AnyError> {
// todo(dsherret): use the preloaded module from the graph if available? // todo(dsherret): use the preloaded module from the graph if available?
let media_type = MediaType::from_path(path); let media_type = MediaType::from_path(path);
let text = self.fs.read_text_file_lossy_sync(path, None)?; let text = self.fs.read_text_file_lossy_sync(path, None)?;
@ -1073,15 +1078,18 @@ impl<TGraphContainer: ModuleGraphContainer> NodeRequireLoader
.into(), .into(),
); );
} }
self.emitter.emit_parsed_source_sync( self
&specifier, .emitter
media_type, .emit_parsed_source_sync(
// this is probably not super accurate due to require esm, but probably ok. &specifier,
// If we find this causes a lot of churn in the emit cache then we should media_type,
// investigate how we can make this better // this is probably not super accurate due to require esm, but probably ok.
ModuleKind::Cjs, // If we find this causes a lot of churn in the emit cache then we should
&text.into(), // investigate how we can make this better
) ModuleKind::Cjs,
&text.into(),
)
.map(Cow::Owned)
} else { } else {
Ok(text) Ok(text)
} }

View file

@ -7,8 +7,6 @@ use deno_ast::MediaType;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_graph::ParsedSourceStore; use deno_graph::ParsedSourceStore;
use deno_path_util::url_from_file_path;
use deno_path_util::url_to_file_path;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node::DenoFsNodeResolverEnv; use deno_runtime::deno_node::DenoFsNodeResolverEnv;
use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis; use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis;
@ -22,7 +20,6 @@ use crate::cache::CacheDBHash;
use crate::cache::NodeAnalysisCache; use crate::cache::NodeAnalysisCache;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
pub type CliNodeCodeTranslator = pub type CliNodeCodeTranslator =
NodeCodeTranslator<CliCjsCodeAnalyzer, DenoFsNodeResolverEnv>; NodeCodeTranslator<CliCjsCodeAnalyzer, DenoFsNodeResolverEnv>;
@ -37,13 +34,9 @@ pub fn resolve_specifier_into_node_modules(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
fs: &dyn deno_fs::FileSystem, fs: &dyn deno_fs::FileSystem,
) -> ModuleSpecifier { ) -> ModuleSpecifier {
url_to_file_path(specifier) node_resolver::resolve_specifier_into_node_modules(specifier, &|path| {
.ok() fs.realpath_sync(path).map_err(|err| err.into_io_error())
// this path might not exist at the time the graph is being created })
// because the node_modules folder might not yet exist
.and_then(|path| canonicalize_path_maybe_not_exists_with_fs(&path, fs).ok())
.and_then(|path| url_from_file_path(&path).ok())
.unwrap_or_else(|| specifier.clone())
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -167,7 +160,7 @@ impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
if let Ok(source_from_file) = if let Ok(source_from_file) =
self.fs.read_text_file_lossy_async(path, None).await self.fs.read_text_file_lossy_async(path, None).await
{ {
Cow::Owned(source_from_file) source_from_file
} else { } else {
return Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports { return Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports {
exports: vec![], exports: vec![],

View file

@ -2,19 +2,17 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::url::Url;
use deno_resolver::npm::ByonmNpmResolver; use deno_resolver::npm::ByonmNpmResolver;
use deno_resolver::npm::ByonmNpmResolverCreateOptions; use deno_resolver::npm::ByonmNpmResolverCreateOptions;
use deno_resolver::npm::CliNpmReqResolver;
use deno_runtime::deno_node::DenoFsNodeResolverEnv; use deno_runtime::deno_node::DenoFsNodeResolverEnv;
use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodePermissions;
use deno_runtime::ops::process::NpmProcessStateProvider; use deno_runtime::ops::process::NpmProcessStateProvider;
use deno_semver::package::PackageReq; use node_resolver::NpmPackageFolderResolver;
use node_resolver::NpmResolver;
use crate::args::NpmProcessState; use crate::args::NpmProcessState;
use crate::args::NpmProcessStateKind; use crate::args::NpmProcessStateKind;
@ -22,7 +20,6 @@ use crate::resolver::CliDenoResolverFs;
use super::CliNpmResolver; use super::CliNpmResolver;
use super::InnerCliNpmResolverRef; use super::InnerCliNpmResolverRef;
use super::ResolvePkgFolderFromDenoReqError;
pub type CliByonmNpmResolverCreateOptions = pub type CliByonmNpmResolverCreateOptions =
ByonmNpmResolverCreateOptions<CliDenoResolverFs, DenoFsNodeResolverEnv>; ByonmNpmResolverCreateOptions<CliDenoResolverFs, DenoFsNodeResolverEnv>;
@ -47,7 +44,13 @@ impl NpmProcessStateProvider for CliByonmWrapper {
} }
impl CliNpmResolver for CliByonmNpmResolver { impl CliNpmResolver for CliByonmNpmResolver {
fn into_npm_resolver(self: Arc<Self>) -> Arc<dyn NpmResolver> { fn into_npm_pkg_folder_resolver(
self: Arc<Self>,
) -> Arc<dyn NpmPackageFolderResolver> {
self
}
fn into_npm_req_resolver(self: Arc<Self>) -> Arc<dyn CliNpmReqResolver> {
self self
} }
@ -57,6 +60,10 @@ impl CliNpmResolver for CliByonmNpmResolver {
Arc::new(CliByonmWrapper(self)) Arc::new(CliByonmWrapper(self))
} }
fn into_maybe_byonm(self: Arc<Self>) -> Option<Arc<CliByonmNpmResolver>> {
Some(self)
}
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> { fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> {
Arc::new(self.clone()) Arc::new(self.clone())
} }
@ -69,17 +76,6 @@ impl CliNpmResolver for CliByonmNpmResolver {
self.root_node_modules_dir() self.root_node_modules_dir()
} }
fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
referrer: &Url,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
ByonmNpmResolver::resolve_pkg_folder_from_deno_module_req(
self, req, referrer,
)
.map_err(ResolvePkgFolderFromDenoReqError::Byonm)
}
fn ensure_read_permission<'a>( fn ensure_read_permission<'a>(
&self, &self,
permissions: &mut dyn NodePermissions, permissions: &mut dyn NodePermissions,

View file

@ -22,6 +22,7 @@ use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
use deno_npm::NpmPackageId; use deno_npm::NpmPackageId;
use deno_npm::NpmResolutionPackage; use deno_npm::NpmResolutionPackage;
use deno_npm::NpmSystemInfo; use deno_npm::NpmSystemInfo;
use deno_resolver::npm::CliNpmReqResolver;
use deno_runtime::colors; use deno_runtime::colors;
use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodePermissions;
@ -31,7 +32,7 @@ use deno_semver::package::PackageReq;
use node_resolver::errors::PackageFolderResolveError; use node_resolver::errors::PackageFolderResolveError;
use node_resolver::errors::PackageFolderResolveIoError; use node_resolver::errors::PackageFolderResolveIoError;
use node_resolver::InNpmPackageChecker; use node_resolver::InNpmPackageChecker;
use node_resolver::NpmResolver; use node_resolver::NpmPackageFolderResolver;
use resolution::AddPkgReqsResult; use resolution::AddPkgReqsResult;
use crate::args::CliLockfile; use crate::args::CliLockfile;
@ -499,7 +500,7 @@ impl ManagedCliNpmResolver {
self.resolve_pkg_folder_from_pkg_id(&pkg_id) self.resolve_pkg_folder_from_pkg_id(&pkg_id)
} }
fn resolve_pkg_id_from_pkg_req( pub fn resolve_pkg_id_from_pkg_req(
&self, &self,
req: &PackageReq, req: &PackageReq,
) -> Result<NpmPackageId, PackageReqNotFoundError> { ) -> Result<NpmPackageId, PackageReqNotFoundError> {
@ -605,7 +606,7 @@ fn npm_process_state(
.unwrap() .unwrap()
} }
impl NpmResolver for ManagedCliNpmResolver { impl NpmPackageFolderResolver for ManagedCliNpmResolver {
fn resolve_package_folder_from_package( fn resolve_package_folder_from_package(
&self, &self,
name: &str, name: &str,
@ -635,8 +636,29 @@ impl NpmProcessStateProvider for ManagedCliNpmResolver {
} }
} }
impl CliNpmReqResolver for ManagedCliNpmResolver {
fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
_referrer: &ModuleSpecifier,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
let pkg_id = self
.resolve_pkg_id_from_pkg_req(req)
.map_err(|err| ResolvePkgFolderFromDenoReqError::Managed(err.into()))?;
self
.resolve_pkg_folder_from_pkg_id(&pkg_id)
.map_err(ResolvePkgFolderFromDenoReqError::Managed)
}
}
impl CliNpmResolver for ManagedCliNpmResolver { impl CliNpmResolver for ManagedCliNpmResolver {
fn into_npm_resolver(self: Arc<Self>) -> Arc<dyn NpmResolver> { fn into_npm_pkg_folder_resolver(
self: Arc<Self>,
) -> Arc<dyn NpmPackageFolderResolver> {
self
}
fn into_npm_req_resolver(self: Arc<Self>) -> Arc<dyn CliNpmReqResolver> {
self self
} }
@ -687,19 +709,6 @@ impl CliNpmResolver for ManagedCliNpmResolver {
self.fs_resolver.node_modules_path() self.fs_resolver.node_modules_path()
} }
fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
_referrer: &ModuleSpecifier,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError> {
let pkg_id = self
.resolve_pkg_id_from_pkg_req(req)
.map_err(|err| ResolvePkgFolderFromDenoReqError::Managed(err.into()))?;
self
.resolve_pkg_folder_from_pkg_id(&pkg_id)
.map_err(ResolvePkgFolderFromDenoReqError::Managed)
}
fn ensure_read_permission<'a>( fn ensure_read_permission<'a>(
&self, &self,
permissions: &mut dyn NodePermissions, permissions: &mut dyn NodePermissions,

View file

@ -9,6 +9,7 @@ use deno_npm::resolution::NpmResolutionSnapshot;
use deno_runtime::deno_io::FromRawIoHandle; use deno_runtime::deno_io::FromRawIoHandle;
use deno_semver::package::PackageNv; use deno_semver::package::PackageNv;
use deno_semver::Version; use deno_semver::Version;
use deno_task_shell::KillSignal;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
@ -155,6 +156,29 @@ impl<'a> LifecycleScripts<'a> {
packages: &[NpmResolutionPackage], packages: &[NpmResolutionPackage],
root_node_modules_dir_path: &Path, root_node_modules_dir_path: &Path,
progress_bar: &ProgressBar, progress_bar: &ProgressBar,
) -> Result<(), AnyError> {
let kill_signal = KillSignal::default();
let _drop_signal = kill_signal.clone().drop_guard();
// we don't run with signals forwarded because once signals
// are setup then they're process wide.
self
.finish_with_cancellation(
snapshot,
packages,
root_node_modules_dir_path,
progress_bar,
kill_signal,
)
.await
}
async fn finish_with_cancellation(
self,
snapshot: &NpmResolutionSnapshot,
packages: &[NpmResolutionPackage],
root_node_modules_dir_path: &Path,
progress_bar: &ProgressBar,
kill_signal: KillSignal,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
self.warn_not_run_scripts()?; self.warn_not_run_scripts()?;
let get_package_path = let get_package_path =
@ -182,6 +206,12 @@ impl<'a> LifecycleScripts<'a> {
); );
let mut env_vars = crate::task_runner::real_env_vars(); let mut env_vars = crate::task_runner::real_env_vars();
// so the subprocess can detect that it is running as part of a lifecycle script,
// and avoid trying to set up node_modules again
env_vars.insert(
LIFECYCLE_SCRIPTS_RUNNING_ENV_VAR.to_string(),
"1".to_string(),
);
// we want to pass the current state of npm resolution down to the deno subprocess // we want to pass the current state of npm resolution down to the deno subprocess
// (that may be running as part of the script). we do this with an inherited temp file // (that may be running as part of the script). we do this with an inherited temp file
// //
@ -240,6 +270,7 @@ impl<'a> LifecycleScripts<'a> {
stderr: TaskStdio::piped(), stderr: TaskStdio::piped(),
stdout: TaskStdio::piped(), stdout: TaskStdio::piped(),
}), }),
kill_signal: kill_signal.clone(),
}, },
) )
.await?; .await?;
@ -303,6 +334,13 @@ impl<'a> LifecycleScripts<'a> {
} }
} }
const LIFECYCLE_SCRIPTS_RUNNING_ENV_VAR: &str =
"DENO_INTERNAL_IS_LIFECYCLE_SCRIPT";
pub fn is_running_lifecycle_script() -> bool {
std::env::var(LIFECYCLE_SCRIPTS_RUNNING_ENV_VAR).is_ok()
}
// take in all (non copy) packages from snapshot, // take in all (non copy) packages from snapshot,
// and resolve the set of available binaries to create // and resolve the set of available binaries to create
// custom commands available to the task runner // custom commands available to the task runner

View file

@ -298,6 +298,12 @@ async fn sync_resolution_with_fs(
return Ok(()); // don't create the directory return Ok(()); // don't create the directory
} }
// don't set up node_modules (and more importantly try to acquire the file lock)
// if we're running as part of a lifecycle script
if super::common::lifecycle_scripts::is_running_lifecycle_script() {
return Ok(());
}
let deno_local_registry_dir = root_node_modules_dir_path.join(".deno"); let deno_local_registry_dir = root_node_modules_dir_path.join(".deno");
let deno_node_modules_dir = deno_local_registry_dir.join("node_modules"); let deno_node_modules_dir = deno_local_registry_dir.join("node_modules");
fs::create_dir_all(&deno_node_modules_dir).with_context(|| { fs::create_dir_all(&deno_node_modules_dir).with_context(|| {

View file

@ -6,19 +6,18 @@ mod managed;
use std::borrow::Cow; use std::borrow::Cow;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use common::maybe_auth_header_for_npm_registry; use common::maybe_auth_header_for_npm_registry;
use dashmap::DashMap; use dashmap::DashMap;
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::registry::NpmPackageInfo; use deno_npm::registry::NpmPackageInfo;
use deno_resolver::npm::ByonmInNpmPackageChecker; use deno_resolver::npm::ByonmInNpmPackageChecker;
use deno_resolver::npm::ByonmNpmResolver; use deno_resolver::npm::ByonmNpmResolver;
use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError; use deno_resolver::npm::CliNpmReqResolver;
use deno_resolver::npm::ResolvePkgFolderFromDenoReqError;
use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodePermissions;
use deno_runtime::ops::process::NpmProcessStateProvider; use deno_runtime::ops::process::NpmProcessStateProvider;
use deno_semver::package::PackageNv; use deno_semver::package::PackageNv;
@ -26,8 +25,7 @@ use deno_semver::package::PackageReq;
use managed::cache::registry_info::get_package_url; use managed::cache::registry_info::get_package_url;
use managed::create_managed_in_npm_pkg_checker; use managed::create_managed_in_npm_pkg_checker;
use node_resolver::InNpmPackageChecker; use node_resolver::InNpmPackageChecker;
use node_resolver::NpmResolver; use node_resolver::NpmPackageFolderResolver;
use thiserror::Error;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
@ -38,14 +36,6 @@ pub use self::managed::CliManagedNpmResolverCreateOptions;
pub use self::managed::CliNpmResolverManagedSnapshotOption; pub use self::managed::CliNpmResolverManagedSnapshotOption;
pub use self::managed::ManagedCliNpmResolver; pub use self::managed::ManagedCliNpmResolver;
#[derive(Debug, Error)]
pub enum ResolvePkgFolderFromDenoReqError {
#[error(transparent)]
Managed(deno_core::error::AnyError),
#[error(transparent)]
Byonm(#[from] ByonmResolvePkgFolderFromDenoReqError),
}
pub enum CliNpmResolverCreateOptions { pub enum CliNpmResolverCreateOptions {
Managed(CliManagedNpmResolverCreateOptions), Managed(CliManagedNpmResolverCreateOptions),
Byonm(CliByonmNpmResolverCreateOptions), Byonm(CliByonmNpmResolverCreateOptions),
@ -95,11 +85,17 @@ pub enum InnerCliNpmResolverRef<'a> {
Byonm(&'a CliByonmNpmResolver), Byonm(&'a CliByonmNpmResolver),
} }
pub trait CliNpmResolver: NpmResolver { pub trait CliNpmResolver: NpmPackageFolderResolver + CliNpmReqResolver {
fn into_npm_resolver(self: Arc<Self>) -> Arc<dyn NpmResolver>; fn into_npm_pkg_folder_resolver(
self: Arc<Self>,
) -> Arc<dyn NpmPackageFolderResolver>;
fn into_npm_req_resolver(self: Arc<Self>) -> Arc<dyn CliNpmReqResolver>;
fn into_process_state_provider( fn into_process_state_provider(
self: Arc<Self>, self: Arc<Self>,
) -> Arc<dyn NpmProcessStateProvider>; ) -> Arc<dyn NpmProcessStateProvider>;
fn into_maybe_byonm(self: Arc<Self>) -> Option<Arc<CliByonmNpmResolver>> {
None
}
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver>; fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver>;
@ -121,12 +117,6 @@ pub trait CliNpmResolver: NpmResolver {
fn root_node_modules_path(&self) -> Option<&Path>; fn root_node_modules_path(&self) -> Option<&Path>;
fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
referrer: &ModuleSpecifier,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError>;
fn ensure_read_permission<'a>( fn ensure_read_permission<'a>(
&self, &self,
permissions: &mut dyn NodePermissions, permissions: &mut dyn NodePermissions,

View file

@ -51,7 +51,7 @@ fn op_bench_get_origin(state: &mut OpState) -> String {
#[derive(Clone)] #[derive(Clone)]
struct PermissionsHolder(Uuid, PermissionsContainer); struct PermissionsHolder(Uuid, PermissionsContainer);
#[op2] #[op2(stack_trace)]
#[serde] #[serde]
pub fn op_pledge_test_permissions( pub fn op_pledge_test_permissions(
state: &mut OpState, state: &mut OpState,

View file

@ -46,7 +46,7 @@ deno_core::extension!(deno_test,
#[derive(Clone)] #[derive(Clone)]
struct PermissionsHolder(Uuid, PermissionsContainer); struct PermissionsHolder(Uuid, PermissionsContainer);
#[op2] #[op2(stack_trace)]
#[serde] #[serde]
pub fn op_pledge_test_permissions( pub fn op_pledge_test_permissions(
state: &mut OpState, state: &mut OpState,

View file

@ -4,45 +4,28 @@ use async_trait::async_trait;
use dashmap::DashMap; use dashmap::DashMap;
use dashmap::DashSet; use dashmap::DashSet;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_config::workspace::MappedResolution;
use deno_config::workspace::MappedResolutionDiagnostic; use deno_config::workspace::MappedResolutionDiagnostic;
use deno_config::workspace::MappedResolutionError; use deno_config::workspace::MappedResolutionError;
use deno_config::workspace::WorkspaceResolver;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::ModuleSourceCode; use deno_core::ModuleSourceCode;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::source::ResolutionMode;
use deno_graph::source::ResolveError; use deno_graph::source::ResolveError;
use deno_graph::source::UnknownBuiltInNodeModuleError; use deno_graph::source::UnknownBuiltInNodeModuleError;
use deno_graph::NpmLoadError; use deno_graph::NpmLoadError;
use deno_graph::NpmResolvePkgReqsResult; use deno_graph::NpmResolvePkgReqsResult;
use deno_npm::resolution::NpmResolutionError; use deno_npm::resolution::NpmResolutionError;
use deno_package_json::PackageJsonDepValue;
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
use deno_resolver::sloppy_imports::SloppyImportsResolver; use deno_resolver::sloppy_imports::SloppyImportsResolver;
use deno_runtime::colors; use deno_runtime::colors;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::is_builtin_node_module; use deno_runtime::deno_node::is_builtin_node_module;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::DenoFsNodeResolverEnv;
use deno_runtime::deno_node::PackageJsonResolver;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use node_resolver::errors::ClosestPkgJsonError; use node_resolver::NodeResolutionKind;
use node_resolver::errors::NodeResolveError; use node_resolver::ResolutionMode;
use node_resolver::errors::NodeResolveErrorKind;
use node_resolver::errors::PackageFolderResolveErrorKind;
use node_resolver::errors::PackageFolderResolveIoError;
use node_resolver::errors::PackageNotFoundError;
use node_resolver::errors::PackageResolveErrorKind;
use node_resolver::errors::PackageSubpathResolveError;
use node_resolver::InNpmPackageChecker;
use node_resolver::NodeModuleKind;
use node_resolver::NodeResolution;
use node_resolver::NodeResolutionMode;
use std::borrow::Cow; use std::borrow::Cow;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -54,7 +37,20 @@ use crate::node::CliNodeCodeTranslator;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::npm::InnerCliNpmResolverRef; use crate::npm::InnerCliNpmResolverRef;
use crate::util::sync::AtomicFlag; use crate::util::sync::AtomicFlag;
use crate::util::text_encoding::from_utf8_lossy_owned; use crate::util::text_encoding::from_utf8_lossy_cow;
pub type CjsTracker = deno_resolver::cjs::CjsTracker<DenoFsNodeResolverEnv>;
pub type IsCjsResolver =
deno_resolver::cjs::IsCjsResolver<DenoFsNodeResolverEnv>;
pub type CliSloppyImportsResolver =
SloppyImportsResolver<SloppyImportsCachedFs>;
pub type CliDenoResolver = deno_resolver::DenoResolver<
CliDenoResolverFs,
DenoFsNodeResolverEnv,
SloppyImportsCachedFs,
>;
pub type CliNpmReqResolver =
deno_resolver::npm::NpmReqResolver<CliDenoResolverFs, DenoFsNodeResolverEnv>;
pub struct ModuleCodeStringSource { pub struct ModuleCodeStringSource {
pub code: ModuleSourceCode, pub code: ModuleSourceCode,
@ -66,7 +62,10 @@ pub struct ModuleCodeStringSource {
pub struct CliDenoResolverFs(pub Arc<dyn FileSystem>); pub struct CliDenoResolverFs(pub Arc<dyn FileSystem>);
impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs { impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs {
fn read_to_string_lossy(&self, path: &Path) -> std::io::Result<String> { fn read_to_string_lossy(
&self,
path: &Path,
) -> std::io::Result<Cow<'static, str>> {
self self
.0 .0
.read_text_file_lossy_sync(path, None) .read_text_file_lossy_sync(path, None)
@ -77,6 +76,10 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs {
self.0.realpath_sync(path).map_err(|e| e.into_io_error()) self.0.realpath_sync(path).map_err(|e| e.into_io_error())
} }
fn exists_sync(&self, path: &Path) -> bool {
self.0.exists_sync(path)
}
fn is_dir_sync(&self, path: &Path) -> bool { fn is_dir_sync(&self, path: &Path) -> bool {
self.0.is_dir_sync(path) self.0.is_dir_sync(path)
} }
@ -102,211 +105,6 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs {
} }
} }
#[derive(Debug)]
pub struct CliNodeResolver {
fs: Arc<dyn deno_fs::FileSystem>,
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
}
impl CliNodeResolver {
pub fn new(
fs: Arc<dyn deno_fs::FileSystem>,
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
) -> Self {
Self {
fs,
in_npm_pkg_checker,
node_resolver,
npm_resolver,
}
}
pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool {
self.in_npm_pkg_checker.in_npm_package(specifier)
}
pub fn resolve_if_for_npm_pkg(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<Option<NodeResolution>, AnyError> {
let resolution_result =
self.resolve(specifier, referrer, referrer_kind, mode);
match resolution_result {
Ok(res) => Ok(Some(res)),
Err(err) => {
let err = err.into_kind();
match err {
NodeResolveErrorKind::RelativeJoin(_)
| NodeResolveErrorKind::PackageImportsResolve(_)
| NodeResolveErrorKind::UnsupportedEsmUrlScheme(_)
| NodeResolveErrorKind::DataUrlReferrer(_)
| NodeResolveErrorKind::TypesNotFound(_)
| NodeResolveErrorKind::FinalizeResolution(_) => Err(err.into()),
NodeResolveErrorKind::PackageResolve(err) => {
let err = err.into_kind();
match err {
PackageResolveErrorKind::ClosestPkgJson(_)
| PackageResolveErrorKind::InvalidModuleSpecifier(_)
| PackageResolveErrorKind::ExportsResolve(_)
| PackageResolveErrorKind::SubpathResolve(_) => Err(err.into()),
PackageResolveErrorKind::PackageFolderResolve(err) => {
match err.as_kind() {
PackageFolderResolveErrorKind::Io(
PackageFolderResolveIoError { package_name, .. },
)
| PackageFolderResolveErrorKind::PackageNotFound(
PackageNotFoundError { package_name, .. },
) => {
if self.in_npm_package(referrer) {
return Err(err.into());
}
if let Some(byonm_npm_resolver) =
self.npm_resolver.as_byonm()
{
if byonm_npm_resolver
.find_ancestor_package_json_with_dep(
package_name,
referrer,
)
.is_some()
{
return Err(anyhow!(
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 `deno install`?"
),
specifier
));
}
}
Ok(None)
}
PackageFolderResolveErrorKind::ReferrerNotFound(_) => {
if self.in_npm_package(referrer) {
return Err(err.into());
}
Ok(None)
}
}
}
}
}
}
}
}
}
pub fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<NodeResolution, NodeResolveError> {
self
.node_resolver
.resolve(specifier, referrer, referrer_kind, mode)
}
pub fn resolve_req_reference(
&self,
req_ref: &NpmPackageReqReference,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<ModuleSpecifier, AnyError> {
self.resolve_req_with_sub_path(
req_ref.req(),
req_ref.sub_path(),
referrer,
referrer_kind,
mode,
)
}
pub fn resolve_req_with_sub_path(
&self,
req: &PackageReq,
sub_path: Option<&str>,
referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<ModuleSpecifier, AnyError> {
let package_folder = self
.npm_resolver
.resolve_pkg_folder_from_deno_module_req(req, referrer)?;
let resolution_result = self.resolve_package_sub_path_from_deno_module(
&package_folder,
sub_path,
Some(referrer),
referrer_kind,
mode,
);
match resolution_result {
Ok(url) => Ok(url),
Err(err) => {
if self.npm_resolver.as_byonm().is_some() {
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 `deno install`?",
package_json_path.display(),
));
}
}
Err(err.into())
}
}
}
pub fn resolve_package_sub_path_from_deno_module(
&self,
package_folder: &Path,
sub_path: Option<&str>,
maybe_referrer: Option<&ModuleSpecifier>,
referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
) -> Result<ModuleSpecifier, PackageSubpathResolveError> {
self.node_resolver.resolve_package_subpath_from_deno_module(
package_folder,
sub_path,
maybe_referrer,
referrer_kind,
mode,
)
}
pub fn handle_if_in_node_modules(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<ModuleSpecifier>, AnyError> {
// skip canonicalizing if we definitely know it's unnecessary
if specifier.scheme() == "file"
&& specifier.path().contains("/node_modules/")
{
// Specifiers in the node_modules directory are canonicalized
// so canoncalize then check if it's in the node_modules directory.
// If so, check if we need to store this specifier as being a CJS
// resolution.
let specifier = crate::node::resolve_specifier_into_node_modules(
specifier,
self.fs.as_ref(),
);
return Ok(Some(specifier));
}
Ok(None)
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("{media_type} files are not supported in npm packages: {specifier}")] #[error("{media_type} files are not supported in npm packages: {specifier}")]
pub struct NotSupportedKindInNpmError { pub struct NotSupportedKindInNpmError {
@ -387,18 +185,21 @@ impl NpmModuleLoader {
let code = if self.cjs_tracker.is_maybe_cjs(specifier, media_type)? { let code = if self.cjs_tracker.is_maybe_cjs(specifier, media_type)? {
// translate cjs to esm if it's cjs and inject node globals // translate cjs to esm if it's cjs and inject node globals
let code = from_utf8_lossy_owned(code); let code = from_utf8_lossy_cow(code);
ModuleSourceCode::String( ModuleSourceCode::String(
self self
.node_code_translator .node_code_translator
.translate_cjs_to_esm(specifier, Some(Cow::Owned(code))) .translate_cjs_to_esm(specifier, Some(code))
.await? .await?
.into_owned() .into_owned()
.into(), .into(),
) )
} else { } else {
// esm and json code is untouched // esm and json code is untouched
ModuleSourceCode::Bytes(code.into_boxed_slice().into()) ModuleSourceCode::Bytes(match code {
Cow::Owned(bytes) => bytes.into_boxed_slice().into(),
Cow::Borrowed(bytes) => bytes.into(),
})
}; };
Ok(ModuleCodeStringSource { Ok(ModuleCodeStringSource {
@ -409,305 +210,36 @@ impl NpmModuleLoader {
} }
} }
/// Keeps track of what module specifiers were resolved as CJS. pub struct CliResolverOptions {
/// pub deno_resolver: Arc<CliDenoResolver>,
/// Modules that are `.js` or `.ts` are only known to be CJS or pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
/// ESM after they're loaded based on their contents. So these files pub bare_node_builtins_enabled: bool,
/// will be "maybe CJS" until they're loaded.
#[derive(Debug)]
pub struct CjsTracker {
is_cjs_resolver: IsCjsResolver,
known: DashMap<ModuleSpecifier, NodeModuleKind>,
} }
impl CjsTracker {
pub fn new(
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver>,
options: IsCjsResolverOptions,
) -> Self {
Self {
is_cjs_resolver: IsCjsResolver::new(
in_npm_pkg_checker,
pkg_json_resolver,
options,
),
known: Default::default(),
}
}
/// Checks whether the file might be treated as CJS, but it's not for sure
/// yet because the source hasn't been loaded to see whether it contains
/// imports or exports.
pub fn is_maybe_cjs(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
) -> Result<bool, ClosestPkgJsonError> {
self.treat_as_cjs_with_is_script(specifier, media_type, None)
}
/// Gets whether the file is CJS. If true, this is for sure
/// cjs because `is_script` is provided.
///
/// `is_script` should be `true` when the contents of the file at the
/// provided specifier are known to be a script and not an ES module.
pub fn is_cjs_with_known_is_script(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
is_script: bool,
) -> Result<bool, ClosestPkgJsonError> {
self.treat_as_cjs_with_is_script(specifier, media_type, Some(is_script))
}
fn treat_as_cjs_with_is_script(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
is_script: Option<bool>,
) -> Result<bool, ClosestPkgJsonError> {
let kind = match self
.get_known_kind_with_is_script(specifier, media_type, is_script)
{
Some(kind) => kind,
None => self.is_cjs_resolver.check_based_on_pkg_json(specifier)?,
};
Ok(kind == NodeModuleKind::Cjs)
}
pub fn get_known_kind(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
) -> Option<NodeModuleKind> {
self.get_known_kind_with_is_script(specifier, media_type, None)
}
pub fn get_referrer_kind(
&self,
specifier: &ModuleSpecifier,
) -> NodeModuleKind {
if specifier.scheme() != "file" {
return NodeModuleKind::Esm;
}
self
.get_known_kind(specifier, MediaType::from_specifier(specifier))
.unwrap_or(NodeModuleKind::Esm)
}
fn get_known_kind_with_is_script(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
is_script: Option<bool>,
) -> Option<NodeModuleKind> {
self.is_cjs_resolver.get_known_kind_with_is_script(
specifier,
media_type,
is_script,
&self.known,
)
}
}
#[derive(Debug)]
pub struct IsCjsResolverOptions {
pub detect_cjs: bool,
pub is_node_main: bool,
}
#[derive(Debug)]
pub struct IsCjsResolver {
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver>,
options: IsCjsResolverOptions,
}
impl IsCjsResolver {
pub fn new(
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pkg_json_resolver: Arc<PackageJsonResolver>,
options: IsCjsResolverOptions,
) -> Self {
Self {
in_npm_pkg_checker,
pkg_json_resolver,
options,
}
}
pub fn get_lsp_referrer_kind(
&self,
specifier: &ModuleSpecifier,
is_script: Option<bool>,
) -> NodeModuleKind {
if specifier.scheme() != "file" {
return NodeModuleKind::Esm;
}
match MediaType::from_specifier(specifier) {
MediaType::Mts | MediaType::Mjs | MediaType::Dmts => NodeModuleKind::Esm,
MediaType::Cjs | MediaType::Cts | MediaType::Dcts => NodeModuleKind::Cjs,
MediaType::Dts => {
// dts files are always determined based on the package.json because
// they contain imports/exports even when considered CJS
self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm)
}
MediaType::Wasm |
MediaType::Json => NodeModuleKind::Esm,
MediaType::JavaScript
| MediaType::Jsx
| MediaType::TypeScript
| MediaType::Tsx
// treat these as unknown
| MediaType::Css
| MediaType::SourceMap
| MediaType::Unknown => {
match is_script {
Some(true) => self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm),
Some(false) | None => NodeModuleKind::Esm,
}
}
}
}
fn get_known_kind_with_is_script(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
is_script: Option<bool>,
known_cache: &DashMap<ModuleSpecifier, NodeModuleKind>,
) -> Option<NodeModuleKind> {
if specifier.scheme() != "file" {
return Some(NodeModuleKind::Esm);
}
match media_type {
MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(NodeModuleKind::Esm),
MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(NodeModuleKind::Cjs),
MediaType::Dts => {
// dts files are always determined based on the package.json because
// they contain imports/exports even when considered CJS
if let Some(value) = known_cache.get(specifier).map(|v| *v) {
Some(value)
} else {
let value = self.check_based_on_pkg_json(specifier).ok();
if let Some(value) = value {
known_cache.insert(specifier.clone(), value);
}
Some(value.unwrap_or(NodeModuleKind::Esm))
}
}
MediaType::Wasm |
MediaType::Json => Some(NodeModuleKind::Esm),
MediaType::JavaScript
| MediaType::Jsx
| MediaType::TypeScript
| MediaType::Tsx
// treat these as unknown
| MediaType::Css
| MediaType::SourceMap
| MediaType::Unknown => {
if let Some(value) = known_cache.get(specifier).map(|v| *v) {
if value == NodeModuleKind::Cjs && is_script == Some(false) {
// we now know this is actually esm
known_cache.insert(specifier.clone(), NodeModuleKind::Esm);
Some(NodeModuleKind::Esm)
} else {
Some(value)
}
} else if is_script == Some(false) {
// we know this is esm
known_cache.insert(specifier.clone(), NodeModuleKind::Esm);
Some(NodeModuleKind::Esm)
} else {
None
}
}
}
}
fn check_based_on_pkg_json(
&self,
specifier: &ModuleSpecifier,
) -> Result<NodeModuleKind, ClosestPkgJsonError> {
if self.in_npm_pkg_checker.in_npm_package(specifier) {
if let Some(pkg_json) =
self.pkg_json_resolver.get_closest_package_json(specifier)?
{
let is_file_location_cjs = pkg_json.typ != "module";
Ok(if is_file_location_cjs {
NodeModuleKind::Cjs
} else {
NodeModuleKind::Esm
})
} else {
Ok(NodeModuleKind::Cjs)
}
} else if self.options.detect_cjs || self.options.is_node_main {
if let Some(pkg_json) =
self.pkg_json_resolver.get_closest_package_json(specifier)?
{
let is_cjs_type = pkg_json.typ == "commonjs"
|| self.options.is_node_main && pkg_json.typ == "none";
Ok(if is_cjs_type {
NodeModuleKind::Cjs
} else {
NodeModuleKind::Esm
})
} else if self.options.is_node_main {
Ok(NodeModuleKind::Cjs)
} else {
Ok(NodeModuleKind::Esm)
}
} else {
Ok(NodeModuleKind::Esm)
}
}
}
pub type CliSloppyImportsResolver =
SloppyImportsResolver<SloppyImportsCachedFs>;
/// A resolver that takes care of resolution, taking into account loaded /// A resolver that takes care of resolution, taking into account loaded
/// import map, JSX settings. /// import map, JSX settings.
#[derive(Debug)] #[derive(Debug)]
pub struct CliResolver { pub struct CliResolver {
node_resolver: Option<Arc<CliNodeResolver>>, deno_resolver: Arc<CliDenoResolver>,
npm_resolver: Option<Arc<dyn CliNpmResolver>>, npm_resolver: Option<Arc<dyn CliNpmResolver>>,
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
workspace_resolver: Arc<WorkspaceResolver>,
maybe_vendor_specifier: Option<ModuleSpecifier>,
found_package_json_dep_flag: AtomicFlag, found_package_json_dep_flag: AtomicFlag,
bare_node_builtins_enabled: bool, bare_node_builtins_enabled: bool,
warned_pkgs: DashSet<PackageReq>, warned_pkgs: DashSet<PackageReq>,
} }
pub struct CliResolverOptions<'a> {
pub node_resolver: Option<Arc<CliNodeResolver>>,
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
pub workspace_resolver: Arc<WorkspaceResolver>,
pub bare_node_builtins_enabled: bool,
pub maybe_vendor_dir: Option<&'a PathBuf>,
}
impl CliResolver { impl CliResolver {
pub fn new(options: CliResolverOptions) -> Self { pub fn new(options: CliResolverOptions) -> Self {
Self { Self {
node_resolver: options.node_resolver, deno_resolver: options.deno_resolver,
npm_resolver: options.npm_resolver, npm_resolver: options.npm_resolver,
sloppy_imports_resolver: options.sloppy_imports_resolver,
workspace_resolver: options.workspace_resolver,
maybe_vendor_specifier: options
.maybe_vendor_dir
.and_then(|v| ModuleSpecifier::from_directory_path(v).ok()),
found_package_json_dep_flag: Default::default(), found_package_json_dep_flag: Default::default(),
bare_node_builtins_enabled: options.bare_node_builtins_enabled, bare_node_builtins_enabled: options.bare_node_builtins_enabled,
warned_pkgs: Default::default(), warned_pkgs: Default::default(),
} }
} }
// todo(dsherret): move this off CliResolver as CliResolver is acting
// like a factory by doing this (it's beyond its responsibility)
pub fn create_graph_npm_resolver(&self) -> WorkerCliNpmGraphResolver { pub fn create_graph_npm_resolver(&self) -> WorkerCliNpmGraphResolver {
WorkerCliNpmGraphResolver { WorkerCliNpmGraphResolver {
npm_resolver: self.npm_resolver.as_ref(), npm_resolver: self.npm_resolver.as_ref(),
@ -719,234 +251,51 @@ impl CliResolver {
pub fn resolve( pub fn resolve(
&self, &self,
raw_specifier: &str, raw_specifier: &str,
referrer_range: &deno_graph::Range, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, referrer_range_start: deno_graph::Position,
mode: ResolutionMode, resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> { ) -> Result<ModuleSpecifier, ResolveError> {
fn to_node_mode(mode: ResolutionMode) -> NodeResolutionMode { let resolution = self
match mode { .deno_resolver
ResolutionMode::Execution => NodeResolutionMode::Execution, .resolve(raw_specifier, referrer, resolution_mode, resolution_kind)
ResolutionMode::Types => NodeResolutionMode::Types, .map_err(|err| match err.into_kind() {
} deno_resolver::DenoResolveErrorKind::MappedResolution(
mapped_resolution_error,
) => match mapped_resolution_error {
MappedResolutionError::Specifier(e) => ResolveError::Specifier(e),
// deno_graph checks specifically for an ImportMapError
MappedResolutionError::ImportMap(e) => ResolveError::Other(e.into()),
err => ResolveError::Other(err.into()),
},
err => ResolveError::Other(err.into()),
})?;
if resolution.found_package_json_dep {
// mark that we need to do an "npm install" later
self.found_package_json_dep_flag.raise();
} }
let referrer = &referrer_range.specifier; if let Some(diagnostic) = resolution.maybe_diagnostic {
match &*diagnostic {
// Use node resolution if we're in an npm package MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
if let Some(node_resolver) = self.node_resolver.as_ref() { reference,
if referrer.scheme() == "file" && node_resolver.in_npm_package(referrer) {
return node_resolver
.resolve(raw_specifier, referrer, referrer_kind, to_node_mode(mode))
.map(|res| res.into_url())
.map_err(|e| ResolveError::Other(e.into()));
}
}
// Attempt to resolve with the workspace resolver
let result: Result<_, ResolveError> = self
.workspace_resolver
.resolve(raw_specifier, referrer)
.map_err(|err| match err {
MappedResolutionError::Specifier(err) => ResolveError::Specifier(err),
MappedResolutionError::ImportMap(err) => {
ResolveError::Other(err.into())
}
MappedResolutionError::Workspace(err) => {
ResolveError::Other(err.into())
}
});
let result = match result {
Ok(resolution) => match resolution {
MappedResolution::Normal {
specifier,
maybe_diagnostic,
}
| MappedResolution::ImportMap {
specifier,
maybe_diagnostic,
} => {
if let Some(diagnostic) = maybe_diagnostic {
match &*diagnostic {
MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion { reference, .. } => {
if self.warned_pkgs.insert(reference.req().clone()) {
log::warn!("{} {}\n at {}", colors::yellow("Warning"), diagnostic, referrer_range);
}
}
}
}
// do sloppy imports resolution if enabled
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
Ok(
sloppy_imports_resolver
.resolve(
&specifier,
match mode {
ResolutionMode::Execution => {
SloppyImportsResolutionMode::Execution
}
ResolutionMode::Types => SloppyImportsResolutionMode::Types,
},
)
.map(|s| s.into_specifier())
.unwrap_or(specifier),
)
} else {
Ok(specifier)
}
}
MappedResolution::WorkspaceJsrPackage { specifier, .. } => {
Ok(specifier)
}
MappedResolution::WorkspaceNpmPackage {
target_pkg_json: pkg_json,
sub_path,
..
} => self
.node_resolver
.as_ref()
.unwrap()
.resolve_package_sub_path_from_deno_module(
pkg_json.dir_path(),
sub_path.as_deref(),
Some(referrer),
referrer_kind,
to_node_mode(mode),
)
.map_err(|e| ResolveError::Other(e.into())),
MappedResolution::PackageJson {
dep_result,
alias,
sub_path,
.. ..
} => { } => {
// found a specifier in the package.json, so mark that if self.warned_pkgs.insert(reference.req().clone()) {
// we need to do an "npm install" later log::warn!(
self.found_package_json_dep_flag.raise(); "{} {}\n at {}:{}",
colors::yellow("Warning"),
dep_result diagnostic,
.as_ref()
.map_err(|e| ResolveError::Other(e.clone().into()))
.and_then(|dep| match dep {
PackageJsonDepValue::Req(req) => {
ModuleSpecifier::parse(&format!(
"npm:{}{}",
req,
sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
))
.map_err(|e| ResolveError::Other(e.into()))
}
PackageJsonDepValue::Workspace(version_req) => self
.workspace_resolver
.resolve_workspace_pkg_json_folder_for_pkg_json_dep(
alias,
version_req,
)
.map_err(|e| ResolveError::Other(e.into()))
.and_then(|pkg_folder| {
self
.node_resolver
.as_ref()
.unwrap()
.resolve_package_sub_path_from_deno_module(
pkg_folder,
sub_path.as_deref(),
Some(referrer),
referrer_kind,
to_node_mode(mode),
)
.map_err(|e| ResolveError::Other(e.into()))
}),
})
}
},
Err(err) => Err(err),
};
// When the user is vendoring, don't allow them to import directly from the vendor/ directory
// as it might cause them confusion or duplicate dependencies. Additionally, this folder has
// special treatment in the language server so it will definitely cause issues/confusion there
// if they do this.
if let Some(vendor_specifier) = &self.maybe_vendor_specifier {
if let Ok(specifier) = &result {
if specifier.as_str().starts_with(vendor_specifier.as_str()) {
return Err(ResolveError::Other(anyhow!("Importing from the vendor directory is not permitted. Use a remote specifier instead or disable vendoring.")));
}
}
}
let Some(node_resolver) = &self.node_resolver else {
return result;
};
let is_byonm = self
.npm_resolver
.as_ref()
.is_some_and(|r| r.as_byonm().is_some());
match result {
Ok(specifier) => {
if let Ok(npm_req_ref) =
NpmPackageReqReference::from_specifier(&specifier)
{
// check if the npm specifier resolves to a workspace member
if let Some(pkg_folder) = self
.workspace_resolver
.resolve_workspace_pkg_json_folder_for_npm_specifier(
npm_req_ref.req(),
)
{
return node_resolver
.resolve_package_sub_path_from_deno_module(
pkg_folder,
npm_req_ref.sub_path(),
Some(referrer),
referrer_kind,
to_node_mode(mode),
)
.map_err(|e| ResolveError::Other(e.into()));
}
// do npm resolution for byonm
if is_byonm {
return node_resolver
.resolve_req_reference(
&npm_req_ref,
referrer,
referrer_kind,
to_node_mode(mode),
)
.map_err(|err| err.into());
}
}
Ok(match node_resolver.handle_if_in_node_modules(&specifier)? {
Some(specifier) => specifier,
None => specifier,
})
}
Err(err) => {
// 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(
raw_specifier,
referrer, referrer,
referrer_kind, referrer_range_start,
to_node_mode(mode), );
)
.map_err(ResolveError::Other)?;
if let Some(res) = maybe_resolution {
match res {
NodeResolution::Module(url) => return Ok(url),
NodeResolution::BuiltIn(_) => {
// don't resolve bare specifiers for built-in modules via node resolution
}
}
} }
} }
Err(err)
} }
} }
Ok(resolution.url)
} }
} }
@ -980,13 +329,10 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> {
module_name: &str, module_name: &str,
range: &deno_graph::Range, range: &deno_graph::Range,
) { ) {
let deno_graph::Range { let start = range.range.start;
start, specifier, .. let specifier = &range.specifier;
} = range;
let line = start.line + 1;
let column = start.character + 1;
if !*DENO_DISABLE_PEDANTIC_NODE_WARNINGS { if !*DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
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")) log::warn!("{} Resolving \"{module_name}\" as \"node:{module_name}\" at {specifier}:{start}. If you want to use a built-in Node module, add a \"node:\" prefix.", colors::yellow("Warning"))
} }
} }

View file

@ -431,8 +431,34 @@
"type": "object", "type": "object",
"patternProperties": { "patternProperties": {
"^[A-Za-z][A-Za-z0-9_\\-:]*$": { "^[A-Za-z][A-Za-z0-9_\\-:]*$": {
"type": "string", "oneOf": [
"description": "Command to execute for this task name." {
"type": "string",
"description": "Command to execute for this task name."
},
{
"type": "object",
"description": "A definition of a task to execute",
"properties": {
"description": {
"type": "string",
"description": "Description of a task that will be shown when running `deno task` without a task name"
},
"command": {
"type": "string",
"required": true,
"description": "The task to execute"
},
"dependencies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tasks that should be executed before this task"
}
}
}
]
} }
}, },
"additionalProperties": false "additionalProperties": false
@ -528,12 +554,15 @@
"bare-node-builtins", "bare-node-builtins",
"byonm", "byonm",
"cron", "cron",
"detect-cjs",
"ffi", "ffi",
"fs", "fs",
"fmt-component", "fmt-component",
"fmt-sql",
"http", "http",
"kv", "kv",
"net", "net",
"node-globals",
"sloppy-imports", "sloppy-imports",
"temporal", "temporal",
"unsafe-proto", "unsafe-proto",

View file

@ -4,6 +4,7 @@ use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::env;
use std::env::current_exe; use std::env::current_exe;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs; use std::fs;
@ -15,6 +16,7 @@ use std::io::Seek;
use std::io::SeekFrom; use std::io::SeekFrom;
use std::io::Write; use std::io::Write;
use std::ops::Range; use std::ops::Range;
use std::path::Component;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@ -47,11 +49,11 @@ use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_fs::RealFs; use deno_runtime::deno_fs::RealFs;
use deno_runtime::deno_io::fs::FsError; use deno_runtime::deno_io::fs::FsError;
use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PackageJson;
use deno_runtime::ops::otel::OtelConfig;
use deno_semver::npm::NpmVersionReqParseError; use deno_semver::npm::NpmVersionReqParseError;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use deno_semver::Version; use deno_semver::Version;
use deno_semver::VersionReqSpecifierParseError; use deno_semver::VersionReqSpecifierParseError;
use deno_telemetry::OtelConfig;
use indexmap::IndexMap; use indexmap::IndexMap;
use log::Level; use log::Level;
use serde::Deserialize; use serde::Deserialize;
@ -64,6 +66,7 @@ use crate::args::NpmInstallDepsProvider;
use crate::args::PermissionFlags; use crate::args::PermissionFlags;
use crate::args::UnstableConfig; use crate::args::UnstableConfig;
use crate::cache::DenoDir; use crate::cache::DenoDir;
use crate::cache::FastInsecureHasher;
use crate::emit::Emitter; use crate::emit::Emitter;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
@ -86,6 +89,7 @@ use super::serialization::RemoteModulesStore;
use super::serialization::RemoteModulesStoreBuilder; use super::serialization::RemoteModulesStoreBuilder;
use super::virtual_fs::FileBackedVfs; use super::virtual_fs::FileBackedVfs;
use super::virtual_fs::VfsBuilder; use super::virtual_fs::VfsBuilder;
use super::virtual_fs::VfsFileSubDataKind;
use super::virtual_fs::VfsRoot; use super::virtual_fs::VfsRoot;
use super::virtual_fs::VirtualDirectory; use super::virtual_fs::VirtualDirectory;
@ -174,6 +178,7 @@ pub struct SerializedWorkspaceResolver {
pub struct Metadata { pub struct Metadata {
pub argv: Vec<String>, pub argv: Vec<String>,
pub seed: Option<u64>, pub seed: Option<u64>,
pub code_cache_key: Option<u64>,
pub permissions: PermissionFlags, pub permissions: PermissionFlags,
pub location: Option<Url>, pub location: Option<Url>,
pub v8_flags: Vec<String>, pub v8_flags: Vec<String>,
@ -199,7 +204,8 @@ fn write_binary_bytes(
compile_flags: &CompileFlags, compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let data_section_bytes = let data_section_bytes =
serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs)?; serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs)
.context("Serializing binary data section.")?;
let target = compile_flags.resolve_target(); let target = compile_flags.resolve_target();
if target.contains("linux") { if target.contains("linux") {
@ -272,16 +278,17 @@ impl StandaloneModules {
if specifier.scheme() == "file" { if specifier.scheme() == "file" {
let path = deno_path_util::url_to_file_path(specifier)?; let path = deno_path_util::url_to_file_path(specifier)?;
let bytes = match self.vfs.file_entry(&path) { let bytes = match self.vfs.file_entry(&path) {
Ok(entry) => self.vfs.read_file_all(entry)?, Ok(entry) => self
.vfs
.read_file_all(entry, VfsFileSubDataKind::ModuleGraph)?,
Err(err) if err.kind() == ErrorKind::NotFound => { Err(err) if err.kind() == ErrorKind::NotFound => {
let bytes = match RealFs.read_file_sync(&path, None) { match RealFs.read_file_sync(&path, None) {
Ok(bytes) => bytes, Ok(bytes) => bytes,
Err(FsError::Io(err)) if err.kind() == ErrorKind::NotFound => { Err(FsError::Io(err)) if err.kind() == ErrorKind::NotFound => {
return Ok(None) return Ok(None)
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; }
Cow::Owned(bytes)
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
@ -362,6 +369,7 @@ pub fn extract_standalone(
pub struct DenoCompileBinaryWriter<'a> { pub struct DenoCompileBinaryWriter<'a> {
cjs_tracker: &'a CjsTracker, cjs_tracker: &'a CjsTracker,
cli_options: &'a CliOptions,
deno_dir: &'a DenoDir, deno_dir: &'a DenoDir,
emitter: &'a Emitter, emitter: &'a Emitter,
file_fetcher: &'a FileFetcher, file_fetcher: &'a FileFetcher,
@ -375,6 +383,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
cjs_tracker: &'a CjsTracker, cjs_tracker: &'a CjsTracker,
cli_options: &'a CliOptions,
deno_dir: &'a DenoDir, deno_dir: &'a DenoDir,
emitter: &'a Emitter, emitter: &'a Emitter,
file_fetcher: &'a FileFetcher, file_fetcher: &'a FileFetcher,
@ -385,6 +394,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
) -> Self { ) -> Self {
Self { Self {
cjs_tracker, cjs_tracker,
cli_options,
deno_dir, deno_dir,
emitter, emitter,
file_fetcher, file_fetcher,
@ -401,8 +411,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
graph: &ModuleGraph, graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>, root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier, entrypoint: &ModuleSpecifier,
include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags, compile_flags: &CompileFlags,
cli_options: &CliOptions,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// Select base binary based on target // Select base binary based on target
let mut original_binary = self.get_base_binary(compile_flags).await?; let mut original_binary = self.get_base_binary(compile_flags).await?;
@ -415,7 +425,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
target, target,
) )
} }
set_windows_binary_to_gui(&mut original_binary)?; set_windows_binary_to_gui(&mut original_binary)
.context("Setting windows binary to GUI.")?;
} }
if compile_flags.icon.is_some() { if compile_flags.icon.is_some() {
let target = compile_flags.resolve_target(); let target = compile_flags.resolve_target();
@ -433,7 +444,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
graph, graph,
root_dir_url, root_dir_url,
entrypoint, entrypoint,
cli_options, include_files,
compile_flags, compile_flags,
) )
.await .await
@ -447,7 +458,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
// //
// Phase 2 of the 'min sized' deno compile RFC talks // Phase 2 of the 'min sized' deno compile RFC talks
// about adding this as a flag. // about adding this as a flag.
if let Some(path) = std::env::var_os("DENORT_BIN") { if let Some(path) = get_dev_binary_path() {
return std::fs::read(&path).with_context(|| { return std::fs::read(&path).with_context(|| {
format!("Could not find denort at '{}'", path.to_string_lossy()) format!("Could not find denort at '{}'", path.to_string_lossy())
}); });
@ -476,10 +487,14 @@ impl<'a> DenoCompileBinaryWriter<'a> {
if !binary_path.exists() { if !binary_path.exists() {
self self
.download_base_binary(&download_directory, &binary_path_suffix) .download_base_binary(&download_directory, &binary_path_suffix)
.await?; .await
.context("Setting up base binary.")?;
} }
let archive_data = std::fs::read(binary_path)?; let read_file = |path: &Path| -> Result<Vec<u8>, AnyError> {
std::fs::read(path).with_context(|| format!("Reading {}", path.display()))
};
let archive_data = read_file(&binary_path)?;
let temp_dir = tempfile::TempDir::new()?; let temp_dir = tempfile::TempDir::new()?;
let base_binary_path = archive::unpack_into_dir(archive::UnpackArgs { let base_binary_path = archive::unpack_into_dir(archive::UnpackArgs {
exe_name: "denort", exe_name: "denort",
@ -488,7 +503,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
is_windows: target.contains("windows"), is_windows: target.contains("windows"),
dest_path: temp_dir.path(), dest_path: temp_dir.path(),
})?; })?;
let base_binary = std::fs::read(base_binary_path)?; let base_binary = read_file(&base_binary_path)?;
drop(temp_dir); // delete the temp dir drop(temp_dir); // delete the temp dir
Ok(base_binary) Ok(base_binary)
} }
@ -516,15 +531,19 @@ impl<'a> DenoCompileBinaryWriter<'a> {
let bytes = match maybe_bytes { let bytes = match maybe_bytes {
Some(bytes) => bytes, Some(bytes) => bytes,
None => { None => {
log::info!("Download could not be found, aborting"); bail!("Download could not be found, aborting");
std::process::exit(1)
} }
}; };
std::fs::create_dir_all(output_directory)?; let create_dir_all = |dir: &Path| {
std::fs::create_dir_all(dir)
.with_context(|| format!("Creating {}", dir.display()))
};
create_dir_all(output_directory)?;
let output_path = output_directory.join(binary_path_suffix); let output_path = output_directory.join(binary_path_suffix);
std::fs::create_dir_all(output_path.parent().unwrap())?; create_dir_all(output_path.parent().unwrap())?;
tokio::fs::write(output_path, bytes).await?; std::fs::write(&output_path, bytes)
.with_context(|| format!("Writing {}", output_path.display()))?;
Ok(()) Ok(())
} }
@ -538,76 +557,101 @@ impl<'a> DenoCompileBinaryWriter<'a> {
graph: &ModuleGraph, graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>, root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier, entrypoint: &ModuleSpecifier,
cli_options: &CliOptions, include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags, compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let ca_data = match cli_options.ca_data() { let ca_data = match self.cli_options.ca_data() {
Some(CaData::File(ca_file)) => Some( Some(CaData::File(ca_file)) => Some(
std::fs::read(ca_file) std::fs::read(ca_file).with_context(|| format!("Reading {ca_file}"))?,
.with_context(|| format!("Reading: {ca_file}"))?,
), ),
Some(CaData::Bytes(bytes)) => Some(bytes.clone()), Some(CaData::Bytes(bytes)) => Some(bytes.clone()),
None => None, None => None,
}; };
let root_path = root_dir_url.inner().to_file_path().unwrap(); let root_path = root_dir_url.inner().to_file_path().unwrap();
let (maybe_npm_vfs, node_modules, npm_snapshot) = match self let (maybe_npm_vfs, node_modules, npm_snapshot) =
.npm_resolver match self.npm_resolver.as_inner() {
.as_inner() InnerCliNpmResolverRef::Managed(managed) => {
{ let snapshot =
InnerCliNpmResolverRef::Managed(managed) => { managed.serialized_valid_snapshot_for_system(&self.npm_system_info);
let snapshot = if !snapshot.as_serialized().packages.is_empty() {
managed.serialized_valid_snapshot_for_system(&self.npm_system_info); let npm_vfs_builder = self
if !snapshot.as_serialized().packages.is_empty() { .build_npm_vfs(&root_path)
let npm_vfs_builder = self.build_npm_vfs(&root_path, cli_options)?; .context("Building npm vfs.")?;
(
Some(npm_vfs_builder),
Some(NodeModules::Managed {
node_modules_dir: self
.npm_resolver
.root_node_modules_path()
.map(|path| {
root_dir_url
.specifier_key(
&ModuleSpecifier::from_directory_path(path).unwrap(),
)
.into_owned()
}),
}),
Some(snapshot),
)
} else {
(None, None, None)
}
}
InnerCliNpmResolverRef::Byonm(resolver) => {
let npm_vfs_builder = self.build_npm_vfs(&root_path)?;
( (
Some(npm_vfs_builder), Some(npm_vfs_builder),
Some(NodeModules::Managed { Some(NodeModules::Byonm {
node_modules_dir: self.npm_resolver.root_node_modules_path().map( root_node_modules_dir: resolver.root_node_modules_path().map(
|path| { |node_modules_dir| {
root_dir_url root_dir_url
.specifier_key( .specifier_key(
&ModuleSpecifier::from_directory_path(path).unwrap(), &ModuleSpecifier::from_directory_path(node_modules_dir)
.unwrap(),
) )
.into_owned() .into_owned()
}, },
), ),
}), }),
Some(snapshot), None,
) )
} else {
(None, None, None)
} }
} };
InnerCliNpmResolverRef::Byonm(resolver) => {
let npm_vfs_builder = self.build_npm_vfs(&root_path, cli_options)?;
(
Some(npm_vfs_builder),
Some(NodeModules::Byonm {
root_node_modules_dir: resolver.root_node_modules_path().map(
|node_modules_dir| {
root_dir_url
.specifier_key(
&ModuleSpecifier::from_directory_path(node_modules_dir)
.unwrap(),
)
.into_owned()
},
),
}),
None,
)
}
};
let mut vfs = if let Some(npm_vfs) = maybe_npm_vfs { let mut vfs = if let Some(npm_vfs) = maybe_npm_vfs {
npm_vfs npm_vfs
} else { } else {
VfsBuilder::new(root_path.clone())? VfsBuilder::new(root_path.clone())?
}; };
for include_file in include_files {
let path = deno_path_util::url_to_file_path(include_file)?;
if path.is_dir() {
// TODO(#26941): we should analyze if any of these are
// modules in order to include their dependencies
vfs
.add_dir_recursive(&path)
.with_context(|| format!("Including {}", path.display()))?;
} else {
vfs
.add_file_at_path(&path)
.with_context(|| format!("Including {}", path.display()))?;
}
}
let mut remote_modules_store = RemoteModulesStoreBuilder::default(); let mut remote_modules_store = RemoteModulesStoreBuilder::default();
let mut code_cache_key_hasher = if self.cli_options.code_cache_enabled() {
Some(FastInsecureHasher::new_deno_versioned())
} else {
None
};
for module in graph.modules() { for module in graph.modules() {
if module.specifier().scheme() == "data" { if module.specifier().scheme() == "data" {
continue; // don't store data urls as an entry as they're in the code continue; // don't store data urls as an entry as they're in the code
} }
if let Some(hasher) = &mut code_cache_key_hasher {
if let Some(source) = module.source() {
hasher.write(module.specifier().as_str().as_bytes());
hasher.write(source.as_bytes());
}
}
let (maybe_source, media_type) = match module { let (maybe_source, media_type) = match module {
deno_graph::Module::Js(m) => { deno_graph::Module::Js(m) => {
let source = if m.media_type.is_emittable() { let source = if m.media_type.is_emittable() {
@ -635,6 +679,9 @@ impl<'a> DenoCompileBinaryWriter<'a> {
deno_graph::Module::Json(m) => { deno_graph::Module::Json(m) => {
(Some(m.source.as_bytes().to_vec()), m.media_type) (Some(m.source.as_bytes().to_vec()), m.media_type)
} }
deno_graph::Module::Wasm(m) => {
(Some(m.source.to_vec()), MediaType::Wasm)
}
deno_graph::Module::Npm(_) deno_graph::Module::Npm(_)
| deno_graph::Module::Node(_) | deno_graph::Module::Node(_)
| deno_graph::Module::External(_) => (None, MediaType::Unknown), | deno_graph::Module::External(_) => (None, MediaType::Unknown),
@ -646,8 +693,9 @@ impl<'a> DenoCompileBinaryWriter<'a> {
&file_path, &file_path,
match maybe_source { match maybe_source {
Some(source) => source, Some(source) => source,
None => RealFs.read_file_sync(&file_path, None)?, None => RealFs.read_file_sync(&file_path, None)?.into_owned(),
}, },
VfsFileSubDataKind::ModuleGraph,
) )
.with_context(|| { .with_context(|| {
format!("Failed adding '{}'", file_path.display()) format!("Failed adding '{}'", file_path.display())
@ -658,25 +706,33 @@ impl<'a> DenoCompileBinaryWriter<'a> {
} }
remote_modules_store.add_redirects(&graph.redirects); remote_modules_store.add_redirects(&graph.redirects);
let env_vars_from_env_file = match cli_options.env_file_name() { let env_vars_from_env_file = match self.cli_options.env_file_name() {
Some(env_filename) => { Some(env_filenames) => {
log::info!("{} Environment variables from the file \"{}\" were embedded in the generated executable file", crate::colors::yellow("Warning"), env_filename); let mut aggregated_env_vars = IndexMap::new();
get_file_env_vars(env_filename.to_string())? for env_filename in env_filenames.iter().rev() {
log::info!("{} Environment variables from the file \"{}\" were embedded in the generated executable file", crate::colors::yellow("Warning"), env_filename);
let env_vars = get_file_env_vars(env_filename.to_string())?;
aggregated_env_vars.extend(env_vars);
}
aggregated_env_vars
} }
None => Default::default(), None => Default::default(),
}; };
let metadata = Metadata { let metadata = Metadata {
argv: compile_flags.args.clone(), argv: compile_flags.args.clone(),
seed: cli_options.seed(), seed: self.cli_options.seed(),
location: cli_options.location_flag().clone(), code_cache_key: code_cache_key_hasher.map(|h| h.finish()),
permissions: cli_options.permission_flags().clone(), location: self.cli_options.location_flag().clone(),
v8_flags: cli_options.v8_flags().clone(), permissions: self.cli_options.permission_flags().clone(),
unsafely_ignore_certificate_errors: cli_options v8_flags: self.cli_options.v8_flags().clone(),
unsafely_ignore_certificate_errors: self
.cli_options
.unsafely_ignore_certificate_errors() .unsafely_ignore_certificate_errors()
.clone(), .clone(),
log_level: cli_options.log_level(), log_level: self.cli_options.log_level(),
ca_stores: cli_options.ca_stores().clone(), ca_stores: self.cli_options.ca_stores().clone(),
ca_data, ca_data,
env_vars_from_env_file, env_vars_from_env_file,
entrypoint_key: root_dir_url.specifier_key(entrypoint).into_owned(), entrypoint_key: root_dir_url.specifier_key(entrypoint).into_owned(),
@ -719,11 +775,12 @@ impl<'a> DenoCompileBinaryWriter<'a> {
node_modules, node_modules,
unstable_config: UnstableConfig { unstable_config: UnstableConfig {
legacy_flag_enabled: false, legacy_flag_enabled: false,
bare_node_builtins: cli_options.unstable_bare_node_builtins(), bare_node_builtins: self.cli_options.unstable_bare_node_builtins(),
sloppy_imports: cli_options.unstable_sloppy_imports(), detect_cjs: self.cli_options.unstable_detect_cjs(),
features: cli_options.unstable_features(), sloppy_imports: self.cli_options.unstable_sloppy_imports(),
features: self.cli_options.unstable_features(),
}, },
otel_config: cli_options.otel_config(), otel_config: self.cli_options.otel_config(),
}; };
write_binary_bytes( write_binary_bytes(
@ -735,13 +792,10 @@ impl<'a> DenoCompileBinaryWriter<'a> {
vfs, vfs,
compile_flags, compile_flags,
) )
.context("Writing binary bytes")
} }
fn build_npm_vfs( fn build_npm_vfs(&self, root_path: &Path) -> Result<VfsBuilder, AnyError> {
&self,
root_path: &Path,
cli_options: &CliOptions,
) -> Result<VfsBuilder, AnyError> {
fn maybe_warn_different_system(system_info: &NpmSystemInfo) { fn maybe_warn_different_system(system_info: &NpmSystemInfo) {
if system_info != &NpmSystemInfo::default() { if system_info != &NpmSystemInfo::default() {
log::warn!("{} The node_modules directory may be incompatible with the target system.", crate::colors::yellow("Warning")); log::warn!("{} The node_modules directory may be incompatible with the target system.", crate::colors::yellow("Warning"));
@ -818,13 +872,18 @@ impl<'a> DenoCompileBinaryWriter<'a> {
InnerCliNpmResolverRef::Byonm(_) => { InnerCliNpmResolverRef::Byonm(_) => {
maybe_warn_different_system(&self.npm_system_info); maybe_warn_different_system(&self.npm_system_info);
let mut builder = VfsBuilder::new(root_path.to_path_buf())?; let mut builder = VfsBuilder::new(root_path.to_path_buf())?;
for pkg_json in cli_options.workspace().package_jsons() { for pkg_json in self.cli_options.workspace().package_jsons() {
builder.add_file_at_path(&pkg_json.path)?; builder.add_file_at_path(&pkg_json.path)?;
} }
// traverse and add all the node_modules directories in the workspace // traverse and add all the node_modules directories in the workspace
let mut pending_dirs = VecDeque::new(); let mut pending_dirs = VecDeque::new();
pending_dirs.push_back( pending_dirs.push_back(
cli_options.workspace().root_dir().to_file_path().unwrap(), self
.cli_options
.workspace()
.root_dir()
.to_file_path()
.unwrap(),
); );
while let Some(pending_dir) = pending_dirs.pop_front() { while let Some(pending_dir) = pending_dirs.pop_front() {
let mut entries = fs::read_dir(&pending_dir) let mut entries = fs::read_dir(&pending_dir)
@ -851,6 +910,31 @@ impl<'a> DenoCompileBinaryWriter<'a> {
} }
} }
fn get_denort_path(deno_exe: PathBuf) -> Option<OsString> {
let mut denort = deno_exe;
denort.set_file_name(if cfg!(windows) {
"denort.exe"
} else {
"denort"
});
denort.exists().then(|| denort.into_os_string())
}
fn get_dev_binary_path() -> Option<OsString> {
env::var_os("DENORT_BIN").or_else(|| {
env::current_exe().ok().and_then(|exec_path| {
if exec_path
.components()
.any(|component| component == Component::Normal("target".as_ref()))
{
get_denort_path(exec_path)
} else {
None
}
})
})
}
/// This function returns the environment variables specified /// This function returns the environment variables specified
/// in the passed environment file. /// in the passed environment file.
fn get_file_env_vars( fn get_file_env_vars(

View file

@ -0,0 +1,523 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_core::unsync::sync::AtomicFlag;
use deno_runtime::code_cache::CodeCache;
use deno_runtime::code_cache::CodeCacheType;
use crate::cache::FastInsecureHasher;
use crate::util::path::get_atomic_file_path;
use crate::worker::CliCodeCache;
enum CodeCacheStrategy {
FirstRun(FirstRunCodeCacheStrategy),
SubsequentRun(SubsequentRunCodeCacheStrategy),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DenoCompileCodeCacheEntry {
pub source_hash: u64,
pub data: Vec<u8>,
}
pub struct DenoCompileCodeCache {
strategy: CodeCacheStrategy,
}
impl DenoCompileCodeCache {
pub fn new(file_path: PathBuf, cache_key: u64) -> Self {
// attempt to deserialize the cache data
match deserialize(&file_path, cache_key) {
Ok(data) => {
log::debug!(
"Loaded {} code cache entries from {}",
data.len(),
file_path.display()
);
Self {
strategy: CodeCacheStrategy::SubsequentRun(
SubsequentRunCodeCacheStrategy {
is_finished: AtomicFlag::lowered(),
data: Mutex::new(data),
},
),
}
}
Err(err) => {
log::debug!(
"Failed to deserialize code cache from {}: {:#}",
file_path.display(),
err
);
Self {
strategy: CodeCacheStrategy::FirstRun(FirstRunCodeCacheStrategy {
cache_key,
file_path,
is_finished: AtomicFlag::lowered(),
data: Mutex::new(FirstRunCodeCacheData {
cache: HashMap::new(),
add_count: 0,
}),
}),
}
}
}
}
}
impl CodeCache for DenoCompileCodeCache {
fn get_sync(
&self,
specifier: &ModuleSpecifier,
code_cache_type: CodeCacheType,
source_hash: u64,
) -> Option<Vec<u8>> {
match &self.strategy {
CodeCacheStrategy::FirstRun(strategy) => {
if !strategy.is_finished.is_raised() {
// we keep track of how many times the cache is requested
// then serialize the cache when we get that number of
// "set" calls
strategy.data.lock().add_count += 1;
}
None
}
CodeCacheStrategy::SubsequentRun(strategy) => {
if strategy.is_finished.is_raised() {
return None;
}
strategy.take_from_cache(specifier, code_cache_type, source_hash)
}
}
}
fn set_sync(
&self,
specifier: ModuleSpecifier,
code_cache_type: CodeCacheType,
source_hash: u64,
bytes: &[u8],
) {
match &self.strategy {
CodeCacheStrategy::FirstRun(strategy) => {
if strategy.is_finished.is_raised() {
return;
}
let data_to_serialize = {
let mut data = strategy.data.lock();
data.cache.insert(
(specifier.to_string(), code_cache_type),
DenoCompileCodeCacheEntry {
source_hash,
data: bytes.to_vec(),
},
);
if data.add_count != 0 {
data.add_count -= 1;
}
if data.add_count == 0 {
// don't allow using the cache anymore
strategy.is_finished.raise();
if data.cache.is_empty() {
None
} else {
Some(std::mem::take(&mut data.cache))
}
} else {
None
}
};
if let Some(cache_data) = &data_to_serialize {
strategy.write_cache_data(cache_data);
}
}
CodeCacheStrategy::SubsequentRun(_) => {
// do nothing
}
}
}
}
impl CliCodeCache for DenoCompileCodeCache {
fn enabled(&self) -> bool {
match &self.strategy {
CodeCacheStrategy::FirstRun(strategy) => {
!strategy.is_finished.is_raised()
}
CodeCacheStrategy::SubsequentRun(strategy) => {
!strategy.is_finished.is_raised()
}
}
}
fn as_code_cache(self: Arc<Self>) -> Arc<dyn CodeCache> {
self
}
}
type CodeCacheKey = (String, CodeCacheType);
struct FirstRunCodeCacheData {
cache: HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
add_count: usize,
}
struct FirstRunCodeCacheStrategy {
cache_key: u64,
file_path: PathBuf,
is_finished: AtomicFlag,
data: Mutex<FirstRunCodeCacheData>,
}
impl FirstRunCodeCacheStrategy {
fn write_cache_data(
&self,
cache_data: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
) {
let count = cache_data.len();
let temp_file = get_atomic_file_path(&self.file_path);
match serialize(&temp_file, self.cache_key, cache_data) {
Ok(()) => {
if let Err(err) = std::fs::rename(&temp_file, &self.file_path) {
log::debug!("Failed to rename code cache: {}", err);
let _ = std::fs::remove_file(&temp_file);
} else {
log::debug!("Serialized {} code cache entries", count);
}
}
Err(err) => {
let _ = std::fs::remove_file(&temp_file);
log::debug!("Failed to serialize code cache: {}", err);
}
}
}
}
struct SubsequentRunCodeCacheStrategy {
is_finished: AtomicFlag,
data: Mutex<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>>,
}
impl SubsequentRunCodeCacheStrategy {
fn take_from_cache(
&self,
specifier: &ModuleSpecifier,
code_cache_type: CodeCacheType,
source_hash: u64,
) -> Option<Vec<u8>> {
let mut data = self.data.lock();
// todo(dsherret): how to avoid the clone here?
let entry = data.remove(&(specifier.to_string(), code_cache_type))?;
if entry.source_hash != source_hash {
return None;
}
if data.is_empty() {
self.is_finished.raise();
}
Some(entry.data)
}
}
/// File format:
/// - <header>
/// - <cache key>
/// - <u32: number of entries>
/// - <[entry length]> - u64 * number of entries
/// - <[entry]>
/// - <[u8]: entry data>
/// - <String: specifier>
/// - <u8>: code cache type
/// - <u32: specifier length>
/// - <u64: source hash>
/// - <u64: entry data hash>
fn serialize(
file_path: &Path,
cache_key: u64,
cache: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
) -> Result<(), AnyError> {
let cache_file = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(file_path)?;
let mut writer = BufWriter::new(cache_file);
serialize_with_writer(&mut writer, cache_key, cache)
}
fn serialize_with_writer<T: Write>(
writer: &mut BufWriter<T>,
cache_key: u64,
cache: &HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>,
) -> Result<(), AnyError> {
// header
writer.write_all(&cache_key.to_le_bytes())?;
writer.write_all(&(cache.len() as u32).to_le_bytes())?;
// lengths of each entry
for ((specifier, _), entry) in cache {
let len: u64 =
entry.data.len() as u64 + specifier.len() as u64 + 1 + 4 + 8 + 8;
writer.write_all(&len.to_le_bytes())?;
}
// entries
for ((specifier, code_cache_type), entry) in cache {
writer.write_all(&entry.data)?;
writer.write_all(&[match code_cache_type {
CodeCacheType::EsModule => 0,
CodeCacheType::Script => 1,
}])?;
writer.write_all(specifier.as_bytes())?;
writer.write_all(&(specifier.len() as u32).to_le_bytes())?;
writer.write_all(&entry.source_hash.to_le_bytes())?;
let hash: u64 = FastInsecureHasher::new_without_deno_version()
.write(&entry.data)
.finish();
writer.write_all(&hash.to_le_bytes())?;
}
writer.flush()?;
Ok(())
}
fn deserialize(
file_path: &Path,
expected_cache_key: u64,
) -> Result<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>, AnyError> {
let cache_file = std::fs::File::open(file_path)?;
let mut reader = BufReader::new(cache_file);
deserialize_with_reader(&mut reader, expected_cache_key)
}
fn deserialize_with_reader<T: Read>(
reader: &mut BufReader<T>,
expected_cache_key: u64,
) -> Result<HashMap<CodeCacheKey, DenoCompileCodeCacheEntry>, AnyError> {
// it's very important to use this below so that a corrupt cache file
// doesn't cause a memory allocation error
fn new_vec_sized<T: Clone>(
capacity: usize,
default_value: T,
) -> Result<Vec<T>, AnyError> {
let mut vec = Vec::new();
vec.try_reserve(capacity)?;
vec.resize(capacity, default_value);
Ok(vec)
}
fn try_subtract(a: usize, b: usize) -> Result<usize, AnyError> {
if a < b {
bail!("Integer underflow");
}
Ok(a - b)
}
let mut header_bytes = vec![0; 8 + 4];
reader.read_exact(&mut header_bytes)?;
let actual_cache_key = u64::from_le_bytes(header_bytes[..8].try_into()?);
if actual_cache_key != expected_cache_key {
// cache bust
bail!("Cache key mismatch");
}
let len = u32::from_le_bytes(header_bytes[8..].try_into()?) as usize;
// read the lengths for each entry found in the file
let entry_len_bytes_capacity = len * 8;
let mut entry_len_bytes = new_vec_sized(entry_len_bytes_capacity, 0)?;
reader.read_exact(&mut entry_len_bytes)?;
let mut lengths = Vec::new();
lengths.try_reserve(len)?;
for i in 0..len {
let pos = i * 8;
lengths.push(
u64::from_le_bytes(entry_len_bytes[pos..pos + 8].try_into()?) as usize,
);
}
let mut map = HashMap::new();
map.try_reserve(len)?;
for len in lengths {
let mut buffer = new_vec_sized(len, 0)?;
reader.read_exact(&mut buffer)?;
let entry_data_hash_start_pos = try_subtract(buffer.len(), 8)?;
let expected_entry_data_hash =
u64::from_le_bytes(buffer[entry_data_hash_start_pos..].try_into()?);
let source_hash_start_pos = try_subtract(entry_data_hash_start_pos, 8)?;
let source_hash = u64::from_le_bytes(
buffer[source_hash_start_pos..entry_data_hash_start_pos].try_into()?,
);
let specifier_end_pos = try_subtract(source_hash_start_pos, 4)?;
let specifier_len = u32::from_le_bytes(
buffer[specifier_end_pos..source_hash_start_pos].try_into()?,
) as usize;
let specifier_start_pos = try_subtract(specifier_end_pos, specifier_len)?;
let specifier = String::from_utf8(
buffer[specifier_start_pos..specifier_end_pos].to_vec(),
)?;
let code_cache_type_pos = try_subtract(specifier_start_pos, 1)?;
let code_cache_type = match buffer[code_cache_type_pos] {
0 => CodeCacheType::EsModule,
1 => CodeCacheType::Script,
_ => bail!("Invalid code cache type"),
};
buffer.truncate(code_cache_type_pos);
let actual_entry_data_hash: u64 =
FastInsecureHasher::new_without_deno_version()
.write(&buffer)
.finish();
if expected_entry_data_hash != actual_entry_data_hash {
bail!("Hash mismatch.")
}
map.insert(
(specifier, code_cache_type),
DenoCompileCodeCacheEntry {
source_hash,
data: buffer,
},
);
}
Ok(map)
}
#[cfg(test)]
mod test {
use test_util::TempDir;
use super::*;
use std::fs::File;
#[test]
fn serialize_deserialize() {
let cache_key = 123456;
let cache = {
let mut cache = HashMap::new();
cache.insert(
("specifier1".to_string(), CodeCacheType::EsModule),
DenoCompileCodeCacheEntry {
source_hash: 1,
data: vec![1, 2, 3],
},
);
cache.insert(
("specifier2".to_string(), CodeCacheType::EsModule),
DenoCompileCodeCacheEntry {
source_hash: 2,
data: vec![4, 5, 6],
},
);
cache.insert(
("specifier2".to_string(), CodeCacheType::Script),
DenoCompileCodeCacheEntry {
source_hash: 2,
data: vec![6, 5, 1],
},
);
cache
};
let mut buffer = Vec::new();
serialize_with_writer(&mut BufWriter::new(&mut buffer), cache_key, &cache)
.unwrap();
let deserialized =
deserialize_with_reader(&mut BufReader::new(&buffer[..]), cache_key)
.unwrap();
assert_eq!(cache, deserialized);
}
#[test]
fn serialize_deserialize_empty() {
let cache_key = 1234;
let cache = HashMap::new();
let mut buffer = Vec::new();
serialize_with_writer(&mut BufWriter::new(&mut buffer), cache_key, &cache)
.unwrap();
let deserialized =
deserialize_with_reader(&mut BufReader::new(&buffer[..]), cache_key)
.unwrap();
assert_eq!(cache, deserialized);
}
#[test]
fn serialize_deserialize_corrupt() {
let buffer = "corrupttestingtestingtesting".as_bytes().to_vec();
let err = deserialize_with_reader(&mut BufReader::new(&buffer[..]), 1234)
.unwrap_err();
assert_eq!(err.to_string(), "Cache key mismatch");
}
#[test]
fn code_cache() {
let temp_dir = TempDir::new();
let file_path = temp_dir.path().join("cache.bin").to_path_buf();
let url1 = ModuleSpecifier::parse("https://deno.land/example1.js").unwrap();
let url2 = ModuleSpecifier::parse("https://deno.land/example2.js").unwrap();
// first run
{
let code_cache = DenoCompileCodeCache::new(file_path.clone(), 1234);
assert!(code_cache
.get_sync(&url1, CodeCacheType::EsModule, 0)
.is_none());
assert!(code_cache
.get_sync(&url2, CodeCacheType::EsModule, 1)
.is_none());
assert!(code_cache.enabled());
code_cache.set_sync(url1.clone(), CodeCacheType::EsModule, 0, &[1, 2, 3]);
assert!(code_cache.enabled());
assert!(!file_path.exists());
code_cache.set_sync(url2.clone(), CodeCacheType::EsModule, 1, &[2, 1, 3]);
assert!(file_path.exists()); // now the new code cache exists
assert!(!code_cache.enabled()); // no longer enabled
}
// second run
{
let code_cache = DenoCompileCodeCache::new(file_path.clone(), 1234);
assert!(code_cache.enabled());
let result1 = code_cache
.get_sync(&url1, CodeCacheType::EsModule, 0)
.unwrap();
assert!(code_cache.enabled());
let result2 = code_cache
.get_sync(&url2, CodeCacheType::EsModule, 1)
.unwrap();
assert!(!code_cache.enabled()); // no longer enabled
assert_eq!(result1, vec![1, 2, 3]);
assert_eq!(result2, vec![2, 1, 3]);
}
// new cache key first run
{
let code_cache = DenoCompileCodeCache::new(file_path.clone(), 54321);
assert!(code_cache
.get_sync(&url1, CodeCacheType::EsModule, 0)
.is_none());
assert!(code_cache
.get_sync(&url2, CodeCacheType::EsModule, 1)
.is_none());
code_cache.set_sync(url1.clone(), CodeCacheType::EsModule, 0, &[2, 2, 3]);
code_cache.set_sync(url2.clone(), CodeCacheType::EsModule, 1, &[3, 2, 3]);
}
// new cache key second run
{
let code_cache = DenoCompileCodeCache::new(file_path.clone(), 54321);
let result1 = code_cache
.get_sync(&url1, CodeCacheType::EsModule, 0)
.unwrap();
assert_eq!(result1, vec![2, 2, 3]);
assert!(code_cache
.get_sync(&url2, CodeCacheType::EsModule, 5) // different hash will cause none
.is_none());
}
}
}

View file

@ -17,6 +17,7 @@ use deno_runtime::deno_io::fs::FsResult;
use deno_runtime::deno_io::fs::FsStat; use deno_runtime::deno_io::fs::FsStat;
use super::virtual_fs::FileBackedVfs; use super::virtual_fs::FileBackedVfs;
use super::virtual_fs::VfsFileSubDataKind;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DenoCompileFileSystem(Arc<FileBackedVfs>); pub struct DenoCompileFileSystem(Arc<FileBackedVfs>);
@ -36,7 +37,8 @@ impl DenoCompileFileSystem {
fn copy_to_real_path(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> { fn copy_to_real_path(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> {
let old_file = self.0.file_entry(oldpath)?; let old_file = self.0.file_entry(oldpath)?;
let old_file_bytes = self.0.read_file_all(old_file)?; let old_file_bytes =
self.0.read_file_all(old_file, VfsFileSubDataKind::Raw)?;
RealFs.write_file_sync( RealFs.write_file_sync(
newpath, newpath,
OpenOptions { OpenOptions {

View file

@ -7,6 +7,7 @@
use binary::StandaloneData; use binary::StandaloneData;
use binary::StandaloneModules; use binary::StandaloneModules;
use code_cache::DenoCompileCodeCache;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_cache_dir::npm::NpmCacheDir; use deno_cache_dir::npm::NpmCacheDir;
use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolution;
@ -17,6 +18,7 @@ use deno_core::anyhow::Context;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::type_error; use deno_core::error::type_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::v8_set_flags; use deno_core::v8_set_flags;
use deno_core::FastString; use deno_core::FastString;
@ -27,8 +29,11 @@ use deno_core::ModuleSpecifier;
use deno_core::ModuleType; use deno_core::ModuleType;
use deno_core::RequestedModuleType; use deno_core::RequestedModuleType;
use deno_core::ResolutionKind; use deno_core::ResolutionKind;
use deno_core::SourceCodeCacheInfo;
use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::npm_rc::ResolvedNpmRc;
use deno_package_json::PackageJsonDepValue; use deno_package_json::PackageJsonDepValue;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::npm::NpmReqResolverOptions;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node::create_host_defined_options; use deno_runtime::deno_node::create_host_defined_options;
use deno_runtime::deno_node::NodeRequireLoader; use deno_runtime::deno_node::NodeRequireLoader;
@ -46,12 +51,14 @@ use deno_semver::npm::NpmPackageReqReference;
use import_map::parse_from_json; use import_map::parse_from_json;
use node_resolver::analyze::NodeCodeTranslator; use node_resolver::analyze::NodeCodeTranslator;
use node_resolver::errors::ClosestPkgJsonError; use node_resolver::errors::ClosestPkgJsonError;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolutionMode; use node_resolver::ResolutionMode;
use serialization::DenoCompileModuleSource; use serialization::DenoCompileModuleSource;
use std::borrow::Cow; use std::borrow::Cow;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use virtual_fs::FileBackedVfs;
use virtual_fs::VfsFileSubDataKind;
use crate::args::create_default_npmrc; use crate::args::create_default_npmrc;
use crate::args::get_root_cert_store; use crate::args::get_root_cert_store;
@ -63,6 +70,7 @@ use crate::args::StorageKeyResolver;
use crate::cache::Caches; use crate::cache::Caches;
use crate::cache::DenoCacheEnvFsAdapter; use crate::cache::DenoCacheEnvFsAdapter;
use crate::cache::DenoDirProvider; use crate::cache::DenoDirProvider;
use crate::cache::FastInsecureHasher;
use crate::cache::NodeAnalysisCache; use crate::cache::NodeAnalysisCache;
use crate::cache::RealDenoCacheEnv; use crate::cache::RealDenoCacheEnv;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
@ -79,18 +87,20 @@ use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::CreateInNpmPkgCheckerOptions; use crate::npm::CreateInNpmPkgCheckerOptions;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use crate::resolver::CliDenoResolverFs; use crate::resolver::CliDenoResolverFs;
use crate::resolver::CliNodeResolver; use crate::resolver::CliNpmReqResolver;
use crate::resolver::IsCjsResolverOptions;
use crate::resolver::NpmModuleLoader; use crate::resolver::NpmModuleLoader;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressBarStyle;
use crate::util::text_encoding::from_utf8_lossy_cow;
use crate::util::v8::construct_v8_flags; use crate::util::v8::construct_v8_flags;
use crate::worker::CliCodeCache;
use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerFactory;
use crate::worker::CliMainWorkerOptions; use crate::worker::CliMainWorkerOptions;
use crate::worker::CreateModuleLoaderResult; use crate::worker::CreateModuleLoaderResult;
use crate::worker::ModuleLoaderFactory; use crate::worker::ModuleLoaderFactory;
pub mod binary; pub mod binary;
mod code_cache;
mod file_system; mod file_system;
mod serialization; mod serialization;
mod virtual_fs; mod virtual_fs;
@ -104,15 +114,46 @@ use self::file_system::DenoCompileFileSystem;
struct SharedModuleLoaderState { struct SharedModuleLoaderState {
cjs_tracker: Arc<CjsTracker>, cjs_tracker: Arc<CjsTracker>,
code_cache: Option<Arc<dyn CliCodeCache>>,
fs: Arc<dyn deno_fs::FileSystem>, fs: Arc<dyn deno_fs::FileSystem>,
modules: StandaloneModules, modules: StandaloneModules,
node_code_translator: Arc<CliNodeCodeTranslator>, node_code_translator: Arc<CliNodeCodeTranslator>,
node_resolver: Arc<CliNodeResolver>, node_resolver: Arc<NodeResolver>,
npm_module_loader: Arc<NpmModuleLoader>, npm_module_loader: Arc<NpmModuleLoader>,
npm_req_resolver: Arc<CliNpmReqResolver>,
npm_resolver: Arc<dyn CliNpmResolver>, npm_resolver: Arc<dyn CliNpmResolver>,
vfs: Arc<FileBackedVfs>,
workspace_resolver: WorkspaceResolver, workspace_resolver: WorkspaceResolver,
} }
impl SharedModuleLoaderState {
fn get_code_cache(
&self,
specifier: &ModuleSpecifier,
source: &[u8],
) -> Option<SourceCodeCacheInfo> {
let Some(code_cache) = &self.code_cache else {
return None;
};
if !code_cache.enabled() {
return None;
}
// deno version is already included in the root cache key
let hash = FastInsecureHasher::new_without_deno_version()
.write_hashable(source)
.finish();
let data = code_cache.get_sync(
specifier,
deno_runtime::code_cache::CodeCacheType::EsModule,
hash,
);
Some(SourceCodeCacheInfo {
hash,
data: data.map(Cow::Owned),
})
}
}
#[derive(Clone)] #[derive(Clone)]
struct EmbeddedModuleLoader { struct EmbeddedModuleLoader {
shared: Arc<SharedModuleLoaderState>, shared: Arc<SharedModuleLoaderState>,
@ -153,9 +194,9 @@ impl ModuleLoader for EmbeddedModuleLoader {
.cjs_tracker .cjs_tracker
.is_maybe_cjs(&referrer, MediaType::from_specifier(&referrer))? .is_maybe_cjs(&referrer, MediaType::from_specifier(&referrer))?
{ {
NodeModuleKind::Cjs ResolutionMode::Require
} else { } else {
NodeModuleKind::Esm ResolutionMode::Import
}; };
if self.shared.node_resolver.in_npm_package(&referrer) { if self.shared.node_resolver.in_npm_package(&referrer) {
@ -167,7 +208,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
raw_specifier, raw_specifier,
&referrer, &referrer,
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
)? )?
.into_url(), .into_url(),
); );
@ -190,12 +231,12 @@ impl ModuleLoader for EmbeddedModuleLoader {
self self
.shared .shared
.node_resolver .node_resolver
.resolve_package_sub_path_from_deno_module( .resolve_package_subpath_from_deno_module(
pkg_json.dir_path(), pkg_json.dir_path(),
sub_path.as_deref(), sub_path.as_deref(),
Some(&referrer), Some(&referrer),
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
)?, )?,
), ),
Ok(MappedResolution::PackageJson { Ok(MappedResolution::PackageJson {
@ -204,15 +245,17 @@ impl ModuleLoader for EmbeddedModuleLoader {
alias, alias,
.. ..
}) => match dep_result.as_ref().map_err(|e| AnyError::from(e.clone()))? { }) => match dep_result.as_ref().map_err(|e| AnyError::from(e.clone()))? {
PackageJsonDepValue::Req(req) => { PackageJsonDepValue::Req(req) => self
self.shared.node_resolver.resolve_req_with_sub_path( .shared
.npm_req_resolver
.resolve_req_with_sub_path(
req, req,
sub_path.as_deref(), sub_path.as_deref(),
&referrer, &referrer,
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
) )
} .map_err(AnyError::from),
PackageJsonDepValue::Workspace(version_req) => { PackageJsonDepValue::Workspace(version_req) => {
let pkg_folder = self let pkg_folder = self
.shared .shared
@ -225,12 +268,12 @@ impl ModuleLoader for EmbeddedModuleLoader {
self self
.shared .shared
.node_resolver .node_resolver
.resolve_package_sub_path_from_deno_module( .resolve_package_subpath_from_deno_module(
pkg_folder, pkg_folder,
sub_path.as_deref(), sub_path.as_deref(),
Some(&referrer), Some(&referrer),
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
)?, )?,
) )
} }
@ -240,12 +283,12 @@ impl ModuleLoader for EmbeddedModuleLoader {
if let Ok(reference) = if let Ok(reference) =
NpmPackageReqReference::from_specifier(&specifier) NpmPackageReqReference::from_specifier(&specifier)
{ {
return self.shared.node_resolver.resolve_req_reference( return Ok(self.shared.npm_req_resolver.resolve_req_reference(
&reference, &reference,
&referrer, &referrer,
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
); )?);
} }
if specifier.scheme() == "jsr" { if specifier.scheme() == "jsr" {
@ -260,18 +303,18 @@ impl ModuleLoader for EmbeddedModuleLoader {
self self
.shared .shared
.node_resolver .node_resolver
.handle_if_in_node_modules(&specifier)? .handle_if_in_node_modules(&specifier)
.unwrap_or(specifier), .unwrap_or(specifier),
) )
} }
Err(err) Err(err)
if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" => if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" =>
{ {
let maybe_res = self.shared.node_resolver.resolve_if_for_npm_pkg( let maybe_res = self.shared.npm_req_resolver.resolve_if_for_npm_pkg(
raw_specifier, raw_specifier,
&referrer, &referrer,
referrer_kind, referrer_kind,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
)?; )?;
if let Some(res) = maybe_res { if let Some(res) = maybe_res {
return Ok(res.into_url()); return Ok(res.into_url());
@ -325,14 +368,19 @@ impl ModuleLoader for EmbeddedModuleLoader {
} }
if self.shared.node_resolver.in_npm_package(original_specifier) { if self.shared.node_resolver.in_npm_package(original_specifier) {
let npm_module_loader = self.shared.npm_module_loader.clone(); let shared = self.shared.clone();
let original_specifier = original_specifier.clone(); let original_specifier = original_specifier.clone();
let maybe_referrer = maybe_referrer.cloned(); let maybe_referrer = maybe_referrer.cloned();
return deno_core::ModuleLoadResponse::Async( return deno_core::ModuleLoadResponse::Async(
async move { async move {
let code_source = npm_module_loader let code_source = shared
.npm_module_loader
.load(&original_specifier, maybe_referrer.as_ref()) .load(&original_specifier, maybe_referrer.as_ref())
.await?; .await?;
let code_cache_entry = shared.get_code_cache(
&code_source.found_url,
code_source.code.as_bytes(),
);
Ok(deno_core::ModuleSource::new_with_redirect( Ok(deno_core::ModuleSource::new_with_redirect(
match code_source.media_type { match code_source.media_type {
MediaType::Json => ModuleType::Json, MediaType::Json => ModuleType::Json,
@ -341,7 +389,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
code_source.code, code_source.code,
&original_specifier, &original_specifier,
&code_source.found_url, &code_source.found_url,
None, code_cache_entry,
)) ))
} }
.boxed_local(), .boxed_local(),
@ -394,25 +442,30 @@ impl ModuleLoader for EmbeddedModuleLoader {
ModuleSourceCode::String(FastString::from_static(source)) ModuleSourceCode::String(FastString::from_static(source))
} }
}; };
let code_cache_entry = shared
.get_code_cache(&module_specifier, module_source.as_bytes());
Ok(deno_core::ModuleSource::new_with_redirect( Ok(deno_core::ModuleSource::new_with_redirect(
module_type, module_type,
module_source, module_source,
&original_specifier, &original_specifier,
&module_specifier, &module_specifier,
None, code_cache_entry,
)) ))
} }
.boxed_local(), .boxed_local(),
) )
} else { } else {
let module_source = module_source.into_for_v8(); let module_source = module_source.into_for_v8();
let code_cache_entry = self
.shared
.get_code_cache(module_specifier, module_source.as_bytes());
deno_core::ModuleLoadResponse::Sync(Ok( deno_core::ModuleLoadResponse::Sync(Ok(
deno_core::ModuleSource::new_with_redirect( deno_core::ModuleSource::new_with_redirect(
module_type, module_type,
module_source, module_source,
original_specifier, original_specifier,
module_specifier, module_specifier,
None, code_cache_entry,
), ),
)) ))
} }
@ -425,6 +478,23 @@ impl ModuleLoader for EmbeddedModuleLoader {
))), ))),
} }
} }
fn code_cache_ready(
&self,
specifier: ModuleSpecifier,
source_hash: u64,
code_cache_data: &[u8],
) -> LocalBoxFuture<'static, ()> {
if let Some(code_cache) = &self.shared.code_cache {
code_cache.set_sync(
specifier,
deno_runtime::code_cache::CodeCacheType::EsModule,
source_hash,
code_cache_data,
);
}
std::future::ready(()).boxed_local()
}
} }
impl NodeRequireLoader for EmbeddedModuleLoader { impl NodeRequireLoader for EmbeddedModuleLoader {
@ -447,8 +517,13 @@ impl NodeRequireLoader for EmbeddedModuleLoader {
fn load_text_file_lossy( fn load_text_file_lossy(
&self, &self,
path: &std::path::Path, path: &std::path::Path,
) -> Result<String, AnyError> { ) -> Result<Cow<'static, str>, AnyError> {
Ok(self.shared.fs.read_text_file_lossy_sync(path, None)?) let file_entry = self.shared.vfs.file_entry(path)?;
let file_bytes = self
.shared
.vfs
.read_file_all(file_entry, VfsFileSubDataKind::ModuleGraph)?;
Ok(from_utf8_lossy_cow(file_bytes))
} }
fn is_maybe_cjs( fn is_maybe_cjs(
@ -651,25 +726,30 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
let node_resolver = Arc::new(NodeResolver::new( let node_resolver = Arc::new(NodeResolver::new(
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
in_npm_pkg_checker.clone(), in_npm_pkg_checker.clone(),
npm_resolver.clone().into_npm_resolver(), npm_resolver.clone().into_npm_pkg_folder_resolver(),
pkg_json_resolver.clone(), pkg_json_resolver.clone(),
)); ));
let cjs_tracker = Arc::new(CjsTracker::new( let cjs_tracker = Arc::new(CjsTracker::new(
in_npm_pkg_checker.clone(), in_npm_pkg_checker.clone(),
pkg_json_resolver.clone(), pkg_json_resolver.clone(),
IsCjsResolverOptions { if metadata.unstable_config.detect_cjs {
detect_cjs: !metadata.workspace_resolver.package_jsons.is_empty(), IsCjsResolutionMode::ImplicitTypeCommonJs
is_node_main: false, } else if metadata.workspace_resolver.package_jsons.is_empty() {
IsCjsResolutionMode::Disabled
} else {
IsCjsResolutionMode::ExplicitTypeCommonJs
}, },
)); ));
let cache_db = Caches::new(deno_dir_provider.clone()); let cache_db = Caches::new(deno_dir_provider.clone());
let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db()); let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db());
let cli_node_resolver = Arc::new(CliNodeResolver::new( let npm_req_resolver =
fs.clone(), Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions {
in_npm_pkg_checker.clone(), byonm_resolver: (npm_resolver.clone()).into_maybe_byonm(),
node_resolver.clone(), fs: CliDenoResolverFs(fs.clone()),
npm_resolver.clone(), in_npm_pkg_checker: in_npm_pkg_checker.clone(),
)); node_resolver: node_resolver.clone(),
npm_req_resolver: npm_resolver.clone().into_npm_req_resolver(),
}));
let cjs_esm_code_analyzer = CliCjsCodeAnalyzer::new( let cjs_esm_code_analyzer = CliCjsCodeAnalyzer::new(
node_analysis_cache, node_analysis_cache,
cjs_tracker.clone(), cjs_tracker.clone(),
@ -681,7 +761,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()),
in_npm_pkg_checker, in_npm_pkg_checker,
node_resolver.clone(), node_resolver.clone(),
npm_resolver.clone().into_npm_resolver(), npm_resolver.clone().into_npm_pkg_folder_resolver(),
pkg_json_resolver.clone(), pkg_json_resolver.clone(),
)); ));
let workspace_resolver = { let workspace_resolver = {
@ -733,19 +813,35 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
metadata.workspace_resolver.pkg_json_resolution, metadata.workspace_resolver.pkg_json_resolution,
) )
}; };
let code_cache = match metadata.code_cache_key {
Some(code_cache_key) => Some(Arc::new(DenoCompileCodeCache::new(
root_path.with_file_name(format!(
"{}.cache",
root_path.file_name().unwrap().to_string_lossy()
)),
code_cache_key,
)) as Arc<dyn CliCodeCache>),
None => {
log::debug!("Code cache disabled.");
None
}
};
let module_loader_factory = StandaloneModuleLoaderFactory { let module_loader_factory = StandaloneModuleLoaderFactory {
shared: Arc::new(SharedModuleLoaderState { shared: Arc::new(SharedModuleLoaderState {
cjs_tracker: cjs_tracker.clone(), cjs_tracker: cjs_tracker.clone(),
code_cache: code_cache.clone(),
fs: fs.clone(), fs: fs.clone(),
modules, modules,
node_code_translator: node_code_translator.clone(), node_code_translator: node_code_translator.clone(),
node_resolver: cli_node_resolver.clone(), node_resolver: node_resolver.clone(),
npm_module_loader: Arc::new(NpmModuleLoader::new( npm_module_loader: Arc::new(NpmModuleLoader::new(
cjs_tracker.clone(), cjs_tracker.clone(),
fs.clone(), fs.clone(),
node_code_translator, node_code_translator,
)), )),
npm_resolver: npm_resolver.clone(), npm_resolver: npm_resolver.clone(),
npm_req_resolver,
vfs,
workspace_resolver, workspace_resolver,
}), }),
}; };
@ -785,8 +881,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
}); });
let worker_factory = CliMainWorkerFactory::new( let worker_factory = CliMainWorkerFactory::new(
Arc::new(BlobStore::default()), Arc::new(BlobStore::default()),
// Code cache is not supported for standalone binary yet. code_cache,
None,
feature_checker, feature_checker,
fs, fs,
None, None,

View file

@ -32,6 +32,15 @@ use thiserror::Error;
use crate::util; use crate::util;
use crate::util::fs::canonicalize_path; use crate::util::fs::canonicalize_path;
#[derive(Debug, Copy, Clone)]
pub enum VfsFileSubDataKind {
/// Raw bytes of the file.
Raw,
/// Bytes to use for module loading. For example, for TypeScript
/// files this will be the transpiled JavaScript source.
ModuleGraph,
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error( #[error(
"Failed to strip prefix '{}' from '{}'", root_path.display(), target.display() "Failed to strip prefix '{}' from '{}'", root_path.display(), target.display()
@ -51,7 +60,8 @@ pub struct VfsBuilder {
impl VfsBuilder { impl VfsBuilder {
pub fn new(root_path: PathBuf) -> Result<Self, AnyError> { pub fn new(root_path: PathBuf) -> Result<Self, AnyError> {
let root_path = canonicalize_path(&root_path)?; let root_path = canonicalize_path(&root_path)
.with_context(|| format!("Canonicalizing {}", root_path.display()))?;
log::debug!("Building vfs with root '{}'", root_path.display()); log::debug!("Building vfs with root '{}'", root_path.display());
Ok(Self { Ok(Self {
root_dir: VirtualDirectory { root_dir: VirtualDirectory {
@ -140,7 +150,11 @@ impl VfsBuilder {
// inline the symlink and make the target file // inline the symlink and make the target file
let file_bytes = std::fs::read(&target) let file_bytes = std::fs::read(&target)
.with_context(|| format!("Reading {}", path.display()))?; .with_context(|| format!("Reading {}", path.display()))?;
self.add_file_with_data_inner(&path, file_bytes)?; self.add_file_with_data_inner(
&path,
file_bytes,
VfsFileSubDataKind::Raw,
)?;
} else { } else {
log::warn!( log::warn!(
"{} Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'.", "{} Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'.",
@ -218,25 +232,27 @@ impl VfsBuilder {
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let file_bytes = std::fs::read(path) let file_bytes = std::fs::read(path)
.with_context(|| format!("Reading {}", path.display()))?; .with_context(|| format!("Reading {}", path.display()))?;
self.add_file_with_data_inner(path, file_bytes) self.add_file_with_data_inner(path, file_bytes, VfsFileSubDataKind::Raw)
} }
pub fn add_file_with_data( pub fn add_file_with_data(
&mut self, &mut self,
path: &Path, path: &Path,
data: Vec<u8>, data: Vec<u8>,
sub_data_kind: VfsFileSubDataKind,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let target_path = canonicalize_path(path)?; let target_path = canonicalize_path(path)?;
if target_path != path { if target_path != path {
self.add_symlink(path, &target_path)?; self.add_symlink(path, &target_path)?;
} }
self.add_file_with_data_inner(&target_path, data) self.add_file_with_data_inner(&target_path, data, sub_data_kind)
} }
fn add_file_with_data_inner( fn add_file_with_data_inner(
&mut self, &mut self,
path: &Path, path: &Path,
data: Vec<u8>, data: Vec<u8>,
sub_data_kind: VfsFileSubDataKind,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
log::debug!("Adding file '{}'", path.display()); log::debug!("Adding file '{}'", path.display());
let checksum = util::checksum::gen(&[&data]); let checksum = util::checksum::gen(&[&data]);
@ -252,8 +268,19 @@ impl VfsBuilder {
let name = path.file_name().unwrap().to_string_lossy(); let name = path.file_name().unwrap().to_string_lossy();
let data_len = data.len(); let data_len = data.len();
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search_by(|e| e.name().cmp(&name)) {
Ok(_) => { Ok(index) => {
// already added, just ignore let entry = &mut dir.entries[index];
match entry {
VfsEntry::File(virtual_file) => match sub_data_kind {
VfsFileSubDataKind::Raw => {
virtual_file.offset = offset;
}
VfsFileSubDataKind::ModuleGraph => {
virtual_file.module_graph_offset = offset;
}
},
VfsEntry::Dir(_) | VfsEntry::Symlink(_) => unreachable!(),
}
} }
Err(insert_index) => { Err(insert_index) => {
dir.entries.insert( dir.entries.insert(
@ -261,6 +288,7 @@ impl VfsBuilder {
VfsEntry::File(VirtualFile { VfsEntry::File(VirtualFile {
name: name.to_string(), name: name.to_string(),
offset, offset,
module_graph_offset: offset,
len: data.len() as u64, len: data.len() as u64,
}), }),
); );
@ -301,7 +329,7 @@ impl VfsBuilder {
let dir = self.add_dir(path.parent().unwrap())?; let dir = self.add_dir(path.parent().unwrap())?;
let name = path.file_name().unwrap().to_string_lossy(); let name = path.file_name().unwrap().to_string_lossy();
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search_by(|e| e.name().cmp(&name)) {
Ok(_) => unreachable!(), Ok(_) => Ok(()), // previously inserted
Err(insert_index) => { Err(insert_index) => {
dir.entries.insert( dir.entries.insert(
insert_index, insert_index,
@ -313,9 +341,9 @@ impl VfsBuilder {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
}), }),
); );
Ok(())
} }
} }
Ok(())
} }
pub fn into_dir_and_files(self) -> (VirtualDirectory, Vec<Vec<u8>>) { pub fn into_dir_and_files(self) -> (VirtualDirectory, Vec<Vec<u8>>) {
@ -453,6 +481,12 @@ pub struct VirtualDirectory {
pub struct VirtualFile { pub struct VirtualFile {
pub name: String, pub name: String,
pub offset: u64, pub offset: u64,
/// Offset file to use for module loading when it differs from the
/// raw file. Often this will be the same offset as above for data
/// such as JavaScript files, but for TypeScript files the `offset`
/// will be the original raw bytes when included as an asset and this
/// offset will be to the transpiled JavaScript source.
pub module_graph_offset: u64,
pub len: u64, pub len: u64,
} }
@ -633,7 +667,7 @@ impl FileBackedVfsFile {
} }
fn read_to_buf(&self, buf: &mut [u8]) -> FsResult<usize> { fn read_to_buf(&self, buf: &mut [u8]) -> FsResult<usize> {
let pos = { let read_pos = {
let mut pos = self.pos.lock(); let mut pos = self.pos.lock();
let read_pos = *pos; let read_pos = *pos;
// advance the position due to the read // advance the position due to the read
@ -642,12 +676,12 @@ impl FileBackedVfsFile {
}; };
self self
.vfs .vfs
.read_file(&self.file, pos, buf) .read_file(&self.file, read_pos, buf)
.map_err(|err| err.into()) .map_err(|err| err.into())
} }
fn read_to_end(&self) -> FsResult<Vec<u8>> { fn read_to_end(&self) -> FsResult<Cow<'static, [u8]>> {
let pos = { let read_pos = {
let mut pos = self.pos.lock(); let mut pos = self.pos.lock();
let read_pos = *pos; let read_pos = *pos;
// todo(dsherret): should this always set it to the end of the file? // todo(dsherret): should this always set it to the end of the file?
@ -657,13 +691,21 @@ impl FileBackedVfsFile {
} }
read_pos read_pos
}; };
if pos > self.file.len { if read_pos > self.file.len {
return Ok(Vec::new()); return Ok(Cow::Borrowed(&[]));
}
if read_pos == 0 {
Ok(
self
.vfs
.read_file_all(&self.file, VfsFileSubDataKind::Raw)?,
)
} else {
let size = (self.file.len - read_pos) as usize;
let mut buf = vec![0; size];
self.vfs.read_file(&self.file, read_pos, &mut buf)?;
Ok(Cow::Owned(buf))
} }
let size = (self.file.len - pos) as usize;
let mut buf = vec![0; size];
self.vfs.read_file(&self.file, pos, &mut buf)?;
Ok(buf)
} }
} }
@ -701,10 +743,10 @@ impl deno_io::fs::File for FileBackedVfsFile {
Err(FsError::NotSupported) Err(FsError::NotSupported)
} }
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> { fn read_all_sync(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
self.read_to_end() self.read_to_end()
} }
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> { async fn read_all_async(self: Rc<Self>) -> FsResult<Cow<'static, [u8]>> {
let inner = (*self).clone(); let inner = (*self).clone();
tokio::task::spawn_blocking(move || inner.read_to_end()).await? tokio::task::spawn_blocking(move || inner.read_to_end()).await?
} }
@ -877,8 +919,9 @@ impl FileBackedVfs {
pub fn read_file_all( pub fn read_file_all(
&self, &self,
file: &VirtualFile, file: &VirtualFile,
sub_data_kind: VfsFileSubDataKind,
) -> std::io::Result<Cow<'static, [u8]>> { ) -> std::io::Result<Cow<'static, [u8]>> {
let read_range = self.get_read_range(file, 0, file.len)?; let read_range = self.get_read_range(file, sub_data_kind, 0, file.len)?;
match &self.vfs_data { match &self.vfs_data {
Cow::Borrowed(data) => Ok(Cow::Borrowed(&data[read_range])), Cow::Borrowed(data) => Ok(Cow::Borrowed(&data[read_range])),
Cow::Owned(data) => Ok(Cow::Owned(data[read_range].to_vec())), Cow::Owned(data) => Ok(Cow::Owned(data[read_range].to_vec())),
@ -891,26 +934,37 @@ impl FileBackedVfs {
pos: u64, pos: u64,
buf: &mut [u8], buf: &mut [u8],
) -> std::io::Result<usize> { ) -> std::io::Result<usize> {
let read_range = self.get_read_range(file, pos, buf.len() as u64)?; let read_range = self.get_read_range(
buf.copy_from_slice(&self.vfs_data[read_range]); file,
Ok(buf.len()) VfsFileSubDataKind::Raw,
pos,
buf.len() as u64,
)?;
let read_len = read_range.len();
buf[..read_len].copy_from_slice(&self.vfs_data[read_range]);
Ok(read_len)
} }
fn get_read_range( fn get_read_range(
&self, &self,
file: &VirtualFile, file: &VirtualFile,
sub_data_kind: VfsFileSubDataKind,
pos: u64, pos: u64,
len: u64, len: u64,
) -> std::io::Result<Range<usize>> { ) -> std::io::Result<Range<usize>> {
let data = &self.vfs_data; if pos > file.len {
let start = self.fs_root.start_file_offset + file.offset + pos;
let end = start + len;
if end > data.len() as u64 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"unexpected EOF", "unexpected EOF",
)); ));
} }
let offset = match sub_data_kind {
VfsFileSubDataKind::Raw => file.offset,
VfsFileSubDataKind::ModuleGraph => file.module_graph_offset,
};
let file_offset = self.fs_root.start_file_offset + offset;
let start = file_offset + pos;
let end = file_offset + std::cmp::min(pos + len, file.len);
Ok(start as usize..end as usize) Ok(start as usize..end as usize)
} }
@ -949,7 +1003,13 @@ mod test {
#[track_caller] #[track_caller]
fn read_file(vfs: &FileBackedVfs, path: &Path) -> String { fn read_file(vfs: &FileBackedVfs, path: &Path) -> String {
let file = vfs.file_entry(path).unwrap(); let file = vfs.file_entry(path).unwrap();
String::from_utf8(vfs.read_file_all(file).unwrap().into_owned()).unwrap() String::from_utf8(
vfs
.read_file_all(file, VfsFileSubDataKind::Raw)
.unwrap()
.into_owned(),
)
.unwrap()
} }
#[test] #[test]
@ -962,23 +1022,40 @@ mod test {
let src_path = src_path.to_path_buf(); let src_path = src_path.to_path_buf();
let mut builder = VfsBuilder::new(src_path.clone()).unwrap(); let mut builder = VfsBuilder::new(src_path.clone()).unwrap();
builder builder
.add_file_with_data_inner(&src_path.join("a.txt"), "data".into()) .add_file_with_data_inner(
&src_path.join("a.txt"),
"data".into(),
VfsFileSubDataKind::Raw,
)
.unwrap(); .unwrap();
builder builder
.add_file_with_data_inner(&src_path.join("b.txt"), "data".into()) .add_file_with_data_inner(
&src_path.join("b.txt"),
"data".into(),
VfsFileSubDataKind::Raw,
)
.unwrap(); .unwrap();
assert_eq!(builder.files.len(), 1); // because duplicate data assert_eq!(builder.files.len(), 1); // because duplicate data
builder builder
.add_file_with_data_inner(&src_path.join("c.txt"), "c".into()) .add_file_with_data_inner(
&src_path.join("c.txt"),
"c".into(),
VfsFileSubDataKind::Raw,
)
.unwrap(); .unwrap();
builder builder
.add_file_with_data_inner( .add_file_with_data_inner(
&src_path.join("sub_dir").join("d.txt"), &src_path.join("sub_dir").join("d.txt"),
"d".into(), "d".into(),
VfsFileSubDataKind::Raw,
) )
.unwrap(); .unwrap();
builder builder
.add_file_with_data_inner(&src_path.join("e.txt"), "e".into()) .add_file_with_data_inner(
&src_path.join("e.txt"),
"e".into(),
VfsFileSubDataKind::Raw,
)
.unwrap(); .unwrap();
builder builder
.add_symlink( .add_symlink(
@ -1149,6 +1226,7 @@ mod test {
.add_file_with_data_inner( .add_file_with_data_inner(
temp_path.join("a.txt").as_path(), temp_path.join("a.txt").as_path(),
"0123456789".to_string().into_bytes(), "0123456789".to_string().into_bytes(),
VfsFileSubDataKind::Raw,
) )
.unwrap(); .unwrap();
let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir); let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir);

View file

@ -14,6 +14,7 @@ use deno_runtime::deno_node::NodeResolver;
use deno_semver::package::PackageNv; use deno_semver::package::PackageNv;
use deno_task_shell::ExecutableCommand; use deno_task_shell::ExecutableCommand;
use deno_task_shell::ExecuteResult; use deno_task_shell::ExecuteResult;
use deno_task_shell::KillSignal;
use deno_task_shell::ShellCommand; use deno_task_shell::ShellCommand;
use deno_task_shell::ShellCommandContext; use deno_task_shell::ShellCommandContext;
use deno_task_shell::ShellPipeReader; use deno_task_shell::ShellPipeReader;
@ -22,6 +23,7 @@ use lazy_regex::Lazy;
use regex::Regex; use regex::Regex;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tokio::task::LocalSet; use tokio::task::LocalSet;
use tokio_util::sync::CancellationToken;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::npm::InnerCliNpmResolverRef; use crate::npm::InnerCliNpmResolverRef;
@ -45,9 +47,11 @@ impl TaskStdio {
pub fn stdout() -> Self { pub fn stdout() -> Self {
Self(None, ShellPipeWriter::stdout()) Self(None, ShellPipeWriter::stdout())
} }
pub fn stderr() -> Self { pub fn stderr() -> Self {
Self(None, ShellPipeWriter::stderr()) Self(None, ShellPipeWriter::stderr())
} }
pub fn piped() -> Self { pub fn piped() -> Self {
let (r, w) = deno_task_shell::pipe(); let (r, w) = deno_task_shell::pipe();
Self(Some(r), w) Self(Some(r), w)
@ -62,8 +66,8 @@ pub struct TaskIo {
impl Default for TaskIo { impl Default for TaskIo {
fn default() -> Self { fn default() -> Self {
Self { Self {
stderr: TaskStdio::stderr(),
stdout: TaskStdio::stdout(), stdout: TaskStdio::stdout(),
stderr: TaskStdio::stderr(),
} }
} }
} }
@ -78,6 +82,7 @@ pub struct RunTaskOptions<'a> {
pub custom_commands: HashMap<String, Rc<dyn ShellCommand>>, pub custom_commands: HashMap<String, Rc<dyn ShellCommand>>,
pub root_node_modules_dir: Option<&'a Path>, pub root_node_modules_dir: Option<&'a Path>,
pub stdio: Option<TaskIo>, pub stdio: Option<TaskIo>,
pub kill_signal: KillSignal,
} }
pub type TaskCustomCommands = HashMap<String, Rc<dyn ShellCommand>>; pub type TaskCustomCommands = HashMap<String, Rc<dyn ShellCommand>>;
@ -96,8 +101,12 @@ pub async fn run_task(
.with_context(|| format!("Error parsing script '{}'.", opts.task_name))?; .with_context(|| format!("Error parsing script '{}'.", opts.task_name))?;
let env_vars = let env_vars =
prepare_env_vars(opts.env_vars, opts.init_cwd, opts.root_node_modules_dir); prepare_env_vars(opts.env_vars, opts.init_cwd, opts.root_node_modules_dir);
let state = let state = deno_task_shell::ShellState::new(
deno_task_shell::ShellState::new(env_vars, opts.cwd, opts.custom_commands); env_vars,
opts.cwd,
opts.custom_commands,
opts.kill_signal,
);
let stdio = opts.stdio.unwrap_or_default(); let stdio = opts.stdio.unwrap_or_default();
let ( let (
TaskStdio(stdout_read, stdout_write), TaskStdio(stdout_read, stdout_write),
@ -483,20 +492,32 @@ fn resolve_execution_path_from_npx_shim(
static SCRIPT_PATH_RE: Lazy<Regex> = static SCRIPT_PATH_RE: Lazy<Regex> =
lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#); lazy_regex::lazy_regex!(r#""\$basedir\/([^"]+)" "\$@""#);
if text.starts_with("#!/usr/bin/env node") { let maybe_first_line = {
// launch this file itself because it's a JS file let index = text.find("\n")?;
Some(file_path) Some(&text[0..index])
} else { };
// Search for...
// > "$basedir/../next/dist/bin/next" "$@" if let Some(first_line) = maybe_first_line {
// ...which is what it will look like on Windows // NOTE(bartlomieju): this is not perfect, but handle two most common scenarios
SCRIPT_PATH_RE // where Node is run without any args. If there are args then we use `NodeCommand`
.captures(text) // struct.
.and_then(|c| c.get(1)) if first_line == "#!/usr/bin/env node"
.map(|relative_path| { || first_line == "#!/usr/bin/env -S node"
file_path.parent().unwrap().join(relative_path.as_str()) {
}) // launch this file itself because it's a JS file
return Some(file_path);
}
} }
// Search for...
// > "$basedir/../next/dist/bin/next" "$@"
// ...which is what it will look like on Windows
SCRIPT_PATH_RE
.captures(text)
.and_then(|c| c.get(1))
.map(|relative_path| {
file_path.parent().unwrap().join(relative_path.as_str())
})
} }
fn resolve_managed_npm_commands( fn resolve_managed_npm_commands(
@ -525,6 +546,80 @@ fn resolve_managed_npm_commands(
Ok(result) Ok(result)
} }
/// Runs a deno task future forwarding any signals received
/// to the process.
///
/// Signal listeners and ctrl+c listening will be setup.
pub async fn run_future_forwarding_signals<TOutput>(
kill_signal: KillSignal,
future: impl std::future::Future<Output = TOutput>,
) -> TOutput {
fn spawn_future_with_cancellation(
future: impl std::future::Future<Output = ()> + 'static,
token: CancellationToken,
) {
deno_core::unsync::spawn(async move {
tokio::select! {
_ = future => {}
_ = token.cancelled() => {}
}
});
}
let token = CancellationToken::new();
let _token_drop_guard = token.clone().drop_guard();
let _drop_guard = kill_signal.clone().drop_guard();
spawn_future_with_cancellation(
listen_ctrl_c(kill_signal.clone()),
token.clone(),
);
#[cfg(unix)]
spawn_future_with_cancellation(
listen_and_forward_all_signals(kill_signal),
token,
);
future.await
}
async fn listen_ctrl_c(kill_signal: KillSignal) {
while let Ok(()) = tokio::signal::ctrl_c().await {
kill_signal.send(deno_task_shell::SignalKind::SIGINT)
}
}
#[cfg(unix)]
async fn listen_and_forward_all_signals(kill_signal: KillSignal) {
use deno_core::futures::FutureExt;
use deno_runtime::signal::SIGNAL_NUMS;
// listen and forward every signal we support
let mut futures = Vec::with_capacity(SIGNAL_NUMS.len());
for signo in SIGNAL_NUMS.iter().copied() {
if signo == libc::SIGKILL || signo == libc::SIGSTOP {
continue; // skip, can't listen to these
}
let kill_signal = kill_signal.clone();
futures.push(
async move {
let Ok(mut stream) = tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::from_raw(signo),
) else {
return;
};
let signal_kind: deno_task_shell::SignalKind = signo.into();
while let Some(()) = stream.recv().await {
kill_signal.send(signal_kind);
}
}
.boxed_local(),
)
}
futures::future::join_all(futures).await;
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -564,6 +659,16 @@ mod test {
let unix_shim = r#"#!/usr/bin/env node let unix_shim = r#"#!/usr/bin/env node
"use strict"; "use strict";
console.log('Hi!'); console.log('Hi!');
"#;
let path = PathBuf::from("/node_modules/.bin/example");
assert_eq!(
resolve_execution_path_from_npx_shim(path.clone(), unix_shim).unwrap(),
path
);
// example shim on unix
let unix_shim = r#"#!/usr/bin/env -S node
"use strict";
console.log('Hi!');
"#; "#;
let path = PathBuf::from("/node_modules/.bin/example"); let path = PathBuf::from("/node_modules/.bin/example");
assert_eq!( assert_eq!(

View file

@ -486,6 +486,7 @@ pub async fn run_benchmarks_with_watch(
), ),
move |flags, watcher_communicator, changed_paths| { move |flags, watcher_communicator, changed_paths| {
let bench_flags = bench_flags.clone(); let bench_flags = bench_flags.clone();
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags_for_watcher( let factory = CliFactory::from_flags_for_watcher(
flags, flags,

View file

@ -380,6 +380,11 @@ fn get_check_hash(
hasher.write_str(module.specifier.as_str()); hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source); hasher.write_str(&module.source);
} }
Module::Wasm(module) => {
has_file_to_type_check = true;
hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source_dts);
}
Module::External(module) => { Module::External(module) => {
hasher.write_str(module.specifier.as_str()); hasher.write_str(module.specifier.as_str());
} }
@ -437,6 +442,7 @@ fn get_tsc_roots(
| MediaType::SourceMap | MediaType::SourceMap
| MediaType::Unknown => None, | MediaType::Unknown => None,
}, },
Module::Wasm(module) => Some((module.specifier.clone(), MediaType::Dmts)),
Module::External(_) Module::External(_)
| Module::Node(_) | Module::Node(_)
| Module::Npm(_) | Module::Npm(_)

View file

@ -7,6 +7,7 @@ use crate::factory::CliFactory;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
use crate::standalone::binary::StandaloneRelativeFileBaseUrl; use crate::standalone::binary::StandaloneRelativeFileBaseUrl;
use crate::standalone::is_standalone_binary; use crate::standalone::is_standalone_binary;
use deno_ast::MediaType;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
@ -31,15 +32,12 @@ pub async fn compile(
let module_graph_creator = factory.module_graph_creator().await?; let module_graph_creator = factory.module_graph_creator().await?;
let binary_writer = factory.create_compile_binary_writer().await?; let binary_writer = factory.create_compile_binary_writer().await?;
let http_client = factory.http_client_provider(); let http_client = factory.http_client_provider();
let module_specifier = cli_options.resolve_main_module()?; let entrypoint = cli_options.resolve_main_module()?;
let module_roots = { let (module_roots, include_files) = get_module_roots_and_include_files(
let mut vec = Vec::with_capacity(compile_flags.include.len() + 1); entrypoint,
vec.push(module_specifier.clone()); &compile_flags,
for side_module in &compile_flags.include { cli_options.initial_cwd(),
vec.push(resolve_url_or_path(side_module, cli_options.initial_cwd())?); )?;
}
vec
};
// this is not supported, so show a warning about it, but don't error in order // this is not supported, so show a warning about it, but don't error in order
// to allow someone to still run `deno compile` when this is in a deno.json // to allow someone to still run `deno compile` when this is in a deno.json
@ -82,18 +80,22 @@ pub async fn compile(
check_warn_tsconfig(&ts_config_for_emit); check_warn_tsconfig(&ts_config_for_emit);
let root_dir_url = resolve_root_dir_from_specifiers( let root_dir_url = resolve_root_dir_from_specifiers(
cli_options.workspace().root_dir(), cli_options.workspace().root_dir(),
graph.specifiers().map(|(s, _)| s).chain( graph
cli_options .specifiers()
.node_modules_dir_path() .map(|(s, _)| s)
.and_then(|p| ModuleSpecifier::from_directory_path(p).ok()) .chain(
.iter(), cli_options
), .node_modules_dir_path()
.and_then(|p| ModuleSpecifier::from_directory_path(p).ok())
.iter(),
)
.chain(include_files.iter()),
); );
log::debug!("Binary root dir: {}", root_dir_url); log::debug!("Binary root dir: {}", root_dir_url);
log::info!( log::info!(
"{} {} to {}", "{} {} to {}",
colors::green("Compile"), colors::green("Compile"),
module_specifier.to_string(), entrypoint,
output_path.display(), output_path.display(),
); );
validate_output_path(&output_path)?; validate_output_path(&output_path)?;
@ -118,9 +120,9 @@ pub async fn compile(
file, file,
&graph, &graph,
StandaloneRelativeFileBaseUrl::from(&root_dir_url), StandaloneRelativeFileBaseUrl::from(&root_dir_url),
module_specifier, entrypoint,
&include_files,
&compile_flags, &compile_flags,
cli_options,
) )
.await .await
.with_context(|| { .with_context(|| {
@ -212,6 +214,48 @@ fn validate_output_path(output_path: &Path) -> Result<(), AnyError> {
Ok(()) Ok(())
} }
fn get_module_roots_and_include_files(
entrypoint: &ModuleSpecifier,
compile_flags: &CompileFlags,
initial_cwd: &Path,
) -> Result<(Vec<ModuleSpecifier>, Vec<ModuleSpecifier>), AnyError> {
fn is_module_graph_module(url: &ModuleSpecifier) -> bool {
if url.scheme() != "file" {
return true;
}
let media_type = MediaType::from_specifier(url);
match media_type {
MediaType::JavaScript
| MediaType::Jsx
| MediaType::Mjs
| MediaType::Cjs
| MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx
| MediaType::Json
| MediaType::Wasm => true,
MediaType::Css | MediaType::SourceMap | MediaType::Unknown => false,
}
}
let mut module_roots = Vec::with_capacity(compile_flags.include.len() + 1);
let mut include_files = Vec::with_capacity(compile_flags.include.len());
module_roots.push(entrypoint.clone());
for side_module in &compile_flags.include {
let url = resolve_url_or_path(side_module, initial_cwd)?;
if is_module_graph_module(&url) {
module_roots.push(url);
} else {
include_files.push(url);
}
}
Ok((module_roots, include_files))
}
async fn resolve_compile_executable_output_path( async fn resolve_compile_executable_output_path(
http_client_provider: &HttpClientProvider, http_client_provider: &HttpClientProvider,
compile_flags: &CompileFlags, compile_flags: &CompileFlags,

View file

@ -21,6 +21,8 @@ use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_doc as doc; use deno_doc as doc;
use deno_doc::html::UrlResolveKind; use deno_doc::html::UrlResolveKind;
use deno_doc::html::UsageComposer;
use deno_doc::html::UsageComposerEntry;
use deno_graph::source::NullFileSystem; use deno_graph::source::NullFileSystem;
use deno_graph::EsParser; use deno_graph::EsParser;
use deno_graph::GraphKind; use deno_graph::GraphKind;
@ -35,6 +37,9 @@ use std::sync::Arc;
const JSON_SCHEMA_VERSION: u8 = 1; const JSON_SCHEMA_VERSION: u8 = 1;
const PRISM_CSS: &str = include_str!("./doc/prism.css");
const PRISM_JS: &str = include_str!("./doc/prism.js");
async fn generate_doc_nodes_for_builtin_types( async fn generate_doc_nodes_for_builtin_types(
doc_flags: DocFlags, doc_flags: DocFlags,
parser: &dyn EsParser, parser: &dyn EsParser,
@ -204,10 +209,14 @@ pub async fn doc(
Default::default() Default::default()
}; };
let mut main_entrypoint = None;
let rewrite_map = let rewrite_map =
if let Some(config_file) = cli_options.start_dir.maybe_deno_json() { if let Some(config_file) = cli_options.start_dir.maybe_deno_json() {
let config = config_file.to_exports_config()?; let config = config_file.to_exports_config()?;
main_entrypoint = config.get_resolved(".").ok().flatten();
let rewrite_map = config let rewrite_map = config
.clone() .clone()
.into_map() .into_map()
@ -235,6 +244,7 @@ pub async fn doc(
html_options, html_options,
deno_ns, deno_ns,
rewrite_map, rewrite_map,
main_entrypoint,
) )
} else { } else {
let modules_len = doc_nodes_by_url.len(); let modules_len = doc_nodes_by_url.len();
@ -312,10 +322,6 @@ impl deno_doc::html::HrefResolver for DocResolver {
None None
} }
fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option<String> {
current_resolve.get_file().map(|file| file.path.to_string())
}
fn resolve_source(&self, location: &deno_doc::Location) -> Option<String> { fn resolve_source(&self, location: &deno_doc::Location) -> Option<String> {
Some(location.filename.to_string()) Some(location.filename.to_string())
} }
@ -350,105 +356,30 @@ impl deno_doc::html::HrefResolver for DocResolver {
} }
} }
struct DenoDocResolver(bool); struct DocComposer;
impl deno_doc::html::HrefResolver for DenoDocResolver { impl UsageComposer for DocComposer {
fn resolve_path( fn is_single_mode(&self) -> bool {
true
}
fn compose(
&self, &self,
current: UrlResolveKind, current_resolve: UrlResolveKind,
target: UrlResolveKind, usage_to_md: deno_doc::html::UsageToMd,
) -> String { ) -> IndexMap<UsageComposerEntry, String> {
let path = deno_doc::html::href_path_resolve(current, target);
if self.0 {
if let Some(path) = path
.strip_suffix("index.html")
.or_else(|| path.strip_suffix(".html"))
{
return path.to_owned();
}
}
path
}
fn resolve_global_symbol(&self, _symbol: &[String]) -> Option<String> {
None
}
fn resolve_import_href(
&self,
_symbol: &[String],
_src: &str,
) -> Option<String> {
None
}
fn resolve_usage(&self, _current_resolve: UrlResolveKind) -> Option<String> {
None
}
fn resolve_source(&self, _location: &deno_doc::Location) -> Option<String> {
None
}
fn resolve_external_jsdoc_module(
&self,
_module: &str,
_symbol: Option<&str>,
) -> Option<(String, String)> {
None
}
}
struct NodeDocResolver(bool);
impl deno_doc::html::HrefResolver for NodeDocResolver {
fn resolve_path(
&self,
current: UrlResolveKind,
target: UrlResolveKind,
) -> String {
let path = deno_doc::html::href_path_resolve(current, target);
if self.0 {
if let Some(path) = path
.strip_suffix("index.html")
.or_else(|| path.strip_suffix(".html"))
{
return path.to_owned();
}
}
path
}
fn resolve_global_symbol(&self, _symbol: &[String]) -> Option<String> {
None
}
fn resolve_import_href(
&self,
_symbol: &[String],
_src: &str,
) -> Option<String> {
None
}
fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option<String> {
current_resolve current_resolve
.get_file() .get_file()
.map(|file| format!("node:{}", file.path)) .map(|current_file| {
} IndexMap::from([(
UsageComposerEntry {
fn resolve_source(&self, _location: &deno_doc::Location) -> Option<String> { name: "".to_string(),
None icon: None,
} },
usage_to_md(current_file.path.as_str(), None),
fn resolve_external_jsdoc_module( )])
&self, })
_module: &str, .unwrap_or_default()
_symbol: Option<&str>,
) -> Option<(String, String)> {
None
} }
} }
@ -457,34 +388,15 @@ fn generate_docs_directory(
html_options: &DocHtmlFlag, html_options: &DocHtmlFlag,
deno_ns: std::collections::HashMap<Vec<String>, Option<Rc<ShortPath>>>, deno_ns: std::collections::HashMap<Vec<String>, Option<Rc<ShortPath>>>,
rewrite_map: Option<IndexMap<ModuleSpecifier, String>>, rewrite_map: Option<IndexMap<ModuleSpecifier, String>>,
main_entrypoint: Option<ModuleSpecifier>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let cwd = std::env::current_dir().context("Failed to get CWD")?; let cwd = std::env::current_dir().context("Failed to get CWD")?;
let output_dir_resolved = cwd.join(&html_options.output); let output_dir_resolved = cwd.join(&html_options.output);
let internal_env = std::env::var("DENO_INTERNAL_HTML_DOCS").ok();
let href_resolver: Rc<dyn deno_doc::html::HrefResolver> = if internal_env
.as_ref()
.is_some_and(|internal_html_docs| internal_html_docs == "node")
{
Rc::new(NodeDocResolver(html_options.strip_trailing_html))
} else if internal_env
.as_ref()
.is_some_and(|internal_html_docs| internal_html_docs == "deno")
|| deno_ns.is_empty()
{
Rc::new(DenoDocResolver(html_options.strip_trailing_html))
} else {
Rc::new(DocResolver {
deno_ns,
strip_trailing_html: html_options.strip_trailing_html,
})
};
let category_docs = let category_docs =
if let Some(category_docs_path) = &html_options.category_docs_path { if let Some(category_docs_path) = &html_options.category_docs_path {
let content = std::fs::read(category_docs_path)?; let content = std::fs::read(category_docs_path)?;
Some(deno_core::serde_json::from_slice(&content)?) Some(serde_json::from_slice(&content)?)
} else { } else {
None None
}; };
@ -493,7 +405,7 @@ fn generate_docs_directory(
&html_options.symbol_redirect_map_path &html_options.symbol_redirect_map_path
{ {
let content = std::fs::read(symbol_redirect_map_path)?; let content = std::fs::read(symbol_redirect_map_path)?;
Some(deno_core::serde_json::from_slice(&content)?) Some(serde_json::from_slice(&content)?)
} else { } else {
None None
}; };
@ -502,26 +414,42 @@ fn generate_docs_directory(
&html_options.default_symbol_map_path &html_options.default_symbol_map_path
{ {
let content = std::fs::read(default_symbol_map_path)?; let content = std::fs::read(default_symbol_map_path)?;
Some(deno_core::serde_json::from_slice(&content)?) Some(serde_json::from_slice(&content)?)
} else { } else {
None None
}; };
let options = deno_doc::html::GenerateOptions { let options = deno_doc::html::GenerateOptions {
package_name: html_options.name.clone(), package_name: html_options.name.clone(),
main_entrypoint: None, main_entrypoint,
rewrite_map, rewrite_map,
href_resolver, href_resolver: Rc::new(DocResolver {
usage_composer: None, deno_ns,
strip_trailing_html: html_options.strip_trailing_html,
}),
usage_composer: Rc::new(DocComposer),
category_docs, category_docs,
disable_search: internal_env.is_some(), disable_search: false,
symbol_redirect_map, symbol_redirect_map,
default_symbol_map, default_symbol_map,
markdown_renderer: deno_doc::html::comrak::create_renderer(
None, None, None,
),
markdown_stripper: Rc::new(deno_doc::html::comrak::strip),
head_inject: Some(Rc::new(|root| {
format!(
r#"<link href="{root}{}" rel="stylesheet" /><link href="{root}prism.css" rel="stylesheet" /><script src="{root}prism.js"></script>"#,
deno_doc::html::comrak::COMRAK_STYLESHEET_FILENAME
)
})),
}; };
let files = deno_doc::html::generate(options, doc_nodes_by_url) let mut files = deno_doc::html::generate(options, doc_nodes_by_url)
.context("Failed to generate HTML documentation")?; .context("Failed to generate HTML documentation")?;
files.insert("prism.js".to_string(), PRISM_JS.to_string());
files.insert("prism.css".to_string(), PRISM_CSS.to_string());
let path = &output_dir_resolved; let path = &output_dir_resolved;
let _ = std::fs::remove_dir_all(path); let _ = std::fs::remove_dir_all(path);
std::fs::create_dir(path) std::fs::create_dir(path)

3
cli/tools/doc/prism.css Normal file
View file

@ -0,0 +1,3 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+json+markdown+regex+rust+typescript */
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}

15
cli/tools/doc/prism.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -83,6 +83,7 @@ pub async fn format(
file_watcher::PrintConfig::new("Fmt", !watch_flags.no_clear_screen), file_watcher::PrintConfig::new("Fmt", !watch_flags.no_clear_screen),
move |flags, watcher_communicator, changed_paths| { move |flags, watcher_communicator, changed_paths| {
let fmt_flags = fmt_flags.clone(); let fmt_flags = fmt_flags.clone();
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags(flags); let factory = CliFactory::from_flags(flags);
let cli_options = factory.cli_options()?; let cli_options = factory.cli_options()?;
@ -227,6 +228,7 @@ fn collect_fmt_files(
}) })
.ignore_git_folder() .ignore_git_folder()
.ignore_node_modules() .ignore_node_modules()
.use_gitignore()
.set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned)) .set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned))
.collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files) .collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files)
} }
@ -270,6 +272,7 @@ fn format_markdown(
| "njk" | "njk"
| "yml" | "yml"
| "yaml" | "yaml"
| "sql"
) { ) {
// It's important to tell dprint proper file extension, otherwise // It's important to tell dprint proper file extension, otherwise
// it might parse the file twice. // it might parse the file twice.
@ -299,6 +302,13 @@ fn format_markdown(
} }
} }
"yml" | "yaml" => format_yaml(text, fmt_options), "yml" | "yaml" => format_yaml(text, fmt_options),
"sql" => {
if unstable_options.sql {
format_sql(text, fmt_options)
} else {
Ok(None)
}
}
_ => { _ => {
let mut codeblock_config = let mut codeblock_config =
get_resolved_typescript_config(fmt_options); get_resolved_typescript_config(fmt_options);
@ -501,7 +511,52 @@ pub fn format_html(
}) })
} }
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file. pub fn format_sql(
file_text: &str,
fmt_options: &FmtOptionsConfig,
) -> Result<Option<String>, AnyError> {
let ignore_file = file_text
.lines()
.take_while(|line| line.starts_with("--"))
.any(|line| {
line
.strip_prefix("--")
.unwrap()
.trim()
.starts_with("deno-fmt-ignore-file")
});
if ignore_file {
return Ok(None);
}
let mut formatted_str = sqlformat::format(
file_text,
&sqlformat::QueryParams::None,
&sqlformat::FormatOptions {
ignore_case_convert: None,
indent: if fmt_options.use_tabs.unwrap_or_default() {
sqlformat::Indent::Tabs
} else {
sqlformat::Indent::Spaces(fmt_options.indent_width.unwrap_or(2))
},
// leave one blank line between queries.
lines_between_queries: 2,
uppercase: Some(true),
},
);
// Add single new line to the end of file.
formatted_str.push('\n');
Ok(if formatted_str == file_text {
None
} else {
Some(formatted_str)
})
}
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, IPYNB or SQL file.
pub fn format_file( pub fn format_file(
file_path: &Path, file_path: &Path,
file_text: &str, file_text: &str,
@ -536,6 +591,13 @@ pub fn format_file(
format_file(file_path, &file_text, fmt_options, unstable_options, None) format_file(file_path, &file_text, fmt_options, unstable_options, None)
}, },
), ),
"sql" => {
if unstable_options.sql {
format_sql(file_text, fmt_options)
} else {
Ok(None)
}
}
_ => { _ => {
let config = get_resolved_typescript_config(fmt_options); let config = get_resolved_typescript_config(fmt_options);
dprint_plugin_typescript::format_text( dprint_plugin_typescript::format_text(
@ -1207,6 +1269,7 @@ fn is_supported_ext_fmt(path: &Path) -> bool {
| "yml" | "yml"
| "yaml" | "yaml"
| "ipynb" | "ipynb"
| "sql"
) )
}) })
} }
@ -1267,6 +1330,11 @@ mod test {
assert!(is_supported_ext_fmt(Path::new("foo.yaml"))); assert!(is_supported_ext_fmt(Path::new("foo.yaml")));
assert!(is_supported_ext_fmt(Path::new("foo.YaML"))); assert!(is_supported_ext_fmt(Path::new("foo.YaML")));
assert!(is_supported_ext_fmt(Path::new("foo.ipynb"))); assert!(is_supported_ext_fmt(Path::new("foo.ipynb")));
assert!(is_supported_ext_fmt(Path::new("foo.sql")));
assert!(is_supported_ext_fmt(Path::new("foo.Sql")));
assert!(is_supported_ext_fmt(Path::new("foo.sQl")));
assert!(is_supported_ext_fmt(Path::new("foo.sqL")));
assert!(is_supported_ext_fmt(Path::new("foo.SQL")));
} }
#[test] #[test]

View file

@ -49,19 +49,67 @@ pub async fn info(
let module_graph_creator = factory.module_graph_creator().await?; let module_graph_creator = factory.module_graph_creator().await?;
let npm_resolver = factory.npm_resolver().await?; let npm_resolver = factory.npm_resolver().await?;
let maybe_lockfile = cli_options.maybe_lockfile(); let maybe_lockfile = cli_options.maybe_lockfile();
let resolver = factory.workspace_resolver().await?.clone();
let npmrc = cli_options.npmrc(); let npmrc = cli_options.npmrc();
let resolver = factory.workspace_resolver().await?; let node_resolver = factory.node_resolver().await?;
let cwd_url = let cwd_url =
url::Url::from_directory_path(cli_options.initial_cwd()).unwrap(); url::Url::from_directory_path(cli_options.initial_cwd()).unwrap();
let maybe_import_specifier = if let Some(import_map) = let maybe_import_specifier = if let Ok(resolved) =
resolver.maybe_import_map() resolver.resolve(&specifier, &cwd_url)
{ {
if let Ok(imports_specifier) = import_map.resolve(&specifier, &cwd_url) { match resolved {
Some(imports_specifier) deno_config::workspace::MappedResolution::Normal {
} else { specifier, ..
None }
| deno_config::workspace::MappedResolution::ImportMap {
specifier,
..
}
| deno_config::workspace::MappedResolution::WorkspaceJsrPackage {
specifier,
..
} => Some(specifier),
deno_config::workspace::MappedResolution::WorkspaceNpmPackage {
target_pkg_json,
sub_path,
..
} => Some(node_resolver.resolve_package_subpath_from_deno_module(
target_pkg_json.clone().dir_path(),
sub_path.as_deref(),
Some(&cwd_url),
node_resolver::ResolutionMode::Import,
node_resolver::NodeResolutionKind::Execution,
)?),
deno_config::workspace::MappedResolution::PackageJson {
alias,
sub_path,
dep_result,
..
} => match dep_result.as_ref().map_err(|e| e.clone())? {
deno_package_json::PackageJsonDepValue::Workspace(version_req) => {
let pkg_folder = resolver
.resolve_workspace_pkg_json_folder_for_pkg_json_dep(
alias,
version_req,
)?;
Some(node_resolver.resolve_package_subpath_from_deno_module(
pkg_folder,
sub_path.as_deref(),
Some(&cwd_url),
node_resolver::ResolutionMode::Import,
node_resolver::NodeResolutionKind::Execution,
)?)
}
deno_package_json::PackageJsonDepValue::Req(req) => {
Some(ModuleSpecifier::parse(&format!(
"npm:{}{}",
req,
sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
))?)
}
},
} }
} else { } else {
None None
@ -126,6 +174,7 @@ fn print_cache_info(
let registry_cache = dir.registries_folder_path(); let registry_cache = dir.registries_folder_path();
let mut origin_dir = dir.origin_data_folder_path(); let mut origin_dir = dir.origin_data_folder_path();
let deno_dir = dir.root_path_for_display().to_string(); let deno_dir = dir.root_path_for_display().to_string();
let web_cache_dir = crate::worker::get_cache_storage_dir();
if let Some(location) = &location { if let Some(location) = &location {
origin_dir = origin_dir =
@ -143,6 +192,7 @@ fn print_cache_info(
"typescriptCache": typescript_cache, "typescriptCache": typescript_cache,
"registryCache": registry_cache, "registryCache": registry_cache,
"originStorage": origin_dir, "originStorage": origin_dir,
"webCacheStorage": web_cache_dir,
}); });
if location.is_some() { if location.is_some() {
@ -177,6 +227,11 @@ fn print_cache_info(
colors::bold("Origin storage:"), colors::bold("Origin storage:"),
origin_dir.display() origin_dir.display()
); );
println!(
"{} {}",
colors::bold("Web cache storage:"),
web_cache_dir.display()
);
if location.is_some() { if location.is_some() {
println!( println!(
"{} {}", "{} {}",
@ -228,22 +283,31 @@ fn add_npm_packages_to_json(
.get_mut("dependencies") .get_mut("dependencies")
.and_then(|d| d.as_array_mut()); .and_then(|d| d.as_array_mut());
if let Some(dependencies) = dependencies { if let Some(dependencies) = dependencies {
for dep in dependencies.iter_mut() { for dep in dependencies.iter_mut().flat_map(|d| d.as_object_mut()) {
if let serde_json::Value::Object(dep) = dep { if let Some(specifier) = dep.get("specifier").and_then(|s| s.as_str())
let specifier = dep.get("specifier").and_then(|s| s.as_str()); {
if let Some(specifier) = specifier { if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) {
if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) { if let Ok(pkg) = snapshot.resolve_pkg_from_pkg_req(npm_ref.req())
if let Ok(pkg) = {
snapshot.resolve_pkg_from_pkg_req(npm_ref.req()) dep.insert(
{ "npmPackage".to_string(),
dep.insert( pkg.id.as_serialized().into(),
"npmPackage".to_string(), );
pkg.id.as_serialized().into(),
);
}
} }
} }
} }
// don't show this in the output unless someone needs it
if let Some(code) =
dep.get_mut("code").and_then(|c| c.as_object_mut())
{
code.remove("resolutionMode");
}
if let Some(types) =
dep.get_mut("types").and_then(|c| c.as_object_mut())
{
types.remove("resolutionMode");
}
} }
} }
} }
@ -446,6 +510,7 @@ impl<'a> GraphDisplayContext<'a> {
let maybe_cache_info = match root { let maybe_cache_info = match root {
Module::Js(module) => module.maybe_cache_info.as_ref(), Module::Js(module) => module.maybe_cache_info.as_ref(),
Module::Json(module) => module.maybe_cache_info.as_ref(), Module::Json(module) => module.maybe_cache_info.as_ref(),
Module::Wasm(module) => module.maybe_cache_info.as_ref(),
Module::Node(_) | Module::Npm(_) | Module::External(_) => None, Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
}; };
if let Some(cache_info) = maybe_cache_info { if let Some(cache_info) = maybe_cache_info {
@ -468,6 +533,7 @@ impl<'a> GraphDisplayContext<'a> {
let size = match m { let size = match m {
Module::Js(module) => module.size(), Module::Js(module) => module.size(),
Module::Json(module) => module.size(), Module::Json(module) => module.size(),
Module::Wasm(module) => module.size(),
Module::Node(_) | Module::Npm(_) | Module::External(_) => 0, Module::Node(_) | Module::Npm(_) | Module::External(_) => 0,
}; };
size as f64 size as f64
@ -567,6 +633,7 @@ impl<'a> GraphDisplayContext<'a> {
Specifier(_) => match module { Specifier(_) => match module {
Module::Js(module) => Some(module.size() as u64), Module::Js(module) => Some(module.size() as u64),
Module::Json(module) => Some(module.size() as u64), Module::Json(module) => Some(module.size() as u64),
Module::Wasm(module) => Some(module.size() as u64),
Module::Node(_) | Module::Npm(_) | Module::External(_) => None, Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
}, },
}; };
@ -580,8 +647,8 @@ impl<'a> GraphDisplayContext<'a> {
Package(package) => { Package(package) => {
tree_node.children.extend(self.build_npm_deps(package)); tree_node.children.extend(self.build_npm_deps(package));
} }
Specifier(_) => { Specifier(_) => match module {
if let Some(module) = module.js() { Module::Js(module) => {
if let Some(types_dep) = &module.maybe_types_dependency { if let Some(types_dep) = &module.maybe_types_dependency {
if let Some(child) = if let Some(child) =
self.build_resolved_info(&types_dep.dependency, true) self.build_resolved_info(&types_dep.dependency, true)
@ -593,7 +660,16 @@ impl<'a> GraphDisplayContext<'a> {
tree_node.children.extend(self.build_dep_info(dep)); tree_node.children.extend(self.build_dep_info(dep));
} }
} }
} Module::Wasm(module) => {
for dep in module.dependencies.values() {
tree_node.children.extend(self.build_dep_info(dep));
}
}
Module::Json(_)
| Module::Npm(_)
| Module::Node(_)
| Module::External(_) => {}
},
} }
} }
tree_node tree_node
@ -658,7 +734,7 @@ impl<'a> GraphDisplayContext<'a> {
}; };
self.build_error_msg(specifier, message.as_ref()) self.build_error_msg(specifier, message.as_ref())
} }
ModuleError::ParseErr(_, _) => { ModuleError::ParseErr(_, _) | ModuleError::WasmParseErr(_, _) => {
self.build_error_msg(specifier, "(parsing error)") self.build_error_msg(specifier, "(parsing error)")
} }
ModuleError::UnsupportedImportAttributeType { .. } => { ModuleError::UnsupportedImportAttributeType { .. } => {

View file

@ -1,15 +1,29 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::DenoSubcommand;
use crate::args::Flags;
use crate::args::InitFlags; use crate::args::InitFlags;
use crate::args::PackagesAllowedScripts;
use crate::args::PermissionFlags;
use crate::args::RunFlags;
use crate::colors; use crate::colors;
use color_print::cformat;
use color_print::cstr;
use deno_config::deno_json::NodeModulesDirMode;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_runtime::WorkerExecutionMode;
use log::info; use log::info;
use std::io::IsTerminal;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
pub fn init_project(init_flags: InitFlags) -> Result<(), AnyError> { pub async fn init_project(init_flags: InitFlags) -> Result<i32, AnyError> {
if let Some(package) = &init_flags.package {
return init_npm(package, init_flags.package_args).await;
}
let cwd = let cwd =
std::env::current_dir().context("Can't read current working directory.")?; std::env::current_dir().context("Can't read current working directory.")?;
let dir = if let Some(dir) = &init_flags.dir { let dir = if let Some(dir) = &init_flags.dir {
@ -235,7 +249,97 @@ Deno.test(function addTest() {
info!(" {}", colors::gray("# Run the tests")); info!(" {}", colors::gray("# Run the tests"));
info!(" deno test"); info!(" deno test");
} }
Ok(()) Ok(0)
}
fn npm_name_to_create_package(name: &str) -> String {
let mut s = "npm:".to_string();
let mut scoped = false;
let mut create = false;
for (i, ch) in name.char_indices() {
if i == 0 {
if ch == '@' {
scoped = true;
} else {
create = true;
s.push_str("create-");
}
} else if scoped {
if ch == '/' {
scoped = false;
create = true;
s.push_str("/create-");
continue;
} else if ch == '@' && !create {
scoped = false;
create = true;
s.push_str("/create@");
continue;
}
}
s.push(ch);
}
if !create {
s.push_str("/create");
}
s
}
async fn init_npm(name: &str, args: Vec<String>) -> Result<i32, AnyError> {
let script_name = npm_name_to_create_package(name);
fn print_manual_usage(script_name: &str, args: &[String]) -> i32 {
log::info!("{}", cformat!("You can initialize project manually by running <u>deno run {} {}</> and applying desired permissions.", script_name, args.join(" ")));
1
}
if std::io::stdin().is_terminal() {
log::info!(
cstr!("⚠️ Do you fully trust <y>{}</> package? Deno will invoke code from it with all permissions. Do you want to continue? <p(245)>[y/n]</>"),
script_name
);
loop {
let _ = std::io::stdout().write(b"> ")?;
std::io::stdout().flush()?;
let mut answer = String::new();
if std::io::stdin().read_line(&mut answer).is_ok() {
let answer = answer.trim().to_ascii_lowercase();
if answer != "y" {
return Ok(print_manual_usage(&script_name, &args));
} else {
break;
}
}
}
} else {
return Ok(print_manual_usage(&script_name, &args));
}
let new_flags = Flags {
permissions: PermissionFlags {
allow_all: true,
..Default::default()
},
allow_scripts: PackagesAllowedScripts::All,
argv: args,
node_modules_dir: Some(NodeModulesDirMode::Auto),
subcommand: DenoSubcommand::Run(RunFlags {
script: script_name,
..Default::default()
}),
..Default::default()
};
crate::tools::run::run_script(
WorkerExecutionMode::Run,
new_flags.into(),
None,
)
.await
} }
fn create_json_file( fn create_json_file(
@ -270,3 +374,37 @@ fn create_file(
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod test {
use crate::tools::init::npm_name_to_create_package;
#[test]
fn npm_name_to_create_package_test() {
// See https://docs.npmjs.com/cli/v8/commands/npm-init#description
assert_eq!(
npm_name_to_create_package("foo"),
"npm:create-foo".to_string()
);
assert_eq!(
npm_name_to_create_package("foo@1.0.0"),
"npm:create-foo@1.0.0".to_string()
);
assert_eq!(
npm_name_to_create_package("@foo"),
"npm:@foo/create".to_string()
);
assert_eq!(
npm_name_to_create_package("@foo@1.0.0"),
"npm:@foo/create@1.0.0".to_string()
);
assert_eq!(
npm_name_to_create_package("@foo/bar"),
"npm:@foo/create-bar".to_string()
);
assert_eq!(
npm_name_to_create_package("@foo/bar@1.0.0"),
"npm:@foo/create-bar@1.0.0".to_string()
);
}
}

View file

@ -3,6 +3,7 @@
use crate::args::resolve_no_prompt; use crate::args::resolve_no_prompt;
use crate::args::AddFlags; use crate::args::AddFlags;
use crate::args::CaData; use crate::args::CaData;
use crate::args::CacheSetting;
use crate::args::ConfigFlag; use crate::args::ConfigFlag;
use crate::args::Flags; use crate::args::Flags;
use crate::args::InstallFlags; use crate::args::InstallFlags;
@ -13,8 +14,11 @@ use crate::args::TypeCheckMode;
use crate::args::UninstallFlags; use crate::args::UninstallFlags;
use crate::args::UninstallKind; use crate::args::UninstallKind;
use crate::factory::CliFactory; use crate::factory::CliFactory;
use crate::file_fetcher::FileFetcher;
use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphContainer;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver;
use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::fs::canonicalize_path_maybe_not_exists;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
@ -354,12 +358,54 @@ async fn install_global(
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// ensure the module is cached // ensure the module is cached
let factory = CliFactory::from_flags(flags.clone()); let factory = CliFactory::from_flags(flags.clone());
let cli_options = factory.cli_options()?;
let http_client = factory.http_client_provider();
let deps_http_cache = factory.global_http_cache()?;
let mut deps_file_fetcher = FileFetcher::new(
deps_http_cache.clone(),
CacheSetting::ReloadAll,
true,
http_client.clone(),
Default::default(),
None,
);
let npmrc = factory.cli_options().unwrap().npmrc();
deps_file_fetcher.set_download_log_level(log::Level::Trace);
let deps_file_fetcher = Arc::new(deps_file_fetcher);
let jsr_resolver = Arc::new(JsrFetchResolver::new(deps_file_fetcher.clone()));
let npm_resolver = Arc::new(NpmFetchResolver::new(
deps_file_fetcher.clone(),
npmrc.clone(),
));
let entry_text = install_flags_global.module_url.as_str();
if !cli_options.initial_cwd().join(entry_text).exists() {
// check for package requirement missing prefix
if let Ok(Err(package_req)) =
super::registry::AddRmPackageReq::parse(entry_text)
{
if jsr_resolver.req_to_nv(&package_req).await.is_some() {
bail!(
"{entry_text} is missing a prefix. Did you mean `{}`?",
crate::colors::yellow(format!("deno install -g jsr:{package_req}"))
);
} else if npm_resolver.req_to_nv(&package_req).await.is_some() {
bail!(
"{entry_text} is missing a prefix. Did you mean `{}`?",
crate::colors::yellow(format!("deno install -g npm:{package_req}"))
);
}
}
}
factory factory
.main_module_graph_container() .main_module_graph_container()
.await? .await?
.load_and_type_check_files(&[install_flags_global.module_url.clone()]) .load_and_type_check_files(&[install_flags_global.module_url.clone()])
.await?; .await?;
let http_client = factory.http_client_provider();
// create the install shim // create the install shim
create_install_shim(http_client, &flags, install_flags_global).await create_install_shim(http_client, &flags, install_flags_global).await

View file

@ -80,6 +80,7 @@ pub async fn lint(
file_watcher::PrintConfig::new("Lint", !watch_flags.no_clear_screen), file_watcher::PrintConfig::new("Lint", !watch_flags.no_clear_screen),
move |flags, watcher_communicator, changed_paths| { move |flags, watcher_communicator, changed_paths| {
let lint_flags = lint_flags.clone(); let lint_flags = lint_flags.clone();
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags(flags); let factory = CliFactory::from_flags(flags);
let cli_options = factory.cli_options()?; let cli_options = factory.cli_options()?;
@ -191,7 +192,7 @@ pub async fn lint(
linter.finish() linter.finish()
}; };
if !success { if !success {
std::process::exit(1); deno_runtime::exit(1);
} }
} }
@ -439,6 +440,7 @@ fn collect_lint_files(
}) })
.ignore_git_folder() .ignore_git_folder()
.ignore_node_modules() .ignore_node_modules()
.use_gitignore()
.set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned)) .set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned))
.collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files) .collect_file_patterns(&deno_config::fs::RealDenoConfigFs, files)
} }

View file

@ -175,6 +175,7 @@ struct JsonLintReporter {
version: u8, version: u8,
diagnostics: Vec<JsonLintDiagnostic>, diagnostics: Vec<JsonLintDiagnostic>,
errors: Vec<LintError>, errors: Vec<LintError>,
checked_files: Vec<String>,
} }
impl JsonLintReporter { impl JsonLintReporter {
@ -183,6 +184,7 @@ impl JsonLintReporter {
version: JSON_SCHEMA_VERSION, version: JSON_SCHEMA_VERSION,
diagnostics: Vec::new(), diagnostics: Vec::new(),
errors: Vec::new(), errors: Vec::new(),
checked_files: Vec::new(),
} }
} }
} }
@ -209,6 +211,17 @@ impl LintReporter for JsonLintReporter {
code: d.code().to_string(), code: d.code().to_string(),
hint: d.hint().map(|h| h.to_string()), hint: d.hint().map(|h| h.to_string()),
}); });
let file_path = d
.specifier
.to_file_path()
.unwrap()
.to_string_lossy()
.to_string();
if !self.checked_files.contains(&file_path) {
self.checked_files.push(file_path);
}
} }
fn visit_error(&mut self, file_path: &str, err: &AnyError) { fn visit_error(&mut self, file_path: &str, err: &AnyError) {
@ -216,10 +229,15 @@ impl LintReporter for JsonLintReporter {
file_path: file_path.to_string(), file_path: file_path.to_string(),
message: err.to_string(), message: err.to_string(),
}); });
if !self.checked_files.contains(&file_path.to_string()) {
self.checked_files.push(file_path.to_string());
}
} }
fn close(&mut self, _check_count: usize) { fn close(&mut self, _check_count: usize) {
sort_diagnostics(&mut self.diagnostics); sort_diagnostics(&mut self.diagnostics);
self.checked_files.sort();
let json = serde_json::to_string_pretty(&self); let json = serde_json::to_string_pretty(&self);
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
{ {

View file

@ -8,7 +8,7 @@ use std::sync::Arc;
use deno_ast::SourceRange; use deno_ast::SourceRange;
use deno_config::workspace::WorkspaceResolver; use deno_config::workspace::WorkspaceResolver;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_graph::source::ResolutionMode; use deno_graph::source::ResolutionKind;
use deno_graph::source::ResolveError; use deno_graph::source::ResolveError;
use deno_graph::Range; use deno_graph::Range;
use deno_lint::diagnostic::LintDiagnosticDetails; use deno_lint::diagnostic::LintDiagnosticDetails;
@ -17,7 +17,7 @@ use deno_lint::diagnostic::LintFix;
use deno_lint::diagnostic::LintFixChange; use deno_lint::diagnostic::LintFixChange;
use deno_lint::rules::LintRule; use deno_lint::rules::LintRule;
use deno_resolver::sloppy_imports::SloppyImportsResolution; use deno_resolver::sloppy_imports::SloppyImportsResolution;
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
use text_lines::LineAndColumnIndex; use text_lines::LineAndColumnIndex;
use crate::graph_util::CliJsrUrlProvider; use crate::graph_util::CliJsrUrlProvider;
@ -101,16 +101,16 @@ impl LintRule for NoSloppyImportsRule {
maybe_npm_resolver: None, maybe_npm_resolver: None,
}); });
for (range, sloppy_import) in resolver.captures.borrow_mut().drain() { for (referrer, sloppy_import) in resolver.captures.borrow_mut().drain() {
let start_range = let start_range =
context.text_info().loc_to_source_pos(LineAndColumnIndex { context.text_info().loc_to_source_pos(LineAndColumnIndex {
line_index: range.start.line, line_index: referrer.range.start.line,
column_index: range.start.character, column_index: referrer.range.start.character,
}); });
let end_range = let end_range =
context.text_info().loc_to_source_pos(LineAndColumnIndex { context.text_info().loc_to_source_pos(LineAndColumnIndex {
line_index: range.end.line, line_index: referrer.range.end.line,
column_index: range.end.character, column_index: referrer.range.end.character,
}); });
let source_range = SourceRange::new(start_range, end_range); let source_range = SourceRange::new(start_range, end_range);
context.add_diagnostic_details( context.add_diagnostic_details(
@ -183,7 +183,7 @@ impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
&self, &self,
specifier_text: &str, specifier_text: &str,
referrer_range: &Range, referrer_range: &Range,
mode: ResolutionMode, resolution_kind: ResolutionKind,
) -> Result<deno_ast::ModuleSpecifier, deno_graph::source::ResolveError> { ) -> Result<deno_ast::ModuleSpecifier, deno_graph::source::ResolveError> {
let resolution = self let resolution = self
.workspace_resolver .workspace_resolver
@ -198,9 +198,9 @@ impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
specifier, .. specifier, ..
} => match self.sloppy_imports_resolver.resolve( } => match self.sloppy_imports_resolver.resolve(
&specifier, &specifier,
match mode { match resolution_kind {
ResolutionMode::Execution => SloppyImportsResolutionMode::Execution, ResolutionKind::Execution => SloppyImportsResolutionKind::Execution,
ResolutionMode::Types => SloppyImportsResolutionMode::Types, ResolutionKind::Types => SloppyImportsResolutionKind::Types,
}, },
) { ) {
Some(res) => { Some(res) => {

View file

@ -169,7 +169,7 @@ impl Diagnostic for PublishDiagnostic {
.. ..
}) => DiagnosticLevel::Warning, }) => DiagnosticLevel::Warning,
FastCheck(_) => DiagnosticLevel::Error, FastCheck(_) => DiagnosticLevel::Error,
SpecifierUnfurl(_) => DiagnosticLevel::Warning, SpecifierUnfurl(d) => d.level(),
InvalidPath { .. } => DiagnosticLevel::Error, InvalidPath { .. } => DiagnosticLevel::Error,
DuplicatePath { .. } => DiagnosticLevel::Error, DuplicatePath { .. } => DiagnosticLevel::Error,
UnsupportedFileType { .. } => DiagnosticLevel::Warning, UnsupportedFileType { .. } => DiagnosticLevel::Warning,
@ -187,7 +187,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.code(), FastCheck(diagnostic) => diagnostic.code(),
SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.code()), SpecifierUnfurl(diagnostic) => diagnostic.code(),
InvalidPath { .. } => Cow::Borrowed("invalid-path"), InvalidPath { .. } => Cow::Borrowed("invalid-path"),
DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"), DuplicatePath { .. } => Cow::Borrowed("case-insensitive-duplicate-path"),
UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"), UnsupportedFileType { .. } => Cow::Borrowed("unsupported-file-type"),
@ -207,7 +207,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.message(), FastCheck(diagnostic) => diagnostic.message(),
SpecifierUnfurl(diagnostic) => Cow::Borrowed(diagnostic.message()), SpecifierUnfurl(diagnostic) => diagnostic.message(),
InvalidPath { message, .. } => Cow::Borrowed(message.as_str()), InvalidPath { message, .. } => Cow::Borrowed(message.as_str()),
DuplicatePath { .. } => { DuplicatePath { .. } => {
Cow::Borrowed("package path is a case insensitive duplicate of another path in the package") Cow::Borrowed("package path is a case insensitive duplicate of another path in the package")
@ -234,8 +234,8 @@ impl Diagnostic for PublishDiagnostic {
specifier: Cow::Borrowed(&referrer.specifier), specifier: Cow::Borrowed(&referrer.specifier),
text_info: Cow::Borrowed(text_info), text_info: Cow::Borrowed(text_info),
source_pos: DiagnosticSourcePos::LineAndCol { source_pos: DiagnosticSourcePos::LineAndCol {
line: referrer.start.line, line: referrer.range.start.line,
column: referrer.start.character, column: referrer.range.start.character,
}, },
} }
} }
@ -243,17 +243,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.location(), FastCheck(diagnostic) => diagnostic.location(),
SpecifierUnfurl(diagnostic) => match diagnostic { SpecifierUnfurl(diagnostic) => diagnostic.location(),
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
specifier,
text_info,
range,
} => DiagnosticLocation::ModulePosition {
specifier: Cow::Borrowed(specifier),
text_info: Cow::Borrowed(text_info),
source_pos: DiagnosticSourcePos::SourcePos(range.start),
},
},
InvalidPath { path, .. } => { InvalidPath { path, .. } => {
DiagnosticLocation::Path { path: path.clone() } DiagnosticLocation::Path { path: path.clone() }
} }
@ -300,7 +290,7 @@ impl Diagnostic for PublishDiagnostic {
text_info: &'a SourceTextInfo, text_info: &'a SourceTextInfo,
referrer: &'a deno_graph::Range, referrer: &'a deno_graph::Range,
) -> Option<DiagnosticSnippet<'a>> { ) -> Option<DiagnosticSnippet<'a>> {
if referrer.start.line == 0 && referrer.start.character == 0 { if referrer.range.start.line == 0 && referrer.range.start.character == 0 {
return None; // no range, probably a jsxImportSource import return None; // no range, probably a jsxImportSource import
} }
@ -310,12 +300,12 @@ impl Diagnostic for PublishDiagnostic {
style: DiagnosticSnippetHighlightStyle::Error, style: DiagnosticSnippetHighlightStyle::Error,
range: DiagnosticSourceRange { range: DiagnosticSourceRange {
start: DiagnosticSourcePos::LineAndCol { start: DiagnosticSourcePos::LineAndCol {
line: referrer.start.line, line: referrer.range.start.line,
column: referrer.start.character, column: referrer.range.start.character,
}, },
end: DiagnosticSourcePos::LineAndCol { end: DiagnosticSourcePos::LineAndCol {
line: referrer.end.line, line: referrer.range.end.line,
column: referrer.end.character, column: referrer.range.end.character,
}, },
}, },
description: Some("the specifier".into()), description: Some("the specifier".into()),
@ -325,24 +315,8 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.snippet(), FastCheck(d) => d.snippet(),
SpecifierUnfurl(diagnostic) => match diagnostic { SpecifierUnfurl(d) => d.snippet(),
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
text_info,
range,
..
} => Some(DiagnosticSnippet {
source: Cow::Borrowed(text_info),
highlights: vec![DiagnosticSnippetHighlight {
style: DiagnosticSnippetHighlightStyle::Warning,
range: DiagnosticSourceRange {
start: DiagnosticSourcePos::SourcePos(range.start),
end: DiagnosticSourcePos::SourcePos(range.end),
},
description: Some("the unanalyzable dynamic import".into()),
}],
}),
},
InvalidPath { .. } => None, InvalidPath { .. } => None,
DuplicatePath { .. } => None, DuplicatePath { .. } => None,
UnsupportedFileType { .. } => None, UnsupportedFileType { .. } => None,
@ -380,7 +354,7 @@ impl Diagnostic for PublishDiagnostic {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.hint(), FastCheck(diagnostic) => diagnostic.hint(),
SpecifierUnfurl(_) => None, SpecifierUnfurl(d) => d.hint(),
InvalidPath { .. } => Some( InvalidPath { .. } => Some(
Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"), Cow::Borrowed("rename or remove the file, or add it to 'publish.exclude' in the config file"),
), ),
@ -436,9 +410,9 @@ impl Diagnostic for PublishDiagnostic {
None => None, None => None,
} }
} }
SyntaxError(diagnostic) => diagnostic.snippet_fixed(), SyntaxError(d) => d.snippet_fixed(),
SpecifierUnfurl(d) => d.snippet_fixed(),
FastCheck(_) FastCheck(_)
| SpecifierUnfurl(_)
| InvalidPath { .. } | InvalidPath { .. }
| DuplicatePath { .. } | DuplicatePath { .. }
| UnsupportedFileType { .. } | UnsupportedFileType { .. }
@ -453,16 +427,8 @@ impl Diagnostic for PublishDiagnostic {
fn info(&self) -> Cow<'_, [Cow<'_, str>]> { fn info(&self) -> Cow<'_, [Cow<'_, str>]> {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => { FastCheck(d) => d.info(),
diagnostic.info() SpecifierUnfurl(d) => d.info(),
}
SpecifierUnfurl(diagnostic) => match diagnostic {
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[
Cow::Borrowed("after publishing this package, imports from the local import map / package.json do not work"),
Cow::Borrowed("dynamic imports that can not be analyzed at publish time will not be rewritten automatically"),
Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map / package.json")
]),
},
InvalidPath { .. } => Cow::Borrowed(&[ InvalidPath { .. } => Cow::Borrowed(&[
Cow::Borrowed("to portably support all platforms, including windows, the allowed characters in package paths are limited"), Cow::Borrowed("to portably support all platforms, including windows, the allowed characters in package paths are limited"),
]), ]),
@ -476,7 +442,7 @@ impl Diagnostic for PublishDiagnostic {
InvalidExternalImport { imported, .. } => Cow::Owned(vec![ InvalidExternalImport { imported, .. } => Cow::Owned(vec![
Cow::Owned(format!("the import was resolved to '{}'", imported)), Cow::Owned(format!("the import was resolved to '{}'", imported)),
Cow::Borrowed("this specifier is not allowed to be imported on jsr"), Cow::Borrowed("this specifier is not allowed to be imported on jsr"),
Cow::Borrowed("jsr only supports importing `jsr:`, `npm:`, and `data:` specifiers"), Cow::Borrowed("jsr only supports importing `jsr:`, `npm:`, `data:`, `bun:`, and `node:` specifiers"),
]), ]),
UnsupportedJsxTsx { .. } => Cow::Owned(vec![ UnsupportedJsxTsx { .. } => Cow::Owned(vec![
Cow::Borrowed("follow https://github.com/jsr-io/jsr/issues/24 for updates"), Cow::Borrowed("follow https://github.com/jsr-io/jsr/issues/24 for updates"),
@ -503,10 +469,8 @@ impl Diagnostic for PublishDiagnostic {
fn docs_url(&self) -> Option<Cow<'_, str>> { fn docs_url(&self) -> Option<Cow<'_, str>> {
use PublishDiagnostic::*; use PublishDiagnostic::*;
match &self { match &self {
FastCheck(diagnostic) => diagnostic.docs_url(), FastCheck(d) => d.docs_url(),
SpecifierUnfurl(diagnostic) => match diagnostic { SpecifierUnfurl(d) => d.docs_url(),
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => None,
},
InvalidPath { .. } => { InvalidPath { .. } => {
Some(Cow::Borrowed("https://jsr.io/go/invalid-path")) Some(Cow::Borrowed("https://jsr.io/go/invalid-path"))
} }

View file

@ -47,7 +47,7 @@ impl GraphDiagnosticsCollector {
resolution: &ResolutionResolved| { resolution: &ResolutionResolved| {
if visited.insert(resolution.specifier.clone()) { if visited.insert(resolution.specifier.clone()) {
match resolution.specifier.scheme() { match resolution.specifier.scheme() {
"file" | "data" | "node" => {} "file" | "data" | "node" | "bun" => {}
"jsr" => { "jsr" => {
skip_specifiers.insert(resolution.specifier.clone()); skip_specifiers.insert(resolution.specifier.clone());

View file

@ -12,8 +12,8 @@ use std::sync::Arc;
use base64::prelude::BASE64_STANDARD; use base64::prelude::BASE64_STANDARD;
use base64::Engine; use base64::Engine;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_config::deno_json::ConfigFile;
use deno_config::workspace::JsrPackageConfig; use deno_config::workspace::JsrPackageConfig;
use deno_config::workspace::PackageJsonDepResolution;
use deno_config::workspace::Workspace; use deno_config::workspace::Workspace;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
@ -43,8 +43,6 @@ use crate::cache::ParsedSourceCache;
use crate::factory::CliFactory; use crate::factory::CliFactory;
use crate::graph_util::ModuleGraphCreator; use crate::graph_util::ModuleGraphCreator;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
use crate::resolver::CliSloppyImportsResolver;
use crate::resolver::SloppyImportsCachedFs;
use crate::tools::check::CheckOptions; use crate::tools::check::CheckOptions;
use crate::tools::lint::collect_no_slow_type_diagnostics; use crate::tools::lint::collect_no_slow_type_diagnostics;
use crate::tools::registry::diagnostics::PublishDiagnostic; use crate::tools::registry::diagnostics::PublishDiagnostic;
@ -67,8 +65,10 @@ use auth::get_auth_method;
use auth::AuthMethod; use auth::AuthMethod;
pub use pm::add; pub use pm::add;
pub use pm::cache_top_level_deps; pub use pm::cache_top_level_deps;
pub use pm::outdated;
pub use pm::remove; pub use pm::remove;
pub use pm::AddCommandName; pub use pm::AddCommandName;
pub use pm::AddRmPackageReq;
use publish_order::PublishOrderGraph; use publish_order::PublishOrderGraph;
use unfurl::SpecifierUnfurler; use unfurl::SpecifierUnfurler;
@ -89,15 +89,15 @@ pub async fn publish(
let cli_options = cli_factory.cli_options()?; let cli_options = cli_factory.cli_options()?;
let directory_path = cli_options.initial_cwd(); let directory_path = cli_options.initial_cwd();
let publish_configs = cli_options.start_dir.jsr_packages_for_publish(); let mut publish_configs = cli_options.start_dir.jsr_packages_for_publish();
if publish_configs.is_empty() { if publish_configs.is_empty() {
match cli_options.start_dir.maybe_deno_json() { match cli_options.start_dir.maybe_deno_json() {
Some(deno_json) => { Some(deno_json) => {
debug_assert!(!deno_json.is_package()); debug_assert!(!deno_json.is_package());
bail!( if deno_json.json.name.is_none() {
"Missing 'name', 'version' and 'exports' field in '{}'.", bail!("Missing 'name' field in '{}'.", deno_json.specifier);
deno_json.specifier }
); error_missing_exports_field(deno_json)?;
} }
None => { None => {
bail!( bail!(
@ -107,20 +107,21 @@ pub async fn publish(
} }
} }
} }
if let Some(version) = &publish_flags.set_version {
if publish_configs.len() > 1 {
bail!("Cannot use --set-version when publishing a workspace. Change your cwd to an individual package instead.");
}
if let Some(publish_config) = publish_configs.get_mut(0) {
let mut config_file = publish_config.config_file.as_ref().clone();
config_file.json.version = Some(version.clone());
publish_config.config_file = Arc::new(config_file);
}
}
let specifier_unfurler = Arc::new(SpecifierUnfurler::new( let specifier_unfurler = Arc::new(SpecifierUnfurler::new(
if cli_options.unstable_sloppy_imports() { cli_factory.sloppy_imports_resolver()?.cloned(),
Some(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new( cli_factory.workspace_resolver().await?.clone(),
cli_factory.fs().clone(),
)))
} else {
None
},
cli_options
.create_workspace_resolver(
cli_factory.file_fetcher()?,
PackageJsonDepResolution::Enabled,
)
.await?,
cli_options.unstable_bare_node_builtins(), cli_options.unstable_bare_node_builtins(),
)); ));
@ -403,43 +404,15 @@ impl PublishPreparer {
graph: Arc<deno_graph::ModuleGraph>, graph: Arc<deno_graph::ModuleGraph>,
diagnostics_collector: &PublishDiagnosticsCollector, diagnostics_collector: &PublishDiagnosticsCollector,
) -> Result<Rc<PreparedPublishPackage>, AnyError> { ) -> Result<Rc<PreparedPublishPackage>, AnyError> {
static SUGGESTED_ENTRYPOINTS: [&str; 4] =
["mod.ts", "mod.js", "index.ts", "index.js"];
let deno_json = &package.config_file; let deno_json = &package.config_file;
let config_path = deno_json.specifier.to_file_path().unwrap(); let config_path = deno_json.specifier.to_file_path().unwrap();
let root_dir = config_path.parent().unwrap().to_path_buf(); let root_dir = config_path.parent().unwrap().to_path_buf();
let Some(version) = deno_json.json.version.clone() else { let version = deno_json.json.version.clone().ok_or_else(|| {
bail!("{} is missing 'version' field", deno_json.specifier); deno_core::anyhow::anyhow!(
}; "{} is missing 'version' field",
if deno_json.json.exports.is_none() { deno_json.specifier
let mut suggested_entrypoint = None; )
})?;
for entrypoint in SUGGESTED_ENTRYPOINTS {
if root_dir.join(entrypoint).exists() {
suggested_entrypoint = Some(entrypoint);
break;
}
}
let exports_content = format!(
r#"{{
"name": "{}",
"version": "{}",
"exports": "{}"
}}"#,
package.name,
version,
suggested_entrypoint.unwrap_or("<path_to_entrypoint>")
);
bail!(
"You did not specify an entrypoint to \"{}\" package in {}. Add `exports` mapping in the configuration file, eg:\n{}",
package.name,
deno_json.specifier,
exports_content
);
}
let Some(name_no_at) = package.name.strip_prefix('@') else { let Some(name_no_at) = package.name.strip_prefix('@') else {
bail!("Invalid package name, use '@<scope_name>/<package_name> format"); bail!("Invalid package name, use '@<scope_name>/<package_name> format");
}; };
@ -1106,9 +1079,9 @@ fn collect_excluded_module_diagnostics(
let graph_specifiers = graph let graph_specifiers = graph
.modules() .modules()
.filter_map(|m| match m { .filter_map(|m| match m {
deno_graph::Module::Js(_) | deno_graph::Module::Json(_) => { deno_graph::Module::Js(_)
Some(m.specifier()) | deno_graph::Module::Json(_)
} | deno_graph::Module::Wasm(_) => Some(m.specifier()),
deno_graph::Module::Npm(_) deno_graph::Module::Npm(_)
| deno_graph::Module::Node(_) | deno_graph::Module::Node(_)
| deno_graph::Module::External(_) => None, | deno_graph::Module::External(_) => None,
@ -1271,6 +1244,36 @@ fn has_license_file<'a>(
}) })
} }
fn error_missing_exports_field(deno_json: &ConfigFile) -> Result<(), AnyError> {
static SUGGESTED_ENTRYPOINTS: [&str; 4] =
["mod.ts", "mod.js", "index.ts", "index.js"];
let mut suggested_entrypoint = None;
for entrypoint in SUGGESTED_ENTRYPOINTS {
if deno_json.dir_path().join(entrypoint).exists() {
suggested_entrypoint = Some(entrypoint);
break;
}
}
let exports_content = format!(
r#"{{
"name": "{}",
"version": "{}",
"exports": "{}"
}}"#,
deno_json.json.name.as_deref().unwrap_or("@scope/name"),
deno_json.json.name.as_deref().unwrap_or("0.0.0"),
suggested_entrypoint.unwrap_or("<path_to_entrypoint>")
);
bail!(
"You did not specify an entrypoint in {}. Add `exports` mapping in the configuration file, eg:\n{}",
deno_json.specifier,
exports_content
);
}
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
fn ring_bell() { fn ring_bell() {
// ASCII code for the bell character. // ASCII code for the bell character.

View file

@ -16,6 +16,7 @@ use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use deno_semver::Version; use deno_semver::Version;
use deno_semver::VersionReq; use deno_semver::VersionReq;
use deps::KeyPath;
use jsonc_parser::cst::CstObject; use jsonc_parser::cst::CstObject;
use jsonc_parser::cst::CstObjectProp; use jsonc_parser::cst::CstObjectProp;
use jsonc_parser::cst::CstRootNode; use jsonc_parser::cst::CstRootNode;
@ -32,10 +33,13 @@ use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver; use crate::npm::NpmFetchResolver;
mod cache_deps; mod cache_deps;
pub(crate) mod deps;
mod outdated;
pub use cache_deps::cache_top_level_deps; pub use cache_deps::cache_top_level_deps;
pub use outdated::outdated;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, Hash)]
enum ConfigKind { enum ConfigKind {
DenoJson, DenoJson,
PackageJson, PackageJson,
@ -86,6 +90,28 @@ impl ConfigUpdater {
self.cst.to_string() self.cst.to_string()
} }
fn get_property_for_mutation(
&mut self,
key_path: &KeyPath,
) -> Option<CstObjectProp> {
let mut current_node = self.root_object.clone();
self.modified = true;
for (i, part) in key_path.parts.iter().enumerate() {
let s = part.as_str();
if i < key_path.parts.len().saturating_sub(1) {
let object = current_node.object_value(s)?;
current_node = object;
} else {
// last part
return current_node.get(s);
}
}
None
}
fn add(&mut self, selected: SelectedPackage, dev: bool) { fn add(&mut self, selected: SelectedPackage, dev: bool) {
fn insert_index(object: &CstObject, searching_name: &str) -> usize { fn insert_index(object: &CstObject, searching_name: &str) -> usize {
object object
@ -679,7 +705,7 @@ enum AddRmPackageReqValue {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct AddRmPackageReq { pub struct AddRmPackageReq {
alias: String, alias: String,
value: AddRmPackageReqValue, value: AddRmPackageReqValue,
} }
@ -824,7 +850,7 @@ async fn npm_install_after_modification(
flags: Arc<Flags>, flags: Arc<Flags>,
// explicitly provided to prevent redownloading // explicitly provided to prevent redownloading
jsr_resolver: Option<Arc<crate::jsr::JsrFetchResolver>>, jsr_resolver: Option<Arc<crate::jsr::JsrFetchResolver>>,
) -> Result<(), AnyError> { ) -> Result<CliFactory, AnyError> {
// clear the previously cached package.json from memory before reloading it // clear the previously cached package.json from memory before reloading it
node_resolver::PackageJsonThreadLocalCache::clear(); node_resolver::PackageJsonThreadLocalCache::clear();
@ -842,7 +868,7 @@ async fn npm_install_after_modification(
lockfile.write_if_changed()?; lockfile.write_if_changed()?;
} }
Ok(()) Ok(cli_factory)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -8,7 +8,7 @@ use crate::graph_container::ModuleGraphUpdatePermit;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::stream::FuturesUnordered; use deno_core::futures::stream::FuturesUnordered;
use deno_core::futures::StreamExt; use deno_core::futures::StreamExt;
use deno_semver::package::PackageReq; use deno_semver::jsr::JsrPackageReqReference;
pub async fn cache_top_level_deps( pub async fn cache_top_level_deps(
// todo(dsherret): don't pass the factory into this function. Instead use ctor deps // todo(dsherret): don't pass the factory into this function. Instead use ctor deps
@ -56,15 +56,20 @@ pub async fn cache_top_level_deps(
match specifier.scheme() { match specifier.scheme() {
"jsr" => { "jsr" => {
let specifier_str = specifier.as_str(); let specifier_str = specifier.as_str();
let specifier_str = if let Ok(req) = JsrPackageReqReference::from_str(specifier_str) {
specifier_str.strip_prefix("jsr:").unwrap_or(specifier_str); if let Some(sub_path) = req.sub_path() {
if let Ok(req) = PackageReq::from_str(specifier_str) { if sub_path.ends_with('/') {
if !seen_reqs.insert(req.clone()) { continue;
}
roots.push(specifier.clone());
continue;
}
if !seen_reqs.insert(req.req().clone()) {
continue; continue;
} }
let jsr_resolver = jsr_resolver.clone(); let jsr_resolver = jsr_resolver.clone();
info_futures.push(async move { info_futures.push(async move {
if let Some(nv) = jsr_resolver.req_to_nv(&req).await { if let Some(nv) = jsr_resolver.req_to_nv(req.req()).await {
if let Some(info) = jsr_resolver.package_version_info(&nv).await if let Some(info) = jsr_resolver.package_version_info(&nv).await
{ {
return Some((specifier.clone(), info)); return Some((specifier.clone(), info));

View file

@ -0,0 +1,898 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use deno_ast::ModuleSpecifier;
use deno_config::deno_json::ConfigFile;
use deno_config::deno_json::ConfigFileRc;
use deno_config::workspace::Workspace;
use deno_config::workspace::WorkspaceDirectory;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::futures::future::try_join;
use deno_core::futures::stream::FuturesOrdered;
use deno_core::futures::stream::FuturesUnordered;
use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt;
use deno_core::serde_json;
use deno_graph::FillFromLockfileOptions;
use deno_package_json::PackageJsonDepsMap;
use deno_package_json::PackageJsonRc;
use deno_runtime::deno_permissions::PermissionsContainer;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use deno_semver::package::PackageReqReference;
use deno_semver::VersionReq;
use import_map::ImportMap;
use import_map::ImportMapWithDiagnostics;
use import_map::SpecifierMapEntry;
use tokio::sync::Semaphore;
use crate::args::CliLockfile;
use crate::graph_container::MainModuleGraphContainer;
use crate::graph_container::ModuleGraphContainer;
use crate::graph_container::ModuleGraphUpdatePermit;
use crate::jsr::JsrFetchResolver;
use crate::module_loader::ModuleLoadPreparer;
use crate::npm::CliNpmResolver;
use crate::npm::NpmFetchResolver;
use super::ConfigUpdater;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImportMapKind {
Inline,
Outline,
}
#[derive(Clone)]
pub enum DepLocation {
DenoJson(ConfigFileRc, KeyPath, ImportMapKind),
PackageJson(PackageJsonRc, KeyPath),
}
impl DepLocation {
pub fn is_deno_json(&self) -> bool {
matches!(self, DepLocation::DenoJson(..))
}
pub fn file_path(&self) -> Cow<std::path::Path> {
match self {
DepLocation::DenoJson(arc, _, _) => {
Cow::Owned(arc.specifier.to_file_path().unwrap())
}
DepLocation::PackageJson(arc, _) => Cow::Borrowed(arc.path.as_ref()),
}
}
fn config_kind(&self) -> super::ConfigKind {
match self {
DepLocation::DenoJson(_, _, _) => super::ConfigKind::DenoJson,
DepLocation::PackageJson(_, _) => super::ConfigKind::PackageJson,
}
}
}
struct DebugAdapter<T>(T);
impl<'a> std::fmt::Debug for DebugAdapter<&'a ConfigFileRc> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConfigFile")
.field("specifier", &self.0.specifier)
.finish()
}
}
impl<'a> std::fmt::Debug for DebugAdapter<&'a PackageJsonRc> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PackageJson")
.field("path", &self.0.path)
.finish()
}
}
impl std::fmt::Debug for DepLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DepLocation::DenoJson(arc, key_path, kind) => {
let mut debug = f.debug_tuple("DenoJson");
debug
.field(&DebugAdapter(arc))
.field(key_path)
.field(kind)
.finish()
}
DepLocation::PackageJson(arc, key_path) => {
let mut debug = f.debug_tuple("PackageJson");
debug.field(&DebugAdapter(arc)).field(key_path).finish()
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DepKind {
Jsr,
Npm,
}
impl DepKind {
pub fn scheme(&self) -> &'static str {
match self {
DepKind::Npm => "npm",
DepKind::Jsr => "jsr",
}
}
}
#[derive(Clone, Debug)]
pub enum KeyPart {
Imports,
Scopes,
Dependencies,
DevDependencies,
String(String),
}
impl From<String> for KeyPart {
fn from(value: String) -> Self {
KeyPart::String(value)
}
}
impl From<PackageJsonDepKind> for KeyPart {
fn from(value: PackageJsonDepKind) -> Self {
match value {
PackageJsonDepKind::Normal => Self::Dependencies,
PackageJsonDepKind::Dev => Self::DevDependencies,
}
}
}
impl KeyPart {
pub fn as_str(&self) -> &str {
match self {
KeyPart::Imports => "imports",
KeyPart::Scopes => "scopes",
KeyPart::Dependencies => "dependencies",
KeyPart::DevDependencies => "devDependencies",
KeyPart::String(s) => s,
}
}
}
#[derive(Clone, Debug)]
pub struct KeyPath {
pub parts: Vec<KeyPart>,
}
impl KeyPath {
fn from_parts(parts: impl IntoIterator<Item = KeyPart>) -> Self {
Self {
parts: parts.into_iter().collect(),
}
}
fn last(&self) -> Option<&KeyPart> {
self.parts.last()
}
fn push(&mut self, part: KeyPart) {
self.parts.push(part)
}
}
#[derive(Clone, Debug)]
pub struct Dep {
pub req: PackageReq,
pub kind: DepKind,
pub location: DepLocation,
#[allow(dead_code)]
pub id: DepId,
#[allow(dead_code)]
pub alias: Option<String>,
}
fn import_map_entries(
import_map: &ImportMap,
) -> impl Iterator<Item = (KeyPath, SpecifierMapEntry<'_>)> {
import_map
.imports()
.entries()
.map(|entry| {
(
KeyPath::from_parts([
KeyPart::Imports,
KeyPart::String(entry.raw_key.into()),
]),
entry,
)
})
.chain(import_map.scopes().flat_map(|scope| {
let path = KeyPath::from_parts([
KeyPart::Scopes,
scope.raw_key.to_string().into(),
]);
scope.imports.entries().map(move |entry| {
let mut full_path = path.clone();
full_path.push(KeyPart::String(entry.raw_key.to_string()));
(full_path, entry)
})
}))
}
fn to_import_map_value_from_imports(
deno_json: &ConfigFile,
) -> serde_json::Value {
let mut value = serde_json::Map::with_capacity(2);
if let Some(imports) = &deno_json.json.imports {
value.insert("imports".to_string(), imports.clone());
}
if let Some(scopes) = &deno_json.json.scopes {
value.insert("scopes".to_string(), scopes.clone());
}
serde_json::Value::Object(value)
}
fn deno_json_import_map(
deno_json: &ConfigFile,
) -> Result<Option<(ImportMapWithDiagnostics, ImportMapKind)>, AnyError> {
let (value, kind) =
if deno_json.json.imports.is_some() || deno_json.json.scopes.is_some() {
(
to_import_map_value_from_imports(deno_json),
ImportMapKind::Inline,
)
} else {
match deno_json.to_import_map_path()? {
Some(path) => {
let text = std::fs::read_to_string(&path)?;
let value = serde_json::from_str(&text)?;
(value, ImportMapKind::Outline)
}
None => return Ok(None),
}
};
import_map::parse_from_value(deno_json.specifier.clone(), value)
.map_err(Into::into)
.map(|import_map| Some((import_map, kind)))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PackageJsonDepKind {
Normal,
Dev,
}
fn add_deps_from_deno_json(
deno_json: &Arc<ConfigFile>,
mut filter: impl DepFilter,
deps: &mut Vec<Dep>,
) {
let (import_map, import_map_kind) = match deno_json_import_map(deno_json) {
Ok(Some((import_map, import_map_kind))) => (import_map, import_map_kind),
Ok(None) => return,
Err(e) => {
log::warn!("failed to parse imports from {}: {e}", &deno_json.specifier);
return;
}
};
for (key_path, entry) in import_map_entries(&import_map.import_map) {
let Some(value) = entry.value else { continue };
let kind = match value.scheme() {
"npm" => DepKind::Npm,
"jsr" => DepKind::Jsr,
_ => continue,
};
let req = match parse_req_reference(value.as_str(), kind) {
Ok(req) => req.req.clone(),
Err(err) => {
log::warn!("failed to parse package req \"{}\": {err}", value.as_str());
continue;
}
};
let alias: &str = key_path.last().unwrap().as_str().trim_end_matches('/');
let alias = (alias != req.name).then(|| alias.to_string());
if !filter.should_include(alias.as_deref(), &req, kind) {
continue;
}
let id = DepId(deps.len());
deps.push(Dep {
location: DepLocation::DenoJson(
deno_json.clone(),
key_path,
import_map_kind,
),
kind,
req,
id,
alias,
});
}
}
fn add_deps_from_package_json(
package_json: &PackageJsonRc,
filter: impl DepFilter,
deps: &mut Vec<Dep>,
) {
let package_json_deps = package_json.resolve_local_package_json_deps();
fn iterate(
package_json: &PackageJsonRc,
mut filter: impl DepFilter,
package_dep_kind: PackageJsonDepKind,
package_json_deps: PackageJsonDepsMap,
deps: &mut Vec<Dep>,
) {
for (k, v) in package_json_deps {
let v = match v {
Ok(v) => v,
Err(e) => {
log::warn!("bad package json dep value: {e}");
continue;
}
};
match v {
deno_package_json::PackageJsonDepValue::Req(req) => {
let alias = k.as_str();
let alias = (alias != req.name).then(|| alias.to_string());
if !filter.should_include(alias.as_deref(), &req, DepKind::Npm) {
continue;
}
let id = DepId(deps.len());
deps.push(Dep {
id,
kind: DepKind::Npm,
location: DepLocation::PackageJson(
package_json.clone(),
KeyPath::from_parts([package_dep_kind.into(), k.into()]),
),
req,
alias,
})
}
deno_package_json::PackageJsonDepValue::Workspace(_) => continue,
}
}
}
iterate(
package_json,
filter,
PackageJsonDepKind::Normal,
package_json_deps.dependencies,
deps,
);
iterate(
package_json,
filter,
PackageJsonDepKind::Dev,
package_json_deps.dev_dependencies,
deps,
);
}
fn deps_from_workspace(
workspace: &Arc<Workspace>,
dep_filter: impl DepFilter,
) -> Result<Vec<Dep>, AnyError> {
let mut deps = Vec::with_capacity(256);
for deno_json in workspace.deno_jsons() {
add_deps_from_deno_json(deno_json, dep_filter, &mut deps);
}
for package_json in workspace.package_jsons() {
add_deps_from_package_json(package_json, dep_filter, &mut deps);
}
Ok(deps)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DepId(usize);
#[derive(Debug, Clone)]
pub enum Change {
Update(DepId, VersionReq),
}
pub trait DepFilter: Copy {
fn should_include(
&mut self,
alias: Option<&str>,
package_req: &PackageReq,
dep_kind: DepKind,
) -> bool;
}
impl<T> DepFilter for T
where
T: FnMut(Option<&str>, &PackageReq, DepKind) -> bool + Copy,
{
fn should_include<'a>(
&mut self,
alias: Option<&'a str>,
package_req: &'a PackageReq,
dep_kind: DepKind,
) -> bool {
(*self)(alias, package_req, dep_kind)
}
}
#[derive(Clone, Debug)]
pub struct PackageLatestVersion {
pub semver_compatible: Option<PackageNv>,
pub latest: Option<PackageNv>,
}
pub struct DepManager {
deps: Vec<Dep>,
resolved_versions: Vec<Option<PackageNv>>,
latest_versions: Vec<PackageLatestVersion>,
pending_changes: Vec<Change>,
dependencies_resolved: AtomicBool,
module_load_preparer: Arc<ModuleLoadPreparer>,
// TODO(nathanwhit): probably shouldn't be pub
pub(crate) jsr_fetch_resolver: Arc<JsrFetchResolver>,
pub(crate) npm_fetch_resolver: Arc<NpmFetchResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
permissions_container: PermissionsContainer,
main_module_graph_container: Arc<MainModuleGraphContainer>,
lockfile: Option<Arc<CliLockfile>>,
}
pub struct DepManagerArgs {
pub module_load_preparer: Arc<ModuleLoadPreparer>,
pub jsr_fetch_resolver: Arc<JsrFetchResolver>,
pub npm_fetch_resolver: Arc<NpmFetchResolver>,
pub npm_resolver: Arc<dyn CliNpmResolver>,
pub permissions_container: PermissionsContainer,
pub main_module_graph_container: Arc<MainModuleGraphContainer>,
pub lockfile: Option<Arc<CliLockfile>>,
}
impl DepManager {
pub fn reloaded_after_modification(self, args: DepManagerArgs) -> Self {
let mut new = Self::with_deps_args(self.deps, args);
new.latest_versions = self.latest_versions;
new
}
fn with_deps_args(deps: Vec<Dep>, args: DepManagerArgs) -> Self {
let DepManagerArgs {
module_load_preparer,
jsr_fetch_resolver,
npm_fetch_resolver,
npm_resolver,
permissions_container,
main_module_graph_container,
lockfile,
} = args;
Self {
deps,
resolved_versions: Vec::new(),
latest_versions: Vec::new(),
jsr_fetch_resolver,
dependencies_resolved: AtomicBool::new(false),
module_load_preparer,
npm_fetch_resolver,
npm_resolver,
permissions_container,
main_module_graph_container,
lockfile,
pending_changes: Vec::new(),
}
}
pub fn from_workspace_dir(
workspace_dir: &Arc<WorkspaceDirectory>,
dep_filter: impl DepFilter,
args: DepManagerArgs,
) -> Result<Self, AnyError> {
let mut deps = Vec::with_capacity(256);
if let Some(deno_json) = workspace_dir.maybe_deno_json() {
if deno_json.specifier.scheme() != "file" {
bail!("remote deno.json files are not supported");
}
let path = deno_json.specifier.to_file_path().unwrap();
if path.parent().unwrap() == workspace_dir.dir_path() {
add_deps_from_deno_json(deno_json, dep_filter, &mut deps);
}
}
if let Some(package_json) = workspace_dir.maybe_pkg_json() {
add_deps_from_package_json(package_json, dep_filter, &mut deps);
}
Ok(Self::with_deps_args(deps, args))
}
pub fn from_workspace(
workspace: &Arc<Workspace>,
dep_filter: impl DepFilter,
args: DepManagerArgs,
) -> Result<Self, AnyError> {
let deps = deps_from_workspace(workspace, dep_filter)?;
Ok(Self::with_deps_args(deps, args))
}
async fn run_dependency_resolution(&self) -> Result<(), AnyError> {
if self
.dependencies_resolved
.load(std::sync::atomic::Ordering::Relaxed)
{
return Ok(());
}
let mut graph_permit = self
.main_module_graph_container
.acquire_update_permit()
.await;
let graph = graph_permit.graph_mut();
// populate the information from the lockfile
if let Some(lockfile) = &self.lockfile {
let lockfile = lockfile.lock();
graph.fill_from_lockfile(FillFromLockfileOptions {
redirects: lockfile
.content
.redirects
.iter()
.map(|(from, to)| (from.as_str(), to.as_str())),
package_specifiers: lockfile
.content
.packages
.specifiers
.iter()
.map(|(dep, id)| (dep, id.as_str())),
});
}
let npm_resolver = self.npm_resolver.as_managed().unwrap();
if self.deps.iter().all(|dep| match dep.kind {
DepKind::Npm => {
npm_resolver.resolve_pkg_id_from_pkg_req(&dep.req).is_ok()
}
DepKind::Jsr => graph.packages.mappings().contains_key(&dep.req),
}) {
self
.dependencies_resolved
.store(true, std::sync::atomic::Ordering::Relaxed);
graph_permit.commit();
return Ok(());
}
npm_resolver.ensure_top_level_package_json_install().await?;
let mut roots = Vec::new();
let mut info_futures = FuturesUnordered::new();
for dep in &self.deps {
if dep.location.is_deno_json() {
match dep.kind {
DepKind::Npm => roots.push(
ModuleSpecifier::parse(&format!("npm:/{}/", dep.req)).unwrap(),
),
DepKind::Jsr => info_futures.push(async {
if let Some(nv) = self.jsr_fetch_resolver.req_to_nv(&dep.req).await
{
if let Some(info) =
self.jsr_fetch_resolver.package_version_info(&nv).await
{
let specifier =
ModuleSpecifier::parse(&format!("jsr:/{}/", dep.req))
.unwrap();
return Some((specifier, info));
}
}
None
}),
}
}
}
while let Some(info_future) = info_futures.next().await {
if let Some((specifier, info)) = info_future {
let exports = info.exports();
for (k, _) in exports {
if let Ok(spec) = specifier.join(k) {
roots.push(spec);
}
}
}
}
self
.module_load_preparer
.prepare_module_load(
graph,
&roots,
false,
deno_config::deno_json::TsTypeLib::DenoWindow,
self.permissions_container.clone(),
None,
)
.await?;
graph_permit.commit();
Ok(())
}
pub fn resolved_version(&self, id: DepId) -> Option<&PackageNv> {
self.resolved_versions[id.0].as_ref()
}
pub async fn resolve_current_versions(&mut self) -> Result<(), AnyError> {
self.run_dependency_resolution().await?;
let graph = self.main_module_graph_container.graph();
let mut resolved = Vec::with_capacity(self.deps.len());
let snapshot = self.npm_resolver.as_managed().unwrap().snapshot();
let resolved_npm = snapshot.package_reqs();
let resolved_jsr = graph.packages.mappings();
for dep in &self.deps {
match dep.kind {
DepKind::Npm => {
let resolved_version = resolved_npm.get(&dep.req).cloned();
resolved.push(resolved_version);
}
DepKind::Jsr => {
let resolved_version = resolved_jsr.get(&dep.req).cloned();
resolved.push(resolved_version)
}
}
}
self.resolved_versions = resolved;
Ok(())
}
async fn load_latest_versions(
&self,
) -> Result<Vec<PackageLatestVersion>, AnyError> {
if self.latest_versions.len() == self.deps.len() {
return Ok(self.latest_versions.clone());
}
let latest_tag_req = deno_semver::VersionReq::from_raw_text_and_inner(
"latest".into(),
deno_semver::RangeSetOrTag::Tag("latest".into()),
);
let mut latest_versions = Vec::with_capacity(self.deps.len());
let npm_sema = Semaphore::new(32);
let jsr_sema = Semaphore::new(32);
let mut futs = FuturesOrdered::new();
for dep in &self.deps {
match dep.kind {
DepKind::Npm => futs.push_back(
async {
let semver_req = &dep.req;
let latest_req = PackageReq {
name: dep.req.name.clone(),
version_req: latest_tag_req.clone(),
};
let _permit = npm_sema.acquire().await;
let semver_compatible =
self.npm_fetch_resolver.req_to_nv(semver_req).await;
let latest = self.npm_fetch_resolver.req_to_nv(&latest_req).await;
PackageLatestVersion {
latest,
semver_compatible,
}
}
.boxed_local(),
),
DepKind::Jsr => futs.push_back(
async {
let semver_req = &dep.req;
let latest_req = PackageReq {
name: dep.req.name.clone(),
version_req: deno_semver::WILDCARD_VERSION_REQ.clone(),
};
let _permit = jsr_sema.acquire().await;
let semver_compatible =
self.jsr_fetch_resolver.req_to_nv(semver_req).await;
let latest = self.jsr_fetch_resolver.req_to_nv(&latest_req).await;
PackageLatestVersion {
latest,
semver_compatible,
}
}
.boxed_local(),
),
}
}
while let Some(nv) = futs.next().await {
latest_versions.push(nv);
}
Ok(latest_versions)
}
pub async fn resolve_versions(&mut self) -> Result<(), AnyError> {
let (_, latest_versions) = try_join(
self.run_dependency_resolution(),
self.load_latest_versions(),
)
.await?;
self.latest_versions = latest_versions;
self.resolve_current_versions().await?;
Ok(())
}
pub fn deps_with_resolved_latest_versions(
&self,
) -> impl IntoIterator<Item = (DepId, Option<PackageNv>, PackageLatestVersion)> + '_
{
self
.resolved_versions
.iter()
.zip(self.latest_versions.iter())
.enumerate()
.map(|(i, (resolved, latest))| {
(DepId(i), resolved.clone(), latest.clone())
})
}
pub fn get_dep(&self, id: DepId) -> &Dep {
&self.deps[id.0]
}
pub fn update_dep(&mut self, dep_id: DepId, new_version_req: VersionReq) {
self
.pending_changes
.push(Change::Update(dep_id, new_version_req));
}
pub fn commit_changes(&mut self) -> Result<(), AnyError> {
let changes = std::mem::take(&mut self.pending_changes);
let mut config_updaters = HashMap::new();
for change in changes {
match change {
Change::Update(dep_id, version_req) => {
// TODO: move most of this to ConfigUpdater
let dep = &mut self.deps[dep_id.0];
dep.req.version_req = version_req.clone();
match &dep.location {
DepLocation::DenoJson(arc, key_path, import_map_kind) => {
if matches!(import_map_kind, ImportMapKind::Outline) {
// not supported
continue;
}
let updater =
get_or_create_updater(&mut config_updaters, &dep.location)?;
let Some(property) = updater.get_property_for_mutation(key_path)
else {
log::warn!(
"failed to find property at path {key_path:?} for file {}",
arc.specifier
);
continue;
};
let Some(string_value) = cst_string_literal(&property) else {
continue;
};
let mut req_reference = match dep.kind {
DepKind::Npm => NpmPackageReqReference::from_str(&string_value)
.unwrap()
.into_inner(),
DepKind::Jsr => JsrPackageReqReference::from_str(&string_value)
.unwrap()
.into_inner(),
};
req_reference.req.version_req = version_req;
let mut new_value =
format!("{}:{}", dep.kind.scheme(), req_reference);
if string_value.ends_with('/') && !new_value.ends_with('/') {
// the display impl for PackageReqReference maps `/` to the root
// subpath, but for the import map the trailing `/` is significant
new_value.push('/');
}
if string_value
.trim_start_matches(format!("{}:", dep.kind.scheme()).as_str())
.starts_with('/')
{
// this is gross
new_value = new_value.replace(':', ":/");
}
property
.set_value(jsonc_parser::cst::CstInputValue::String(new_value));
}
DepLocation::PackageJson(arc, key_path) => {
let updater =
get_or_create_updater(&mut config_updaters, &dep.location)?;
let Some(property) = updater.get_property_for_mutation(key_path)
else {
log::warn!(
"failed to find property at path {key_path:?} for file {}",
arc.path.display()
);
continue;
};
let Some(string_value) = cst_string_literal(&property) else {
continue;
};
let new_value = if string_value.starts_with("npm:") {
// aliased
let rest = string_value.trim_start_matches("npm:");
let mut parts = rest.split('@');
let first = parts.next().unwrap();
if first.is_empty() {
let scope_and_name = parts.next().unwrap();
format!("npm:@{scope_and_name}@{version_req}")
} else {
format!("npm:{first}@{version_req}")
}
} else if string_value.contains(":") {
bail!("Unexpected package json dependency string: \"{string_value}\" in {}", arc.path.display());
} else {
version_req.to_string()
};
property
.set_value(jsonc_parser::cst::CstInputValue::String(new_value));
}
}
}
}
}
for (_, updater) in config_updaters {
updater.commit()?;
}
Ok(())
}
}
fn get_or_create_updater<'a>(
config_updaters: &'a mut HashMap<std::path::PathBuf, ConfigUpdater>,
location: &DepLocation,
) -> Result<&'a mut ConfigUpdater, AnyError> {
match config_updaters.entry(location.file_path().into_owned()) {
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
Ok(occupied_entry.into_mut())
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let updater = ConfigUpdater::new(
location.config_kind(),
location.file_path().into_owned(),
)?;
Ok(vacant_entry.insert(updater))
}
}
}
fn cst_string_literal(
property: &jsonc_parser::cst::CstObjectProp,
) -> Option<String> {
// TODO(nathanwhit): ensure this unwrap is safe
let value = property.value().unwrap();
let Some(string) = value.as_string_lit() else {
log::warn!("malformed entry");
return None;
};
let Ok(string_value) = string.decoded_value() else {
log::warn!("malformed string: {string:?}");
return None;
};
Some(string_value)
}
fn parse_req_reference(
input: &str,
kind: DepKind,
) -> Result<
PackageReqReference,
deno_semver::package::PackageReqReferenceParseError,
> {
Ok(match kind {
DepKind::Npm => NpmPackageReqReference::from_str(input)?.into_inner(),
DepKind::Jsr => JsrPackageReqReference::from_str(input)?.into_inner(),
})
}

View file

@ -0,0 +1,661 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashSet;
use std::sync::Arc;
use deno_core::error::AnyError;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageReq;
use deno_semver::VersionReq;
use deno_terminal::colors;
use crate::args::CacheSetting;
use crate::args::CliOptions;
use crate::args::Flags;
use crate::args::OutdatedFlags;
use crate::factory::CliFactory;
use crate::file_fetcher::FileFetcher;
use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver;
use crate::tools::registry::pm::deps::DepKind;
use super::deps::Dep;
use super::deps::DepManager;
use super::deps::DepManagerArgs;
use super::deps::PackageLatestVersion;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct OutdatedPackage {
kind: DepKind,
latest: String,
semver_compatible: String,
current: String,
name: String,
}
#[allow(clippy::print_stdout)]
fn print_outdated_table(packages: &[OutdatedPackage]) {
const HEADINGS: &[&str] = &["Package", "Current", "Update", "Latest"];
let mut longest_package = 0;
let mut longest_current = 0;
let mut longest_update = 0;
let mut longest_latest = 0;
for package in packages {
let name_len = package.kind.scheme().len() + 1 + package.name.len();
longest_package = longest_package.max(name_len);
longest_current = longest_current.max(package.current.len());
longest_update = longest_update.max(package.semver_compatible.len());
longest_latest = longest_latest.max(package.latest.len());
}
let package_column_width = longest_package.max(HEADINGS[0].len()) + 2;
let current_column_width = longest_current.max(HEADINGS[1].len()) + 2;
let update_column_width = longest_update.max(HEADINGS[2].len()) + 2;
let latest_column_width = longest_latest.max(HEADINGS[3].len()) + 2;
let package_fill = "".repeat(package_column_width);
let current_fill = "".repeat(current_column_width);
let update_fill = "".repeat(update_column_width);
let latest_fill = "".repeat(latest_column_width);
println!("{package_fill}{current_fill}{update_fill}{latest_fill}");
println!(
"│ {}{} │ {}{} │ {}{} │ {}{} │",
colors::intense_blue(HEADINGS[0]),
" ".repeat(package_column_width - 2 - HEADINGS[0].len()),
colors::intense_blue(HEADINGS[1]),
" ".repeat(current_column_width - 2 - HEADINGS[1].len()),
colors::intense_blue(HEADINGS[2]),
" ".repeat(update_column_width - 2 - HEADINGS[2].len()),
colors::intense_blue(HEADINGS[3]),
" ".repeat(latest_column_width - 2 - HEADINGS[3].len())
);
for package in packages {
println!("{package_fill}{current_fill}{update_fill}{latest_fill}",);
print!(
"│ {:<package_column_width$} ",
format!("{}:{}", package.kind.scheme(), package.name),
package_column_width = package_column_width - 2
);
print!(
"│ {:<current_column_width$} ",
package.current,
current_column_width = current_column_width - 2
);
print!(
"│ {:<update_column_width$} ",
package.semver_compatible,
update_column_width = update_column_width - 2
);
println!(
"│ {:<latest_column_width$} │",
package.latest,
latest_column_width = latest_column_width - 2
);
}
println!("{package_fill}{current_fill}{update_fill}{latest_fill}",);
}
fn print_outdated(
deps: &mut DepManager,
compatible: bool,
) -> Result<(), AnyError> {
let mut outdated = Vec::new();
let mut seen = std::collections::BTreeSet::new();
for (dep_id, resolved, latest_versions) in
deps.deps_with_resolved_latest_versions()
{
let dep = deps.get_dep(dep_id);
let Some(resolved) = resolved else { continue };
let latest = {
let preferred = if compatible {
&latest_versions.semver_compatible
} else {
&latest_versions.latest
};
if let Some(v) = preferred {
v
} else {
continue;
}
};
if latest > &resolved
&& seen.insert((dep.kind, dep.req.name.clone(), resolved.version.clone()))
{
outdated.push(OutdatedPackage {
kind: dep.kind,
name: dep.req.name.clone(),
current: resolved.version.to_string(),
latest: latest_versions
.latest
.map(|l| l.version.to_string())
.unwrap_or_default(),
semver_compatible: latest_versions
.semver_compatible
.map(|l| l.version.to_string())
.unwrap_or_default(),
})
}
}
if !outdated.is_empty() {
outdated.sort();
print_outdated_table(&outdated);
}
Ok(())
}
pub async fn outdated(
flags: Arc<Flags>,
update_flags: OutdatedFlags,
) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags.clone());
let cli_options = factory.cli_options()?;
let workspace = cli_options.workspace();
let http_client = factory.http_client_provider();
let deps_http_cache = factory.global_http_cache()?;
let mut file_fetcher = FileFetcher::new(
deps_http_cache.clone(),
CacheSetting::RespectHeaders,
true,
http_client.clone(),
Default::default(),
None,
);
file_fetcher.set_download_log_level(log::Level::Trace);
let file_fetcher = Arc::new(file_fetcher);
let npm_fetch_resolver = Arc::new(NpmFetchResolver::new(
file_fetcher.clone(),
cli_options.npmrc().clone(),
));
let jsr_fetch_resolver =
Arc::new(JsrFetchResolver::new(file_fetcher.clone()));
let args = dep_manager_args(
&factory,
cli_options,
npm_fetch_resolver.clone(),
jsr_fetch_resolver.clone(),
)
.await?;
let filter_set = filter::FilterSet::from_filter_strings(
update_flags.filters.iter().map(|s| s.as_str()),
)?;
let filter_fn = |alias: Option<&str>, req: &PackageReq, _: DepKind| {
if filter_set.is_empty() {
return true;
}
let name = alias.unwrap_or(&req.name);
filter_set.matches(name)
};
let mut deps = if update_flags.recursive {
super::deps::DepManager::from_workspace(workspace, filter_fn, args)?
} else {
super::deps::DepManager::from_workspace_dir(
&cli_options.start_dir,
filter_fn,
args,
)?
};
deps.resolve_versions().await?;
match update_flags.kind {
crate::args::OutdatedKind::Update { latest } => {
update(deps, latest, &filter_set, flags).await?;
}
crate::args::OutdatedKind::PrintOutdated { compatible } => {
print_outdated(&mut deps, compatible)?;
}
}
Ok(())
}
fn choose_new_version_req(
dep: &Dep,
resolved: Option<&PackageNv>,
latest_versions: &PackageLatestVersion,
update_to_latest: bool,
filter_set: &filter::FilterSet,
) -> Option<VersionReq> {
let explicit_version_req = filter_set
.matching_filter(dep.alias.as_deref().unwrap_or(&dep.req.name))
.version_spec()
.cloned();
if let Some(version_req) = explicit_version_req {
if let Some(resolved) = resolved {
// todo(nathanwhit): handle tag
if version_req.tag().is_none() && version_req.matches(&resolved.version) {
return None;
}
}
Some(version_req)
} else {
let preferred = if update_to_latest {
latest_versions.latest.as_ref()?
} else {
latest_versions.semver_compatible.as_ref()?
};
if preferred.version <= resolved?.version {
return None;
}
Some(
VersionReq::parse_from_specifier(
format!("^{}", preferred.version).as_str(),
)
.unwrap(),
)
}
}
async fn update(
mut deps: DepManager,
update_to_latest: bool,
filter_set: &filter::FilterSet,
flags: Arc<Flags>,
) -> Result<(), AnyError> {
let mut updated = Vec::new();
for (dep_id, resolved, latest_versions) in deps
.deps_with_resolved_latest_versions()
.into_iter()
.collect::<Vec<_>>()
{
let dep = deps.get_dep(dep_id);
let new_version_req = choose_new_version_req(
dep,
resolved.as_ref(),
&latest_versions,
update_to_latest,
filter_set,
);
let Some(new_version_req) = new_version_req else {
continue;
};
updated.push((
dep_id,
format!("{}:{}", dep.kind.scheme(), dep.req.name),
deps.resolved_version(dep.id).cloned(),
new_version_req.clone(),
));
deps.update_dep(dep_id, new_version_req);
}
deps.commit_changes()?;
if !updated.is_empty() {
let factory = super::npm_install_after_modification(
flags.clone(),
Some(deps.jsr_fetch_resolver.clone()),
)
.await?;
let mut updated_to_versions = HashSet::new();
let cli_options = factory.cli_options()?;
let args = dep_manager_args(
&factory,
cli_options,
deps.npm_fetch_resolver.clone(),
deps.jsr_fetch_resolver.clone(),
)
.await?;
let mut deps = deps.reloaded_after_modification(args);
deps.resolve_current_versions().await?;
for (dep_id, package_name, maybe_current_version, new_version_req) in
updated
{
if let Some(nv) = deps.resolved_version(dep_id) {
updated_to_versions.insert((
package_name,
maybe_current_version,
nv.version.clone(),
));
} else {
log::warn!(
"Failed to resolve version for new version requirement: {} -> {}",
package_name,
new_version_req
);
}
}
log::info!(
"Updated {} dependenc{}:",
updated_to_versions.len(),
if updated_to_versions.len() == 1 {
"y"
} else {
"ies"
}
);
let mut updated_to_versions =
updated_to_versions.into_iter().collect::<Vec<_>>();
updated_to_versions.sort_by(|(k, _, _), (k2, _, _)| k.cmp(k2));
let max_name = updated_to_versions
.iter()
.map(|(name, _, _)| name.len())
.max()
.unwrap_or(0);
let max_old = updated_to_versions
.iter()
.map(|(_, maybe_current, _)| {
maybe_current
.as_ref()
.map(|v| v.version.to_string().len())
.unwrap_or(0)
})
.max()
.unwrap_or(0);
let max_new = updated_to_versions
.iter()
.map(|(_, _, new_version)| new_version.to_string().len())
.max()
.unwrap_or(0);
for (package_name, maybe_current_version, new_version) in
updated_to_versions
{
let current_version = if let Some(current_version) = maybe_current_version
{
current_version.version.to_string()
} else {
"".to_string()
};
log::info!(
" - {}{} {}{} -> {}{}",
format!(
"{}{}",
colors::gray(package_name[0..4].to_string()),
package_name[4..].to_string()
),
" ".repeat(max_name - package_name.len()),
" ".repeat(max_old - current_version.len()),
colors::gray(&current_version),
" ".repeat(max_new - new_version.to_string().len()),
colors::green(&new_version),
);
}
} else {
log::info!(
"All {}dependencies are up to date.",
if filter_set.is_empty() {
""
} else {
"matching "
}
);
}
Ok(())
}
async fn dep_manager_args(
factory: &CliFactory,
cli_options: &CliOptions,
npm_fetch_resolver: Arc<NpmFetchResolver>,
jsr_fetch_resolver: Arc<JsrFetchResolver>,
) -> Result<DepManagerArgs, AnyError> {
Ok(DepManagerArgs {
module_load_preparer: factory.module_load_preparer().await?.clone(),
jsr_fetch_resolver,
npm_fetch_resolver,
npm_resolver: factory.npm_resolver().await?.clone(),
permissions_container: factory.root_permissions_container()?.clone(),
main_module_graph_container: factory
.main_module_graph_container()
.await?
.clone(),
lockfile: cli_options.maybe_lockfile().cloned(),
})
}
mod filter {
use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_semver::VersionReq;
enum FilterKind {
Exclude,
Include,
}
pub struct Filter {
kind: FilterKind,
regex: regex::Regex,
version_spec: Option<VersionReq>,
}
fn pattern_to_regex(pattern: &str) -> Result<regex::Regex, AnyError> {
let escaped = regex::escape(pattern);
let unescaped_star = escaped.replace(r"\*", ".*");
Ok(regex::Regex::new(&format!("^{}$", unescaped_star))?)
}
impl Filter {
pub fn version_spec(&self) -> Option<&VersionReq> {
self.version_spec.as_ref()
}
pub fn from_str(input: &str) -> Result<Self, AnyError> {
let (kind, first_idx) = if input.starts_with('!') {
(FilterKind::Exclude, 1)
} else {
(FilterKind::Include, 0)
};
let s = &input[first_idx..];
let (pattern, version_spec) =
if let Some(scope_name) = s.strip_prefix('@') {
if let Some(idx) = scope_name.find('@') {
let (pattern, version_spec) = s.split_at(idx + 1);
(
pattern,
Some(
VersionReq::parse_from_specifier(
version_spec.trim_start_matches('@'),
)
.with_context(|| format!("Invalid filter \"{input}\""))?,
),
)
} else {
(s, None)
}
} else {
let mut parts = s.split('@');
let Some(pattern) = parts.next() else {
return Err(anyhow!("Invalid filter \"{input}\""));
};
(
pattern,
parts
.next()
.map(VersionReq::parse_from_specifier)
.transpose()
.with_context(|| format!("Invalid filter \"{input}\""))?,
)
};
Ok(Filter {
kind,
regex: pattern_to_regex(pattern)
.with_context(|| format!("Invalid filter \"{input}\""))?,
version_spec,
})
}
pub fn matches(&self, name: &str) -> bool {
self.regex.is_match(name)
}
}
pub struct FilterSet {
filters: Vec<Filter>,
has_exclude: bool,
has_include: bool,
}
impl FilterSet {
pub fn from_filter_strings<'a>(
filter_strings: impl IntoIterator<Item = &'a str>,
) -> Result<Self, AnyError> {
let filters = filter_strings
.into_iter()
.map(Filter::from_str)
.collect::<Result<Vec<_>, _>>()?;
let has_exclude = filters
.iter()
.any(|f| matches!(f.kind, FilterKind::Exclude));
let has_include = filters
.iter()
.any(|f| matches!(f.kind, FilterKind::Include));
Ok(FilterSet {
filters,
has_exclude,
has_include,
})
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty()
}
pub fn matches(&self, name: &str) -> bool {
self.matching_filter(name).is_included()
}
pub fn matching_filter(&self, name: &str) -> MatchResult<'_> {
if self.filters.is_empty() {
return MatchResult::Included;
}
let mut matched = None;
for filter in &self.filters {
match filter.kind {
FilterKind::Include => {
if matched.is_none() && filter.matches(name) {
matched = Some(filter);
}
}
FilterKind::Exclude => {
if filter.matches(name) {
return MatchResult::Excluded;
}
}
}
}
if let Some(filter) = matched {
MatchResult::Matches(filter)
} else if self.has_exclude && !self.has_include {
MatchResult::Included
} else {
MatchResult::Excluded
}
}
}
pub enum MatchResult<'a> {
Matches(&'a Filter),
Included,
Excluded,
}
impl MatchResult<'_> {
pub fn version_spec(&self) -> Option<&VersionReq> {
match self {
MatchResult::Matches(filter) => filter.version_spec(),
_ => None,
}
}
pub fn is_included(&self) -> bool {
matches!(self, MatchResult::Included | MatchResult::Matches(_))
}
}
#[cfg(test)]
mod test {
fn matches_filters<'a, 'b>(
filters: impl IntoIterator<Item = &'a str>,
name: &str,
) -> bool {
let filters = super::FilterSet::from_filter_strings(filters).unwrap();
filters.matches(name)
}
fn version_spec(s: &str) -> deno_semver::VersionReq {
deno_semver::VersionReq::parse_from_specifier(s).unwrap()
}
#[test]
fn basic_glob() {
assert!(matches_filters(["foo*"], "foo"));
assert!(matches_filters(["foo*"], "foobar"));
assert!(!matches_filters(["foo*"], "barfoo"));
assert!(matches_filters(["*foo"], "foo"));
assert!(matches_filters(["*foo"], "barfoo"));
assert!(!matches_filters(["*foo"], "foobar"));
assert!(matches_filters(["@scope/foo*"], "@scope/foobar"));
}
#[test]
fn basic_glob_with_version() {
assert!(matches_filters(["foo*@1"], "foo",));
assert!(matches_filters(["foo*@1"], "foobar",));
assert!(matches_filters(["foo*@1"], "foo-bar",));
assert!(!matches_filters(["foo*@1"], "barfoo",));
assert!(matches_filters(["@scope/*@1"], "@scope/foo"));
}
#[test]
fn glob_exclude() {
assert!(!matches_filters(["!foo*"], "foo"));
assert!(!matches_filters(["!foo*"], "foobar"));
assert!(matches_filters(["!foo*"], "barfoo"));
assert!(!matches_filters(["!*foo"], "foo"));
assert!(!matches_filters(["!*foo"], "barfoo"));
assert!(matches_filters(["!*foo"], "foobar"));
assert!(!matches_filters(["!@scope/foo*"], "@scope/foobar"));
}
#[test]
fn multiple_globs() {
assert!(matches_filters(["foo*", "bar*"], "foo"));
assert!(matches_filters(["foo*", "bar*"], "bar"));
assert!(!matches_filters(["foo*", "bar*"], "baz"));
assert!(matches_filters(["foo*", "!bar*"], "foo"));
assert!(!matches_filters(["foo*", "!bar*"], "bar"));
assert!(matches_filters(["foo*", "!bar*"], "foobar"));
assert!(!matches_filters(["foo*", "!*bar"], "foobar"));
assert!(!matches_filters(["foo*", "!*bar"], "baz"));
let filters =
super::FilterSet::from_filter_strings(["foo*@1", "bar*@2"]).unwrap();
assert_eq!(
filters.matching_filter("foo").version_spec().cloned(),
Some(version_spec("1"))
);
assert_eq!(
filters.matching_filter("bar").version_spec().cloned(),
Some(version_spec("2"))
);
}
}
}

View file

@ -1,19 +1,35 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::sync::Arc;
use deno_ast::diagnostics::Diagnostic;
use deno_ast::diagnostics::DiagnosticLevel;
use deno_ast::diagnostics::DiagnosticLocation;
use deno_ast::diagnostics::DiagnosticSnippet;
use deno_ast::diagnostics::DiagnosticSnippetHighlight;
use deno_ast::diagnostics::DiagnosticSnippetHighlightStyle;
use deno_ast::diagnostics::DiagnosticSourcePos;
use deno_ast::diagnostics::DiagnosticSourceRange;
use deno_ast::ParsedSource; use deno_ast::ParsedSource;
use deno_ast::SourceRange; use deno_ast::SourceRange;
use deno_ast::SourceTextInfo; use deno_ast::SourceTextInfo;
use deno_ast::SourceTextProvider;
use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolution;
use deno_config::workspace::PackageJsonDepResolution; use deno_config::workspace::PackageJsonDepResolution;
use deno_config::workspace::WorkspaceResolver; use deno_config::workspace::WorkspaceResolver;
use deno_core::anyhow;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::DependencyDescriptor; use deno_graph::DependencyDescriptor;
use deno_graph::DynamicTemplatePart; use deno_graph::DynamicTemplatePart;
use deno_graph::ParserModuleAnalyzer; use deno_graph::ParserModuleAnalyzer;
use deno_graph::TypeScriptReference; use deno_graph::TypeScriptReference;
use deno_package_json::PackageJsonDepValue; use deno_package_json::PackageJsonDepValue;
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_package_json::PackageJsonDepWorkspaceReq;
use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
use deno_runtime::deno_node::is_builtin_node_module; use deno_runtime::deno_node::is_builtin_node_module;
use deno_semver::Version;
use deno_semver::VersionReq;
use crate::resolver::CliSloppyImportsResolver; use crate::resolver::CliSloppyImportsResolver;
@ -24,34 +40,163 @@ pub enum SpecifierUnfurlerDiagnostic {
text_info: SourceTextInfo, text_info: SourceTextInfo,
range: SourceRange, range: SourceRange,
}, },
ResolvingNpmWorkspacePackage {
specifier: ModuleSpecifier,
package_name: String,
text_info: SourceTextInfo,
range: SourceRange,
reason: String,
},
} }
impl SpecifierUnfurlerDiagnostic { impl Diagnostic for SpecifierUnfurlerDiagnostic {
pub fn code(&self) -> &'static str { fn level(&self) -> DiagnosticLevel {
match self { match self {
Self::UnanalyzableDynamicImport { .. } => "unanalyzable-dynamic-import", SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => {
} DiagnosticLevel::Warning
} }
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage { .. } => {
pub fn message(&self) -> &'static str { DiagnosticLevel::Error
match self {
Self::UnanalyzableDynamicImport { .. } => {
"unable to analyze dynamic import"
} }
} }
} }
fn code(&self) -> Cow<'_, str> {
match self {
Self::UnanalyzableDynamicImport { .. } => "unanalyzable-dynamic-import",
Self::ResolvingNpmWorkspacePackage { .. } => "npm-workspace-package",
}
.into()
}
fn message(&self) -> Cow<'_, str> {
match self {
Self::UnanalyzableDynamicImport { .. } => {
"unable to analyze dynamic import".into()
}
Self::ResolvingNpmWorkspacePackage {
package_name,
reason,
..
} => format!(
"failed resolving npm workspace package '{}': {}",
package_name, reason
)
.into(),
}
}
fn location(&self) -> deno_ast::diagnostics::DiagnosticLocation {
match self {
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
specifier,
text_info,
range,
} => DiagnosticLocation::ModulePosition {
specifier: Cow::Borrowed(specifier),
text_info: Cow::Borrowed(text_info),
source_pos: DiagnosticSourcePos::SourcePos(range.start),
},
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage {
specifier,
text_info,
range,
..
} => DiagnosticLocation::ModulePosition {
specifier: Cow::Borrowed(specifier),
text_info: Cow::Borrowed(text_info),
source_pos: DiagnosticSourcePos::SourcePos(range.start),
},
}
}
fn snippet(&self) -> Option<deno_ast::diagnostics::DiagnosticSnippet<'_>> {
match self {
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport {
text_info,
range,
..
} => Some(DiagnosticSnippet {
source: Cow::Borrowed(text_info),
highlights: vec![DiagnosticSnippetHighlight {
style: DiagnosticSnippetHighlightStyle::Warning,
range: DiagnosticSourceRange {
start: DiagnosticSourcePos::SourcePos(range.start),
end: DiagnosticSourcePos::SourcePos(range.end),
},
description: Some("the unanalyzable dynamic import".into()),
}],
}),
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage {
text_info,
range,
..
} => Some(DiagnosticSnippet {
source: Cow::Borrowed(text_info),
highlights: vec![DiagnosticSnippetHighlight {
style: DiagnosticSnippetHighlightStyle::Warning,
range: DiagnosticSourceRange {
start: DiagnosticSourcePos::SourcePos(range.start),
end: DiagnosticSourcePos::SourcePos(range.end),
},
description: Some("the unresolved import".into()),
}],
}),
}
}
fn hint(&self) -> Option<Cow<'_, str>> {
match self {
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => {
None
}
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage { .. } => Some(
"make sure the npm workspace package is resolvable and has a version field in its package.json".into()
),
}
}
fn snippet_fixed(
&self,
) -> Option<deno_ast::diagnostics::DiagnosticSnippet<'_>> {
None
}
fn info(&self) -> Cow<'_, [Cow<'_, str>]> {
match self {
SpecifierUnfurlerDiagnostic::UnanalyzableDynamicImport { .. } => Cow::Borrowed(&[
Cow::Borrowed("after publishing this package, imports from the local import map / package.json do not work"),
Cow::Borrowed("dynamic imports that can not be analyzed at publish time will not be rewritten automatically"),
Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map / package.json")
]),
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage { .. } => {
Cow::Borrowed(&[])
},
}
}
fn docs_url(&self) -> Option<Cow<'_, str>> {
None
}
}
enum UnfurlSpecifierError {
Workspace {
package_name: String,
reason: String,
},
} }
pub struct SpecifierUnfurler { pub struct SpecifierUnfurler {
sloppy_imports_resolver: Option<CliSloppyImportsResolver>, sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
workspace_resolver: WorkspaceResolver, workspace_resolver: Arc<WorkspaceResolver>,
bare_node_builtins: bool, bare_node_builtins: bool,
} }
impl SpecifierUnfurler { impl SpecifierUnfurler {
pub fn new( pub fn new(
sloppy_imports_resolver: Option<CliSloppyImportsResolver>, sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
workspace_resolver: WorkspaceResolver, workspace_resolver: Arc<WorkspaceResolver>,
bare_node_builtins: bool, bare_node_builtins: bool,
) -> Self { ) -> Self {
debug_assert_eq!( debug_assert_eq!(
@ -65,11 +210,45 @@ impl SpecifierUnfurler {
} }
} }
fn unfurl_specifier_reporting_diagnostic(
&self,
referrer: &ModuleSpecifier,
specifier: &str,
text_info: &SourceTextInfo,
range: &deno_graph::PositionRange,
diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic),
) -> Option<String> {
match self.unfurl_specifier(referrer, specifier) {
Ok(maybe_unfurled) => maybe_unfurled,
Err(diagnostic) => match diagnostic {
UnfurlSpecifierError::Workspace {
package_name,
reason,
} => {
let range = to_range(text_info, range);
diagnostic_reporter(
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage {
specifier: referrer.clone(),
package_name,
text_info: text_info.clone(),
range: SourceRange::new(
text_info.start_pos() + range.start,
text_info.start_pos() + range.end,
),
reason,
},
);
None
}
},
}
}
fn unfurl_specifier( fn unfurl_specifier(
&self, &self,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
specifier: &str, specifier: &str,
) -> Option<String> { ) -> Result<Option<String>, UnfurlSpecifierError> {
let resolved = if let Ok(resolved) = let resolved = if let Ok(resolved) =
self.workspace_resolver.resolve(specifier, referrer) self.workspace_resolver.resolve(specifier, referrer)
{ {
@ -120,8 +299,40 @@ impl SpecifierUnfurler {
)) ))
.ok() .ok()
} }
PackageJsonDepValue::Workspace(version_req) => { PackageJsonDepValue::Workspace(workspace_version_req) => {
// todo(#24612): consider warning or error when this is also a jsr package? let version_req = match workspace_version_req {
PackageJsonDepWorkspaceReq::VersionReq(version_req) => {
Cow::Borrowed(version_req)
}
PackageJsonDepWorkspaceReq::Caret => {
let version = self
.find_workspace_npm_dep_version(alias)
.map_err(|err| UnfurlSpecifierError::Workspace {
package_name: alias.to_string(),
reason: err.to_string(),
})?;
// version was validated, so ok to unwrap
Cow::Owned(
VersionReq::parse_from_npm(&format!("^{}", version))
.unwrap(),
)
}
PackageJsonDepWorkspaceReq::Tilde => {
let version = self
.find_workspace_npm_dep_version(alias)
.map_err(|err| UnfurlSpecifierError::Workspace {
package_name: alias.to_string(),
reason: err.to_string(),
})?;
// version was validated, so ok to unwrap
Cow::Owned(
VersionReq::parse_from_npm(&format!("~{}", version))
.unwrap(),
)
}
};
// todo(#24612): warn when this is also a jsr package telling
// people to map the specifiers in the import map
ModuleSpecifier::parse(&format!( ModuleSpecifier::parse(&format!(
"npm:{}@{}{}", "npm:{}@{}{}",
alias, alias,
@ -151,10 +362,14 @@ impl SpecifierUnfurler {
None if self.bare_node_builtins && is_builtin_node_module(specifier) => { None if self.bare_node_builtins && is_builtin_node_module(specifier) => {
format!("node:{specifier}").parse().unwrap() format!("node:{specifier}").parse().unwrap()
} }
None => ModuleSpecifier::options() None => match ModuleSpecifier::options()
.base_url(Some(referrer)) .base_url(Some(referrer))
.parse(specifier) .parse(specifier)
.ok()?, .ok()
{
Some(value) => value,
None => return Ok(None),
},
}; };
// TODO(lucacasonato): this requires integration in deno_graph first // TODO(lucacasonato): this requires integration in deno_graph first
// let resolved = if let Ok(specifier) = // let resolved = if let Ok(specifier) =
@ -180,7 +395,7 @@ impl SpecifierUnfurler {
let resolved = let resolved =
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver { if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
sloppy_imports_resolver sloppy_imports_resolver
.resolve(&resolved, SloppyImportsResolutionMode::Execution) .resolve(&resolved, SloppyImportsResolutionKind::Execution)
.map(|res| res.into_specifier()) .map(|res| res.into_specifier())
.unwrap_or(resolved) .unwrap_or(resolved)
} else { } else {
@ -188,7 +403,7 @@ impl SpecifierUnfurler {
}; };
let relative_resolved = relative_url(&resolved, referrer); let relative_resolved = relative_url(&resolved, referrer);
if relative_resolved == specifier { if relative_resolved == specifier {
None // nothing to unfurl Ok(None) // nothing to unfurl
} else { } else {
log::debug!( log::debug!(
"Unfurled specifier: {} from {} -> {}", "Unfurled specifier: {} from {} -> {}",
@ -196,7 +411,29 @@ impl SpecifierUnfurler {
referrer, referrer,
relative_resolved relative_resolved
); );
Some(relative_resolved) Ok(Some(relative_resolved))
}
}
fn find_workspace_npm_dep_version(
&self,
pkg_name: &str,
) -> Result<Version, anyhow::Error> {
// todo(#24612): warn when this is also a jsr package telling
// people to map the specifiers in the import map
let pkg_json = self
.workspace_resolver
.package_jsons()
.find(|pkg| pkg.name.as_deref() == Some(pkg_name))
.ok_or_else(|| {
anyhow::anyhow!("unable to find npm package in workspace")
})?;
if let Some(version) = &pkg_json.version {
Ok(Version::parse_from_npm(version)?)
} else {
Err(anyhow::anyhow!(
"missing version in package.json of npm package",
))
} }
} }
@ -208,6 +445,7 @@ impl SpecifierUnfurler {
text_info: &SourceTextInfo, text_info: &SourceTextInfo,
dep: &deno_graph::DynamicDependencyDescriptor, dep: &deno_graph::DynamicDependencyDescriptor,
text_changes: &mut Vec<deno_ast::TextChange>, text_changes: &mut Vec<deno_ast::TextChange>,
diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic),
) -> bool { ) -> bool {
match &dep.argument { match &dep.argument {
deno_graph::DynamicArgument::String(specifier) => { deno_graph::DynamicArgument::String(specifier) => {
@ -217,8 +455,14 @@ impl SpecifierUnfurler {
let Some(relative_index) = maybe_relative_index else { let Some(relative_index) = maybe_relative_index else {
return true; // always say it's analyzable for a string return true; // always say it's analyzable for a string
}; };
let unfurled = self.unfurl_specifier(module_url, specifier); let maybe_unfurled = self.unfurl_specifier_reporting_diagnostic(
if let Some(unfurled) = unfurled { module_url,
specifier,
text_info,
&dep.argument_range,
diagnostic_reporter,
);
if let Some(unfurled) = maybe_unfurled {
let start = range.start + relative_index; let start = range.start + relative_index;
text_changes.push(deno_ast::TextChange { text_changes.push(deno_ast::TextChange {
range: start..start + specifier.len(), range: start..start + specifier.len(),
@ -238,7 +482,13 @@ impl SpecifierUnfurler {
if !specifier.ends_with('/') { if !specifier.ends_with('/') {
return false; return false;
} }
let unfurled = self.unfurl_specifier(module_url, specifier); let unfurled = self.unfurl_specifier_reporting_diagnostic(
module_url,
specifier,
text_info,
&dep.argument_range,
diagnostic_reporter,
);
let Some(unfurled) = unfurled else { let Some(unfurled) = unfurled else {
return true; // nothing to unfurl return true; // nothing to unfurl
}; };
@ -280,8 +530,15 @@ impl SpecifierUnfurler {
let analyze_specifier = let analyze_specifier =
|specifier: &str, |specifier: &str,
range: &deno_graph::PositionRange, range: &deno_graph::PositionRange,
text_changes: &mut Vec<deno_ast::TextChange>| { text_changes: &mut Vec<deno_ast::TextChange>,
if let Some(unfurled) = self.unfurl_specifier(url, specifier) { diagnostic_reporter: &mut dyn FnMut(SpecifierUnfurlerDiagnostic)| {
if let Some(unfurled) = self.unfurl_specifier_reporting_diagnostic(
url,
specifier,
text_info,
range,
diagnostic_reporter,
) {
text_changes.push(deno_ast::TextChange { text_changes.push(deno_ast::TextChange {
range: to_range(text_info, range), range: to_range(text_info, range),
new_text: unfurled, new_text: unfurled,
@ -295,11 +552,17 @@ impl SpecifierUnfurler {
&dep.specifier, &dep.specifier,
&dep.specifier_range, &dep.specifier_range,
&mut text_changes, &mut text_changes,
diagnostic_reporter,
); );
} }
DependencyDescriptor::Dynamic(dep) => { DependencyDescriptor::Dynamic(dep) => {
let success = let success = self.try_unfurl_dynamic_dep(
self.try_unfurl_dynamic_dep(url, text_info, dep, &mut text_changes); url,
text_info,
dep,
&mut text_changes,
diagnostic_reporter,
);
if !success { if !success {
let start_pos = text_info.line_start(dep.argument_range.start.line) let start_pos = text_info.line_start(dep.argument_range.start.line)
@ -319,20 +582,22 @@ impl SpecifierUnfurler {
} }
for ts_ref in &module_info.ts_references { for ts_ref in &module_info.ts_references {
let specifier_with_range = match ts_ref { let specifier_with_range = match ts_ref {
TypeScriptReference::Path(range) => range, TypeScriptReference::Path(s) => s,
TypeScriptReference::Types(range) => range, TypeScriptReference::Types { specifier, .. } => specifier,
}; };
analyze_specifier( analyze_specifier(
&specifier_with_range.text, &specifier_with_range.text,
&specifier_with_range.range, &specifier_with_range.range,
&mut text_changes, &mut text_changes,
diagnostic_reporter,
); );
} }
for specifier_with_range in &module_info.jsdoc_imports { for jsdoc in &module_info.jsdoc_imports {
analyze_specifier( analyze_specifier(
&specifier_with_range.text, &jsdoc.specifier.text,
&specifier_with_range.range, &jsdoc.specifier.range,
&mut text_changes, &mut text_changes,
diagnostic_reporter,
); );
} }
if let Some(specifier_with_range) = &module_info.jsx_import_source { if let Some(specifier_with_range) = &module_info.jsx_import_source {
@ -340,6 +605,7 @@ impl SpecifierUnfurler {
&specifier_with_range.text, &specifier_with_range.text,
&specifier_with_range.range, &specifier_with_range.range,
&mut text_changes, &mut text_changes,
diagnostic_reporter,
); );
} }
@ -458,10 +724,10 @@ mod tests {
); );
let fs = Arc::new(RealFs); let fs = Arc::new(RealFs);
let unfurler = SpecifierUnfurler::new( let unfurler = SpecifierUnfurler::new(
Some(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new( Some(Arc::new(CliSloppyImportsResolver::new(
fs, SloppyImportsCachedFs::new(fs),
))), ))),
workspace_resolver, Arc::new(workspace_resolver),
true, true,
); );
@ -547,4 +813,114 @@ const warn2 = await import(`${expr}`);
assert_eq!(unfurled_source, expected_source); assert_eq!(unfurled_source, expected_source);
} }
} }
#[test]
fn test_unfurling_npm_dep_workspace_specifier() {
let cwd = testdata_path().join("unfurl").to_path_buf();
let pkg_json_add = PackageJson::load_from_value(
cwd.join("add/package.json"),
json!({ "name": "add", "version": "0.1.0", }),
);
let pkg_json_subtract = PackageJson::load_from_value(
cwd.join("subtract/package.json"),
json!({ "name": "subtract", "version": "0.2.0", }),
);
let pkg_json_publishing = PackageJson::load_from_value(
cwd.join("publish/package.json"),
json!({
"name": "@denotest/main",
"version": "1.0.0",
"dependencies": {
"add": "workspace:~",
"subtract": "workspace:^",
"non-existent": "workspace:~",
}
}),
);
let root_pkg_json = PackageJson::load_from_value(
cwd.join("package.json"),
json!({ "workspaces": ["./publish", "./subtract", "./add"] }),
);
let workspace_resolver = WorkspaceResolver::new_raw(
Arc::new(ModuleSpecifier::from_directory_path(&cwd).unwrap()),
None,
vec![ResolverWorkspaceJsrPackage {
is_patch: false,
base: ModuleSpecifier::from_directory_path(
cwd.join("publish/jsr.json"),
)
.unwrap(),
name: "@denotest/main".to_string(),
version: Some(Version::parse_standard("1.0.0").unwrap()),
exports: IndexMap::from([(".".to_string(), "mod.ts".to_string())]),
}],
vec![
Arc::new(root_pkg_json),
Arc::new(pkg_json_add),
Arc::new(pkg_json_subtract),
Arc::new(pkg_json_publishing),
],
deno_config::workspace::PackageJsonDepResolution::Enabled,
);
let fs = Arc::new(RealFs);
let unfurler = SpecifierUnfurler::new(
Some(Arc::new(CliSloppyImportsResolver::new(
SloppyImportsCachedFs::new(fs),
))),
Arc::new(workspace_resolver),
true,
);
{
let source_code = r#"import add from "add";
import subtract from "subtract";
console.log(add, subtract);
"#;
let specifier =
ModuleSpecifier::from_file_path(cwd.join("publish").join("mod.ts"))
.unwrap();
let source = parse_ast(&specifier, source_code);
let mut d = Vec::new();
let mut reporter = |diagnostic| d.push(diagnostic);
let unfurled_source = unfurler.unfurl(&specifier, &source, &mut reporter);
assert_eq!(d.len(), 0);
// it will inline the version
let expected_source = r#"import add from "npm:add@~0.1.0";
import subtract from "npm:subtract@^0.2.0";
console.log(add, subtract);
"#;
assert_eq!(unfurled_source, expected_source);
}
{
let source_code = r#"import nonExistent from "non-existent";
console.log(nonExistent);
"#;
let specifier =
ModuleSpecifier::from_file_path(cwd.join("publish").join("other.ts"))
.unwrap();
let source = parse_ast(&specifier, source_code);
let mut d = Vec::new();
let mut reporter = |diagnostic| d.push(diagnostic);
let unfurled_source = unfurler.unfurl(&specifier, &source, &mut reporter);
assert_eq!(d.len(), 1);
match &d[0] {
SpecifierUnfurlerDiagnostic::ResolvingNpmWorkspacePackage {
package_name,
reason,
..
} => {
assert_eq!(package_name, "non-existent");
assert_eq!(reason, "unable to find npm package in workspace");
}
_ => unreachable!(),
}
// won't make any changes, but the above will be a fatal error
assert!(matches!(d[0].level(), DiagnosticLevel::Error));
assert_eq!(unfurled_source, source_code);
}
}
} }

View file

@ -43,13 +43,13 @@ use deno_core::unsync::spawn;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::LocalInspectorSession; use deno_core::LocalInspectorSession;
use deno_core::PollEventLoopOptions; use deno_core::PollEventLoopOptions;
use deno_graph::source::ResolutionMode;
use deno_graph::Position; use deno_graph::Position;
use deno_graph::PositionRange; use deno_graph::PositionRange;
use deno_graph::SpecifierWithRange; use deno_graph::SpecifierWithRange;
use deno_runtime::worker::MainWorker; use deno_runtime::worker::MainWorker;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Match; use regex::Match;
use regex::Regex; use regex::Regex;
@ -701,11 +701,6 @@ impl ReplSession {
let mut collector = ImportCollector::new(); let mut collector = ImportCollector::new();
program.visit_with(&mut collector); program.visit_with(&mut collector);
let referrer_range = deno_graph::Range {
specifier: self.referrer.clone(),
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
};
let resolved_imports = collector let resolved_imports = collector
.imports .imports
.iter() .iter()
@ -714,9 +709,10 @@ impl ReplSession {
.resolver .resolver
.resolve( .resolve(
i, i,
&referrer_range, &self.referrer,
NodeModuleKind::Esm, deno_graph::Position::zeroed(),
ResolutionMode::Execution, ResolutionMode::Import,
NodeResolutionKind::Execution,
) )
.ok() .ok()
.or_else(|| ModuleSpecifier::parse(i).ok()) .or_else(|| ModuleSpecifier::parse(i).ok())

View file

@ -124,7 +124,8 @@ async fn run_with_watch(
!watch_flags.no_clear_screen, !watch_flags.no_clear_screen,
), ),
WatcherRestartMode::Automatic, WatcherRestartMode::Automatic,
move |flags, watcher_communicator, _changed_paths| { move |flags, watcher_communicator, changed_paths| {
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags_for_watcher( let factory = CliFactory::from_flags_for_watcher(
flags, flags,

View file

@ -151,7 +151,8 @@ async fn serve_with_watch(
!watch_flags.no_clear_screen, !watch_flags.no_clear_screen,
), ),
WatcherRestartMode::Automatic, WatcherRestartMode::Automatic,
move |flags, watcher_communicator, _changed_paths| { move |flags, watcher_communicator, changed_paths| {
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags_for_watcher( let factory = CliFactory::from_flags_for_watcher(
flags, flags,

View file

@ -1,23 +1,35 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use deno_config::deno_json::Task; use console_static_text::ansi::strip_ansi_codes;
use deno_config::workspace::FolderConfigs;
use deno_config::workspace::TaskDefinition;
use deno_config::workspace::TaskOrScript; use deno_config::workspace::TaskOrScript;
use deno_config::workspace::WorkspaceDirectory; use deno_config::workspace::WorkspaceDirectory;
use deno_config::workspace::WorkspaceMemberTasksConfig;
use deno_config::workspace::WorkspaceTasksConfig; use deno_config::workspace::WorkspaceTasksConfig;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::stream::futures_unordered;
use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt;
use deno_core::url::Url;
use deno_path_util::normalize_path; use deno_path_util::normalize_path;
use deno_runtime::deno_node::NodeResolver;
use deno_task_shell::KillSignal;
use deno_task_shell::ShellCommand; use deno_task_shell::ShellCommand;
use indexmap::IndexMap;
use regex::Regex;
use crate::args::CliOptions; use crate::args::CliOptions;
use crate::args::Flags; use crate::args::Flags;
@ -26,8 +38,15 @@ use crate::colors;
use crate::factory::CliFactory; use crate::factory::CliFactory;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::task_runner; use crate::task_runner;
use crate::task_runner::run_future_forwarding_signals;
use crate::util::fs::canonicalize_path; use crate::util::fs::canonicalize_path;
#[derive(Debug)]
struct PackageTaskInfo {
matched_tasks: Vec<String>,
tasks_config: WorkspaceTasksConfig,
}
pub async fn execute_script( pub async fn execute_script(
flags: Arc<Flags>, flags: Arc<Flags>,
task_flags: TaskFlags, task_flags: TaskFlags,
@ -35,7 +54,7 @@ pub async fn execute_script(
let factory = CliFactory::from_flags(flags); let factory = CliFactory::from_flags(flags);
let cli_options = factory.cli_options()?; let cli_options = factory.cli_options()?;
let start_dir = &cli_options.start_dir; let start_dir = &cli_options.start_dir;
if !start_dir.has_deno_or_pkg_json() { if !start_dir.has_deno_or_pkg_json() && !task_flags.eval {
bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config") bail!("deno task couldn't find deno.json(c). See https://docs.deno.com/go/config")
} }
let force_use_pkg_json = let force_use_pkg_json =
@ -48,155 +67,586 @@ pub async fn execute_script(
v == "1" v == "1"
}) })
.unwrap_or(false); .unwrap_or(false);
let tasks_config = start_dir.to_tasks_config()?;
let tasks_config = if force_use_pkg_json {
tasks_config.with_only_pkg_json()
} else {
tasks_config
};
let task_name = match &task_flags.task { fn arg_to_regex(input: &str) -> Result<regex::Regex, regex::Error> {
Some(task) => task, let mut regex_str = regex::escape(input);
None => { regex_str = regex_str.replace("\\*", ".*");
Regex::new(&regex_str)
}
let packages_task_configs: Vec<PackageTaskInfo> = if let Some(filter) =
&task_flags.filter
{
let task_name = task_flags.task.as_ref().unwrap();
// Filter based on package name
let package_regex = arg_to_regex(filter)?;
let task_regex = arg_to_regex(task_name)?;
let mut packages_task_info: Vec<PackageTaskInfo> = vec![];
fn matches_package(
config: &FolderConfigs,
force_use_pkg_json: bool,
regex: &Regex,
) -> bool {
if !force_use_pkg_json {
if let Some(deno_json) = &config.deno_json {
if let Some(name) = &deno_json.json.name {
if regex.is_match(name) {
return true;
}
}
}
}
if let Some(package_json) = &config.pkg_json {
if let Some(name) = &package_json.name {
if regex.is_match(name) {
return true;
}
}
}
false
}
let workspace = cli_options.workspace();
for folder in workspace.config_folders() {
if !matches_package(folder.1, force_use_pkg_json, &package_regex) {
continue;
}
let member_dir = workspace.resolve_member_dir(folder.0);
let mut tasks_config = member_dir.to_tasks_config()?;
if force_use_pkg_json {
tasks_config = tasks_config.with_only_pkg_json();
}
// Any of the matched tasks could be a child task of another matched
// one. Therefore we need to filter these out to ensure that every
// task is only run once.
let mut matched: HashSet<String> = HashSet::new();
let mut visited: HashSet<String> = HashSet::new();
fn visit_task(
tasks_config: &WorkspaceTasksConfig,
visited: &mut HashSet<String>,
name: &str,
) {
if visited.contains(name) {
return;
}
visited.insert(name.to_string());
if let Some((_, TaskOrScript::Task(_, task))) = &tasks_config.task(name)
{
for dep in &task.dependencies {
visit_task(tasks_config, visited, dep);
}
}
}
// Match tasks in deno.json
for name in tasks_config.task_names() {
if task_regex.is_match(name) && !visited.contains(name) {
matched.insert(name.to_string());
visit_task(&tasks_config, &mut visited, name);
}
}
packages_task_info.push(PackageTaskInfo {
matched_tasks: matched
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
tasks_config,
});
}
// Logging every task definition would be too spammy. Pnpm only
// logs a simple message too.
if packages_task_info
.iter()
.all(|config| config.matched_tasks.is_empty())
{
log::warn!(
"{}",
colors::red(format!(
"No matching task or script '{}' found in selected packages.",
task_name
))
);
return Ok(0);
}
// FIXME: Sort packages topologically
//
packages_task_info
} else {
let mut tasks_config = start_dir.to_tasks_config()?;
if force_use_pkg_json {
tasks_config = tasks_config.with_only_pkg_json()
}
let Some(task_name) = &task_flags.task else {
print_available_tasks( print_available_tasks(
&mut std::io::stdout(), &mut std::io::stdout(),
&cli_options.start_dir, &cli_options.start_dir,
&tasks_config, &tasks_config,
)?; )?;
return Ok(0); return Ok(0);
} };
vec![PackageTaskInfo {
tasks_config,
matched_tasks: vec![task_name.to_string()],
}]
}; };
let npm_resolver = factory.npm_resolver().await?; let npm_resolver = factory.npm_resolver().await?;
let node_resolver = factory.node_resolver().await?; let node_resolver = factory.node_resolver().await?;
let env_vars = task_runner::real_env_vars(); let env_vars = task_runner::real_env_vars();
match tasks_config.task(task_name) { let no_of_concurrent_tasks = if let Ok(value) = std::env::var("DENO_JOBS") {
Some((dir_url, task_or_script)) => match task_or_script { value.parse::<NonZeroUsize>().ok()
TaskOrScript::Task(_tasks, script) => { } else {
let cwd = match task_flags.cwd { std::thread::available_parallelism().ok()
Some(path) => canonicalize_path(&PathBuf::from(path))
.context("failed canonicalizing --cwd")?,
None => normalize_path(dir_url.to_file_path().unwrap()),
};
let custom_commands = task_runner::resolve_custom_commands(
npm_resolver.as_ref(),
node_resolver,
)?;
run_task(RunTaskOptions {
task_name,
script,
cwd: &cwd,
env_vars,
custom_commands,
npm_resolver: npm_resolver.as_ref(),
cli_options,
})
.await
}
TaskOrScript::Script(scripts, _script) => {
// 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 {
Some(path) => canonicalize_path(&PathBuf::from(path))?,
None => normalize_path(dir_url.to_file_path().unwrap()),
};
// At this point we already checked if the task name exists in package.json.
// We can therefore check for "pre" and "post" scripts too, since we're only
// dealing with package.json here and not deno.json
let task_names = vec![
format!("pre{}", task_name),
task_name.clone(),
format!("post{}", task_name),
];
let custom_commands = task_runner::resolve_custom_commands(
npm_resolver.as_ref(),
node_resolver,
)?;
for task_name in &task_names {
if let Some(script) = scripts.get(task_name) {
let exit_code = run_task(RunTaskOptions {
task_name,
script,
cwd: &cwd,
env_vars: env_vars.clone(),
custom_commands: custom_commands.clone(),
npm_resolver: npm_resolver.as_ref(),
cli_options,
})
.await?;
if exit_code > 0 {
return Ok(exit_code);
}
}
}
Ok(0)
}
},
None => {
if task_flags.is_run {
return Err(anyhow!("Task not found: {}", task_name));
}
log::error!("Task not found: {}", task_name);
if log::log_enabled!(log::Level::Error) {
print_available_tasks(
&mut std::io::stderr(),
&cli_options.start_dir,
&tasks_config,
)?;
}
Ok(1)
}
} }
.unwrap_or_else(|| NonZeroUsize::new(2).unwrap());
let task_runner = TaskRunner {
task_flags: &task_flags,
npm_resolver: npm_resolver.as_ref(),
node_resolver: node_resolver.as_ref(),
env_vars,
cli_options,
concurrency: no_of_concurrent_tasks.into(),
};
let kill_signal = KillSignal::default();
run_future_forwarding_signals(kill_signal.clone(), async {
if task_flags.eval {
return task_runner
.run_deno_task(
&Url::from_directory_path(cli_options.initial_cwd()).unwrap(),
"",
&TaskDefinition {
command: task_flags.task.as_ref().unwrap().to_string(),
dependencies: vec![],
description: None,
},
kill_signal,
)
.await;
}
for task_config in &packages_task_configs {
let exit_code = task_runner.run_tasks(task_config, &kill_signal).await?;
if exit_code > 0 {
return Ok(exit_code);
}
}
Ok(0)
})
.await
} }
struct RunTaskOptions<'a> { struct RunSingleOptions<'a> {
task_name: &'a str, task_name: &'a str,
script: &'a str, script: &'a str,
cwd: &'a Path, cwd: &'a Path,
env_vars: HashMap<String, String>,
custom_commands: HashMap<String, Rc<dyn ShellCommand>>, custom_commands: HashMap<String, Rc<dyn ShellCommand>>,
npm_resolver: &'a dyn CliNpmResolver, kill_signal: KillSignal,
cli_options: &'a CliOptions,
} }
async fn run_task(opts: RunTaskOptions<'_>) -> Result<i32, AnyError> { struct TaskRunner<'a> {
let RunTaskOptions { task_flags: &'a TaskFlags,
task_name, npm_resolver: &'a dyn CliNpmResolver,
script, node_resolver: &'a NodeResolver,
cwd, env_vars: HashMap<String, String>,
env_vars, cli_options: &'a CliOptions,
custom_commands, concurrency: usize,
npm_resolver, }
cli_options,
} = opts;
output_task( impl<'a> TaskRunner<'a> {
opts.task_name, pub async fn run_tasks(
&task_runner::get_script_with_args(script, cli_options.argv()), &self,
); pkg_tasks_config: &PackageTaskInfo,
kill_signal: &KillSignal,
) -> Result<i32, deno_core::anyhow::Error> {
match sort_tasks_topo(pkg_tasks_config) {
Ok(sorted) => self.run_tasks_in_parallel(sorted, kill_signal).await,
Err(err) => match err {
TaskError::NotFound(name) => {
if self.task_flags.is_run {
return Err(anyhow!("Task not found: {}", name));
}
Ok( log::error!("Task not found: {}", name);
task_runner::run_task(task_runner::RunTaskOptions { if log::log_enabled!(log::Level::Error) {
self.print_available_tasks(&pkg_tasks_config.tasks_config)?;
}
Ok(1)
}
TaskError::TaskDepCycle { path } => {
log::error!("Task cycle detected: {}", path.join(" -> "));
Ok(1)
}
},
}
}
pub fn print_available_tasks(
&self,
tasks_config: &WorkspaceTasksConfig,
) -> Result<(), std::io::Error> {
print_available_tasks(
&mut std::io::stderr(),
&self.cli_options.start_dir,
tasks_config,
)
}
async fn run_tasks_in_parallel(
&self,
tasks: Vec<ResolvedTask<'a>>,
kill_signal: &KillSignal,
) -> Result<i32, deno_core::anyhow::Error> {
struct PendingTasksContext<'a> {
completed: HashSet<usize>,
running: HashSet<usize>,
tasks: &'a [ResolvedTask<'a>],
}
impl<'a> PendingTasksContext<'a> {
fn has_remaining_tasks(&self) -> bool {
self.completed.len() < self.tasks.len()
}
fn mark_complete(&mut self, task: &ResolvedTask) {
self.running.remove(&task.id);
self.completed.insert(task.id);
}
fn get_next_task<'b>(
&mut self,
runner: &'b TaskRunner<'b>,
kill_signal: &KillSignal,
) -> Option<
LocalBoxFuture<'b, Result<(i32, &'a ResolvedTask<'a>), AnyError>>,
>
where
'a: 'b,
{
for task in self.tasks.iter() {
if self.completed.contains(&task.id)
|| self.running.contains(&task.id)
{
continue;
}
let should_run = task
.dependencies
.iter()
.all(|dep_id| self.completed.contains(dep_id));
if !should_run {
continue;
}
self.running.insert(task.id);
let kill_signal = kill_signal.clone();
return Some(
async move {
match task.task_or_script {
TaskOrScript::Task(_, def) => {
runner
.run_deno_task(task.folder_url, task.name, def, kill_signal)
.await
}
TaskOrScript::Script(scripts, _) => {
runner
.run_npm_script(
task.folder_url,
task.name,
scripts,
kill_signal,
)
.await
}
}
.map(|exit_code| (exit_code, task))
}
.boxed_local(),
);
}
None
}
}
let mut context = PendingTasksContext {
completed: HashSet::with_capacity(tasks.len()),
running: HashSet::with_capacity(self.concurrency),
tasks: &tasks,
};
let mut queue = futures_unordered::FuturesUnordered::new();
while context.has_remaining_tasks() {
while queue.len() < self.concurrency {
if let Some(task) = context.get_next_task(self, kill_signal) {
queue.push(task);
} else {
break;
}
}
// If queue is empty at this point, then there are no more tasks in the queue.
let Some(result) = queue.next().await else {
debug_assert_eq!(context.tasks.len(), 0);
break;
};
let (exit_code, name) = result?;
if exit_code > 0 {
return Ok(exit_code);
}
context.mark_complete(name);
}
Ok(0)
}
pub async fn run_deno_task(
&self,
dir_url: &Url,
task_name: &str,
definition: &TaskDefinition,
kill_signal: KillSignal,
) -> Result<i32, deno_core::anyhow::Error> {
let cwd = match &self.task_flags.cwd {
Some(path) => canonicalize_path(&PathBuf::from(path))
.context("failed canonicalizing --cwd")?,
None => normalize_path(dir_url.to_file_path().unwrap()),
};
let custom_commands = task_runner::resolve_custom_commands(
self.npm_resolver,
self.node_resolver,
)?;
self
.run_single(RunSingleOptions {
task_name,
script: &definition.command,
cwd: &cwd,
custom_commands,
kill_signal,
})
.await
}
pub async fn run_npm_script(
&self,
dir_url: &Url,
task_name: &str,
scripts: &IndexMap<String, String>,
kill_signal: KillSignal,
) -> Result<i32, deno_core::anyhow::Error> {
// ensure the npm packages are installed if using a managed resolver
if let Some(npm_resolver) = self.npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
}
let cwd = match &self.task_flags.cwd {
Some(path) => canonicalize_path(&PathBuf::from(path))?,
None => normalize_path(dir_url.to_file_path().unwrap()),
};
// At this point we already checked if the task name exists in package.json.
// We can therefore check for "pre" and "post" scripts too, since we're only
// dealing with package.json here and not deno.json
let task_names = vec![
format!("pre{}", task_name),
task_name.to_string(),
format!("post{}", task_name),
];
let custom_commands = task_runner::resolve_custom_commands(
self.npm_resolver,
self.node_resolver,
)?;
for task_name in &task_names {
if let Some(script) = scripts.get(task_name) {
let exit_code = self
.run_single(RunSingleOptions {
task_name,
script,
cwd: &cwd,
custom_commands: custom_commands.clone(),
kill_signal: kill_signal.clone(),
})
.await?;
if exit_code > 0 {
return Ok(exit_code);
}
}
}
Ok(0)
}
async fn run_single(
&self,
opts: RunSingleOptions<'_>,
) -> Result<i32, AnyError> {
let RunSingleOptions {
task_name, task_name,
script, script,
cwd, cwd,
env_vars,
custom_commands, custom_commands,
init_cwd: opts.cli_options.initial_cwd(), kill_signal,
argv: cli_options.argv(), } = opts;
root_node_modules_dir: npm_resolver.root_node_modules_path(),
stdio: None, output_task(
}) opts.task_name,
.await? &task_runner::get_script_with_args(script, self.cli_options.argv()),
.exit_code, );
)
Ok(
task_runner::run_task(task_runner::RunTaskOptions {
task_name,
script,
cwd,
env_vars: self.env_vars.clone(),
custom_commands,
init_cwd: self.cli_options.initial_cwd(),
argv: self.cli_options.argv(),
root_node_modules_dir: self.npm_resolver.root_node_modules_path(),
stdio: None,
kill_signal,
})
.await?
.exit_code,
)
}
}
#[derive(Debug)]
enum TaskError {
NotFound(String),
TaskDepCycle { path: Vec<String> },
}
struct ResolvedTask<'a> {
id: usize,
name: &'a str,
folder_url: &'a Url,
task_or_script: TaskOrScript<'a>,
dependencies: Vec<usize>,
}
fn sort_tasks_topo<'a>(
pkg_task_config: &'a PackageTaskInfo,
) -> Result<Vec<ResolvedTask<'a>>, TaskError> {
trait TasksConfig {
fn task(
&self,
name: &str,
) -> Option<(&Url, TaskOrScript, &dyn TasksConfig)>;
}
impl TasksConfig for WorkspaceTasksConfig {
fn task(
&self,
name: &str,
) -> Option<(&Url, TaskOrScript, &dyn TasksConfig)> {
if let Some(member) = &self.member {
if let Some((dir_url, task_or_script)) = member.task(name) {
return Some((dir_url, task_or_script, self as &dyn TasksConfig));
}
}
if let Some(root) = &self.root {
if let Some((dir_url, task_or_script)) = root.task(name) {
// switch to only using the root tasks for the dependencies
return Some((dir_url, task_or_script, root as &dyn TasksConfig));
}
}
None
}
}
impl TasksConfig for WorkspaceMemberTasksConfig {
fn task(
&self,
name: &str,
) -> Option<(&Url, TaskOrScript, &dyn TasksConfig)> {
self.task(name).map(|(dir_url, task_or_script)| {
(dir_url, task_or_script, self as &dyn TasksConfig)
})
}
}
fn sort_visit<'a>(
name: &'a str,
sorted: &mut Vec<ResolvedTask<'a>>,
mut path: Vec<(&'a Url, &'a str)>,
tasks_config: &'a dyn TasksConfig,
) -> Result<usize, TaskError> {
let Some((folder_url, task_or_script, tasks_config)) =
tasks_config.task(name)
else {
return Err(TaskError::NotFound(name.to_string()));
};
if let Some(existing_task) = sorted
.iter()
.find(|task| task.name == name && task.folder_url == folder_url)
{
// already exists
return Ok(existing_task.id);
}
if path.contains(&(folder_url, name)) {
path.push((folder_url, name));
return Err(TaskError::TaskDepCycle {
path: path.iter().map(|(_, s)| s.to_string()).collect(),
});
}
let mut dependencies: Vec<usize> = Vec::new();
if let TaskOrScript::Task(_, task) = task_or_script {
dependencies.reserve(task.dependencies.len());
for dep in &task.dependencies {
let mut path = path.clone();
path.push((folder_url, name));
dependencies.push(sort_visit(dep, sorted, path, tasks_config)?);
}
}
let id = sorted.len();
sorted.push(ResolvedTask {
id,
name,
folder_url,
task_or_script,
dependencies,
});
Ok(id)
}
let mut sorted: Vec<ResolvedTask<'a>> = vec![];
for name in &pkg_task_config.matched_tasks {
sort_visit(name, &mut sorted, Vec::new(), &pkg_task_config.tasks_config)?;
}
Ok(sorted)
} }
fn output_task(task_name: &str, script: &str) { fn output_task(task_name: &str, script: &str) {
@ -222,80 +672,123 @@ fn print_available_tasks(
" {}", " {}",
colors::red("No tasks found in configuration file") colors::red("No tasks found in configuration file")
)?; )?;
} else { return Ok(());
let mut seen_task_names = }
HashSet::with_capacity(tasks_config.tasks_count());
for maybe_config in [&tasks_config.member, &tasks_config.root] { struct AvailableTaskDescription {
let Some(config) = maybe_config else { is_root: bool,
continue; is_deno: bool,
}; name: String,
for (is_root, is_deno, (key, task)) in config task: TaskDefinition,
.deno_json }
.as_ref() let mut seen_task_names = HashSet::with_capacity(tasks_config.tasks_count());
.map(|config| { let mut task_descriptions = Vec::with_capacity(tasks_config.tasks_count());
let is_root = !is_cwd_root_dir
&& config.folder_url for maybe_config in [&tasks_config.member, &tasks_config.root] {
== *workspace_dir.workspace.root_dir().as_ref(); let Some(config) = maybe_config else {
config continue;
.tasks };
.iter()
.map(move |(k, t)| (is_root, true, (k, Cow::Borrowed(t)))) if let Some(config) = config.deno_json.as_ref() {
}) let is_root = !is_cwd_root_dir
.into_iter() && config.folder_url == *workspace_dir.workspace.root_dir().as_ref();
.flatten()
.chain( for (name, definition) in &config.tasks {
config if !seen_task_names.insert(name) {
.package_json
.as_ref()
.map(|config| {
let is_root = !is_cwd_root_dir
&& config.folder_url
== *workspace_dir.workspace.root_dir().as_ref();
config.tasks.iter().map(move |(k, v)| {
(is_root, false, (k, Cow::Owned(Task::Definition(v.clone()))))
})
})
.into_iter()
.flatten(),
)
{
if !seen_task_names.insert(key) {
continue; // already seen continue; // already seen
} }
task_descriptions.push(AvailableTaskDescription {
is_root,
is_deno: true,
name: name.to_string(),
task: definition.clone(),
});
}
}
if let Some(config) = config.package_json.as_ref() {
let is_root = !is_cwd_root_dir
&& config.folder_url == *workspace_dir.workspace.root_dir().as_ref();
for (name, script) in &config.tasks {
if !seen_task_names.insert(name) {
continue; // already seen
}
task_descriptions.push(AvailableTaskDescription {
is_root,
is_deno: false,
name: name.to_string(),
task: deno_config::deno_json::TaskDefinition {
command: script.to_string(),
dependencies: vec![],
description: None,
},
});
}
}
}
for desc in task_descriptions {
writeln!(
writer,
"- {}{}",
colors::cyan(desc.name),
if desc.is_root {
if desc.is_deno {
format!(" {}", colors::italic_gray("(workspace)"))
} else {
format!(" {}", colors::italic_gray("(workspace package.json)"))
}
} else if desc.is_deno {
"".to_string()
} else {
format!(" {}", colors::italic_gray("(package.json)"))
}
)?;
if let Some(description) = &desc.task.description {
let slash_slash = colors::italic_gray("//");
for line in description.lines() {
writeln!( writeln!(
writer, writer,
"- {}{}", " {slash_slash} {}",
colors::cyan(key), colors::italic_gray(strip_ansi_codes_and_escape_control_chars(line))
if is_root {
if is_deno {
format!(" {}", colors::italic_gray("(workspace)"))
} else {
format!(" {}", colors::italic_gray("(workspace package.json)"))
}
} else if is_deno {
"".to_string()
} else {
format!(" {}", colors::italic_gray("(package.json)"))
}
)?; )?;
let definition = match task.as_ref() {
Task::Definition(definition) => definition,
Task::Commented { definition, .. } => definition,
};
if let Task::Commented { comments, .. } = task.as_ref() {
let slash_slash = colors::italic_gray("//");
for comment in comments {
writeln!(
writer,
" {slash_slash} {}",
colors::italic_gray(comment)
)?;
}
}
writeln!(writer, " {definition}")?;
} }
} }
writeln!(
writer,
" {}",
strip_ansi_codes_and_escape_control_chars(&desc.task.command)
)?;
if !desc.task.dependencies.is_empty() {
let dependencies = desc
.task
.dependencies
.into_iter()
.map(|d| strip_ansi_codes_and_escape_control_chars(&d))
.collect::<Vec<_>>()
.join(", ");
writeln!(
writer,
" {} {}",
colors::gray("depends on:"),
colors::cyan(dependencies)
)?;
}
} }
Ok(()) Ok(())
} }
fn strip_ansi_codes_and_escape_control_chars(s: &str) -> String {
strip_ansi_codes(s)
.chars()
.map(|c| match c {
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
c if c.is_control() => format!("\\x{:02x}", c as u8),
c => c.to_string(),
})
.collect()
}

View file

@ -1357,6 +1357,7 @@ pub async fn report_tests(
if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) { if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) {
eprint!("Test reporter failed to flush: {}", err) eprint!("Test reporter failed to flush: {}", err)
} }
#[allow(clippy::disallowed_methods)]
std::process::exit(130); std::process::exit(130);
} }
} }
@ -1642,6 +1643,7 @@ pub async fn run_tests_with_watch(
loop { loop {
signal::ctrl_c().await.unwrap(); signal::ctrl_c().await.unwrap();
if !HAS_TEST_RUN_SIGINT_HANDLER.load(Ordering::Relaxed) { if !HAS_TEST_RUN_SIGINT_HANDLER.load(Ordering::Relaxed) {
#[allow(clippy::disallowed_methods)]
std::process::exit(130); std::process::exit(130);
} }
} }
@ -1659,6 +1661,7 @@ pub async fn run_tests_with_watch(
), ),
move |flags, watcher_communicator, changed_paths| { move |flags, watcher_communicator, changed_paths| {
let test_flags = test_flags.clone(); let test_flags = test_flags.clone();
watcher_communicator.show_path_changed(changed_paths.clone());
Ok(async move { Ok(async move {
let factory = CliFactory::from_flags_for_watcher( let factory = CliFactory::from_flags_for_watcher(
flags, flags,

View file

@ -540,7 +540,7 @@ pub async fn upgrade(
let Some(archive_data) = download_package(&client, download_url).await? let Some(archive_data) = download_package(&client, download_url).await?
else { else {
log::error!("Download could not be found, aborting"); log::error!("Download could not be found, aborting");
std::process::exit(1) deno_runtime::exit(1)
}; };
log::info!( log::info!(

View file

@ -450,6 +450,12 @@ delete Object.prototype.__proto__;
// We specify the resolution mode to be CommonJS for some npm files and this // We specify the resolution mode to be CommonJS for some npm files and this
// diagnostic gets generated even though we're using custom module resolution. // diagnostic gets generated even though we're using custom module resolution.
1452, 1452,
// Module '...' cannot be imported using this construct. The specifier only resolves to an
// ES module, which cannot be imported with 'require'.
1471,
// TS1479: The current file is a CommonJS module whose imports will produce 'require' calls;
// however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
1479,
// TS2306: File '.../index.d.ts' is not a module. // TS2306: File '.../index.d.ts' is not a module.
// We get this for `x-typescript-types` declaration files which don't export // We get this for `x-typescript-types` declaration files which don't export
// anything. We prefer to treat these as modules with no exports. // anything. We prefer to treat these as modules with no exports.
@ -675,14 +681,18 @@ delete Object.prototype.__proto__;
getNewLine() { getNewLine() {
return "\n"; return "\n";
}, },
resolveTypeReferenceDirectives( resolveTypeReferenceDirectiveReferences(
typeDirectiveNames, typeDirectiveReferences,
containingFilePath, containingFilePath,
redirectedReference, redirectedReference,
options, options,
containingFileMode, containingSourceFile,
_reusedNames,
) { ) {
return typeDirectiveNames.map((arg) => { const isCjs =
containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS;
/** @type {Array<ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations>} */
const result = typeDirectiveReferences.map((arg) => {
/** @type {ts.FileReference} */ /** @type {ts.FileReference} */
const fileReference = typeof arg === "string" const fileReference = typeof arg === "string"
? { ? {
@ -695,16 +705,28 @@ delete Object.prototype.__proto__;
/** @type {[string, ts.Extension] | undefined} */ /** @type {[string, ts.Extension] | undefined} */
const resolved = ops.op_resolve( const resolved = ops.op_resolve(
containingFilePath, containingFilePath,
containingFileMode === ts.ModuleKind.CommonJS, [
[fileReference.fileName], [
fileReference.resolutionMode == null
? isCjs
: fileReference.resolutionMode === ts.ModuleKind.CommonJS,
fileReference.fileName,
],
],
)?.[0]; )?.[0];
if (resolved) { if (resolved) {
return { return {
primary: true, resolvedTypeReferenceDirective: {
resolvedFileName: resolved[0], primary: true,
resolvedFileName: resolved[0],
// todo(dsherret): we should probably be setting this
isExternalLibraryImport: undefined,
},
}; };
} else { } else {
return undefined; return {
resolvedTypeReferenceDirective: undefined,
};
} }
} else { } else {
return ts.resolveTypeReferenceDirective( return ts.resolveTypeReferenceDirective(
@ -714,41 +736,56 @@ delete Object.prototype.__proto__;
host, host,
redirectedReference, redirectedReference,
undefined, undefined,
containingFileMode ?? fileReference.resolutionMode, containingSourceFile?.impliedNodeFormat ??
).resolvedTypeReferenceDirective; fileReference.resolutionMode,
);
} }
}); });
return result;
}, },
resolveModuleNames( resolveModuleNameLiterals(
specifiers, moduleLiterals,
base, base,
_reusedNames,
_redirectedReference, _redirectedReference,
_options, compilerOptions,
containingSourceFile, containingSourceFile,
_reusedNames,
) { ) {
const specifiers = moduleLiterals.map((literal) => [
ts.getModeForUsageLocation(
containingSourceFile,
literal,
compilerOptions,
) === ts.ModuleKind.CommonJS,
literal.text,
]);
if (logDebug) { if (logDebug) {
debug(`host.resolveModuleNames()`); debug(`host.resolveModuleNames()`);
debug(` base: ${base}`); debug(` base: ${base}`);
debug(` specifiers: ${specifiers.join(", ")}`); debug(` specifiers: ${specifiers.map((s) => s[1]).join(", ")}`);
} }
/** @type {Array<[string, ts.Extension] | undefined>} */ /** @type {Array<[string, ts.Extension] | undefined>} */
const resolved = ops.op_resolve( const resolved = ops.op_resolve(
base, base,
containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS,
specifiers, specifiers,
); );
if (resolved) { if (resolved) {
/** @type {Array<ts.ResolvedModuleWithFailedLookupLocations>} */
const result = resolved.map((item) => { const result = resolved.map((item) => {
if (item) { if (item) {
const [resolvedFileName, extension] = item; const [resolvedFileName, extension] = item;
return { return {
resolvedFileName, resolvedModule: {
extension, resolvedFileName,
isExternalLibraryImport: false, extension,
// todo(dsherret): we should probably be setting this
isExternalLibraryImport: false,
},
}; };
} }
return undefined; return {
resolvedModule: undefined,
};
}); });
result.length = specifiers.length; result.length = specifiers.length;
return result; return result;

View file

@ -4535,7 +4535,7 @@ declare namespace Deno {
/** The object that is returned from a {@linkcode Deno.upgradeWebSocket} /** The object that is returned from a {@linkcode Deno.upgradeWebSocket}
* request. * request.
* *
* @category Web Sockets */ * @category WebSockets */
export interface WebSocketUpgrade { export interface WebSocketUpgrade {
/** The response object that represents the HTTP response to the client, /** The response object that represents the HTTP response to the client,
* which should be used to the {@linkcode RequestEvent} `.respondWith()` for * which should be used to the {@linkcode RequestEvent} `.respondWith()` for
@ -4549,7 +4549,7 @@ declare namespace Deno {
/** Options which can be set when performing a /** Options which can be set when performing a
* {@linkcode Deno.upgradeWebSocket} upgrade of a {@linkcode Request} * {@linkcode Deno.upgradeWebSocket} upgrade of a {@linkcode Request}
* *
* @category Web Sockets */ * @category WebSockets */
export interface UpgradeWebSocketOptions { export interface UpgradeWebSocketOptions {
/** Sets the `.protocol` property on the client side web socket to the /** Sets the `.protocol` property on the client side web socket to the
* value provided here, which should be one of the strings specified in the * value provided here, which should be one of the strings specified in the
@ -4597,7 +4597,7 @@ declare namespace Deno {
* This operation does not yet consume the request or open the websocket. This * This operation does not yet consume the request or open the websocket. This
* only happens once the returned response has been passed to `respondWith()`. * only happens once the returned response has been passed to `respondWith()`.
* *
* @category Web Sockets * @category WebSockets
*/ */
export function upgradeWebSocket( export function upgradeWebSocket(
request: Request, request: Request,

View file

@ -15,14 +15,14 @@
/// <reference lib="deno.crypto" /> /// <reference lib="deno.crypto" />
/// <reference lib="deno.ns" /> /// <reference lib="deno.ns" />
/** @category WASM */ /** @category Wasm */
declare namespace WebAssembly { declare namespace WebAssembly {
/** /**
* The `WebAssembly.CompileError` object indicates an error during WebAssembly decoding or validation. * The `WebAssembly.CompileError` object indicates an error during WebAssembly decoding or validation.
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/CompileError) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/CompileError)
* *
* @category WASM * @category Wasm
*/ */
export class CompileError extends Error { export class CompileError extends Error {
/** Creates a new `WebAssembly.CompileError` object. */ /** Creates a new `WebAssembly.CompileError` object. */
@ -36,7 +36,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global)
* *
* @category WASM * @category Wasm
*/ */
export class Global { export class Global {
/** Creates a new `Global` object. */ /** Creates a new `Global` object. */
@ -59,7 +59,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance)
* *
* @category WASM * @category Wasm
*/ */
export class Instance { export class Instance {
/** Creates a new Instance object. */ /** Creates a new Instance object. */
@ -79,7 +79,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError)
* *
* @category WASM * @category Wasm
*/ */
export class LinkError extends Error { export class LinkError extends Error {
/** Creates a new WebAssembly.LinkError object. */ /** Creates a new WebAssembly.LinkError object. */
@ -95,7 +95,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory)
* *
* @category WASM * @category Wasm
*/ */
export class Memory { export class Memory {
/** Creates a new `Memory` object. */ /** Creates a new `Memory` object. */
@ -117,7 +117,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module)
* *
* @category WASM * @category Wasm
*/ */
export class Module { export class Module {
/** Creates a new `Module` object. */ /** Creates a new `Module` object. */
@ -145,7 +145,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/RuntimeError) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/RuntimeError)
* *
* @category WASM * @category Wasm
*/ */
export class RuntimeError extends Error { export class RuntimeError extends Error {
/** Creates a new `WebAssembly.RuntimeError` object. */ /** Creates a new `WebAssembly.RuntimeError` object. */
@ -160,7 +160,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table)
* *
* @category WASM * @category Wasm
*/ */
export class Table { export class Table {
/** Creates a new `Table` object. */ /** Creates a new `Table` object. */
@ -182,7 +182,7 @@ declare namespace WebAssembly {
/** The `GlobalDescriptor` describes the options you can pass to /** The `GlobalDescriptor` describes the options you can pass to
* `new WebAssembly.Global()`. * `new WebAssembly.Global()`.
* *
* @category WASM * @category Wasm
*/ */
export interface GlobalDescriptor { export interface GlobalDescriptor {
mutable?: boolean; mutable?: boolean;
@ -192,7 +192,7 @@ declare namespace WebAssembly {
/** The `MemoryDescriptor` describes the options you can pass to /** The `MemoryDescriptor` describes the options you can pass to
* `new WebAssembly.Memory()`. * `new WebAssembly.Memory()`.
* *
* @category WASM * @category Wasm
*/ */
export interface MemoryDescriptor { export interface MemoryDescriptor {
initial: number; initial: number;
@ -203,7 +203,7 @@ declare namespace WebAssembly {
/** A `ModuleExportDescriptor` is the description of a declared export in a /** A `ModuleExportDescriptor` is the description of a declared export in a
* `WebAssembly.Module`. * `WebAssembly.Module`.
* *
* @category WASM * @category Wasm
*/ */
export interface ModuleExportDescriptor { export interface ModuleExportDescriptor {
kind: ImportExportKind; kind: ImportExportKind;
@ -213,7 +213,7 @@ declare namespace WebAssembly {
/** A `ModuleImportDescriptor` is the description of a declared import in a /** A `ModuleImportDescriptor` is the description of a declared import in a
* `WebAssembly.Module`. * `WebAssembly.Module`.
* *
* @category WASM * @category Wasm
*/ */
export interface ModuleImportDescriptor { export interface ModuleImportDescriptor {
kind: ImportExportKind; kind: ImportExportKind;
@ -224,7 +224,7 @@ declare namespace WebAssembly {
/** The `TableDescriptor` describes the options you can pass to /** The `TableDescriptor` describes the options you can pass to
* `new WebAssembly.Table()`. * `new WebAssembly.Table()`.
* *
* @category WASM * @category Wasm
*/ */
export interface TableDescriptor { export interface TableDescriptor {
element: TableKind; element: TableKind;
@ -234,7 +234,7 @@ declare namespace WebAssembly {
/** The value returned from `WebAssembly.instantiate`. /** The value returned from `WebAssembly.instantiate`.
* *
* @category WASM * @category Wasm
*/ */
export interface WebAssemblyInstantiatedSource { export interface WebAssemblyInstantiatedSource {
/* A `WebAssembly.Instance` object that contains all the exported WebAssembly functions. */ /* A `WebAssembly.Instance` object that contains all the exported WebAssembly functions. */
@ -247,21 +247,21 @@ declare namespace WebAssembly {
module: Module; module: Module;
} }
/** @category WASM */ /** @category Wasm */
export type ImportExportKind = "function" | "global" | "memory" | "table"; export type ImportExportKind = "function" | "global" | "memory" | "table";
/** @category WASM */ /** @category Wasm */
export type TableKind = "anyfunc"; export type TableKind = "anyfunc";
/** @category WASM */ /** @category Wasm */
export type ValueType = "f32" | "f64" | "i32" | "i64"; export type ValueType = "f32" | "f64" | "i32" | "i64";
/** @category WASM */ /** @category Wasm */
export type ExportValue = Function | Global | Memory | Table; export type ExportValue = Function | Global | Memory | Table;
/** @category WASM */ /** @category Wasm */
export type Exports = Record<string, ExportValue>; export type Exports = Record<string, ExportValue>;
/** @category WASM */ /** @category Wasm */
export type ImportValue = ExportValue | number; export type ImportValue = ExportValue | number;
/** @category WASM */ /** @category Wasm */
export type ModuleImports = Record<string, ImportValue>; export type ModuleImports = Record<string, ImportValue>;
/** @category WASM */ /** @category Wasm */
export type Imports = Record<string, ModuleImports>; export type Imports = Record<string, ModuleImports>;
/** /**
@ -272,7 +272,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compile) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compile)
* *
* @category WASM * @category Wasm
*/ */
export function compile(bytes: BufferSource): Promise<Module>; export function compile(bytes: BufferSource): Promise<Module>;
@ -284,7 +284,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming)
* *
* @category WASM * @category Wasm
*/ */
export function compileStreaming( export function compileStreaming(
source: Response | Promise<Response>, source: Response | Promise<Response>,
@ -301,7 +301,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)
* *
* @category WASM * @category Wasm
*/ */
export function instantiate( export function instantiate(
bytes: BufferSource, bytes: BufferSource,
@ -318,7 +318,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)
* *
* @category WASM * @category Wasm
*/ */
export function instantiate( export function instantiate(
moduleObject: Module, moduleObject: Module,
@ -332,7 +332,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming)
* *
* @category WASM * @category Wasm
*/ */
export function instantiateStreaming( export function instantiateStreaming(
response: Response | PromiseLike<Response>, response: Response | PromiseLike<Response>,
@ -346,7 +346,7 @@ declare namespace WebAssembly {
* *
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate) * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate)
* *
* @category WASM * @category Wasm
*/ */
export function validate(bytes: BufferSource): boolean; export function validate(bytes: BufferSource): boolean;
} }

View file

@ -1180,6 +1180,32 @@ declare namespace Deno {
...values: unknown[] ...values: unknown[]
): Displayable; ): Displayable;
/**
* Display a JPG or PNG image.
*
* ```
* Deno.jupyter.image("./cat.jpg");
* Deno.jupyter.image("./dog.png");
* ```
*
* @category Jupyter
* @experimental
*/
export function image(path: string): Displayable;
/**
* Display a JPG or PNG image.
*
* ```
* const img = Deno.readFileSync("./cat.jpg");
* Deno.jupyter.image(img);
* ```
*
* @category Jupyter
* @experimental
*/
export function image(data: Uint8Array): Displayable;
/** /**
* Format an object for displaying in Deno * Format an object for displaying in Deno
* *
@ -1226,80 +1252,53 @@ declare namespace Deno {
} }
/** /**
* **UNSTABLE**: New API, yet to be vetted.
*
* APIs for working with the OpenTelemetry observability framework. Deno can
* export traces, metrics, and logs to OpenTelemetry compatible backends via
* the OTLP protocol.
*
* Deno automatically instruments the runtime with OpenTelemetry traces and
* metrics. This data is exported via OTLP to OpenTelemetry compatible
* backends. User logs from the `console` API are exported as OpenTelemetry
* logs via OTLP.
*
* User code can also create custom traces, metrics, and logs using the
* OpenTelemetry API. This is done using the official OpenTelemetry package
* for JavaScript:
* [`npm:@opentelemetry/api`](https://opentelemetry.io/docs/languages/js/).
* Deno integrates with this package to provide trace context propagation
* between native Deno APIs (like `Deno.serve` or `fetch`) and custom user
* code. Deno also provides APIs that allow exporting custom telemetry data
* via the same OTLP channel used by the Deno runtime. This is done using the
* [`jsr:@deno/otel`](https://jsr.io/@deno/otel) package.
*
* @example Using OpenTelemetry API to create custom traces
* ```ts,ignore
* import { trace } from "npm:@opentelemetry/api@1";
* import "jsr:@deno/otel@0.0.2/register";
*
* const tracer = trace.getTracer("example-tracer");
*
* async function doWork() {
* return tracer.startActiveSpan("doWork", async (span) => {
* span.setAttribute("key", "value");
* await new Promise((resolve) => setTimeout(resolve, 1000));
* span.end();
* });
* }
*
* Deno.serve(async (req) => {
* await doWork();
* const resp = await fetch("https://example.com");
* return resp;
* });
* ```
*
* @category Telemetry * @category Telemetry
* @experimental * @experimental
*/ */
export namespace tracing { export namespace telemetry {
/**
* Whether tracing is enabled.
* @category Telemetry
* @experimental
*/
export const enabled: boolean;
/**
* Allowed attribute type.
* @category Telemetry
* @experimental
*/
export type AttributeValue = string | number | boolean | bigint;
/**
* A tracing span.
* @category Telemetry
* @experimental
*/
export class Span implements Disposable {
readonly traceId: string;
readonly spanId: string;
readonly parentSpanId: string;
readonly kind: string;
readonly name: string;
readonly startTime: number;
readonly endTime: number;
readonly status: null | { code: 1 } | { code: 2; message: string };
readonly attributes: Record<string, AttributeValue>;
readonly traceFlags: number;
/**
* Construct a new Span and enter it as the "current" span.
*/
constructor(
name: string,
kind?: "internal" | "server" | "client" | "producer" | "consumer",
);
/**
* Set an attribute on this span.
*/
setAttribute(
name: string,
value: AttributeValue,
): void;
/**
* Enter this span as the "current" span.
*/
enter(): void;
/**
* Exit this span as the "current" span and restore the previous one.
*/
exit(): void;
/**
* End this span, and exit it as the "current" span.
*/
end(): void;
[Symbol.dispose](): void;
/**
* Get the "current" span, if one exists.
*/
static current(): Span | undefined | null;
}
/** /**
* A SpanExporter compatible with OpenTelemetry.js * A SpanExporter compatible with OpenTelemetry.js
* https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_trace_base.SpanExporter.html * https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_trace_base.SpanExporter.html
@ -1319,14 +1318,6 @@ declare namespace Deno {
export {}; // only export exports export {}; // only export exports
} }
/**
* @category Telemetry
* @experimental
*/
export namespace metrics {
export {}; // only export exports
}
export {}; // only export exports export {}; // only export exports
} }

View file

@ -18277,7 +18277,7 @@ declare var ReadableStream: {
new(underlyingSource: UnderlyingByteSource, strategy?: { highWaterMark?: number }): ReadableStream<Uint8Array>; new(underlyingSource: UnderlyingByteSource, strategy?: { highWaterMark?: number }): ReadableStream<Uint8Array>;
new<R = any>(underlyingSource: UnderlyingDefaultSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>; new<R = any>(underlyingSource: UnderlyingDefaultSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
new<R = any>(underlyingSource?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>; new<R = any>(underlyingSource?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
from<R>(asyncIterable: AsyncIterable<R> | Iterable<R | PromiseLike<R>>): ReadableStream<R>; from<R>(asyncIterable: AsyncIterable<R> | Iterable<R | PromiseLike<R>> & object): ReadableStream<R>;
}; };
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */

View file

@ -6,10 +6,10 @@ use crate::cache::FastInsecureHasher;
use crate::cache::ModuleInfoCache; use crate::cache::ModuleInfoCache;
use crate::node; use crate::node;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::npm::ResolvePkgFolderFromDenoReqError;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use crate::util::checksum; use crate::util::checksum;
use crate::util::path::mapped_specifier_for_tsc; use crate::util::path::mapped_specifier_for_tsc;
use crate::worker::create_isolate_create_params;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
@ -34,14 +34,15 @@ use deno_graph::GraphKind;
use deno_graph::Module; use deno_graph::Module;
use deno_graph::ModuleGraph; use deno_graph::ModuleGraph;
use deno_graph::ResolutionResolved; use deno_graph::ResolutionResolved;
use deno_resolver::npm::ResolvePkgFolderFromDenoReqError;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use node_resolver::errors::NodeJsErrorCode; use node_resolver::errors::NodeJsErrorCode;
use node_resolver::errors::NodeJsErrorCoded; use node_resolver::errors::NodeJsErrorCoded;
use node_resolver::errors::PackageSubpathResolveError; use node_resolver::errors::PackageSubpathResolveError;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolutionMode; use node_resolver::ResolutionMode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
@ -649,19 +650,27 @@ fn op_load_inner(
media_type = MediaType::Json; media_type = MediaType::Json;
Some(Cow::Borrowed(&*module.source)) Some(Cow::Borrowed(&*module.source))
} }
Module::Wasm(module) => {
media_type = MediaType::Dts;
Some(Cow::Borrowed(&*module.source_dts))
}
Module::Npm(_) | Module::Node(_) => None, Module::Npm(_) | Module::Node(_) => None,
Module::External(module) => { Module::External(module) => {
// means it's Deno code importing an npm module if module.specifier.scheme() != "file" {
let specifier = node::resolve_specifier_into_node_modules( None
&module.specifier, } else {
&deno_fs::RealFs, // means it's Deno code importing an npm module
); let specifier = node::resolve_specifier_into_node_modules(
Some(Cow::Owned(load_from_node_modules( &module.specifier,
&specifier, &deno_fs::RealFs,
state.maybe_npm.as_ref(), );
&mut media_type, Some(Cow::Owned(load_from_node_modules(
&mut is_cjs, &specifier,
)?)) state.maybe_npm.as_ref(),
&mut media_type,
&mut is_cjs,
)?))
}
} }
} }
} else if let Some(npm) = state } else if let Some(npm) = state
@ -698,10 +707,9 @@ pub struct ResolveArgs {
/// The base specifier that the supplied specifier strings should be resolved /// The base specifier that the supplied specifier strings should be resolved
/// relative to. /// relative to.
pub base: String, pub base: String,
/// If the base is cjs.
pub is_base_cjs: bool,
/// A list of specifiers that should be resolved. /// A list of specifiers that should be resolved.
pub specifiers: Vec<String>, /// (is_cjs: bool, raw_specifier: String)
pub specifiers: Vec<(bool, String)>,
} }
#[op2] #[op2]
@ -709,17 +717,9 @@ pub struct ResolveArgs {
fn op_resolve( fn op_resolve(
state: &mut OpState, state: &mut OpState,
#[string] base: String, #[string] base: String,
is_base_cjs: bool, #[serde] specifiers: Vec<(bool, String)>,
#[serde] specifiers: Vec<String>,
) -> Result<Vec<(String, &'static str)>, AnyError> { ) -> Result<Vec<(String, &'static str)>, AnyError> {
op_resolve_inner( op_resolve_inner(state, ResolveArgs { base, specifiers })
state,
ResolveArgs {
base,
is_base_cjs,
specifiers,
},
)
} }
#[inline] #[inline]
@ -730,11 +730,6 @@ fn op_resolve_inner(
let state = state.borrow_mut::<State>(); let state = state.borrow_mut::<State>();
let mut resolved: Vec<(String, &'static str)> = let mut resolved: Vec<(String, &'static str)> =
Vec::with_capacity(args.specifiers.len()); Vec::with_capacity(args.specifiers.len());
let referrer_kind = if args.is_base_cjs {
NodeModuleKind::Cjs
} else {
NodeModuleKind::Esm
};
let referrer = if let Some(remapped_specifier) = let referrer = if let Some(remapped_specifier) =
state.remapped_specifiers.get(&args.base) state.remapped_specifiers.get(&args.base)
{ {
@ -747,7 +742,7 @@ fn op_resolve_inner(
)? )?
}; };
let referrer_module = state.graph.get(&referrer); let referrer_module = state.graph.get(&referrer);
for specifier in args.specifiers { for (is_cjs, specifier) in args.specifiers {
if specifier.starts_with("node:") { if specifier.starts_with("node:") {
resolved.push(( resolved.push((
MISSING_DEPENDENCY_SPECIFIER.to_string(), MISSING_DEPENDENCY_SPECIFIER.to_string(),
@ -766,13 +761,20 @@ fn op_resolve_inner(
.and_then(|m| m.js()) .and_then(|m| m.js())
.and_then(|m| m.dependencies_prefer_fast_check().get(&specifier)) .and_then(|m| m.dependencies_prefer_fast_check().get(&specifier))
.and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok())); .and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok()));
let resolution_mode = if is_cjs {
ResolutionMode::Require
} else {
ResolutionMode::Import
};
let maybe_result = match resolved_dep { let maybe_result = match resolved_dep {
Some(ResolutionResolved { specifier, .. }) => { Some(ResolutionResolved { specifier, .. }) => {
resolve_graph_specifier_types( resolve_graph_specifier_types(
specifier, specifier,
&referrer, &referrer,
referrer_kind, // we could get this from the resolved dep, but for now assume
// the value resolved in TypeScript is better
resolution_mode,
state, state,
)? )?
} }
@ -780,7 +782,7 @@ fn op_resolve_inner(
match resolve_non_graph_specifier_types( match resolve_non_graph_specifier_types(
&specifier, &specifier,
&referrer, &referrer,
referrer_kind, resolution_mode,
state, state,
) { ) {
Ok(maybe_result) => maybe_result, Ok(maybe_result) => maybe_result,
@ -847,7 +849,7 @@ fn op_resolve_inner(
fn resolve_graph_specifier_types( fn resolve_graph_specifier_types(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
state: &State, state: &State,
) -> Result<Option<(ModuleSpecifier, MediaType)>, AnyError> { ) -> Result<Option<(ModuleSpecifier, MediaType)>, AnyError> {
let graph = &state.graph; let graph = &state.graph;
@ -888,6 +890,9 @@ fn resolve_graph_specifier_types(
Some(Module::Json(module)) => { Some(Module::Json(module)) => {
Ok(Some((module.specifier.clone(), module.media_type))) Ok(Some((module.specifier.clone(), module.media_type)))
} }
Some(Module::Wasm(module)) => {
Ok(Some((module.specifier.clone(), MediaType::Dmts)))
}
Some(Module::Npm(module)) => { Some(Module::Npm(module)) => {
if let Some(npm) = &state.maybe_npm.as_ref() { if let Some(npm) = &state.maybe_npm.as_ref() {
let package_folder = npm let package_folder = npm
@ -900,8 +905,8 @@ fn resolve_graph_specifier_types(
&package_folder, &package_folder,
module.nv_reference.sub_path(), module.nv_reference.sub_path(),
Some(referrer), Some(referrer),
referrer_kind, resolution_mode,
NodeResolutionMode::Types, NodeResolutionKind::Types,
); );
let maybe_url = match res_result { let maybe_url = match res_result {
Ok(url) => Some(url), Ok(url) => Some(url),
@ -941,7 +946,7 @@ enum ResolveNonGraphSpecifierTypesError {
fn resolve_non_graph_specifier_types( fn resolve_non_graph_specifier_types(
raw_specifier: &str, raw_specifier: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind, resolution_mode: ResolutionMode,
state: &State, state: &State,
) -> Result< ) -> Result<
Option<(ModuleSpecifier, MediaType)>, Option<(ModuleSpecifier, MediaType)>,
@ -959,8 +964,8 @@ fn resolve_non_graph_specifier_types(
.resolve( .resolve(
raw_specifier, raw_specifier,
referrer, referrer,
referrer_kind, resolution_mode,
NodeResolutionMode::Types, NodeResolutionKind::Types,
) )
.ok() .ok()
.map(|res| res.into_url()), .map(|res| res.into_url()),
@ -968,7 +973,7 @@ fn resolve_non_graph_specifier_types(
} else if let Ok(npm_req_ref) = } else if let Ok(npm_req_ref) =
NpmPackageReqReference::from_str(raw_specifier) NpmPackageReqReference::from_str(raw_specifier)
{ {
debug_assert_eq!(referrer_kind, NodeModuleKind::Esm); debug_assert_eq!(resolution_mode, ResolutionMode::Import);
// todo(dsherret): add support for injecting this in the graph so // todo(dsherret): add support for injecting this in the graph so
// we don't need this special code here. // we don't need this special code here.
// This could occur when resolving npm:@types/node when it is // This could occur when resolving npm:@types/node when it is
@ -980,8 +985,8 @@ fn resolve_non_graph_specifier_types(
&package_folder, &package_folder,
npm_req_ref.sub_path(), npm_req_ref.sub_path(),
Some(referrer), Some(referrer),
referrer_kind, resolution_mode,
NodeResolutionMode::Types, NodeResolutionKind::Types,
); );
let maybe_url = match res_result { let maybe_url = match res_result {
Ok(url) => Some(url), Ok(url) => Some(url),
@ -1104,6 +1109,7 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
root_map, root_map,
remapped_specifiers, remapped_specifiers,
)], )],
create_params: create_isolate_create_params(),
..Default::default() ..Default::default()
}); });
@ -1194,7 +1200,7 @@ mod tests {
.context("Unable to get CWD") .context("Unable to get CWD")
.unwrap(), .unwrap(),
); );
let mut op_state = OpState::new(None); let mut op_state = OpState::new(None, None);
op_state.put(state); op_state.put(state);
op_state op_state
} }
@ -1379,8 +1385,7 @@ mod tests {
&mut state, &mut state,
ResolveArgs { ResolveArgs {
base: "https://deno.land/x/a.ts".to_string(), base: "https://deno.land/x/a.ts".to_string(),
is_base_cjs: false, specifiers: vec![(false, "./b.ts".to_string())],
specifiers: vec!["./b.ts".to_string()],
}, },
) )
.expect("should have invoked op"); .expect("should have invoked op");
@ -1399,8 +1404,7 @@ mod tests {
&mut state, &mut state,
ResolveArgs { ResolveArgs {
base: "https://deno.land/x/a.ts".to_string(), base: "https://deno.land/x/a.ts".to_string(),
is_base_cjs: false, specifiers: vec![(false, "./bad.ts".to_string())],
specifiers: vec!["./bad.ts".to_string()],
}, },
) )
.expect("should have not errored"); .expect("should have not errored");

View file

@ -73,7 +73,6 @@ impl DebouncedReceiver {
} }
} }
#[allow(clippy::print_stderr)]
async fn error_handler<F>(watch_future: F) -> bool async fn error_handler<F>(watch_future: F) -> bool
where where
F: Future<Output = Result<(), AnyError>>, F: Future<Output = Result<(), AnyError>>,
@ -84,7 +83,7 @@ where
Some(e) => format_js_error(e), Some(e) => format_js_error(e),
None => format!("{err:?}"), None => format!("{err:?}"),
}; };
eprintln!( log::error!(
"{}: {}", "{}: {}",
colors::red_bold("error"), colors::red_bold("error"),
error_string.trim_start_matches("error: ") error_string.trim_start_matches("error: ")
@ -128,19 +127,12 @@ impl PrintConfig {
} }
} }
fn create_print_after_restart_fn( fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() {
banner: &'static str,
clear_screen: bool,
) -> impl Fn() {
move || { move || {
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
if clear_screen && std::io::stderr().is_terminal() { if clear_screen && std::io::stderr().is_terminal() {
eprint!("{}", CLEAR_SCREEN); eprint!("{}", CLEAR_SCREEN);
} }
info!(
"{} File change detected! Restarting!",
colors::intense_blue(banner),
);
} }
} }
@ -188,7 +180,17 @@ impl WatcherCommunicator {
} }
pub fn print(&self, msg: String) { pub fn print(&self, msg: String) {
log::info!("{} {}", self.banner, msg); log::info!("{} {}", self.banner, colors::gray(msg));
}
pub fn show_path_changed(&self, changed_paths: Option<Vec<PathBuf>>) {
if let Some(paths) = changed_paths {
if !paths.is_empty() {
self.print(format!("Restarting! File change detected: {:?}", paths[0]))
} else {
self.print("Restarting! File change detected.".to_string())
}
}
} }
} }
@ -264,7 +266,7 @@ where
clear_screen, clear_screen,
} = print_config; } = print_config;
let print_after_restart = create_print_after_restart_fn(banner, clear_screen); let print_after_restart = create_print_after_restart_fn(clear_screen);
let watcher_communicator = Arc::new(WatcherCommunicator { let watcher_communicator = Arc::new(WatcherCommunicator {
paths_to_watch_tx: paths_to_watch_tx.clone(), paths_to_watch_tx: paths_to_watch_tx.clone(),
changed_paths_rx: changed_paths_rx.resubscribe(), changed_paths_rx: changed_paths_rx.resubscribe(),

View file

@ -659,7 +659,7 @@ impl LaxSingleProcessFsFlag {
// //
// This uses a blocking task because we use a single threaded // This uses a blocking task because we use a single threaded
// runtime and this is time sensitive so we don't want it to update // runtime and this is time sensitive so we don't want it to update
// at the whims of of whatever is occurring on the runtime thread. // at the whims of whatever is occurring on the runtime thread.
spawn_blocking({ spawn_blocking({
let token = token.clone(); let token = token.clone();
let last_updated_path = last_updated_path.clone(); let last_updated_path = last_updated_path.clone();

View file

@ -29,6 +29,7 @@ impl log::Log for CliLogger {
// thread's state // thread's state
DrawThread::hide(); DrawThread::hide();
self.0.log(record); self.0.log(record);
deno_telemetry::handle_log(record);
DrawThread::show(); DrawThread::show();
} }
} }

View file

@ -27,7 +27,16 @@ pub fn is_importable_ext(path: &Path) -> bool {
if let Some(ext) = get_extension(path) { if let Some(ext) = get_extension(path) {
matches!( matches!(
ext.as_str(), ext.as_str(),
"ts" | "tsx" | "js" | "jsx" | "mjs" | "mts" | "cjs" | "cts" | "json" "ts"
| "tsx"
| "js"
| "jsx"
| "mjs"
| "mts"
| "cjs"
| "cts"
| "json"
| "wasm"
) )
} else { } else {
false false
@ -222,6 +231,7 @@ mod test {
assert!(is_script_ext(Path::new("foo.cjs"))); assert!(is_script_ext(Path::new("foo.cjs")));
assert!(is_script_ext(Path::new("foo.cts"))); assert!(is_script_ext(Path::new("foo.cts")));
assert!(!is_script_ext(Path::new("foo.json"))); assert!(!is_script_ext(Path::new("foo.json")));
assert!(!is_script_ext(Path::new("foo.wasm")));
assert!(!is_script_ext(Path::new("foo.mjsx"))); assert!(!is_script_ext(Path::new("foo.mjsx")));
} }
@ -243,6 +253,7 @@ mod test {
assert!(is_importable_ext(Path::new("foo.cjs"))); assert!(is_importable_ext(Path::new("foo.cjs")));
assert!(is_importable_ext(Path::new("foo.cts"))); assert!(is_importable_ext(Path::new("foo.cts")));
assert!(is_importable_ext(Path::new("foo.json"))); assert!(is_importable_ext(Path::new("foo.json")));
assert!(is_importable_ext(Path::new("foo.wasm")));
assert!(!is_importable_ext(Path::new("foo.mjsx"))); assert!(!is_importable_ext(Path::new("foo.mjsx")));
} }

View file

@ -11,6 +11,15 @@ use deno_core::ModuleSourceCode;
static SOURCE_MAP_PREFIX: &[u8] = static SOURCE_MAP_PREFIX: &[u8] =
b"//# sourceMappingURL=data:application/json;base64,"; b"//# sourceMappingURL=data:application/json;base64,";
#[inline(always)]
pub fn from_utf8_lossy_cow(bytes: Cow<[u8]>) -> Cow<str> {
match bytes {
Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
Cow::Owned(bytes) => Cow::Owned(from_utf8_lossy_owned(bytes)),
}
}
#[inline(always)]
pub fn from_utf8_lossy_owned(bytes: Vec<u8>) -> String { pub fn from_utf8_lossy_owned(bytes: Vec<u8>) -> String {
match String::from_utf8_lossy(&bytes) { match String::from_utf8_lossy(&bytes) {
Cow::Owned(code) => code, Cow::Owned(code) => code,

View file

@ -46,15 +46,14 @@ pub fn init_v8_flags(
.skip(1) .skip(1)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
#[allow(clippy::print_stderr)]
if !unrecognized_v8_flags.is_empty() { if !unrecognized_v8_flags.is_empty() {
for f in unrecognized_v8_flags { for f in unrecognized_v8_flags {
eprintln!("error: V8 did not recognize flag '{f}'"); log::error!("error: V8 did not recognize flag '{f}'");
} }
eprintln!("\nFor a list of V8 flags, use '--v8-flags=--help'"); log::error!("\nFor a list of V8 flags, use '--v8-flags=--help'");
std::process::exit(1); deno_runtime::exit(1);
} }
if v8_flags_includes_help { if v8_flags_includes_help {
std::process::exit(0); deno_runtime::exit(0);
} }
} }

View file

@ -30,7 +30,6 @@ use deno_runtime::deno_tls::RootCertStoreProvider;
use deno_runtime::deno_web::BlobStore; use deno_runtime::deno_web::BlobStore;
use deno_runtime::fmt_errors::format_js_error; use deno_runtime::fmt_errors::format_js_error;
use deno_runtime::inspector_server::InspectorServer; use deno_runtime::inspector_server::InspectorServer;
use deno_runtime::ops::otel::OtelConfig;
use deno_runtime::ops::process::NpmProcessStateProviderRc; use deno_runtime::ops::process::NpmProcessStateProviderRc;
use deno_runtime::ops::worker_host::CreateWebWorkerCb; use deno_runtime::ops::worker_host::CreateWebWorkerCb;
use deno_runtime::web_worker::WebWorker; use deno_runtime::web_worker::WebWorker;
@ -43,9 +42,10 @@ use deno_runtime::BootstrapOptions;
use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerExecutionMode;
use deno_runtime::WorkerLogLevel; use deno_runtime::WorkerLogLevel;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use deno_telemetry::OtelConfig;
use deno_terminal::colors; use deno_terminal::colors;
use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolutionMode; use node_resolver::ResolutionMode;
use tokio::select; use tokio::select;
use crate::args::CliLockfile; use crate::args::CliLockfile;
@ -83,6 +83,15 @@ pub trait HmrRunner: Send + Sync {
async fn run(&mut self) -> Result<(), AnyError>; async fn run(&mut self) -> Result<(), AnyError>;
} }
pub trait CliCodeCache: code_cache::CodeCache {
/// Gets if the code cache is still enabled.
fn enabled(&self) -> bool {
true
}
fn as_code_cache(self: Arc<Self>) -> Arc<dyn code_cache::CodeCache>;
}
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait CoverageCollector: Send + Sync { pub trait CoverageCollector: Send + Sync {
async fn start_collecting(&mut self) -> Result<(), AnyError>; async fn start_collecting(&mut self) -> Result<(), AnyError>;
@ -127,7 +136,7 @@ pub struct CliMainWorkerOptions {
struct SharedWorkerState { struct SharedWorkerState {
blob_store: Arc<BlobStore>, blob_store: Arc<BlobStore>,
broadcast_channel: InMemoryBroadcastChannel, broadcast_channel: InMemoryBroadcastChannel,
code_cache: Option<Arc<dyn code_cache::CodeCache>>, code_cache: Option<Arc<dyn CliCodeCache>>,
compiled_wasm_module_store: CompiledWasmModuleStore, compiled_wasm_module_store: CompiledWasmModuleStore,
feature_checker: Arc<FeatureChecker>, feature_checker: Arc<FeatureChecker>,
fs: Arc<dyn deno_fs::FileSystem>, fs: Arc<dyn deno_fs::FileSystem>,
@ -155,7 +164,7 @@ impl SharedWorkerState {
NodeExtInitServices { NodeExtInitServices {
node_require_loader, node_require_loader,
node_resolver: self.node_resolver.clone(), node_resolver: self.node_resolver.clone(),
npm_resolver: self.npm_resolver.clone().into_npm_resolver(), npm_resolver: self.npm_resolver.clone().into_npm_pkg_folder_resolver(),
pkg_json_resolver: self.pkg_json_resolver.clone(), pkg_json_resolver: self.pkg_json_resolver.clone(),
} }
} }
@ -384,6 +393,13 @@ impl CliMainWorker {
} }
} }
// TODO(bartlomieju): this should be moved to some other place, added to avoid string
// duplication between worker setups and `deno info` output.
pub fn get_cache_storage_dir() -> PathBuf {
// Note: we currently use temp_dir() to avoid managing storage size.
std::env::temp_dir().join("deno_cache")
}
#[derive(Clone)] #[derive(Clone)]
pub struct CliMainWorkerFactory { pub struct CliMainWorkerFactory {
shared: Arc<SharedWorkerState>, shared: Arc<SharedWorkerState>,
@ -393,7 +409,7 @@ impl CliMainWorkerFactory {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
blob_store: Arc<BlobStore>, blob_store: Arc<BlobStore>,
code_cache: Option<Arc<dyn code_cache::CodeCache>>, code_cache: Option<Arc<dyn CliCodeCache>>,
feature_checker: Arc<FeatureChecker>, feature_checker: Arc<FeatureChecker>,
fs: Arc<dyn deno_fs::FileSystem>, fs: Arc<dyn deno_fs::FileSystem>,
maybe_file_watcher_communicator: Option<Arc<WatcherCommunicator>>, maybe_file_watcher_communicator: Option<Arc<WatcherCommunicator>>,
@ -520,10 +536,7 @@ impl CliMainWorkerFactory {
}); });
let cache_storage_dir = maybe_storage_key.map(|key| { let cache_storage_dir = maybe_storage_key.map(|key| {
// TODO(@satyarohith): storage quota management // TODO(@satyarohith): storage quota management
// Note: we currently use temp_dir() to avoid managing storage size. get_cache_storage_dir().join(checksum::gen(&[key.as_bytes()]))
std::env::temp_dir()
.join("deno_cache")
.join(checksum::gen(&[key.as_bytes()]))
}); });
// TODO(bartlomieju): this is cruft, update FeatureChecker to spit out // TODO(bartlomieju): this is cruft, update FeatureChecker to spit out
@ -547,14 +560,16 @@ impl CliMainWorkerFactory {
npm_process_state_provider: Some(shared.npm_process_state_provider()), npm_process_state_provider: Some(shared.npm_process_state_provider()),
blob_store: shared.blob_store.clone(), blob_store: shared.blob_store.clone(),
broadcast_channel: shared.broadcast_channel.clone(), broadcast_channel: shared.broadcast_channel.clone(),
fetch_dns_resolver: Default::default(),
shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()), shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()),
compiled_wasm_module_store: Some( compiled_wasm_module_store: Some(
shared.compiled_wasm_module_store.clone(), shared.compiled_wasm_module_store.clone(),
), ),
feature_checker, feature_checker,
permissions, permissions,
v8_code_cache: shared.code_cache.clone(), v8_code_cache: shared.code_cache.clone().map(|c| c.as_code_cache()),
}; };
let options = WorkerOptions { let options = WorkerOptions {
bootstrap: BootstrapOptions { bootstrap: BootstrapOptions {
deno_version: crate::version::DENO_VERSION_INFO.deno.to_string(), deno_version: crate::version::DENO_VERSION_INFO.deno.to_string(),
@ -585,7 +600,7 @@ impl CliMainWorkerFactory {
}, },
extensions: custom_extensions, extensions: custom_extensions,
startup_snapshot: crate::js::deno_isolate_init(), startup_snapshot: crate::js::deno_isolate_init(),
create_params: None, create_params: create_isolate_create_params(),
unsafely_ignore_certificate_errors: shared unsafely_ignore_certificate_errors: shared
.options .options
.unsafely_ignore_certificate_errors .unsafely_ignore_certificate_errors
@ -602,6 +617,8 @@ impl CliMainWorkerFactory {
origin_storage_dir, origin_storage_dir,
stdio, stdio,
skip_op_registration: shared.options.skip_op_registration, skip_op_registration: shared.options.skip_op_registration,
enable_stack_trace_arg_in_ops: crate::args::has_trace_permissions_enabled(
),
}; };
let mut worker = MainWorker::bootstrap_from_options( let mut worker = MainWorker::bootstrap_from_options(
@ -681,8 +698,8 @@ impl CliMainWorkerFactory {
package_folder, package_folder,
sub_path, sub_path,
/* referrer */ None, /* referrer */ None,
NodeModuleKind::Esm, ResolutionMode::Import,
NodeResolutionMode::Execution, NodeResolutionKind::Execution,
)?; )?;
if specifier if specifier
.to_file_path() .to_file_path()
@ -718,10 +735,7 @@ fn create_web_worker_callback(
.resolve_storage_key(&args.main_module); .resolve_storage_key(&args.main_module);
let cache_storage_dir = maybe_storage_key.map(|key| { let cache_storage_dir = maybe_storage_key.map(|key| {
// TODO(@satyarohith): storage quota management // TODO(@satyarohith): storage quota management
// Note: we currently use temp_dir() to avoid managing storage size. get_cache_storage_dir().join(checksum::gen(&[key.as_bytes()]))
std::env::temp_dir()
.join("deno_cache")
.join(checksum::gen(&[key.as_bytes()]))
}); });
// TODO(bartlomieju): this is cruft, update FeatureChecker to spit out // TODO(bartlomieju): this is cruft, update FeatureChecker to spit out
@ -786,6 +800,7 @@ fn create_web_worker_callback(
}, },
extensions: vec![], extensions: vec![],
startup_snapshot: crate::js::deno_isolate_init(), startup_snapshot: crate::js::deno_isolate_init(),
create_params: create_isolate_create_params(),
unsafely_ignore_certificate_errors: shared unsafely_ignore_certificate_errors: shared
.options .options
.unsafely_ignore_certificate_errors .unsafely_ignore_certificate_errors
@ -800,12 +815,25 @@ fn create_web_worker_callback(
strace_ops: shared.options.strace_ops.clone(), strace_ops: shared.options.strace_ops.clone(),
close_on_idle: args.close_on_idle, close_on_idle: args.close_on_idle,
maybe_worker_metadata: args.maybe_worker_metadata, maybe_worker_metadata: args.maybe_worker_metadata,
enable_stack_trace_arg_in_ops: crate::args::has_trace_permissions_enabled(
),
}; };
WebWorker::bootstrap_from_options(services, options) WebWorker::bootstrap_from_options(services, options)
}) })
} }
/// By default V8 uses 1.4Gb heap limit which is meant for browser tabs.
/// Instead probe for the total memory on the system and use it instead
/// as a default.
pub fn create_isolate_create_params() -> Option<v8::CreateParams> {
let maybe_mem_info = deno_runtime::sys_info::mem_info();
maybe_mem_info.map(|mem_info| {
v8::CreateParams::default()
.heap_limits_from_system_memory(mem_info.total, 0)
})
}
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
#[cfg(test)] #[cfg(test)]
@ -842,6 +870,7 @@ mod tests {
node_services: Default::default(), node_services: Default::default(),
npm_process_state_provider: Default::default(), npm_process_state_provider: Default::default(),
root_cert_store_provider: Default::default(), root_cert_store_provider: Default::default(),
fetch_dns_resolver: Default::default(),
shared_array_buffer_store: Default::default(), shared_array_buffer_store: Default::default(),
compiled_wasm_module_store: Default::default(), compiled_wasm_module_store: Default::default(),
v8_code_cache: Default::default(), v8_code_cache: Default::default(),

View file

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

View file

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

9
ext/cache/sqlite.rs vendored
View file

@ -8,6 +8,7 @@ use std::time::SystemTime;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
use async_trait::async_trait; use async_trait::async_trait;
use deno_core::anyhow::Context;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future::poll_fn; use deno_core::futures::future::poll_fn;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
@ -45,7 +46,13 @@ impl SqliteBackedCache {
pub fn new(cache_storage_dir: PathBuf) -> Result<Self, CacheError> { pub fn new(cache_storage_dir: PathBuf) -> Result<Self, CacheError> {
{ {
std::fs::create_dir_all(&cache_storage_dir) std::fs::create_dir_all(&cache_storage_dir)
.expect("failed to create cache dir"); .with_context(|| {
format!(
"Failed to create cache storage directory {}",
cache_storage_dir.display()
)
})
.map_err(CacheError::Other)?;
let path = cache_storage_dir.join("cache_metadata.db"); let path = cache_storage_dir.join("cache_metadata.db");
let connection = rusqlite::Connection::open(&path).unwrap_or_else(|_| { let connection = rusqlite::Connection::open(&path).unwrap_or_else(|_| {
panic!("failed to open cache db at {}", path.display()) panic!("failed to open cache db at {}", path.display())

View file

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

View file

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

View file

@ -9,4 +9,7 @@ declare module "ext:deno_console/01_console.js" {
keys: (keyof TObject)[]; keys: (keyof TObject)[];
evaluate: boolean; evaluate: boolean;
}): Record<string, unknown>; }): Record<string, unknown>;
class Console {
}
} }

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