diff --git a/.dprint.json b/.dprint.json index 6ee3db3c5d..082eb30058 100644 --- a/.dprint.json +++ b/.dprint.json @@ -71,7 +71,7 @@ "https://plugins.dprint.dev/typescript-0.93.0.wasm", "https://plugins.dprint.dev/json-0.19.3.wasm", "https://plugins.dprint.dev/markdown-0.17.8.wasm", - "https://plugins.dprint.dev/toml-0.6.2.wasm", + "https://plugins.dprint.dev/toml-0.6.3.wasm", "https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0", "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.0.wasm" ] diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 35020a5f8c..f1fa7edf65 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -5,7 +5,7 @@ import { stringify } from "jsr:@std/yaml@^0.221/stringify"; // Bump this number when you want to purge the cache. // Note: the tools/release/01_bump_crate_versions.ts script will update this version // automatically via regex, so ensure that this line maintains this format. -const cacheVersion = 15; +const cacheVersion = 16; const ubuntuX86Runner = "ubuntu-22.04"; const ubuntuX86XlRunner = "ubuntu-22.04-xl"; @@ -757,8 +757,10 @@ const ci = { ].join("\n"), run: [ "cd target/release", + "shasum -a 256 deno > deno-${{ matrix.arch }}-unknown-linux-gnu.sha256sum", "zip -r deno-${{ matrix.arch }}-unknown-linux-gnu.zip deno", "strip denort", + "shasum -a 256 denort > denort-${{ matrix.arch }}-unknown-linux-gnu.sha256sum", "zip -r denort-${{ matrix.arch }}-unknown-linux-gnu.zip denort", "./deno types > lib.deno.d.ts", ].join("\n"), @@ -783,8 +785,10 @@ const ci = { "--p12-file=<(echo $APPLE_CODESIGN_KEY | base64 -d) " + "--entitlements-xml-file=cli/entitlements.plist", "cd target/release", + "shasum -a 256 deno > deno-${{ matrix.arch }}-apple-darwin.sha256sum", "zip -r deno-${{ matrix.arch }}-apple-darwin.zip deno", "strip denort", + "shasum -a 256 denort > denort-${{ matrix.arch }}-apple-darwin.sha256sum", "zip -r denort-${{ matrix.arch }}-apple-darwin.zip denort", ] .join("\n"), @@ -799,7 +803,9 @@ const ci = { ].join("\n"), shell: "pwsh", run: [ + "Get-FileHash target/release/deno.exe -Algorithm SHA256 | Format-List > target/release/deno-${{ matrix.arch }}-pc-windows-msvc.sha256sum", "Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${{ matrix.arch }}-pc-windows-msvc.zip", + "Get-FileHash target/release/denort.exe -Algorithm SHA256 | Format-List > target/release/denort-${{ matrix.arch }}-pc-windows-msvc.sha256sum", "Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-${{ matrix.arch }}-pc-windows-msvc.zip", ].join("\n"), }, @@ -813,6 +819,7 @@ const ci = { ].join("\n"), run: [ 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/canary/$(git rev-parse HEAD)/', + 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/canary/$(git rev-parse HEAD)/', "echo ${{ github.sha }} > canary-latest.txt", 'gsutil -h "Cache-Control: no-cache" cp canary-latest.txt gs://dl.deno.land/canary-$(rustc -vV | sed -n "s|host: ||p")-latest.txt', ].join("\n"), @@ -994,8 +1001,10 @@ const ci = { "github.repository == 'denoland/deno' &&", "startsWith(github.ref, 'refs/tags/')", ].join("\n"), - run: + run: [ 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/', + 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/', + ].join("\n"), }, { name: "Upload release to dl.deno.land (windows)", @@ -1009,8 +1018,10 @@ const ci = { env: { CLOUDSDK_PYTHON: "${{env.pythonLocation}}\\python.exe", }, - run: + run: [ 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/', + 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/', + ].join("\n"), }, { name: "Create release notes", @@ -1040,15 +1051,25 @@ const ci = { with: { files: [ "target/release/deno-x86_64-pc-windows-msvc.zip", + "target/release/deno-x86_64-pc-windows-msvc.sha256sum", "target/release/denort-x86_64-pc-windows-msvc.zip", + "target/release/denort-x86_64-pc-windows-msvc.sha256sum", "target/release/deno-x86_64-unknown-linux-gnu.zip", + "target/release/deno-x86_64-unknown-linux-gnu.sha256sum", "target/release/denort-x86_64-unknown-linux-gnu.zip", + "target/release/denort-x86_64-unknown-linux-gnu.sha256sum", "target/release/deno-x86_64-apple-darwin.zip", + "target/release/deno-x86_64-apple-darwin.sha256sum", "target/release/denort-x86_64-apple-darwin.zip", + "target/release/denort-x86_64-apple-darwin.sha256sum", "target/release/deno-aarch64-unknown-linux-gnu.zip", + "target/release/deno-aarch64-unknown-linux-gnu.sha256sum", "target/release/denort-aarch64-unknown-linux-gnu.zip", + "target/release/denort-aarch64-unknown-linux-gnu.sha256sum", "target/release/deno-aarch64-apple-darwin.zip", + "target/release/deno-aarch64-apple-darwin.sha256sum", "target/release/denort-aarch64-apple-darwin.zip", + "target/release/denort-aarch64-apple-darwin.sha256sum", "target/release/deno_src.tar.gz", "target/release/lib.deno.d.ts", ].join("\n"), @@ -1067,6 +1088,7 @@ const ci = { "./target", "!./target/*/gn_out", "!./target/*/*.zip", + "!./target/*/*.sha256sum", "!./target/*/*.tar.gz", ].join("\n"), key: prCacheKeyPrefix + "${{ github.sha }}", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 280eb7cb84..728d1deb21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -367,8 +367,8 @@ jobs: path: |- ~/.cargo/registry/index ~/.cargo/registry/cache - key: '15-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' - restore-keys: '15-cargo-home-${{ matrix.os }}-${{ matrix.arch }}' + key: '16-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' + restore-keys: '16-cargo-home-${{ matrix.os }}-${{ matrix.arch }}' if: '!(matrix.skip)' - name: Restore cache build output (PR) uses: actions/cache/restore@v4 @@ -381,7 +381,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: never_saved - restore-keys: '15-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-' + restore-keys: '16-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-' - name: Apply and update mtime cache if: '!(matrix.skip) && (!startsWith(github.ref, ''refs/tags/''))' uses: ./.github/mtime_cache @@ -448,8 +448,10 @@ jobs: github.repository == 'denoland/deno') run: |- cd target/release + shasum -a 256 deno > deno-${{ matrix.arch }}-unknown-linux-gnu.sha256sum zip -r deno-${{ matrix.arch }}-unknown-linux-gnu.zip deno strip denort + shasum -a 256 denort > denort-${{ matrix.arch }}-unknown-linux-gnu.sha256sum zip -r denort-${{ matrix.arch }}-unknown-linux-gnu.zip denort ./deno types > lib.deno.d.ts - name: Pre-release (mac) @@ -465,8 +467,10 @@ jobs: echo "Key is $(echo $APPLE_CODESIGN_KEY | base64 -d | wc -c) bytes" rcodesign sign target/release/deno --code-signature-flags=runtime --p12-password="$APPLE_CODESIGN_PASSWORD" --p12-file=<(echo $APPLE_CODESIGN_KEY | base64 -d) --entitlements-xml-file=cli/entitlements.plist cd target/release + shasum -a 256 deno > deno-${{ matrix.arch }}-apple-darwin.sha256sum zip -r deno-${{ matrix.arch }}-apple-darwin.zip deno strip denort + shasum -a 256 denort > denort-${{ matrix.arch }}-apple-darwin.sha256sum zip -r denort-${{ matrix.arch }}-apple-darwin.zip denort - name: Pre-release (windows) if: |- @@ -476,7 +480,9 @@ jobs: github.repository == 'denoland/deno') shell: pwsh run: |- + Get-FileHash target/release/deno.exe -Algorithm SHA256 | Format-List > target/release/deno-${{ matrix.arch }}-pc-windows-msvc.sha256sum Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${{ matrix.arch }}-pc-windows-msvc.zip + Get-FileHash target/release/denort.exe -Algorithm SHA256 | Format-List > target/release/denort-${{ matrix.arch }}-pc-windows-msvc.sha256sum Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-${{ matrix.arch }}-pc-windows-msvc.zip - name: Upload canary to dl.deno.land if: |- @@ -486,6 +492,7 @@ jobs: github.ref == 'refs/heads/main') run: |- gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/canary/$(git rev-parse HEAD)/ + gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/canary/$(git rev-parse HEAD)/ echo ${{ github.sha }} > canary-latest.txt gsutil -h "Cache-Control: no-cache" cp canary-latest.txt gs://dl.deno.land/canary-$(rustc -vV | sed -n "s|host: ||p")-latest.txt - name: Autobahn testsuite @@ -615,7 +622,9 @@ jobs: matrix.profile == 'release' && github.repository == 'denoland/deno' && startsWith(github.ref, 'refs/tags/')) - run: 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/' + run: |- + gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/ + gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/ - name: Upload release to dl.deno.land (windows) if: |- !(matrix.skip) && (matrix.os == 'windows' && @@ -625,7 +634,9 @@ jobs: startsWith(github.ref, 'refs/tags/')) env: CLOUDSDK_PYTHON: '${{env.pythonLocation}}\python.exe' - run: 'gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/' + run: |- + gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.zip gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/ + gsutil -h "Cache-Control: public, max-age=3600" cp ./target/release/*.sha256sum gs://dl.deno.land/release/${GITHUB_REF#refs/*/}/ - name: Create release notes if: |- !(matrix.skip) && (matrix.job == 'test' && @@ -647,15 +658,25 @@ jobs: with: files: |- target/release/deno-x86_64-pc-windows-msvc.zip + target/release/deno-x86_64-pc-windows-msvc.sha256sum target/release/denort-x86_64-pc-windows-msvc.zip + target/release/denort-x86_64-pc-windows-msvc.sha256sum target/release/deno-x86_64-unknown-linux-gnu.zip + target/release/deno-x86_64-unknown-linux-gnu.sha256sum target/release/denort-x86_64-unknown-linux-gnu.zip + target/release/denort-x86_64-unknown-linux-gnu.sha256sum target/release/deno-x86_64-apple-darwin.zip + target/release/deno-x86_64-apple-darwin.sha256sum target/release/denort-x86_64-apple-darwin.zip + target/release/denort-x86_64-apple-darwin.sha256sum target/release/deno-aarch64-unknown-linux-gnu.zip + target/release/deno-aarch64-unknown-linux-gnu.sha256sum target/release/denort-aarch64-unknown-linux-gnu.zip + target/release/denort-aarch64-unknown-linux-gnu.sha256sum target/release/deno-aarch64-apple-darwin.zip + target/release/deno-aarch64-apple-darwin.sha256sum target/release/denort-aarch64-apple-darwin.zip + target/release/denort-aarch64-apple-darwin.sha256sum target/release/deno_src.tar.gz target/release/lib.deno.d.ts body_path: target/release/release-notes.md @@ -668,8 +689,9 @@ jobs: ./target !./target/*/gn_out !./target/*/*.zip + !./target/*/*.sha256sum !./target/*/*.tar.gz - key: '15-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' + key: '16-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' publish-canary: name: publish canary runs-on: ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index 492cfa2606..0ab2d96eaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,11 +1146,10 @@ dependencies = [ [[package]] name = "deno" -version = "2.0.0-rc.5" +version = "2.0.0-rc.9" dependencies = [ "anstream", "async-trait", - "base32", "base64 0.21.7", "bincode", "bytes", @@ -1174,6 +1173,8 @@ dependencies = [ "deno_lockfile", "deno_npm", "deno_package_json", + "deno_path_util", + "deno_resolver", "deno_runtime", "deno_semver", "deno_task_shell", @@ -1272,9 +1273,9 @@ dependencies = [ [[package]] name = "deno_ast" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b08d11d9e4086b00d3428650e31153cf5896586411763cb88a6423ce5b18791" +checksum = "89ea2fd038c9c7e3e87e624fd708303cd33f39c33707f6c48fa9a65d65fefc47" dependencies = [ "base64 0.21.7", "deno_media_type", @@ -1316,7 +1317,7 @@ dependencies = [ [[package]] name = "deno_bench_util" -version = "0.162.0" +version = "0.163.0" dependencies = [ "bencher", "deno_core", @@ -1325,7 +1326,7 @@ dependencies = [ [[package]] name = "deno_broadcast_channel" -version = "0.162.0" +version = "0.163.0" dependencies = [ "async-trait", "deno_core", @@ -1335,7 +1336,7 @@ dependencies = [ [[package]] name = "deno_cache" -version = "0.100.0" +version = "0.101.0" dependencies = [ "async-trait", "deno_core", @@ -1347,11 +1348,13 @@ dependencies = [ [[package]] name = "deno_cache_dir" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df43311cb7703fa3242c282823a850e4c8d0c06b9527d8209b55bd695452ea5" +checksum = "186a102b13b4512841f5f40784cd25822042d22954afe3b5b070d406d15eb4f2" dependencies = [ + "base32", "deno_media_type", + "deno_path_util", "indexmap", "log", "once_cell", @@ -1365,7 +1368,7 @@ dependencies = [ [[package]] name = "deno_canvas" -version = "0.37.0" +version = "0.38.0" dependencies = [ "deno_core", "deno_webgpu", @@ -1398,7 +1401,7 @@ dependencies = [ [[package]] name = "deno_console" -version = "0.168.0" +version = "0.169.0" dependencies = [ "deno_core", ] @@ -1443,7 +1446,7 @@ checksum = "a13951ea98c0a4c372f162d669193b4c9d991512de9f2381dd161027f34b26b1" [[package]] name = "deno_cron" -version = "0.48.0" +version = "0.49.0" dependencies = [ "anyhow", "async-trait", @@ -1455,7 +1458,7 @@ dependencies = [ [[package]] name = "deno_crypto" -version = "0.182.0" +version = "0.183.0" dependencies = [ "aes", "aes-gcm", @@ -1489,9 +1492,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.148.0" +version = "0.150.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144fa07977ba9eeeb98bcd267b7f0a6f8033f0f1f20fd210e669b3c4f30cefa2" +checksum = "0841188bc852535b76e53be6c3d13c61cfc6751a731969b8959fe31fa696c73f" dependencies = [ "ammonia", "anyhow", @@ -1515,7 +1518,7 @@ dependencies = [ [[package]] name = "deno_fetch" -version = "0.192.0" +version = "0.193.0" dependencies = [ "base64 0.21.7", "bytes", @@ -1547,7 +1550,7 @@ dependencies = [ [[package]] name = "deno_ffi" -version = "0.155.0" +version = "0.156.0" dependencies = [ "deno_core", "deno_permissions", @@ -1564,12 +1567,13 @@ dependencies = [ [[package]] name = "deno_fs" -version = "0.78.0" +version = "0.79.0" dependencies = [ "async-trait", "base32", "deno_core", "deno_io", + "deno_path_util", "deno_permissions", "filetime", "junction", @@ -1584,9 +1588,9 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.82.1" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b63015c73aa203da206b5d35b4c1eaa23bc7fed37ab325da62d525a5524a04" +checksum = "20088a4497b1a212482883dc7b0365e99f703d575fb512d4a793531cdc92ea76" dependencies = [ "anyhow", "async-trait", @@ -1613,7 +1617,7 @@ dependencies = [ [[package]] name = "deno_http" -version = "0.166.0" +version = "0.167.0" dependencies = [ "async-compression", "async-trait", @@ -1652,7 +1656,7 @@ dependencies = [ [[package]] name = "deno_io" -version = "0.78.0" +version = "0.79.0" dependencies = [ "async-trait", "deno_core", @@ -1673,7 +1677,7 @@ dependencies = [ [[package]] name = "deno_kv" -version = "0.76.0" +version = "0.77.0" dependencies = [ "anyhow", "async-trait", @@ -1682,6 +1686,7 @@ dependencies = [ "chrono", "deno_core", "deno_fetch", + "deno_path_util", "deno_permissions", "deno_tls", "denokv_proto", @@ -1743,7 +1748,7 @@ dependencies = [ [[package]] name = "deno_napi" -version = "0.99.0" +version = "0.100.0" dependencies = [ "deno_core", "deno_permissions", @@ -1765,7 +1770,7 @@ dependencies = [ [[package]] name = "deno_net" -version = "0.160.0" +version = "0.161.0" dependencies = [ "deno_core", "deno_permissions", @@ -1781,7 +1786,7 @@ dependencies = [ [[package]] name = "deno_node" -version = "0.105.0" +version = "0.106.0" dependencies = [ "aead-gcm-stream", "aes", @@ -1800,6 +1805,7 @@ dependencies = [ "deno_media_type", "deno_net", "deno_package_json", + "deno_path_util", "deno_permissions", "deno_whoami", "der", @@ -1916,11 +1922,23 @@ dependencies = [ "url", ] +[[package]] +name = "deno_path_util" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4889646c1ce8437a6fde3acb057fd7e2d039e62c61f5063fc125ed1ede114dc6" +dependencies = [ + "percent-encoding", + "thiserror", + "url", +] + [[package]] name = "deno_permissions" -version = "0.28.0" +version = "0.29.0" dependencies = [ "deno_core", + "deno_path_util", "deno_terminal 0.2.0", "fqdn", "libc", @@ -1932,9 +1950,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "deno_resolver" +version = "0.1.0" +dependencies = [ + "anyhow", + "base32", + "deno_media_type", + "deno_package_json", + "deno_path_util", + "deno_semver", + "node_resolver", + "test_server", + "url", +] + [[package]] name = "deno_runtime" -version = "0.177.0" +version = "0.178.0" dependencies = [ "deno_ast", "deno_broadcast_channel", @@ -1953,6 +1986,7 @@ dependencies = [ "deno_napi", "deno_net", "deno_node", + "deno_path_util", "deno_permissions", "deno_terminal 0.2.0", "deno_tls", @@ -1985,6 +2019,7 @@ dependencies = [ "serde", "signal-hook", "signal-hook-registry", + "tempfile", "test_server", "tokio", "tokio-metrics", @@ -1997,9 +2032,9 @@ dependencies = [ [[package]] name = "deno_semver" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6657fecb9ac6a7a71f552c95e8cc492466a75f5660224577e2226bcf30db9768" +checksum = "670fec7ef309384e23c2a90ac5d2d9d91a776d225306c75f5cdd28cf6cc8a59f" dependencies = [ "monch", "once_cell", @@ -2047,7 +2082,7 @@ dependencies = [ [[package]] name = "deno_tls" -version = "0.155.0" +version = "0.156.0" dependencies = [ "deno_core", "deno_native_certs", @@ -2095,7 +2130,7 @@ dependencies = [ [[package]] name = "deno_url" -version = "0.168.0" +version = "0.169.0" dependencies = [ "deno_bench_util", "deno_console", @@ -2106,7 +2141,7 @@ dependencies = [ [[package]] name = "deno_web" -version = "0.199.0" +version = "0.200.0" dependencies = [ "async-trait", "base64-simd 0.8.0", @@ -2127,7 +2162,7 @@ dependencies = [ [[package]] name = "deno_webgpu" -version = "0.135.0" +version = "0.136.0" dependencies = [ "deno_core", "raw-window-handle", @@ -2139,7 +2174,7 @@ dependencies = [ [[package]] name = "deno_webidl" -version = "0.168.0" +version = "0.169.0" dependencies = [ "deno_bench_util", "deno_core", @@ -2147,7 +2182,7 @@ dependencies = [ [[package]] name = "deno_websocket" -version = "0.173.0" +version = "0.174.0" dependencies = [ "bytes", "deno_core", @@ -2168,7 +2203,7 @@ dependencies = [ [[package]] name = "deno_webstorage" -version = "0.163.0" +version = "0.164.0" dependencies = [ "deno_core", "deno_web", @@ -2797,9 +2832,9 @@ checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8" [[package]] name = "eszip" -version = "0.78.0" +version = "0.79.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0546f00d41dbc6e90b50e922759c02559a897e59b683369c3a13519cd5108b6" +checksum = "8eb55c89bdde75a3826a79d49c9d847623ae7fbdb2695b542982982da990d33e" dependencies = [ "anyhow", "async-trait", @@ -4071,70 +4106,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lexical-core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" -dependencies = [ - "lexical-parse-integer", - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-parse-integer" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" -dependencies = [ - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-util" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "lexical-write-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" -dependencies = [ - "lexical-util", - "lexical-write-integer", - "static_assertions", -] - -[[package]] -name = "lexical-write-integer" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" -dependencies = [ - "lexical-util", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.153" @@ -4209,9 +4180,9 @@ dependencies = [ [[package]] name = "libsui" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e39af24eff8df7c8b9980ef56a1a1f4d2e77b34b2d5c0529f108c53ae96a7a" +checksum = "205eca4e7beaad637dcd38fe41292065894ee7f498077cf3c135d5f7252b9f27" dependencies = [ "editpe", "libc", @@ -4342,9 +4313,9 @@ dependencies = [ [[package]] name = "markup_fmt" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fc137a4a591720176339bf7e857586a48ff35c0caee7ad6cf709327901232c" +checksum = "9dab5ae899659fbe5c8835b2c8ca8d3e357974a3e454138925b404004973361f" dependencies = [ "aho-corasick", "css_dataset", @@ -4517,7 +4488,7 @@ dependencies = [ [[package]] name = "napi_sym" -version = "0.98.0" +version = "0.99.0" dependencies = [ "quote", "serde", @@ -4586,12 +4557,13 @@ dependencies = [ [[package]] name = "node_resolver" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", "deno_media_type", "deno_package_json", + "deno_path_util", "futures", "lazy-regex", "once_cell", @@ -6386,13 +6358,12 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd-json" -version = "0.13.9" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b84c23a1066e1d650ebc99aa8fb9f8ed0ab96fd36e2e836173c92fc9fb29bc" +checksum = "05f0b376aada35f30a0012f5790e50aed62f91804a0682669aefdbe81c7fcb91" dependencies = [ "getrandom", "halfbrown", - "lexical-core", "ref-cast", "serde", "serde_json", @@ -7947,9 +7918,9 @@ dependencies = [ [[package]] name = "value-trait" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" +checksum = "bcaa56177466248ba59d693a048c0959ddb67f1151b963f904306312548cf392" dependencies = [ "float-cmp", "halfbrown", @@ -8119,9 +8090,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d93b773107ba49bc84dd3b241e019c702d886fd5c457defe2ea8b1123a5dcd" +checksum = "e8c6dfa3ac045bc517de14c7b1384298de1dbd229d38e08e169d9ae8c170937c" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index 81cbf8b4c6..26ef3be19b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,14 @@ members = [ "ext/napi", "ext/net", "ext/node", - "ext/node_resolver", "ext/url", "ext/web", "ext/webgpu", "ext/webidl", "ext/websocket", "ext/webstorage", + "resolvers/deno", + "resolvers/node", "runtime", "runtime/permissions", "tests", @@ -44,17 +45,19 @@ license = "MIT" repository = "https://github.com/denoland/deno" [workspace.dependencies] -deno_ast = { version = "=0.42.0", features = ["transpiling"] } +deno_ast = { version = "=0.42.1", features = ["transpiling"] } deno_core = { version = "0.311.0" } -deno_bench_util = { version = "0.162.0", path = "./bench_util" } +deno_bench_util = { version = "0.163.0", path = "./bench_util" } deno_lockfile = "=0.23.1" deno_media_type = { version = "0.1.4", features = ["module_specifier"] } -deno_permissions = { version = "0.28.0", path = "./runtime/permissions" } -deno_runtime = { version = "0.177.0", path = "./runtime" } -deno_semver = "=0.5.13" +deno_npm = "=0.25.2" +deno_path_util = "=0.2.0" +deno_permissions = { version = "0.29.0", path = "./runtime/permissions" } +deno_runtime = { version = "0.178.0", path = "./runtime" } +deno_semver = "=0.5.14" deno_terminal = "0.2.0" -napi_sym = { version = "0.98.0", path = "./cli/napi/sym" } +napi_sym = { version = "0.99.0", path = "./cli/napi/sym" } test_util = { package = "test_server", path = "./tests/util/server" } denokv_proto = "0.8.1" @@ -63,29 +66,32 @@ denokv_remote = "0.8.1" denokv_sqlite = { default-features = false, version = "0.8.2" } # exts -deno_broadcast_channel = { version = "0.162.0", path = "./ext/broadcast_channel" } -deno_cache = { version = "0.100.0", path = "./ext/cache" } -deno_canvas = { version = "0.37.0", path = "./ext/canvas" } -deno_console = { version = "0.168.0", path = "./ext/console" } -deno_cron = { version = "0.48.0", path = "./ext/cron" } -deno_crypto = { version = "0.182.0", path = "./ext/crypto" } -deno_fetch = { version = "0.192.0", path = "./ext/fetch" } -deno_ffi = { version = "0.155.0", path = "./ext/ffi" } -deno_fs = { version = "0.78.0", path = "./ext/fs" } -deno_http = { version = "0.166.0", path = "./ext/http" } -deno_io = { version = "0.78.0", path = "./ext/io" } -deno_kv = { version = "0.76.0", path = "./ext/kv" } -deno_napi = { version = "0.99.0", path = "./ext/napi" } -deno_net = { version = "0.160.0", path = "./ext/net" } -deno_node = { version = "0.105.0", path = "./ext/node" } -deno_tls = { version = "0.155.0", path = "./ext/tls" } -deno_url = { version = "0.168.0", path = "./ext/url" } -deno_web = { version = "0.199.0", path = "./ext/web" } -deno_webgpu = { version = "0.135.0", path = "./ext/webgpu" } -deno_webidl = { version = "0.168.0", path = "./ext/webidl" } -deno_websocket = { version = "0.173.0", path = "./ext/websocket" } -deno_webstorage = { version = "0.163.0", path = "./ext/webstorage" } -node_resolver = { version = "0.7.0", path = "./ext/node_resolver" } +deno_broadcast_channel = { version = "0.163.0", path = "./ext/broadcast_channel" } +deno_cache = { version = "0.101.0", path = "./ext/cache" } +deno_canvas = { version = "0.38.0", path = "./ext/canvas" } +deno_console = { version = "0.169.0", path = "./ext/console" } +deno_cron = { version = "0.49.0", path = "./ext/cron" } +deno_crypto = { version = "0.183.0", path = "./ext/crypto" } +deno_fetch = { version = "0.193.0", path = "./ext/fetch" } +deno_ffi = { version = "0.156.0", path = "./ext/ffi" } +deno_fs = { version = "0.79.0", path = "./ext/fs" } +deno_http = { version = "0.167.0", path = "./ext/http" } +deno_io = { version = "0.79.0", path = "./ext/io" } +deno_kv = { version = "0.77.0", path = "./ext/kv" } +deno_napi = { version = "0.100.0", path = "./ext/napi" } +deno_net = { version = "0.161.0", path = "./ext/net" } +deno_node = { version = "0.106.0", path = "./ext/node" } +deno_tls = { version = "0.156.0", path = "./ext/tls" } +deno_url = { version = "0.169.0", path = "./ext/url" } +deno_web = { version = "0.200.0", path = "./ext/web" } +deno_webgpu = { version = "0.136.0", path = "./ext/webgpu" } +deno_webidl = { version = "0.169.0", path = "./ext/webidl" } +deno_websocket = { version = "0.174.0", path = "./ext/websocket" } +deno_webstorage = { version = "0.164.0", path = "./ext/webstorage" } + +# resolvers +deno_resolver = { version = "0.1.0", path = "./resolvers/deno" } +node_resolver = { version = "0.8.0", path = "./resolvers/node" } aes = "=0.8.3" anyhow = "1.0.57" @@ -101,10 +107,11 @@ cbc = { version = "=0.1.2", features = ["alloc"] } # Instead use util::time::utc_now() chrono = { version = "0.4", default-features = false, features = ["std", "serde"] } console_static_text = "=0.8.1" +dashmap = "5.5.3" data-encoding = "2.3.3" data-url = "=0.3.0" -deno_cache_dir = "=0.11.1" -deno_package_json = { version = "=0.1.1", default-features = false } +deno_cache_dir = "=0.13.0" +deno_package_json = { version = "0.1.1", default-features = false } dlopen2 = "0.6.1" ecb = "=0.1.2" elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] } diff --git a/bench_util/Cargo.toml b/bench_util/Cargo.toml index 5a04ab5355..d668d3a944 100644 --- a/bench_util/Cargo.toml +++ b/bench_util/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_bench_util" -version = "0.162.0" +version = "0.163.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fc3216a606..2cc32c880b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno" -version = "2.0.0-rc.5" +version = "2.0.0-rc.9" authors.workspace = true default-run = "deno" edition.workspace = true @@ -67,24 +67,25 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposa deno_cache_dir = { workspace = true } deno_config = { version = "=0.35.0", features = ["workspace", "sync"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } -deno_doc = { version = "0.148.0", features = ["html", "syntect"] } -deno_graph = { version = "=0.82.1" } +deno_doc = { version = "0.150.1", features = ["html", "syntect"] } +deno_graph = { version = "=0.83.0" } deno_lint = { version = "=0.67.0", features = ["docs"] } deno_lockfile.workspace = true -deno_npm = "=0.25.2" +deno_npm.workspace = true deno_package_json.workspace = true +deno_path_util.workspace = true +deno_resolver.workspace = true deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_semver.workspace = true deno_task_shell = "=0.17.0" deno_terminal.workspace = true -eszip = "=0.78.0" -libsui = "0.3.1" +eszip = "=0.79.1" +libsui = "0.4.0" napi_sym.workspace = true node_resolver.workspace = true anstream = "0.6.14" async-trait.workspace = true -base32.workspace = true base64.workspace = true bincode = "=1.3.3" bytes.workspace = true @@ -95,7 +96,7 @@ clap_complete = "=4.5.24" clap_complete_fig = "=4.5.2" color-print = "0.3.5" console_static_text.workspace = true -dashmap = "5.5.3" +dashmap.workspace = true data-encoding.workspace = true dissimilar = "=1.0.4" dotenvy = "0.15.7" @@ -124,7 +125,7 @@ libz-sys.workspace = true log = { workspace = true, features = ["serde"] } lsp-types.workspace = true malva = "=0.10.1" -markup_fmt = "=0.13.0" +markup_fmt = "=0.13.1" memmem.workspace = true monch.workspace = true notify.workspace = true diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 10fa07bed2..6caef29d9f 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::collections::HashSet; use std::env; use std::ffi::OsString; @@ -28,13 +29,13 @@ use deno_config::glob::PathOrPatternSet; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::normalize_path; use deno_core::resolve_url_or_path; use deno_core::url::Url; use deno_graph::GraphKind; -use deno_runtime::deno_permissions::parse_sys_kind; +use deno_path_util::normalize_path; +use deno_path_util::url_to_file_path; use deno_runtime::deno_permissions::PermissionsOptions; -use deno_runtime::fs_util::specifier_to_file_path; +use deno_runtime::deno_permissions::SysDescriptor; use log::debug; use log::Level; use serde::Deserialize; @@ -44,6 +45,7 @@ use crate::args::resolve_no_prompt; use crate::util::fs::canonicalize_path; use super::flags_net; +use super::jsr_url; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum ConfigFlag { @@ -546,6 +548,8 @@ pub struct LifecycleScriptsConfig { pub allowed: PackagesAllowedScripts, pub initial_cwd: PathBuf, pub root_dir: PathBuf, + /// Part of an explicit `deno install` + pub explicit_install: bool, } #[derive(Debug, Clone, Eq, PartialEq, Default)] @@ -639,6 +643,7 @@ pub struct PermissionFlags { pub allow_write: Option>, pub deny_write: Option>, pub no_prompt: bool, + pub allow_import: Option>, } impl PermissionFlags { @@ -658,9 +663,10 @@ impl PermissionFlags { || self.deny_sys.is_some() || self.allow_write.is_some() || self.deny_write.is_some() + || self.allow_import.is_some() } - pub fn to_options(&self) -> PermissionsOptions { + pub fn to_options(&self, cli_arg_urls: &[Cow]) -> PermissionsOptions { fn handle_allow( allow_all: bool, value: Option, @@ -673,6 +679,41 @@ impl PermissionFlags { } } + fn handle_imports( + cli_arg_urls: &[Cow], + imports: Option>, + ) -> Option> { + if imports.is_some() { + return imports; + } + + let builtin_allowed_import_hosts = [ + "deno.land:443", + "esm.sh:443", + "jsr.io:443", + "raw.githubusercontent.com:443", + "gist.githubusercontent.com:443", + ]; + + let mut imports = + Vec::with_capacity(builtin_allowed_import_hosts.len() + 1); + imports + .extend(builtin_allowed_import_hosts.iter().map(|s| s.to_string())); + + // also add the JSR_URL env var + if let Some(jsr_host) = allow_import_host_from_url(jsr_url()) { + imports.push(jsr_host); + } + // include the cli arg urls + for url in cli_arg_urls { + if let Some(host) = allow_import_host_from_url(url) { + imports.push(host); + } + } + + Some(imports) + } + PermissionsOptions { allow_all: self.allow_all, allow_env: handle_allow(self.allow_all, self.allow_env.clone()), @@ -689,11 +730,33 @@ impl PermissionFlags { deny_sys: self.deny_sys.clone(), allow_write: handle_allow(self.allow_all, self.allow_write.clone()), deny_write: self.deny_write.clone(), + allow_import: handle_imports( + cli_arg_urls, + handle_allow(self.allow_all, self.allow_import.clone()), + ), prompt: !resolve_no_prompt(self), } } } +/// Gets the --allow-import host from the provided url +fn allow_import_host_from_url(url: &Url) -> Option { + let host = url.host()?; + if let Some(port) = url.port() { + Some(format!("{}:{}", host, port)) + } else { + use deno_core::url::Host::*; + match host { + Domain(domain) if domain == "jsr.io" && url.scheme() == "https" => None, + _ => match url.scheme() { + "https" => Some(format!("{}:443", host)), + "http" => Some(format!("{}:80", host)), + _ => None, + }, + } + } +} + fn join_paths(allowlist: &[String], d: &str) -> String { allowlist .iter() @@ -881,6 +944,17 @@ impl Flags { _ => {} } + match &self.permissions.allow_import { + Some(allowlist) if allowlist.is_empty() => { + args.push("--allow-import".to_string()); + } + Some(allowlist) => { + let s = format!("--allow-import={}", allowlist.join(",")); + args.push(s); + } + _ => {} + } + args } @@ -928,7 +1002,7 @@ impl Flags { if module_specifier.scheme() == "file" || module_specifier.scheme() == "npm" { - if let Ok(p) = specifier_to_file_path(&module_specifier) { + if let Ok(p) = url_to_file_path(&module_specifier) { Some(vec![p.parent().unwrap().to_path_buf()]) } else { Some(vec![current_dir.to_path_buf()]) @@ -991,6 +1065,7 @@ impl Flags { self.permissions.allow_write = None; self.permissions.allow_sys = None; self.permissions.allow_ffi = None; + self.permissions.allow_import = None; } pub fn resolve_watch_exclude_set( @@ -1101,7 +1176,6 @@ static DENO_HELP: &str = cstr!( Tooling: bench Run benchmarks deno bench bench.ts - cache Cache the dependencies check Type-check the dependencies clean Remove the cache directory compile Compile the script into a self contained executable @@ -1183,28 +1257,7 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { .get_arguments() .any(|arg| arg.get_id().as_str() == "unstable") { - subcommand = subcommand - .mut_arg("unstable", |arg| { - let new_help = arg - .get_help() - .unwrap() - .to_string() - .split_once("\n") - .unwrap() - .0 - .to_string(); - arg.help_heading(UNSTABLE_HEADING).help(new_help) - }) - .mut_args(|arg| { - // long_help here is being used as a metadata, see unstable args definition - if arg.get_help_heading() == Some(UNSTABLE_HEADING) - && arg.get_long_help().is_some() - { - arg.hide(false) - } else { - arg - } - }); + subcommand = enable_unstable(subcommand); } help_parse(&mut flags, subcommand); @@ -1339,6 +1392,31 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { Ok(flags) } +fn enable_unstable(command: Command) -> Command { + command + .mut_arg("unstable", |arg| { + let new_help = arg + .get_help() + .unwrap() + .to_string() + .split_once("\n") + .unwrap() + .0 + .to_string(); + arg.help_heading(UNSTABLE_HEADING).help(new_help) + }) + .mut_args(|arg| { + // long_help here is being used as a metadata, see unstable args definition + if arg.get_help_heading() == Some(UNSTABLE_HEADING) + && arg.get_long_help().is_some() + { + arg.hide(false) + } else { + arg + } + }) +} + macro_rules! heading { ($($name:ident = $title:expr),+; $total:literal) => { $(const $name: &str = $title;)+ @@ -1707,6 +1785,7 @@ Future runs of this module will trigger no downloads or compilation unless --rel ) .arg(frozen_lockfile_arg()) .arg(allow_scripts_arg()) + .arg(allow_import_arg()) }) } @@ -1766,6 +1845,7 @@ Unless --reload is specified, this command will not re-download already cached d .required_unless_present("help") .value_hint(ValueHint::FilePath), ) + .arg(allow_import_arg()) } ) } @@ -1775,11 +1855,15 @@ fn compile_subcommand() -> Command { "compile", cstr!("Compiles the given script into a self contained executable. - deno compile -A jsr:@std/http/file-server + deno compile --allow-read --allow-net jsr:@std/http/file-server deno compile --output file_server jsr:@std/http/file-server Any flags specified which affect runtime behavior will be applied to the resulting binary. +This allows distribution of a Deno application to systems that do not have Deno installed. +Under the hood, it bundles a slimmed down version of the Deno runtime along with your +JavaScript or TypeScript code. + Cross-compiling to different target architectures is supported using the --target flag. On the first invocation with deno will download the proper binary and cache it in $DENO_DIR. @@ -1994,6 +2078,7 @@ Show documentation for runtime built-ins: .arg(no_lock_arg()) .arg(no_npm_arg()) .arg(no_remote_arg()) + .arg(allow_import_arg()) .arg( Arg::new("json") .long("json") @@ -2145,6 +2230,9 @@ Supported file types which are behind corresponding unstable flags (see formatti Format stdin and write to stdout: cat file.ts | deno fmt - +Check if the files are formatted: + deno fmt --check + Ignore formatting code by preceding it with an ignore comment: // deno-fmt-ignore @@ -2295,7 +2383,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file: } fn init_subcommand() -> Command { - command("init", "Initialize a new project", UnstableArgsConfig::None).defer( + command("init", "scaffolds a basic Deno project with a script, test, and configuration file", UnstableArgsConfig::None).defer( |cmd| { cmd .arg(Arg::new("dir").value_hint(ValueHint::DirPath)) @@ -2340,7 +2428,7 @@ The following information is shown: .arg( location_arg() .conflicts_with("file") - .help("Show files used for origin bound APIs like the Web Storage API when running a script with '--location='") + .help(cstr!("Show files used for origin bound APIs like the Web Storage API when running a script with --location=<>")) ) .arg(no_check_arg().hide(true)) // TODO(lucacasonato): remove for 2.0 .arg(no_config_arg()) @@ -2358,6 +2446,7 @@ The following information is shown: .help("UNSTABLE: Outputs the information in JSON format") .action(ArgAction::SetTrue), )) + .arg(allow_import_arg()) } fn install_subcommand() -> Command { @@ -2381,7 +2470,7 @@ If the --global flag is set, installs a script as an executable in the deno install --global --allow-net --allow-read jsr:@std/http/file-server deno install -g https://examples.deno.land/color-logging.ts -To change the executable name, use -n/--name: +To change the executable name, use -n/--name: deno install -g --allow-net --allow-read -n serve jsr:@std/http/file-server The executable name is inferred by default: @@ -2663,7 +2752,12 @@ To ignore linting on an entire file, you can add an ignore comment at the top of } fn repl_subcommand() -> Command { - command("repl", "Read Eval Print Loop", UnstableArgsConfig::ResolutionAndRuntime) + command("repl", cstr!( + "Starts a read-eval-print-loop, which lets you interactively build up program state in the global context. +It is especially useful for quick prototyping and checking snippets of code. + +TypeScript is supported, however it is not type-checked, only transpiled." + ), UnstableArgsConfig::ResolutionAndRuntime) .defer(|cmd| runtime_args(cmd, true, true) .arg(check_arg(false)) .arg( @@ -2746,8 +2840,6 @@ fn serve_subcommand() -> Command { The serve command uses the default exports of the main module to determine which servers to start. -See https://docs.deno.com/runtime/manual/tools/serve for more detailed information. - Start a server defined in server.ts: deno serve server.ts @@ -2758,7 +2850,7 @@ Start a server defined in server.ts, watching for changes and running on port 50 .arg( Arg::new("port") .long("port") - .help("The TCP port to serve on, defaulting to 8000. Pass 0 to pick a random free port") + .help(cstr!("The TCP port to serve on. Pass 0 to pick a random free port [default: 8000]")) .value_parser(value_parser!(u16)), ) .arg( @@ -2968,11 +3060,13 @@ fn parallel_arg(descr: &str) -> Arg { fn types_subcommand() -> Command { command( "types", - "Print runtime TypeScript declarations. + cstr!( + "Print runtime TypeScript declarations. deno types > lib.deno.d.ts -The declaration file could be saved and used for typing information.", +The declaration file could be saved and used for typing information." + ), UnstableArgsConfig::None, ) } @@ -3082,7 +3176,7 @@ See the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs } fn publish_subcommand() -> Command { - command("publish", "Publish the current working directory's package or workspace", UnstableArgsConfig::ResolutionOnly) + command("publish", "Publish the current working directory's package or workspace to JSR", UnstableArgsConfig::ResolutionOnly) .defer(|cmd| { cmd .arg( @@ -3151,47 +3245,44 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { .after_help(cstr!(r#"Permission options: Docs: https://docs.deno.com/go/permissions - -A, --allow-all Allow all permissions. - --no-prompt Always throw if required permission wasn't passed. - Can also be set via the DENO_NO_PROMPT environment variable. - -R, --allow-read[=<...] Allow file system read access. Optionally specify allowed paths. - --allow-read | --allow-read="/etc,/var/log.txt" - -W, --allow-write[=<...] Allow file system write access. Optionally specify allowed paths. - --allow-write | --allow-write="/etc,/var/log.txt" - -N, --allow-net[=<...] Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary. - --allow-net | --allow-net="localhost:8080,deno.land" - -E, --allow-env[=<...] Allow access to environment variables. Optionally specify accessible environment variables. - --allow-env | --allow-env="PORT,HOME,PATH" - -S, --allow-sys[=<...] Allow access to OS information. Optionally allow specific APIs by function name. - --allow-sys | --allow-sys="systemMemoryInfo,osRelease" - --allow-run[=<...] Allow running subprocesses. Optionally specify allowed runnable program names. - --allow-run | --allow-run="whoami,ps" - --allow-ffi[=<...] (Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files. - --allow-ffi | --allow-ffi="./libfoo.so" - --deny-read[=<...] Deny file system read access. Optionally specify denied paths. - --deny-read | --deny-read="/etc,/var/log.txt" - --deny-write[=<...] Deny file system write access. Optionally specify denied paths. - --deny-write | --deny-write="/etc,/var/log.txt" - --deny-net[=<...] Deny network access. Optionally specify defined IP addresses and host names, with ports as necessary. - --deny-net | --deny-net="localhost:8080,deno.land" - --deny-env[=<...] Deny access to environment variables. Optionally specify inacessible environment variables. - --deny-env | --deny-env="PORT,HOME,PATH" - -S, --deny-sys[=<...] Deny access to OS information. Optionally deny specific APIs by function name. - --deny-sys | --deny-sys="systemMemoryInfo,osRelease" - --deny-run[=<...] Deny running subprocesses. Optionally specify denied runnable program names. - --deny-run | --deny-run="whoami,ps" - --deny-ffi[=<...] (Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files. - --deny-ffi | --deny-ffi="./libfoo.so" + -A, --allow-all Allow all permissions. + --no-prompt Always throw if required permission wasn't passed. + Can also be set via the DENO_NO_PROMPT environment variable. + -R, --allow-read[=<...] Allow file system read access. Optionally specify allowed paths. + --allow-read | --allow-read="/etc,/var/log.txt" + -W, --allow-write[=<...] Allow file system write access. Optionally specify allowed paths. + --allow-write | --allow-write="/etc,/var/log.txt" + -I, --allow-import[=<...] Allow importing from remote hosts. Optionally specify allowed IP addresses and host names, with ports as necessary. + Default value: deno.land:443,jsr.io:443,esm.sh:443,raw.githubusercontent.com:443,user.githubusercontent.com:443 + --allow-import | --allow-import="example.com,github.com" + -N, --allow-net[=<...] Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary. + --allow-net | --allow-net="localhost:8080,deno.land" + -E, --allow-env[=<...] Allow access to environment variables. Optionally specify accessible environment variables. + --allow-env | --allow-env="PORT,HOME,PATH" + -S, --allow-sys[=<...] Allow access to OS information. Optionally allow specific APIs by function name. + --allow-sys | --allow-sys="systemMemoryInfo,osRelease" + --allow-run[=<...] Allow running subprocesses. Optionally specify allowed runnable program names. + --allow-run | --allow-run="whoami,ps" + --allow-ffi[=<...] (Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files. + --allow-ffi | --allow-ffi="./libfoo.so" + --deny-read[=<...] Deny file system read access. Optionally specify denied paths. + --deny-read | --deny-read="/etc,/var/log.txt" + --deny-write[=<...] Deny file system write access. Optionally specify denied paths. + --deny-write | --deny-write="/etc,/var/log.txt" + --deny-net[=<...] Deny network access. Optionally specify defined IP addresses and host names, with ports as necessary. + --deny-net | --deny-net="localhost:8080,deno.land" + --deny-env[=<...] Deny access to environment variables. Optionally specify inacessible environment variables. + --deny-env | --deny-env="PORT,HOME,PATH" + --deny-sys[=<...] Deny access to OS information. Optionally deny specific APIs by function name. + --deny-sys | --deny-sys="systemMemoryInfo,osRelease" + --deny-run[=<...] Deny running subprocesses. Optionally specify denied runnable program names. + --deny-run | --deny-run="whoami,ps" + --deny-ffi[=<...] (Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files. + --deny-ffi | --deny-ffi="./libfoo.so" "#)) .arg( { - let mut arg = Arg::new("allow-all") - .short('A') - .long("allow-all") - .action(ArgAction::SetTrue) - .help("Allow all permissions") - .hide(true) - ; + let mut arg = allow_all_arg().hide(true); if let Some(requires) = requires { arg = arg.requires(requires) } @@ -3200,7 +3291,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("allow-read") + let mut arg = Arg::new("allow-read") .long("allow-read") .short('R') .num_args(0..) @@ -3218,7 +3309,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("deny-read") + let mut arg = Arg::new("deny-read") .long("deny-read") .num_args(0..) .action(ArgAction::Append) @@ -3235,7 +3326,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("allow-write") + let mut arg = Arg::new("allow-write") .long("allow-write") .short('W') .num_args(0..) @@ -3253,7 +3344,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("deny-write") + let mut arg = Arg::new("deny-write") .long("deny-write") .num_args(0..) .action(ArgAction::Append) @@ -3270,7 +3361,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("allow-net") + let mut arg = Arg::new("allow-net") .long("allow-net") .short('N') .num_args(0..) @@ -3289,7 +3380,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("deny-net") + let mut arg = Arg::new("deny-net") .long("deny-net") .num_args(0..) .use_value_delimiter(true) @@ -3372,7 +3463,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { .require_equals(true) .value_name("API_NAME") .help("Allow access to OS information. Optionally allow specific APIs by function name") - .value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)) + .value_parser(|key: &str| SysDescriptor::parse(key.to_string()).map(|s| s.into_string())) .hide(true) ; if let Some(requires) = requires { @@ -3383,14 +3474,14 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("deny-sys") + let mut arg = Arg::new("deny-sys") .long("deny-sys") .num_args(0..) .use_value_delimiter(true) .require_equals(true) .value_name("API_NAME") .help("Deny access to OS information. Optionally deny specific APIs by function name") - .value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)) + .value_parser(|key: &str| SysDescriptor::parse(key.to_string()).map(|s| s.into_string())) .hide(true) ; if let Some(requires) = requires { @@ -3418,7 +3509,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { ) .arg( { - let mut arg = Arg::new("deny-run") + let mut arg = Arg::new("deny-run") .long("deny-run") .num_args(0..) .use_value_delimiter(true) @@ -3474,8 +3565,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { .long("allow-hrtime") .action(ArgAction::SetTrue) .help("REMOVED in Deno 2.0") - .hide(true) - ; + .hide(true); if let Some(requires) = requires { arg = arg.requires(requires) } @@ -3488,8 +3578,7 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { .long("deny-hrtime") .action(ArgAction::SetTrue) .help("REMOVED in Deno 2.0") - .hide(true) - ; + .hide(true); if let Some(requires) = requires { arg = arg.requires(requires) } @@ -3509,6 +3598,34 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { arg } ) + .arg( + { + let mut arg = allow_import_arg().hide(true); + if let Some(requires) = requires { + // allow this for install --global + if requires != "global" { + arg = arg.requires(requires) + } + } + arg + } + ) +} + +fn allow_all_arg() -> Arg { + Arg::new("allow-all") + .short('A') + .long("allow-all") + .conflicts_with("allow-read") + .conflicts_with("allow-write") + .conflicts_with("allow-net") + .conflicts_with("allow-env") + .conflicts_with("allow-run") + .conflicts_with("allow-sys") + .conflicts_with("allow-ffi") + .conflicts_with("allow-import") + .action(ArgAction::SetTrue) + .help("Allow all permissions") } fn runtime_args( @@ -3537,6 +3654,20 @@ fn runtime_args( .arg(strace_ops_arg()) } +fn allow_import_arg() -> Arg { + Arg::new("allow-import") + .long("allow-import") + .short('I') + .num_args(0..) + .use_value_delimiter(true) + .require_equals(true) + .value_name("IP_OR_HOSTNAME") + .help(cstr!( + "Allow importing from remote hosts. Optionally specify allowed IP addresses and host names, with ports as necessary. Default value: deno.land:443,jsr.io:443,esm.sh:443,raw.githubusercontent.com:443,user.githubusercontent.com:443" + )) + .value_parser(flags_net::validator) +} + fn inspect_args(app: Command) -> Command { app .arg( @@ -3683,7 +3814,9 @@ fn location_arg() -> Arg { url.set_password(None).unwrap(); Ok(url) }) - .help("Value of 'globalThis.location' used by some web APIs") + .help(cstr!( + "Value of globalThis.location used by some web APIs" + )) .value_hint(ValueHint::Url) } @@ -4174,6 +4307,7 @@ fn cache_parse( unstable_args_parse(flags, matches, UnstableArgsConfig::ResolutionOnly); frozen_lockfile_arg_parse(flags, matches); allow_scripts_arg_parse(flags, matches)?; + allow_import_parse(flags, matches); let files = matches.remove_many::("file").unwrap().collect(); flags.subcommand = DenoSubcommand::Cache(CacheFlags { files }); Ok(()) @@ -4195,6 +4329,7 @@ fn check_parse( doc: matches.get_flag("doc"), doc_only: matches.get_flag("doc-only"), }); + allow_import_parse(flags, matches); Ok(()) } @@ -4320,6 +4455,7 @@ fn doc_parse( no_lock_arg_parse(flags, matches); no_npm_arg_parse(flags, matches); no_remote_arg_parse(flags, matches); + allow_import_parse(flags, matches); let source_files_val = matches.remove_many::("source_file"); let source_files = if let Some(val) = source_files_val { @@ -4460,6 +4596,7 @@ fn info_parse( lock_args_parse(flags, matches); no_remote_arg_parse(flags, matches); no_npm_arg_parse(flags, matches); + allow_import_parse(flags, matches); let json = matches.get_flag("json"); flags.subcommand = DenoSubcommand::Info(InfoFlags { file: matches.remove_one::("file"), @@ -4495,6 +4632,7 @@ fn install_parse( force, }), }); + return Ok(()); } @@ -4532,7 +4670,7 @@ fn json_reference_parse( app.build(); fn serialize_command( - command: &mut Command, + mut command: Command, top_level: bool, ) -> deno_core::serde_json::Value { let args = command @@ -4540,7 +4678,7 @@ fn json_reference_parse( .filter(|arg| { !arg.is_hide_set() && if top_level { - true + arg.is_global_set() } else { !arg.is_global_set() } @@ -4549,40 +4687,49 @@ fn json_reference_parse( let name = arg.get_id().as_str(); let short = arg.get_short(); let long = arg.get_long(); - let aliases = arg.get_visible_aliases(); let required = arg.is_required_set(); - let help = arg.get_help().map(|help| help.to_string()); + let help = arg.get_help().map(|help| help.ansi().to_string()); + let help_heading = arg + .get_help_heading() + .map(|help_heading| help_heading.to_string()); let usage = arg.to_string(); json!({ "name": name, "short": short, "long": long, - "aliases": aliases, "required": required, "help": help, + "help_heading": help_heading, "usage": usage, }) }) .collect::>(); let name = command.get_name().to_string(); - let about = command.get_about().map(|about| about.to_string()); - let visible_aliases = command - .get_visible_aliases() - .map(|s| s.to_string()) - .collect::>(); - let usage = command.render_usage().to_string(); + let about = command.get_about().map(|about| about.ansi().to_string()); + let usage = command.render_usage().ansi().to_string(); let subcommands = command - .get_subcommands_mut() - .map(|command| serialize_command(command, false)) + .get_subcommands() + .map(|command| { + serialize_command( + if command + .get_arguments() + .any(|arg| arg.get_id().as_str() == "unstable") + { + enable_unstable(command.clone()) + } else { + command.clone() + }, + false, + ) + }) .collect::>(); json!({ "name": name, "about": about, - "visible_aliases": visible_aliases, "args": args, "subcommands": subcommands, "usage": usage, @@ -4590,7 +4737,7 @@ fn json_reference_parse( } flags.subcommand = DenoSubcommand::JSONReference(JSONReferenceFlags { - json: serialize_command(&mut app, true), + json: serialize_command(app, true), }) } @@ -5175,13 +5322,22 @@ fn permission_args_parse( } if matches.get_flag("allow-hrtime") || matches.get_flag("deny-hrtime") { - log::warn!("⚠️ Warning: `allow-hrtime` and `deny-hrtime` have been removed in Deno 2, as high resolution time is now always allowed."); + // use eprintln instead of log::warn because logging hasn't been initialized yet + #[allow(clippy::print_stderr)] + { + eprintln!( + "{} `allow-hrtime` and `deny-hrtime` have been removed in Deno 2, as high resolution time is now always allowed", + deno_runtime::colors::yellow("Warning") + ); + } } if matches.get_flag("allow-all") { flags.allow_all(); } + allow_import_parse(flags, matches); + if matches.get_flag("no-prompt") { flags.permissions.no_prompt = true; } @@ -5189,6 +5345,13 @@ fn permission_args_parse( Ok(()) } +fn allow_import_parse(flags: &mut Flags, matches: &mut ArgMatches) { + if let Some(imports_wl) = matches.remove_many::("allow-import") { + let imports_allowlist = flags_net::parse(imports_wl.collect()).unwrap(); + flags.permissions.allow_import = Some(imports_allowlist); + } +} + fn unsafely_ignore_certificate_errors_parse( flags: &mut Flags, matches: &mut ArgMatches, @@ -6215,7 +6378,7 @@ mod tests { #[test] fn short_permission_flags() { - let r = flags_from_vec(svec!["deno", "run", "-RNESW", "gist.ts"]); + let r = flags_from_vec(svec!["deno", "run", "-RNESWI", "gist.ts"]); assert_eq!( r.unwrap(), Flags { @@ -6226,6 +6389,7 @@ mod tests { allow_read: Some(vec![]), allow_write: Some(vec![]), allow_env: Some(vec![]), + allow_import: Some(vec![]), allow_net: Some(vec![]), allow_sys: Some(vec![]), ..Default::default() @@ -10777,7 +10941,7 @@ mod tests { } ); // just make sure this doesn't panic - let _ = flags.permissions.to_options(); + let _ = flags.permissions.to_options(&[]); } #[test] @@ -10852,4 +11016,46 @@ mod tests { Usage: deno repl [OPTIONS] [-- [ARGS]...]\n" ) } + + #[test] + fn test_allow_import_host_from_url() { + fn parse(text: &str) -> Option { + allow_import_host_from_url(&Url::parse(text).unwrap()) + } + + assert_eq!(parse("https://jsr.io"), None); + assert_eq!( + parse("http://127.0.0.1:4250"), + Some("127.0.0.1:4250".to_string()) + ); + assert_eq!(parse("http://jsr.io"), Some("jsr.io:80".to_string())); + assert_eq!( + parse("https://example.com"), + Some("example.com:443".to_string()) + ); + assert_eq!( + parse("http://example.com"), + Some("example.com:80".to_string()) + ); + assert_eq!(parse("file:///example.com"), None); + } + + #[test] + fn allow_all_conflicts_allow_perms() { + let flags = [ + "--allow-read", + "--allow-write", + "--allow-net", + "--allow-env", + "--allow-run", + "--allow-sys", + "--allow-ffi", + "--allow-import", + ]; + for flag in flags { + let r = + flags_from_vec(svec!["deno", "run", "--allow-all", flag, "foo.ts"]); + assert!(r.is_err()); + } + } } diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 1c92777ae3..2ae7098da5 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -20,13 +20,13 @@ use deno_config::workspace::WorkspaceDiscoverOptions; use deno_config::workspace::WorkspaceDiscoverStart; use deno_config::workspace::WorkspaceLintConfig; use deno_config::workspace::WorkspaceResolver; -use deno_core::normalize_path; use deno_core::resolve_url_or_path; use deno_graph::GraphKind; use deno_npm::npm_rc::NpmRc; use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::NpmSystemInfo; +use deno_path_util::normalize_path; use deno_semver::npm::NpmPackageReqReference; use import_map::resolve_import_map_value_from_specifier; @@ -69,6 +69,8 @@ use std::collections::HashMap; use std::env; use std::io::BufReader; use std::io::Cursor; +use std::io::Read; +use std::io::Seek; use std::net::SocketAddr; use std::num::NonZeroUsize; use std::path::Path; @@ -742,15 +744,33 @@ pub enum NpmProcessStateKind { Byonm, } -pub(crate) const NPM_RESOLUTION_STATE_ENV_VAR_NAME: &str = - "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE"; - static NPM_PROCESS_STATE: Lazy> = Lazy::new(|| { - let state = std::env::var(NPM_RESOLUTION_STATE_ENV_VAR_NAME).ok()?; - let state: NpmProcessState = serde_json::from_str(&state).ok()?; - // remove the environment variable so that sub processes - // that are spawned do not also use this. - std::env::remove_var(NPM_RESOLUTION_STATE_ENV_VAR_NAME); + use deno_runtime::ops::process::NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME; + let fd = std::env::var(NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME).ok()?; + std::env::remove_var(NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME); + let fd = fd.parse::().ok()?; + let mut file = { + use deno_runtime::deno_io::FromRawIoHandle; + unsafe { std::fs::File::from_raw_io_handle(fd as _) } + }; + let mut buf = Vec::new(); + // seek to beginning. after the file is written the position will be inherited by this subprocess, + // and also this file might have been read before + file.seek(std::io::SeekFrom::Start(0)).unwrap(); + file + .read_to_end(&mut buf) + .inspect_err(|e| { + log::error!("failed to read npm process state from fd {fd}: {e}"); + }) + .ok()?; + let state: NpmProcessState = serde_json::from_slice(&buf) + .inspect_err(|e| { + log::error!( + "failed to deserialize npm process state: {e} {}", + String::from_utf8_lossy(&buf) + ) + }) + .ok()?; Some(state) }); @@ -769,6 +789,7 @@ pub struct CliOptions { // application need not concern itself with, so keep these private flags: Arc, initial_cwd: PathBuf, + main_module_cell: std::sync::OnceLock>, maybe_node_modules_folder: Option, npmrc: Arc, maybe_lockfile: Option>, @@ -825,6 +846,7 @@ impl CliOptions { npmrc, maybe_node_modules_folder, overrides: Default::default(), + main_module_cell: std::sync::OnceLock::new(), start_dir, deno_dir_provider, }) @@ -1105,40 +1127,39 @@ impl CliOptions { self.flags.env_file.as_ref() } - pub fn resolve_main_module(&self) -> Result { - let main_module = match &self.flags.subcommand { - DenoSubcommand::Compile(compile_flags) => { - resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())? - } - DenoSubcommand::Eval(_) => { - resolve_url_or_path("./$deno$eval.ts", self.initial_cwd())? - } - DenoSubcommand::Repl(_) => { - resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())? - } - DenoSubcommand::Run(run_flags) => { - if run_flags.is_stdin() { - std::env::current_dir() - .context("Unable to get CWD") - .and_then(|cwd| { - resolve_url_or_path("./$deno$stdin.ts", &cwd) - .map_err(AnyError::from) - })? - } else if NpmPackageReqReference::from_str(&run_flags.script).is_ok() { - ModuleSpecifier::parse(&run_flags.script)? - } else { - resolve_url_or_path(&run_flags.script, self.initial_cwd())? - } - } - DenoSubcommand::Serve(run_flags) => { - resolve_url_or_path(&run_flags.script, self.initial_cwd())? - } - _ => { - bail!("No main module.") - } - }; + pub fn resolve_main_module(&self) -> Result<&ModuleSpecifier, AnyError> { + self + .main_module_cell + .get_or_init(|| { + let main_module = match &self.flags.subcommand { + DenoSubcommand::Compile(compile_flags) => { + resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())? + } + DenoSubcommand::Eval(_) => { + resolve_url_or_path("./$deno$eval.ts", self.initial_cwd())? + } + DenoSubcommand::Repl(_) => { + resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())? + } + DenoSubcommand::Run(run_flags) => { + if run_flags.is_stdin() { + resolve_url_or_path("./$deno$stdin.ts", self.initial_cwd())? + } else { + resolve_url_or_path(&run_flags.script, self.initial_cwd())? + } + } + DenoSubcommand::Serve(run_flags) => { + resolve_url_or_path(&run_flags.script, self.initial_cwd())? + } + _ => { + bail!("No main module.") + } + }; - Ok(main_module) + Ok(main_module) + }) + .as_ref() + .map_err(|err| deno_core::anyhow::anyhow!("{}", err)) } pub fn resolve_file_header_overrides( @@ -1159,7 +1180,7 @@ impl CliOptions { (maybe_main_specifier, maybe_content_type) { HashMap::from([( - main_specifier, + main_specifier.clone(), HashMap::from([("content-type".to_string(), content_type.to_string())]), )]) } else { @@ -1322,11 +1343,9 @@ impl CliOptions { )?; Ok(deno_lint::linter::LintConfig { - default_jsx_factory: transpile_options - .jsx_automatic + default_jsx_factory: (!transpile_options.jsx_automatic) .then(|| transpile_options.jsx_factory.clone()), - default_jsx_fragment_factory: transpile_options - .jsx_automatic + default_jsx_fragment_factory: (!transpile_options.jsx_automatic) .then(|| transpile_options.jsx_fragment_factory.clone()), }) } @@ -1480,7 +1499,34 @@ impl CliOptions { } pub fn permissions_options(&self) -> PermissionsOptions { - self.flags.permissions.to_options() + fn files_to_urls(files: &[String]) -> Vec> { + files + .iter() + .filter_map(|f| Url::parse(f).ok().map(Cow::Owned)) + .collect() + } + + // get a list of urls to imply for --allow-import + let cli_arg_urls = self + .resolve_main_module() + .ok() + .map(|url| vec![Cow::Borrowed(url)]) + .or_else(|| match &self.flags.subcommand { + DenoSubcommand::Cache(cache_flags) => { + Some(files_to_urls(&cache_flags.files)) + } + DenoSubcommand::Check(check_flags) => { + Some(files_to_urls(&check_flags.files)) + } + DenoSubcommand::Install(InstallFlags { + kind: InstallKind::Global(flags), + }) => Url::parse(&flags.module_url) + .ok() + .map(|url| vec![Cow::Owned(url)]), + _ => None, + }) + .unwrap_or_default(); + self.flags.permissions.to_options(&cli_arg_urls) } pub fn reload_flag(&self) -> bool { @@ -1654,6 +1700,12 @@ impl CliOptions { allowed: self.flags.allow_scripts.clone(), initial_cwd: self.initial_cwd.clone(), root_dir: self.workspace().root_dir_path(), + explicit_install: matches!( + self.sub_command(), + DenoSubcommand::Install(_) + | DenoSubcommand::Cache(_) + | DenoSubcommand::Add(_) + ), } } } diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index 88d8a31c04..7b7059c224 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -126,9 +126,9 @@ impl DenoDir { self.root.join("registries") } - /// Path to the dependencies cache folder. - pub fn deps_folder_path(&self) -> PathBuf { - self.root.join("deps") + /// Path to the remote cache folder. + pub fn remote_folder_path(&self) -> PathBuf { + self.root.join("remote") } /// Path to the origin data cache folder. diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index a95c350866..628502c506 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -1,14 +1,17 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::args::jsr_url; use crate::args::CacheSetting; use crate::errors::get_error_class_name; use crate::file_fetcher::FetchNoFollowOptions; use crate::file_fetcher::FetchOptions; -use crate::file_fetcher::FetchPermissionsOption; +use crate::file_fetcher::FetchPermissionsOptionRef; use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileOrRedirect; use crate::npm::CliNpmResolver; use crate::util::fs::atomic_write_file_with_retries; +use crate::util::fs::atomic_write_file_with_retries_and_fs; +use crate::util::fs::AtomicWriteFileFsAdapter; use crate::util::path::specifier_has_extension; use deno_ast::MediaType; @@ -19,6 +22,7 @@ use deno_graph::source::CacheInfo; use deno_graph::source::LoadFuture; use deno_graph::source::LoadResponse; use deno_graph::source::Loader; +use deno_runtime::deno_permissions::PermissionsContainer; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -75,8 +79,12 @@ impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv { atomic_write_file_with_retries(path, bytes, CACHE_PERM) } - fn remove_file(&self, path: &Path) -> std::io::Result<()> { - std::fs::remove_file(path) + fn canonicalize_path(&self, path: &Path) -> std::io::Result { + crate::util::fs::canonicalize_path(path) + } + + fn create_dir_all(&self, path: &Path) -> std::io::Result<()> { + std::fs::create_dir_all(path) } fn modified(&self, path: &Path) -> std::io::Result> { @@ -98,12 +106,79 @@ impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv { } } +#[derive(Debug, Clone)] +pub struct DenoCacheEnvFsAdapter<'a>( + pub &'a dyn deno_runtime::deno_fs::FileSystem, +); + +impl<'a> deno_cache_dir::DenoCacheEnv for DenoCacheEnvFsAdapter<'a> { + fn read_file_bytes(&self, path: &Path) -> std::io::Result> { + self + .0 + .read_file_sync(path, None) + .map_err(|err| err.into_io_error()) + } + + fn atomic_write_file( + &self, + path: &Path, + bytes: &[u8], + ) -> std::io::Result<()> { + atomic_write_file_with_retries_and_fs( + &AtomicWriteFileFsAdapter { + fs: self.0, + write_mode: CACHE_PERM, + }, + path, + bytes, + ) + } + + fn canonicalize_path(&self, path: &Path) -> std::io::Result { + self.0.realpath_sync(path).map_err(|e| e.into_io_error()) + } + + fn create_dir_all(&self, path: &Path) -> std::io::Result<()> { + self + .0 + .mkdir_sync(path, true, None) + .map_err(|e| e.into_io_error()) + } + + fn modified(&self, path: &Path) -> std::io::Result> { + self + .0 + .stat_sync(path) + .map(|stat| { + stat + .mtime + .map(|ts| SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(ts)) + }) + .map_err(|e| e.into_io_error()) + } + + fn is_file(&self, path: &Path) -> bool { + self.0.is_file_sync(path) + } + + fn time_now(&self) -> SystemTime { + SystemTime::now() + } +} + pub type GlobalHttpCache = deno_cache_dir::GlobalHttpCache; pub type LocalHttpCache = deno_cache_dir::LocalHttpCache; pub type LocalLspHttpCache = deno_cache_dir::LocalLspHttpCache; pub use deno_cache_dir::HttpCache; +pub struct FetchCacherOptions { + pub file_header_overrides: HashMap>, + pub permissions: PermissionsContainer, + /// If we're publishing for `deno publish`. + pub is_deno_publish: bool, +} + /// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides /// a concise interface to the DENO_DIR when building module graphs. pub struct FetchCacher { @@ -112,26 +187,27 @@ pub struct FetchCacher { global_http_cache: Arc, npm_resolver: Arc, module_info_cache: Arc, - permissions: FetchPermissionsOption, + permissions: PermissionsContainer, cache_info_enabled: bool, + is_deno_publish: bool, } impl FetchCacher { pub fn new( file_fetcher: Arc, - file_header_overrides: HashMap>, global_http_cache: Arc, npm_resolver: Arc, module_info_cache: Arc, - permissions: FetchPermissionsOption, + options: FetchCacherOptions, ) -> Self { Self { file_fetcher, - file_header_overrides, global_http_cache, npm_resolver, module_info_cache, - permissions, + file_header_overrides: options.file_header_overrides, + permissions: options.permissions, + is_deno_publish: options.is_deno_publish, cache_info_enabled: false, } } @@ -208,10 +284,24 @@ impl Loader for FetchCacher { } } + if self.is_deno_publish + && matches!(specifier.scheme(), "http" | "https") + && !specifier.as_str().starts_with(jsr_url().as_str()) + { + // mark non-JSR remote modules as external so we don't need --allow-import + // permissions as these will error out later when publishing + return Box::pin(futures::future::ready(Ok(Some( + LoadResponse::External { + specifier: specifier.clone(), + }, + )))); + } + let file_fetcher = self.file_fetcher.clone(); let file_header_overrides = self.file_header_overrides.clone(); let permissions = self.permissions.clone(); let specifier = specifier.clone(); + let is_statically_analyzable = !options.was_dynamic_root; async move { let maybe_cache_setting = match options.cache_setting { @@ -230,7 +320,11 @@ impl Loader for FetchCacher { .fetch_no_follow_with_options(FetchNoFollowOptions { fetch_options: FetchOptions { specifier: &specifier, - permissions: permissions.as_ref(), + permissions: if is_statically_analyzable { + FetchPermissionsOptionRef::StaticContainer(&permissions) + } else { + FetchPermissionsOptionRef::DynamicContainer(&permissions) + }, maybe_accept: None, maybe_cache_setting: maybe_cache_setting.as_ref(), }, diff --git a/cli/factory.rs b/cli/factory.rs index 0f49546d07..b96a133e98 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -32,17 +32,19 @@ use crate::module_loader::ModuleLoadPreparer; use crate::node::CliCjsCodeAnalyzer; use crate::node::CliNodeCodeTranslator; use crate::npm::create_cli_npm_resolver; +use crate::npm::CliByonmNpmResolverCreateOptions; use crate::npm::CliNpmResolver; -use crate::npm::CliNpmResolverByonmCreateOptions; use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::resolver::CjsResolutionStore; +use crate::resolver::CliDenoResolverFs; use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::resolver::CliNodeResolver; +use crate::resolver::CliSloppyImportsResolver; use crate::resolver::NpmModuleLoader; -use crate::resolver::SloppyImportsResolver; +use crate::resolver::SloppyImportsCachedFs; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; use crate::tools::coverage::CoverageCollector; @@ -185,7 +187,8 @@ struct CliFactoryServices { node_resolver: Deferred>, npm_resolver: Deferred>, permission_desc_parser: Deferred>, - sloppy_imports_resolver: Deferred>>, + root_permissions_container: Deferred, + sloppy_imports_resolver: Deferred>>, text_only_progress_bar: Deferred, type_checker: Deferred>, cjs_resolutions: Deferred>, @@ -298,7 +301,7 @@ impl CliFactory { pub fn global_http_cache(&self) -> Result<&Arc, AnyError> { self.services.global_http_cache.get_or_try_init(|| { Ok(Arc::new(GlobalHttpCache::new( - self.deno_dir()?.deps_folder_path(), + self.deno_dir()?.remote_folder_path(), crate::cache::RealDenoCacheEnv, ))) }) @@ -359,8 +362,8 @@ impl CliFactory { let cli_options = self.cli_options()?; // For `deno install` we want to force the managed resolver so it can set up `node_modules/` directory. create_cli_npm_resolver(if cli_options.use_byonm() && !matches!(cli_options.sub_command(), DenoSubcommand::Install(_) | DenoSubcommand::Add(_) | DenoSubcommand::Remove(_)) { - CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { - fs: fs.clone(), + CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions { + fs: CliDenoResolverFs(fs.clone()), root_node_modules_dir: Some(match cli_options.node_modules_dir_path() { Some(node_modules_path) => node_modules_path.to_path_buf(), // path needs to be canonicalized for node resolution @@ -403,17 +406,16 @@ impl CliFactory { pub fn sloppy_imports_resolver( &self, - ) -> Result>, AnyError> { + ) -> Result>, AnyError> { self .services .sloppy_imports_resolver .get_or_try_init(|| { - Ok( - self - .cli_options()? - .unstable_sloppy_imports() - .then(|| Arc::new(SloppyImportsResolver::new(self.fs().clone()))), - ) + Ok(self.cli_options()?.unstable_sloppy_imports().then(|| { + Arc::new(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new( + self.fs().clone(), + ))) + })) }) .map(|maybe| maybe.as_ref()) } @@ -626,6 +628,7 @@ impl CliFactory { self.maybe_file_watcher_reporter().clone(), self.file_fetcher()?.clone(), self.global_http_cache()?.clone(), + self.root_permissions_container()?.clone(), ))) }) .await @@ -659,6 +662,7 @@ impl CliFactory { Ok(Arc::new(MainModuleGraphContainer::new( self.cli_options()?.clone(), self.module_load_preparer().await?.clone(), + self.root_permissions_container()?.clone(), ))) }) .await @@ -755,15 +759,20 @@ impl CliFactory { )) } - pub fn create_permissions_container( + pub fn root_permissions_container( &self, - ) -> Result { - let desc_parser = self.permission_desc_parser()?.clone(); - let permissions = Permissions::from_options( - desc_parser.as_ref(), - &self.cli_options()?.permissions_options(), - )?; - Ok(PermissionsContainer::new(desc_parser, permissions)) + ) -> Result<&PermissionsContainer, AnyError> { + self + .services + .root_permissions_container + .get_or_try_init(|| { + let desc_parser = self.permission_desc_parser()?.clone(); + let permissions = Permissions::from_options( + desc_parser.as_ref(), + &self.cli_options()?.permissions_options(), + )?; + Ok(PermissionsContainer::new(desc_parser, permissions)) + }) } pub async fn create_cli_main_worker_factory( @@ -774,6 +783,7 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; + let cli_npm_resolver = self.npm_resolver().await?.clone(); let maybe_file_watcher_communicator = if cli_options.has_hmr() { Some(self.watcher_communicator.clone().unwrap()) } else { @@ -803,6 +813,7 @@ impl CliFactory { self.main_module_graph_container().await?.clone(), self.module_load_preparer().await?.clone(), cli_node_resolver.clone(), + cli_npm_resolver.clone(), NpmModuleLoader::new( self.cjs_resolutions().clone(), self.node_code_translator().await?.clone(), @@ -814,8 +825,8 @@ impl CliFactory { )), node_resolver.clone(), npm_resolver.clone(), - self.permission_desc_parser()?.clone(), self.root_cert_store_provider().clone(), + self.root_permissions_container()?.clone(), StorageKeyResolver::from_options(cli_options), cli_options.sub_command().clone(), self.create_cli_main_worker_options()?, diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 2f4b0b3dc5..69daf14954 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -21,6 +21,7 @@ use deno_core::url::Url; use deno_core::ModuleSpecifier; use deno_graph::source::LoaderChecksum; +use deno_path_util::url_to_file_path; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_web::BlobStore; use log::debug; @@ -135,7 +136,7 @@ impl MemoryFiles { /// Fetch a source file from the local file system. fn fetch_local(specifier: &ModuleSpecifier) -> Result { - let local = specifier.to_file_path().map_err(|_| { + let local = url_to_file_path(specifier).map_err(|_| { uri_error(format!("Invalid file path.\n Specifier: {specifier}")) })?; // If it doesnt have a extension, we want to treat it as typescript by default @@ -173,30 +174,8 @@ fn get_validated_scheme( #[derive(Debug, Copy, Clone)] pub enum FetchPermissionsOptionRef<'a> { AllowAll, - Container(&'a PermissionsContainer), -} - -#[derive(Debug, Clone)] -pub enum FetchPermissionsOption { - AllowAll, - Container(PermissionsContainer), -} - -impl FetchPermissionsOption { - pub fn as_ref(&self) -> FetchPermissionsOptionRef { - match self { - FetchPermissionsOption::AllowAll => FetchPermissionsOptionRef::AllowAll, - FetchPermissionsOption::Container(container) => { - FetchPermissionsOptionRef::Container(container) - } - } - } -} - -impl From for FetchPermissionsOption { - fn from(value: PermissionsContainer) -> Self { - Self::Container(value) - } + DynamicContainer(&'a PermissionsContainer), + StaticContainer(&'a PermissionsContainer), } pub struct FetchOptions<'a> { @@ -564,7 +543,6 @@ impl FileFetcher { } /// Fetch a source file and asynchronously return it. - #[allow(dead_code)] // todo(25469): undo when merging #[inline(always)] pub async fn fetch( &self, @@ -572,7 +550,10 @@ impl FileFetcher { permissions: &PermissionsContainer, ) -> Result { self - .fetch_inner(specifier, FetchPermissionsOptionRef::Container(permissions)) + .fetch_inner( + specifier, + FetchPermissionsOptionRef::StaticContainer(permissions), + ) .await } @@ -647,8 +628,17 @@ impl FileFetcher { FetchPermissionsOptionRef::AllowAll => { // allow } - FetchPermissionsOptionRef::Container(permissions) => { - permissions.check_specifier(specifier)?; + FetchPermissionsOptionRef::StaticContainer(permissions) => { + permissions.check_specifier( + specifier, + deno_runtime::deno_permissions::CheckSpecifierKind::Static, + )?; + } + FetchPermissionsOptionRef::DynamicContainer(permissions) => { + permissions.check_specifier( + specifier, + deno_runtime::deno_permissions::CheckSpecifierKind::Dynamic, + )?; } } if let Some(file) = self.memory_files.get(specifier) { @@ -736,7 +726,7 @@ mod tests { maybe_temp_dir: Option, ) -> (FileFetcher, TempDir, Arc) { let temp_dir = maybe_temp_dir.unwrap_or_default(); - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let blob_store: Arc = Default::default(); let file_fetcher = FileFetcher::new( Arc::new(GlobalHttpCache::new(location, RealDenoCacheEnv)), @@ -974,7 +964,7 @@ mod tests { // This creates a totally new instance, simulating another Deno process // invocation and indicates to "cache bust". - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let file_fetcher = FileFetcher::new( Arc::new(GlobalHttpCache::new( location, @@ -1000,7 +990,7 @@ mod tests { async fn test_fetch_uses_cache() { let _http_server_guard = test_util::http_server(); let temp_dir = TempDir::new(); - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let specifier = resolve_url("http://localhost:4545/subdir/mismatch_ext.ts").unwrap(); @@ -1166,7 +1156,7 @@ mod tests { async fn test_fetch_uses_cache_with_redirects() { let _http_server_guard = test_util::http_server(); let temp_dir = TempDir::new(); - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let specifier = resolve_url("http://localhost:4548/subdir/mismatch_ext.ts").unwrap(); let redirected_specifier = @@ -1334,7 +1324,7 @@ mod tests { async fn test_fetch_no_remote() { let _http_server_guard = test_util::http_server(); let temp_dir = TempDir::new(); - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let file_fetcher = FileFetcher::new( Arc::new(GlobalHttpCache::new( location, @@ -1360,7 +1350,7 @@ mod tests { async fn test_fetch_cache_only() { let _http_server_guard = test_util::http_server(); let temp_dir = TempDir::new(); - let location = temp_dir.path().join("deps").to_path_buf(); + let location = temp_dir.path().join("remote").to_path_buf(); let file_fetcher_01 = FileFetcher::new( Arc::new(GlobalHttpCache::new(location.clone(), RealDenoCacheEnv)), CacheSetting::Only, diff --git a/cli/graph_container.rs b/cli/graph_container.rs index 211b278e08..c463d71a6a 100644 --- a/cli/graph_container.rs +++ b/cli/graph_container.rs @@ -9,9 +9,9 @@ use deno_core::error::AnyError; use deno_core::parking_lot::RwLock; use deno_graph::ModuleGraph; use deno_runtime::colors; +use deno_runtime::deno_permissions::PermissionsContainer; use crate::args::CliOptions; -use crate::file_fetcher::FetchPermissionsOption; use crate::module_loader::ModuleLoadPreparer; use crate::util::fs::collect_specifiers; use crate::util::path::is_script_ext; @@ -45,12 +45,14 @@ pub struct MainModuleGraphContainer { inner: Arc>>, cli_options: Arc, module_load_preparer: Arc, + root_permissions: PermissionsContainer, } impl MainModuleGraphContainer { pub fn new( cli_options: Arc, module_load_preparer: Arc, + root_permissions: PermissionsContainer, ) -> Self { Self { update_queue: Default::default(), @@ -59,6 +61,7 @@ impl MainModuleGraphContainer { )))), cli_options, module_load_preparer, + root_permissions, } } @@ -76,7 +79,7 @@ impl MainModuleGraphContainer { specifiers, false, self.cli_options.ts_type_lib_window(), - FetchPermissionsOption::AllowAll, + self.root_permissions.clone(), ext_overwrite, ) .await?; diff --git a/cli/graph_util.rs b/cli/graph_util.rs index cd98c3824d..e2f6246e74 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -11,17 +11,19 @@ use crate::cache::ModuleInfoCache; use crate::cache::ParsedSourceCache; use crate::colors; use crate::errors::get_error_class_name; -use crate::file_fetcher::FetchPermissionsOption; use crate::file_fetcher::FileFetcher; use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; -use crate::resolver::SloppyImportsResolver; +use crate::resolver::CliSloppyImportsResolver; +use crate::resolver::SloppyImportsCachedFs; use crate::tools::check; use crate::tools::check::TypeChecker; use crate::util::file_watcher::WatcherCommunicator; use crate::util::fs::canonicalize_path; use deno_config::workspace::JsrPackageConfig; +use deno_core::anyhow::bail; use deno_graph::source::LoaderChecksum; +use deno_graph::FillFromLockfileOptions; use deno_graph::JsrLoadError; use deno_graph::ModuleLoadError; use deno_graph::WorkspaceFastCheckOption; @@ -31,7 +33,6 @@ use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::ModuleSpecifier; use deno_graph::source::Loader; -use deno_graph::source::ResolutionMode; use deno_graph::source::ResolveError; use deno_graph::GraphKind; use deno_graph::ModuleError; @@ -39,12 +40,13 @@ use deno_graph::ModuleGraph; use deno_graph::ModuleGraphError; use deno_graph::ResolutionError; use deno_graph::SpecifierError; +use deno_path_util::url_to_file_path; +use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node; -use deno_runtime::fs_util::specifier_to_file_path; +use deno_runtime::deno_permissions::PermissionsContainer; use deno_semver::jsr::JsrDepPackageReq; use deno_semver::package::PackageNv; -use deno_semver::Version; use import_map::ImportMapError; use std::collections::HashSet; use std::error::Error; @@ -52,14 +54,14 @@ use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct GraphValidOptions { pub check_js: bool, - pub follow_type_only: bool, - pub is_vendoring: bool, - /// Whether to exit the process for lockfile errors. - /// Otherwise, surfaces lockfile errors as errors. - pub exit_lockfile_errors: bool, + pub kind: GraphKind, + /// Whether to exit the process for integrity check errors such as + /// lockfile checksum mismatches and JSR integrity failures. + /// Otherwise, surfaces integrity errors as errors. + pub exit_integrity_errors: bool, } /// Check if `roots` and their deps are available. Returns `Ok(())` if @@ -75,17 +77,54 @@ pub fn graph_valid( roots: &[ModuleSpecifier], options: GraphValidOptions, ) -> Result<(), AnyError> { - if options.exit_lockfile_errors { - graph_exit_lock_errors(graph); + if options.exit_integrity_errors { + graph_exit_integrity_errors(graph); } - let mut errors = graph + let mut errors = graph_walk_errors( + graph, + fs, + roots, + GraphWalkErrorsOptions { + check_js: options.check_js, + kind: options.kind, + }, + ); + if let Some(error) = errors.next() { + Err(error) + } else { + // finally surface the npm resolution result + if let Err(err) = &graph.npm_dep_graph_result { + return Err(custom_error( + get_error_class_name(err), + format_deno_graph_error(err.as_ref().deref()), + )); + } + Ok(()) + } +} + +#[derive(Clone)] +pub struct GraphWalkErrorsOptions { + pub check_js: bool, + pub kind: GraphKind, +} + +/// Walks the errors found in the module graph that should be surfaced to users +/// and enhances them with CLI information. +pub fn graph_walk_errors<'a>( + graph: &'a ModuleGraph, + fs: &'a Arc, + roots: &'a [ModuleSpecifier], + options: GraphWalkErrorsOptions, +) -> impl Iterator + 'a { + graph .walk( roots.iter(), deno_graph::WalkOptions { check_js: options.check_js, - follow_type_only: options.follow_type_only, - follow_dynamic: options.is_vendoring, + kind: options.kind, + follow_dynamic: false, prefer_fast_check_graph: false, }, ) @@ -109,7 +148,7 @@ pub fn graph_valid( ) } ModuleGraphError::ModuleError(error) => { - enhanced_lockfile_error_message(error) + enhanced_integrity_error_message(error) .or_else(|| enhanced_sloppy_imports_error_message(fs, error)) .unwrap_or_else(|| format_deno_graph_error(error)) } @@ -132,56 +171,18 @@ pub fn graph_valid( return None; } - if options.is_vendoring { - // warn about failing dynamic imports when vendoring, but don't fail completely - if matches!( - error, - ModuleGraphError::ModuleError(ModuleError::MissingDynamic(_, _)) - ) { - log::warn!("Ignoring: {}", message); - return None; - } - - // ignore invalid downgrades and invalid local imports when vendoring - match &error { - ModuleGraphError::ResolutionError(err) - | ModuleGraphError::TypesResolutionError(err) => { - if matches!( - err, - ResolutionError::InvalidDowngrade { .. } - | ResolutionError::InvalidLocalImport { .. } - ) { - return None; - } - } - ModuleGraphError::ModuleError(_) => {} - } - } - Some(custom_error(get_error_class_name(&error.into()), message)) - }); - if let Some(error) = errors.next() { - Err(error) - } else { - // finally surface the npm resolution result - if let Err(err) = &graph.npm_dep_graph_result { - return Err(custom_error( - get_error_class_name(err), - format_deno_graph_error(err.as_ref().deref()), - )); - } - Ok(()) - } + }) } -pub fn graph_exit_lock_errors(graph: &ModuleGraph) { +pub fn graph_exit_integrity_errors(graph: &ModuleGraph) { for error in graph.module_errors() { - exit_for_lockfile_error(error); + exit_for_integrity_error(error); } } -fn exit_for_lockfile_error(err: &ModuleError) { - if let Some(err_message) = enhanced_lockfile_error_message(err) { +fn exit_for_integrity_error(err: &ModuleError) { + if let Some(err_message) = enhanced_integrity_error_message(err) { log::error!("{} {}", colors::red("error:"), err_message); std::process::exit(10); } @@ -249,6 +250,19 @@ impl ModuleGraphCreator { package_configs: &[JsrPackageConfig], build_fast_check_graph: bool, ) -> Result { + fn graph_has_external_remote(graph: &ModuleGraph) -> bool { + // Earlier on, we marked external non-JSR modules as external. + // If the graph contains any of those, it would cause type checking + // to crash, so since publishing is going to fail anyway, skip type + // checking. + graph.modules().any(|module| match module { + deno_graph::Module::External(external_module) => { + matches!(external_module.specifier.scheme(), "http" | "https") + } + _ => false, + }) + } + let mut roots = Vec::new(); for package_config in package_configs { roots.extend(package_config.config_file.resolve_export_value_urls()?); @@ -262,9 +276,12 @@ impl ModuleGraphCreator { }) .await?; self.graph_valid(&graph)?; - if self.options.type_check_mode().is_true() { + if self.options.type_check_mode().is_true() + && !graph_has_external_remote(&graph) + { self.type_check_graph(graph.clone()).await?; } + if build_fast_check_graph { let fast_check_workspace_members = package_configs .iter() @@ -279,6 +296,7 @@ impl ModuleGraphCreator { }, )?; } + Ok(graph) } @@ -370,6 +388,7 @@ pub struct ModuleGraphBuilder { maybe_file_watcher_reporter: Option, file_fetcher: Arc, global_http_cache: Arc, + root_permissions_container: PermissionsContainer, } impl ModuleGraphBuilder { @@ -386,6 +405,7 @@ impl ModuleGraphBuilder { maybe_file_watcher_reporter: Option, file_fetcher: Arc, global_http_cache: Arc, + root_permissions_container: PermissionsContainer, ) -> Self { Self { options, @@ -399,6 +419,7 @@ impl ModuleGraphBuilder { maybe_file_watcher_reporter, file_fetcher, global_http_cache, + root_permissions_container, } } @@ -553,33 +574,19 @@ impl ModuleGraphBuilder { // populate the information from the lockfile if let Some(lockfile) = &self.lockfile { let lockfile = lockfile.lock(); - for (from, to) in &lockfile.content.redirects { - if let Ok(from) = ModuleSpecifier::parse(from) { - if let Ok(to) = ModuleSpecifier::parse(to) { - if !matches!(from.scheme(), "file" | "npm" | "jsr") { - graph.redirects.insert(from, to); - } - } - } - } - for (req_dep, value) in &lockfile.content.packages.specifiers { - match req_dep.kind { - deno_semver::package::PackageKind::Jsr => { - if let Ok(version) = Version::parse_standard(value) { - graph.packages.add_nv( - req_dep.req.clone(), - PackageNv { - name: req_dep.req.name.clone(), - version, - }, - ); - } - } - deno_semver::package::PackageKind::Npm => { - // ignore - } - } - } + 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())), + }); } } @@ -587,6 +594,12 @@ impl ModuleGraphBuilder { let initial_package_deps_len = graph.packages.package_deps_sum(); let initial_package_mappings_len = graph.packages.mappings().len(); + if roots.iter().any(|r| r.scheme() == "npm") + && self.npm_resolver.as_byonm().is_some() + { + bail!("Resolving npm specifier entrypoints this way is currently not supported with \"nodeModules\": \"manual\". In the meantime, try with --node-modules-dir=auto instead"); + } + graph.build(roots, loader, options).await; let has_redirects_changed = graph.redirects.len() != initial_redirects_len; @@ -670,20 +683,26 @@ impl ModuleGraphBuilder { /// Creates the default loader used for creating a graph. pub fn create_graph_loader(&self) -> cache::FetchCacher { - self.create_fetch_cacher(FetchPermissionsOption::AllowAll) + self.create_fetch_cacher(self.root_permissions_container.clone()) } pub fn create_fetch_cacher( &self, - permissions: FetchPermissionsOption, + permissions: PermissionsContainer, ) -> cache::FetchCacher { cache::FetchCacher::new( self.file_fetcher.clone(), - self.options.resolve_file_header_overrides(), self.global_http_cache.clone(), self.npm_resolver.clone(), self.module_info_cache.clone(), - permissions, + cache::FetchCacherOptions { + file_header_overrides: self.options.resolve_file_header_overrides(), + permissions, + is_deno_publish: matches!( + self.options.sub_command(), + crate::args::DenoSubcommand::Publish { .. } + ), + }, ) } @@ -707,10 +726,13 @@ impl ModuleGraphBuilder { &self.fs, roots, GraphValidOptions { - is_vendoring: false, - follow_type_only: self.options.type_check_mode().is_true(), + kind: if self.options.type_check_mode().is_true() { + GraphKind::All + } else { + GraphKind::CodeOnly + }, check_js: self.options.check_js(), - exit_lockfile_errors: true, + exit_integrity_errors: true, }, ) } @@ -751,8 +773,8 @@ fn enhanced_sloppy_imports_error_message( match error { ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error | ModuleError::Missing(specifier, _) => { - let additional_message = SloppyImportsResolver::new(fs.clone()) - .resolve(specifier, ResolutionMode::Execution)? + let additional_message = CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(fs.clone())) + .resolve(specifier, SloppyImportsResolutionMode::Execution)? .as_suggestion_message(); Some(format!( "{} {} or run with --unstable-sloppy-imports", @@ -764,7 +786,7 @@ fn enhanced_sloppy_imports_error_message( } } -fn enhanced_lockfile_error_message(err: &ModuleError) -> Option { +fn enhanced_integrity_error_message(err: &ModuleError) -> Option { match err { ModuleError::LoadingErr( specifier, @@ -928,13 +950,13 @@ pub fn has_graph_root_local_dependent_changed( std::iter::once(root), deno_graph::WalkOptions { follow_dynamic: true, - follow_type_only: true, + kind: GraphKind::All, prefer_fast_check_graph: true, check_js: true, }, ); while let Some((s, _)) = dependent_specifiers.next() { - if let Ok(path) = specifier_to_file_path(s) { + if let Ok(path) = url_to_file_path(s) { if let Ok(path) = canonicalize_path(&path) { if canonicalized_changed_paths.contains(&path) { return true; diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index fee9c86cbe..d189ab2534 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -2,6 +2,7 @@ use super::diagnostics::DenoDiagnostic; use super::diagnostics::DiagnosticSource; +use super::documents::Document; use super::documents::Documents; use super::language_server; use super::resolver::LspResolver; @@ -9,7 +10,9 @@ use super::tsc; use super::urls::url_to_uri; use crate::args::jsr_url; +use crate::lsp::search::PackageSearchApi; use crate::tools::lint::CliLinter; +use deno_config::workspace::MappedResolution; use deno_lint::diagnostic::LintDiagnosticRange; use deno_ast::SourceRange; @@ -23,8 +26,8 @@ use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; +use deno_path_util::url_to_file_path; use deno_runtime::deno_node::PathClean; -use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::jsr::JsrPackageNvReference; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; @@ -401,7 +404,7 @@ impl<'a> TsResponseImportMapper<'a> { .flatten()?; let root_folder = package_json.path.parent()?; - let specifier_path = specifier_to_file_path(specifier).ok()?; + let specifier_path = url_to_file_path(specifier).ok()?; let mut search_paths = vec![specifier_path.clone()]; // TypeScript will provide a .js extension for quick fixes, so do // a search for the .d.ts file instead @@ -1151,6 +1154,162 @@ impl CodeActionCollection { ..Default::default() })); } + + pub async fn add_source_actions( + &mut self, + document: &Document, + range: &lsp::Range, + language_server: &language_server::Inner, + ) { + async fn deno_types_for_npm_action( + document: &Document, + range: &lsp::Range, + language_server: &language_server::Inner, + ) -> Option { + let (dep_key, dependency, _) = + document.get_maybe_dependency(&range.end)?; + if dependency.maybe_deno_types_specifier.is_some() { + return None; + } + if dependency.maybe_code.maybe_specifier().is_none() + && dependency.maybe_type.maybe_specifier().is_none() + { + // We're using byonm and the package is not cached. + return None; + } + let position = deno_graph::Position::new( + range.end.line as usize, + range.end.character as usize, + ); + let import_range = dependency.imports.iter().find_map(|i| { + if json!(i.kind) != json!("es") && json!(i.kind) != json!("tsType") { + return None; + } + if !i.specifier_range.includes(&position) { + return None; + } + i.full_range.as_ref() + })?; + let referrer = document.specifier(); + let file_referrer = document.file_referrer(); + let config_data = language_server + .config + .tree + .data_for_specifier(file_referrer?)?; + let workspace_resolver = config_data.resolver.clone(); + let npm_ref = if let Ok(resolution) = + workspace_resolver.resolve(&dep_key, document.specifier()) + { + let specifier = match resolution { + MappedResolution::Normal { specifier, .. } + | MappedResolution::ImportMap { specifier, .. } => specifier, + _ => { + return None; + } + }; + NpmPackageReqReference::from_specifier(&specifier).ok()? + } else { + // Only resolve bare package.json deps for byonm. + if !config_data.byonm { + return None; + } + if !language_server + .resolver + .is_bare_package_json_dep(&dep_key, referrer) + { + return None; + } + NpmPackageReqReference::from_str(&format!("npm:{}", &dep_key)).ok()? + }; + let package_name = &npm_ref.req().name; + if package_name.starts_with("@types/") { + return None; + } + let managed_npm_resolver = language_server + .resolver + .maybe_managed_npm_resolver(file_referrer); + if let Some(npm_resolver) = managed_npm_resolver { + if !npm_resolver.is_pkg_req_folder_cached(npm_ref.req()) { + return None; + } + } + if language_server + .resolver + .npm_to_file_url(&npm_ref, document.specifier(), file_referrer) + .is_some() + { + // The package import has types. + return None; + } + let types_package_name = format!("@types/{package_name}"); + let types_package_version = language_server + .npm_search_api + .versions(&types_package_name) + .await + .ok() + .and_then(|versions| versions.first().cloned())?; + let types_specifier_text = + if let Some(npm_resolver) = managed_npm_resolver { + let mut specifier_text = if let Some(req) = + npm_resolver.top_package_req_for_name(&types_package_name) + { + format!("npm:{req}") + } else { + format!("npm:{}@^{}", &types_package_name, types_package_version) + }; + let specifier = ModuleSpecifier::parse(&specifier_text).ok()?; + if let Some(file_referrer) = file_referrer { + if let Some(text) = language_server + .get_ts_response_import_mapper(file_referrer) + .check_specifier(&specifier, referrer) + { + specifier_text = text; + } + } + specifier_text + } else { + types_package_name.clone() + }; + let uri = language_server + .url_map + .specifier_to_uri(referrer, file_referrer) + .ok()?; + let position = lsp::Position { + line: import_range.start.line as u32, + character: import_range.start.character as u32, + }; + let new_text = format!( + "{}// @deno-types=\"{}\"\n", + if position.character == 0 { "" } else { "\n" }, + &types_specifier_text + ); + let text_edit = lsp::TextEdit { + range: lsp::Range { + start: position, + end: position, + }, + new_text, + }; + Some(lsp::CodeAction { + title: format!( + "Add @deno-types directive for \"{}\"", + &types_specifier_text + ), + kind: Some(lsp::CodeActionKind::QUICKFIX), + diagnostics: None, + edit: Some(lsp::WorkspaceEdit { + changes: Some([(uri, vec![text_edit])].into_iter().collect()), + ..Default::default() + }), + ..Default::default() + }) + } + if let Some(action) = + deno_types_for_npm_action(document, range, language_server).await + { + self.actions.push(CodeActionKind::Deno(action)); + } + } } /// Prepend the whitespace characters found at the start of line_content to content. diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index a32087842d..fbf9ea6f1b 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -10,7 +10,7 @@ use crate::lsp::logging::lsp_warn; use deno_core::url::Url; use deno_core::ModuleSpecifier; -use deno_runtime::fs_util::specifier_to_file_path; +use deno_path_util::url_to_file_path; use std::collections::BTreeMap; use std::fs; use std::path::Path; @@ -24,7 +24,7 @@ pub fn calculate_fs_version( ) -> Option { match specifier.scheme() { "npm" | "node" | "data" | "blob" => None, - "file" => specifier_to_file_path(specifier) + "file" => url_to_file_path(specifier) .ok() .and_then(|path| calculate_fs_version_at_path(&path)), _ => calculate_fs_version_in_cache(cache, specifier, file_referrer), @@ -82,7 +82,7 @@ impl Default for LspCache { impl LspCache { pub fn new(global_cache_url: Option) -> Self { let global_cache_path = global_cache_url.and_then(|s| { - specifier_to_file_path(&s) + url_to_file_path(&s) .inspect(|p| { lsp_log!("Resolved global cache path: \"{}\"", p.to_string_lossy()); }) @@ -94,7 +94,7 @@ impl LspCache { let deno_dir = DenoDir::new(global_cache_path) .expect("should be infallible with absolute custom root"); let global = Arc::new(GlobalHttpCache::new( - deno_dir.deps_folder_path(), + deno_dir.remote_folder_path(), crate::cache::RealDenoCacheEnv, )); Self { @@ -165,7 +165,7 @@ impl LspCache { &self, specifier: &ModuleSpecifier, ) -> Option { - let path = specifier_to_file_path(specifier).ok()?; + let path = url_to_file_path(specifier).ok()?; let vendor = self .vendors_by_scope .iter() @@ -176,7 +176,7 @@ impl LspCache { } pub fn is_valid_file_referrer(&self, specifier: &ModuleSpecifier) -> bool { - if let Ok(path) = specifier_to_file_path(specifier) { + if let Ok(path) = url_to_file_path(specifier) { if !path.starts_with(&self.deno_dir().root) { return true; } diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index b3d6fbbd04..a040be5691 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -29,7 +29,7 @@ use deno_core::serde::Serialize; use deno_core::serde_json::json; use deno_core::url::Position; use deno_core::ModuleSpecifier; -use deno_runtime::fs_util::specifier_to_file_path; +use deno_path_util::url_to_file_path; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::package::PackageNv; use import_map::ImportMap; @@ -380,7 +380,7 @@ fn get_local_completions( ResolutionMode::Execution, ) .ok()?; - let resolved_parent_path = specifier_to_file_path(&resolved_parent).ok()?; + let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?; let raw_parent = &text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1]; if resolved_parent_path.is_dir() { diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index e55e7b734d..c54de3a235 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -36,8 +36,8 @@ use deno_core::ModuleSpecifier; use deno_lint::linter::LintConfig as DenoLintConfig; use deno_npm::npm_rc::ResolvedNpmRc; use deno_package_json::PackageJsonCache; +use deno_path_util::url_to_file_path; use deno_runtime::deno_node::PackageJson; -use deno_runtime::fs_util::specifier_to_file_path; use indexmap::IndexSet; use lsp_types::ClientCapabilities; use std::collections::BTreeMap; @@ -59,7 +59,8 @@ use crate::args::LintOptions; use crate::cache::FastInsecureHasher; use crate::file_fetcher::FileFetcher; use crate::lsp::logging::lsp_warn; -use crate::resolver::SloppyImportsResolver; +use crate::resolver::CliSloppyImportsResolver; +use crate::resolver::SloppyImportsCachedFs; use crate::tools::lint::CliLinter; use crate::tools::lint::CliLinterOptions; use crate::tools::lint::LintRuleProvider; @@ -801,7 +802,7 @@ impl Settings { /// Returns `None` if the value should be deferred to the presence of a /// `deno.json` file. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option { - let Ok(path) = specifier_to_file_path(specifier) else { + let Ok(path) = url_to_file_path(specifier) else { // Non-file URLs are not disabled by these settings. return Some(true); }; @@ -810,7 +811,7 @@ impl Settings { let mut disable_paths = vec![]; let mut enable_paths = None; if let Some(folder_uri) = folder_uri { - if let Ok(folder_path) = specifier_to_file_path(folder_uri) { + if let Ok(folder_path) = url_to_file_path(folder_uri) { disable_paths = settings .disable_paths .iter() @@ -847,12 +848,12 @@ impl Settings { &self, specifier: &ModuleSpecifier, ) -> (&WorkspaceSettings, Option<&ModuleSpecifier>) { - let Ok(path) = specifier_to_file_path(specifier) else { + let Ok(path) = url_to_file_path(specifier) else { return (&self.unscoped, self.first_folder.as_ref()); }; for (folder_uri, settings) in self.by_workspace_folder.iter().rev() { if let Some(settings) = settings { - let Ok(folder_path) = specifier_to_file_path(folder_uri) else { + let Ok(folder_path) = url_to_file_path(folder_uri) else { continue; }; if path.starts_with(folder_path) { @@ -1181,7 +1182,7 @@ pub struct ConfigData { pub lockfile: Option>, pub npmrc: Option>, pub resolver: Arc, - pub sloppy_imports_resolver: Option>, + pub sloppy_imports_resolver: Option>, pub import_map_from_settings: Option, watched_files: HashMap, } @@ -1584,9 +1585,11 @@ impl ConfigData { .is_ok() || member_dir.workspace.has_unstable("sloppy-imports"); let sloppy_imports_resolver = unstable_sloppy_imports.then(|| { - Arc::new(SloppyImportsResolver::new_without_stat_cache(Arc::new( - deno_runtime::deno_fs::RealFs, - ))) + Arc::new(CliSloppyImportsResolver::new( + SloppyImportsCachedFs::new_without_stat_cache(Arc::new( + deno_runtime::deno_fs::RealFs, + )), + )) }); let resolver = Arc::new(resolver); let lint_rule_provider = LintRuleProvider::new( @@ -1767,7 +1770,7 @@ impl ConfigTree { let config_file_path = (|| { let config_setting = ws_settings.config.as_ref()?; let config_uri = folder_uri.join(config_setting).ok()?; - specifier_to_file_path(&config_uri).ok() + url_to_file_path(&config_uri).ok() })(); if config_file_path.is_some() || ws_settings.import_map.is_some() { scopes.insert( @@ -1844,7 +1847,7 @@ impl ConfigTree { let scope = config_file.specifier.join(".").unwrap(); let json_text = serde_json::to_string(&config_file.json).unwrap(); let test_fs = deno_runtime::deno_fs::InMemoryFs::default(); - let config_path = specifier_to_file_path(&config_file.specifier).unwrap(); + let config_path = url_to_file_path(&config_file.specifier).unwrap(); test_fs.setup_text_files(vec![( config_path.to_string_lossy().to_string(), json_text, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 1aebaf56fc..caabd3f04e 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -19,8 +19,8 @@ use super::urls::LspUrlMap; use crate::graph_util; use crate::graph_util::enhanced_resolution_error_message; use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams; -use crate::resolver::SloppyImportsResolution; -use crate::resolver::SloppyImportsResolver; +use crate::resolver::CliSloppyImportsResolver; +use crate::resolver::SloppyImportsCachedFs; use crate::tools::lint::CliLinter; use crate::tools::lint::CliLinterOptions; use crate::tools::lint::LintRuleProvider; @@ -40,11 +40,12 @@ use deno_core::unsync::spawn_blocking; use deno_core::unsync::JoinHandle; use deno_core::url::Url; use deno_core::ModuleSpecifier; -use deno_graph::source::ResolutionMode; use deno_graph::source::ResolveError; use deno_graph::Resolution; use deno_graph::ResolutionError; use deno_graph::SpecifierError; +use deno_resolver::sloppy_imports::SloppyImportsResolution; +use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_runtime::deno_fs; use deno_runtime::deno_node; use deno_runtime::tokio_util::create_basic_runtime; @@ -1263,7 +1264,9 @@ impl DenoDiagnostic { Self::NotInstalledJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("JSR package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))), Self::NotInstalledNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("NPM package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))), Self::NoLocal(specifier) => { - let maybe_sloppy_resolution = SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)).resolve(specifier, ResolutionMode::Execution); + let maybe_sloppy_resolution = CliSloppyImportsResolver::new( + SloppyImportsCachedFs::new(Arc::new(deno_fs::RealFs)) + ).resolve(specifier, SloppyImportsResolutionMode::Execution); let data = maybe_sloppy_resolution.as_ref().map(|res| { json!({ "specifier": specifier, @@ -1514,17 +1517,19 @@ fn diagnose_dependency( let import_ranges: Vec<_> = dependency .imports .iter() - .map(|i| documents::to_lsp_range(&i.range)) + .map(|i| documents::to_lsp_range(&i.specifier_range)) .collect(); // TODO(nayeemrmn): This is a crude way of detecting `@deno-types` which has // a different specifier and therefore needs a separate call to // `diagnose_resolution()`. It would be much cleaner if that were modelled as // a separate dependency: https://github.com/denoland/deno_graph/issues/247. let is_types_deno_types = !dependency.maybe_type.is_none() - && !dependency - .imports - .iter() - .any(|i| dependency.maybe_type.includes(&i.range.start).is_some()); + && !dependency.imports.iter().any(|i| { + dependency + .maybe_type + .includes(&i.specifier_range.start) + .is_some() + }); diagnostics.extend( diagnose_resolution( diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index e96b8831bb..7d1ca6810d 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -26,8 +26,8 @@ use deno_core::parking_lot::Mutex; use deno_core::ModuleSpecifier; use deno_graph::source::ResolutionMode; use deno_graph::Resolution; +use deno_path_util::url_to_file_path; use deno_runtime::deno_node; -use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; @@ -849,7 +849,7 @@ impl FileSystemDocuments { file_referrer: Option<&ModuleSpecifier>, ) -> Option> { let doc = if specifier.scheme() == "file" { - let path = specifier_to_file_path(specifier).ok()?; + let path = url_to_file_path(specifier).ok()?; let bytes = fs::read(path).ok()?; let content = deno_graph::source::decode_owned_source(specifier, bytes, None).ok()?; @@ -1136,7 +1136,7 @@ impl Documents { return true; } if specifier.scheme() == "file" { - return specifier_to_file_path(&specifier) + return url_to_file_path(&specifier) .map(|p| p.is_file()) .unwrap_or(false); } @@ -1325,7 +1325,7 @@ impl Documents { let fs_docs = &self.file_system_docs; // Clean up non-existent documents. fs_docs.docs.retain(|specifier, _| { - let Ok(path) = specifier_to_file_path(specifier) else { + let Ok(path) = url_to_file_path(specifier) else { // Remove non-file schemed docs (deps). They may not be dependencies // anymore after updating resolvers. return false; diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index a1cc5079d4..6ddbb1a514 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -15,9 +15,9 @@ use deno_core::url::Url; use deno_core::ModuleSpecifier; use deno_graph::GraphKind; use deno_graph::Resolution; +use deno_path_util::url_to_file_path; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; -use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::jsr::JsrPackageReqReference; use indexmap::Equivalent; use indexmap::IndexSet; @@ -207,11 +207,11 @@ pub struct Inner { module_registry: ModuleRegistry, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option, - npm_search_api: CliNpmSearchApi, + pub npm_search_api: CliNpmSearchApi, project_version: usize, /// A collection of measurements which instrument that performance of the LSP. performance: Arc, - resolver: Arc, + pub resolver: Arc, task_queue: LanguageServerTaskQueue, /// A memoized version of fixable diagnostic codes retrieved from TypeScript. ts_fixable_diagnostics: Vec, @@ -274,10 +274,9 @@ impl LanguageServer { factory.fs(), &roots, graph_util::GraphValidOptions { - is_vendoring: false, - follow_type_only: true, + kind: GraphKind::All, check_js: false, - exit_lockfile_errors: false, + exit_integrity_errors: false, }, )?; @@ -627,7 +626,7 @@ impl Inner { let maybe_root_path = self .config .root_uri() - .and_then(|uri| specifier_to_file_path(uri).ok()); + .and_then(|uri| url_to_file_path(uri).ok()); let root_cert_store = get_root_cert_store( maybe_root_path, workspace_settings.certificate_stores.clone(), @@ -803,7 +802,7 @@ impl Inner { let mut roots = config .workspace_folders .iter() - .filter_map(|p| specifier_to_file_path(&p.0).ok()) + .filter_map(|p| url_to_file_path(&p.0).ok()) .collect::>(); roots.sort(); let roots = roots @@ -1125,7 +1124,7 @@ impl Inner { { return; } - match specifier_to_file_path(&specifier) { + match url_to_file_path(&specifier) { Ok(path) if is_importable_ext(&path) => {} _ => return, } @@ -1363,7 +1362,7 @@ impl Inner { { specifier = uri_to_url(¶ms.text_document.uri); } - let file_path = specifier_to_file_path(&specifier).map_err(|err| { + let file_path = url_to_file_path(&specifier).map_err(|err| { error!("{:#}", err); LspError::invalid_request() })?; @@ -1613,8 +1612,8 @@ impl Inner { None => false, }) .collect(); + let mut code_actions = CodeActionCollection::default(); if !fixable_diagnostics.is_empty() { - let mut code_actions = CodeActionCollection::default(); let file_diagnostics = self .diagnostics_server .get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version()); @@ -1722,9 +1721,14 @@ impl Inner { .add_cache_all_action(&specifier, no_cache_diagnostics.to_owned()); } } - code_actions.set_preferred_fixes(); - all_actions.extend(code_actions.get_response()); } + if let Some(document) = asset_or_doc.document() { + code_actions + .add_source_actions(document, ¶ms.range, self) + .await; + } + code_actions.set_preferred_fixes(); + all_actions.extend(code_actions.get_response()); // Refactor let only = params @@ -2509,7 +2513,7 @@ impl Inner { let maybe_root_path_owned = self .config .root_uri() - .and_then(|uri| specifier_to_file_path(uri).ok()); + .and_then(|uri| url_to_file_path(uri).ok()); let mut resolved_items = Vec::::new(); for item in incoming_calls.iter() { if let Some(resolved) = item.try_resolve_call_hierarchy_incoming_call( @@ -2555,7 +2559,7 @@ impl Inner { let maybe_root_path_owned = self .config .root_uri() - .and_then(|uri| specifier_to_file_path(uri).ok()); + .and_then(|uri| url_to_file_path(uri).ok()); let mut resolved_items = Vec::::new(); for item in outgoing_calls.iter() { if let Some(resolved) = item.try_resolve_call_hierarchy_outgoing_call( @@ -2604,7 +2608,7 @@ impl Inner { let maybe_root_path_owned = self .config .root_uri() - .and_then(|uri| specifier_to_file_path(uri).ok()); + .and_then(|uri| url_to_file_path(uri).ok()); let mut resolved_items = Vec::::new(); match one_or_many { tsc::OneOrMany::One(item) => { @@ -3614,6 +3618,11 @@ impl Inner { }), // bit of a hack to force the lsp to cache the @types/node package type_check_mode: crate::args::TypeCheckMode::Local, + permissions: crate::args::PermissionFlags { + // allow remote import permissions in the lsp for now + allow_import: Some(vec![]), + ..Default::default() + }, ..Default::default() }), initial_cwd, diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 2844eb6f98..c89273147a 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -10,10 +10,10 @@ use deno_graph::source::Resolver; use deno_graph::GraphImport; use deno_graph::ModuleSpecifier; use deno_npm::NpmSystemInfo; +use deno_path_util::url_to_file_path; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::PackageJson; -use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageNv; @@ -42,13 +42,14 @@ use crate::lsp::config::Config; use crate::lsp::config::ConfigData; use crate::lsp::logging::lsp_warn; use crate::npm::create_cli_npm_resolver_for_lsp; +use crate::npm::CliByonmNpmResolverCreateOptions; use crate::npm::CliNpmResolver; -use crate::npm::CliNpmResolverByonmCreateOptions; use crate::npm::CliNpmResolverCreateOptions; use crate::npm::CliNpmResolverManagedCreateOptions; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::ManagedCliNpmResolver; use crate::resolver::CjsResolutionStore; +use crate::resolver::CliDenoResolverFs; use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::resolver::CliNodeResolver; @@ -327,11 +328,11 @@ impl LspResolver { ) -> Option<(ModuleSpecifier, MediaType)> { let resolver = self.get_scope_resolver(file_referrer); let node_resolver = resolver.node_resolver.as_ref()?; - Some(NodeResolution::into_specifier_and_media_type( + Some(NodeResolution::into_specifier_and_media_type(Some( node_resolver .resolve_req_reference(req_ref, referrer, NodeResolutionMode::Types) - .ok(), - )) + .ok()?, + ))) } pub fn in_node_modules(&self, specifier: &ModuleSpecifier) -> bool { @@ -372,6 +373,26 @@ impl LspResolver { Some(NodeResolution::into_specifier_and_media_type(Some(resolution)).1) } + pub fn is_bare_package_json_dep( + &self, + specifier_text: &str, + referrer: &ModuleSpecifier, + ) -> bool { + let resolver = self.get_scope_resolver(Some(referrer)); + let Some(node_resolver) = resolver.node_resolver.as_ref() else { + return false; + }; + node_resolver + .resolve_if_for_npm_pkg( + specifier_text, + referrer, + NodeResolutionMode::Types, + ) + .ok() + .flatten() + .is_some() + } + pub fn get_closest_package_json( &self, referrer: &ModuleSpecifier, @@ -439,11 +460,11 @@ async fn create_npm_resolver( ) -> Option> { let enable_byonm = config_data.map(|d| d.byonm).unwrap_or(false); let options = if enable_byonm { - CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { - fs: Arc::new(deno_fs::RealFs), + CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions { + fs: CliDenoResolverFs(Arc::new(deno_fs::RealFs)), root_node_modules_dir: config_data.and_then(|config_data| { config_data.node_modules_dir.clone().or_else(|| { - specifier_to_file_path(&config_data.scope) + url_to_file_path(&config_data.scope) .ok() .map(|p| p.join("node_modules/")) }) diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index fe3708d3cb..c8b5c47f83 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -62,7 +62,7 @@ use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_core::PollEventLoopOptions; use deno_core::RuntimeOptions; -use deno_runtime::fs_util::specifier_to_file_path; +use deno_path_util::url_to_file_path; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::tokio_util::create_basic_runtime; use indexmap::IndexMap; @@ -3191,7 +3191,7 @@ impl CallHierarchyItem { let use_file_name = self.is_source_file_item(); let maybe_file_path = if uri.scheme().is_some_and(|s| s.as_str() == "file") { - specifier_to_file_path(&uri_to_url(&uri)).ok() + url_to_file_path(&uri_to_url(&uri)).ok() } else { None }; diff --git a/cli/main.rs b/cli/main.rs index c0eccab5df..31bebc882f 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -392,6 +392,31 @@ fn get_suggestions_for_terminal_errors(e: &JsError) -> Vec { "Run again with `--unstable-webgpu` flag to enable this API.", ), ]; + // Try to capture errors like: + // ``` + // Uncaught Error: Cannot find module '../build/Release/canvas.node' + // Require stack: + // - /.../deno/npm/registry.npmjs.org/canvas/2.11.2/lib/bindings.js + // - /.../.cache/deno/npm/registry.npmjs.org/canvas/2.11.2/lib/canvas.js + // ``` + } else if msg.contains("Cannot find module") + && msg.contains("Require stack") + && msg.contains(".node'") + { + return vec![ + FixSuggestion::info_multiline( + &[ + "Trying to execute an npm package using Node-API addons,", + "these packages require local `node_modules` directory to be present." + ] + ), + FixSuggestion::hint_multiline( + &[ + "Add `\"nodeModulesDir\": \"auto\" option to `deno.json`, and then run", + "`deno install --allow-scripts=npm: --entrypoint