From 147411e64b22fe74cb258125acab83f9182c9f81 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 3 Jul 2024 20:54:33 -0400 Subject: [PATCH] feat: npm workspace and better Deno workspace support (#24334) Adds much better support for the unstable Deno workspaces as well as support for npm workspaces. npm workspaces is still lacking in that we only install packages into the root node_modules folder. We'll make it smarter over time in order for it to figure out when to add node_modules folders within packages. This includes a breaking change in config file resolution where we stop searching for config files on the first found package.json unless it's in a workspace. For the previous behaviour, the root deno.json needs to be updated to be a workspace by adding `"workspace": ["./path-to-pkg-json-folder-goes-here"]`. See details in https://github.com/denoland/deno_config/pull/66 Closes #24340 Closes #24159 Closes #24161 Closes #22020 Closes #18546 Closes #16106 Closes #24160 --- .dprint.json | 53 +- .github/workflows/ci.generate.ts | 2 +- .github/workflows/ci.yml | 8 +- Cargo.lock | 8 +- Cargo.toml | 2 +- cli/Cargo.toml | 2 +- cli/args/flags.rs | 160 +-- cli/args/import_map.rs | 118 +- cli/args/mod.rs | 1039 ++++++++--------- cli/args/package_json.rs | 128 +- cli/factory.rs | 207 ++-- cli/graph_container.rs | 2 +- cli/graph_util.rs | 38 +- cli/lsp/config.rs | 34 +- cli/lsp/diagnostics.rs | 2 +- cli/lsp/documents.rs | 4 +- cli/lsp/language_server.rs | 26 +- cli/lsp/resolver.rs | 34 +- cli/lsp/tsc.rs | 2 +- cli/module_loader.rs | 12 +- cli/npm/byonm.rs | 20 +- cli/npm/managed/mod.rs | 23 +- cli/npm/managed/resolvers/local.rs | 22 +- cli/npm/managed/resolvers/mod.rs | 4 + cli/resolver.rs | 343 +++--- cli/schemas/config-file.v1.json | 2 +- cli/standalone/binary.rs | 313 +++-- cli/standalone/mod.rs | 447 ++++--- cli/standalone/virtual_fs.rs | 41 +- cli/tools/bench/mod.rs | 93 +- cli/tools/check.rs | 2 +- cli/tools/compile.rs | 113 +- cli/tools/doc.rs | 47 +- cli/tools/fmt.rs | 477 ++++---- cli/tools/info.rs | 21 +- cli/tools/lint/mod.rs | 419 ++++--- cli/tools/registry/mod.rs | 111 +- cli/tools/registry/pm.rs | 25 +- cli/tools/registry/publish_order.rs | 21 +- cli/tools/registry/unfurl.rs | 70 +- cli/tools/task.rs | 327 +++--- cli/tools/test/mod.rs | 118 +- cli/tools/vendor/build.rs | 12 +- cli/tools/vendor/import_map.rs | 16 +- cli/tools/vendor/mod.rs | 25 +- cli/tools/vendor/test.rs | 18 +- cli/util/collections.rs | 38 + cli/util/file_watcher.rs | 3 + cli/util/mod.rs | 1 + cli/worker.rs | 26 - tests/integration/compile_tests.rs | 60 +- tests/integration/lsp_tests.rs | 8 +- tests/integration/run_tests.rs | 40 +- tests/integration/watcher_tests.rs | 10 +- .../npm/@types/lz-string/lz-string-1.3.33.tgz | Bin 0 -> 2184 bytes .../npm/@types/lz-string/lz-string-1.5.0.tgz | Bin 0 -> 1133 bytes .../npm/@types/lz-string/registry.json | 113 ++ .../npm/lz-string/lz-string-1.3.6.tgz | Bin 0 -> 30429 bytes .../npm/lz-string/lz-string-1.5.0.tgz | Bin 0 -> 36908 bytes tests/registry/npm/lz-string/registry.json | 165 +++ tests/specs/bench/workspace/__test__.jsonc | 13 + tests/specs/bench/workspace/deno.json | 6 + .../specs/bench/workspace/package-a/deno.json | 5 + .../bench/workspace/package-a/mod.bench.ts | 7 + tests/specs/bench/workspace/package-a/mod.ts | 3 + .../specs/bench/workspace/package-b/deno.json | 5 + .../bench/workspace/package-b/mod.bench.ts | 7 + tests/specs/bench/workspace/package-b/mod.ts | 5 + tests/specs/bench/workspace/package_b.out | 9 + tests/specs/bench/workspace/root.out | 16 + tests/specs/check/workspace/__test__.jsonc | 22 + tests/specs/check/workspace/deno.json | 6 + .../specs/check/workspace/package-a/deno.json | 5 + tests/specs/check/workspace/package-a/mod.ts | 3 + .../specs/check/workspace/package-b/deno.json | 5 + tests/specs/check/workspace/package-b/mod.ts | 4 + tests/specs/check/workspace/package_a.out | 1 + tests/specs/check/workspace/package_b.out | 5 + tests/specs/check/workspace/root.out | 6 + .../{npmrc => npmrc_auto_install}/.npmrc | 0 .../compile/npmrc_auto_install/__test__.jsonc | 22 + .../compile/npmrc_auto_install/deno.json | 3 + .../{npmrc => npmrc_auto_install}/main.js | 0 .../{npmrc => npmrc_auto_install}/main.out | 0 .../package.json | 0 tests/specs/compile/npmrc_byonm/.npmrc | 4 + .../{npmrc => npmrc_byonm}/__test__.jsonc | 0 .../{npmrc => npmrc_byonm}/install.out | 0 tests/specs/compile/npmrc_byonm/main.js | 8 + tests/specs/compile/npmrc_byonm/main.out | 3 + tests/specs/compile/npmrc_byonm/package.json | 8 + tests/specs/fmt/workspace/__test__.jsonc | 26 + tests/specs/fmt/workspace/a/a.ts | 1 + tests/specs/fmt/workspace/a/deno.json | 5 + tests/specs/fmt/workspace/a_check.out | 6 + tests/specs/fmt/workspace/a_fmt.out | 2 + tests/specs/fmt/workspace/b/b.ts | 1 + tests/specs/fmt/workspace/b/deno.json | 5 + tests/specs/fmt/workspace/deno.json | 9 + tests/specs/fmt/workspace/root.ts | 1 + tests/specs/fmt/workspace/root_check.out | 16 + tests/specs/fmt/workspace/root_fmt.out | 4 + .../future_install_global/__test__.jsonc | 2 +- .../install/future_install_global/install.out | 1 - .../install/future_install_global/main.js | 3 - .../pkg}/main.js | 0 .../{ => pkg}/package.json | 1 - .../no_future_install_global/__test__.jsonc | 2 +- .../no_future_install_global/install.out | 1 + .../no_future_install_global/pkg/main.js | 3 + .../{ => pkg}/package.json | 0 .../lint/no_slow_types_workspace/deno.json | 2 +- tests/specs/lint/workspace/__test__.jsonc | 15 + tests/specs/lint/workspace/a.out | 32 + tests/specs/lint/workspace/deno.json | 11 + tests/specs/lint/workspace/package-a/a.ts | 11 + .../specs/lint/workspace/package-a/deno.json | 12 + tests/specs/lint/workspace/package-b/b.ts | 11 + .../specs/lint/workspace/package-b/deno.json | 9 + tests/specs/lint/workspace/root.out | 82 ++ tests/specs/lint/workspace/root.ts | 11 + .../workspace_no_slow_types/__test__.jsonc | 27 + .../specs/lint/workspace_no_slow_types/a.out | 14 + .../specs/lint/workspace_no_slow_types/a/a.ts | 3 + .../lint/workspace_no_slow_types/a/deno.json | 5 + .../specs/lint/workspace_no_slow_types/b.out | 14 + .../specs/lint/workspace_no_slow_types/b/b.ts | 9 + .../lint/workspace_no_slow_types/b/deno.json | 5 + .../specs/lint/workspace_no_slow_types/c/c.ts | 6 + .../lint/workspace_no_slow_types/c/deno.json | 5 + .../lint/workspace_no_slow_types/deno.json | 7 + .../lint/workspace_no_slow_types/root.out | 26 + .../__test__.jsonc | 31 +- .../expected.out | 4 + .../check_prefers_non_types_node_pkg/main.ts | 4 +- .../@types/lz-string/package.json | 12 - .../node_modules/lz-string/index.d.ts | 1 - .../node_modules/lz-string/index.js | 1 - .../node_modules/lz-string/package.json | 4 - .../package.json | 4 +- .../check_types_in_types_pkg/__test__.jsonc | 28 + .../npm/check_types_in_types_pkg/expected.out | 4 + .../npm/check_types_in_types_pkg/main.ts | 5 + .../main_auto_install.ts | 6 + .../npm/check_types_in_types_pkg/package.json | 6 + .../specs/npm/workspace_basic/__test__.jsonc | 35 + tests/specs/npm/workspace_basic/a/mod.ts | 6 + .../specs/npm/workspace_basic/a/package.json | 10 + .../b/exports-sub-path-not-exists.out | 2 + .../b/exports-sub-path-not-exists.ts | 2 + tests/specs/npm/workspace_basic/b/main.ts | 9 + .../npm/workspace_basic/b/main_byonm.out | 4 + .../workspace_basic/b/main_global_cache.out | 6 + .../b/main_node_modules_dir.out | 7 + .../b/no-exports-sub-path-not-exists.out | 3 + .../b/no-exports-sub-path-not-exists.ts | 2 + .../specs/npm/workspace_basic/b/package.json | 8 + tests/specs/npm/workspace_basic/c/index.js | 3 + .../specs/npm/workspace_basic/c/package.json | 4 + tests/specs/npm/workspace_basic/package.json | 7 + tests/specs/publish/byonm_dep/publish.out | 4 +- tests/specs/publish/workspace/__test__.jsonc | 19 +- tests/specs/publish/workspace/deno.json | 2 +- tests/specs/publish/workspace/workspace.out | 8 +- .../workspace/workspace_individual.out | 4 +- tests/specs/run/no_deno_json/__test__.jsonc | 71 +- .../run/no_deno_json/no_package_json.out | 4 + tests/specs/run/no_deno_json/noconfig.out | 1 - tests/specs/run/workspaces/basic/deno.json | 2 +- tests/specs/run/workspaces/basic/main.out | 14 +- .../member_outside_root_dir/__test__.jsonc | 2 +- .../member_outside_root_dir/main.out | 4 +- .../{ => sub_dir/child}/deno.json | 2 +- .../{ => sub_dir/child}/foo/bar/hello.ts | 0 .../{ => sub_dir/child}/foo/deno.json | 0 .../{ => sub_dir/child}/foo/fizz/buzz.ts | 0 .../{ => sub_dir/child}/foo/mod.ts | 0 .../{ => sub_dir/child}/main.ts | 0 .../workspaces/members_are_imports/deno.json | 2 +- .../workspaces/nested_member/__test__.jsonc | 2 +- .../workspaces/nested_member/bar/deno.json | 3 +- .../run/workspaces/nested_member/deno.json | 2 +- .../nested_member/foo/bar/deno.json | 3 +- .../workspaces/nested_member/foo/deno.json | 3 +- .../run/workspaces/nested_member/main.out | 5 +- tests/specs/task/workspace/__test__.jsonc | 52 + tests/specs/task/workspace/deno.json | 9 + tests/specs/task/workspace/package-a.out | 9 + .../specs/task/workspace/package-a/deno.json | 5 + tests/specs/task/workspace/package-b.out | 11 + .../specs/task/workspace/package-b/deno.json | 6 + .../task/workspace/package-b/package.json | 6 + tests/specs/task/workspace/package.json | 6 + tests/specs/task/workspace/root.out | 7 + tests/specs/task/workspace/scripts.out | 7 + tests/specs/task/workspace/scripts/main.ts | 1 + tests/specs/test/workspace/__test__.jsonc | 20 + tests/specs/test/workspace/deno.json | 6 + .../specs/test/workspace/package-a/deno.json | 5 + .../test/workspace/package-a/mod.test.ts | 7 + tests/specs/test/workspace/package-a/mod.ts | 3 + .../specs/test/workspace/package-b/deno.json | 5 + .../test/workspace/package-b/mod.test.ts | 11 + tests/specs/test/workspace/package-b/mod.ts | 5 + tests/specs/test/workspace/package_a.out | 6 + tests/specs/test/workspace/package_b.out | 20 + tests/specs/test/workspace/root.out | 23 + .../specs/workspaces/lockfile/__test__.jsonc | 24 + tests/specs/workspaces/lockfile/deno.json | 6 + .../workspaces/lockfile/expected-lock.out | 24 + .../workspaces/lockfile/integration.test.ts | 7 + .../lockfile/pkg-no-deps/deno.jsonc | 7 + .../workspaces/lockfile/pkg-no-deps/mod.ts | 0 .../specs/workspaces/lockfile/pkg/deno.jsonc | 8 + .../specs/workspaces/lockfile/pkg/mod.test.ts | 7 + tests/specs/workspaces/lockfile/pkg/mod.ts | 5 + tests/specs/workspaces/lockfile/test_pkg.out | 9 + tests/specs/workspaces/lockfile/test_root.out | 9 + .../non_fatal_diagnostics/__test__.jsonc | 13 + .../non_fatal_diagnostics/deno.json | 5 + .../workspaces/non_fatal_diagnostics/lint.out | 5 + .../non_fatal_diagnostics/sub/deno.json | 8 + .../non_fatal_diagnostics/sub/main.ts | 0 tests/specs/workspaces/vendor/__test__.jsonc | 13 + tests/specs/workspaces/vendor/deno.json | 9 + .../specs/workspaces/vendor/modify_vendor.ts | 7 + .../workspaces/vendor/package-a/deno.json | 5 + .../specs/workspaces/vendor/package-a/mod.ts | 3 + .../dynamic_imports/main_unanalyzable.ts | 4 +- .../main_compile_file.out | 2 +- .../main_compile_folder.out | 4 +- .../package_json/invalid_value/error.ts.out | 4 +- .../package_json/invalid_value/task.out | 3 - .../run/with_package_json/with_stop/main.out | 1 - tests/util/server/src/builders.rs | 38 +- 235 files changed, 4446 insertions(+), 2451 deletions(-) create mode 100644 cli/util/collections.rs create mode 100644 tests/registry/npm/@types/lz-string/lz-string-1.3.33.tgz create mode 100644 tests/registry/npm/@types/lz-string/lz-string-1.5.0.tgz create mode 100644 tests/registry/npm/@types/lz-string/registry.json create mode 100644 tests/registry/npm/lz-string/lz-string-1.3.6.tgz create mode 100644 tests/registry/npm/lz-string/lz-string-1.5.0.tgz create mode 100644 tests/registry/npm/lz-string/registry.json create mode 100644 tests/specs/bench/workspace/__test__.jsonc create mode 100644 tests/specs/bench/workspace/deno.json create mode 100644 tests/specs/bench/workspace/package-a/deno.json create mode 100644 tests/specs/bench/workspace/package-a/mod.bench.ts create mode 100644 tests/specs/bench/workspace/package-a/mod.ts create mode 100644 tests/specs/bench/workspace/package-b/deno.json create mode 100644 tests/specs/bench/workspace/package-b/mod.bench.ts create mode 100644 tests/specs/bench/workspace/package-b/mod.ts create mode 100644 tests/specs/bench/workspace/package_b.out create mode 100644 tests/specs/bench/workspace/root.out create mode 100644 tests/specs/check/workspace/__test__.jsonc create mode 100644 tests/specs/check/workspace/deno.json create mode 100644 tests/specs/check/workspace/package-a/deno.json create mode 100644 tests/specs/check/workspace/package-a/mod.ts create mode 100644 tests/specs/check/workspace/package-b/deno.json create mode 100644 tests/specs/check/workspace/package-b/mod.ts create mode 100644 tests/specs/check/workspace/package_a.out create mode 100644 tests/specs/check/workspace/package_b.out create mode 100644 tests/specs/check/workspace/root.out rename tests/specs/compile/{npmrc => npmrc_auto_install}/.npmrc (100%) create mode 100644 tests/specs/compile/npmrc_auto_install/__test__.jsonc create mode 100644 tests/specs/compile/npmrc_auto_install/deno.json rename tests/specs/compile/{npmrc => npmrc_auto_install}/main.js (100%) rename tests/specs/compile/{npmrc => npmrc_auto_install}/main.out (100%) rename tests/specs/compile/{npmrc => npmrc_auto_install}/package.json (100%) create mode 100644 tests/specs/compile/npmrc_byonm/.npmrc rename tests/specs/compile/{npmrc => npmrc_byonm}/__test__.jsonc (100%) rename tests/specs/compile/{npmrc => npmrc_byonm}/install.out (100%) create mode 100644 tests/specs/compile/npmrc_byonm/main.js create mode 100644 tests/specs/compile/npmrc_byonm/main.out create mode 100644 tests/specs/compile/npmrc_byonm/package.json create mode 100644 tests/specs/fmt/workspace/__test__.jsonc create mode 100644 tests/specs/fmt/workspace/a/a.ts create mode 100644 tests/specs/fmt/workspace/a/deno.json create mode 100644 tests/specs/fmt/workspace/a_check.out create mode 100644 tests/specs/fmt/workspace/a_fmt.out create mode 100644 tests/specs/fmt/workspace/b/b.ts create mode 100644 tests/specs/fmt/workspace/b/deno.json create mode 100644 tests/specs/fmt/workspace/deno.json create mode 100644 tests/specs/fmt/workspace/root.ts create mode 100644 tests/specs/fmt/workspace/root_check.out create mode 100644 tests/specs/fmt/workspace/root_fmt.out delete mode 100644 tests/specs/install/future_install_global/main.js rename tests/specs/install/{no_future_install_global => future_install_global/pkg}/main.js (100%) rename tests/specs/install/future_install_global/{ => pkg}/package.json (70%) create mode 100644 tests/specs/install/no_future_install_global/pkg/main.js rename tests/specs/install/no_future_install_global/{ => pkg}/package.json (100%) create mode 100644 tests/specs/lint/workspace/__test__.jsonc create mode 100644 tests/specs/lint/workspace/a.out create mode 100644 tests/specs/lint/workspace/deno.json create mode 100644 tests/specs/lint/workspace/package-a/a.ts create mode 100644 tests/specs/lint/workspace/package-a/deno.json create mode 100644 tests/specs/lint/workspace/package-b/b.ts create mode 100644 tests/specs/lint/workspace/package-b/deno.json create mode 100644 tests/specs/lint/workspace/root.out create mode 100644 tests/specs/lint/workspace/root.ts create mode 100644 tests/specs/lint/workspace_no_slow_types/__test__.jsonc create mode 100644 tests/specs/lint/workspace_no_slow_types/a.out create mode 100644 tests/specs/lint/workspace_no_slow_types/a/a.ts create mode 100644 tests/specs/lint/workspace_no_slow_types/a/deno.json create mode 100644 tests/specs/lint/workspace_no_slow_types/b.out create mode 100644 tests/specs/lint/workspace_no_slow_types/b/b.ts create mode 100644 tests/specs/lint/workspace_no_slow_types/b/deno.json create mode 100644 tests/specs/lint/workspace_no_slow_types/c/c.ts create mode 100644 tests/specs/lint/workspace_no_slow_types/c/deno.json create mode 100644 tests/specs/lint/workspace_no_slow_types/deno.json create mode 100644 tests/specs/lint/workspace_no_slow_types/root.out create mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/expected.out delete mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json delete mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts delete mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js delete mode 100644 tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json create mode 100644 tests/specs/npm/check_types_in_types_pkg/__test__.jsonc create mode 100644 tests/specs/npm/check_types_in_types_pkg/expected.out create mode 100644 tests/specs/npm/check_types_in_types_pkg/main.ts create mode 100644 tests/specs/npm/check_types_in_types_pkg/main_auto_install.ts create mode 100644 tests/specs/npm/check_types_in_types_pkg/package.json create mode 100644 tests/specs/npm/workspace_basic/__test__.jsonc create mode 100644 tests/specs/npm/workspace_basic/a/mod.ts create mode 100644 tests/specs/npm/workspace_basic/a/package.json create mode 100644 tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.out create mode 100644 tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.ts create mode 100644 tests/specs/npm/workspace_basic/b/main.ts create mode 100644 tests/specs/npm/workspace_basic/b/main_byonm.out create mode 100644 tests/specs/npm/workspace_basic/b/main_global_cache.out create mode 100644 tests/specs/npm/workspace_basic/b/main_node_modules_dir.out create mode 100644 tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.out create mode 100644 tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.ts create mode 100644 tests/specs/npm/workspace_basic/b/package.json create mode 100644 tests/specs/npm/workspace_basic/c/index.js create mode 100644 tests/specs/npm/workspace_basic/c/package.json create mode 100644 tests/specs/npm/workspace_basic/package.json create mode 100644 tests/specs/run/no_deno_json/no_package_json.out rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/deno.json (82%) rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/foo/bar/hello.ts (100%) rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/foo/deno.json (100%) rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/foo/fizz/buzz.ts (100%) rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/foo/mod.ts (100%) rename tests/specs/run/workspaces/member_outside_root_dir/{ => sub_dir/child}/main.ts (100%) create mode 100644 tests/specs/task/workspace/__test__.jsonc create mode 100644 tests/specs/task/workspace/deno.json create mode 100644 tests/specs/task/workspace/package-a.out create mode 100644 tests/specs/task/workspace/package-a/deno.json create mode 100644 tests/specs/task/workspace/package-b.out create mode 100644 tests/specs/task/workspace/package-b/deno.json create mode 100644 tests/specs/task/workspace/package-b/package.json create mode 100644 tests/specs/task/workspace/package.json create mode 100644 tests/specs/task/workspace/root.out create mode 100644 tests/specs/task/workspace/scripts.out create mode 100644 tests/specs/task/workspace/scripts/main.ts create mode 100644 tests/specs/test/workspace/__test__.jsonc create mode 100644 tests/specs/test/workspace/deno.json create mode 100644 tests/specs/test/workspace/package-a/deno.json create mode 100644 tests/specs/test/workspace/package-a/mod.test.ts create mode 100644 tests/specs/test/workspace/package-a/mod.ts create mode 100644 tests/specs/test/workspace/package-b/deno.json create mode 100644 tests/specs/test/workspace/package-b/mod.test.ts create mode 100644 tests/specs/test/workspace/package-b/mod.ts create mode 100644 tests/specs/test/workspace/package_a.out create mode 100644 tests/specs/test/workspace/package_b.out create mode 100644 tests/specs/test/workspace/root.out create mode 100644 tests/specs/workspaces/lockfile/__test__.jsonc create mode 100644 tests/specs/workspaces/lockfile/deno.json create mode 100644 tests/specs/workspaces/lockfile/expected-lock.out create mode 100644 tests/specs/workspaces/lockfile/integration.test.ts create mode 100644 tests/specs/workspaces/lockfile/pkg-no-deps/deno.jsonc create mode 100644 tests/specs/workspaces/lockfile/pkg-no-deps/mod.ts create mode 100644 tests/specs/workspaces/lockfile/pkg/deno.jsonc create mode 100644 tests/specs/workspaces/lockfile/pkg/mod.test.ts create mode 100644 tests/specs/workspaces/lockfile/pkg/mod.ts create mode 100644 tests/specs/workspaces/lockfile/test_pkg.out create mode 100644 tests/specs/workspaces/lockfile/test_root.out create mode 100644 tests/specs/workspaces/non_fatal_diagnostics/__test__.jsonc create mode 100644 tests/specs/workspaces/non_fatal_diagnostics/deno.json create mode 100644 tests/specs/workspaces/non_fatal_diagnostics/lint.out create mode 100644 tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json create mode 100644 tests/specs/workspaces/non_fatal_diagnostics/sub/main.ts create mode 100644 tests/specs/workspaces/vendor/__test__.jsonc create mode 100644 tests/specs/workspaces/vendor/deno.json create mode 100644 tests/specs/workspaces/vendor/modify_vendor.ts create mode 100644 tests/specs/workspaces/vendor/package-a/deno.json create mode 100644 tests/specs/workspaces/vendor/package-a/mod.ts diff --git a/.dprint.json b/.dprint.json index f9cb60c74d..475e4b141a 100644 --- a/.dprint.json +++ b/.dprint.json @@ -18,43 +18,46 @@ ".cargo_home", ".git", "cli/bench/testdata/express-router.js", - "cli/bench/testdata/npm/", "cli/bench/testdata/lsp_benchdata/", + "cli/bench/testdata/npm/", + "cli/tsc/*typescript.js", "cli/tsc/dts/lib.d.ts", - "cli/tsc/dts/lib.scripthost.d.ts", "cli/tsc/dts/lib.decorators*.d.ts", - "cli/tsc/dts/lib.webworker*.d.ts", "cli/tsc/dts/lib.dom*.d.ts", "cli/tsc/dts/lib.es*.d.ts", + "cli/tsc/dts/lib.scripthost.d.ts", + "cli/tsc/dts/lib.webworker*.d.ts", "cli/tsc/dts/typescript.d.ts", - "tests/node_compat/test", - "tests/registry/", - "tests/testdata/file_extensions/ts_with_js_extension.js", - "tests/testdata/fmt/badly_formatted.json", - "tests/testdata/fmt/badly_formatted.md", - "tests/testdata/fmt/badly_formatted.ipynb", - "tests/testdata/byte_order_mark.ts", - "tests/testdata/encoding", - "tests/testdata/fmt/", - "tests/testdata/lint/glob/", - "tests/testdata/test/glob/", - "tests/testdata/import_attributes/json_with_shebang.json", - "tests/testdata/run/error_syntax_empty_trailing_line.mjs", - "tests/testdata/run/inline_js_source_map*", - "tests/testdata/malformed_config/", - "tests/testdata/test/markdown_windows.md", - "cli/tsc/*typescript.js", + "ext/websocket/autobahn/reports", "gh-pages", "target", "tests/ffi/tests/test.js", - "tests/util/std", - "tests/wpt/suite", - "third_party", - "tests/node_compat/runner/TODO.md", "tests/node_compat/runner/suite", + "tests/node_compat/runner/TODO.md", + "tests/node_compat/test", + "tests/registry/", + "tests/specs/fmt", + "tests/specs/lint/bom", + "tests/testdata/byte_order_mark.ts", + "tests/testdata/encoding", + "tests/testdata/file_extensions/ts_with_js_extension.js", + "tests/testdata/fmt/", + "tests/testdata/fmt/badly_formatted.ipynb", + "tests/testdata/fmt/badly_formatted.json", + "tests/testdata/fmt/badly_formatted.md", + "tests/testdata/import_attributes/json_with_shebang.json", + "tests/testdata/lint/glob/", + "tests/testdata/malformed_config/", + "tests/testdata/run/byte_order_mark.ts", + "tests/testdata/run/error_syntax_empty_trailing_line.mjs", + "tests/testdata/run/inline_js_source_map*", + "tests/testdata/test/glob/", + "tests/testdata/test/markdown_windows.md", + "tests/util/std", "tests/wpt/runner/expectation.json", "tests/wpt/runner/manifest.json", - "ext/websocket/autobahn/reports" + "tests/wpt/suite", + "third_party" ], "plugins": [ "https://plugins.dprint.dev/typescript-0.91.1.wasm", diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 53308a3f7c..ba2e067f24 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 = 99; +const cacheVersion = 1; const ubuntuX86Runner = "ubuntu-22.04"; const ubuntuX86XlRunner = "ubuntu-22.04-xl"; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ddcaf153..8f1014451d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -367,8 +367,8 @@ jobs: path: |- ~/.cargo/registry/index ~/.cargo/registry/cache - key: '99-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' - restore-keys: '99-cargo-home-${{ matrix.os }}-${{ matrix.arch }}' + key: '1-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' + restore-keys: '1-cargo-home-${{ matrix.os }}-${{ matrix.arch }}' if: '!(matrix.skip)' - name: Restore cache build output (PR) uses: actions/cache/restore@v4 @@ -380,7 +380,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: never_saved - restore-keys: '99-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-' + restore-keys: '1-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 @@ -669,7 +669,7 @@ jobs: !./target/*/gn_out !./target/*/*.zip !./target/*/*.tar.gz - key: '99-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' + key: '1-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 7afafcb95d..85f63ba37b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1308,9 +1308,9 @@ dependencies = [ [[package]] name = "deno_config" -version = "0.17.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b0852c0dd8594926d51a5dae80cd1679f87f79a7c02415e60625d6ee2a99ba" +checksum = "ddc80f97cffe52c9a430201f288111fc89d33491b1675c0e01feb3a497ce76b3" dependencies = [ "anyhow", "deno_semver", @@ -1947,9 +1947,9 @@ dependencies = [ [[package]] name = "deno_unsync" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7557a5e9278b9a5cc8056dc37062ea4344770bda4eeb5973c7cbb7ebf636b9a4" +checksum = "10eb3aaf83c3431d4215741140ec3a63b0c0edb972ee898c89bdf8462e9e136b" dependencies = [ "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index dc4fa13027..275aa653c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ console_static_text = "=0.8.1" data-encoding = "2.3.3" data-url = "=0.3.0" deno_cache_dir = "=0.10.0" -deno_config = { version = "=0.17.0", default-features = false } +deno_config = { version = "=0.19.1", default-features = false } dlopen2 = "0.6.1" ecb = "=0.1.2" elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ff82fc3cca..31232e093e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -65,7 +65,7 @@ winres.workspace = true [dependencies] deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_cache_dir = { workspace = true } -deno_config = { workspace = true, features = ["deno_json", "package_json"] } +deno_config = { workspace = true, features = ["workspace"] } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_doc = { version = "=0.141.0", features = ["html", "syntect"] } deno_emit = "=0.43.0" diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 5f58911c2c..56fb4f09d2 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -9,11 +9,13 @@ use clap::ArgMatches; use clap::ColorChoice; use clap::Command; use clap::ValueHint; +use deno_config::glob::FilePatterns; use deno_config::glob::PathOrPatternSet; use deno_config::ConfigFlag; 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; @@ -34,6 +36,7 @@ use std::path::PathBuf; use std::str::FromStr; use crate::args::resolve_no_prompt; +use crate::util::collections::CheckedSet; use crate::util::fs::canonicalize_path; use super::flags_net; @@ -45,6 +48,29 @@ pub struct FileFlags { pub include: Vec, } +impl FileFlags { + pub fn as_file_patterns( + &self, + base: &Path, + ) -> Result { + Ok(FilePatterns { + include: if self.include.is_empty() { + None + } else { + Some(PathOrPatternSet::from_include_relative_path_or_patterns( + base, + &self.include, + )?) + }, + exclude: PathOrPatternSet::from_exclude_relative_path_or_patterns( + base, + &self.ignore, + )?, + base: base.to_path_buf(), + }) + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AddFlags { pub packages: Vec, @@ -156,7 +182,7 @@ pub struct EvalFlags { pub code: String, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct FmtFlags { pub check: bool, pub files: FileFlags, @@ -235,7 +261,7 @@ pub struct UninstallFlags { pub kind: UninstallKind, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct LintFlags { pub files: FileFlags, pub rules: bool, @@ -323,7 +349,7 @@ pub struct TaskFlags { pub task: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum TestReporterConfig { #[default] Pretty, @@ -838,30 +864,54 @@ impl Flags { args } - /// Extract path arguments for config search paths. - /// If it returns Some(vec), the config should be discovered - /// from the passed `current_dir` after trying to discover from each entry in - /// the returned vector. - /// If it returns None, the config file shouldn't be discovered at all. + /// Extract the directory paths the config file should be discovered from. + /// + /// Returns `None` if the config file should not be auto-discovered. pub fn config_path_args(&self, current_dir: &Path) -> Option> { - use DenoSubcommand::*; + fn resolve_multiple_files( + files: &[String], + current_dir: &Path, + ) -> Vec { + let mut seen = CheckedSet::with_capacity(files.len()); + let result = files + .iter() + .filter_map(|p| { + let path = normalize_path(current_dir.join(p).parent()?); + if seen.insert(&path) { + Some(path) + } else { + None + } + }) + .collect::>(); + if result.is_empty() { + vec![current_dir.to_path_buf()] + } else { + result + } + } + use DenoSubcommand::*; match &self.subcommand { Fmt(FmtFlags { files, .. }) => { - Some(files.include.iter().map(|p| current_dir.join(p)).collect()) + Some(resolve_multiple_files(&files.include, current_dir)) } Lint(LintFlags { files, .. }) => { - Some(files.include.iter().map(|p| current_dir.join(p)).collect()) + Some(resolve_multiple_files(&files.include, current_dir)) } - Run(RunFlags { script, .. }) => { + Run(RunFlags { script, .. }) + | Compile(CompileFlags { + source_file: script, + .. + }) => { if let Ok(module_specifier) = resolve_url_or_path(script, current_dir) { if module_specifier.scheme() == "file" || module_specifier.scheme() == "npm" { if let Ok(p) = module_specifier.to_file_path() { - Some(vec![p]) + Some(vec![p.parent().unwrap().to_path_buf()]) } else { - Some(vec![]) + Some(vec![current_dir.to_path_buf()]) } } else { // When the entrypoint doesn't have file: scheme (it's the remote @@ -869,7 +919,7 @@ impl Flags { None } } else { - Some(vec![]) + Some(vec![current_dir.to_path_buf()]) } } Task(TaskFlags { @@ -880,57 +930,10 @@ impl Flags { // `--cwd` when specified match canonicalize_path(&PathBuf::from(path)) { Ok(path) => Some(vec![path]), - Err(_) => Some(vec![]), - } - } - _ => Some(vec![]), - } - } - - /// Extract path argument for `package.json` search paths. - /// If it returns Some(path), the `package.json` should be discovered - /// from the `path` dir. - /// If it returns None, the `package.json` file shouldn't be discovered at - /// all. - pub fn package_json_search_dir(&self, current_dir: &Path) -> Option { - use DenoSubcommand::*; - - match &self.subcommand { - Run(RunFlags { script, .. }) | Serve(ServeFlags { script, .. }) => { - let module_specifier = resolve_url_or_path(script, current_dir).ok()?; - if module_specifier.scheme() == "file" { - let p = module_specifier - .to_file_path() - .unwrap() - .parent()? - .to_owned(); - Some(p) - } else if module_specifier.scheme() == "npm" { - Some(current_dir.to_path_buf()) - } else { - None - } - } - Task(TaskFlags { cwd: Some(cwd), .. }) => { - resolve_url_or_path(cwd, current_dir) - .ok()? - .to_file_path() - .ok() - } - Task(_) | Check(_) | Coverage(_) | Cache(_) | Info(_) | Eval(_) - | Test(_) | Bench(_) | Repl(_) | Compile(_) | Publish(_) => { - Some(current_dir.to_path_buf()) - } - Add(_) | Bundle(_) | Completions(_) | Doc(_) | Fmt(_) | Init(_) - | Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types | Upgrade(_) - | Vendor(_) => None, - Install(_) => { - if *DENO_FUTURE { - Some(current_dir.to_path_buf()) - } else { - None + Err(_) => Some(vec![current_dir.to_path_buf()]), } } + _ => Some(vec![current_dir.to_path_buf()]), } } @@ -9271,7 +9274,15 @@ mod tests { fn test_config_path_args() { let flags = flags_from_vec(svec!["deno", "run", "foo.js"]).unwrap(); let cwd = std::env::current_dir().unwrap(); - assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.join("foo.js")])); + + assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()])); + + let flags = flags_from_vec(svec!["deno", "run", "sub_dir/foo.js"]).unwrap(); + let cwd = std::env::current_dir().unwrap(); + assert_eq!( + flags.config_path_args(&cwd), + Some(vec![cwd.join("sub_dir").clone()]) + ); let flags = flags_from_vec(svec!["deno", "run", "https://example.com/foo.js"]) @@ -9279,20 +9290,27 @@ mod tests { assert_eq!(flags.config_path_args(&cwd), None); let flags = - flags_from_vec(svec!["deno", "lint", "dir/a.js", "dir/b.js"]).unwrap(); + flags_from_vec(svec!["deno", "lint", "dir/a/a.js", "dir/b/b.js"]) + .unwrap(); assert_eq!( flags.config_path_args(&cwd), - Some(vec![cwd.join("dir/a.js"), cwd.join("dir/b.js")]) + Some(vec![cwd.join("dir/a/"), cwd.join("dir/b/")]) ); let flags = flags_from_vec(svec!["deno", "lint"]).unwrap(); - assert!(flags.config_path_args(&cwd).unwrap().is_empty()); + assert_eq!(flags.config_path_args(&cwd), Some(vec![cwd.clone()])); - let flags = - flags_from_vec(svec!["deno", "fmt", "dir/a.js", "dir/b.js"]).unwrap(); + let flags = flags_from_vec(svec![ + "deno", + "fmt", + "dir/a/a.js", + "dir/a/a2.js", + "dir/b.js" + ]) + .unwrap(); assert_eq!( flags.config_path_args(&cwd), - Some(vec![cwd.join("dir/a.js"), cwd.join("dir/b.js")]) + Some(vec![cwd.join("dir/a/"), cwd.join("dir/")]) ); } diff --git a/cli/args/import_map.rs b/cli/args/import_map.rs index 2dc5a21d1a..7a16ab2156 100644 --- a/cli/args/import_map.rs +++ b/cli/args/import_map.rs @@ -1,127 +1,25 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::url::Url; use deno_runtime::deno_permissions::PermissionsContainer; -use import_map::ImportMap; -use import_map::ImportMapDiagnostic; -use log::warn; -use super::ConfigFile; use crate::file_fetcher::FileFetcher; -pub async fn resolve_import_map( - specified_specifier: Option<&Url>, - maybe_config_file: Option<&ConfigFile>, +pub async fn resolve_import_map_value_from_specifier( + specifier: &Url, file_fetcher: &FileFetcher, -) -> Result, AnyError> { - if let Some(specifier) = specified_specifier { - resolve_import_map_from_specifier(specifier.clone(), file_fetcher) - .await - .with_context(|| format!("Unable to load '{}' import map", specifier)) - .map(Some) - } else if let Some(config_file) = maybe_config_file { - let maybe_url_and_value = config_file - .to_import_map_value(|specifier| { - let specifier = specifier.clone(); - async move { - let file = file_fetcher - .fetch(&specifier, &PermissionsContainer::allow_all()) - .await? - .into_text_decoded()?; - Ok(file.source.to_string()) - } - }) - .await - .with_context(|| { - format!( - "Unable to resolve import map in '{}'", - config_file.specifier - ) - })?; - match maybe_url_and_value { - Some((url, value)) => { - import_map_from_value(url.into_owned(), value).map(Some) - } - None => Ok(None), - } - } else { - Ok(None) - } -} - -async fn resolve_import_map_from_specifier( - specifier: Url, - file_fetcher: &FileFetcher, -) -> Result { - let value: serde_json::Value = if specifier.scheme() == "data" { +) -> Result { + if specifier.scheme() == "data" { let data_url_text = - deno_graph::source::RawDataUrl::parse(&specifier)?.decode()?; - serde_json::from_str(&data_url_text)? + deno_graph::source::RawDataUrl::parse(specifier)?.decode()?; + Ok(serde_json::from_str(&data_url_text)?) } else { let file = file_fetcher - .fetch(&specifier, &PermissionsContainer::allow_all()) + .fetch(specifier, &PermissionsContainer::allow_all()) .await? .into_text_decoded()?; - serde_json::from_str(&file.source)? - }; - import_map_from_value(specifier, value) -} - -pub fn import_map_from_value( - specifier: Url, - json_value: serde_json::Value, -) -> Result { - debug_assert!( - !specifier.as_str().contains("../"), - "Import map specifier incorrectly contained ../: {}", - specifier.as_str() - ); - let result = import_map::parse_from_value(specifier, json_value)?; - print_import_map_diagnostics(&result.diagnostics); - Ok(result.import_map) -} - -fn print_import_map_diagnostics(diagnostics: &[ImportMapDiagnostic]) { - if !diagnostics.is_empty() { - warn!( - "Import map diagnostics:\n{}", - diagnostics - .iter() - .map(|d| format!(" - {d}")) - .collect::>() - .join("\n") - ); + Ok(serde_json::from_str(&file.source)?) } } - -pub fn enhance_import_map_value_with_workspace_members( - mut import_map_value: serde_json::Value, - workspace_members: &[deno_config::WorkspaceMemberConfig], -) -> serde_json::Value { - let mut imports = - if let Some(imports) = import_map_value.get("imports").as_ref() { - imports.as_object().unwrap().clone() - } else { - serde_json::Map::new() - }; - - for workspace_member in workspace_members { - let name = &workspace_member.package_name; - let version = &workspace_member.package_version; - // Don't override existings, explicit imports - if imports.contains_key(name) { - continue; - } - - imports.insert( - name.to_string(), - serde_json::Value::String(format!("jsr:{}@^{}", name, version)), - ); - } - - import_map_value["imports"] = serde_json::Value::Object(imports); - ::import_map::ext::expand_import_map_value(import_map_value) -} diff --git a/cli/args/mod.rs b/cli/args/mod.rs index bf52c460f7..f747271b81 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -5,21 +5,30 @@ mod flags; mod flags_net; mod import_map; mod lockfile; -pub mod package_json; +mod package_json; -pub use self::import_map::resolve_import_map; -use ::import_map::ImportMap; use deno_ast::SourceMapOption; -use deno_config::package_json::PackageJsonDeps; +use deno_config::workspace::CreateResolverOptions; +use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::Workspace; +use deno_config::workspace::WorkspaceDiscoverOptions; +use deno_config::workspace::WorkspaceDiscoverStart; +use deno_config::workspace::WorkspaceMemberContext; +use deno_config::workspace::WorkspaceResolver; +use deno_config::WorkspaceLintConfig; +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_runtime::deno_fs::DenoConfigFsAdapter; +use deno_runtime::deno_fs::RealFs; +use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_semver::npm::NpmPackageReqReference; -use indexmap::IndexMap; +use import_map::resolve_import_map_value_from_specifier; pub use deno_config::glob::FilePatterns; pub use deno_config::BenchConfig; @@ -32,10 +41,9 @@ pub use deno_config::TsConfig; pub use deno_config::TsConfigForEmit; pub use deno_config::TsConfigType; pub use deno_config::TsTypeLib; -pub use deno_config::WorkspaceConfig; pub use flags::*; pub use lockfile::CliLockfile; -pub use package_json::PackageJsonDepsProvider; +pub use package_json::PackageJsonInstallDepsProvider; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; @@ -68,7 +76,6 @@ use std::path::PathBuf; use std::sync::Arc; use thiserror::Error; -use crate::args::import_map::enhance_import_map_value_with_workspace_members; use crate::cache; use crate::file_fetcher::FileFetcher; use crate::util::fs::canonicalize_path_maybe_not_exists; @@ -243,37 +250,45 @@ impl CacheSetting { } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct BenchOptions { - pub files: FilePatterns, +pub struct WorkspaceBenchOptions { pub filter: Option, pub json: bool, pub no_run: bool, } -impl BenchOptions { - pub fn resolve( - maybe_bench_config: Option, - maybe_bench_flags: Option, - initial_cwd: &Path, - ) -> Result { - let bench_flags = maybe_bench_flags.unwrap_or_default(); - Ok(Self { - files: resolve_files( - maybe_bench_config.map(|c| c.files), - Some(bench_flags.files), - initial_cwd, - )?, - filter: bench_flags.filter, +impl WorkspaceBenchOptions { + pub fn resolve(bench_flags: &BenchFlags) -> Self { + Self { + filter: bench_flags.filter.clone(), json: bench_flags.json, no_run: bench_flags.no_run, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BenchOptions { + pub files: FilePatterns, +} + +impl BenchOptions { + pub fn resolve( + bench_config: BenchConfig, + bench_flags: &BenchFlags, + maybe_flags_base: Option<&Path>, + ) -> Result { + Ok(Self { + files: resolve_files( + bench_config.files, + &bench_flags.files, + maybe_flags_base, + )?, }) } } #[derive(Clone, Debug)] pub struct FmtOptions { - pub check: bool, pub options: FmtOptionsConfig, pub files: FilePatterns, } @@ -287,79 +302,66 @@ impl Default for FmtOptions { impl FmtOptions { pub fn new_with_base(base: PathBuf) -> Self { Self { - check: false, options: FmtOptionsConfig::default(), files: FilePatterns::new_with_base(base), } } pub fn resolve( - maybe_fmt_config: Option, - maybe_fmt_flags: Option, - initial_cwd: &Path, + fmt_config: FmtConfig, + fmt_flags: &FmtFlags, + maybe_flags_base: Option<&Path>, ) -> Result { - let (maybe_config_options, maybe_config_files) = - maybe_fmt_config.map(|c| (c.options, c.files)).unzip(); - Ok(Self { - check: maybe_fmt_flags.as_ref().map(|f| f.check).unwrap_or(false), - options: resolve_fmt_options( - maybe_fmt_flags.as_ref(), - maybe_config_options, - ), + options: resolve_fmt_options(fmt_flags, fmt_config.options), files: resolve_files( - maybe_config_files, - maybe_fmt_flags.map(|f| f.files), - initial_cwd, + fmt_config.files, + &fmt_flags.files, + maybe_flags_base, )?, }) } } fn resolve_fmt_options( - fmt_flags: Option<&FmtFlags>, - options: Option, + fmt_flags: &FmtFlags, + mut options: FmtOptionsConfig, ) -> FmtOptionsConfig { - let mut options = options.unwrap_or_default(); + if let Some(use_tabs) = fmt_flags.use_tabs { + options.use_tabs = Some(use_tabs); + } - if let Some(fmt_flags) = fmt_flags { - if let Some(use_tabs) = fmt_flags.use_tabs { - options.use_tabs = Some(use_tabs); - } + if let Some(line_width) = fmt_flags.line_width { + options.line_width = Some(line_width.get()); + } - if let Some(line_width) = fmt_flags.line_width { - options.line_width = Some(line_width.get()); - } + if let Some(indent_width) = fmt_flags.indent_width { + options.indent_width = Some(indent_width.get()); + } - if let Some(indent_width) = fmt_flags.indent_width { - options.indent_width = Some(indent_width.get()); - } + if let Some(single_quote) = fmt_flags.single_quote { + options.single_quote = Some(single_quote); + } - if let Some(single_quote) = fmt_flags.single_quote { - options.single_quote = Some(single_quote); - } + if let Some(prose_wrap) = &fmt_flags.prose_wrap { + options.prose_wrap = Some(match prose_wrap.as_str() { + "always" => ProseWrap::Always, + "never" => ProseWrap::Never, + "preserve" => ProseWrap::Preserve, + // validators in `flags.rs` makes other values unreachable + _ => unreachable!(), + }); + } - if let Some(prose_wrap) = &fmt_flags.prose_wrap { - options.prose_wrap = Some(match prose_wrap.as_str() { - "always" => ProseWrap::Always, - "never" => ProseWrap::Never, - "preserve" => ProseWrap::Preserve, - // validators in `flags.rs` makes other values unreachable - _ => unreachable!(), - }); - } - - if let Some(no_semis) = &fmt_flags.no_semicolons { - options.semi_colons = Some(!no_semis); - } + if let Some(no_semis) = &fmt_flags.no_semicolons { + options.semi_colons = Some(!no_semis); } options } -#[derive(Clone)] -pub struct TestOptions { - pub files: FilePatterns, +#[derive(Clone, Debug)] +pub struct WorkspaceTestOptions { pub doc: bool, pub no_run: bool, pub fail_fast: Option, @@ -372,37 +374,47 @@ pub struct TestOptions { pub junit_path: Option, } -impl TestOptions { - pub fn resolve( - maybe_test_config: Option, - maybe_test_flags: Option, - initial_cwd: &Path, - ) -> Result { - let test_flags = maybe_test_flags.unwrap_or_default(); - - Ok(Self { - files: resolve_files( - maybe_test_config.map(|c| c.files), - Some(test_flags.files), - initial_cwd, - )?, +impl WorkspaceTestOptions { + pub fn resolve(test_flags: &TestFlags) -> Self { + Self { allow_none: test_flags.allow_none, concurrent_jobs: test_flags .concurrent_jobs .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()), doc: test_flags.doc, fail_fast: test_flags.fail_fast, - filter: test_flags.filter, + filter: test_flags.filter.clone(), no_run: test_flags.no_run, shuffle: test_flags.shuffle, trace_leaks: test_flags.trace_leaks, reporter: test_flags.reporter, - junit_path: test_flags.junit_path, + junit_path: test_flags.junit_path.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct TestOptions { + pub files: FilePatterns, +} + +impl TestOptions { + pub fn resolve( + test_config: TestConfig, + test_flags: TestFlags, + maybe_flags_base: Option<&Path>, + ) -> Result { + Ok(Self { + files: resolve_files( + test_config.files, + &test_flags.files, + maybe_flags_base, + )?, }) } } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Copy, Default, Debug)] pub enum LintReporterKind { #[default] Pretty, @@ -410,11 +422,46 @@ pub enum LintReporterKind { Compact, } +#[derive(Clone, Debug)] +pub struct WorkspaceLintOptions { + pub reporter_kind: LintReporterKind, +} + +impl WorkspaceLintOptions { + pub fn resolve( + lint_config: &WorkspaceLintConfig, + lint_flags: &LintFlags, + ) -> Result { + let mut maybe_reporter_kind = if lint_flags.json { + Some(LintReporterKind::Json) + } else if lint_flags.compact { + Some(LintReporterKind::Compact) + } else { + None + }; + + if maybe_reporter_kind.is_none() { + // Flag not set, so try to get lint reporter from the config file. + maybe_reporter_kind = match lint_config.report.as_deref() { + Some("json") => Some(LintReporterKind::Json), + Some("compact") => Some(LintReporterKind::Compact), + Some("pretty") => Some(LintReporterKind::Pretty), + Some(_) => { + bail!("Invalid lint report type in config file") + } + None => None, + } + } + Ok(Self { + reporter_kind: maybe_reporter_kind.unwrap_or_default(), + }) + } +} + #[derive(Clone, Debug)] pub struct LintOptions { pub rules: LintRulesConfig, pub files: FilePatterns, - pub reporter_kind: LintReporterKind, pub fix: bool, } @@ -429,99 +476,51 @@ impl LintOptions { Self { rules: Default::default(), files: FilePatterns::new_with_base(base), - reporter_kind: Default::default(), fix: false, } } pub fn resolve( - maybe_lint_config: Option, - maybe_lint_flags: Option, - initial_cwd: &Path, + lint_config: LintConfig, + lint_flags: LintFlags, + maybe_flags_base: Option<&Path>, ) -> Result { - let fix = maybe_lint_flags.as_ref().map(|f| f.fix).unwrap_or(false); - let mut maybe_reporter_kind = - maybe_lint_flags.as_ref().and_then(|lint_flags| { - if lint_flags.json { - Some(LintReporterKind::Json) - } else if lint_flags.compact { - Some(LintReporterKind::Compact) - } else { - None - } - }); - - if maybe_reporter_kind.is_none() { - // Flag not set, so try to get lint reporter from the config file. - if let Some(lint_config) = &maybe_lint_config { - maybe_reporter_kind = match lint_config.report.as_deref() { - Some("json") => Some(LintReporterKind::Json), - Some("compact") => Some(LintReporterKind::Compact), - Some("pretty") => Some(LintReporterKind::Pretty), - Some(_) => { - bail!("Invalid lint report type in config file") - } - None => None, - } - } - } - - let ( - maybe_file_flags, - maybe_rules_tags, - maybe_rules_include, - maybe_rules_exclude, - ) = maybe_lint_flags - .map(|f| { - ( - f.files, - f.maybe_rules_tags, - f.maybe_rules_include, - f.maybe_rules_exclude, - ) - }) - .unwrap_or_default(); - - let (maybe_config_files, maybe_config_rules) = - maybe_lint_config.map(|c| (c.files, c.rules)).unzip(); Ok(Self { - reporter_kind: maybe_reporter_kind.unwrap_or_default(), files: resolve_files( - maybe_config_files, - Some(maybe_file_flags), - initial_cwd, + lint_config.files, + &lint_flags.files, + maybe_flags_base, )?, rules: resolve_lint_rules_options( - maybe_config_rules, - maybe_rules_tags, - maybe_rules_include, - maybe_rules_exclude, + lint_config.rules, + lint_flags.maybe_rules_tags, + lint_flags.maybe_rules_include, + lint_flags.maybe_rules_exclude, ), - fix, + fix: lint_flags.fix, }) } } fn resolve_lint_rules_options( - maybe_lint_rules_config: Option, + config_rules: LintRulesConfig, mut maybe_rules_tags: Option>, mut maybe_rules_include: Option>, mut maybe_rules_exclude: Option>, ) -> LintRulesConfig { - if let Some(config_rules) = maybe_lint_rules_config { - // Try to get configured rules. CLI flags take precedence - // over config file, i.e. if there's `rules.include` in config file - // and `--rules-include` CLI flag, only the flag value is taken into account. - if maybe_rules_include.is_none() { - maybe_rules_include = config_rules.include; - } - if maybe_rules_exclude.is_none() { - maybe_rules_exclude = config_rules.exclude; - } - if maybe_rules_tags.is_none() { - maybe_rules_tags = config_rules.tags; - } + // Try to get configured rules. CLI flags take precedence + // over config file, i.e. if there's `rules.include` in config file + // and `--rules-include` CLI flag, only the flag value is taken into account. + if maybe_rules_include.is_none() { + maybe_rules_include = config_rules.include; } + if maybe_rules_exclude.is_none() { + maybe_rules_exclude = config_rules.exclude; + } + if maybe_rules_tags.is_none() { + maybe_rules_tags = config_rules.tags; + } + LintRulesConfig { exclude: maybe_rules_exclude, include: maybe_rules_include, @@ -529,24 +528,6 @@ fn resolve_lint_rules_options( } } -/// Discover `package.json` file. If `maybe_stop_at` is provided, we will stop -/// crawling up the directory tree at that path. -fn discover_package_json( - flags: &Flags, - maybe_stop_at: Option, - current_dir: &Path, -) -> Result>, AnyError> { - // TODO(bartlomieju): discover for all subcommands, but print warnings that - // `package.json` is ignored in bundle/compile/etc. - - if let Some(package_json_dir) = flags.package_json_search_dir(current_dir) { - return package_json::discover_from(&package_json_dir, maybe_stop_at); - } - - log::debug!("No package.json file found"); - Ok(None) -} - /// Discover `.npmrc` file - currently we only support it next to `package.json` /// or next to `deno.json`. /// @@ -798,12 +779,10 @@ pub struct CliOptions { initial_cwd: PathBuf, maybe_node_modules_folder: Option, maybe_vendor_folder: Option, - maybe_config_file: Option, - maybe_package_json: Option>, npmrc: Arc, maybe_lockfile: Option>, overrides: CliOptionOverrides, - maybe_workspace_config: Option, + pub workspace: Arc, pub disable_deprecated_api_warning: bool, pub verbose_deprecated_api_warning: bool, } @@ -812,10 +791,9 @@ impl CliOptions { pub fn new( flags: Flags, initial_cwd: PathBuf, - maybe_config_file: Option, maybe_lockfile: Option>, - maybe_package_json: Option>, npmrc: Arc, + workspace: Arc, force_global_cache: bool, ) -> Result { if let Some(insecure_allowlist) = @@ -836,24 +814,23 @@ impl CliOptions { } let maybe_lockfile = maybe_lockfile.filter(|_| !force_global_cache); + let root_folder = workspace.root_folder().1; let maybe_node_modules_folder = resolve_node_modules_folder( &initial_cwd, &flags, - maybe_config_file.as_ref(), - maybe_package_json.as_deref(), + root_folder.deno_json.as_deref(), + root_folder.pkg_json.as_deref(), ) .with_context(|| "Resolving node_modules folder.")?; let maybe_vendor_folder = if force_global_cache { None } else { - resolve_vendor_folder(&initial_cwd, &flags, maybe_config_file.as_ref()) + resolve_vendor_folder( + &initial_cwd, + &flags, + root_folder.deno_json.as_deref(), + ) }; - let maybe_workspace_config = - if let Some(config_file) = maybe_config_file.as_ref() { - config_file.to_workspace_config()? - } else { - None - }; if let Some(env_file_name) = &flags.env_file { match from_filename(env_file_name) { @@ -879,14 +856,12 @@ impl CliOptions { Ok(Self { flags, initial_cwd, - maybe_config_file, maybe_lockfile, - maybe_package_json, npmrc, maybe_node_modules_folder, maybe_vendor_folder, overrides: Default::default(), - maybe_workspace_config, + workspace, disable_deprecated_api_warning, verbose_deprecated_api_warning, }) @@ -895,50 +870,71 @@ impl CliOptions { pub fn from_flags(flags: Flags) -> Result { let initial_cwd = std::env::current_dir().with_context(|| "Failed getting cwd.")?; - let additional_config_file_names = - if matches!(flags.subcommand, DenoSubcommand::Publish(..)) { - Some(vec!["jsr.json", "jsr.jsonc"]) - } else { - None + let config_fs_adapter = DenoConfigFsAdapter::new(&RealFs); + let resolve_workspace_discover_options = || { + let additional_config_file_names: &'static [&'static str] = + if matches!(flags.subcommand, DenoSubcommand::Publish(..)) { + &["jsr.json", "jsr.jsonc"] + } else { + &[] + }; + let config_parse_options = deno_config::ConfigParseOptions { + include_task_comments: matches!( + flags.subcommand, + DenoSubcommand::Task(..) + ), }; - let parse_options = deno_config::ParseOptions { - include_task_comments: matches!( - flags.subcommand, - DenoSubcommand::Task(..) - ), - }; - let maybe_config_file = ConfigFile::discover( - &flags.config_flag, - flags.config_path_args(&initial_cwd), - &initial_cwd, - additional_config_file_names, - &parse_options, - )?; - - let mut maybe_package_json = None; - if flags.config_flag == deno_config::ConfigFlag::Disabled - || flags.no_npm - || has_flag_env_var("DENO_NO_PACKAGE_JSON") - { - log::debug!("package.json auto-discovery is disabled") - } else if let Some(config_file) = &maybe_config_file { - let specifier = config_file.specifier.clone(); - if specifier.scheme() == "file" { - let maybe_stop_at = specifier - .to_file_path() - .unwrap() - .parent() - .map(|p| p.to_path_buf()); - - maybe_package_json = - discover_package_json(&flags, maybe_stop_at, &initial_cwd)?; + let discover_pkg_json = flags.config_flag + != deno_config::ConfigFlag::Disabled + && !flags.no_npm + && !has_flag_env_var("DENO_NO_PACKAGE_JSON"); + if !discover_pkg_json { + log::debug!("package.json auto-discovery is disabled"); } - } else { - maybe_package_json = discover_package_json(&flags, None, &initial_cwd)?; + WorkspaceDiscoverOptions { + fs: &config_fs_adapter, + pkg_json_cache: Some( + &deno_runtime::deno_node::PackageJsonThreadLocalCache, + ), + config_parse_options, + additional_config_file_names, + discover_pkg_json, + } + }; + + let workspace = match &flags.config_flag { + deno_config::ConfigFlag::Discover => { + if let Some(start_dirs) = flags.config_path_args(&initial_cwd) { + Workspace::discover( + WorkspaceDiscoverStart::Dirs(&start_dirs), + &resolve_workspace_discover_options(), + )? + } else { + Workspace::empty(Arc::new( + ModuleSpecifier::from_directory_path(&initial_cwd).unwrap(), + )) + } + } + deno_config::ConfigFlag::Path(path) => { + let config_path = normalize_path(initial_cwd.join(path)); + Workspace::discover( + WorkspaceDiscoverStart::ConfigFile(&config_path), + &resolve_workspace_discover_options(), + )? + } + deno_config::ConfigFlag::Disabled => Workspace::empty(Arc::new( + ModuleSpecifier::from_directory_path(&initial_cwd).unwrap(), + )), + }; + + for diagnostic in workspace.diagnostics() { + log::warn!("{}", colors::yellow(diagnostic)); } + + let root_folder = workspace.root_folder().1; let (npmrc, _) = discover_npmrc( - maybe_package_json.as_ref().map(|p| p.path.clone()), - maybe_config_file.as_ref().and_then(|cf| { + root_folder.pkg_json.as_ref().map(|p| p.path.clone()), + root_folder.deno_json.as_ref().and_then(|cf| { if cf.specifier.scheme() == "file" { Some(cf.specifier.to_file_path().unwrap()) } else { @@ -949,16 +945,18 @@ impl CliOptions { let maybe_lock_file = CliLockfile::discover( &flags, - maybe_config_file.as_ref(), - maybe_package_json.as_deref(), + root_folder.deno_json.as_deref(), + root_folder.pkg_json.as_deref(), )?; + + log::debug!("Finished config loading."); + Self::new( flags, initial_cwd, - maybe_config_file, maybe_lock_file.map(Arc::new), - maybe_package_json, npmrc, + Arc::new(workspace), false, ) } @@ -968,10 +966,6 @@ impl CliOptions { &self.initial_cwd } - pub fn maybe_config_file_specifier(&self) -> Option { - self.maybe_config_file.as_ref().map(|f| f.specifier.clone()) - } - pub fn graph_kind(&self) -> GraphKind { match self.sub_command() { DenoSubcommand::Cache(_) => GraphKind::All, @@ -1057,70 +1051,78 @@ impl CliOptions { Some(maybe_url) => Ok(maybe_url), None => resolve_import_map_specifier( self.flags.import_map_path.as_deref(), - self.maybe_config_file.as_ref(), + self.workspace.root_folder().1.deno_json.as_deref(), &self.initial_cwd, ), } } - pub async fn resolve_import_map( + pub async fn create_workspace_resolver( &self, file_fetcher: &FileFetcher, - ) -> Result, AnyError> { - if let Some(workspace_config) = self.maybe_workspace_config.as_ref() { - let root_config_file = self.maybe_config_file.as_ref().unwrap(); - let base_import_map_config = ::import_map::ext::ImportMapConfig { - base_url: root_config_file.specifier.clone(), - import_map_value: root_config_file.to_import_map_value_from_imports(), - }; - let children_configs = workspace_config - .members - .iter() - .map(|member| ::import_map::ext::ImportMapConfig { - base_url: member.config_file.specifier.clone(), - import_map_value: member - .config_file - .to_import_map_value_from_imports(), - }) - .collect(); - - let (import_map_url, import_map) = - ::import_map::ext::create_synthetic_import_map( - base_import_map_config, - children_configs, - ); - let import_map = enhance_import_map_value_with_workspace_members( - import_map, - &workspace_config.members, - ); - log::debug!( - "Workspace config generated this import map {}", - serde_json::to_string_pretty(&import_map).unwrap() - ); - let maybe_import_map_result = - import_map::import_map_from_value(import_map_url, import_map).map(Some); - - return maybe_import_map_result; - } - - if self + ) -> Result { + let overrode_no_import_map = self .overrides .import_map_specifier .as_ref() .map(|s| s.is_none()) - == Some(true) - { - // overrode to not use an import map - return Ok(None); - } - - let import_map_specifier = self.resolve_specified_import_map_specifier()?; - resolve_import_map( - import_map_specifier.as_ref(), - self.maybe_config_file().as_ref(), - file_fetcher, + == Some(true); + let cli_arg_specified_import_map = if overrode_no_import_map { + // use a fake empty import map + Some(deno_config::workspace::SpecifiedImportMap { + base_url: self + .workspace + .root_folder() + .0 + .join("import_map.json") + .unwrap(), + value: serde_json::Value::Object(Default::default()), + }) + } else { + let maybe_import_map_specifier = + self.resolve_specified_import_map_specifier()?; + match maybe_import_map_specifier { + Some(specifier) => { + let value = + resolve_import_map_value_from_specifier(&specifier, file_fetcher) + .await + .with_context(|| { + format!("Unable to load '{}' import map", specifier) + })?; + Some(deno_config::workspace::SpecifiedImportMap { + base_url: specifier, + value, + }) + } + None => None, + } + }; + Ok( + self + .workspace + .create_resolver( + CreateResolverOptions { + // todo(dsherret): this should be false for nodeModulesDir: true + pkg_json_dep_resolution: if self.use_byonm() { + PackageJsonDepResolution::Disabled + } else { + PackageJsonDepResolution::Enabled + }, + specified_import_map: cli_arg_specified_import_map, + }, + |specifier| { + let specifier = specifier.clone(); + async move { + let file = file_fetcher + .fetch(&specifier, &PermissionsContainer::allow_all()) + .await? + .into_text_decoded()?; + Ok(file.source.to_string()) + } + }, + ) + .await?, ) - .await } pub fn node_ipc_fd(&self) -> Option { @@ -1155,22 +1157,18 @@ impl CliOptions { } pub fn resolve_main_module(&self) -> Result { - match &self.flags.subcommand { + let main_module = match &self.flags.subcommand { DenoSubcommand::Bundle(bundle_flags) => { - resolve_url_or_path(&bundle_flags.source_file, self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path(&bundle_flags.source_file, self.initial_cwd())? } DenoSubcommand::Compile(compile_flags) => { - resolve_url_or_path(&compile_flags.source_file, self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path(&compile_flags.source_file, self.initial_cwd())? } DenoSubcommand::Eval(_) => { - resolve_url_or_path("./$deno$eval", self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path("./$deno$eval", self.initial_cwd())? } DenoSubcommand::Repl(_) => { - resolve_url_or_path("./$deno$repl.ts", self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path("./$deno$repl.ts", self.initial_cwd())? } DenoSubcommand::Run(run_flags) => { if run_flags.is_stdin() { @@ -1179,25 +1177,24 @@ impl CliOptions { .and_then(|cwd| { resolve_url_or_path("./$deno$stdin.ts", &cwd) .map_err(AnyError::from) - }) + })? } else if run_flags.watch.is_some() { - resolve_url_or_path(&run_flags.script, self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path(&run_flags.script, self.initial_cwd())? } else if NpmPackageReqReference::from_str(&run_flags.script).is_ok() { - ModuleSpecifier::parse(&run_flags.script).map_err(AnyError::from) + ModuleSpecifier::parse(&run_flags.script)? } else { - resolve_url_or_path(&run_flags.script, self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path(&run_flags.script, self.initial_cwd())? } } DenoSubcommand::Serve(run_flags) => { - resolve_url_or_path(&run_flags.script, self.initial_cwd()) - .map_err(AnyError::from) + resolve_url_or_path(&run_flags.script, self.initial_cwd())? } _ => { bail!("No main module.") } - } + }; + + Ok(main_module) } pub fn resolve_file_header_overrides( @@ -1266,11 +1263,9 @@ impl CliOptions { initial_cwd: self.initial_cwd.clone(), maybe_node_modules_folder: Some(path), maybe_vendor_folder: self.maybe_vendor_folder.clone(), - maybe_config_file: self.maybe_config_file.clone(), - maybe_package_json: self.maybe_package_json.clone(), npmrc: self.npmrc.clone(), maybe_lockfile: self.maybe_lockfile.clone(), - maybe_workspace_config: self.maybe_workspace_config.clone(), + workspace: self.workspace.clone(), overrides: self.overrides.clone(), disable_deprecated_api_warning: self.disable_deprecated_api_warning, verbose_deprecated_api_warning: self.verbose_deprecated_api_warning, @@ -1278,12 +1273,10 @@ impl CliOptions { } pub fn node_modules_dir_enablement(&self) -> Option { - self.flags.node_modules_dir.or_else(|| { - self - .maybe_config_file - .as_ref() - .and_then(|c| c.json.node_modules_dir) - }) + self + .flags + .node_modules_dir + .or_else(|| self.workspace.node_modules_dir()) } pub fn vendor_dir_path(&self) -> Option<&PathBuf> { @@ -1304,10 +1297,7 @@ impl CliOptions { &self, config_type: TsConfigType, ) -> Result { - let result = deno_config::get_ts_config_for_emit( - config_type, - self.maybe_config_file.as_ref(), - ); + let result = self.workspace.resolve_ts_config_for_emit(config_type); match result { Ok(mut ts_config_for_emit) => { @@ -1346,101 +1336,83 @@ impl CliOptions { self.maybe_lockfile.clone() } - pub fn resolve_tasks_config( - &self, - ) -> Result, AnyError> { - if let Some(config_file) = &self.maybe_config_file { - config_file.resolve_tasks_config() - } else if self.maybe_package_json.is_some() { - Ok(Default::default()) - } else { - bail!("deno task couldn't find deno.json(c). See https://deno.land/manual@v{}/getting_started/configuration_file", env!("CARGO_PKG_VERSION")) - } - } - - /// Return the JSX import source configuration. - pub fn to_maybe_jsx_import_source_config( - &self, - ) -> Result, AnyError> { - match self.maybe_config_file.as_ref() { - Some(config) => config.to_maybe_jsx_import_source_config(), - None => Ok(None), - } - } - /// Return any imports that should be brought into the scope of the module /// graph. pub fn to_maybe_imports( &self, ) -> Result, AnyError> { - if let Some(config_file) = &self.maybe_config_file { - config_file.to_maybe_imports().map(|maybe_imports| { - maybe_imports - .into_iter() - .map(|(referrer, imports)| deno_graph::ReferrerImports { - referrer, - imports, - }) - .collect() - }) - } else { - Ok(Vec::new()) - } - } - - pub fn maybe_config_file(&self) -> &Option { - &self.maybe_config_file - } - - pub fn maybe_workspace_config(&self) -> &Option { - &self.maybe_workspace_config - } - - pub fn maybe_package_json(&self) -> Option<&Arc> { - self.maybe_package_json.as_ref() + self.workspace.to_maybe_imports().map(|maybe_imports| { + maybe_imports + .into_iter() + .map(|(referrer, imports)| deno_graph::ReferrerImports { + referrer, + imports, + }) + .collect() + }) } pub fn npmrc(&self) -> &Arc { &self.npmrc } - pub fn maybe_package_json_deps(&self) -> Option { - if matches!( - self.flags.subcommand, - DenoSubcommand::Task(TaskFlags { task: None, .. }) - ) { - // don't have any package json dependencies for deno task with no args - None - } else { - self - .maybe_package_json() - .as_ref() - .map(|p| p.resolve_local_package_json_version_reqs()) + pub fn resolve_fmt_options_for_members( + &self, + fmt_flags: &FmtFlags, + ) -> Result, AnyError> { + let cli_arg_patterns = + fmt_flags.files.as_file_patterns(self.initial_cwd())?; + let member_ctxs = + self.workspace.resolve_ctxs_from_patterns(&cli_arg_patterns); + let mut result = Vec::with_capacity(member_ctxs.len()); + for member_ctx in &member_ctxs { + let options = self.resolve_fmt_options(fmt_flags, member_ctx)?; + result.push(options); } + Ok(result) } pub fn resolve_fmt_options( &self, - fmt_flags: FmtFlags, + fmt_flags: &FmtFlags, + ctx: &WorkspaceMemberContext, ) -> Result { - let maybe_fmt_config = if let Some(config_file) = &self.maybe_config_file { - config_file.to_fmt_config()? - } else { - None - }; - FmtOptions::resolve(maybe_fmt_config, Some(fmt_flags), &self.initial_cwd) + let fmt_config = ctx.to_fmt_config()?; + FmtOptions::resolve(fmt_config, fmt_flags, Some(&self.initial_cwd)) + } + + pub fn resolve_workspace_lint_options( + &self, + lint_flags: &LintFlags, + ) -> Result { + let lint_config = self.workspace.to_lint_config()?; + WorkspaceLintOptions::resolve(&lint_config, lint_flags) + } + + pub fn resolve_lint_options_for_members( + &self, + lint_flags: &LintFlags, + ) -> Result, AnyError> { + let cli_arg_patterns = + lint_flags.files.as_file_patterns(self.initial_cwd())?; + let member_ctxs = + self.workspace.resolve_ctxs_from_patterns(&cli_arg_patterns); + let mut result = Vec::with_capacity(member_ctxs.len()); + for member_ctx in member_ctxs { + let options = + self.resolve_lint_options(lint_flags.clone(), &member_ctx)?; + result.push((member_ctx, options)); + } + Ok(result) } pub fn resolve_lint_options( &self, lint_flags: LintFlags, + ctx: &WorkspaceMemberContext, ) -> Result { - let maybe_lint_config = if let Some(config_file) = &self.maybe_config_file { - config_file.to_lint_config()? - } else { - None - }; - LintOptions::resolve(maybe_lint_config, Some(lint_flags), &self.initial_cwd) + let lint_config = ctx.to_lint_config()?; + LintOptions::resolve(lint_config, lint_flags, Some(&self.initial_cwd)) } pub fn resolve_lint_config( @@ -1464,104 +1436,80 @@ impl CliOptions { }) } - pub fn resolve_config_excludes(&self) -> Result { - let maybe_config_files = if let Some(config_file) = &self.maybe_config_file - { - Some(config_file.to_files_config()?) - } else { - None - }; - Ok(maybe_config_files.map(|f| f.exclude).unwrap_or_default()) + pub fn resolve_workspace_test_options( + &self, + test_flags: &TestFlags, + ) -> WorkspaceTestOptions { + WorkspaceTestOptions::resolve(test_flags) + } + + pub fn resolve_test_options_for_members( + &self, + test_flags: &TestFlags, + ) -> Result, AnyError> { + let cli_arg_patterns = + test_flags.files.as_file_patterns(self.initial_cwd())?; + let member_ctxs = + self.workspace.resolve_ctxs_from_patterns(&cli_arg_patterns); + let mut result = Vec::with_capacity(member_ctxs.len()); + for member_ctx in member_ctxs { + let options = + self.resolve_test_options(test_flags.clone(), &member_ctx)?; + result.push((member_ctx, options)); + } + Ok(result) + } + + pub fn resolve_workspace_bench_options( + &self, + bench_flags: &BenchFlags, + ) -> WorkspaceBenchOptions { + WorkspaceBenchOptions::resolve(bench_flags) } pub fn resolve_test_options( &self, test_flags: TestFlags, + ctx: &WorkspaceMemberContext, ) -> Result { - let maybe_test_config = if let Some(config_file) = &self.maybe_config_file { - config_file.to_test_config()? - } else { - None - }; - TestOptions::resolve(maybe_test_config, Some(test_flags), &self.initial_cwd) + let test_config = ctx.to_test_config()?; + TestOptions::resolve(test_config, test_flags, Some(&self.initial_cwd)) + } + + pub fn resolve_bench_options_for_members( + &self, + bench_flags: &BenchFlags, + ) -> Result, AnyError> { + let cli_arg_patterns = + bench_flags.files.as_file_patterns(self.initial_cwd())?; + let member_ctxs = + self.workspace.resolve_ctxs_from_patterns(&cli_arg_patterns); + let mut result = Vec::with_capacity(member_ctxs.len()); + for member_ctx in member_ctxs { + let options = self.resolve_bench_options(bench_flags, &member_ctx)?; + result.push((member_ctx, options)); + } + Ok(result) } pub fn resolve_bench_options( &self, - bench_flags: BenchFlags, + bench_flags: &BenchFlags, + ctx: &WorkspaceMemberContext, ) -> Result { - let maybe_bench_config = if let Some(config_file) = &self.maybe_config_file - { - config_file.to_bench_config()? - } else { - None - }; - BenchOptions::resolve( - maybe_bench_config, - Some(bench_flags), - &self.initial_cwd, - ) + let bench_config = ctx.to_bench_config()?; + BenchOptions::resolve(bench_config, bench_flags, Some(&self.initial_cwd)) } pub fn resolve_deno_graph_workspace_members( &self, ) -> Result, AnyError> { - fn workspace_config_to_workspace_members( - workspace_config: &deno_config::WorkspaceConfig, - ) -> Result, AnyError> { - workspace_config - .members - .iter() - .map(|member| { - config_to_workspace_member(&member.config_file).with_context(|| { - format!( - "Failed to resolve configuration for '{}' workspace member at '{}'", - member.member_name, - member.config_file.specifier.as_str() - ) - }) - }) - .collect() - } - - fn config_to_workspace_member( - config: &ConfigFile, - ) -> Result { - let nv = deno_semver::package::PackageNv { - name: match &config.json.name { - Some(name) => name.clone(), - None => bail!("Missing 'name' field in config file."), - }, - version: match &config.json.version { - Some(name) => deno_semver::Version::parse_standard(name)?, - None => bail!("Missing 'version' field in config file."), - }, - }; - Ok(deno_graph::WorkspaceMember { - base: config.specifier.join("./").unwrap(), - nv, - exports: config.to_exports_config()?.into_map(), - }) - } - - let maybe_workspace_config = self.maybe_workspace_config(); - if let Some(wc) = maybe_workspace_config { - workspace_config_to_workspace_members(wc) - } else { - Ok( - self - .maybe_config_file() - .as_ref() - .and_then(|c| match config_to_workspace_member(c) { - Ok(m) => Some(vec![m]), - Err(e) => { - log::debug!("Deno config was not a package: {:#}", e); - None - } - }) - .unwrap_or_default(), - ) - } + self + .workspace + .jsr_packages() + .into_iter() + .map(|pkg| config_to_deno_graph_workspace_member(&pkg.config_file)) + .collect::, _>>() } /// Vector of user script CLI arguments. @@ -1578,11 +1526,7 @@ impl CliOptions { } pub fn check_js(&self) -> bool { - self - .maybe_config_file - .as_ref() - .map(|cf| cf.get_check_js()) - .unwrap_or(false) + self.workspace.check_js() } pub fn coverage_dir(&self) -> Option { @@ -1729,17 +1673,17 @@ impl CliOptions { pub fn unstable_bare_node_builtins(&self) -> bool { self.flags.unstable_config.bare_node_builtins - || self - .maybe_config_file() - .as_ref() - .map(|c| c.has_unstable("bare-node-builtins")) - .unwrap_or(false) + || self.workspace.has_unstable("bare-node-builtins") } pub fn use_byonm(&self) -> bool { if self.enable_future_features() && self.node_modules_dir_enablement().is_none() - && self.maybe_package_json.is_some() + && self + .workspace + .config_folders() + .values() + .any(|f| f.pkg_json.is_some()) { return true; } @@ -1750,28 +1694,16 @@ impl CliOptions { .as_ref() .map(|s| matches!(s.kind, NpmProcessStateKind::Byonm)) .unwrap_or(false) - || self - .maybe_config_file() - .as_ref() - .map(|c| c.has_unstable("byonm")) - .unwrap_or(false) + || self.workspace.has_unstable("byonm") } pub fn unstable_sloppy_imports(&self) -> bool { self.flags.unstable_config.sloppy_imports - || self - .maybe_config_file() - .as_ref() - .map(|c| c.has_unstable("sloppy-imports")) - .unwrap_or(false) + || self.workspace.has_unstable("sloppy-imports") } pub fn unstable_features(&self) -> Vec { - let mut from_config_file = self - .maybe_config_file() - .as_ref() - .map(|c| c.json.unstable.clone()) - .unwrap_or_default(); + let mut from_config_file = self.workspace.unstable_features().to_vec(); self .flags @@ -1824,12 +1756,18 @@ impl CliOptions { { full_paths.push(import_map_path); } - if let Some(specifier) = self.maybe_config_file_specifier() { - if specifier.scheme() == "file" { - if let Ok(path) = specifier.to_file_path() { - full_paths.push(path); + + for (_, folder) in self.workspace.config_folders() { + if let Some(deno_json) = &folder.deno_json { + if deno_json.specifier.scheme() == "file" { + if let Ok(path) = deno_json.specifier.to_file_path() { + full_paths.push(path); + } } } + if let Some(pkg_json) = &folder.pkg_json { + full_paths.push(pkg_json.path.clone()); + } } full_paths } @@ -1938,8 +1876,9 @@ impl StorageKeyResolver { // otherwise we will use the path to the config file or None to // fall back to using the main module's path options - .maybe_config_file - .as_ref() + .workspace + .resolve_start_ctx() + .maybe_deno_json() .map(|config_file| Some(config_file.specifier.to_string())) }) } @@ -1967,29 +1906,25 @@ impl StorageKeyResolver { /// over config file, i.e. if there's `files.ignore` in config file /// and `--ignore` CLI flag, only the flag value is taken into account. fn resolve_files( - maybe_files_config: Option, - maybe_file_flags: Option, - initial_cwd: &Path, + mut files_config: FilePatterns, + file_flags: &FileFlags, + maybe_flags_base: Option<&Path>, ) -> Result { - let mut maybe_files_config = maybe_files_config - .unwrap_or_else(|| FilePatterns::new_with_base(initial_cwd.to_path_buf())); - if let Some(file_flags) = maybe_file_flags { - if !file_flags.include.is_empty() { - maybe_files_config.include = - Some(PathOrPatternSet::from_include_relative_path_or_patterns( - initial_cwd, - &file_flags.include, - )?); - } - if !file_flags.ignore.is_empty() { - maybe_files_config.exclude = - PathOrPatternSet::from_exclude_relative_path_or_patterns( - initial_cwd, - &file_flags.ignore, - )?; - } + if !file_flags.include.is_empty() { + files_config.include = + Some(PathOrPatternSet::from_include_relative_path_or_patterns( + maybe_flags_base.unwrap_or(&files_config.base), + &file_flags.include, + )?); } - Ok(maybe_files_config) + if !file_flags.ignore.is_empty() { + files_config.exclude = + PathOrPatternSet::from_exclude_relative_path_or_patterns( + maybe_flags_base.unwrap_or(&files_config.base), + &file_flags.ignore, + )?; + } + Ok(files_config) } /// Resolves the no_prompt value based on the cli flags and environment. @@ -2009,6 +1944,26 @@ pub fn npm_pkg_req_ref_to_binary_command( binary_name.to_string() } +pub fn config_to_deno_graph_workspace_member( + config: &ConfigFile, +) -> Result { + let nv = deno_semver::package::PackageNv { + name: match &config.json.name { + Some(name) => name.clone(), + None => bail!("Missing 'name' field in config file."), + }, + version: match &config.json.version { + Some(name) => deno_semver::Version::parse_standard(name)?, + None => bail!("Missing 'version' field in config file."), + }, + }; + Ok(deno_graph::WorkspaceMember { + base: config.specifier.join("./").unwrap(), + nv, + exports: config.to_exports_config()?.into_map(), + }) +} + #[cfg(test)] mod test { use crate::util::fs::FileCollector; @@ -2027,7 +1982,7 @@ mod test { let config_file = ConfigFile::new( config_text, config_specifier, - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(); let actual = resolve_import_map_specifier( @@ -2051,7 +2006,7 @@ mod test { let config_file = ConfigFile::new( config_text, config_specifier, - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(); let actual = resolve_import_map_specifier( @@ -2130,7 +2085,7 @@ mod test { assert!(error.to_string().starts_with("Failed to expand glob")); let resolved_files = resolve_files( - Some(FilePatterns { + FilePatterns { base: temp_dir_path.to_path_buf(), include: Some( PathOrPatternSet::from_include_relative_path_or_patterns( @@ -2149,9 +2104,9 @@ mod test { &["nested/**/*bazz.ts".to_string()], ) .unwrap(), - }), - None, - temp_dir_path, + }, + &Default::default(), + Some(temp_dir_path), ) .unwrap(); diff --git a/cli/args/package_json.rs b/cli/args/package_json.rs index b6ccb33a4d..eb1c41c5df 100644 --- a/cli/args/package_json.rs +++ b/cli/args/package_json.rs @@ -1,77 +1,87 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::path::Path; use std::path::PathBuf; use std::sync::Arc; -use deno_config::package_json::PackageJsonDeps; -use deno_core::anyhow::bail; -use deno_core::error::AnyError; -use deno_runtime::deno_fs::RealFs; -use deno_runtime::deno_node::load_pkg_json; -use deno_runtime::deno_node::PackageJson; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::Workspace; use deno_semver::package::PackageReq; -#[derive(Debug, Default)] -pub struct PackageJsonDepsProvider(Option); - -impl PackageJsonDepsProvider { - pub fn new(deps: Option) -> Self { - Self(deps) - } - - pub fn deps(&self) -> Option<&PackageJsonDeps> { - self.0.as_ref() - } - - pub fn reqs(&self) -> Option> { - match &self.0 { - Some(deps) => { - let mut package_reqs = deps - .values() - .filter_map(|r| r.as_ref().ok()) - .collect::>(); - package_reqs.sort(); // deterministic resolution - Some(package_reqs) - } - None => None, - } - } +#[derive(Debug)] +pub struct InstallNpmWorkspacePkg { + pub alias: String, + pub pkg_dir: PathBuf, } -/// Attempts to discover the package.json file, maybe stopping when it -/// reaches the specified `maybe_stop_at` directory. -pub fn discover_from( - start: &Path, - maybe_stop_at: Option, -) -> Result>, AnyError> { - const PACKAGE_JSON_NAME: &str = "package.json"; +// todo(#24419): this is not correct, but it's good enough for now. +// We need deno_npm to be able to understand workspace packages and +// then have a way to properly lay them out on the file system +#[derive(Debug, Default)] +pub struct PackageJsonInstallDepsProvider { + remote_pkg_reqs: Vec, + workspace_pkgs: Vec, +} - // note: ancestors() includes the `start` path - for ancestor in start.ancestors() { - let path = ancestor.join(PACKAGE_JSON_NAME); +impl PackageJsonInstallDepsProvider { + pub fn empty() -> Self { + Self::default() + } - let package_json = match load_pkg_json(&RealFs, &path) { - Ok(Some(package_json)) => package_json, - Ok(None) => { - if let Some(stop_at) = maybe_stop_at.as_ref() { - if ancestor == stop_at { - break; + pub fn from_workspace(workspace: &Arc) -> Self { + let mut workspace_pkgs = Vec::new(); + let mut remote_pkg_reqs = Vec::new(); + let workspace_npm_pkgs = workspace.npm_packages(); + for pkg_json in workspace.package_jsons() { + let deps = pkg_json.resolve_local_package_json_deps(); + let mut pkg_reqs = Vec::with_capacity(deps.len()); + for (alias, dep) in deps { + let Ok(dep) = dep else { + continue; + }; + match dep { + PackageJsonDepValue::Req(pkg_req) => { + if let Some(pkg) = workspace_npm_pkgs + .iter() + .find(|pkg| pkg.matches_req(&pkg_req)) + { + workspace_pkgs.push(InstallNpmWorkspacePkg { + alias, + pkg_dir: pkg.pkg_json.dir_path().to_path_buf(), + }); + } else { + pkg_reqs.push(pkg_req) + } + } + PackageJsonDepValue::Workspace(version_req) => { + if let Some(pkg) = workspace_npm_pkgs.iter().find(|pkg| { + pkg.matches_name_and_version_req(&alias, &version_req) + }) { + workspace_pkgs.push(InstallNpmWorkspacePkg { + alias, + pkg_dir: pkg.pkg_json.dir_path().to_path_buf(), + }); + } } } - continue; } - Err(err) => bail!( - "Error loading package.json at {}. {:#}", - path.display(), - err - ), - }; + // sort within each package + pkg_reqs.sort(); - log::debug!("package.json file found at '{}'", path.display()); - return Ok(Some(package_json)); + remote_pkg_reqs.extend(pkg_reqs); + } + remote_pkg_reqs.shrink_to_fit(); + workspace_pkgs.shrink_to_fit(); + Self { + remote_pkg_reqs, + workspace_pkgs, + } } - log::debug!("No package.json file found"); - Ok(None) + pub fn remote_pkg_reqs(&self) -> &Vec { + &self.remote_pkg_reqs + } + + pub fn workspace_pkgs(&self) -> &Vec { + &self.workspace_pkgs + } } diff --git a/cli/factory.rs b/cli/factory.rs index 56a28b6d9b..62ab251f16 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1,11 +1,10 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use crate::args::deno_json::deno_json_deps; use crate::args::CliLockfile; use crate::args::CliOptions; use crate::args::DenoSubcommand; use crate::args::Flags; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::args::StorageKeyResolver; use crate::args::TsConfigType; use crate::cache::Caches; @@ -52,8 +51,12 @@ use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; +use std::collections::BTreeSet; use std::path::PathBuf; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::WorkspaceResolver; +use deno_config::ConfigFile; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::FeatureChecker; @@ -62,10 +65,10 @@ use deno_lockfile::WorkspaceMemberConfig; use deno_runtime::deno_fs; use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; use deno_runtime::inspector_server::InspectorServer; -use import_map::ImportMap; use log::warn; use std::future::Future; use std::sync::Arc; @@ -156,7 +159,6 @@ struct CliFactoryServices { fs: Deferred>, main_graph_container: Deferred>, lockfile: Deferred>>, - maybe_import_map: Deferred>>, maybe_inspector_server: Deferred>>, root_cert_store_provider: Deferred>, blob_store: Deferred>, @@ -170,13 +172,13 @@ struct CliFactoryServices { node_code_translator: Deferred>, node_resolver: Deferred>, npm_resolver: Deferred>, - package_json_deps_provider: Deferred>, text_only_progress_bar: Deferred, type_checker: Deferred>, cjs_resolutions: Deferred>, cli_node_resolver: Deferred>, feature_checker: Deferred>, code_cache: Deferred>, + workspace_resolver: Deferred>, } pub struct CliFactory { @@ -304,19 +306,33 @@ impl CliFactory { } pub fn maybe_lockfile(&self) -> &Option> { - fn check_no_npm(lockfile: &CliLockfile, options: &CliOptions) -> bool { - if options.no_npm() { - return true; - } - // Deno doesn't yet understand npm workspaces and the package.json resolution - // may be in a different folder than the deno.json/lockfile. So for now, ignore - // any package.jsons that are in different folders - options - .maybe_package_json() - .map(|package_json| { - package_json.path.parent() != lockfile.filename.parent() + fn pkg_json_deps(maybe_pkg_json: Option<&PackageJson>) -> BTreeSet { + let Some(pkg_json) = maybe_pkg_json else { + return Default::default(); + }; + pkg_json + .resolve_local_package_json_deps() + .values() + .filter_map(|dep| dep.as_ref().ok()) + .filter_map(|dep| match dep { + PackageJsonDepValue::Req(req) => Some(req), + PackageJsonDepValue::Workspace(_) => None, }) - .unwrap_or(false) + .map(|r| format!("npm:{}", r)) + .collect() + } + + fn deno_json_deps( + maybe_deno_json: Option<&ConfigFile>, + ) -> BTreeSet { + maybe_deno_json + .map(|c| { + crate::args::deno_json::deno_json_deps(c) + .into_iter() + .map(|req| req.to_string()) + .collect() + }) + .unwrap_or_default() } self.services.lockfile.get_or_init(|| { @@ -324,67 +340,52 @@ impl CliFactory { // initialize the lockfile with the workspace's configuration if let Some(lockfile) = &maybe_lockfile { - let no_npm = check_no_npm(lockfile, &self.options); - let package_json_deps = (!no_npm) - .then(|| { - self - .package_json_deps_provider() - .reqs() - .map(|reqs| { - reqs.into_iter().map(|s| format!("npm:{}", s)).collect() - }) - .unwrap_or_default() - }) - .unwrap_or_default(); - let config = match self.options.maybe_workspace_config() { - Some(workspace_config) => deno_lockfile::WorkspaceConfig { - root: WorkspaceMemberConfig { - package_json_deps, - dependencies: deno_json_deps( - self.options.maybe_config_file().as_ref().unwrap(), - ) - .into_iter() - .map(|req| req.to_string()) - .collect(), - }, - members: workspace_config - .members - .iter() - .map(|member| { - ( - member.package_name.clone(), - WorkspaceMemberConfig { - package_json_deps: Default::default(), - dependencies: deno_json_deps(&member.config_file) - .into_iter() - .map(|req| req.to_string()) - .collect(), - }, - ) - }) - .collect(), - }, - None => deno_lockfile::WorkspaceConfig { - root: WorkspaceMemberConfig { - package_json_deps, - dependencies: self - .options - .maybe_config_file() - .as_ref() - .map(|config| { - deno_json_deps(config) - .into_iter() - .map(|req| req.to_string()) - .collect() - }) - .unwrap_or_default(), - }, - members: Default::default(), + let (root_url, root_folder) = self.options.workspace.root_folder(); + let config = deno_lockfile::WorkspaceConfig { + root: WorkspaceMemberConfig { + package_json_deps: pkg_json_deps(root_folder.pkg_json.as_deref()), + dependencies: deno_json_deps(root_folder.deno_json.as_deref()), }, + members: self + .options + .workspace + .config_folders() + .iter() + .filter(|(folder_url, _)| *folder_url != root_url) + .filter_map(|(folder_url, folder)| { + Some(( + { + // should never be None here, but just ignore members that + // do fail for this + let mut relative_path = root_url.make_relative(folder_url)?; + if relative_path.ends_with('/') { + // make it slightly cleaner by removing the trailing slash + relative_path.pop(); + } + relative_path + }, + { + let config = WorkspaceMemberConfig { + package_json_deps: pkg_json_deps( + folder.pkg_json.as_deref(), + ), + dependencies: deno_json_deps(folder.deno_json.as_deref()), + }; + if config.package_json_deps.is_empty() + && config.dependencies.is_empty() + { + // exclude empty workspace members + return None; + } + config + }, + )) + }) + .collect(), }; lockfile.set_workspace_config( deno_lockfile::SetWorkspaceConfigOptions { - no_npm, + no_npm: self.options.no_npm(), no_config: self.options.no_config(), config, }, @@ -437,8 +438,9 @@ impl CliFactory { cache_setting: self.options.cache_setting(), text_only_progress_bar: self.text_only_progress_bar().clone(), maybe_node_modules_path: self.options.node_modules_dir_path().cloned(), - package_json_deps_provider: - self.package_json_deps_provider().clone(), + package_json_deps_provider: Arc::new(PackageJsonInstallDepsProvider::from_workspace( + &self.options.workspace, + )), npm_system_info: self.options.npm_system_info(), npmrc: self.options.npmrc().clone() }) @@ -447,28 +449,29 @@ impl CliFactory { .await } - pub fn package_json_deps_provider(&self) -> &Arc { - self.services.package_json_deps_provider.get_or_init(|| { - Arc::new(PackageJsonDepsProvider::new( - self.options.maybe_package_json_deps(), - )) - }) - } - - pub async fn maybe_import_map( + pub async fn workspace_resolver( &self, - ) -> Result<&Option>, AnyError> { + ) -> Result<&Arc, AnyError> { self .services - .maybe_import_map + .workspace_resolver .get_or_try_init_async(async { - Ok( - self - .options - .resolve_import_map(self.file_fetcher()?) - .await? - .map(Arc::new), - ) + let resolver = self + .options + .create_workspace_resolver(self.file_fetcher()?) + .await?; + if !resolver.diagnostics().is_empty() { + warn!( + "Import map diagnostics:\n{}", + resolver + .diagnostics() + .iter() + .map(|d| format!(" - {d}")) + .collect::>() + .join("\n") + ); + } + Ok(Arc::new(resolver)) }) .await } @@ -491,17 +494,15 @@ impl CliFactory { } else { Some(self.npm_resolver().await?.clone()) }, - package_json_deps_provider: self - .package_json_deps_provider() - .clone(), - maybe_jsx_import_source_config: self - .options - .to_maybe_jsx_import_source_config()?, - maybe_import_map: self.maybe_import_map().await?.clone(), - maybe_vendor_dir: self.options.vendor_dir_path(), + workspace_resolver: self.workspace_resolver().await?.clone(), bare_node_builtins_enabled: self .options .unstable_bare_node_builtins(), + maybe_jsx_import_source_config: self + .options + .workspace + .to_maybe_jsx_import_source_config()?, + maybe_vendor_dir: self.options.vendor_dir_path(), }))) } .boxed_local(), @@ -759,7 +760,6 @@ impl CliFactory { self.http_client_provider(), self.npm_resolver().await?.as_ref(), self.options.npm_system_info(), - self.package_json_deps_provider(), )) } @@ -885,7 +885,6 @@ impl CliFactory { .unsafely_ignore_certificate_errors() .clone(), unstable: self.options.legacy_unstable_flag(), - maybe_root_package_json_deps: self.options.maybe_package_json_deps(), create_hmr_runner, create_coverage_collector, }) diff --git a/cli/graph_container.rs b/cli/graph_container.rs index 40ccda9b20..d439f93609 100644 --- a/cli/graph_container.rs +++ b/cli/graph_container.rs @@ -98,7 +98,7 @@ impl MainModuleGraphContainer { &self, files: &[String], ) -> Result, AnyError> { - let excludes = self.cli_options.resolve_config_excludes()?; + let excludes = self.cli_options.workspace.resolve_config_excludes()?; Ok( files .iter() diff --git a/cli/graph_util.rs b/cli/graph_util.rs index f1e98e7c66..2f9ee8d93d 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::args::config_to_deno_graph_workspace_member; use crate::args::jsr_url; use crate::args::CliLockfile; use crate::args::CliOptions; @@ -18,12 +19,13 @@ 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_emit::LoaderChecksum; use deno_graph::JsrLoadError; use deno_graph::ModuleLoadError; +use deno_graph::WorkspaceFastCheckOption; use deno_runtime::fs_util::specifier_to_file_path; -use deno_config::WorkspaceMemberConfig; use deno_core::anyhow::bail; use deno_core::error::custom_error; use deno_core::error::AnyError; @@ -240,12 +242,12 @@ impl ModuleGraphCreator { pub async fn create_and_validate_publish_graph( &self, - packages: &[WorkspaceMemberConfig], + package_configs: &[JsrPackageConfig], build_fast_check_graph: bool, ) -> Result { let mut roots = Vec::new(); - for package in packages { - roots.extend(package.config_file.resolve_export_value_urls()?); + for package_config in package_configs { + roots.extend(package_config.config_file.resolve_export_value_urls()?); } let mut graph = self .create_graph_with_options(CreateGraphOptions { @@ -260,10 +262,16 @@ impl ModuleGraphCreator { self.type_check_graph(graph.clone()).await?; } if build_fast_check_graph { + let fast_check_workspace_members = package_configs + .iter() + .map(|p| config_to_deno_graph_workspace_member(&p.config_file)) + .collect::, _>>()?; self.module_graph_builder.build_fast_check_graph( &mut graph, BuildFastCheckGraphOptions { - workspace_fast_check: true, + workspace_fast_check: WorkspaceFastCheckOption::Enabled( + &fast_check_workspace_members, + ), }, )?; } @@ -340,10 +348,10 @@ impl ModuleGraphCreator { } } -pub struct BuildFastCheckGraphOptions { +pub struct BuildFastCheckGraphOptions<'a> { /// Whether to do fast check on workspace members. This /// is mostly only useful when publishing. - pub workspace_fast_check: bool, + pub workspace_fast_check: deno_graph::WorkspaceFastCheckOption<'a>, } pub struct ModuleGraphBuilder { @@ -622,7 +630,10 @@ impl ModuleGraphBuilder { } log::debug!("Building fast check graph"); - let fast_check_cache = if !options.workspace_fast_check { + let fast_check_cache = if matches!( + options.workspace_fast_check, + deno_graph::WorkspaceFastCheckOption::Disabled + ) { Some(cache::FastCheckCache::new(self.caches.fast_check_db())) } else { None @@ -631,11 +642,6 @@ impl ModuleGraphBuilder { let cli_resolver = &self.resolver; let graph_resolver = cli_resolver.as_graph_resolver(); let graph_npm_resolver = cli_resolver.create_graph_npm_resolver(); - let workspace_members = if options.workspace_fast_check { - Some(self.options.resolve_deno_graph_workspace_members()?) - } else { - None - }; graph.build_fast_check_type_graph( deno_graph::BuildFastCheckTypeGraphOptions { @@ -645,11 +651,7 @@ impl ModuleGraphBuilder { module_parser: Some(&parser), resolver: Some(graph_resolver), npm_resolver: Some(&graph_npm_resolver), - workspace_fast_check: if let Some(members) = &workspace_members { - deno_graph::WorkspaceFastCheckOption::Enabled(members) - } else { - deno_graph::WorkspaceFastCheckOption::Disabled - }, + workspace_fast_check: options.workspace_fast_check, }, ); Ok(()) diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index e1f3e32071..4b96511c03 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -16,7 +16,6 @@ use crate::util::fs::canonicalize_path_maybe_not_exists; use deno_ast::MediaType; use deno_config::FmtOptionsConfig; use deno_config::TsConfig; -use deno_core::anyhow::anyhow; use deno_core::normalize_path; use deno_core::serde::de::DeserializeOwned; use deno_core::serde::Deserialize; @@ -27,6 +26,8 @@ use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; use deno_lint::linter::LintConfig; use deno_npm::npm_rc::ResolvedNpmRc; +use deno_runtime::deno_fs::DenoConfigFsAdapter; +use deno_runtime::deno_fs::RealFs; use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::fs_util::specifier_to_file_path; @@ -935,7 +936,7 @@ impl Config { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { let config_file = self.tree.config_file_for_specifier(specifier); if let Some(cf) = config_file { - if let Ok(files) = cf.to_files_config() { + if let Ok(files) = cf.to_exclude_files_config() { if !files.matches_specifier(specifier) { return false; } @@ -952,7 +953,7 @@ impl Config { specifier: &ModuleSpecifier, ) -> bool { if let Some(cf) = self.tree.config_file_for_specifier(specifier) { - if let Some(options) = cf.to_test_config().ok().flatten() { + if let Ok(options) = cf.to_test_config() { if !options.files.matches_specifier(specifier) { return false; } @@ -1135,8 +1136,9 @@ impl ConfigData { ) -> Self { if let Some(specifier) = config_file_specifier { match ConfigFile::from_specifier( + &DenoConfigFsAdapter::new(&RealFs), specifier.clone(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) { Ok(config_file) => { lsp_log!( @@ -1230,13 +1232,7 @@ impl ConfigData { .and_then(|config_file| { config_file .to_fmt_config() - .and_then(|o| { - let base_path = config_file - .specifier - .to_file_path() - .map_err(|_| anyhow!("Invalid base path."))?; - FmtOptions::resolve(o, None, &base_path) - }) + .and_then(|o| FmtOptions::resolve(o, &Default::default(), None)) .inspect_err(|err| { lsp_warn!(" Couldn't read formatter configuration: {}", err) }) @@ -1264,13 +1260,7 @@ impl ConfigData { .and_then(|config_file| { config_file .to_lint_config() - .and_then(|o| { - let base_path = config_file - .specifier - .to_file_path() - .map_err(|_| anyhow!("Invalid base path."))?; - LintOptions::resolve(o, None, &base_path) - }) + .and_then(|o| LintOptions::resolve(o, Default::default(), None)) .inspect_err(|err| { lsp_warn!(" Couldn't read lint configuration: {}", err) }) @@ -2115,7 +2105,7 @@ mod tests { ConfigFile::new( "{}", root_uri.join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) @@ -2173,7 +2163,7 @@ mod tests { }) .to_string(), root_uri.join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) @@ -2199,7 +2189,7 @@ mod tests { }) .to_string(), root_uri.join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) @@ -2217,7 +2207,7 @@ mod tests { }) .to_string(), root_uri.join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 27983867a6..9b500567d4 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1655,7 +1655,7 @@ mod tests { let config_file = ConfigFile::new( json_string, base_url, - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(); config.tree.inject_config_file(config_file).await; diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 0d9cd4fbbf..48cfebfcc4 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -1751,7 +1751,7 @@ console.log(b, "hello deno"); }) .to_string(), config.root_uri().unwrap().join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) @@ -1795,7 +1795,7 @@ console.log(b, "hello deno"); }) .to_string(), config.root_uri().unwrap().join("deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 25782b95c4..cfc58439d5 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2,6 +2,8 @@ use base64::Engine; use deno_ast::MediaType; +use deno_config::workspace::Workspace; +use deno_config::workspace::WorkspaceDiscoverOptions; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::resolve_url; @@ -13,6 +15,7 @@ use deno_core::url; use deno_core::ModuleSpecifier; use deno_graph::GraphKind; use deno_graph::Resolution; +use deno_runtime::deno_fs::DenoConfigFsAdapter; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_semver::jsr::JsrPackageReqReference; @@ -3549,6 +3552,24 @@ impl Inner { } let workspace_settings = self.config.workspace_settings(); + let initial_cwd = config_data + .and_then(|d| d.scope.to_file_path().ok()) + .unwrap_or_else(|| self.initial_cwd.clone()); + // todo: we need a way to convert config data to a Workspace + let workspace = Arc::new(Workspace::discover( + deno_config::workspace::WorkspaceDiscoverStart::Dirs(&[ + initial_cwd.clone() + ]), + &WorkspaceDiscoverOptions { + fs: &DenoConfigFsAdapter::new(&deno_runtime::deno_fs::RealFs), + pkg_json_cache: None, + config_parse_options: deno_config::ConfigParseOptions { + include_task_comments: false, + }, + additional_config_file_names: &[], + discover_pkg_json: true, + }, + )?); let cli_options = CliOptions::new( Flags { cache_path: Some(self.cache.deno_dir().root.clone()), @@ -3572,13 +3593,12 @@ impl Inner { type_check_mode: crate::args::TypeCheckMode::Local, ..Default::default() }, - self.initial_cwd.clone(), - config_data.and_then(|d| d.config_file.as_deref().cloned()), + initial_cwd, config_data.and_then(|d| d.lockfile.clone()), - config_data.and_then(|d| d.package_json.clone()), config_data .and_then(|d| d.npmrc.clone()) .unwrap_or_else(create_default_npmrc), + workspace, force_global_cache, )?; diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 5cf7f82b15..18d22afade 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -1,9 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::args::create_default_npmrc; -use crate::args::package_json; use crate::args::CacheSetting; use crate::args::CliLockfile; +use crate::args::PackageJsonInstallDepsProvider; use crate::graph_util::CliJsrUrlProvider; use crate::http_util::HttpClientProvider; use crate::lsp::config::Config; @@ -26,6 +26,8 @@ use crate::util::progress_bar::ProgressBarStyle; use dashmap::DashMap; use deno_ast::MediaType; use deno_cache_dir::HttpCache; +use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::WorkspaceResolver; use deno_core::error::AnyError; use deno_core::url::Url; use deno_graph::source::Resolver; @@ -43,7 +45,6 @@ use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use indexmap::IndexMap; -use package_json::PackageJsonDepsProvider; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -460,13 +461,10 @@ async fn create_npm_resolver( text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly), maybe_node_modules_path: config_data .and_then(|d| d.node_modules_dir.clone()), - package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( - config_data - .and_then(|d| d.package_json.as_ref()) - .map(|package_json| { - package_json.resolve_local_package_json_version_reqs() - }), - )), + // only used for top level install, so we can ignore this + package_json_deps_provider: Arc::new( + PackageJsonInstallDepsProvider::empty(), + ), npmrc: config_data .and_then(|d| d.npmrc.clone()) .unwrap_or_else(create_default_npmrc), @@ -504,16 +502,22 @@ fn create_graph_resolver( Arc::new(CliGraphResolver::new(CliGraphResolverOptions { node_resolver: node_resolver.cloned(), npm_resolver: npm_resolver.cloned(), - package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( + workspace_resolver: Arc::new(WorkspaceResolver::new_raw( + config_data.and_then(|d| d.import_map.as_ref().map(|i| (**i).clone())), config_data - .and_then(|d| d.package_json.as_ref()) - .map(|package_json| { - package_json.resolve_local_package_json_version_reqs() - }), + .and_then(|d| d.package_json.clone()) + .into_iter() + .collect(), + if config_data.map(|d| d.byonm).unwrap_or(false) { + PackageJsonDepResolution::Disabled + } else { + // todo(dsherret): this should also be disabled for when using + // auto-install with a node_modules directory + PackageJsonDepResolution::Enabled + }, )), maybe_jsx_import_source_config: config_file .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()), - maybe_import_map: config_data.and_then(|d| d.import_map.clone()), maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()), bare_node_builtins_enabled: config_file .map(|cf| cf.has_unstable("bare-node-builtins")) diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index bab9766eb4..cc88a08112 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -5405,7 +5405,7 @@ mod tests { }) .to_string(), resolve_url("file:///deno.json").unwrap(), - &deno_config::ParseOptions::default(), + &deno_config::ConfigParseOptions::default(), ) .unwrap(), ) diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 0e81736e5b..4786b742f5 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -81,7 +81,8 @@ pub async fn load_top_level_deps(factory: &CliFactory) -> Result<(), AnyError> { } } // cache as many entries in the import map as we can - if let Some(import_map) = factory.maybe_import_map().await? { + let resolver = factory.workspace_resolver().await?; + if let Some(import_map) = resolver.maybe_import_map() { let roots = import_map .imports() .entries() @@ -510,7 +511,7 @@ impl .as_managed() .unwrap() // byonm won't create a Module::Npm .resolve_pkg_folder_from_deno_module(module.nv_reference.nv())?; - let maybe_resolution = self + self .shared .node_resolver .resolve_package_sub_path_from_deno_module( @@ -521,11 +522,8 @@ impl ) .with_context(|| { format!("Could not resolve '{}'.", module.nv_reference) - })?; - match maybe_resolution { - Some(res) => res.into_url(), - None => return Err(generic_error("not found")), - } + })? + .into_url() } Some(Module::Node(module)) => module.specifier.clone(), Some(Module::Js(module)) => module.specifier.clone(), diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs index 0d4b9d4d46..bbd5da8ec0 100644 --- a/cli/npm/byonm.rs +++ b/cli/npm/byonm.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; use std::sync::Arc; use deno_ast::ModuleSpecifier; +use deno_config::package_json::PackageJsonDepValue; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::serde_json; @@ -87,13 +88,22 @@ impl ByonmCliNpmResolver { req: &PackageReq, pkg_json: &PackageJson, ) -> Option { - let deps = pkg_json.resolve_local_package_json_version_reqs(); + let deps = pkg_json.resolve_local_package_json_deps(); for (key, value) in deps { if let Ok(value) = value { - if value.name == req.name - && value.version_req.intersects(&req.version_req) - { - return Some(key); + match value { + PackageJsonDepValue::Req(dep_req) => { + if dep_req.name == req.name + && dep_req.version_req.intersects(&req.version_req) + { + return Some(key); + } + } + PackageJsonDepValue::Workspace(_workspace) => { + if key == req.name && req.version_req.tag() == Some("workspace") { + return Some(key); + } + } } } } diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 393fc8632c..467703b052 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -29,7 +29,7 @@ use resolution::AddPkgReqsResult; use crate::args::CliLockfile; use crate::args::NpmProcessState; use crate::args::NpmProcessStateKind; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::cache::FastInsecureHasher; use crate::http_util::HttpClientProvider; use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs; @@ -66,7 +66,7 @@ pub struct CliNpmResolverManagedCreateOptions { pub text_only_progress_bar: crate::util::progress_bar::ProgressBar, pub maybe_node_modules_path: Option, pub npm_system_info: NpmSystemInfo, - pub package_json_deps_provider: Arc, + pub package_json_deps_provider: Arc, pub npmrc: Arc, } @@ -131,7 +131,7 @@ fn create_inner( npm_api: Arc, npm_cache: Arc, npm_rc: Arc, - package_json_deps_provider: Arc, + package_json_deps_provider: Arc, text_only_progress_bar: crate::util::progress_bar::ProgressBar, node_modules_dir_path: Option, npm_system_info: NpmSystemInfo, @@ -152,6 +152,7 @@ fn create_inner( let fs_resolver = create_npm_fs_resolver( fs.clone(), npm_cache.clone(), + &package_json_deps_provider, &text_only_progress_bar, resolution.clone(), tarball_cache.clone(), @@ -249,7 +250,7 @@ pub struct ManagedCliNpmResolver { maybe_lockfile: Option>, npm_api: Arc, npm_cache: Arc, - package_json_deps_provider: Arc, + package_json_deps_provider: Arc, resolution: Arc, tarball_cache: Arc, text_only_progress_bar: ProgressBar, @@ -273,7 +274,7 @@ impl ManagedCliNpmResolver { maybe_lockfile: Option>, npm_api: Arc, npm_cache: Arc, - package_json_deps_provider: Arc, + package_json_deps_provider: Arc, resolution: Arc, tarball_cache: Arc, text_only_progress_bar: ProgressBar, @@ -459,12 +460,14 @@ impl ManagedCliNpmResolver { pub async fn ensure_top_level_package_json_install( &self, ) -> Result { - let Some(reqs) = self.package_json_deps_provider.reqs() else { - return Ok(false); - }; if !self.top_level_install_flag.raise() { return Ok(false); // already did this } + let reqs = self.package_json_deps_provider.remote_pkg_reqs(); + if reqs.is_empty() { + return Ok(false); + } + // check if something needs resolving before bothering to load all // the package information (which is slow) if reqs @@ -477,8 +480,7 @@ impl ManagedCliNpmResolver { return Ok(false); // everything is already resolvable } - let reqs = reqs.into_iter().cloned().collect::>(); - self.add_package_reqs(&reqs).await.map(|_| true) + self.add_package_reqs(reqs).await.map(|_| true) } pub async fn cache_package_info( @@ -563,6 +565,7 @@ impl CliNpmResolver for ManagedCliNpmResolver { create_npm_fs_resolver( self.fs.clone(), self.npm_cache.clone(), + &self.package_json_deps_provider, &self.text_only_progress_bar, npm_resolution.clone(), self.tarball_cache.clone(), diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index d338720b67..e8fffa0cd1 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -15,6 +15,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; +use crate::args::PackageJsonInstallDepsProvider; use crate::cache::CACHE_PERM; use crate::npm::cache_dir::mixed_case_package_name_decode; use crate::util::fs::atomic_write_file_with_retries; @@ -57,6 +58,7 @@ use super::common::RegistryReadPermissionChecker; pub struct LocalNpmPackageResolver { cache: Arc, fs: Arc, + pkg_json_deps_provider: Arc, progress_bar: ProgressBar, resolution: Arc, tarball_cache: Arc, @@ -67,9 +69,11 @@ pub struct LocalNpmPackageResolver { } impl LocalNpmPackageResolver { + #[allow(clippy::too_many_arguments)] pub fn new( cache: Arc, fs: Arc, + pkg_json_deps_provider: Arc, progress_bar: ProgressBar, resolution: Arc, tarball_cache: Arc, @@ -79,6 +83,7 @@ impl LocalNpmPackageResolver { Self { cache, fs: fs.clone(), + pkg_json_deps_provider, progress_bar, resolution, tarball_cache, @@ -221,6 +226,7 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { sync_resolution_with_fs( &self.resolution.snapshot(), &self.cache, + &self.pkg_json_deps_provider, &self.progress_bar, &self.tarball_cache, &self.root_node_modules_path, @@ -244,12 +250,13 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver { async fn sync_resolution_with_fs( snapshot: &NpmResolutionSnapshot, cache: &Arc, + pkg_json_deps_provider: &PackageJsonInstallDepsProvider, progress_bar: &ProgressBar, tarball_cache: &Arc, root_node_modules_dir_path: &Path, system_info: &NpmSystemInfo, ) -> Result<(), AnyError> { - if snapshot.is_empty() { + if snapshot.is_empty() && pkg_json_deps_provider.workspace_pkgs().is_empty() { return Ok(()); // don't create the directory } @@ -475,6 +482,19 @@ async fn sync_resolution_with_fs( bin_entries.finish(snapshot, &bin_node_modules_dir_path)?; } + // 7. Create symlinks for the workspace packages + { + // todo(#24419): this is not exactly correct because it should + // install correctly for a workspace (potentially in sub directories), + // but this is good enough for a first pass + for workspace in pkg_json_deps_provider.workspace_pkgs() { + symlink_package_dir( + &workspace.pkg_dir, + &root_node_modules_dir_path.join(&workspace.alias), + )?; + } + } + setup_cache.save(); drop(single_process_lock); drop(pb_clear_guard); diff --git a/cli/npm/managed/resolvers/mod.rs b/cli/npm/managed/resolvers/mod.rs index 2d812a2be2..a7f5459160 100644 --- a/cli/npm/managed/resolvers/mod.rs +++ b/cli/npm/managed/resolvers/mod.rs @@ -10,6 +10,7 @@ use std::sync::Arc; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; +use crate::args::PackageJsonInstallDepsProvider; use crate::util::progress_bar::ProgressBar; pub use self::common::NpmPackageFsResolver; @@ -21,9 +22,11 @@ use super::cache::NpmCache; use super::cache::TarballCache; use super::resolution::NpmResolution; +#[allow(clippy::too_many_arguments)] pub fn create_npm_fs_resolver( fs: Arc, npm_cache: Arc, + pkg_json_deps_provider: &Arc, progress_bar: &ProgressBar, resolution: Arc, tarball_cache: Arc, @@ -34,6 +37,7 @@ pub fn create_npm_fs_resolver( Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new( npm_cache, fs, + pkg_json_deps_provider.clone(), progress_bar.clone(), resolution, tarball_cache, diff --git a/cli/resolver.rs b/cli/resolver.rs index 9305cd1c92..26cf16ba95 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -4,7 +4,10 @@ use async_trait::async_trait; use dashmap::DashMap; use dashmap::DashSet; use deno_ast::MediaType; -use deno_config::package_json::PackageJsonDeps; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::MappedResolutionError; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -30,14 +33,12 @@ use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; -use import_map::ImportMap; use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use crate::args::JsxImportSourceConfig; -use crate::args::PackageJsonDepsProvider; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::colors; use crate::node::CliNodeCodeTranslator; @@ -128,15 +129,31 @@ impl CliNodeResolver { referrer: &ModuleSpecifier, mode: NodeResolutionMode, ) -> Result { - let package_folder = self - .npm_resolver - .resolve_pkg_folder_from_deno_module_req(req_ref.req(), referrer)?; - let maybe_resolution = self.resolve_package_sub_path_from_deno_module( - &package_folder, + self.resolve_req_with_sub_path( + req_ref.req(), req_ref.sub_path(), referrer, mode, - )?; + ) + } + + pub fn resolve_req_with_sub_path( + &self, + req: &PackageReq, + sub_path: Option<&str>, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, + ) -> Result { + let package_folder = self + .npm_resolver + .resolve_pkg_folder_from_deno_module_req(req, referrer)?; + let maybe_resolution = self + .maybe_resolve_package_sub_path_from_deno_module( + &package_folder, + sub_path, + referrer, + mode, + )?; match maybe_resolution { Some(resolution) => Ok(resolution), None => { @@ -150,8 +167,9 @@ impl CliNodeResolver { } } Err(anyhow!( - "Failed resolving package subpath for '{}' in '{}'.", - req_ref, + "Failed resolving '{}{}' in '{}'.", + req, + sub_path.map(|s| format!("/{}", s)).unwrap_or_default(), package_folder.display() )) } @@ -164,6 +182,31 @@ impl CliNodeResolver { sub_path: Option<&str>, referrer: &ModuleSpecifier, mode: NodeResolutionMode, + ) -> Result { + self + .maybe_resolve_package_sub_path_from_deno_module( + package_folder, + sub_path, + referrer, + mode, + )? + .ok_or_else(|| { + anyhow!( + "Failed resolving '{}' in '{}'.", + sub_path + .map(|s| format!("/{}", s)) + .unwrap_or_else(|| ".".to_string()), + package_folder.display(), + ) + }) + } + + pub fn maybe_resolve_package_sub_path_from_deno_module( + &self, + package_folder: &Path, + sub_path: Option<&str>, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, ) -> Result, AnyError> { self.handle_node_resolve_result( self.node_resolver.resolve_package_subpath_from_deno_module( @@ -350,120 +393,39 @@ impl CjsResolutionStore { } } -/// Result of checking if a specifier is mapped via -/// an import map or package.json. -pub enum MappedResolution { - None, - PackageJson(ModuleSpecifier), - ImportMap(ModuleSpecifier), -} - -impl MappedResolution { - pub fn into_specifier(self) -> Option { - match self { - MappedResolution::None => Option::None, - MappedResolution::PackageJson(specifier) => Some(specifier), - MappedResolution::ImportMap(specifier) => Some(specifier), - } - } -} - -/// Resolver for specifiers that could be mapped via an -/// import map or package.json. -#[derive(Debug)] -pub struct MappedSpecifierResolver { - maybe_import_map: Option>, - package_json_deps_provider: Arc, -} - -impl MappedSpecifierResolver { - pub fn new( - maybe_import_map: Option>, - package_json_deps_provider: Arc, - ) -> Self { - Self { - maybe_import_map, - package_json_deps_provider, - } - } - - pub fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Result { - // attempt to resolve with the import map first - let maybe_import_map_err = match self - .maybe_import_map - .as_ref() - .map(|import_map| import_map.resolve(specifier, referrer)) - { - Some(Ok(value)) => return Ok(MappedResolution::ImportMap(value)), - Some(Err(err)) => Some(err), - None => None, - }; - - // then with package.json - if let Some(deps) = self.package_json_deps_provider.deps() { - if let Some(specifier) = resolve_package_json_dep(specifier, deps)? { - return Ok(MappedResolution::PackageJson(specifier)); - } - } - - // otherwise, surface the import map error or try resolving when has no import map - if let Some(err) = maybe_import_map_err { - Err(err.into()) - } else { - Ok(MappedResolution::None) - } - } -} - /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] pub struct CliGraphResolver { + node_resolver: Option>, + npm_resolver: Option>, sloppy_imports_resolver: Option, - mapped_specifier_resolver: MappedSpecifierResolver, + workspace_resolver: Arc, maybe_default_jsx_import_source: Option, maybe_default_jsx_import_source_types: Option, maybe_jsx_import_source_module: Option, maybe_vendor_specifier: Option, - node_resolver: Option>, - npm_resolver: Option>, found_package_json_dep_flag: AtomicFlag, bare_node_builtins_enabled: bool, } pub struct CliGraphResolverOptions<'a> { - pub sloppy_imports_resolver: Option, pub node_resolver: Option>, pub npm_resolver: Option>, - pub package_json_deps_provider: Arc, - pub maybe_jsx_import_source_config: Option, - pub maybe_import_map: Option>, - pub maybe_vendor_dir: Option<&'a PathBuf>, + pub sloppy_imports_resolver: Option, + pub workspace_resolver: Arc, pub bare_node_builtins_enabled: bool, + pub maybe_jsx_import_source_config: Option, + pub maybe_vendor_dir: Option<&'a PathBuf>, } impl CliGraphResolver { pub fn new(options: CliGraphResolverOptions) -> Self { - let is_byonm = options - .npm_resolver - .as_ref() - .map(|n| n.as_byonm().is_some()) - .unwrap_or(false); Self { + node_resolver: options.node_resolver, + npm_resolver: options.npm_resolver, sloppy_imports_resolver: options.sloppy_imports_resolver, - mapped_specifier_resolver: MappedSpecifierResolver::new( - options.maybe_import_map, - if is_byonm { - // don't resolve from the root package.json deps for byonm - Arc::new(PackageJsonDepsProvider::new(None)) - } else { - options.package_json_deps_provider - }, - ), + workspace_resolver: options.workspace_resolver, maybe_default_jsx_import_source: options .maybe_jsx_import_source_config .as_ref() @@ -478,8 +440,6 @@ impl CliGraphResolver { maybe_vendor_specifier: options .maybe_vendor_dir .and_then(|v| ModuleSpecifier::from_directory_path(v).ok()), - node_resolver: options.node_resolver, - npm_resolver: options.npm_resolver, found_package_json_dep_flag: Default::default(), bare_node_builtins_enabled: options.bare_node_builtins_enabled, } @@ -497,6 +457,7 @@ impl CliGraphResolver { } } + // todo(dsherret): if we returned structured errors from the NodeResolver we wouldn't need this fn check_surface_byonm_node_error( &self, specifier: &str, @@ -561,22 +522,92 @@ impl Resolver for CliGraphResolver { let referrer = &referrer_range.specifier; let result: Result<_, ResolveError> = self - .mapped_specifier_resolver + .workspace_resolver .resolve(specifier, referrer) - .map_err(|err| err.into()) - .and_then(|resolution| match resolution { - MappedResolution::ImportMap(specifier) => Ok(specifier), - MappedResolution::PackageJson(specifier) => { + .map_err(|err| match err { + MappedResolutionError::Specifier(err) => ResolveError::Specifier(err), + MappedResolutionError::ImportMap(err) => { + ResolveError::Other(err.into()) + } + }); + let result = match result { + Ok(resolution) => match resolution { + MappedResolution::Normal(specifier) + | MappedResolution::ImportMap(specifier) => Ok(specifier), + // todo(dsherret): for byonm it should do resolution solely based on + // the referrer and not the package.json + MappedResolution::PackageJson { + dep_result, + alias, + sub_path, + .. + } => { // found a specifier in the package.json, so mark that // we need to do an "npm install" later self.found_package_json_dep_flag.raise(); - Ok(specifier) + + dep_result + .as_ref() + .map_err(|e| ResolveError::Other(e.clone().into())) + .and_then(|dep| match dep { + PackageJsonDepValue::Req(req) => { + ModuleSpecifier::parse(&format!( + "npm:{}{}", + req, + sub_path.map(|s| format!("/{}", s)).unwrap_or_default() + )) + .map_err(|e| ResolveError::Other(e.into())) + } + PackageJsonDepValue::Workspace(version_req) => self + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_pkg_json_dep( + alias, + version_req, + ) + .map_err(|e| ResolveError::Other(e.into())) + .and_then(|pkg_folder| { + Ok( + self + .node_resolver + .as_ref() + .unwrap() + .resolve_package_sub_path_from_deno_module( + pkg_folder, + sub_path.as_deref(), + referrer, + to_node_mode(mode), + )? + .into_url(), + ) + }), + }) } - MappedResolution::None => { - deno_graph::resolve_import(specifier, &referrer_range.specifier) - .map_err(|err| err.into()) + }, + Err(err) => Err(err), + }; + + // check if it's an npm specifier that resolves to a workspace member + if let Some(node_resolver) = &self.node_resolver { + if let Ok(specifier) = &result { + if let Ok(req_ref) = NpmPackageReqReference::from_specifier(specifier) { + if let Some(pkg_folder) = self + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_npm_specifier(req_ref.req()) + { + return Ok( + node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_folder, + req_ref.sub_path(), + referrer, + to_node_mode(mode), + )? + .into_url(), + ); + } } - }); + } + } // do sloppy imports resolution if enabled let result = @@ -733,28 +764,6 @@ fn sloppy_imports_resolve( resolution.into_specifier().into_owned() } -fn resolve_package_json_dep( - specifier: &str, - deps: &PackageJsonDeps, -) -> Result, AnyError> { - for (bare_specifier, req_result) in deps { - if specifier.starts_with(bare_specifier) { - let path = &specifier[bare_specifier.len()..]; - if path.is_empty() || path.starts_with('/') { - let req = req_result.as_ref().map_err(|err| { - anyhow!( - "Parsing version constraints in the application-level package.json is more strict at the moment.\n\n{:#}", - err.clone() - ) - })?; - return Ok(Some(ModuleSpecifier::parse(&format!("npm:{req}{path}"))?)); - } - } - } - - Ok(None) -} - #[derive(Debug)] pub struct WorkerCliNpmGraphResolver<'a> { npm_resolver: Option<&'a Arc>, @@ -1266,72 +1275,10 @@ impl SloppyImportsResolver { #[cfg(test)] mod test { - use std::collections::BTreeMap; - use test_util::TestContext; use super::*; - #[test] - fn test_resolve_package_json_dep() { - fn resolve( - specifier: &str, - deps: &BTreeMap, - ) -> Result, String> { - let deps = deps - .iter() - .map(|(key, value)| (key.to_string(), Ok(value.clone()))) - .collect(); - resolve_package_json_dep(specifier, &deps) - .map(|s| s.map(|s| s.to_string())) - .map_err(|err| err.to_string()) - } - - let deps = BTreeMap::from([ - ( - "package".to_string(), - PackageReq::from_str("package@1.0").unwrap(), - ), - ( - "package-alias".to_string(), - PackageReq::from_str("package@^1.2").unwrap(), - ), - ( - "@deno/test".to_string(), - PackageReq::from_str("@deno/test@~0.2").unwrap(), - ), - ]); - - assert_eq!( - resolve("package", &deps).unwrap(), - Some("npm:package@1.0".to_string()), - ); - assert_eq!( - resolve("package/some_path.ts", &deps).unwrap(), - Some("npm:package@1.0/some_path.ts".to_string()), - ); - - assert_eq!( - resolve("@deno/test", &deps).unwrap(), - Some("npm:@deno/test@~0.2".to_string()), - ); - assert_eq!( - resolve("@deno/test/some_path.ts", &deps).unwrap(), - Some("npm:@deno/test@~0.2/some_path.ts".to_string()), - ); - // matches the start, but doesn't have the same length or a path - assert_eq!(resolve("@deno/testing", &deps).unwrap(), None,); - - // alias - assert_eq!( - resolve("package-alias", &deps).unwrap(), - Some("npm:package@^1.2".to_string()), - ); - - // non-existent bare specifier - assert_eq!(resolve("non-existent", &deps).unwrap(), None); - } - #[test] fn test_unstable_sloppy_imports() { fn resolve(specifier: &ModuleSpecifier) -> SloppyImportsResolution { diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index bfcae271b0..84e65fc771 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -604,7 +604,7 @@ } ] }, - "workspaces": { + "workspace": { "type": "array", "items": { "type": "string" diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 98af5fa771..bf035577c9 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; +use std::collections::VecDeque; use std::env::current_exe; use std::ffi::OsString; use std::fs; @@ -15,8 +16,8 @@ use std::path::PathBuf; use std::process::Command; use deno_ast::ModuleSpecifier; -use deno_config::package_json::PackageJsonDepValueParseError; -use deno_config::package_json::PackageJsonDeps; +use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::Workspace; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -26,9 +27,12 @@ use deno_core::futures::AsyncSeekExt; use deno_core::serde_json; use deno_core::url::Url; use deno_npm::NpmSystemInfo; +use deno_runtime::deno_node::PackageJson; use deno_semver::npm::NpmVersionReqParseError; use deno_semver::package::PackageReq; use deno_semver::VersionReqSpecifierParseError; +use eszip::EszipRelativeFileBaseUrl; +use indexmap::IndexMap; use log::Level; use serde::Deserialize; use serde::Serialize; @@ -36,7 +40,7 @@ use serde::Serialize; use crate::args::CaData; use crate::args::CliOptions; use crate::args::CompileFlags; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::args::PermissionFlags; use crate::args::UnstableConfig; use crate::cache::DenoDir; @@ -44,6 +48,8 @@ use crate::file_fetcher::FileFetcher; use crate::http_util::HttpClientProvider; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; +use crate::standalone::virtual_fs::VfsEntry; +use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -54,81 +60,30 @@ use super::virtual_fs::VirtualDirectory; const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; -#[derive(Serialize, Deserialize)] -enum SerializablePackageJsonDepValueParseError { - VersionReq(String), - Unsupported { scheme: String }, -} - -impl SerializablePackageJsonDepValueParseError { - pub fn from_err(err: PackageJsonDepValueParseError) -> Self { - match err { - PackageJsonDepValueParseError::VersionReq(err) => { - Self::VersionReq(err.source.to_string()) - } - PackageJsonDepValueParseError::Unsupported { scheme } => { - Self::Unsupported { scheme } - } - } - } - - pub fn into_err(self) -> PackageJsonDepValueParseError { - match self { - SerializablePackageJsonDepValueParseError::VersionReq(source) => { - PackageJsonDepValueParseError::VersionReq(NpmVersionReqParseError { - source: monch::ParseErrorFailureError::new(source), - }) - } - SerializablePackageJsonDepValueParseError::Unsupported { scheme } => { - PackageJsonDepValueParseError::Unsupported { scheme } - } - } - } -} - -#[derive(Serialize, Deserialize)] -pub struct SerializablePackageJsonDeps( - BTreeMap< - String, - Result, - >, -); - -impl SerializablePackageJsonDeps { - pub fn from_deps(deps: PackageJsonDeps) -> Self { - Self( - deps - .into_iter() - .map(|(name, req)| { - let res = - req.map_err(SerializablePackageJsonDepValueParseError::from_err); - (name, res) - }) - .collect(), - ) - } - - pub fn into_deps(self) -> PackageJsonDeps { - self - .0 - .into_iter() - .map(|(name, res)| (name, res.map_err(|err| err.into_err()))) - .collect() - } -} - #[derive(Deserialize, Serialize)] pub enum NodeModules { Managed { - /// Whether this uses a node_modules directory (true) or the global cache (false). - node_modules_dir: bool, - package_json_deps: Option, + /// Relative path for the node_modules directory in the vfs. + node_modules_dir: Option, }, Byonm { - package_json_deps: Option, + root_node_modules_dir: String, }, } +#[derive(Deserialize, Serialize)] +pub struct SerializedWorkspaceResolverImportMap { + pub specifier: String, + pub json: String, +} + +#[derive(Deserialize, Serialize)] +pub struct SerializedWorkspaceResolver { + pub import_map: Option, + pub package_jsons: BTreeMap, + pub pkg_json_resolution: PackageJsonDepResolution, +} + #[derive(Deserialize, Serialize)] pub struct Metadata { pub argv: Vec, @@ -140,8 +95,8 @@ pub struct Metadata { pub ca_stores: Option>, pub ca_data: Option>, pub unsafely_ignore_certificate_errors: Option>, - pub maybe_import_map: Option<(Url, String)>, - pub entrypoint: ModuleSpecifier, + pub workspace_resolver: SerializedWorkspaceResolver, + pub entrypoint_key: String, pub node_modules: Option, pub disable_deprecated_api_warning: bool, pub unstable_config: UnstableConfig, @@ -415,13 +370,13 @@ pub fn unpack_into_dir( fs::remove_file(&archive_path)?; Ok(exe_path) } + pub struct DenoCompileBinaryWriter<'a> { deno_dir: &'a DenoDir, file_fetcher: &'a FileFetcher, http_client_provider: &'a HttpClientProvider, npm_resolver: &'a dyn CliNpmResolver, npm_system_info: NpmSystemInfo, - package_json_deps_provider: &'a PackageJsonDepsProvider, } impl<'a> DenoCompileBinaryWriter<'a> { @@ -432,7 +387,6 @@ impl<'a> DenoCompileBinaryWriter<'a> { http_client_provider: &'a HttpClientProvider, npm_resolver: &'a dyn CliNpmResolver, npm_system_info: NpmSystemInfo, - package_json_deps_provider: &'a PackageJsonDepsProvider, ) -> Self { Self { deno_dir, @@ -440,7 +394,6 @@ impl<'a> DenoCompileBinaryWriter<'a> { http_client_provider, npm_resolver, npm_system_info, - package_json_deps_provider, } } @@ -448,7 +401,8 @@ impl<'a> DenoCompileBinaryWriter<'a> { &self, writer: &mut impl Write, eszip: eszip::EszipV2, - module_specifier: &ModuleSpecifier, + root_dir_url: EszipRelativeFileBaseUrl<'_>, + entrypoint: &ModuleSpecifier, compile_flags: &CompileFlags, cli_options: &CliOptions, ) -> Result<(), AnyError> { @@ -465,13 +419,13 @@ impl<'a> DenoCompileBinaryWriter<'a> { } set_windows_binary_to_gui(&mut original_binary)?; } - self .write_standalone_binary( writer, original_binary, eszip, - module_specifier, + root_dir_url, + entrypoint, cli_options, compile_flags, ) @@ -557,11 +511,13 @@ impl<'a> DenoCompileBinaryWriter<'a> { /// This functions creates a standalone deno binary by appending a bundle /// and magic trailer to the currently executing binary. + #[allow(clippy::too_many_arguments)] async fn write_standalone_binary( &self, writer: &mut impl Write, original_bin: Vec, mut eszip: eszip::EszipV2, + root_dir_url: EszipRelativeFileBaseUrl<'_>, entrypoint: &ModuleSpecifier, cli_options: &CliOptions, compile_flags: &CompileFlags, @@ -574,48 +530,60 @@ impl<'a> DenoCompileBinaryWriter<'a> { Some(CaData::Bytes(bytes)) => Some(bytes.clone()), None => None, }; - let maybe_import_map = cli_options - .resolve_import_map(self.file_fetcher) - .await? - .map(|import_map| (import_map.base_url().clone(), import_map.to_json())); - let (npm_vfs, npm_files, node_modules) = - match self.npm_resolver.as_inner() { - InnerCliNpmResolverRef::Managed(managed) => { - let snapshot = - managed.serialized_valid_snapshot_for_system(&self.npm_system_info); - if !snapshot.as_serialized().packages.is_empty() { - let (root_dir, files) = self.build_vfs()?.into_dir_and_files(); - eszip.add_npm_snapshot(snapshot); - ( - Some(root_dir), - files, - Some(NodeModules::Managed { - node_modules_dir: self - .npm_resolver - .root_node_modules_path() - .is_some(), - package_json_deps: self.package_json_deps_provider.deps().map( - |deps| SerializablePackageJsonDeps::from_deps(deps.clone()), - ), - }), - ) - } else { - (None, Vec::new(), None) - } - } - InnerCliNpmResolverRef::Byonm(_) => { - let (root_dir, files) = self.build_vfs()?.into_dir_and_files(); + let workspace_resolver = cli_options + .create_workspace_resolver(self.file_fetcher) + .await?; + let root_path = root_dir_url.inner().to_file_path().unwrap(); + let (npm_vfs, npm_files, node_modules) = match self.npm_resolver.as_inner() + { + InnerCliNpmResolverRef::Managed(managed) => { + let snapshot = + managed.serialized_valid_snapshot_for_system(&self.npm_system_info); + if !snapshot.as_serialized().packages.is_empty() { + let (root_dir, files) = self + .build_vfs(&root_path, cli_options)? + .into_dir_and_files(); + eszip.add_npm_snapshot(snapshot); ( Some(root_dir), files, - Some(NodeModules::Byonm { - package_json_deps: self.package_json_deps_provider.deps().map( - |deps| SerializablePackageJsonDeps::from_deps(deps.clone()), + Some(NodeModules::Managed { + node_modules_dir: self.npm_resolver.root_node_modules_path().map( + |path| { + root_dir_url + .specifier_key( + &ModuleSpecifier::from_directory_path(path).unwrap(), + ) + .into_owned() + }, ), }), ) + } else { + (None, Vec::new(), None) } - }; + } + InnerCliNpmResolverRef::Byonm(resolver) => { + let (root_dir, files) = self + .build_vfs(&root_path, cli_options)? + .into_dir_and_files(); + ( + Some(root_dir), + files, + Some(NodeModules::Byonm { + root_node_modules_dir: root_dir_url + .specifier_key( + &ModuleSpecifier::from_directory_path( + // will always be set for byonm + resolver.root_node_modules_path().unwrap(), + ) + .unwrap(), + ) + .into_owned(), + }), + ) + } + }; let metadata = Metadata { argv: compile_flags.args.clone(), @@ -629,8 +597,32 @@ impl<'a> DenoCompileBinaryWriter<'a> { log_level: cli_options.log_level(), ca_stores: cli_options.ca_stores().clone(), ca_data, - entrypoint: entrypoint.clone(), - maybe_import_map, + entrypoint_key: root_dir_url.specifier_key(entrypoint).into_owned(), + workspace_resolver: SerializedWorkspaceResolver { + import_map: workspace_resolver.maybe_import_map().map(|i| { + SerializedWorkspaceResolverImportMap { + specifier: if i.base_url().scheme() == "file" { + root_dir_url.specifier_key(i.base_url()).into_owned() + } else { + // just make a remote url local + "deno.json".to_string() + }, + json: i.to_json(), + } + }), + package_jsons: workspace_resolver + .package_jsons() + .map(|pkg_json| { + ( + root_dir_url + .specifier_key(&pkg_json.specifier()) + .into_owned(), + serde_json::to_value(pkg_json).unwrap(), + ) + }) + .collect(), + pkg_json_resolution: workspace_resolver.pkg_json_dep_resolution(), + }, node_modules, disable_deprecated_api_warning: cli_options .disable_deprecated_api_warning, @@ -653,7 +645,11 @@ impl<'a> DenoCompileBinaryWriter<'a> { ) } - fn build_vfs(&self) -> Result { + fn build_vfs( + &self, + root_path: &Path, + cli_options: &CliOptions, + ) -> Result { fn maybe_warn_different_system(system_info: &NpmSystemInfo) { if system_info != &NpmSystemInfo::default() { log::warn!("{} The node_modules directory may be incompatible with the target system.", crate::colors::yellow("Warning")); @@ -664,7 +660,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { InnerCliNpmResolverRef::Managed(npm_resolver) => { if let Some(node_modules_path) = npm_resolver.root_node_modules_path() { maybe_warn_different_system(&self.npm_system_info); - let mut builder = VfsBuilder::new(node_modules_path.clone())?; + let mut builder = VfsBuilder::new(root_path.to_path_buf())?; builder.add_dir_recursive(node_modules_path)?; Ok(builder) } else { @@ -678,23 +674,82 @@ impl<'a> DenoCompileBinaryWriter<'a> { npm_resolver.resolve_pkg_folder_from_pkg_id(&package.id)?; builder.add_dir_recursive(&folder)?; } - // overwrite the root directory's name to obscure the user's registry url - builder.set_root_dir_name("node_modules".to_string()); + + // Flatten all the registries folders into a single "node_modules/localhost" folder + // that will be used by denort when loading the npm cache. This avoids us exposing + // the user's private registry information and means we don't have to bother + // serializing all the different registry config into the binary. + builder.with_root_dir(|root_dir| { + root_dir.name = "node_modules".to_string(); + let mut new_entries = Vec::with_capacity(root_dir.entries.len()); + let mut localhost_entries = IndexMap::new(); + for entry in std::mem::take(&mut root_dir.entries) { + match entry { + VfsEntry::Dir(dir) => { + for entry in dir.entries { + log::debug!( + "Flattening {} into node_modules", + entry.name() + ); + if let Some(existing) = + localhost_entries.insert(entry.name().to_string(), entry) + { + panic!( + "Unhandled scenario where a duplicate entry was found: {:?}", + existing + ); + } + } + } + VfsEntry::File(_) | VfsEntry::Symlink(_) => { + new_entries.push(entry); + } + } + } + new_entries.push(VfsEntry::Dir(VirtualDirectory { + name: "localhost".to_string(), + entries: localhost_entries.into_iter().map(|(_, v)| v).collect(), + })); + // needs to be sorted by name + new_entries.sort_by(|a, b| a.name().cmp(b.name())); + root_dir.entries = new_entries; + }); + Ok(builder) } } - InnerCliNpmResolverRef::Byonm(npm_resolver) => { + InnerCliNpmResolverRef::Byonm(_) => { maybe_warn_different_system(&self.npm_system_info); - // the root_node_modules directory will always exist for byonm - let node_modules_path = npm_resolver.root_node_modules_path().unwrap(); - let parent_path = node_modules_path.parent().unwrap(); - let mut builder = VfsBuilder::new(parent_path.to_path_buf())?; - let package_json_path = parent_path.join("package.json"); - if package_json_path.exists() { - builder.add_file_at_path(&package_json_path)?; + let mut builder = VfsBuilder::new(root_path.to_path_buf())?; + for pkg_json in cli_options.workspace.package_jsons() { + builder.add_file_at_path(&pkg_json.path)?; } - if node_modules_path.exists() { - builder.add_dir_recursive(node_modules_path)?; + // traverse and add all the node_modules directories in the workspace + let mut pending_dirs = VecDeque::new(); + pending_dirs.push_back( + cli_options + .workspace + .root_folder() + .0 + .to_file_path() + .unwrap(), + ); + while let Some(pending_dir) = pending_dirs.pop_front() { + let entries = fs::read_dir(&pending_dir).with_context(|| { + format!("Failed reading: {}", pending_dir.display()) + })?; + for entry in entries { + let entry = entry?; + let path = entry.path(); + if !path.is_dir() { + continue; + } + if path.ends_with("node_modules") { + builder.add_dir_recursive(&path)?; + } else { + pending_dirs.push_back(path); + } + } } Ok(builder) } diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 24ba7c9dbe..cbd14db4fd 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -10,7 +10,7 @@ use crate::args::get_root_cert_store; use crate::args::npm_pkg_req_ref_to_binary_command; use crate::args::CaData; use crate::args::CacheSetting; -use crate::args::PackageJsonDepsProvider; +use crate::args::PackageJsonInstallDepsProvider; use crate::args::StorageKeyResolver; use crate::cache::Caches; use crate::cache::DenoDirProvider; @@ -25,7 +25,6 @@ use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::npm::NpmCacheDir; use crate::resolver::CjsResolutionStore; use crate::resolver::CliNodeResolver; -use crate::resolver::MappedSpecifierResolver; use crate::resolver::NpmModuleLoader; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -35,6 +34,10 @@ use crate::worker::CliMainWorkerOptions; use crate::worker::ModuleLoaderAndSourceMapGetter; use crate::worker::ModuleLoaderFactory; use deno_ast::MediaType; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::MappedResolutionError; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::type_error; @@ -48,6 +51,7 @@ use deno_core::ModuleSpecifier; use deno_core::ModuleType; use deno_core::RequestedModuleType; use deno_core::ResolutionKind; +use deno_npm::npm_rc::ResolvedNpmRc; use deno_runtime::deno_fs; use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::NodeResolutionMode; @@ -59,7 +63,9 @@ use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; +use eszip::EszipRelativeFileBaseUrl; use import_map::parse_from_json; +use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; @@ -75,9 +81,43 @@ use self::binary::load_npm_vfs; use self::binary::Metadata; use self::file_system::DenoCompileFileSystem; -struct SharedModuleLoaderState { +struct WorkspaceEszipModule { + specifier: ModuleSpecifier, + inner: eszip::Module, +} + +struct WorkspaceEszip { eszip: eszip::EszipV2, - mapped_specifier_resolver: MappedSpecifierResolver, + root_dir_url: ModuleSpecifier, +} + +impl WorkspaceEszip { + pub fn get_module( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + if specifier.scheme() == "file" { + let specifier_key = EszipRelativeFileBaseUrl::new(&self.root_dir_url) + .specifier_key(specifier); + let module = self.eszip.get_module(&specifier_key)?; + let specifier = self.root_dir_url.join(&module.specifier).unwrap(); + Some(WorkspaceEszipModule { + specifier, + inner: module, + }) + } else { + let module = self.eszip.get_module(specifier.as_str())?; + Some(WorkspaceEszipModule { + specifier: ModuleSpecifier::parse(&module.specifier).unwrap(), + inner: module, + }) + } + } +} + +struct SharedModuleLoaderState { + eszip: WorkspaceEszip, + workspace_resolver: WorkspaceResolver, node_resolver: Arc, npm_module_loader: Arc, } @@ -122,44 +162,92 @@ impl ModuleLoader for EmbeddedModuleLoader { }; } - let maybe_mapped = self - .shared - .mapped_specifier_resolver - .resolve(specifier, &referrer)? - .into_specifier(); + let mapped_resolution = + self.shared.workspace_resolver.resolve(specifier, &referrer); - // npm specifier - let specifier_text = maybe_mapped - .as_ref() - .map(|r| r.as_str()) - .unwrap_or(specifier); - if let Ok(reference) = NpmPackageReqReference::from_str(specifier_text) { - return self - .shared - .node_resolver - .resolve_req_reference( - &reference, + match mapped_resolution { + Ok(MappedResolution::PackageJson { + dep_result, + sub_path, + alias, + .. + }) => match dep_result.as_ref().map_err(|e| AnyError::from(e.clone()))? { + PackageJsonDepValue::Req(req) => self + .shared + .node_resolver + .resolve_req_with_sub_path( + req, + sub_path.as_deref(), + &referrer, + NodeResolutionMode::Execution, + ) + .map(|res| res.into_url()), + PackageJsonDepValue::Workspace(version_req) => { + let pkg_folder = self + .shared + .workspace_resolver + .resolve_workspace_pkg_json_folder_for_pkg_json_dep( + alias, + version_req, + )?; + Ok( + self + .shared + .node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_folder, + sub_path.as_deref(), + &referrer, + NodeResolutionMode::Execution, + )? + .into_url(), + ) + } + }, + Ok(MappedResolution::Normal(specifier)) + | Ok(MappedResolution::ImportMap(specifier)) => { + if let Ok(reference) = + NpmPackageReqReference::from_specifier(&specifier) + { + return self + .shared + .node_resolver + .resolve_req_reference( + &reference, + &referrer, + NodeResolutionMode::Execution, + ) + .map(|res| res.into_url()); + } + + if specifier.scheme() == "jsr" { + if let Some(module) = self.shared.eszip.get_module(&specifier) { + return Ok(module.specifier); + } + } + + self + .shared + .node_resolver + .handle_if_in_node_modules(specifier) + } + Err(err) + if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" => + { + // todo(dsherret): return a better error from node resolution so that + // we can more easily tell whether to surface it or not + let node_result = self.shared.node_resolver.resolve( + specifier, &referrer, NodeResolutionMode::Execution, - ) - .map(|res| res.into_url()); - } - - let specifier = match maybe_mapped { - Some(resolved) => resolved, - None => deno_core::resolve_import(specifier, referrer.as_str())?, - }; - - if specifier.scheme() == "jsr" { - if let Some(module) = self.shared.eszip.get_module(specifier.as_str()) { - return Ok(ModuleSpecifier::parse(&module.specifier).unwrap()); + ); + if let Ok(Some(res)) = node_result { + return Ok(res.into_url()); + } + Err(err.into()) } + Err(err) => Err(err.into()), } - - self - .shared - .node_resolver - .handle_if_in_node_modules(specifier) } fn load( @@ -215,27 +303,23 @@ impl ModuleLoader for EmbeddedModuleLoader { ); } - let Some(module) = - self.shared.eszip.get_module(original_specifier.as_str()) - else { + let Some(module) = self.shared.eszip.get_module(original_specifier) else { return deno_core::ModuleLoadResponse::Sync(Err(type_error(format!( "Module not found: {}", original_specifier )))); }; let original_specifier = original_specifier.clone(); - let found_specifier = - ModuleSpecifier::parse(&module.specifier).expect("invalid url in eszip"); deno_core::ModuleLoadResponse::Async( async move { - let code = module.source().await.ok_or_else(|| { + let code = module.inner.source().await.ok_or_else(|| { type_error(format!("Module not found: {}", original_specifier)) })?; let code = arc_u8_to_arc_str(code) .map_err(|_| type_error("Module source is not utf-8"))?; Ok(deno_core::ModuleSource::new_with_redirect( - match module.kind { + match module.inner.kind { eszip::ModuleKind::JavaScript => ModuleType::JavaScript, eszip::ModuleKind::Json => ModuleType::Json, eszip::ModuleKind::Jsonc => { @@ -247,7 +331,7 @@ impl ModuleLoader for EmbeddedModuleLoader { }, ModuleSourceCode::String(code.into()), &original_specifier, - &found_specifier, + &module.specifier, None, )) } @@ -324,10 +408,10 @@ pub async fn run( mut eszip: eszip::EszipV2, metadata: Metadata, ) -> Result { - let main_module = &metadata.entrypoint; let current_exe_path = std::env::current_exe().unwrap(); let current_exe_name = current_exe_path.file_name().unwrap().to_string_lossy(); + let maybe_cwd = std::env::current_dir().ok(); let deno_dir_provider = Arc::new(DenoDirProvider::new(None)); let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider { ca_stores: metadata.ca_stores, @@ -341,119 +425,109 @@ pub async fn run( )); // use a dummy npm registry url let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap(); - let root_path = std::env::temp_dir() - .join(format!("deno-compile-{}", current_exe_name)) - .join("node_modules"); - let npm_cache_dir = - NpmCacheDir::new(root_path.clone(), vec![npm_registry_url.clone()]); + let root_path = + std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name)); + let root_dir_url = ModuleSpecifier::from_directory_path(&root_path).unwrap(); + let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap(); + let root_node_modules_path = root_path.join("node_modules"); + let npm_cache_dir = NpmCacheDir::new( + root_node_modules_path.clone(), + vec![npm_registry_url.clone()], + ); let npm_global_cache_dir = npm_cache_dir.get_cache_location(); let cache_setting = CacheSetting::Only; - let (package_json_deps_provider, fs, npm_resolver, maybe_vfs_root) = - match metadata.node_modules { - Some(binary::NodeModules::Managed { - node_modules_dir, - package_json_deps, - }) => { - // this will always have a snapshot - let snapshot = eszip.take_npm_snapshot().unwrap(); - let vfs_root_dir_path = if node_modules_dir { - root_path - } else { - npm_cache_dir.root_dir().to_owned() - }; - let vfs = load_npm_vfs(vfs_root_dir_path.clone()) - .context("Failed to load npm vfs.")?; - let maybe_node_modules_path = if node_modules_dir { - Some(vfs.root().to_path_buf()) - } else { - None - }; - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new( - package_json_deps.map(|serialized| serialized.into_deps()), - )); - let fs = Arc::new(DenoCompileFileSystem::new(vfs)) - as Arc; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( - CliNpmResolverManagedCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some( - snapshot, - )), - maybe_lockfile: None, - fs: fs.clone(), - http_client_provider: http_client_provider.clone(), - npm_global_cache_dir, - cache_setting, - text_only_progress_bar: progress_bar, - maybe_node_modules_path, - package_json_deps_provider: package_json_deps_provider.clone(), - npm_system_info: Default::default(), - // Packages from different registries are already inlined in the ESZip, - // so no need to create actual `.npmrc` configuration. - npmrc: create_default_npmrc(), - }, - )) - .await?; - ( - package_json_deps_provider, - fs, - npm_resolver, - Some(vfs_root_dir_path), - ) - } - Some(binary::NodeModules::Byonm { package_json_deps }) => { - let vfs_root_dir_path = root_path; - let vfs = load_npm_vfs(vfs_root_dir_path.clone()) - .context("Failed to load npm vfs.")?; - let node_modules_path = vfs.root().join("node_modules"); - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new( - package_json_deps.map(|serialized| serialized.into_deps()), - )); - let fs = Arc::new(DenoCompileFileSystem::new(vfs)) - as Arc; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Byonm( - CliNpmResolverByonmCreateOptions { - fs: fs.clone(), - root_node_modules_dir: node_modules_path, - }, - )) - .await?; - ( - package_json_deps_provider, - fs, - npm_resolver, - Some(vfs_root_dir_path), - ) - } - None => { - let package_json_deps_provider = - Arc::new(PackageJsonDepsProvider::new(None)); - let fs = Arc::new(deno_fs::RealFs) as Arc; - let npm_resolver = - create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( - CliNpmResolverManagedCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), - maybe_lockfile: None, - fs: fs.clone(), - http_client_provider: http_client_provider.clone(), - npm_global_cache_dir, - cache_setting, - text_only_progress_bar: progress_bar, - maybe_node_modules_path: None, - package_json_deps_provider: package_json_deps_provider.clone(), - npm_system_info: Default::default(), - // Packages from different registries are already inlined in the ESZip, - // so no need to create actual `.npmrc` configuration. - npmrc: create_default_npmrc(), - }, - )) - .await?; - (package_json_deps_provider, fs, npm_resolver, None) - } - }; + let (fs, npm_resolver, maybe_vfs_root) = match metadata.node_modules { + Some(binary::NodeModules::Managed { node_modules_dir }) => { + // this will always have a snapshot + let snapshot = eszip.take_npm_snapshot().unwrap(); + let vfs_root_dir_path = if node_modules_dir.is_some() { + root_path.clone() + } else { + npm_cache_dir.root_dir().to_owned() + }; + let vfs = load_npm_vfs(vfs_root_dir_path.clone()) + .context("Failed to load npm vfs.")?; + let maybe_node_modules_path = node_modules_dir + .map(|node_modules_dir| vfs_root_dir_path.join(node_modules_dir)); + let fs = Arc::new(DenoCompileFileSystem::new(vfs)) + as Arc; + let npm_resolver = + create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( + CliNpmResolverManagedCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some( + snapshot, + )), + maybe_lockfile: None, + fs: fs.clone(), + http_client_provider: http_client_provider.clone(), + npm_global_cache_dir, + cache_setting, + text_only_progress_bar: progress_bar, + maybe_node_modules_path, + npm_system_info: Default::default(), + package_json_deps_provider: Arc::new( + // this is only used for installing packages, which isn't necessary with deno compile + PackageJsonInstallDepsProvider::empty(), + ), + // create an npmrc that uses the fake npm_registry_url to resolve packages + npmrc: Arc::new(ResolvedNpmRc { + default_config: deno_npm::npm_rc::RegistryConfigWithUrl { + registry_url: npm_registry_url.clone(), + config: Default::default(), + }, + scopes: Default::default(), + registry_configs: Default::default(), + }), + }, + )) + .await?; + (fs, npm_resolver, Some(vfs_root_dir_path)) + } + Some(binary::NodeModules::Byonm { + root_node_modules_dir, + }) => { + let vfs_root_dir_path = root_path.clone(); + let vfs = load_npm_vfs(vfs_root_dir_path.clone()) + .context("Failed to load vfs.")?; + let root_node_modules_dir = vfs.root().join(root_node_modules_dir); + let fs = Arc::new(DenoCompileFileSystem::new(vfs)) + as Arc; + let npm_resolver = create_cli_npm_resolver( + CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { + fs: fs.clone(), + root_node_modules_dir, + }), + ) + .await?; + (fs, npm_resolver, Some(vfs_root_dir_path)) + } + None => { + let fs = Arc::new(deno_fs::RealFs) as Arc; + let npm_resolver = + create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( + CliNpmResolverManagedCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), + maybe_lockfile: None, + fs: fs.clone(), + http_client_provider: http_client_provider.clone(), + npm_global_cache_dir, + cache_setting, + text_only_progress_bar: progress_bar, + maybe_node_modules_path: None, + npm_system_info: Default::default(), + package_json_deps_provider: Arc::new( + // this is only used for installing packages, which isn't necessary with deno compile + PackageJsonInstallDepsProvider::empty(), + ), + // Packages from different registries are already inlined in the ESZip, + // so no need to create actual `.npmrc` configuration. + npmrc: create_default_npmrc(), + }, + )) + .await?; + (fs, npm_resolver, None) + } + }; let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some(); let node_resolver = Arc::new(NodeResolver::new( @@ -471,9 +545,42 @@ pub async fn run( node_resolver.clone(), npm_resolver.clone().into_npm_resolver(), )); - let maybe_import_map = metadata.maybe_import_map.map(|(base, source)| { - Arc::new(parse_from_json(base, &source).unwrap().import_map) - }); + let workspace_resolver = { + let import_map = match metadata.workspace_resolver.import_map { + Some(import_map) => Some( + import_map::parse_from_json_with_options( + root_dir_url.join(&import_map.specifier).unwrap(), + &import_map.json, + import_map::ImportMapOptions { + address_hook: None, + expand_imports: true, + }, + )? + .import_map, + ), + None => None, + }; + let pkg_jsons = metadata + .workspace_resolver + .package_jsons + .into_iter() + .map(|(relative_path, json)| { + let path = root_dir_url + .join(&relative_path) + .unwrap() + .to_file_path() + .unwrap(); + let pkg_json = + deno_config::package_json::PackageJson::load_from_value(path, json); + Arc::new(pkg_json) + }) + .collect(); + WorkspaceResolver::new_raw( + import_map, + pkg_jsons, + metadata.workspace_resolver.pkg_json_resolution, + ) + }; let cli_node_resolver = Arc::new(CliNodeResolver::new( Some(cjs_resolutions.clone()), fs.clone(), @@ -482,11 +589,11 @@ pub async fn run( )); let module_loader_factory = StandaloneModuleLoaderFactory { shared: Arc::new(SharedModuleLoaderState { - eszip, - mapped_specifier_resolver: MappedSpecifierResolver::new( - maybe_import_map.clone(), - package_json_deps_provider.clone(), - ), + eszip: WorkspaceEszip { + eszip, + root_dir_url, + }, + workspace_resolver, node_resolver: cli_node_resolver.clone(), npm_module_loader: Arc::new(NpmModuleLoader::new( cjs_resolutions, @@ -498,7 +605,6 @@ pub async fn run( }; let permissions = { - let maybe_cwd = std::env::current_dir().ok(); let mut permissions = metadata.permissions.to_options(maybe_cwd.as_deref())?; // if running with an npm vfs, grant read access to it @@ -561,7 +667,7 @@ pub async fn run( is_npm_main: main_module.scheme() == "npm", skip_op_registration: true, location: metadata.location, - argv0: NpmPackageReqReference::from_specifier(main_module) + argv0: NpmPackageReqReference::from_specifier(&main_module) .ok() .map(|req_ref| npm_pkg_req_ref_to_binary_command(&req_ref)) .or(std::env::args().next()), @@ -571,7 +677,6 @@ pub async fn run( unsafely_ignore_certificate_errors: metadata .unsafely_ignore_certificate_errors, unstable: metadata.unstable_config.legacy_flag_enabled, - maybe_root_package_json_deps: package_json_deps_provider.deps().cloned(), create_hmr_runner: None, create_coverage_collector: None, }, @@ -592,11 +697,7 @@ pub async fn run( deno_core::JsRuntime::init_platform(None); let mut worker = worker_factory - .create_main_worker( - WorkerExecutionMode::Run, - main_module.clone(), - permissions, - ) + .create_main_worker(WorkerExecutionMode::Run, main_module, permissions) .await?; let exit_code = worker.run().await?; diff --git a/cli/standalone/virtual_fs.rs b/cli/standalone/virtual_fs.rs index 3e6823d506..ee91b9f7fe 100644 --- a/cli/standalone/virtual_fs.rs +++ b/cli/standalone/virtual_fs.rs @@ -12,6 +12,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; +use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -55,9 +56,8 @@ impl VfsBuilder { root_dir: VirtualDirectory { name: root_path .file_stem() - .unwrap() - .to_string_lossy() - .into_owned(), + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or("root".to_string()), entries: Vec::new(), }, root_path, @@ -67,13 +67,19 @@ impl VfsBuilder { }) } - pub fn set_root_dir_name(&mut self, name: String) { - self.root_dir.name = name; + pub fn with_root_dir( + &mut self, + with_root: impl FnOnce(&mut VirtualDirectory) -> R, + ) -> R { + with_root(&mut self.root_dir) } pub fn add_dir_recursive(&mut self, path: &Path) -> Result<(), AnyError> { - let path = canonicalize_path(path)?; - self.add_dir_recursive_internal(&path) + let target_path = canonicalize_path(path)?; + if path != target_path { + self.add_symlink(path, &target_path)?; + } + self.add_dir_recursive_internal(&target_path) } fn add_dir_recursive_internal( @@ -92,7 +98,7 @@ impl VfsBuilder { if file_type.is_dir() { self.add_dir_recursive_internal(&path)?; } else if file_type.is_file() { - self.add_file_at_path(&path)?; + self.add_file_at_path_not_symlink(&path)?; } else if file_type.is_symlink() { match util::fs::canonicalize_path(&path) { Ok(target) => { @@ -175,6 +181,17 @@ impl VfsBuilder { } pub fn add_file_at_path(&mut self, path: &Path) -> Result<(), AnyError> { + let target_path = canonicalize_path(path)?; + if target_path != path { + self.add_symlink(path, &target_path)?; + } + self.add_file_at_path_not_symlink(&target_path) + } + + pub fn add_file_at_path_not_symlink( + &mut self, + path: &Path, + ) -> Result<(), AnyError> { let file_bytes = std::fs::read(path) .with_context(|| format!("Reading {}", path.display()))?; self.add_file(path, file_bytes) @@ -195,7 +212,9 @@ impl VfsBuilder { let name = path.file_name().unwrap().to_string_lossy(); let data_len = data.len(); match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { - Ok(_) => unreachable!(), + Ok(_) => { + // already added, just ignore + } Err(insert_index) => { dir.entries.insert( insert_index, @@ -228,6 +247,10 @@ impl VfsBuilder { target.display() ); let dest = self.path_relative_root(target)?; + if dest == self.path_relative_root(path)? { + // it's the same, ignore + return Ok(()); + } let dir = self.add_dir(path.parent().unwrap())?; let name = path.file_name().unwrap().to_string_lossy(); match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 0378d6ae21..d801b908cd 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -407,7 +407,8 @@ pub async fn run_benchmarks( bench_flags: BenchFlags, ) -> Result<(), AnyError> { let cli_options = CliOptions::from_flags(flags)?; - let bench_options = cli_options.resolve_bench_options(bench_flags)?; + let workspace_bench_options = + cli_options.resolve_workspace_bench_options(&bench_flags); let factory = CliFactory::from_cli_options(Arc::new(cli_options)); let cli_options = factory.cli_options(); // Various bench files should not share the same permissions in terms of @@ -416,11 +417,21 @@ pub async fn run_benchmarks( let permissions = Permissions::from_options(&cli_options.permissions_options()?)?; - let specifiers = collect_specifiers( - bench_options.files, - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - )?; + let members_with_bench_options = + cli_options.resolve_bench_options_for_members(&bench_flags)?; + let specifiers = members_with_bench_options + .iter() + .map(|(_, bench_options)| { + collect_specifiers( + bench_options.files.clone(), + cli_options.vendor_dir_path().map(ToOwned::to_owned), + is_supported_bench_path, + ) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(); if specifiers.is_empty() { return Err(generic_error("No bench modules found")); @@ -429,7 +440,7 @@ pub async fn run_benchmarks( let main_graph_container = factory.main_module_graph_container().await?; main_graph_container.check_specifiers(&specifiers).await?; - if bench_options.no_run { + if workspace_bench_options.no_run { return Ok(()); } @@ -441,8 +452,8 @@ pub async fn run_benchmarks( &permissions, specifiers, BenchSpecifierOptions { - filter: TestFilter::from_flag(&bench_options.filter), - json: bench_options.json, + filter: TestFilter::from_flag(&workspace_bench_options.filter), + json: workspace_bench_options.json, log_level, }, ) @@ -472,24 +483,40 @@ pub async fn run_benchmarks_with_watch( let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_communicator.clone())?; let cli_options = factory.cli_options(); - let bench_options = cli_options.resolve_bench_options(bench_flags)?; + let workspace_bench_options = + cli_options.resolve_workspace_bench_options(&bench_flags); let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - if let Some(set) = &bench_options.files.include { - let watch_paths = set.base_paths(); - if !watch_paths.is_empty() { - let _ = watcher_communicator.watch_paths(watch_paths); - } - } let graph_kind = cli_options.type_check_mode().as_graph_kind(); let module_graph_creator = factory.module_graph_creator().await?; - - let bench_modules = collect_specifiers( - bench_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - )?; + let members_with_bench_options = + cli_options.resolve_bench_options_for_members(&bench_flags)?; + let watch_paths = members_with_bench_options + .iter() + .filter_map(|(_, bench_options)| { + bench_options + .files + .include + .as_ref() + .map(|set| set.base_paths()) + }) + .flatten() + .collect::>(); + let _ = watcher_communicator.watch_paths(watch_paths); + let collected_bench_modules = members_with_bench_options + .iter() + .map(|(_, bench_options)| { + collect_specifiers( + bench_options.files.clone(), + cli_options.vendor_dir_path().map(ToOwned::to_owned), + is_supported_bench_path, + ) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(); // Various bench files should not share the same permissions in terms of // `PermissionsContainer` - otherwise granting/revoking permissions in one @@ -498,7 +525,7 @@ pub async fn run_benchmarks_with_watch( Permissions::from_options(&cli_options.permissions_options()?)?; let graph = module_graph_creator - .create_graph(graph_kind, bench_modules) + .create_graph(graph_kind, collected_bench_modules.clone()) .await?; module_graph_creator.graph_valid(&graph)?; let bench_modules = &graph.roots; @@ -524,16 +551,10 @@ pub async fn run_benchmarks_with_watch( let worker_factory = Arc::new(factory.create_cli_main_worker_factory().await?); - // todo(dsherret): why are we collecting specifiers twice in a row? - // Seems like a perf bug. - let specifiers = collect_specifiers( - bench_options.files, - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - )? - .into_iter() - .filter(|specifier| bench_modules_to_reload.contains(specifier)) - .collect::>(); + let specifiers = collected_bench_modules + .into_iter() + .filter(|specifier| bench_modules_to_reload.contains(specifier)) + .collect::>(); factory .main_module_graph_container() @@ -541,7 +562,7 @@ pub async fn run_benchmarks_with_watch( .check_specifiers(&specifiers) .await?; - if bench_options.no_run { + if workspace_bench_options.no_run { return Ok(()); } @@ -551,8 +572,8 @@ pub async fn run_benchmarks_with_watch( &permissions, specifiers, BenchSpecifierOptions { - filter: TestFilter::from_flag(&bench_options.filter), - json: bench_options.json, + filter: TestFilter::from_flag(&workspace_bench_options.filter), + json: workspace_bench_options.json, log_level, }, ) diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 6eb7a071c5..4ec677f8f3 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -183,7 +183,7 @@ impl TypeChecker { self.module_graph_builder.build_fast_check_graph( &mut graph, BuildFastCheckGraphOptions { - workspace_fast_check: false, + workspace_fast_check: deno_graph::WorkspaceFastCheckOption::Disabled, }, )?; } diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index b7aa946914..e395c351b7 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -5,6 +5,7 @@ use crate::args::Flags; use crate::factory::CliFactory; use crate::http_util::HttpClientProvider; use crate::standalone::is_standalone_binary; +use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::generic_error; @@ -12,6 +13,7 @@ use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_graph::GraphKind; use deno_terminal::colors; +use eszip::EszipRelativeFileBaseUrl; use rand::Rng; use std::path::Path; use std::path::PathBuf; @@ -82,12 +84,24 @@ pub async fn compile( ts_config_for_emit.ts_config, )?; let parser = parsed_source_cache.as_capturing_parser(); + let root_dir_url = resolve_root_dir_from_specifiers( + cli_options.workspace.root_folder().0, + graph.specifiers().map(|(s, _)| s).chain( + cli_options + .node_modules_dir_path() + .and_then(|p| ModuleSpecifier::from_directory_path(p).ok()) + .iter(), + ), + ); + log::debug!("Binary root dir: {}", root_dir_url); + let root_dir_url = EszipRelativeFileBaseUrl::new(&root_dir_url); let eszip = eszip::EszipV2::from_graph(eszip::FromGraphOptions { graph, parser, transpile_options, emit_options, - relative_file_base: None, + // make all the modules relative to the root folder + relative_file_base: Some(root_dir_url), })?; log::info!( @@ -116,6 +130,7 @@ pub async fn compile( .write_bin( &mut file, eszip, + root_dir_url, &module_specifier, &compile_flags, cli_options, @@ -268,6 +283,68 @@ fn get_os_specific_filepath( } } +fn resolve_root_dir_from_specifiers<'a>( + starting_dir: &ModuleSpecifier, + specifiers: impl Iterator, +) -> ModuleSpecifier { + fn select_common_root<'a>(a: &'a str, b: &'a str) -> &'a str { + let min_length = a.len().min(b.len()); + + let mut last_slash = 0; + for i in 0..min_length { + if a.as_bytes()[i] == b.as_bytes()[i] && a.as_bytes()[i] == b'/' { + last_slash = i; + } else if a.as_bytes()[i] != b.as_bytes()[i] { + break; + } + } + + // Return the common root path up to the last common slash. + // This returns a slice of the original string 'a', up to and including the last matching '/'. + let common = &a[..=last_slash]; + if cfg!(windows) && common == "file:///" { + a + } else { + common + } + } + + fn is_file_system_root(url: &str) -> bool { + let Some(path) = url.strip_prefix("file:///") else { + return false; + }; + if cfg!(windows) { + let Some((_drive, path)) = path.split_once('/') else { + return true; + }; + path.is_empty() + } else { + path.is_empty() + } + } + + let mut found_dir = starting_dir.as_str(); + if !is_file_system_root(found_dir) { + for specifier in specifiers { + if specifier.scheme() == "file" { + found_dir = select_common_root(found_dir, specifier.as_str()); + } + } + } + let found_dir = if is_file_system_root(found_dir) { + found_dir + } else { + // include the parent dir name because it helps create some context + found_dir + .strip_suffix('/') + .unwrap_or(found_dir) + .rfind('/') + .map(|i| &found_dir[..i + 1]) + .unwrap_or(found_dir) + }; + ModuleSpecifier::parse(found_dir).unwrap() +} + #[cfg(test)] mod test { pub use super::*; @@ -342,4 +419,38 @@ mod test { run_test("C:\\my-exe.0.1.2", Some("windows"), "C:\\my-exe.0.1.2.exe"); run_test("my-exe-0.1.2", Some("linux"), "my-exe-0.1.2"); } + + #[test] + fn test_resolve_root_dir_from_specifiers() { + fn resolve(start: &str, specifiers: &[&str]) -> String { + let specifiers = specifiers + .iter() + .map(|s| ModuleSpecifier::parse(s).unwrap()) + .collect::>(); + resolve_root_dir_from_specifiers( + &ModuleSpecifier::parse(start).unwrap(), + specifiers.iter(), + ) + .to_string() + } + + assert_eq!(resolve("file:///a/b/c", &["file:///a/b/c/d"]), "file:///a/"); + assert_eq!( + resolve("file:///a/b/c/", &["file:///a/b/c/d"]), + "file:///a/b/" + ); + assert_eq!( + resolve("file:///a/b/c/", &["file:///a/b/c/d", "file:///a/b/c/e"]), + "file:///a/b/" + ); + assert_eq!(resolve("file:///", &["file:///a/b/c/d"]), "file:///"); + if cfg!(windows) { + assert_eq!(resolve("file:///c:/", &["file:///c:/test"]), "file:///c:/"); + // this will ignore the other one because it's on a separate drive + assert_eq!( + resolve("file:///c:/a/b/c/", &["file:///v:/a/b/c/d"]), + "file:///c:/a/b/" + ); + } + } } diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index f123fc55a2..79765a91d4 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -187,31 +187,32 @@ pub async fn doc(flags: Flags, doc_flags: DocFlags) -> Result<(), AnyError> { Default::default() }; - let rewrite_map = - if let Some(config_file) = cli_options.maybe_config_file().clone() { - let config = config_file.to_exports_config()?; + let rewrite_map = if let Some(config_file) = + cli_options.workspace.resolve_start_ctx().maybe_deno_json() + { + let config = config_file.to_exports_config()?; - let rewrite_map = config - .clone() - .into_map() - .into_keys() - .map(|key| { - Ok(( - config.get_resolved(&key)?.unwrap(), - key - .strip_prefix('.') - .unwrap_or(&key) - .strip_prefix('/') - .unwrap_or(&key) - .to_owned(), - )) - }) - .collect::, AnyError>>()?; + let rewrite_map = config + .clone() + .into_map() + .into_keys() + .map(|key| { + Ok(( + config.get_resolved(&key)?.unwrap(), + key + .strip_prefix('.') + .unwrap_or(&key) + .strip_prefix('/') + .unwrap_or(&key) + .to_owned(), + )) + }) + .collect::, AnyError>>()?; - Some(rewrite_map) - } else { - None - }; + Some(rewrite_map) + } else { + None + }; generate_docs_directory( doc_nodes_by_url, diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index b37a8e06b4..c16be9fb2d 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -13,6 +13,7 @@ use crate::args::FmtFlags; use crate::args::FmtOptions; use crate::args::FmtOptionsConfig; use crate::args::ProseWrap; +use crate::cache::Caches; use crate::colors; use crate::factory::CliFactory; use crate::util::diff::diff; @@ -20,6 +21,7 @@ use crate::util::file_watcher; use crate::util::fs::canonicalize_path; use crate::util::fs::FileCollector; use crate::util::path::get_extension; +use async_trait::async_trait; use deno_ast::ParsedSource; use deno_config::glob::FilePatterns; use deno_core::anyhow::anyhow; @@ -50,8 +52,11 @@ use crate::cache::IncrementalCache; pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { if fmt_flags.is_stdin() { let cli_options = CliOptions::from_flags(flags)?; - let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; + let start_ctx = cli_options.workspace.resolve_start_ctx(); + let fmt_options = + cli_options.resolve_fmt_options(&fmt_flags, &start_ctx)?; return format_stdin( + &fmt_flags, fmt_options, cli_options .ext_flag() @@ -70,42 +75,42 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { Ok(async move { let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); - let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; - let files = collect_fmt_files(cli_options, fmt_options.files.clone()) - .and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) + let caches = factory.caches()?; + let mut paths_with_options_batches = + resolve_paths_with_options_batches(cli_options, &fmt_flags)?; + + for paths_with_options in &mut paths_with_options_batches { + let _ = watcher_communicator + .watch_paths(paths_with_options.paths.clone()); + let files = std::mem::take(&mut paths_with_options.paths); + paths_with_options.paths = if let Some(paths) = &changed_paths { + if fmt_flags.check { + // check all files on any changed (https://github.com/denoland/deno/issues/12446) + files + .iter() + .any(|path| { + canonicalize_path(path) + .map(|path| paths.contains(&path)) + .unwrap_or(false) + }) + .then_some(files) + .unwrap_or_else(|| [].to_vec()) } else { - Ok(files) + files + .into_iter() + .filter(|path| { + canonicalize_path(path) + .map(|path| paths.contains(&path)) + .unwrap_or(false) + }) + .collect::>() } - })?; - let _ = watcher_communicator.watch_paths(files.clone()); - let refmt_files = if let Some(paths) = changed_paths { - if fmt_options.check { - // check all files on any changed (https://github.com/denoland/deno/issues/12446) - files - .iter() - .any(|path| { - canonicalize_path(path) - .map(|path| paths.contains(&path)) - .unwrap_or(false) - }) - .then_some(files) - .unwrap_or_else(|| [].to_vec()) } else { files - .into_iter() - .filter(|path| { - canonicalize_path(path) - .map(|path| paths.contains(&path)) - .unwrap_or(false) - }) - .collect::>() - } - } else { - files - }; - format_files(factory, fmt_options, refmt_files).await?; + }; + } + + format_files(caches, &fmt_flags, paths_with_options_batches).await?; Ok(()) }) @@ -114,43 +119,77 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { .await?; } else { let factory = CliFactory::from_flags(flags)?; + let caches = factory.caches()?; let cli_options = factory.cli_options(); - let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; - let files = collect_fmt_files(cli_options, fmt_options.files.clone()) - .and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; - format_files(factory, fmt_options, files).await?; + let paths_with_options_batches = + resolve_paths_with_options_batches(cli_options, &fmt_flags)?; + format_files(caches, &fmt_flags, paths_with_options_batches).await?; } Ok(()) } -async fn format_files( - factory: CliFactory, - fmt_options: FmtOptions, +struct PathsWithOptions { + base: PathBuf, paths: Vec, -) -> Result<(), AnyError> { - let caches = factory.caches()?; - let check = fmt_options.check; - let incremental_cache = Arc::new(IncrementalCache::new( - caches.fmt_incremental_cache_db(), - &fmt_options.options, - &paths, - )); - if check { - check_source_files(paths, fmt_options.options, incremental_cache.clone()) - .await?; - } else { - format_source_files(paths, fmt_options.options, incremental_cache.clone()) - .await?; + options: FmtOptions, +} + +fn resolve_paths_with_options_batches( + cli_options: &CliOptions, + fmt_flags: &FmtFlags, +) -> Result, AnyError> { + let members_fmt_options = + cli_options.resolve_fmt_options_for_members(fmt_flags)?; + let mut paths_with_options_batches = + Vec::with_capacity(members_fmt_options.len()); + for member_fmt_options in members_fmt_options { + let files = + collect_fmt_files(cli_options, member_fmt_options.files.clone())?; + if !files.is_empty() { + paths_with_options_batches.push(PathsWithOptions { + base: member_fmt_options.files.base.clone(), + paths: files, + options: member_fmt_options, + }); + } } - incremental_cache.wait_completion().await; - Ok(()) + if paths_with_options_batches.is_empty() { + return Err(generic_error("No target files found.")); + } + Ok(paths_with_options_batches) +} + +async fn format_files( + caches: &Arc, + fmt_flags: &FmtFlags, + paths_with_options_batches: Vec, +) -> Result<(), AnyError> { + let formatter: Box = if fmt_flags.check { + Box::new(CheckFormatter::default()) + } else { + Box::new(RealFormatter::default()) + }; + for paths_with_options in paths_with_options_batches { + log::debug!( + "Formatting {} file(s) in {}", + paths_with_options.paths.len(), + paths_with_options.base.display() + ); + let fmt_options = paths_with_options.options; + let paths = paths_with_options.paths; + let incremental_cache = Arc::new(IncrementalCache::new( + caches.fmt_incremental_cache_db(), + &fmt_options.options, + &paths, + )); + formatter + .handle_files(paths, fmt_options.options, incremental_cache.clone()) + .await?; + incremental_cache.wait_completion().await; + } + + formatter.finish() } fn collect_fmt_files( @@ -274,156 +313,190 @@ pub fn format_parsed_source( ) } -async fn check_source_files( - paths: Vec, - fmt_options: FmtOptionsConfig, - incremental_cache: Arc, -) -> Result<(), AnyError> { - let not_formatted_files_count = Arc::new(AtomicUsize::new(0)); - let checked_files_count = Arc::new(AtomicUsize::new(0)); +#[async_trait] +trait Formatter { + async fn handle_files( + &self, + paths: Vec, + fmt_options: FmtOptionsConfig, + incremental_cache: Arc, + ) -> Result<(), AnyError>; - // prevent threads outputting at the same time - let output_lock = Arc::new(Mutex::new(0)); + fn finish(&self) -> Result<(), AnyError>; +} - run_parallelized(paths, { - let not_formatted_files_count = not_formatted_files_count.clone(); - let checked_files_count = checked_files_count.clone(); - move |file_path| { - checked_files_count.fetch_add(1, Ordering::Relaxed); - let file_text = read_file_contents(&file_path)?.text; +#[derive(Default)] +struct CheckFormatter { + not_formatted_files_count: Arc, + checked_files_count: Arc, +} - // skip checking the file if we know it's formatted - if incremental_cache.is_file_same(&file_path, &file_text) { - return Ok(()); +#[async_trait] +impl Formatter for CheckFormatter { + async fn handle_files( + &self, + paths: Vec, + fmt_options: FmtOptionsConfig, + incremental_cache: Arc, + ) -> Result<(), AnyError> { + // prevent threads outputting at the same time + let output_lock = Arc::new(Mutex::new(0)); + + run_parallelized(paths, { + let not_formatted_files_count = self.not_formatted_files_count.clone(); + let checked_files_count = self.checked_files_count.clone(); + move |file_path| { + checked_files_count.fetch_add(1, Ordering::Relaxed); + let file_text = read_file_contents(&file_path)?.text; + + // skip checking the file if we know it's formatted + if incremental_cache.is_file_same(&file_path, &file_text) { + return Ok(()); + } + + match format_file(&file_path, &file_text, &fmt_options) { + Ok(Some(formatted_text)) => { + not_formatted_files_count.fetch_add(1, Ordering::Relaxed); + let _g = output_lock.lock(); + let diff = diff(&file_text, &formatted_text); + info!(""); + info!("{} {}:", colors::bold("from"), file_path.display()); + info!("{}", diff); + } + Ok(None) => { + // When checking formatting, only update the incremental cache when + // the file is the same since we don't bother checking for stable + // formatting here. Additionally, ensure this is done during check + // so that CIs that cache the DENO_DIR will get the benefit of + // incremental formatting + incremental_cache.update_file(&file_path, &file_text); + } + Err(e) => { + not_formatted_files_count.fetch_add(1, Ordering::Relaxed); + let _g = output_lock.lock(); + warn!("Error checking: {}", file_path.to_string_lossy()); + warn!( + "{}", + format!("{e}") + .split('\n') + .map(|l| { + if l.trim().is_empty() { + String::new() + } else { + format!(" {l}") + } + }) + .collect::>() + .join("\n") + ); + } + } + Ok(()) } + }) + .await?; - match format_file(&file_path, &file_text, &fmt_options) { - Ok(Some(formatted_text)) => { - not_formatted_files_count.fetch_add(1, Ordering::Relaxed); - let _g = output_lock.lock(); - let diff = diff(&file_text, &formatted_text); - info!(""); - info!("{} {}:", colors::bold("from"), file_path.display()); - info!("{}", diff); - } - Ok(None) => { - // When checking formatting, only update the incremental cache when - // the file is the same since we don't bother checking for stable - // formatting here. Additionally, ensure this is done during check - // so that CIs that cache the DENO_DIR will get the benefit of - // incremental formatting - incremental_cache.update_file(&file_path, &file_text); - } - Err(e) => { - not_formatted_files_count.fetch_add(1, Ordering::Relaxed); - let _g = output_lock.lock(); - warn!("Error checking: {}", file_path.to_string_lossy()); - warn!( - "{}", - format!("{e}") - .split('\n') - .map(|l| { - if l.trim().is_empty() { - String::new() - } else { - format!(" {l}") - } - }) - .collect::>() - .join("\n") - ); - } - } - Ok(()) - } - }) - .await?; - - let not_formatted_files_count = - not_formatted_files_count.load(Ordering::Relaxed); - let checked_files_count = checked_files_count.load(Ordering::Relaxed); - let checked_files_str = - format!("{} {}", checked_files_count, files_str(checked_files_count)); - if not_formatted_files_count == 0 { - info!("Checked {}", checked_files_str); Ok(()) - } else { - let not_formatted_files_str = files_str(not_formatted_files_count); - Err(generic_error(format!( - "Found {not_formatted_files_count} not formatted {not_formatted_files_str} in {checked_files_str}", - ))) + } + + fn finish(&self) -> Result<(), AnyError> { + let not_formatted_files_count = + self.not_formatted_files_count.load(Ordering::Relaxed); + let checked_files_count = self.checked_files_count.load(Ordering::Relaxed); + let checked_files_str = + format!("{} {}", checked_files_count, files_str(checked_files_count)); + if not_formatted_files_count == 0 { + info!("Checked {}", checked_files_str); + Ok(()) + } else { + let not_formatted_files_str = files_str(not_formatted_files_count); + Err(generic_error(format!( + "Found {not_formatted_files_count} not formatted {not_formatted_files_str} in {checked_files_str}", + ))) + } } } -async fn format_source_files( - paths: Vec, - fmt_options: FmtOptionsConfig, - incremental_cache: Arc, -) -> Result<(), AnyError> { - let formatted_files_count = Arc::new(AtomicUsize::new(0)); - let checked_files_count = Arc::new(AtomicUsize::new(0)); - let output_lock = Arc::new(Mutex::new(0)); // prevent threads outputting at the same time +#[derive(Default)] +struct RealFormatter { + formatted_files_count: Arc, + checked_files_count: Arc, +} - run_parallelized(paths, { - let formatted_files_count = formatted_files_count.clone(); - let checked_files_count = checked_files_count.clone(); - move |file_path| { - checked_files_count.fetch_add(1, Ordering::Relaxed); - let file_contents = read_file_contents(&file_path)?; +#[async_trait] +impl Formatter for RealFormatter { + async fn handle_files( + &self, + paths: Vec, + fmt_options: FmtOptionsConfig, + incremental_cache: Arc, + ) -> Result<(), AnyError> { + let output_lock = Arc::new(Mutex::new(0)); // prevent threads outputting at the same time - // skip formatting the file if we know it's formatted - if incremental_cache.is_file_same(&file_path, &file_contents.text) { - return Ok(()); + run_parallelized(paths, { + let formatted_files_count = self.formatted_files_count.clone(); + let checked_files_count = self.checked_files_count.clone(); + move |file_path| { + checked_files_count.fetch_add(1, Ordering::Relaxed); + let file_contents = read_file_contents(&file_path)?; + + // skip formatting the file if we know it's formatted + if incremental_cache.is_file_same(&file_path, &file_contents.text) { + return Ok(()); + } + + match format_ensure_stable( + &file_path, + &file_contents.text, + &fmt_options, + format_file, + ) { + Ok(Some(formatted_text)) => { + incremental_cache.update_file(&file_path, &formatted_text); + write_file_contents( + &file_path, + FileContents { + had_bom: file_contents.had_bom, + text: formatted_text, + }, + )?; + formatted_files_count.fetch_add(1, Ordering::Relaxed); + let _g = output_lock.lock(); + info!("{}", file_path.to_string_lossy()); + } + Ok(None) => { + incremental_cache.update_file(&file_path, &file_contents.text); + } + Err(e) => { + let _g = output_lock.lock(); + log::error!("Error formatting: {}", file_path.to_string_lossy()); + log::error!(" {e}"); + } + } + Ok(()) } + }) + .await?; + Ok(()) + } - match format_ensure_stable( - &file_path, - &file_contents.text, - &fmt_options, - format_file, - ) { - Ok(Some(formatted_text)) => { - incremental_cache.update_file(&file_path, &formatted_text); - write_file_contents( - &file_path, - FileContents { - had_bom: file_contents.had_bom, - text: formatted_text, - }, - )?; - formatted_files_count.fetch_add(1, Ordering::Relaxed); - let _g = output_lock.lock(); - info!("{}", file_path.to_string_lossy()); - } - Ok(None) => { - incremental_cache.update_file(&file_path, &file_contents.text); - } - Err(e) => { - let _g = output_lock.lock(); - log::error!("Error formatting: {}", file_path.to_string_lossy()); - log::error!(" {e}"); - } - } - Ok(()) - } - }) - .await?; + fn finish(&self) -> Result<(), AnyError> { + let formatted_files_count = + self.formatted_files_count.load(Ordering::Relaxed); + debug!( + "Formatted {} {}", + formatted_files_count, + files_str(formatted_files_count), + ); - let formatted_files_count = formatted_files_count.load(Ordering::Relaxed); - debug!( - "Formatted {} {}", - formatted_files_count, - files_str(formatted_files_count), - ); - - let checked_files_count = checked_files_count.load(Ordering::Relaxed); - info!( - "Checked {} {}", - checked_files_count, - files_str(checked_files_count) - ); - - Ok(()) + let checked_files_count = self.checked_files_count.load(Ordering::Relaxed); + info!( + "Checked {} {}", + checked_files_count, + files_str(checked_files_count) + ); + Ok(()) + } } /// When storing any formatted text in the incremental cache, we want @@ -491,14 +564,18 @@ fn format_ensure_stable( /// Format stdin and write result to stdout. /// Treats input as set by `--ext` flag. /// Compatible with `--check` flag. -fn format_stdin(fmt_options: FmtOptions, ext: &str) -> Result<(), AnyError> { +fn format_stdin( + fmt_flags: &FmtFlags, + fmt_options: FmtOptions, + ext: &str, +) -> Result<(), AnyError> { let mut source = String::new(); if stdin().read_to_string(&mut source).is_err() { bail!("Failed to read from stdin"); } let file_path = PathBuf::from(format!("_stdin.{ext}")); let formatted_text = format_file(&file_path, &source, &fmt_options.options)?; - if fmt_options.check { + if fmt_flags.check { #[allow(clippy::print_stdout)] if formatted_text.is_some() { println!("Not formatted stdin"); diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 76951b13dc..18a4bed57c 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -42,19 +42,20 @@ pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> { let module_graph_creator = factory.module_graph_creator().await?; let npm_resolver = factory.npm_resolver().await?; let maybe_lockfile = factory.maybe_lockfile(); - let maybe_imports_map = factory.maybe_import_map().await?; + let resolver = factory.workspace_resolver().await?; - let maybe_import_specifier = if let Some(imports_map) = maybe_imports_map { - if let Ok(imports_specifier) = - imports_map.resolve(&specifier, imports_map.base_url()) - { - Some(imports_specifier) + let maybe_import_specifier = + if let Some(import_map) = resolver.maybe_import_map() { + if let Ok(imports_specifier) = + import_map.resolve(&specifier, import_map.base_url()) + { + Some(imports_specifier) + } else { + None + } } else { None - } - } else { - None - }; + }; let specifier = match maybe_import_specifier { Some(specifier) => specifier, diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index 0d9868cf2b..e3f2844a7b 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -9,13 +9,21 @@ use deno_ast::ParsedSource; use deno_ast::SourceRange; use deno_ast::SourceTextInfo; use deno_config::glob::FilePatterns; +use deno_config::workspace::Workspace; +use deno_config::workspace::WorkspaceMemberContext; +use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; +use deno_core::futures::future::LocalBoxFuture; +use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; use deno_core::serde_json; +use deno_core::unsync::future::LocalFutureExt; +use deno_core::unsync::future::SharedLocal; use deno_graph::FastCheckDiagnostic; +use deno_graph::ModuleGraph; use deno_lint::diagnostic::LintDiagnostic; use deno_lint::linter::LintConfig; use deno_lint::linter::LintFileOptions; @@ -33,6 +41,7 @@ use std::io::stdin; use std::io::Read; use std::path::Path; use std::path::PathBuf; +use std::rc::Rc; use std::sync::Arc; use crate::args::CliOptions; @@ -41,9 +50,12 @@ use crate::args::LintFlags; use crate::args::LintOptions; use crate::args::LintReporterKind; use crate::args::LintRulesConfig; +use crate::args::WorkspaceLintOptions; +use crate::cache::Caches; use crate::cache::IncrementalCache; use crate::colors; use crate::factory::CliFactory; +use crate::graph_util::ModuleGraphCreator; use crate::tools::fmt::run_parallelized; use crate::util::file_watcher; use crate::util::fs::canonicalize_path; @@ -79,35 +91,49 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(async move { let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); - let lint_options = cli_options.resolve_lint_options(lint_flags)?; let lint_config = cli_options.resolve_lint_config()?; - let files = - collect_lint_files(cli_options, lint_options.files.clone()) - .and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; - _ = watcher_communicator.watch_paths(files.clone()); + let mut paths_with_options_batches = + resolve_paths_with_options_batches(cli_options, &lint_flags)?; + for paths_with_options in &mut paths_with_options_batches { + _ = watcher_communicator + .watch_paths(paths_with_options.paths.clone()); - let lint_paths = if let Some(paths) = changed_paths { - // lint all files on any changed (https://github.com/denoland/deno/issues/12446) - files - .iter() - .any(|path| { - canonicalize_path(path) - .map(|p| paths.contains(&p)) - .unwrap_or(false) - }) - .then_some(files) - .unwrap_or_else(|| [].to_vec()) - } else { - files - }; + let files = std::mem::take(&mut paths_with_options.paths); + paths_with_options.paths = if let Some(paths) = &changed_paths { + // lint all files on any changed (https://github.com/denoland/deno/issues/12446) + files + .iter() + .any(|path| { + canonicalize_path(path) + .map(|p| paths.contains(&p)) + .unwrap_or(false) + }) + .then_some(files) + .unwrap_or_else(|| [].to_vec()) + } else { + files + }; + } + + let mut linter = WorkspaceLinter::new( + factory.caches()?.clone(), + factory.module_graph_creator().await?.clone(), + cli_options.workspace.clone(), + &cli_options.resolve_workspace_lint_options(&lint_flags)?, + ); + for paths_with_options in paths_with_options_batches { + linter + .lint_files( + paths_with_options.options, + lint_config.clone(), + paths_with_options.ctx, + paths_with_options.paths, + ) + .await?; + } + + linter.finish(); - lint_files(factory, lint_options, lint_config, lint_paths).await?; Ok(()) }) }, @@ -117,15 +143,19 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); let is_stdin = lint_flags.is_stdin(); - let lint_options = cli_options.resolve_lint_options(lint_flags)?; let lint_config = cli_options.resolve_lint_config()?; - let files = &lint_options.files; + let workspace_lint_options = + cli_options.resolve_workspace_lint_options(&lint_flags)?; let success = if is_stdin { - let reporter_kind = lint_options.reporter_kind; - let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind))); + let start_ctx = cli_options.workspace.resolve_start_ctx(); + let reporter_lock = Arc::new(Mutex::new(create_reporter( + workspace_lint_options.reporter_kind, + ))); + let lint_options = + cli_options.resolve_lint_options(lint_flags, &start_ctx)?; let lint_rules = get_config_rules_err_empty( lint_options.rules, - cli_options.maybe_config_file().as_ref(), + start_ctx.maybe_deno_json().map(|c| c.as_ref()), )?; let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME); let r = lint_stdin(&file_path, lint_rules.rules, lint_config); @@ -137,16 +167,25 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { reporter_lock.lock().close(1); success } else { - let target_files = collect_lint_files(cli_options, files.clone()) - .and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; - debug!("Found {} files", target_files.len()); - lint_files(factory, lint_options, lint_config, target_files).await? + let mut linter = WorkspaceLinter::new( + factory.caches()?.clone(), + factory.module_graph_creator().await?.clone(), + cli_options.workspace.clone(), + &workspace_lint_options, + ); + let paths_with_options_batches = + resolve_paths_with_options_batches(cli_options, &lint_flags)?; + for paths_with_options in paths_with_options_batches { + linter + .lint_files( + paths_with_options.options, + lint_config.clone(), + paths_with_options.ctx, + paths_with_options.paths, + ) + .await?; + } + linter.finish() }; if !success { std::process::exit(1); @@ -156,121 +195,202 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(()) } -async fn lint_files( - factory: CliFactory, - lint_options: LintOptions, - lint_config: LintConfig, +struct PathsWithOptions { + ctx: WorkspaceMemberContext, paths: Vec, -) -> Result { - let caches = factory.caches()?; - let maybe_config_file = factory.cli_options().maybe_config_file().as_ref(); - let lint_rules = - get_config_rules_err_empty(lint_options.rules, maybe_config_file)?; - let incremental_cache = Arc::new(IncrementalCache::new( - caches.lint_incremental_cache_db(), - &lint_rules.incremental_cache_state(), - &paths, - )); - let target_files_len = paths.len(); - let reporter_kind = lint_options.reporter_kind; - // todo(dsherret): abstract away this lock behind a performant interface - let reporter_lock = - Arc::new(Mutex::new(create_reporter(reporter_kind.clone()))); - let has_error = Arc::new(AtomicFlag::default()); + options: LintOptions, +} - let mut futures = Vec::with_capacity(2); - if lint_rules.no_slow_types { - if let Some(config_file) = maybe_config_file { - let members = config_file.to_workspace_members()?; - let has_error = has_error.clone(); - let reporter_lock = reporter_lock.clone(); - let module_graph_creator = factory.module_graph_creator().await?.clone(); - let path_urls = paths - .iter() - .filter_map(|p| ModuleSpecifier::from_file_path(p).ok()) - .collect::>(); - futures.push(deno_core::unsync::spawn(async move { - let graph = module_graph_creator - .create_and_validate_publish_graph(&members, true) - .await?; - // todo(dsherret): this isn't exactly correct as linting isn't properly - // setup to handle workspaces. Iterating over the workspace members - // should be done at a higher level because it also needs to take into - // account the config per workspace member. - for member in &members { - let export_urls = member.config_file.resolve_export_value_urls()?; - if !export_urls.iter().any(|url| path_urls.contains(url)) { - continue; // entrypoint is not specified, so skip - } - let diagnostics = no_slow_types::collect_no_slow_type_diagnostics( - &export_urls, - &graph, - ); - if !diagnostics.is_empty() { - has_error.raise(); - let mut reporter = reporter_lock.lock(); - for diagnostic in &diagnostics { - reporter - .visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic)); - } - } - } - Ok(()) - })); +fn resolve_paths_with_options_batches( + cli_options: &CliOptions, + lint_flags: &LintFlags, +) -> Result, AnyError> { + let members_lint_options = + cli_options.resolve_lint_options_for_members(lint_flags)?; + let mut paths_with_options_batches = + Vec::with_capacity(members_lint_options.len()); + for (ctx, lint_options) in members_lint_options { + let files = collect_lint_files(cli_options, lint_options.files.clone())?; + if !files.is_empty() { + paths_with_options_batches.push(PathsWithOptions { + ctx, + paths: files, + options: lint_options, + }); + } + } + if paths_with_options_batches.is_empty() { + return Err(generic_error("No target files found.")); + } + Ok(paths_with_options_batches) +} + +type WorkspaceModuleGraphFuture = + SharedLocal, Rc>>>; + +struct WorkspaceLinter { + caches: Arc, + module_graph_creator: Arc, + workspace: Arc, + reporter_lock: Arc>>, + workspace_module_graph: Option, + has_error: Arc, + file_count: usize, +} + +impl WorkspaceLinter { + pub fn new( + caches: Arc, + module_graph_creator: Arc, + workspace: Arc, + workspace_options: &WorkspaceLintOptions, + ) -> Self { + let reporter_lock = + Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind))); + Self { + caches, + module_graph_creator, + workspace, + reporter_lock, + workspace_module_graph: None, + has_error: Default::default(), + file_count: 0, } } - futures.push({ - let has_error = has_error.clone(); - let linter = create_linter(lint_rules.rules); - let reporter_lock = reporter_lock.clone(); - let incremental_cache = incremental_cache.clone(); - let lint_config = lint_config.clone(); - let fix = lint_options.fix; - deno_core::unsync::spawn(async move { - run_parallelized(paths, { - move |file_path| { - let file_text = deno_ast::strip_bom(fs::read_to_string(&file_path)?); + pub async fn lint_files( + &mut self, + lint_options: LintOptions, + lint_config: LintConfig, + member_ctx: WorkspaceMemberContext, + paths: Vec, + ) -> Result<(), AnyError> { + self.file_count += paths.len(); - // don't bother rechecking this file if it didn't have any diagnostics before - if incremental_cache.is_file_same(&file_path, &file_text) { - return Ok(()); + let lint_rules = get_config_rules_err_empty( + lint_options.rules, + member_ctx.maybe_deno_json().map(|c| c.as_ref()), + )?; + let incremental_cache = Arc::new(IncrementalCache::new( + self.caches.lint_incremental_cache_db(), + &lint_rules.incremental_cache_state(), + &paths, + )); + + let mut futures = Vec::with_capacity(2); + if lint_rules.no_slow_types { + if self.workspace_module_graph.is_none() { + let module_graph_creator = self.module_graph_creator.clone(); + let packages = self.workspace.jsr_packages_for_publish(); + self.workspace_module_graph = Some( + async move { + module_graph_creator + .create_and_validate_publish_graph(&packages, true) + .await + .map(Rc::new) + .map_err(Rc::new) } - - let r = lint_file(&linter, &file_path, file_text, lint_config, fix); - if let Ok((file_source, file_diagnostics)) = &r { - if file_diagnostics.is_empty() { - // update the incremental cache if there were no diagnostics - incremental_cache.update_file( - &file_path, - // ensure the returned text is used here as it may have been modified via --fix - file_source.text(), - ) + .boxed_local() + .shared_local(), + ); + } + let workspace_module_graph_future = + self.workspace_module_graph.as_ref().unwrap().clone(); + let publish_config = member_ctx.maybe_package_config(); + if let Some(publish_config) = publish_config { + let has_error = self.has_error.clone(); + let reporter_lock = self.reporter_lock.clone(); + let path_urls = paths + .iter() + .filter_map(|p| ModuleSpecifier::from_file_path(p).ok()) + .collect::>(); + futures.push( + async move { + let graph = workspace_module_graph_future + .await + .map_err(|err| anyhow!("{:#}", err))?; + let export_urls = + publish_config.config_file.resolve_export_value_urls()?; + if !export_urls.iter().any(|url| path_urls.contains(url)) { + return Ok(()); // entrypoint is not specified, so skip } + let diagnostics = no_slow_types::collect_no_slow_type_diagnostics( + &export_urls, + &graph, + ); + if !diagnostics.is_empty() { + has_error.raise(); + let mut reporter = reporter_lock.lock(); + for diagnostic in &diagnostics { + reporter + .visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic)); + } + } + Ok(()) } + .boxed_local(), + ); + } + } - let success = handle_lint_result( - &file_path.to_string_lossy(), - r, - reporter_lock.clone(), - ); - if !success { - has_error.raise(); + futures.push({ + let has_error = self.has_error.clone(); + let linter = create_linter(lint_rules.rules); + let reporter_lock = self.reporter_lock.clone(); + let incremental_cache = incremental_cache.clone(); + let lint_config = lint_config.clone(); + let fix = lint_options.fix; + async move { + run_parallelized(paths, { + move |file_path| { + let file_text = + deno_ast::strip_bom(fs::read_to_string(&file_path)?); + + // don't bother rechecking this file if it didn't have any diagnostics before + if incremental_cache.is_file_same(&file_path, &file_text) { + return Ok(()); + } + + let r = lint_file(&linter, &file_path, file_text, lint_config, fix); + if let Ok((file_source, file_diagnostics)) = &r { + if file_diagnostics.is_empty() { + // update the incremental cache if there were no diagnostics + incremental_cache.update_file( + &file_path, + // ensure the returned text is used here as it may have been modified via --fix + file_source.text(), + ) + } + } + + let success = handle_lint_result( + &file_path.to_string_lossy(), + r, + reporter_lock.clone(), + ); + if !success { + has_error.raise(); + } + + Ok(()) } + }) + .await + } + .boxed_local() + }); - Ok(()) - } - }) - .await - }) - }); + deno_core::futures::future::try_join_all(futures).await?; - deno_core::futures::future::try_join_all(futures).await?; + incremental_cache.wait_completion().await; + Ok(()) + } - incremental_cache.wait_completion().await; - reporter_lock.lock().close(target_files_len); - - Ok(!has_error.is_raised()) + pub fn finish(self) -> bool { + debug!("Found {} files", self.file_count); + self.reporter_lock.lock().close(self.file_count); + !self.has_error.is_raised() // success + } } fn collect_lint_files( @@ -692,9 +812,8 @@ impl LintReporter for PrettyLintReporter { } match check_count { - n if n <= 1 => info!("Checked {} file", n), - n if n > 1 => info!("Checked {} files", n), - _ => unreachable!(), + 1 => info!("Checked 1 file"), + n => info!("Checked {} files", n), } } } @@ -744,9 +863,8 @@ impl LintReporter for CompactLintReporter { } match check_count { - n if n <= 1 => info!("Checked {} file", n), - n if n > 1 => info!("Checked {} files", n), - _ => unreachable!(), + 1 => info!("Checked 1 file"), + n => info!("Checked {} files", n), } } } @@ -910,9 +1028,8 @@ pub fn get_configured_rules( maybe_config_file: Option<&deno_config::ConfigFile>, ) -> ConfiguredRules { const NO_SLOW_TYPES_NAME: &str = "no-slow-types"; - let implicit_no_slow_types = maybe_config_file - .map(|c| c.is_package() || c.json.workspace.is_some()) - .unwrap_or(false); + let implicit_no_slow_types = + maybe_config_file.map(|c| c.is_package()).unwrap_or(false); let no_slow_types = implicit_no_slow_types && !rules .exclude diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index d300e5eafd..134a973f7d 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -11,9 +11,8 @@ use std::sync::Arc; use base64::prelude::BASE64_STANDARD; use base64::Engine; use deno_ast::ModuleSpecifier; -use deno_config::glob::FilePatterns; -use deno_config::ConfigFile; -use deno_config::WorkspaceMemberConfig; +use deno_config::workspace::JsrPackageConfig; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -27,7 +26,6 @@ use deno_core::serde_json::Value; use deno_runtime::deno_fetch::reqwest; use deno_runtime::deno_fs::FileSystem; use deno_terminal::colors; -use import_map::ImportMap; use lsp_types::Url; use serde::Deserialize; use serde::Serialize; @@ -44,7 +42,6 @@ use crate::cache::ParsedSourceCache; use crate::factory::CliFactory; use crate::graph_util::ModuleGraphCreator; use crate::http_util::HttpClient; -use crate::resolver::MappedSpecifierResolver; use crate::resolver::SloppyImportsResolver; use crate::tools::check::CheckOptions; use crate::tools::lint::no_slow_types; @@ -84,27 +81,28 @@ pub async fn publish( let auth_method = get_auth_method(publish_flags.token, publish_flags.dry_run)?; - let import_map = cli_factory - .maybe_import_map() - .await? - .clone() - .unwrap_or_else(|| { - Arc::new(ImportMap::new(Url::parse("file:///dev/null").unwrap())) - }); + let workspace_resolver = cli_factory.workspace_resolver().await?.clone(); let directory_path = cli_factory.cli_options().initial_cwd(); - - let mapped_resolver = Arc::new(MappedSpecifierResolver::new( - Some(import_map), - cli_factory.package_json_deps_provider().clone(), - )); let cli_options = cli_factory.cli_options(); - let Some(config_file) = cli_options.maybe_config_file() else { - bail!( - "Couldn't find a deno.json, deno.jsonc, jsr.json or jsr.jsonc configuration file in {}.", - directory_path.display() - ); - }; + let publish_configs = cli_options.workspace.jsr_packages_for_publish(); + if publish_configs.is_empty() { + match cli_options.workspace.resolve_start_ctx().maybe_deno_json() { + Some(deno_json) => { + debug_assert!(!deno_json.is_package()); + bail!( + "Missing 'name', 'version' and 'exports' field in '{}'.", + deno_json.specifier + ); + } + None => { + bail!( + "Couldn't find a deno.json, deno.jsonc, jsr.json or jsr.jsonc configuration file in {}.", + directory_path.display() + ); + } + } + } let diagnostics_collector = PublishDiagnosticsCollector::default(); let publish_preparer = PublishPreparer::new( @@ -114,14 +112,14 @@ pub async fn publish( cli_factory.type_checker().await?.clone(), cli_factory.fs().clone(), cli_factory.cli_options().clone(), - mapped_resolver, + workspace_resolver, ); let prepared_data = publish_preparer .prepare_packages_for_publishing( publish_flags.allow_slow_types, &diagnostics_collector, - config_file.clone(), + publish_configs, ) .await?; @@ -193,8 +191,8 @@ struct PublishPreparer { source_cache: Arc, type_checker: Arc, cli_options: Arc, - mapped_resolver: Arc, sloppy_imports_resolver: Option>, + workspace_resolver: Arc, } impl PublishPreparer { @@ -205,7 +203,7 @@ impl PublishPreparer { type_checker: Arc, fs: Arc, cli_options: Arc, - mapped_resolver: Arc, + workspace_resolver: Arc, ) -> Self { let sloppy_imports_resolver = if cli_options.unstable_sloppy_imports() { Some(Arc::new(SloppyImportsResolver::new(fs.clone()))) @@ -218,8 +216,8 @@ impl PublishPreparer { source_cache, type_checker, cli_options, - mapped_resolver, sloppy_imports_resolver, + workspace_resolver, } } @@ -227,11 +225,9 @@ impl PublishPreparer { &self, allow_slow_types: bool, diagnostics_collector: &PublishDiagnosticsCollector, - deno_json: ConfigFile, + publish_configs: Vec, ) -> Result { - let members = deno_json.to_workspace_members()?; - - if members.len() > 1 { + if publish_configs.len() > 1 { log::info!("Publishing a workspace..."); } @@ -240,31 +236,24 @@ impl PublishPreparer { .build_and_check_graph_for_publish( allow_slow_types, diagnostics_collector, - &members, + &publish_configs, ) .await?; - let mut package_by_name = HashMap::with_capacity(members.len()); + let mut package_by_name = HashMap::with_capacity(publish_configs.len()); let publish_order_graph = - publish_order::build_publish_order_graph(&graph, &members)?; + publish_order::build_publish_order_graph(&graph, &publish_configs)?; - let results = members + let results = publish_configs .into_iter() .map(|member| { let graph = graph.clone(); async move { let package = self - .prepare_publish( - &member.package_name, - &member.config_file, - graph, - diagnostics_collector, - ) + .prepare_publish(&member, graph, diagnostics_collector) .await - .with_context(|| { - format!("Failed preparing '{}'.", member.package_name) - })?; - Ok::<_, AnyError>((member.package_name, package)) + .with_context(|| format!("Failed preparing '{}'.", member.name))?; + Ok::<_, AnyError>((member.name, package)) } .boxed() }) @@ -284,12 +273,15 @@ impl PublishPreparer { &self, allow_slow_types: bool, diagnostics_collector: &PublishDiagnosticsCollector, - packages: &[WorkspaceMemberConfig], + package_configs: &[JsrPackageConfig], ) -> Result, deno_core::anyhow::Error> { let build_fast_check_graph = !allow_slow_types; let graph = self .module_graph_creator - .create_and_validate_publish_graph(packages, build_fast_check_graph) + .create_and_validate_publish_graph( + package_configs, + build_fast_check_graph, + ) .await?; // todo(dsherret): move to lint rule @@ -335,7 +327,7 @@ impl PublishPreparer { } else { log::info!("Checking for slow types in the public API..."); let mut any_pkg_had_diagnostics = false; - for package in packages { + for package in package_configs { let export_urls = package.config_file.resolve_export_value_urls()?; let diagnostics = no_slow_types::collect_no_slow_type_diagnostics(&export_urls, &graph); @@ -389,14 +381,14 @@ impl PublishPreparer { #[allow(clippy::too_many_arguments)] async fn prepare_publish( &self, - package_name: &str, - deno_json: &ConfigFile, + package: &JsrPackageConfig, graph: Arc, diagnostics_collector: &PublishDiagnosticsCollector, ) -> Result, AnyError> { static SUGGESTED_ENTRYPOINTS: [&str; 4] = ["mod.ts", "mod.js", "index.ts", "index.js"]; + let deno_json = &package.config_file; let config_path = deno_json.specifier.to_file_path().unwrap(); let root_dir = config_path.parent().unwrap().to_path_buf(); let Some(version) = deno_json.json.version.clone() else { @@ -418,32 +410,29 @@ impl PublishPreparer { "version": "{}", "exports": "{}" }}"#, - package_name, + package.name, version, suggested_entrypoint.unwrap_or("") ); bail!( "You did not specify an entrypoint to \"{}\" package in {}. Add `exports` mapping in the configuration file, eg:\n{}", - package_name, + package.name, deno_json.specifier, exports_content ); } - let Some(name_no_at) = package_name.strip_prefix('@') else { + let Some(name_no_at) = package.name.strip_prefix('@') else { bail!("Invalid package name, use '@/ format"); }; let Some((scope, name_no_scope)) = name_no_at.split_once('/') else { bail!("Invalid package name, use '@/ format"); }; - let file_patterns = deno_json - .to_publish_config()? - .map(|c| c.files) - .unwrap_or_else(|| FilePatterns::new_with_base(root_dir.to_path_buf())); + let file_patterns = package.member_ctx.to_publish_config()?.files; let tarball = deno_core::unsync::spawn_blocking({ let diagnostics_collector = diagnostics_collector.clone(); - let mapped_resolver = self.mapped_resolver.clone(); + let workspace_resolver = self.workspace_resolver.clone(); let sloppy_imports_resolver = self.sloppy_imports_resolver.clone(); let cli_options = self.cli_options.clone(); let source_cache = self.source_cache.clone(); @@ -451,8 +440,8 @@ impl PublishPreparer { move || { let bare_node_builtins = cli_options.unstable_bare_node_builtins(); let unfurler = SpecifierUnfurler::new( - &mapped_resolver, sloppy_imports_resolver.as_deref(), + &workspace_resolver, bare_node_builtins, ); let root_specifier = @@ -482,7 +471,7 @@ impl PublishPreparer { }) .await??; - log::debug!("Tarball size ({}): {}", package_name, tarball.bytes.len()); + log::debug!("Tarball size ({}): {}", package.name, tarball.bytes.len()); Ok(Rc::new(PreparedPublishPackage { scope: scope.to_string(), diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 4fdc025505..e3e2f1b558 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -49,7 +49,7 @@ impl DenoConfigFormat { } enum DenoOrPackageJson { - Deno(deno_config::ConfigFile, DenoConfigFormat), + Deno(Arc, DenoConfigFormat), Npm(Arc, Option), } @@ -87,7 +87,6 @@ impl DenoOrPackageJson { DenoOrPackageJson::Deno(deno, ..) => deno .to_fmt_config() .ok() - .flatten() .map(|f| f.options) .unwrap_or_default(), DenoOrPackageJson::Npm(_, config) => config.clone().unwrap_or_default(), @@ -122,9 +121,10 @@ impl DenoOrPackageJson { /// the new config fn from_flags(flags: Flags) -> Result<(Self, CliFactory), AnyError> { let factory = CliFactory::from_flags(flags.clone())?; - let options = factory.cli_options().clone(); + let options = factory.cli_options(); + let start_ctx = options.workspace.resolve_start_ctx(); - match (options.maybe_config_file(), options.maybe_package_json()) { + match (start_ctx.maybe_deno_json(), start_ctx.maybe_pkg_json()) { // when both are present, for now, // default to deno.json (Some(deno), Some(_) | None) => Ok(( @@ -141,20 +141,17 @@ impl DenoOrPackageJson { std::fs::write(options.initial_cwd().join("deno.json"), "{}\n") .context("Failed to create deno.json file")?; log::info!("Created deno.json configuration file."); - let new_factory = CliFactory::from_flags(flags.clone())?; - let new_options = new_factory.cli_options().clone(); + let factory = CliFactory::from_flags(flags.clone())?; + let options = factory.cli_options().clone(); + let start_ctx = options.workspace.resolve_start_ctx(); Ok(( DenoOrPackageJson::Deno( - new_options - .maybe_config_file() - .as_ref() - .ok_or_else(|| { - anyhow!("config not found, but it was just created") - })? - .clone(), + start_ctx.maybe_deno_json().cloned().ok_or_else(|| { + anyhow!("config not found, but it was just created") + })?, DenoConfigFormat::Json, ), - new_factory, + factory, )) } } diff --git a/cli/tools/registry/publish_order.rs b/cli/tools/registry/publish_order.rs index ad0f72272b..ad77a56bb1 100644 --- a/cli/tools/registry/publish_order.rs +++ b/cli/tools/registry/publish_order.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use std::collections::VecDeque; use deno_ast::ModuleSpecifier; -use deno_config::WorkspaceMemberConfig; +use deno_config::workspace::JsrPackageConfig; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_graph::ModuleGraph; @@ -114,7 +114,7 @@ impl PublishOrderGraph { pub fn build_publish_order_graph( graph: &ModuleGraph, - roots: &[WorkspaceMemberConfig], + roots: &[JsrPackageConfig], ) -> Result { let packages = build_pkg_deps(graph, roots)?; Ok(build_publish_order_graph_from_pkgs_deps(packages)) @@ -122,18 +122,23 @@ pub fn build_publish_order_graph( fn build_pkg_deps( graph: &deno_graph::ModuleGraph, - roots: &[WorkspaceMemberConfig], + roots: &[JsrPackageConfig], ) -> Result>, AnyError> { let mut members = HashMap::with_capacity(roots.len()); let mut seen_modules = HashSet::with_capacity(graph.modules().count()); let roots = roots .iter() - .map(|r| (ModuleSpecifier::from_file_path(&r.dir_path).unwrap(), r)) + .map(|r| { + ( + ModuleSpecifier::from_directory_path(r.config_file.dir_path()).unwrap(), + r, + ) + }) .collect::>(); - for (root_dir_url, root) in &roots { + for (root_dir_url, pkg_config) in &roots { let mut deps = HashSet::new(); let mut pending = VecDeque::new(); - pending.extend(root.config_file.resolve_export_value_urls()?); + pending.extend(pkg_config.config_file.resolve_export_value_urls()?); while let Some(specifier) = pending.pop_front() { let Some(module) = graph.get(&specifier).and_then(|m| m.js()) else { continue; @@ -168,12 +173,12 @@ fn build_pkg_deps( specifier.as_str().starts_with(dir_url.as_str()) }); if let Some(root) = found_root { - deps.insert(root.1.package_name.clone()); + deps.insert(root.1.name.clone()); } } } } - members.insert(root.package_name.clone(), deps); + members.insert(pkg_config.name.clone(), deps); } Ok(members) } diff --git a/cli/tools/registry/unfurl.rs b/cli/tools/registry/unfurl.rs index 36bff64bbb..147b59f30c 100644 --- a/cli/tools/registry/unfurl.rs +++ b/cli/tools/registry/unfurl.rs @@ -3,6 +3,9 @@ use deno_ast::ParsedSource; use deno_ast::SourceRange; use deno_ast::SourceTextInfo; +use deno_config::package_json::PackageJsonDepValue; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::WorkspaceResolver; use deno_core::ModuleSpecifier; use deno_graph::DependencyDescriptor; use deno_graph::DynamicTemplatePart; @@ -10,7 +13,6 @@ use deno_graph::ParserModuleAnalyzer; use deno_graph::TypeScriptReference; use deno_runtime::deno_node::is_builtin_node_module; -use crate::resolver::MappedSpecifierResolver; use crate::resolver::SloppyImportsResolver; #[derive(Debug, Clone)] @@ -39,20 +41,20 @@ impl SpecifierUnfurlerDiagnostic { } pub struct SpecifierUnfurler<'a> { - mapped_resolver: &'a MappedSpecifierResolver, sloppy_imports_resolver: Option<&'a SloppyImportsResolver>, + workspace_resolver: &'a WorkspaceResolver, bare_node_builtins: bool, } impl<'a> SpecifierUnfurler<'a> { pub fn new( - mapped_resolver: &'a MappedSpecifierResolver, sloppy_imports_resolver: Option<&'a SloppyImportsResolver>, + workspace_resolver: &'a WorkspaceResolver, bare_node_builtins: bool, ) -> Self { Self { - mapped_resolver, sloppy_imports_resolver, + workspace_resolver, bare_node_builtins, } } @@ -62,12 +64,46 @@ impl<'a> SpecifierUnfurler<'a> { referrer: &ModuleSpecifier, specifier: &str, ) -> Option { - let resolved = - if let Ok(resolved) = self.mapped_resolver.resolve(specifier, referrer) { - resolved.into_specifier() - } else { - None - }; + let resolved = if let Ok(resolved) = + self.workspace_resolver.resolve(specifier, referrer) + { + match resolved { + MappedResolution::Normal(specifier) + | MappedResolution::ImportMap(specifier) => Some(specifier), + MappedResolution::PackageJson { + sub_path, + dep_result, + .. + } => match dep_result { + Ok(dep) => match dep { + PackageJsonDepValue::Req(req) => ModuleSpecifier::parse(&format!( + "npm:{}{}", + req, + sub_path + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default() + )) + .ok(), + PackageJsonDepValue::Workspace(_) => { + log::warn!( + "package.json workspace entries are not implemented yet for publishing." + ); + None + } + }, + Err(err) => { + log::warn!( + "Ignoring failed to resolve package.json dependency. {:#}", + err + ); + None + } + }, + } + } else { + None + }; let resolved = match resolved { Some(resolved) => resolved, None if self.bare_node_builtins && is_builtin_node_module(specifier) => { @@ -305,8 +341,6 @@ fn to_range( mod tests { use std::sync::Arc; - use crate::args::PackageJsonDepsProvider; - use super::*; use deno_ast::MediaType; use deno_ast::ModuleSpecifier; @@ -355,19 +389,17 @@ mod tests { } }), ); - let mapped_resolver = MappedSpecifierResolver::new( - Some(Arc::new(import_map)), - Arc::new(PackageJsonDepsProvider::new(Some( - package_json.resolve_local_package_json_version_reqs(), - ))), + let workspace_resolver = WorkspaceResolver::new_raw( + Some(import_map), + vec![Arc::new(package_json)], + deno_config::workspace::PackageJsonDepResolution::Enabled, ); - let fs = Arc::new(RealFs); let sloppy_imports_resolver = SloppyImportsResolver::new(fs); let unfurler = SpecifierUnfurler::new( - &mapped_resolver, Some(&sloppy_imports_resolver), + &workspace_resolver, true, ); diff --git a/cli/tools/task.rs b/cli/tools/task.rs index a44dc8dbba..2905134f4f 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -8,24 +8,30 @@ use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; use crate::npm::ManagedCliNpmResolver; use crate::util::fs::canonicalize_path; +use deno_config::workspace::TaskOrScript; +use deno_config::workspace::Workspace; +use deno_config::workspace::WorkspaceTasksConfig; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures; use deno_core::futures::future::LocalBoxFuture; +use deno_core::normalize_path; use deno_runtime::deno_node::NodeResolver; use deno_semver::package::PackageNv; use deno_task_shell::ExecutableCommand; use deno_task_shell::ExecuteResult; use deno_task_shell::ShellCommand; use deno_task_shell::ShellCommandContext; -use indexmap::IndexMap; use lazy_regex::Lazy; use regex::Regex; +use std::borrow::Cow; use std::collections::HashMap; +use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; use tokio::task::LocalSet; // WARNING: Do not depend on this env var in user code. It's not stable API. @@ -38,27 +44,10 @@ pub async fn execute_script( ) -> Result { let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); - let tasks_config = cli_options.resolve_tasks_config()?; - let maybe_package_json = cli_options.maybe_package_json(); - let package_json_scripts = maybe_package_json - .as_ref() - .and_then(|p| p.scripts.clone()) - .unwrap_or_default(); - - let task_name = match &task_flags.task { - Some(task) => task, - None => { - print_available_tasks( - &mut std::io::stdout(), - &tasks_config, - &package_json_scripts, - )?; - return Ok(1); - } - }; - let npm_resolver = factory.npm_resolver().await?; - let node_resolver = factory.node_resolver().await?; - let env_vars = real_env_vars(); + let start_ctx = cli_options.workspace.resolve_start_ctx(); + if !start_ctx.has_deno_or_pkg_json() { + bail!("deno task couldn't find deno.json(c). See https://deno.land/manual@v{}/getting_started/configuration_file", env!("CARGO_PKG_VERSION")) + } let force_use_pkg_json = std::env::var_os(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME) .map(|v| { // always remove so sub processes don't inherit this env var @@ -66,118 +55,113 @@ pub async fn execute_script( v == "1" }) .unwrap_or(false); + let tasks_config = start_ctx.to_tasks_config()?; + let tasks_config = if force_use_pkg_json { + tasks_config.with_only_pkg_json() + } else { + tasks_config + }; - if let Some( - deno_config::Task::Definition(script) - | deno_config::Task::Commented { - definition: script, .. - }, - ) = tasks_config.get(task_name).filter(|_| !force_use_pkg_json) - { - let config_file_url = cli_options.maybe_config_file_specifier().unwrap(); - let config_file_path = if config_file_url.scheme() == "file" { - config_file_url.to_file_path().unwrap() - } else { - bail!("Only local configuration files are supported") - }; - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path)) - .context("failed canonicalizing --cwd")?, - None => config_file_path.parent().unwrap().to_owned(), - }; - - let custom_commands = - resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?; - run_task(RunTaskOptions { - task_name, - script, - cwd: &cwd, - init_cwd: cli_options.initial_cwd(), - env_vars, - argv: cli_options.argv(), - custom_commands, - root_node_modules_dir: npm_resolver - .root_node_modules_path() - .map(|p| p.as_path()), - }) - .await - } else if package_json_scripts.contains_key(task_name) { - let package_json_deps_provider = factory.package_json_deps_provider(); - - if let Some(package_deps) = package_json_deps_provider.deps() { - for (key, value) in package_deps { - if let Err(err) = value { - log::info!( - "{} Ignoring dependency '{}' in package.json because its version requirement failed to parse: {:#}", - colors::yellow("Warning"), - key, - err, - ); - } - } + let task_name = match &task_flags.task { + Some(task) => task, + None => { + print_available_tasks( + &mut std::io::stdout(), + &cli_options.workspace, + &tasks_config, + )?; + return Ok(1); } + }; - // ensure the npm packages are installed if using a node_modules - // directory and managed resolver - if cli_options.has_node_modules_dir() { - if let Some(npm_resolver) = npm_resolver.as_managed() { - npm_resolver.ensure_top_level_package_json_install().await?; - } - } + let npm_resolver = factory.npm_resolver().await?; + let node_resolver = factory.node_resolver().await?; + let env_vars = real_env_vars(); - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path))?, - None => maybe_package_json - .as_ref() - .unwrap() - .path - .parent() - .unwrap() - .to_owned(), - }; + match tasks_config.task(task_name) { + Some((dir_url, task_or_script)) => match task_or_script { + TaskOrScript::Task(_tasks, script) => { + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path)) + .context("failed canonicalizing --cwd")?, + None => normalize_path(dir_url.to_file_path().unwrap()), + }; - // At this point we already checked if the task name exists in package.json. - // We can therefore check for "pre" and "post" scripts too, since we're only - // dealing with package.json here and not deno.json - let task_names = vec![ - format!("pre{}", task_name), - task_name.clone(), - format!("post{}", task_name), - ]; - let custom_commands = - resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?; - for task_name in &task_names { - if let Some(script) = package_json_scripts.get(task_name) { - let exit_code = run_task(RunTaskOptions { + let custom_commands = + resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?; + run_task(RunTaskOptions { task_name, script, cwd: &cwd, init_cwd: cli_options.initial_cwd(), - env_vars: env_vars.clone(), + env_vars, argv: cli_options.argv(), - custom_commands: custom_commands.clone(), + custom_commands, root_node_modules_dir: npm_resolver .root_node_modules_path() .map(|p| p.as_path()), }) - .await?; - if exit_code > 0 { - return Ok(exit_code); - } + .await } - } + TaskOrScript::Script(scripts, _script) => { + // ensure the npm packages are installed if using a node_modules + // directory and managed resolver + if cli_options.has_node_modules_dir() { + if let Some(npm_resolver) = npm_resolver.as_managed() { + npm_resolver.ensure_top_level_package_json_install().await?; + } + } - Ok(0) - } else { - log::error!("Task not found: {task_name}"); - if log::log_enabled!(log::Level::Error) { - print_available_tasks( - &mut std::io::stderr(), - &tasks_config, - &package_json_scripts, - )?; + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => normalize_path(dir_url.to_file_path().unwrap()), + }; + + // At this point we already checked if the task name exists in package.json. + // We can therefore check for "pre" and "post" scripts too, since we're only + // dealing with package.json here and not deno.json + let task_names = vec![ + format!("pre{}", task_name), + task_name.clone(), + format!("post{}", task_name), + ]; + let custom_commands = + resolve_custom_commands(npm_resolver.as_ref(), node_resolver)?; + for task_name in &task_names { + if let Some(script) = scripts.get(task_name) { + let exit_code = run_task(RunTaskOptions { + task_name, + script, + cwd: &cwd, + init_cwd: cli_options.initial_cwd(), + env_vars: env_vars.clone(), + argv: cli_options.argv(), + custom_commands: custom_commands.clone(), + root_node_modules_dir: npm_resolver + .root_node_modules_path() + .map(|p| p.as_path()), + }) + .await?; + if exit_code > 0 { + return Ok(exit_code); + } + } + } + + Ok(0) + } + }, + None => { + log::error!("Task not found: {task_name}"); + if log::log_enabled!(log::Level::Error) { + print_available_tasks( + &mut std::io::stderr(), + &cli_options.workspace, + &tasks_config, + )?; + } + Ok(1) } - Ok(1) } } @@ -282,53 +266,92 @@ fn real_env_vars() -> HashMap { fn print_available_tasks( writer: &mut dyn std::io::Write, - tasks_config: &IndexMap, - package_json_scripts: &IndexMap, + workspace: &Arc, + tasks_config: &WorkspaceTasksConfig, ) -> Result<(), std::io::Error> { writeln!(writer, "{}", colors::green("Available tasks:"))?; + let is_cwd_root_dir = tasks_config.root.is_none(); - if tasks_config.is_empty() && package_json_scripts.is_empty() { + if tasks_config.is_empty() { writeln!( writer, " {}", colors::red("No tasks found in configuration file") )?; } else { - for (is_deno, (key, task)) in tasks_config - .iter() - .map(|(k, t)| (true, (k, t.clone()))) - .chain( - package_json_scripts - .iter() - .filter(|(key, _)| !tasks_config.contains_key(*key)) - .map(|(k, v)| (false, (k, deno_config::Task::Definition(v.clone())))), - ) - { - writeln!( - writer, - "- {}{}", - colors::cyan(key), - if is_deno { - "".to_string() - } else { - format!(" {}", colors::italic_gray("(package.json)")) - } - )?; - let definition = match &task { - deno_config::Task::Definition(definition) => definition, - deno_config::Task::Commented { definition, .. } => definition, + let mut seen_task_names = + HashSet::with_capacity(tasks_config.tasks_count()); + for maybe_config in [&tasks_config.member, &tasks_config.root] { + let Some(config) = maybe_config else { + continue; }; - if let deno_config::Task::Commented { comments, .. } = &task { - let slash_slash = colors::italic_gray("//"); - for comment in comments { - writeln!( - writer, - " {slash_slash} {}", - colors::italic_gray(comment) - )?; + for (is_root, is_deno, (key, task)) in config + .deno_json + .as_ref() + .map(|config| { + let is_root = !is_cwd_root_dir + && config.folder_url == *workspace.root_folder().0.as_ref(); + config + .tasks + .iter() + .map(move |(k, t)| (is_root, true, (k, Cow::Borrowed(t)))) + }) + .into_iter() + .flatten() + .chain( + config + .package_json + .as_ref() + .map(|config| { + let is_root = !is_cwd_root_dir + && config.folder_url == *workspace.root_folder().0.as_ref(); + config.tasks.iter().map(move |(k, v)| { + ( + is_root, + false, + (k, Cow::Owned(deno_config::Task::Definition(v.clone()))), + ) + }) + }) + .into_iter() + .flatten(), + ) + { + if !seen_task_names.insert(key) { + continue; // already seen } + writeln!( + writer, + "- {}{}", + colors::cyan(key), + if is_root { + if is_deno { + format!(" {}", colors::italic_gray("(workspace)")) + } else { + format!(" {}", colors::italic_gray("(workspace package.json)")) + } + } else if is_deno { + "".to_string() + } else { + format!(" {}", colors::italic_gray("(package.json)")) + } + )?; + let definition = match task.as_ref() { + deno_config::Task::Definition(definition) => definition, + deno_config::Task::Commented { definition, .. } => definition, + }; + if let deno_config::Task::Commented { comments, .. } = task.as_ref() { + let slash_slash = colors::italic_gray("//"); + for comment in comments { + writeln!( + writer, + " {slash_slash} {}", + colors::italic_gray(comment) + )?; + } + } + writeln!(writer, " {definition}")?; } - writeln!(writer, " {definition}")?; } } diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 88b539470c..7042a82b97 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1705,11 +1705,17 @@ fn collect_specifiers_with_test_mode( async fn fetch_specifiers_with_test_mode( cli_options: &CliOptions, file_fetcher: &FileFetcher, - files: FilePatterns, + member_patterns: impl Iterator, doc: &bool, ) -> Result, AnyError> { - let mut specifiers_with_mode = - collect_specifiers_with_test_mode(cli_options, files, doc)?; + let mut specifiers_with_mode = member_patterns + .map(|files| { + collect_specifiers_with_test_mode(cli_options, files.clone(), doc) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(); for (specifier, mode) in &mut specifiers_with_mode { let file = file_fetcher @@ -1731,7 +1737,8 @@ pub async fn run_tests( ) -> Result<(), AnyError> { let factory = CliFactory::from_flags(flags)?; let cli_options = factory.cli_options(); - let test_options = cli_options.resolve_test_options(test_flags)?; + let workspace_test_options = + cli_options.resolve_workspace_test_options(&test_flags); let file_fetcher = factory.file_fetcher()?; // Various test files should not share the same permissions in terms of // `PermissionsContainer` - otherwise granting/revoking permissions in one @@ -1740,15 +1747,17 @@ pub async fn run_tests( Permissions::from_options(&cli_options.permissions_options()?)?; let log_level = cli_options.log_level(); + let members_with_test_options = + cli_options.resolve_test_options_for_members(&test_flags)?; let specifiers_with_mode = fetch_specifiers_with_test_mode( cli_options, file_fetcher, - test_options.files.clone(), - &test_options.doc, + members_with_test_options.into_iter().map(|(_, v)| v.files), + &workspace_test_options.doc, ) .await?; - if !test_options.allow_none && specifiers_with_mode.is_empty() { + if !workspace_test_options.allow_none && specifiers_with_mode.is_empty() { return Err(generic_error("No test modules found")); } @@ -1761,7 +1770,7 @@ pub async fn run_tests( ) .await?; - if test_options.no_run { + if workspace_test_options.no_run { return Ok(()); } @@ -1787,16 +1796,16 @@ pub async fn run_tests( )) }, )?, - concurrent_jobs: test_options.concurrent_jobs, - fail_fast: test_options.fail_fast, + concurrent_jobs: workspace_test_options.concurrent_jobs, + fail_fast: workspace_test_options.fail_fast, log_level, - filter: test_options.filter.is_some(), - reporter: test_options.reporter, - junit_path: test_options.junit_path, + filter: workspace_test_options.filter.is_some(), + reporter: workspace_test_options.reporter, + junit_path: workspace_test_options.junit_path, specifier: TestSpecifierOptions { - filter: TestFilter::from_flag(&test_options.filter), - shuffle: test_options.shuffle, - trace_leaks: test_options.trace_leaks, + filter: TestFilter::from_flag(&workspace_test_options.filter), + shuffle: workspace_test_options.shuffle, + trace_leaks: workspace_test_options.trace_leaks, }, }, ) @@ -1838,34 +1847,47 @@ pub async fn run_tests_with_watch( let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_communicator.clone())?; let cli_options = factory.cli_options(); - let test_options = cli_options.resolve_test_options(test_flags)?; + let workspace_test_options = + cli_options.resolve_workspace_test_options(&test_flags); let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - if let Some(set) = &test_options.files.include { - let watch_paths = set.base_paths(); - if !watch_paths.is_empty() { - let _ = watcher_communicator.watch_paths(watch_paths); - } - } - let graph_kind = cli_options.type_check_mode().as_graph_kind(); let log_level = cli_options.log_level(); let cli_options = cli_options.clone(); let module_graph_creator = factory.module_graph_creator().await?; let file_fetcher = factory.file_fetcher()?; - let test_modules = if test_options.doc { - collect_specifiers( - test_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - |e| is_supported_test_ext(e.path), - ) - } else { - collect_specifiers( - test_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_test_path_predicate, - ) - }?; + let members_with_test_options = + cli_options.resolve_test_options_for_members(&test_flags)?; + let watch_paths = members_with_test_options + .iter() + .filter_map(|(_, test_options)| { + test_options + .files + .include + .as_ref() + .map(|set| set.base_paths()) + }) + .flatten() + .collect::>(); + let _ = watcher_communicator.watch_paths(watch_paths); + let test_modules = members_with_test_options + .iter() + .map(|(_, test_options)| { + collect_specifiers( + test_options.files.clone(), + cli_options.vendor_dir_path().map(ToOwned::to_owned), + if workspace_test_options.doc { + Box::new(|e: WalkEntry| is_supported_test_ext(e.path)) + as Box bool> + } else { + Box::new(is_supported_test_path_predicate) + }, + ) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(); let permissions = Permissions::from_options(&cli_options.permissions_options()?)?; @@ -1898,8 +1920,8 @@ pub async fn run_tests_with_watch( let specifiers_with_mode = fetch_specifiers_with_test_mode( &cli_options, file_fetcher, - test_options.files.clone(), - &test_options.doc, + members_with_test_options.into_iter().map(|(_, v)| v.files), + &workspace_test_options.doc, ) .await? .into_iter() @@ -1915,7 +1937,7 @@ pub async fn run_tests_with_watch( ) .await?; - if test_options.no_run { + if workspace_test_options.no_run { return Ok(()); } @@ -1938,16 +1960,16 @@ pub async fn run_tests_with_watch( )) }, )?, - concurrent_jobs: test_options.concurrent_jobs, - fail_fast: test_options.fail_fast, + concurrent_jobs: workspace_test_options.concurrent_jobs, + fail_fast: workspace_test_options.fail_fast, log_level, - filter: test_options.filter.is_some(), - reporter: test_options.reporter, - junit_path: test_options.junit_path, + filter: workspace_test_options.filter.is_some(), + reporter: workspace_test_options.reporter, + junit_path: workspace_test_options.junit_path, specifier: TestSpecifierOptions { - filter: TestFilter::from_flag(&test_options.filter), - shuffle: test_options.shuffle, - trace_leaks: test_options.trace_leaks, + filter: TestFilter::from_flag(&workspace_test_options.filter), + shuffle: workspace_test_options.shuffle, + trace_leaks: workspace_test_options.trace_leaks, }, }, ) diff --git a/cli/tools/vendor/build.rs b/cli/tools/vendor/build.rs index 5aef631928..a4424e3f32 100644 --- a/cli/tools/vendor/build.rs +++ b/cli/tools/vendor/build.rs @@ -81,8 +81,8 @@ pub async fn build< build_graph, parsed_source_cache, output_dir, - maybe_original_import_map: original_import_map, - maybe_jsx_import_source: jsx_import_source, + maybe_original_import_map, + maybe_jsx_import_source, resolver, environment, } = input; @@ -90,12 +90,12 @@ pub async fn build< let output_dir_specifier = ModuleSpecifier::from_directory_path(output_dir).unwrap(); - if let Some(original_im) = &original_import_map { + if let Some(original_im) = &maybe_original_import_map { validate_original_import_map(original_im, &output_dir_specifier)?; } // add the jsx import source to the entry points to ensure it is always vendored - if let Some(jsx_import_source) = jsx_import_source { + if let Some(jsx_import_source) = maybe_jsx_import_source { if let Some(specifier_text) = jsx_import_source.maybe_specifier_text() { if let Ok(specifier) = resolver.resolve( &specifier_text, @@ -171,8 +171,8 @@ pub async fn build< graph: &graph, modules: &all_modules, mappings: &mappings, - original_import_map, - jsx_import_source, + maybe_original_import_map, + maybe_jsx_import_source, resolver, parsed_source_cache, })?; diff --git a/cli/tools/vendor/import_map.rs b/cli/tools/vendor/import_map.rs index 68f2530d71..644e84a7b3 100644 --- a/cli/tools/vendor/import_map.rs +++ b/cli/tools/vendor/import_map.rs @@ -59,7 +59,7 @@ impl<'a> ImportMapBuilder<'a> { pub fn into_import_map( self, - original_import_map: Option<&ImportMap>, + maybe_original_import_map: Option<&ImportMap>, ) -> ImportMap { fn get_local_imports( new_relative_path: &str, @@ -99,7 +99,7 @@ impl<'a> ImportMapBuilder<'a> { let mut import_map = ImportMap::new(self.base_dir.clone()); - if let Some(original_im) = original_import_map { + if let Some(original_im) = maybe_original_import_map { let original_base_dir = ModuleSpecifier::from_directory_path( original_im .base_url() @@ -183,8 +183,8 @@ pub struct BuildImportMapInput<'a> { pub modules: &'a [&'a Module], pub graph: &'a ModuleGraph, pub mappings: &'a Mappings, - pub original_import_map: Option<&'a ImportMap>, - pub jsx_import_source: Option<&'a JsxImportSourceConfig>, + pub maybe_original_import_map: Option<&'a ImportMap>, + pub maybe_jsx_import_source: Option<&'a JsxImportSourceConfig>, pub resolver: &'a dyn deno_graph::source::Resolver, pub parsed_source_cache: &'a ParsedSourceCache, } @@ -197,8 +197,8 @@ pub fn build_import_map( modules, graph, mappings, - original_import_map, - jsx_import_source, + maybe_original_import_map, + maybe_jsx_import_source, resolver, parsed_source_cache, } = input; @@ -212,7 +212,7 @@ pub fn build_import_map( } // add the jsx import source to the destination import map, if mapped in the original import map - if let Some(jsx_import_source) = jsx_import_source { + if let Some(jsx_import_source) = maybe_jsx_import_source { if let Some(specifier_text) = jsx_import_source.maybe_specifier_text() { if let Ok(resolved_url) = resolver.resolve( &specifier_text, @@ -228,7 +228,7 @@ pub fn build_import_map( } } - Ok(builder.into_import_map(original_import_map).to_json()) + Ok(builder.into_import_map(maybe_original_import_map).to_json()) } fn visit_modules( diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs index a8d8000d8f..2dfa71c44c 100644 --- a/cli/tools/vendor/mod.rs +++ b/cli/tools/vendor/mod.rs @@ -48,10 +48,17 @@ pub async fn vendor( validate_options(&mut cli_options, &output_dir)?; let factory = CliFactory::from_cli_options(Arc::new(cli_options)); let cli_options = factory.cli_options(); + if cli_options.workspace.config_folders().len() > 1 { + bail!("deno vendor is not supported in a workspace. Set `\"vendor\": true` in the workspace deno.json file instead"); + } let entry_points = resolve_entry_points(&vendor_flags, cli_options.initial_cwd())?; - let jsx_import_source = cli_options.to_maybe_jsx_import_source_config()?; + let jsx_import_source = + cli_options.workspace.to_maybe_jsx_import_source_config()?; let module_graph_creator = factory.module_graph_creator().await?.clone(); + let workspace_resolver = factory.workspace_resolver().await?; + let root_folder = cli_options.workspace.root_folder().1; + let maybe_config_file = root_folder.deno_json.as_ref(); let output = build::build(build::BuildInput { entry_points, build_graph: move |entry_points| { @@ -64,7 +71,7 @@ pub async fn vendor( }, parsed_source_cache: factory.parsed_source_cache(), output_dir: &output_dir, - maybe_original_import_map: factory.maybe_import_map().await?.as_deref(), + maybe_original_import_map: workspace_resolver.maybe_import_map(), maybe_jsx_import_source: jsx_import_source.as_ref(), resolver: factory.resolver().await?.as_graph_resolver(), environment: &build::RealVendorEnvironment, @@ -91,7 +98,7 @@ pub async fn vendor( let try_add_import_map = vendored_count > 0; let modified_result = maybe_update_config_file( &output_dir, - cli_options, + maybe_config_file, try_add_import_map, try_add_node_modules_dir, ); @@ -100,8 +107,9 @@ pub async fn vendor( if modified_result.added_node_modules_dir { let node_modules_path = cli_options.node_modules_dir_path().cloned().or_else(|| { - cli_options - .maybe_config_file_specifier() + maybe_config_file + .as_ref() + .map(|d| &d.specifier) .filter(|c| c.scheme() == "file") .and_then(|c| c.to_file_path().ok()) .map(|config_path| config_path.parent().unwrap().join("node_modules")) @@ -176,7 +184,7 @@ fn validate_options( let import_map_specifier = options .resolve_specified_import_map_specifier()? .or_else(|| { - let config_file = options.maybe_config_file().as_ref()?; + let config_file = options.workspace.root_folder().1.deno_json.as_ref()?; config_file .to_import_map_specifier() .ok() @@ -229,12 +237,12 @@ fn validate_options( fn maybe_update_config_file( output_dir: &Path, - options: &CliOptions, + maybe_config_file: Option<&Arc>, try_add_import_map: bool, try_add_node_modules_dir: bool, ) -> ModifiedResult { assert!(output_dir.is_absolute()); - let config_file = match options.maybe_config_file() { + let config_file = match maybe_config_file { Some(config_file) => config_file, None => return ModifiedResult::default(), }; @@ -245,7 +253,6 @@ fn maybe_update_config_file( let fmt_config_options = config_file .to_fmt_config() .ok() - .flatten() .map(|config| config.options) .unwrap_or_default(); let result = update_config_file( diff --git a/cli/tools/vendor/test.rs b/cli/tools/vendor/test.rs index 830d5f8f0c..ac07c47d17 100644 --- a/cli/tools/vendor/test.rs +++ b/cli/tools/vendor/test.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use std::sync::Arc; use deno_ast::ModuleSpecifier; +use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::error::AnyError; @@ -182,7 +183,7 @@ pub struct VendorOutput { pub struct VendorTestBuilder { entry_points: Vec, loader: TestLoader, - original_import_map: Option, + maybe_original_import_map: Option, environment: TestVendorEnvironment, jsx_import_source_config: Option, } @@ -207,7 +208,7 @@ impl VendorTestBuilder { &mut self, import_map: ImportMap, ) -> &mut Self { - self.original_import_map = Some(import_map); + self.maybe_original_import_map = Some(import_map); self } @@ -234,7 +235,7 @@ impl VendorTestBuilder { let parsed_source_cache = ParsedSourceCache::default(); let resolver = Arc::new(build_resolver( self.jsx_import_source_config.clone(), - self.original_import_map.clone(), + self.maybe_original_import_map.clone(), )); super::build::build(super::build::BuildInput { entry_points, @@ -257,7 +258,7 @@ impl VendorTestBuilder { }, parsed_source_cache: &parsed_source_cache, output_dir: &output_dir, - maybe_original_import_map: self.original_import_map.as_ref(), + maybe_original_import_map: self.maybe_original_import_map.as_ref(), maybe_jsx_import_source: self.jsx_import_source_config.as_ref(), resolver: resolver.as_graph_resolver(), environment: &self.environment, @@ -287,15 +288,18 @@ impl VendorTestBuilder { fn build_resolver( maybe_jsx_import_source_config: Option, - original_import_map: Option, + maybe_original_import_map: Option, ) -> CliGraphResolver { CliGraphResolver::new(CliGraphResolverOptions { node_resolver: None, npm_resolver: None, sloppy_imports_resolver: None, - package_json_deps_provider: Default::default(), + workspace_resolver: Arc::new(WorkspaceResolver::new_raw( + maybe_original_import_map, + Vec::new(), + deno_config::workspace::PackageJsonDepResolution::Enabled, + )), maybe_jsx_import_source_config, - maybe_import_map: original_import_map.map(Arc::new), maybe_vendor_dir: None, bare_node_builtins_enabled: false, }) diff --git a/cli/util/collections.rs b/cli/util/collections.rs new file mode 100644 index 0000000000..21f73024b1 --- /dev/null +++ b/cli/util/collections.rs @@ -0,0 +1,38 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::marker::PhantomData; + +pub struct CheckedSet { + _kind: PhantomData, + checked: std::collections::HashSet, +} + +impl Default for CheckedSet { + fn default() -> Self { + Self { + _kind: Default::default(), + checked: Default::default(), + } + } +} + +impl CheckedSet { + pub fn with_capacity(capacity: usize) -> Self { + Self { + _kind: PhantomData, + checked: std::collections::HashSet::with_capacity(capacity), + } + } + + pub fn insert(&mut self, value: &T) -> bool { + self.checked.insert(self.get_hash(value)) + } + + fn get_hash(&self, value: &T) -> u64 { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } +} diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index b2628760b4..176ca43f04 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -163,6 +163,9 @@ pub struct WatcherCommunicator { impl WatcherCommunicator { pub fn watch_paths(&self, paths: Vec) -> Result<(), AnyError> { + if paths.is_empty() { + return Ok(()); + } self.paths_to_watch_tx.send(paths).map_err(AnyError::from) } diff --git a/cli/util/mod.rs b/cli/util/mod.rs index 89df7bb986..69cdc77c34 100644 --- a/cli/util/mod.rs +++ b/cli/util/mod.rs @@ -2,6 +2,7 @@ // Note: Only add code in this folder that has no application specific logic pub mod checksum; +pub mod collections; pub mod console; pub mod diff; pub mod display; diff --git a/cli/worker.rs b/cli/worker.rs index 00a20ab4de..987d65192a 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -6,7 +6,6 @@ use std::rc::Rc; use std::sync::Arc; use deno_ast::ModuleSpecifier; -use deno_config::package_json::PackageJsonDeps; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::futures::FutureExt; @@ -41,7 +40,6 @@ use deno_runtime::BootstrapOptions; use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; -use deno_semver::package::PackageReqReference; use deno_terminal::colors; use tokio::select; @@ -117,7 +115,6 @@ pub struct CliMainWorkerOptions { pub unsafely_ignore_certificate_errors: Option>, pub unstable: bool, pub skip_op_registration: bool, - pub maybe_root_package_json_deps: Option, pub create_hmr_runner: Option, pub create_coverage_collector: Option, } @@ -479,29 +476,6 @@ impl CliMainWorkerFactory { let (main_module, is_main_cjs) = if let Ok(package_ref) = NpmPackageReqReference::from_specifier(&main_module) { - let package_ref = if package_ref.req().version_req.version_text() == "*" { - // When using the wildcard version, select the same version used in the - // package.json deps in order to prevent adding new dependency version - shared - .options - .maybe_root_package_json_deps - .as_ref() - .and_then(|deps| { - deps - .values() - .filter_map(|v| v.as_ref().ok()) - .find(|dep| dep.name == package_ref.req().name) - .map(|dep| { - NpmPackageReqReference::new(PackageReqReference { - req: dep.clone(), - sub_path: package_ref.sub_path().map(|s| s.to_string()), - }) - }) - }) - .unwrap_or(package_ref) - } else { - package_ref - }; if let Some(npm_resolver) = shared.npm_resolver.as_managed() { npm_resolver .add_package_reqs(&[package_ref.req().clone()]) diff --git a/tests/integration/compile_tests.rs b/tests/integration/compile_tests.rs index 0d94d43671..c902adfb2e 100644 --- a/tests/integration/compile_tests.rs +++ b/tests/integration/compile_tests.rs @@ -822,7 +822,7 @@ testing[WILDCARD]this .args("compile --output binary main.ts") .run() .assert_exit_code(0) - .assert_matches_text("Check file:///[WILDCARD]/main.ts\nCompile file:///[WILDCARD]/main.ts to binary[WILDCARD]\n"); + .assert_matches_text("Check file:///[WILDLINE]/main.ts\nCompile file:///[WILDLINE]/main.ts to binary[WILDLINE]\n"); context .new_command() @@ -835,6 +835,7 @@ testing[WILDCARD]this fn compile_npm_file_system() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "compile/npm_fs/main.ts", + copy_temp_dir: Some("compile/npm_fs"), compile_args: vec!["-A"], run_args: vec![], output_file: "compile/npm_fs/main.out", @@ -849,6 +850,7 @@ fn compile_npm_file_system() { fn compile_npm_bin_esm() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:@denotest/bin/cli-esm", + copy_temp_dir: None, compile_args: vec![], run_args: vec!["this", "is", "a", "test"], output_file: "npm/deno_run_esm.out", @@ -863,6 +865,7 @@ fn compile_npm_bin_esm() { fn compile_npm_bin_cjs() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:@denotest/bin/cli-cjs", + copy_temp_dir: None, compile_args: vec![], run_args: vec!["this", "is", "a", "test"], output_file: "npm/deno_run_cjs.out", @@ -877,6 +880,7 @@ fn compile_npm_bin_cjs() { fn compile_npm_cowsay_main() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0", + copy_temp_dir: None, compile_args: vec!["--allow-read"], run_args: vec!["Hello"], output_file: "npm/deno_run_cowsay.out", @@ -891,6 +895,7 @@ fn compile_npm_cowsay_main() { fn compile_npm_vfs_implicit_read_permissions() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "compile/vfs_implicit_read_permission/main.ts", + copy_temp_dir: Some("compile/vfs_implicit_read_permission"), compile_args: vec![], run_args: vec![], output_file: "compile/vfs_implicit_read_permission/main.out", @@ -905,6 +910,7 @@ fn compile_npm_vfs_implicit_read_permissions() { fn compile_npm_no_permissions() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0", + copy_temp_dir: None, compile_args: vec![], run_args: vec!["Hello"], output_file: "npm/deno_run_cowsay_no_permissions.out", @@ -919,6 +925,7 @@ fn compile_npm_no_permissions() { fn compile_npm_cowsay_explicit() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0/cowsay", + copy_temp_dir: None, compile_args: vec!["--allow-read"], run_args: vec!["Hello"], output_file: "npm/deno_run_cowsay.out", @@ -933,6 +940,7 @@ fn compile_npm_cowsay_explicit() { fn compile_npm_cowthink() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0/cowthink", + copy_temp_dir: None, compile_args: vec!["--allow-read"], run_args: vec!["Hello"], output_file: "npm/deno_run_cowthink.out", @@ -945,6 +953,7 @@ fn compile_npm_cowthink() { struct RunNpmBinCompileOptions<'a> { input_specifier: &'a str, + copy_temp_dir: Option<&'a str>, node_modules_dir: bool, output_file: &'a str, input_name: Option<&'a str>, @@ -955,15 +964,13 @@ struct RunNpmBinCompileOptions<'a> { } fn run_npm_bin_compile_test(opts: RunNpmBinCompileOptions) { - let context = TestContextBuilder::for_npm().use_temp_cwd().build(); - - let temp_dir = context.temp_dir(); - let main_specifier = if opts.input_specifier.starts_with("npm:") { - opts.input_specifier.to_string() - } else { - testdata_path().join(opts.input_specifier).to_string() + let builder = TestContextBuilder::for_npm(); + let context = match opts.copy_temp_dir { + Some(copy_temp_dir) => builder.use_copy_temp_dir(copy_temp_dir).build(), + None => builder.use_temp_cwd().build(), }; + let temp_dir = context.temp_dir(); let mut args = vec!["compile".to_string()]; args.extend(opts.compile_args.iter().map(|s| s.to_string())); @@ -977,7 +984,7 @@ fn run_npm_bin_compile_test(opts: RunNpmBinCompileOptions) { args.push(bin_name.to_string()); } - args.push(main_specifier); + args.push(opts.input_specifier.to_string()); // compile let output = context.new_command().args_vec(args).run(); @@ -1004,7 +1011,13 @@ fn run_npm_bin_compile_test(opts: RunNpmBinCompileOptions) { #[test] fn compile_node_modules_symlink_outside() { + // this code is using a canonicalized temp dir because otherwise + // it fails on the Windows CI because Deno makes the root directory + // a common ancestor of the symlinked temp dir and the canonicalized + // temp dir, which causes the warnings to not be surfaced + #[allow(deprecated)] let context = TestContextBuilder::for_npm() + .use_canonicalized_temp_dir() .use_copy_temp_dir("compile/node_modules_symlink_outside") .cwd("compile/node_modules_symlink_outside") .build(); @@ -1014,15 +1027,15 @@ fn compile_node_modules_symlink_outside() { .path() .join("compile") .join("node_modules_symlink_outside"); - temp_dir.create_dir_all(project_dir.join("node_modules")); - temp_dir.create_dir_all(project_dir.join("some_folder")); - temp_dir.write(project_dir.join("test.txt"), "5"); + let symlink_target_dir = temp_dir.path().join("some_folder"); + project_dir.join("node_modules").create_dir_all(); + symlink_target_dir.create_dir_all(); + let symlink_target_file = temp_dir.path().join("target.txt"); + symlink_target_file.write("5"); + let symlink_dir = project_dir.join("node_modules").join("symlink_dir"); - // create a symlink in the node_modules directory that points to a folder in the cwd - temp_dir.symlink_dir( - project_dir.join("some_folder"), - project_dir.join("node_modules").join("some_folder"), - ); + // create a symlink in the node_modules directory that points to a folder outside the project + temp_dir.symlink_dir(&symlink_target_dir, &symlink_dir); // compile folder let output = context .new_command() @@ -1032,16 +1045,16 @@ fn compile_node_modules_symlink_outside() { output.assert_matches_file( "compile/node_modules_symlink_outside/main_compile_folder.out", ); - assert!(project_dir.join("node_modules/some_folder").exists()); + assert!(symlink_dir.exists()); // Cleanup and remove the folder. The folder test is done separately from // the file symlink test because different systems would traverse // the directory items in different order. - temp_dir.remove_dir_all(project_dir.join("node_modules/some_folder")); + symlink_dir.remove_dir_all(); // create a symlink in the node_modules directory that points to a file in the cwd temp_dir.symlink_file( - project_dir.join("test.txt"), + &symlink_target_file, project_dir.join("node_modules").join("test.txt"), ); assert!(project_dir.join("node_modules/test.txt").exists()); @@ -1154,8 +1167,11 @@ fn granular_unstable_features() { #[test] fn granular_unstable_features_config_file() { - let context = TestContextBuilder::new().build(); + let context = TestContextBuilder::new().use_temp_cwd().build(); let dir = context.temp_dir(); + testdata_path() + .join("compile/unstable_features.ts") + .copy(&dir.path().join("unstable_features.ts")); let exe = if cfg!(windows) { dir.path().join("app.exe") } else { @@ -1176,7 +1192,7 @@ fn granular_unstable_features_config_file() { &dir.path().join("deno.json").to_string(), "--output", &exe.to_string_lossy(), - "./compile/unstable_features.ts", + "./unstable_features.ts", ]) .run(); output.assert_exit_code(0); diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index d0df5e6e8a..f66fc97bed 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -13051,7 +13051,7 @@ fn lsp_deno_json_workspace_fmt_config() { temp_dir.write( "deno.json", json!({ - "workspaces": ["project1", "project2"], + "workspace": ["project1", "project2"], "fmt": { "semiColons": false, }, @@ -13174,7 +13174,7 @@ fn lsp_deno_json_workspace_lint_config() { temp_dir.write( "deno.json", json!({ - "workspaces": ["project1", "project2"], + "workspace": ["project1", "project2"], "lint": { "rules": { "include": ["camelcase"], @@ -13315,7 +13315,7 @@ fn lsp_deno_json_workspace_import_map() { temp_dir.write( "project1/deno.json", json!({ - "workspaces": ["project2"], + "workspace": ["project2"], "imports": { "foo": "./foo1.ts", }, @@ -13376,7 +13376,7 @@ fn lsp_deno_json_workspace_jsr_resolution() { temp_dir.write( "deno.json", json!({ - "workspaces": ["project1"], + "workspace": ["project1"], }) .to_string(), ); diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index b8263be291..d4d1fea2eb 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -1113,7 +1113,9 @@ fn lock_deno_json_package_json_deps_workspace() { // deno.json let deno_json = temp_dir.join("deno.json"); - deno_json.write_json(&json!({})); + deno_json.write_json(&json!({ + "nodeModulesDir": true + })); // package.json let package_json = temp_dir.join("package.json"); @@ -1147,16 +1149,23 @@ fn lock_deno_json_package_json_deps_workspace() { let lockfile = temp_dir.join("deno.lock"); let esm_basic_integrity = get_lockfile_npm_package_integrity(&lockfile, "@denotest/esm-basic@1.0.0"); + let cjs_default_export_integrity = get_lockfile_npm_package_integrity( + &lockfile, + "@denotest/cjs-default-export@1.0.0", + ); - // no "workspace" because deno isn't smart enough to figure this out yet - // since it discovered the package.json in a folder different from the lockfile lockfile.assert_matches_json(json!({ "version": "3", "packages": { "specifiers": { + "npm:@denotest/cjs-default-export@1": "npm:@denotest/cjs-default-export@1.0.0", "npm:@denotest/esm-basic@1": "npm:@denotest/esm-basic@1.0.0" }, "npm": { + "@denotest/cjs-default-export@1.0.0": { + "integrity": cjs_default_export_integrity, + "dependencies": {} + }, "@denotest/esm-basic@1.0.0": { "integrity": esm_basic_integrity, "dependencies": {} @@ -1164,6 +1173,22 @@ fn lock_deno_json_package_json_deps_workspace() { } }, "remote": {}, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@denotest/cjs-default-export@1" + ] + }, + "members": { + "package-a": { + "packageJson": { + "dependencies": [ + "npm:@denotest/esm-basic@1" + ] + } + } + } + } })); // run a command that causes discovery of the root package.json beside the lockfile @@ -1201,6 +1226,15 @@ fn lock_deno_json_package_json_deps_workspace() { "dependencies": [ "npm:@denotest/cjs-default-export@1" ] + }, + "members": { + "package-a": { + "packageJson": { + "dependencies": [ + "npm:@denotest/esm-basic@1" + ] + } + } } } }); diff --git a/tests/integration/watcher_tests.rs b/tests/integration/watcher_tests.rs index 252411627d..7864938f87 100644 --- a/tests/integration/watcher_tests.rs +++ b/tests/integration/watcher_tests.rs @@ -1547,7 +1547,7 @@ async fn run_watch_dynamic_imports() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); wait_contains("Process started", &mut stderr_lines).await; - wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Finished config loading.", &mut stderr_lines).await; wait_contains( "Hopefully dynamic import will be watched...", @@ -1714,7 +1714,7 @@ console.log("Listening...") .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); wait_contains("Process started", &mut stderr_lines).await; - wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Finished config loading.", &mut stderr_lines).await; wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; wait_contains("Listening...", &mut stdout_lines).await; @@ -1787,7 +1787,7 @@ export function foo() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); wait_contains("Process started", &mut stderr_lines).await; - wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Finished config loading.", &mut stderr_lines).await; wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; wait_contains("5

Hello

", &mut stdout_lines).await; @@ -1846,7 +1846,7 @@ export function foo() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); wait_contains("Process started", &mut stderr_lines).await; - wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Finished config loading.", &mut stderr_lines).await; wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; wait_contains("

asd1

", &mut stdout_lines).await; @@ -1912,7 +1912,7 @@ export function foo() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); wait_contains("Process started", &mut stderr_lines).await; - wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Finished config loading.", &mut stderr_lines).await; wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; wait_contains("2

asd1

", &mut stdout_lines).await; diff --git a/tests/registry/npm/@types/lz-string/lz-string-1.3.33.tgz b/tests/registry/npm/@types/lz-string/lz-string-1.3.33.tgz new file mode 100644 index 0000000000000000000000000000000000000000..e33d3bcdcfd7c21e98d6ce6ed810ce7ec3282dc1 GIT binary patch literal 2184 zcmV;32zU1%iwFP!000003hh~KPvb}up3g}0A4-`oFbd8a2P}8DT2JDD_wuIL3^#i^ zX*+f&@nXB9ZktSQ#ecu5wjBouuxX|cM<+)UnMe#XSJT)wg!&% z?)gW(UOzlM1p35r`?J|>9l^Us>!8(W)LZ!ZuHIZR7q& z2H@!1F7yJA$BMt-dA~zbZLwI%U^Y)+*V}`B;7OsxG{Jef5RxT z5(a8s1wSDCNt)oKqDk%hDp^z$5)=<}1ra7dGqmZYo|=$EEy&49?lwg;%ejbd+7#rP zO;Z`8aIP7Bfo{~Yf8kz2GsH6$VJMblsV8DTAOqD&v7s&!u!*?h+O{k)T_jxDeq7>$RY z?T*!fis|6*N);|__iT9X0#c04f%^r9T`&h<;2-v&Qw8gvqp{^UFdS!ic7N2fEnKh% z?cRCE9-P7_WF8FhSlD@;p>=I{KrO$`iwuoIWv1bl9vIR=V;8B)P?qN z^kr~$=h{A8hy=BJ*O$wM34J=5-2p=0*VQ%iFV5h^#8XftKu;>@CXl5FC? zwrdXuWL0}OaL4$wibjv!HUEX}SXD5`wnP5uj)(o+Uh*@t4K)%n53CG|_Tc7LAqo9H zcdRukbS$%nfDSRQEiO3T?|kd@_@^J?y8e$Xv(vYX$bWVj57+Z5=E>Q!4%dP8vl_VtsaX_GjKD zn^Ap;seWTULME)XQ>^7#LZ4!$Ut_>7%#&oHPHMFo7S?oPcp|D{L6G9>suTr#MkcJo z5jDhW*43qbiB}5XU#VDNRhdexdt2Z-nRFhmTvW!t^E<(dNG)P2;j(tiMv`j!SsBEt zv=$E^p@$5RE_}LooWKR$Xc{f(@(DESjqjk@IH@0;G>_r5?`rxEU+~!Hv4_>}1WRMg z_4H{dCWw5+!j$HFKRIi`AD}Hc%2woCyj$IHqc(jhgCyZG;I(QjA{N6SxWx4s{Xsvq3F{~x{G|6ga+YT!P6OdCdP2dPF< z&|UH5Hyzrlrb428#fW)Qn}lLkTVRIRIAnR$&66l3A)O5^CaaStMfNTJt%^R94t92Y z?uB$~zG8BH9?O$y6ki;={(FDj$?^~UeNC~RVhUmwafL~dZ>+8b++BoU_1&lT@uU8v zH1pH`gelB!sQLomB>G~_!kNIMKacb_{m8DEMoz$BbSe{5j zpI!qh5i)xVgu-K?gFmTqh&Wu~f0*DVxZ|5B6DgMX!lV}+zRi#osm$1yBEa0+o@4gM zSCPY}Oa^+i+-?k3zxj<#2P!41F1 z-5u_g4ln(9!}Xrz`elnLp58$b$LD7m47-mRy3Ne&XsCWpe1C6L6P;83l{8fX)6{vjU@Ofh7=@C+h ze~CcZioGpfy2ziAzEOn1fd2&T(n(fFY581oW} z<;+|Jjd|9=L6PU<-hQs;D@S$ak!K0(8=Li{*v^%Bnc&_AeqVIww7<(2`Mwr@$jDV0 zoTTk<3eP;UV9J-9_{oy9OWhfa*rg6dMS)}Mcs1Y*aj<7i~VKblY%IqJvx8gaJ4dLq1lv8@PNh1 zw@b;}lzEvLepJ$Z0lkg!k3~1eR*F~BV^d1DdgThdmV4ixmlRLjcSbyq*Wglt7b?W} z2)MTzm-L(dD5p1embkBXuHX8wf7VgH#Pjx@@rx4i^U8R*|I=tT?(~0Jt!Cq`|MMCn z>jzY*9Z@-f%I!vPCA&FS@D@Pb2O?=0Eu+;c7JROB`ypSXHcHlyxWA)Ru;zdEB(}co z7PHhn)_)rnQsL6S)=8}FT-?z3MvJIYy-AeHkZ2z28{KBfXwLEGQ%ERD*Ob@#+rM*_ zBTUdlSeT%FNE5obRm|d@PN)FQTH#V@jHV`H6uhC!1FApL#4-pdOWe8SY==mrU$58O zR(7N}=e6pi?C;cEws9}8_@6N~CtX@cygohlrp@W3G3E8+iSK#GyxC|T9edx=G4>Bw zz2!GN|8UZ3PQ5y3+-ud3{&M6U`UjK7rhl>^`CNuo)2LT=uD|thcpGoyZM=>DoAF=a KGmU%zC;$Mes#}@> literal 0 HcmV?d00001 diff --git a/tests/registry/npm/@types/lz-string/lz-string-1.5.0.tgz b/tests/registry/npm/@types/lz-string/lz-string-1.5.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6d7a01381162759ddd38f81673d3f3d0893160c3 GIT binary patch literal 1133 zcmV-z1d{t7iwFP!000001MOGcQ{y%e=b67^>z6{u%?BZ2hNqfDxoFO@Gq!V-N5@uT zT^-9~DRAix|9e+*z7B3mrw2o)u#+KZwfoyIX;;br{8g%o7mMc6i&3l9y12N2qc;A% z=$3;6N(^*?L(x)1Ar-f4Gm{{?(`L~p$QfHbBNxIAHnWXH#v(HG^q;)_hdNpcEf zo>W4Lxk7$biYiuI6pdPP!m1^g5*ffHWUOMdH7u&QP%O1!UNHt@4oMbQ3uc275EpAG zStU_H%v8(^JbDmAf)))RN~;W^w7t8yVyKWpETu?zjF^y$WVK|4GETsp=S;$h${1MT zM#DNahEf*i29Xz__qW?{$5kd)3M!@#exh+~6iV_{s(smR=X}XGRZjR3KSiyN4(oV=!^=y)<&#QmGTH zHYt;ubC-$bw-NE$ulcGf&@?l8slXH(%b!`IG)Jq?!%BbCn_2d z2>oGn?F0m92$R77LsM!q{B$h;!xp>ySu+8hShcO5#mVc?9N55#Esh*Za^MQcytdPFo^+ja2M zkErkIkovwC1-P^^;vm|oU(=A--~=?($r%RzxDHmQ7`1!@gt{K7k#zdMtrCjp^)w_q zP#6%$MZ8ceZ^PXx9KTXOe5c2>pZ@`I24m7#rq6EUiT7Wp)q6DmyS>)?{QptCzmM7Ww z+reQQl3ROA{KcRRZP&GRiL9^p8`kEft)=jNw|BH;*o=wvaBKNptf=veWj5~j zS`|z54-dWhM6YbXziV*%EnRmw^T^(HwT5+k|4Jw4o!;r4-U9svbH;dc02TlM+d(x( literal 0 HcmV?d00001 diff --git a/tests/registry/npm/@types/lz-string/registry.json b/tests/registry/npm/@types/lz-string/registry.json new file mode 100644 index 0000000000..677c5d24a5 --- /dev/null +++ b/tests/registry/npm/@types/lz-string/registry.json @@ -0,0 +1,113 @@ +{ + "_id": "@types/lz-string", + "_rev": "554-923923210a37cca16c53a3e8dd472e22", + "name": "@types/lz-string", + "description": "Stub TypeScript definitions entry for lz-string, which provides its own types definitions", + "dist-tags": { + "latest": "1.5.0" + }, + "versions": { + "1.3.33": { + "name": "@types/lz-string", + "version": "1.3.33", + "description": "TypeScript definitions for lz-string", + "license": "MIT", + "contributors": [ + { + "name": "Roman Nikitin", + "url": "https://github.com/M0ns1gn0r", + "githubUsername": "M0ns1gn0r" + } + ], + "main": "", + "types": "index", + "repository": { + "type": "git", + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git" + }, + "scripts": {}, + "dependencies": {}, + "typesPublisherContentHash": "37e0f8cf2fb1fe08bdcc8e21278c91217d4a03d1cd6b32fc0eaec30757c6d4b1", + "typeScriptVersion": "2.0", + "_id": "@types/lz-string@1.3.33", + "dist": { + "integrity": "sha512-yWj3OnlKlwNpq9+Jh/nJkVAD3ta8Abk2kIRpjWpVkDlAD43tn6Q6xk5hurp84ndcq54jBDBGCD/WcIR0pspG0A==", + "shasum": "de2d6105ea7bcaf67dd1d9451d580700d30473fc", + "tarball": "http://localhost:4260/@types/lz-string/lz-string-1.3.33.tgz", + "fileCount": 4, + "unpackedSize": 5960, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcZIpFCRA9TVsSAnZWagAAYm8QAJCahktTDI8BUR8q+Qra\nzsvv7Vbb20Ti7uoh97yzJiEC8UEWCGnxpZouWr3xoy0FjByYIvGmHqslGohP\nksiikCXiy+5pfT0Yi3M4QeADPlQjqUVTweCoeMmpUaHWGBdqG2kE6tnioCQy\nAL9n/YnQc10b5SE/XYgKHuBN/HJ5tx1Ejcg/o7qJG/2cUe/1K1asIMFUockV\ncgwFXFl8OSMTcA3Bs0C84zIdcaC4njVqUIQOWqdgKbe1vs+O/Zf/OdiYQh9f\nZZMXffwJKVpLSfhOTeDHeD1WMNmiww+FVIikeUIihp7Xahk9YbrLtE5BUSgG\nl9/vNfzUDW+J5oJb6n8k9WojHjte00inzMa1O7QVT7cUC+e5Nup1df0VErNF\nVuaBMUy2o0LViCVcXOYUnDBQCoaKpQ8cIVhtl0VLFrOdyn+a0blcwaNNrvE1\nFKb+OgBqipIDwAx1QghV45MPtRzI/TLYeSZtHoOYVJ8zc11FzjaQ33NZj/5w\nVzMnRkmjpwF5j++JSOa3687iKJTgrJ6XHYliYpxRRpJY3Oa4Nl0/G+xMm1BS\n0ueZuqpM+h2ZMuG7TQOeDKtTll7tsuKwy2UlkkP2uJOVurqJkCvcK/ImG25W\nKENAcoJvsk956vlbvJCdqvIcV5OF5XhgQh10gaAfHl+pJiLbCBhHpeWd95+Y\n5/3T\r\n=MjUN\r\n-----END PGP SIGNATURE-----\r\n", + "signatures": [ + { + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", + "sig": "MEUCIHlPUpoP+v+OWyats51tKkKMx97XrotlO8GzoVtS22/KAiEAxLb7ultFaZZIfGVCNeHE/X+J9I58zkNA6a8LKcm2Wns=" + } + ] + }, + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "directories": {}, + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/lz-string_1.3.33_1550092868900_0.5979975733337666" + }, + "_hasShrinkwrap": false + }, + "1.5.0": { + "name": "@types/lz-string", + "version": "1.5.0", + "description": "Stub TypeScript definitions entry for lz-string, which provides its own types definitions", + "main": "", + "scripts": {}, + "license": "MIT", + "dependencies": { + "lz-string": "*" + }, + "deprecated": "This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed.", + "_id": "@types/lz-string@1.5.0", + "dist": { + "integrity": "sha512-s84fKOrzqqNCAPljhVyC5TjAo6BH4jKHw9NRNFNiRUY5QSgZCmVm5XILlWbisiKl+0OcS7eWihmKGS5akc2iQw==", + "shasum": "2f15d2dd9dbfb344f3d31aee5a82cf350b037e5f", + "tarball": "http://localhost:4260/@types/lz-string/lz-string-1.5.0.tgz", + "fileCount": 4, + "unpackedSize": 1754, + "signatures": [ + { + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", + "sig": "MEQCIFwugI1BNDwbq90OnD5/morYlSnSQheJEnyTkclzw0SKAiAThdPB2+I/hjRlN5URdZcK4v0XXcVnh5xvMSf7SgQZ8A==" + } + ], + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkECMgACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmopOQ//U+7G8WFrWQ3ecjTZrMAAqmwWNK1jgA3r0PonmwkiDjQlNAHj\r\nXAfJK8YSuFBrl8buIAkoJT9i+H6bpHIShj5fA4FKVtA1ihcwclAdvvoilwH2\r\nNCvoFeZZgrZB6y5e6AvGDHY67C2DzQ9XhfqYM0myyXS+of2gfznAPVqXwGCs\r\nWW39ee/WAbBEoN2Z1/hEAh+W51hV0HUjs39sbupo0vOHy9GdYuVJtTMeqesF\r\nmCfDDaM1FxbsMFccy8qRsihD26iwBMRa+W3+208gCc0i9xs8wRc+8GQcAGWd\r\nxSrTEgRd8hfBs6bxDKlSD3Qg7pTq3L+HvlUZGL2AHSbC6k/MCNduHhxEcrrj\r\nssFE4iuCievfQsd0CC4rI/8s5MDGwdQ+nldv0rYjsSphjLgHDly0LE1kAbNv\r\nxZWFXmFb7318wmbC38KYDn1I0b6YndHQFu1usVJ+Z107H/mxWRZeRg0THlD8\r\n3LuLEkCJqRddGmLkSQkJ6IZtX8H9EuuhU4ny6Xb3FYFhnXWmw7YSuvrrfSgs\r\nPlLlscCRsXgWYPzQ7h8mOyE4MoHfrjzcgFKIUgWPvW6EprDPAKu28vIXnn7j\r\nG0CiCYL+IWWTqa6pKkOJsE1ILkPYTZj/592zfGPzspl9Kfb/4+IaDMmApBVO\r\n51TMBjyXgYYDajmh6y8/U389X93/bIV/wjY=\r\n=935O\r\n-----END PGP SIGNATURE-----\r\n" + }, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "directories": {}, + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/lz-string_1.5.0_1678779167950_0.5151061207876493" + }, + "_hasShrinkwrap": false + } + }, + "license": "MIT", + "readmeFilename": "", + "users": { + "flumpus-dev": true + } +} diff --git a/tests/registry/npm/lz-string/lz-string-1.3.6.tgz b/tests/registry/npm/lz-string/lz-string-1.3.6.tgz new file mode 100644 index 0000000000000000000000000000000000000000..153b8733ca9cbd9066f613e00e1b1698b1e124e3 GIT binary patch literal 30429 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 z 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 F0sw*vJ2wCT literal 0 HcmV?d00001 diff --git a/tests/registry/npm/lz-string/registry.json b/tests/registry/npm/lz-string/registry.json new file mode 100644 index 0000000000..5bf86f4ce7 --- /dev/null +++ b/tests/registry/npm/lz-string/registry.json @@ -0,0 +1,165 @@ +{ + "_id": "lz-string", + "_rev": "45-a265b69aa69ae37972e7a7931a9be325", + "name": "lz-string", + "description": "LZ-based compression algorithm", + "dist-tags": { + "latest": "1.5.0" + }, + "versions": { + "1.3.6": { + "name": "lz-string", + "version": "1.3.6", + "license": "WTFPL", + "description": "LZ-based compression algorithm", + "homepage": "http://pieroxy.net/blog/pages/lz-string/index.html", + "keywords": [ + "lz", + "compression", + "string" + ], + "main": "libs/lz-string.js", + "bin": { + "lz-string": "bin/bin.js" + }, + "scripts": {}, + "dependencies": {}, + "devDependencies": {}, + "repository": { + "type": "git", + "url": "https://github.com/pieroxy/lz-string.git" + }, + "bugs": { + "url": "https://github.com/pieroxy/lz-string/issues" + }, + "directories": { + "test": "tests" + }, + "author": { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + }, + "_id": "lz-string@1.3.6", + "dist": { + "shasum": "cc91b00d3264b15402e428e76dfeb709193bc10f", + "tarball": "http://localhost:4260/lz-string/lz-string-1.3.6.tgz", + "integrity": "sha512-gIHN4Nkmln8SrIRAXJ3qzGH7gJ8WjAORiwD+SB3PYW4n4ri+gP257pXSeyw/VGOV+6ZLIkZmNfK4xT6e2U5QIQ==", + "signatures": [ + { + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", + "sig": "MEQCICsj8exNp9xi4L5Kz31ojhaj18oeqnD4vzlhr/RMaAIiAiA/3mY8M6oycukeCebQdfWQtZC640OyMjQO11da2GnGGg==" + } + ] + }, + "_from": "./", + "_npmVersion": "1.3.10", + "_npmUser": { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + }, + "maintainers": [ + { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + } + ] + }, + "1.5.0": { + "name": "lz-string", + "version": "1.5.0", + "license": "MIT", + "filename": "lz-string.js", + "description": "LZ-based compression algorithm", + "homepage": "http://pieroxy.net/blog/pages/lz-string/index.html", + "keywords": [ + "lz", + "compression", + "string" + ], + "main": "libs/lz-string.js", + "typings": "typings/lz-string.d.ts", + "bin": { + "lz-string": "bin/bin.js" + }, + "scripts": {}, + "dependencies": {}, + "devDependencies": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/pieroxy/lz-string.git" + }, + "bugs": { + "url": "https://github.com/pieroxy/lz-string/issues" + }, + "directories": { + "test": "tests" + }, + "author": { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + }, + "autoupdate": { + "source": "git", + "target": "git://github.com/pieroxy/lz-string.git", + "basePath": "libs/", + "files": [ + "lz-string.js", + "lz-string.min.js", + "base64-string.js" + ] + }, + "gitHead": "4a94308c1e684fb98866f7ba1288f3db6d9f8801", + "_id": "lz-string@1.5.0", + "_nodeVersion": "16.19.1", + "_npmVersion": "8.19.3", + "dist": { + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "shasum": "c1ab50f77887b712621201ba9fd4e3a6ed099941", + "tarball": "http://localhost:4260/lz-string/lz-string-1.5.0.tgz", + "fileCount": 16, + "unpackedSize": 175825, + "signatures": [ + { + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", + "sig": "MEQCIDFXe2mJhe/c2RygpDTZFwYF+ZLzmWmrobWbcX05nZzgAiB2NY0LGdJ8X/8K5Y24goCdb/HvaDnCxn4BdQm7jfU/Jw==" + } + ], + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkAwBbACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrCaw/+L77yb5aRlRo8abeR0BMuhftlzyMGzGh+asUdX+afBEOGYTyJ\r\n2XM9fqdpZrtJv3+q9D+gqnLg7MoRQQkmvC+U0HTHEWtEJNaIH1at/IMhi+xB\r\n5/3Jho9VOtLhPto1/ld1CVu0JTxdUTDiTjpE26a4wdd7qMDhjaSJkypjtutn\r\nfwZXUs2YzKZQ1h6RlLSpB2b19KwiVjFsqnV+tIgs1WmjcrC7RxqEtA2yDdt5\r\nfWDM3lLgSGjFkedydnOskMNqLaL9COVzQ8iuFXGeS/NJvhi64gKDcGFl2ztx\r\nQS30dC/ud+EkF3omjN/cFhAnBCcXLvK52MxglR4+Ph4QAa4f3NhbUZbc1i4G\r\nf3Qa8GxOPHAAfR4X7z4E2fKlpybz7it3Sl5SJ8RQo3X24TGR69rM4Flc7G7S\r\ncNUtFXu/zJLmxYlc3u0Qcbx8sbdkg65V9y0n1aFXpwlofPbSqjOp/M4F5Yu4\r\nqQjGV6n8fz7CUb5ZpcEWFgztd+pi+7G0hhbKWrznOPxss9LWjr1j5PbIsY/9\r\nfZNeHynSv7Bkx2X7Cr7UPVZr9zNWLXdT7bxcI3ielAUVAeQRtRB9ostiCGvL\r\nChEZ3dZmIbYAeeSgL/175rpseCxPotDpLJ9xMBcyozfC1bbedA2LFbIkDzwA\r\nDKmVP8Nl733GahX08ZwxYSsoIU6oh9hYTeQ=\r\n=6NYt\r\n-----END PGP SIGNATURE-----\r\n" + }, + "_npmUser": { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + }, + "maintainers": [ + { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/lz-string_1.5.0_1677918299665_0.8929158378621742" + }, + "_hasShrinkwrap": false + } + }, + "maintainers": [], + "time": {}, + "repository": {}, + "users": {}, + "homepage": "http://pieroxy.net/blog/pages/lz-string/index.html", + "keywords": [ + "lz", + "compression", + "string" + ], + "license": "MIT", + "readmeFilename": "README.md", + "author": { + "name": "pieroxy", + "email": "pieroxy@pieroxy.net" + }, + "bugs": { + "url": "https://github.com/pieroxy/lz-string/issues" + } +} diff --git a/tests/specs/bench/workspace/__test__.jsonc b/tests/specs/bench/workspace/__test__.jsonc new file mode 100644 index 0000000000..fa1bd69da6 --- /dev/null +++ b/tests/specs/bench/workspace/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "tests": { + "root": { + "args": "bench", + "output": "root.out" + }, + "package": { + "args": "bench", + "cwd": "package-b", + "output": "package_b.out" + } + } +} diff --git a/tests/specs/bench/workspace/deno.json b/tests/specs/bench/workspace/deno.json new file mode 100644 index 0000000000..b72d884428 --- /dev/null +++ b/tests/specs/bench/workspace/deno.json @@ -0,0 +1,6 @@ +{ + "workspace": [ + "./package-a", + "./package-b" + ] +} diff --git a/tests/specs/bench/workspace/package-a/deno.json b/tests/specs/bench/workspace/package-a/deno.json new file mode 100644 index 0000000000..e6e03ae858 --- /dev/null +++ b/tests/specs/bench/workspace/package-a/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/a", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/bench/workspace/package-a/mod.bench.ts b/tests/specs/bench/workspace/package-a/mod.bench.ts new file mode 100644 index 0000000000..5fbf79e66c --- /dev/null +++ b/tests/specs/bench/workspace/package-a/mod.bench.ts @@ -0,0 +1,7 @@ +import { add } from "./mod.ts"; + +Deno.bench("add", () => { + if (add(1, 2) !== 3) { + throw new Error("failed"); + } +}); diff --git a/tests/specs/bench/workspace/package-a/mod.ts b/tests/specs/bench/workspace/package-a/mod.ts new file mode 100644 index 0000000000..8d9b8a22a1 --- /dev/null +++ b/tests/specs/bench/workspace/package-a/mod.ts @@ -0,0 +1,3 @@ +export function add(a: number, b: number): number { + return a + b; +} diff --git a/tests/specs/bench/workspace/package-b/deno.json b/tests/specs/bench/workspace/package-b/deno.json new file mode 100644 index 0000000000..f131c191b6 --- /dev/null +++ b/tests/specs/bench/workspace/package-b/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/b", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/bench/workspace/package-b/mod.bench.ts b/tests/specs/bench/workspace/package-b/mod.bench.ts new file mode 100644 index 0000000000..ca972c39e1 --- /dev/null +++ b/tests/specs/bench/workspace/package-b/mod.bench.ts @@ -0,0 +1,7 @@ +import { addOne } from "./mod.ts"; + +Deno.bench("addOne", () => { + if (addOne(1) !== 2) { + throw new Error("failed"); + } +}); diff --git a/tests/specs/bench/workspace/package-b/mod.ts b/tests/specs/bench/workspace/package-b/mod.ts new file mode 100644 index 0000000000..53148ac2f2 --- /dev/null +++ b/tests/specs/bench/workspace/package-b/mod.ts @@ -0,0 +1,5 @@ +import { add } from "@scope/a"; + +export function addOne(a: number): number { + return add(a, 1); +} diff --git a/tests/specs/bench/workspace/package_b.out b/tests/specs/bench/workspace/package_b.out new file mode 100644 index 0000000000..bb452e3e9f --- /dev/null +++ b/tests/specs/bench/workspace/package_b.out @@ -0,0 +1,9 @@ +Check file:///[WILDLINE]/package-b/mod.bench.ts +cpu: [WILDLINE] +runtime: [WILDLINE] + +file:///[WILDLINE]/package-b/mod.bench.ts +benchmark[WILDLINE] +---[WILDLINE] +addOne[WILDLINE] + diff --git a/tests/specs/bench/workspace/root.out b/tests/specs/bench/workspace/root.out new file mode 100644 index 0000000000..897cd7d3c6 --- /dev/null +++ b/tests/specs/bench/workspace/root.out @@ -0,0 +1,16 @@ +Check file:///[WILDLINE]/package-a/mod.bench.ts +Check file:///[WILDLINE]/package-b/mod.bench.ts +cpu: [WILDLINE] +runtime: [WILDLINE] + +file:///[WILDLINE]/package-a/mod.bench.ts +benchmark[WILDLINE] +---[WILDLINE] +add[WILDLINE] + + +file:///[WILDLINE]/package-b/mod.bench.ts +benchmark[WILDLINE] +---[WILDLINE] +addOne[WILDLINE] + diff --git a/tests/specs/check/workspace/__test__.jsonc b/tests/specs/check/workspace/__test__.jsonc new file mode 100644 index 0000000000..5df2fd70ec --- /dev/null +++ b/tests/specs/check/workspace/__test__.jsonc @@ -0,0 +1,22 @@ +{ + "tests": { + "root": { + // todo(dsherret): should be possible to not provide args here + "args": "check package-a/mod.ts package-b/mod.ts", + "output": "root.out", + "exitCode": 1 + }, + "package_a": { + "args": "check mod.ts", + "cwd": "package-a", + "output": "package_a.out", + "exitCode": 0 + }, + "package_b": { + "args": "check mod.ts", + "cwd": "package-b", + "output": "package_b.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/check/workspace/deno.json b/tests/specs/check/workspace/deno.json new file mode 100644 index 0000000000..b72d884428 --- /dev/null +++ b/tests/specs/check/workspace/deno.json @@ -0,0 +1,6 @@ +{ + "workspace": [ + "./package-a", + "./package-b" + ] +} diff --git a/tests/specs/check/workspace/package-a/deno.json b/tests/specs/check/workspace/package-a/deno.json new file mode 100644 index 0000000000..e6e03ae858 --- /dev/null +++ b/tests/specs/check/workspace/package-a/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/a", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/check/workspace/package-a/mod.ts b/tests/specs/check/workspace/package-a/mod.ts new file mode 100644 index 0000000000..8d9b8a22a1 --- /dev/null +++ b/tests/specs/check/workspace/package-a/mod.ts @@ -0,0 +1,3 @@ +export function add(a: number, b: number): number { + return a + b; +} diff --git a/tests/specs/check/workspace/package-b/deno.json b/tests/specs/check/workspace/package-b/deno.json new file mode 100644 index 0000000000..f131c191b6 --- /dev/null +++ b/tests/specs/check/workspace/package-b/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/b", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/check/workspace/package-b/mod.ts b/tests/specs/check/workspace/package-b/mod.ts new file mode 100644 index 0000000000..554ba5674e --- /dev/null +++ b/tests/specs/check/workspace/package-b/mod.ts @@ -0,0 +1,4 @@ +import { add } from "@scope/a"; + +const test: string = add(1, 2); +console.log(test); diff --git a/tests/specs/check/workspace/package_a.out b/tests/specs/check/workspace/package_a.out new file mode 100644 index 0000000000..faecec870e --- /dev/null +++ b/tests/specs/check/workspace/package_a.out @@ -0,0 +1 @@ +Check file:///[WILDLINE]/package-a/mod.ts diff --git a/tests/specs/check/workspace/package_b.out b/tests/specs/check/workspace/package_b.out new file mode 100644 index 0000000000..8db6c5476c --- /dev/null +++ b/tests/specs/check/workspace/package_b.out @@ -0,0 +1,5 @@ +Check file:///[WILDLINE]/package-b/mod.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const test: string = add(1, 2); + ~~~~ + at [WILDLINE] diff --git a/tests/specs/check/workspace/root.out b/tests/specs/check/workspace/root.out new file mode 100644 index 0000000000..21ae7acd3f --- /dev/null +++ b/tests/specs/check/workspace/root.out @@ -0,0 +1,6 @@ +Check file:///[WILDLINE]/package-a/mod.ts +Check file:///[WILDLINE]/package-b/mod.ts +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const test: string = add(1, 2); + ~~~~ + at [WILDLINE] diff --git a/tests/specs/compile/npmrc/.npmrc b/tests/specs/compile/npmrc_auto_install/.npmrc similarity index 100% rename from tests/specs/compile/npmrc/.npmrc rename to tests/specs/compile/npmrc_auto_install/.npmrc diff --git a/tests/specs/compile/npmrc_auto_install/__test__.jsonc b/tests/specs/compile/npmrc_auto_install/__test__.jsonc new file mode 100644 index 0000000000..f4ba8ee283 --- /dev/null +++ b/tests/specs/compile/npmrc_auto_install/__test__.jsonc @@ -0,0 +1,22 @@ +{ + "tempDir": true, + "steps": [{ + "if": "unix", + "args": "compile --output main main.js", + "output": "[WILDCARD]" + }, { + "if": "unix", + "commandName": "./main", + "args": [], + "output": "main.out" + }, { + "if": "windows", + "args": "compile --output main.exe main.js", + "output": "[WILDCARD]" + }, { + "if": "windows", + "commandName": "./main.exe", + "args": [], + "output": "main.out" + }] +} diff --git a/tests/specs/compile/npmrc_auto_install/deno.json b/tests/specs/compile/npmrc_auto_install/deno.json new file mode 100644 index 0000000000..176354f98f --- /dev/null +++ b/tests/specs/compile/npmrc_auto_install/deno.json @@ -0,0 +1,3 @@ +{ + "nodeModulesDir": true +} diff --git a/tests/specs/compile/npmrc/main.js b/tests/specs/compile/npmrc_auto_install/main.js similarity index 100% rename from tests/specs/compile/npmrc/main.js rename to tests/specs/compile/npmrc_auto_install/main.js diff --git a/tests/specs/compile/npmrc/main.out b/tests/specs/compile/npmrc_auto_install/main.out similarity index 100% rename from tests/specs/compile/npmrc/main.out rename to tests/specs/compile/npmrc_auto_install/main.out diff --git a/tests/specs/compile/npmrc/package.json b/tests/specs/compile/npmrc_auto_install/package.json similarity index 100% rename from tests/specs/compile/npmrc/package.json rename to tests/specs/compile/npmrc_auto_install/package.json diff --git a/tests/specs/compile/npmrc_byonm/.npmrc b/tests/specs/compile/npmrc_byonm/.npmrc new file mode 100644 index 0000000000..13552ad61f --- /dev/null +++ b/tests/specs/compile/npmrc_byonm/.npmrc @@ -0,0 +1,4 @@ +@denotest:registry=http://localhost:4261/ +//localhost:4261/:_authToken=private-reg-token +@denotest2:registry=http://localhost:4262/ +//localhost:4262/:_authToken=private-reg-token2 diff --git a/tests/specs/compile/npmrc/__test__.jsonc b/tests/specs/compile/npmrc_byonm/__test__.jsonc similarity index 100% rename from tests/specs/compile/npmrc/__test__.jsonc rename to tests/specs/compile/npmrc_byonm/__test__.jsonc diff --git a/tests/specs/compile/npmrc/install.out b/tests/specs/compile/npmrc_byonm/install.out similarity index 100% rename from tests/specs/compile/npmrc/install.out rename to tests/specs/compile/npmrc_byonm/install.out diff --git a/tests/specs/compile/npmrc_byonm/main.js b/tests/specs/compile/npmrc_byonm/main.js new file mode 100644 index 0000000000..66b3936360 --- /dev/null +++ b/tests/specs/compile/npmrc_byonm/main.js @@ -0,0 +1,8 @@ +import { getValue, setValue } from "@denotest/basic"; +import * as test from "@denotest2/basic"; + +console.log(getValue()); +setValue(42); +console.log(getValue()); + +console.log(test.getValue()); diff --git a/tests/specs/compile/npmrc_byonm/main.out b/tests/specs/compile/npmrc_byonm/main.out new file mode 100644 index 0000000000..bbe210bdbc --- /dev/null +++ b/tests/specs/compile/npmrc_byonm/main.out @@ -0,0 +1,3 @@ +0 +42 +0 diff --git a/tests/specs/compile/npmrc_byonm/package.json b/tests/specs/compile/npmrc_byonm/package.json new file mode 100644 index 0000000000..274d1ed7f4 --- /dev/null +++ b/tests/specs/compile/npmrc_byonm/package.json @@ -0,0 +1,8 @@ +{ + "name": "npmrc_test", + "version": "0.0.1", + "dependencies": { + "@denotest/basic": "1.0.0", + "@denotest2/basic": "1.0.0" + } +} diff --git a/tests/specs/fmt/workspace/__test__.jsonc b/tests/specs/fmt/workspace/__test__.jsonc new file mode 100644 index 0000000000..80e3639f9d --- /dev/null +++ b/tests/specs/fmt/workspace/__test__.jsonc @@ -0,0 +1,26 @@ +{ + "tests": { + "root_fmt": { + "tempDir": true, + "args": "fmt", + "output": "root_fmt.out" + }, + "root_check": { + "args": "fmt --check", + "exitCode": 1, + "output": "root_check.out" + }, + "sub_dir_fmt": { + "tempDir": true, + "args": "fmt", + "cwd": "a", + "output": "a_fmt.out" + }, + "subdir_check": { + "args": "fmt --check", + "cwd": "a", + "exitCode": 1, + "output": "a_check.out" + } + } +} diff --git a/tests/specs/fmt/workspace/a/a.ts b/tests/specs/fmt/workspace/a/a.ts new file mode 100644 index 0000000000..7b2a346011 --- /dev/null +++ b/tests/specs/fmt/workspace/a/a.ts @@ -0,0 +1 @@ +console.log("a"); diff --git a/tests/specs/fmt/workspace/a/deno.json b/tests/specs/fmt/workspace/a/deno.json new file mode 100644 index 0000000000..0dd8856d77 --- /dev/null +++ b/tests/specs/fmt/workspace/a/deno.json @@ -0,0 +1,5 @@ +{ + "fmt": { + "semiColons": false + } +} diff --git a/tests/specs/fmt/workspace/a_check.out b/tests/specs/fmt/workspace/a_check.out new file mode 100644 index 0000000000..150f18b2e6 --- /dev/null +++ b/tests/specs/fmt/workspace/a_check.out @@ -0,0 +1,6 @@ + +from [WILDLINE]a.ts: +1 | -console.log("a"); +1 | +console.log('a') + +error: Found 1 not formatted file in 2 files diff --git a/tests/specs/fmt/workspace/a_fmt.out b/tests/specs/fmt/workspace/a_fmt.out new file mode 100644 index 0000000000..18da23175c --- /dev/null +++ b/tests/specs/fmt/workspace/a_fmt.out @@ -0,0 +1,2 @@ +[WILDLINE]a.ts +Checked 2 files diff --git a/tests/specs/fmt/workspace/b/b.ts b/tests/specs/fmt/workspace/b/b.ts new file mode 100644 index 0000000000..8609d07554 --- /dev/null +++ b/tests/specs/fmt/workspace/b/b.ts @@ -0,0 +1 @@ +console.log('a'); diff --git a/tests/specs/fmt/workspace/b/deno.json b/tests/specs/fmt/workspace/b/deno.json new file mode 100644 index 0000000000..388b147499 --- /dev/null +++ b/tests/specs/fmt/workspace/b/deno.json @@ -0,0 +1,5 @@ +{ + "fmt": { + "singleQuote": false + } +} diff --git a/tests/specs/fmt/workspace/deno.json b/tests/specs/fmt/workspace/deno.json new file mode 100644 index 0000000000..2b030605de --- /dev/null +++ b/tests/specs/fmt/workspace/deno.json @@ -0,0 +1,9 @@ +{ + "workspace": [ + "./a", + "./b" + ], + "fmt": { + "singleQuote": true + } +} diff --git a/tests/specs/fmt/workspace/root.ts b/tests/specs/fmt/workspace/root.ts new file mode 100644 index 0000000000..9300c8169d --- /dev/null +++ b/tests/specs/fmt/workspace/root.ts @@ -0,0 +1 @@ +console.log("root") diff --git a/tests/specs/fmt/workspace/root_check.out b/tests/specs/fmt/workspace/root_check.out new file mode 100644 index 0000000000..323f43f349 --- /dev/null +++ b/tests/specs/fmt/workspace/root_check.out @@ -0,0 +1,16 @@ + +from [WILDLINE]root.ts: +1 | -console.log("root") +1 | +console.log('root'); + + +from [WILDLINE]workspace[WILDCHAR]a[WILDCHAR]a.ts: +1 | -console.log("a"); +1 | +console.log('a') + + +from [WILDLINE]workspace[WILDCHAR]b[WILDCHAR]b.ts: +1 | -console.log('a'); +1 | +console.log("a"); + +error: Found 3 not formatted files in 7 files diff --git a/tests/specs/fmt/workspace/root_fmt.out b/tests/specs/fmt/workspace/root_fmt.out new file mode 100644 index 0000000000..306ecada65 --- /dev/null +++ b/tests/specs/fmt/workspace/root_fmt.out @@ -0,0 +1,4 @@ +[WILDLINE]root.ts +[WILDLINE]a.ts +[WILDLINE]b.ts +Checked 6 files diff --git a/tests/specs/install/future_install_global/__test__.jsonc b/tests/specs/install/future_install_global/__test__.jsonc index be6fcab972..e646164c6f 100644 --- a/tests/specs/install/future_install_global/__test__.jsonc +++ b/tests/specs/install/future_install_global/__test__.jsonc @@ -5,7 +5,7 @@ }, "steps": [ { - "args": "install --global --root ./bins --name deno-test-bin ./main.js", + "args": "install --global --root ./bins --name deno-test-bin ./pkg/main.js", "output": "install.out" }, { diff --git a/tests/specs/install/future_install_global/install.out b/tests/specs/install/future_install_global/install.out index adb8b45989..58cd88ada1 100644 --- a/tests/specs/install/future_install_global/install.out +++ b/tests/specs/install/future_install_global/install.out @@ -1,5 +1,4 @@ Download http://localhost:4260/@denotest/esm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz -Initialize @denotest/esm-basic@1.0.0 ✅ Successfully installed deno-test-bin[WILDCARD] [WILDCARD] diff --git a/tests/specs/install/future_install_global/main.js b/tests/specs/install/future_install_global/main.js deleted file mode 100644 index 2ba55540b5..0000000000 --- a/tests/specs/install/future_install_global/main.js +++ /dev/null @@ -1,3 +0,0 @@ -import { setValue } from "@denotest/esm-basic"; - -setValue(5); diff --git a/tests/specs/install/no_future_install_global/main.js b/tests/specs/install/future_install_global/pkg/main.js similarity index 100% rename from tests/specs/install/no_future_install_global/main.js rename to tests/specs/install/future_install_global/pkg/main.js diff --git a/tests/specs/install/future_install_global/package.json b/tests/specs/install/future_install_global/pkg/package.json similarity index 70% rename from tests/specs/install/future_install_global/package.json rename to tests/specs/install/future_install_global/pkg/package.json index f3b6cb7be1..57493f5569 100644 --- a/tests/specs/install/future_install_global/package.json +++ b/tests/specs/install/future_install_global/pkg/package.json @@ -1,7 +1,6 @@ { "name": "deno-test-bin", "dependencies": { - "@denotest/esm-basic": "*" }, "type": "module" } diff --git a/tests/specs/install/no_future_install_global/__test__.jsonc b/tests/specs/install/no_future_install_global/__test__.jsonc index ca351ee0df..657cdab803 100644 --- a/tests/specs/install/no_future_install_global/__test__.jsonc +++ b/tests/specs/install/no_future_install_global/__test__.jsonc @@ -2,7 +2,7 @@ "tempDir": true, "steps": [ { - "args": "install --root ./bins --name deno-test-bin ./main.js", + "args": "install --root ./bins --name deno-test-bin ./pkg/main.js", "output": "install.out" }, { diff --git a/tests/specs/install/no_future_install_global/install.out b/tests/specs/install/no_future_install_global/install.out index f3a394c6ff..b1933f536a 100644 --- a/tests/specs/install/no_future_install_global/install.out +++ b/tests/specs/install/no_future_install_global/install.out @@ -1,5 +1,6 @@ ⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag. Download http://localhost:4260/@denotest/esm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz +[# there shouldn't be a line saying it initialized the node_modules folder here because this is a global install] ✅ Successfully installed deno-test-bin[WILDCARD] [WILDCARD] diff --git a/tests/specs/install/no_future_install_global/pkg/main.js b/tests/specs/install/no_future_install_global/pkg/main.js new file mode 100644 index 0000000000..6268d71362 --- /dev/null +++ b/tests/specs/install/no_future_install_global/pkg/main.js @@ -0,0 +1,3 @@ +import { setValue } from "npm:@denotest/esm-basic"; + +setValue(5); diff --git a/tests/specs/install/no_future_install_global/package.json b/tests/specs/install/no_future_install_global/pkg/package.json similarity index 100% rename from tests/specs/install/no_future_install_global/package.json rename to tests/specs/install/no_future_install_global/pkg/package.json diff --git a/tests/specs/lint/no_slow_types_workspace/deno.json b/tests/specs/lint/no_slow_types_workspace/deno.json index e3dd981e50..499731c1ec 100644 --- a/tests/specs/lint/no_slow_types_workspace/deno.json +++ b/tests/specs/lint/no_slow_types_workspace/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "./a", "./b", "./c" diff --git a/tests/specs/lint/workspace/__test__.jsonc b/tests/specs/lint/workspace/__test__.jsonc new file mode 100644 index 0000000000..6581222c9d --- /dev/null +++ b/tests/specs/lint/workspace/__test__.jsonc @@ -0,0 +1,15 @@ +{ + "tests": { + "root": { + "args": "lint", + "exitCode": 1, + "output": "root.out" + }, + "subdir": { + "args": "lint", + "cwd": "package-a", + "exitCode": 1, + "output": "a.out" + } + } +} diff --git a/tests/specs/lint/workspace/a.out b/tests/specs/lint/workspace/a.out new file mode 100644 index 0000000000..52f05af990 --- /dev/null +++ b/tests/specs/lint/workspace/a.out @@ -0,0 +1,32 @@ +error[no-eval]: `eval` call is not allowed + --> [WILDLINE]a.ts:1:1 + | +1 | eval(""); + | ^^^^^^^^ + = hint: Remove the use of `eval` + + docs: https://lint.deno.land/rules/no-eval + + +error[no-await-in-loop]: Unexpected `await` inside a loop. + --> [WILDLINE]a.ts:4:3 + | +4 | await Deno.open("test"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + = hint: Remove `await` in loop body, store all promises generated and then `await Promise.all(storedPromises)` after the loop + + docs: https://lint.deno.land/rules/no-await-in-loop + + +error[no-explicit-any]: `any` type is not allowed + --> [WILDLINE]a.ts:9:25 + | +9 | export function test(): any { + | ^^^ + = hint: Use a specific type other than `any` + + docs: https://lint.deno.land/rules/no-explicit-any + + +Found 3 problems +Checked 1 file diff --git a/tests/specs/lint/workspace/deno.json b/tests/specs/lint/workspace/deno.json new file mode 100644 index 0000000000..2dab3a4ecd --- /dev/null +++ b/tests/specs/lint/workspace/deno.json @@ -0,0 +1,11 @@ +{ + "workspace": [ + "./package-a", + "./package-b" + ], + "lint": { + "rules": { + "include": ["no-eval"] + } + } +} diff --git a/tests/specs/lint/workspace/package-a/a.ts b/tests/specs/lint/workspace/package-a/a.ts new file mode 100644 index 0000000000..52bd0cbc00 --- /dev/null +++ b/tests/specs/lint/workspace/package-a/a.ts @@ -0,0 +1,11 @@ +eval(""); + +for (let i = 0; i < 10; i++) { + await Deno.open("test"); +} + +const unused = 1; + +export function test(): any { + return {}; +} diff --git a/tests/specs/lint/workspace/package-a/deno.json b/tests/specs/lint/workspace/package-a/deno.json new file mode 100644 index 0000000000..34130b647d --- /dev/null +++ b/tests/specs/lint/workspace/package-a/deno.json @@ -0,0 +1,12 @@ +{ + "lint": { + "rules": { + "include": [ + "no-await-in-loop" + ], + "exclude": [ + "no-unused-vars" + ] + } + } +} diff --git a/tests/specs/lint/workspace/package-b/b.ts b/tests/specs/lint/workspace/package-b/b.ts new file mode 100644 index 0000000000..52bd0cbc00 --- /dev/null +++ b/tests/specs/lint/workspace/package-b/b.ts @@ -0,0 +1,11 @@ +eval(""); + +for (let i = 0; i < 10; i++) { + await Deno.open("test"); +} + +const unused = 1; + +export function test(): any { + return {}; +} diff --git a/tests/specs/lint/workspace/package-b/deno.json b/tests/specs/lint/workspace/package-b/deno.json new file mode 100644 index 0000000000..93fdf6ca79 --- /dev/null +++ b/tests/specs/lint/workspace/package-b/deno.json @@ -0,0 +1,9 @@ +{ + "lint": { + "rules": { + "exclude": [ + "no-explicit-any" + ] + } + } +} diff --git a/tests/specs/lint/workspace/root.out b/tests/specs/lint/workspace/root.out new file mode 100644 index 0000000000..1d892a93f0 --- /dev/null +++ b/tests/specs/lint/workspace/root.out @@ -0,0 +1,82 @@ +error[no-eval]: `eval` call is not allowed + --> [WILDLINE]root.ts:1:1 + | +1 | eval(""); + | ^^^^^^^^ + = hint: Remove the use of `eval` + + docs: https://lint.deno.land/rules/no-eval + + +error[no-unused-vars]: `unused` is never used + --> [WILDLINE]root.ts:7:7 + | +7 | const unused = 1; + | ^^^^^^ + = hint: If this is intentional, prefix it with an underscore like `_unused` + + docs: https://lint.deno.land/rules/no-unused-vars + + +error[no-explicit-any]: `any` type is not allowed + --> [WILDLINE]root.ts:9:25 + | +9 | export function test(): any { + | ^^^ + = hint: Use a specific type other than `any` + + docs: https://lint.deno.land/rules/no-explicit-any + + +error[no-eval]: `eval` call is not allowed + --> [WILDLINE]package-a[WILDCHAR]a.ts:1:1 + | +1 | eval(""); + | ^^^^^^^^ + = hint: Remove the use of `eval` + + docs: https://lint.deno.land/rules/no-eval + + +error[no-await-in-loop]: Unexpected `await` inside a loop. + --> [WILDLINE]package-a[WILDCHAR]a.ts:4:3 + | +4 | await Deno.open("test"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + = hint: Remove `await` in loop body, store all promises generated and then `await Promise.all(storedPromises)` after the loop + + docs: https://lint.deno.land/rules/no-await-in-loop + + +error[no-explicit-any]: `any` type is not allowed + --> [WILDLINE]package-a[WILDCHAR]a.ts:9:25 + | +9 | export function test(): any { + | ^^^ + = hint: Use a specific type other than `any` + + docs: https://lint.deno.land/rules/no-explicit-any + + +error[no-eval]: `eval` call is not allowed + --> [WILDLINE]package-b[WILDCHAR]b.ts:1:1 + | +1 | eval(""); + | ^^^^^^^^ + = hint: Remove the use of `eval` + + docs: https://lint.deno.land/rules/no-eval + + +error[no-unused-vars]: `unused` is never used + --> [WILDLINE]b.ts:7:7 + | +7 | const unused = 1; + | ^^^^^^ + = hint: If this is intentional, prefix it with an underscore like `_unused` + + docs: https://lint.deno.land/rules/no-unused-vars + + +Found 8 problems +Checked 3 files diff --git a/tests/specs/lint/workspace/root.ts b/tests/specs/lint/workspace/root.ts new file mode 100644 index 0000000000..52bd0cbc00 --- /dev/null +++ b/tests/specs/lint/workspace/root.ts @@ -0,0 +1,11 @@ +eval(""); + +for (let i = 0; i < 10; i++) { + await Deno.open("test"); +} + +const unused = 1; + +export function test(): any { + return {}; +} diff --git a/tests/specs/lint/workspace_no_slow_types/__test__.jsonc b/tests/specs/lint/workspace_no_slow_types/__test__.jsonc new file mode 100644 index 0000000000..489ee52ab9 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/__test__.jsonc @@ -0,0 +1,27 @@ +{ + "tests": { + "root": { + "args": "lint", + "exitCode": 1, + "output": "root.out" + }, + "package_a": { + "args": "lint", + "cwd": "a", + "exitCode": 1, + "output": "a.out" + }, + "package_b": { + "args": "lint", + "cwd": "b", + "exitCode": 1, + "output": "b.out" + }, + "package_c": { + "args": "lint", + "cwd": "c", + "exitCode": 0, + "output": "Checked 1 file\n" + } + } +} diff --git a/tests/specs/lint/workspace_no_slow_types/a.out b/tests/specs/lint/workspace_no_slow_types/a.out new file mode 100644 index 0000000000..12c6715bef --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/a.out @@ -0,0 +1,14 @@ +error[no-slow-types]: missing explicit return type in the public API + --> [WILDLINE]a.ts:1:17 + | +1 | export function noReturnType() { + | ^^^^^^^^^^^^ this function is missing an explicit return type + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an explicit return type + docs: https://jsr.io/go/slow-type-missing-explicit-return-type + + +Found 1 problem +Checked 1 file diff --git a/tests/specs/lint/workspace_no_slow_types/a/a.ts b/tests/specs/lint/workspace_no_slow_types/a/a.ts new file mode 100644 index 0000000000..6db944d6c5 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/a/a.ts @@ -0,0 +1,3 @@ +export function noReturnType() { + return Math.random(); +} diff --git a/tests/specs/lint/workspace_no_slow_types/a/deno.json b/tests/specs/lint/workspace_no_slow_types/a/deno.json new file mode 100644 index 0000000000..9547d1bd96 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/a/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/a", + "version": "1.0.0", + "exports": "./a.ts" +} diff --git a/tests/specs/lint/workspace_no_slow_types/b.out b/tests/specs/lint/workspace_no_slow_types/b.out new file mode 100644 index 0000000000..e3e2d575dd --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/b.out @@ -0,0 +1,14 @@ +error[no-slow-types]: missing explicit return type in the public API + --> [WILDLINE]b.ts:7:17 + | +7 | export function doesNotHaveReturnType() { + | ^^^^^^^^^^^^^^^^^^^^^ this function is missing an explicit return type + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an explicit return type + docs: https://jsr.io/go/slow-type-missing-explicit-return-type + + +Found 1 problem +Checked 1 file diff --git a/tests/specs/lint/workspace_no_slow_types/b/b.ts b/tests/specs/lint/workspace_no_slow_types/b/b.ts new file mode 100644 index 0000000000..05cb086288 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/b/b.ts @@ -0,0 +1,9 @@ +import { noReturnType } from "@scope/a"; + +export function hasReturnType(): number { + return noReturnType(); +} + +export function doesNotHaveReturnType() { + return noReturnType(); +} diff --git a/tests/specs/lint/workspace_no_slow_types/b/deno.json b/tests/specs/lint/workspace_no_slow_types/b/deno.json new file mode 100644 index 0000000000..a27c1e5cd5 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/b/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/b", + "version": "1.0.0", + "exports": "./b.ts" +} diff --git a/tests/specs/lint/workspace_no_slow_types/c/c.ts b/tests/specs/lint/workspace_no_slow_types/c/c.ts new file mode 100644 index 0000000000..3f5f461715 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/c/c.ts @@ -0,0 +1,6 @@ +import { noReturnType } from "@scope/a"; +import { hasReturnType } from "@scope/b"; + +export function myExport(): number { + return noReturnType() + hasReturnType(); +} diff --git a/tests/specs/lint/workspace_no_slow_types/c/deno.json b/tests/specs/lint/workspace_no_slow_types/c/deno.json new file mode 100644 index 0000000000..618250b98f --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/c/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/c", + "version": "1.0.0", + "exports": "./c.ts" +} diff --git a/tests/specs/lint/workspace_no_slow_types/deno.json b/tests/specs/lint/workspace_no_slow_types/deno.json new file mode 100644 index 0000000000..499731c1ec --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/deno.json @@ -0,0 +1,7 @@ +{ + "workspace": [ + "./a", + "./b", + "./c" + ] +} diff --git a/tests/specs/lint/workspace_no_slow_types/root.out b/tests/specs/lint/workspace_no_slow_types/root.out new file mode 100644 index 0000000000..50fda50c36 --- /dev/null +++ b/tests/specs/lint/workspace_no_slow_types/root.out @@ -0,0 +1,26 @@ +error[no-slow-types]: missing explicit return type in the public API + --> [WILDLINE]a.ts:1:17 + | +1 | export function noReturnType() { + | ^^^^^^^^^^^^ this function is missing an explicit return type + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an explicit return type + docs: https://jsr.io/go/slow-type-missing-explicit-return-type + + +error[no-slow-types]: missing explicit return type in the public API + --> [WILDLINE]b.ts:7:17 + | +7 | export function doesNotHaveReturnType() { + | ^^^^^^^^^^^^^^^^^^^^^ this function is missing an explicit return type + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an explicit return type + docs: https://jsr.io/go/slow-type-missing-explicit-return-type + + +Found 2 problems +Checked 3 files diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc b/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc index ed3827ef64..8c4d0fb206 100644 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/__test__.jsonc @@ -1,7 +1,28 @@ { - "envs": { - "DENO_FUTURE": "1" - }, - "args": "check --quiet main.ts", - "output": "" + "tempDir": true, + "tests": { + "byonm": { + "envs": { + "DENO_FUTURE": "1" + }, + "steps": [{ + "args": "install", + "output": "[WILDCARD]" + }, { + "args": "check --quiet main.ts", + "exitCode": 1, + "output": "expected.out" + }] + }, + "auto_install": { + "args": "check --node-modules-dir=true --quiet main.ts", + "exitCode": 1, + "output": "expected.out" + }, + "global_folder": { + "args": "check --node-modules-dir=false --quiet main.ts", + "exitCode": 1, + "output": "expected.out" + } + } } diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/expected.out b/tests/specs/npm/check_prefers_non_types_node_pkg/expected.out new file mode 100644 index 0000000000..37d41aae29 --- /dev/null +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/expected.out @@ -0,0 +1,4 @@ +error: TS2345 [ERROR]: Argument of type 'number' is not assignable to parameter of type 'string'. +console.log(compressToEncodedURIComponent(123)); + ~~~ + at file:///[WILDLINE] diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts b/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts index 8774bdbfc2..f28d132d1a 100644 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/main.ts @@ -1,3 +1,5 @@ +// this lz-string@1.5 pkg has types only in the regular package and not the @types/lz-string pkg import { compressToEncodedURIComponent } from "lz-string"; -console.log(compressToEncodedURIComponent("Hello, World!")); +// cause a deliberate type checking error +console.log(compressToEncodedURIComponent(123)); diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json deleted file mode 100644 index afe623e003..0000000000 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/@types/lz-string/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@types/lz-string", - "version": "1.5.0", - "description": "Stub TypeScript definitions entry for lz-string, which provides its own types definitions", - "main": "", - "scripts": {}, - "license": "MIT", - "dependencies": { - "lz-string": "*" - }, - "deprecated": "This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed." -} diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts deleted file mode 100644 index b6abfd8ba5..0000000000 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function compressToEncodedURIComponent(input: string): string; diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js deleted file mode 100644 index 603b710ba3..0000000000 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports.compressToEncodedURIComponent = (a) => a; diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json deleted file mode 100644 index f8bfd5d988..0000000000 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/node_modules/lz-string/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "lz-string", - "version": "1.5.0" -} \ No newline at end of file diff --git a/tests/specs/npm/check_prefers_non_types_node_pkg/package.json b/tests/specs/npm/check_prefers_non_types_node_pkg/package.json index ea3b2d26f4..a812a973e2 100644 --- a/tests/specs/npm/check_prefers_non_types_node_pkg/package.json +++ b/tests/specs/npm/check_prefers_non_types_node_pkg/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "lz-string": "*", - "@types/lz-string": "*" + "lz-string": "1.5", + "@types/lz-string": "1.5" } } diff --git a/tests/specs/npm/check_types_in_types_pkg/__test__.jsonc b/tests/specs/npm/check_types_in_types_pkg/__test__.jsonc new file mode 100644 index 0000000000..7b7a429f4d --- /dev/null +++ b/tests/specs/npm/check_types_in_types_pkg/__test__.jsonc @@ -0,0 +1,28 @@ +{ + "tempDir": true, + "tests": { + "byonm": { + "envs": { + "DENO_FUTURE": "1" + }, + "steps": [{ + "args": "install", + "output": "[WILDCARD]" + }, { + "args": "check --quiet main.ts", + "exitCode": 1, + "output": "expected.out" + }] + }, + "auto_install": { + "args": "check --node-modules-dir=true --quiet main_auto_install.ts", + "exitCode": 1, + "output": "expected.out" + }, + "global_folder": { + "args": "check --node-modules-dir=false --quiet main_auto_install.ts", + "exitCode": 1, + "output": "expected.out" + } + } +} diff --git a/tests/specs/npm/check_types_in_types_pkg/expected.out b/tests/specs/npm/check_types_in_types_pkg/expected.out new file mode 100644 index 0000000000..37d41aae29 --- /dev/null +++ b/tests/specs/npm/check_types_in_types_pkg/expected.out @@ -0,0 +1,4 @@ +error: TS2345 [ERROR]: Argument of type 'number' is not assignable to parameter of type 'string'. +console.log(compressToEncodedURIComponent(123)); + ~~~ + at file:///[WILDLINE] diff --git a/tests/specs/npm/check_types_in_types_pkg/main.ts b/tests/specs/npm/check_types_in_types_pkg/main.ts new file mode 100644 index 0000000000..adc164ea5f --- /dev/null +++ b/tests/specs/npm/check_types_in_types_pkg/main.ts @@ -0,0 +1,5 @@ +// this lz-string@1.3 pkg doesn't have types, but the @types/lz-string@1.3 does +import { compressToEncodedURIComponent } from "lz-string"; + +// cause a deliberate type checking error +console.log(compressToEncodedURIComponent(123)); diff --git a/tests/specs/npm/check_types_in_types_pkg/main_auto_install.ts b/tests/specs/npm/check_types_in_types_pkg/main_auto_install.ts new file mode 100644 index 0000000000..af47e13ac0 --- /dev/null +++ b/tests/specs/npm/check_types_in_types_pkg/main_auto_install.ts @@ -0,0 +1,6 @@ +// this lz-string@1.3 pkg doesn't have types, but the @types/lz-string@1.3 does +// @deno-types="@types/lz-string" +import { compressToEncodedURIComponent } from "lz-string"; + +// cause a deliberate type checking error +console.log(compressToEncodedURIComponent(123)); diff --git a/tests/specs/npm/check_types_in_types_pkg/package.json b/tests/specs/npm/check_types_in_types_pkg/package.json new file mode 100644 index 0000000000..e8079d1127 --- /dev/null +++ b/tests/specs/npm/check_types_in_types_pkg/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "lz-string": "1.3", + "@types/lz-string": "1.3" + } +} diff --git a/tests/specs/npm/workspace_basic/__test__.jsonc b/tests/specs/npm/workspace_basic/__test__.jsonc new file mode 100644 index 0000000000..79e059ca18 --- /dev/null +++ b/tests/specs/npm/workspace_basic/__test__.jsonc @@ -0,0 +1,35 @@ +{ + "tempDir": true, + "tests": { + "global_cache": { + "args": "run --node-modules-dir=false b/main.ts", + "output": "b/main_global_cache.out" + }, + "node_modules_dir": { + "args": "run --node-modules-dir=true b/main.ts", + "output": "b/main_node_modules_dir.out" + }, + "byonm": { + "envs": { + "DENO_FUTURE": "1" + }, + "steps": [{ + "args": "install", + "output": "[WILDCARD]" + }, { + "args": "run b/main.ts", + "output": "b/main_byonm.out" + }] + }, + "exports_sub_path_not_exists": { + "args": "run b/exports-sub-path-not-exists.ts", + "output": "b/exports-sub-path-not-exists.out", + "exitCode": 1 + }, + "no_exports_sub_path_not_exists": { + "args": "run b/no-exports-sub-path-not-exists.ts", + "output": "b/no-exports-sub-path-not-exists.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/npm/workspace_basic/a/mod.ts b/tests/specs/npm/workspace_basic/a/mod.ts new file mode 100644 index 0000000000..9f8d1c594a --- /dev/null +++ b/tests/specs/npm/workspace_basic/a/mod.ts @@ -0,0 +1,6 @@ +import { getValue, setValue } from "@denotest/esm-basic"; + +export function sayHello() { + setValue(5); + console.log("Hello", getValue()); +} diff --git a/tests/specs/npm/workspace_basic/a/package.json b/tests/specs/npm/workspace_basic/a/package.json new file mode 100644 index 0000000000..1467823cff --- /dev/null +++ b/tests/specs/npm/workspace_basic/a/package.json @@ -0,0 +1,10 @@ +{ + "name": "@denotest/a", + "version": "1.0.0", + "dependencies": { + "@denotest/esm-basic": "*" + }, + "exports": { + ".": "./mod.ts" + } +} diff --git a/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.out b/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.out new file mode 100644 index 0000000000..5e61cdfc3c --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.out @@ -0,0 +1,2 @@ +error: [ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './non-existent' is not defined by "exports" in '[WILDLINE]package.json' imported from '[WILDLINE]exports-sub-path-not-exists.ts' + at file:///[WILDLINE]exports-sub-path-not-exists.ts:1:20 diff --git a/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.ts b/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.ts new file mode 100644 index 0000000000..716f0f97d3 --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/exports-sub-path-not-exists.ts @@ -0,0 +1,2 @@ +import * as a from "@denotest/a/non-existent"; +console.log(a); diff --git a/tests/specs/npm/workspace_basic/b/main.ts b/tests/specs/npm/workspace_basic/b/main.ts new file mode 100644 index 0000000000..03956388c3 --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/main.ts @@ -0,0 +1,9 @@ +import * as a1 from "@denotest/a"; +import * as a2 from "npm:@denotest/a@1"; +import * as a3 from "npm:@denotest/a@workspace"; +import * as c from "@denotest/c"; + +a1.sayHello(); +a2.sayHello(); +a3.sayHello(); +c.sayHello(); diff --git a/tests/specs/npm/workspace_basic/b/main_byonm.out b/tests/specs/npm/workspace_basic/b/main_byonm.out new file mode 100644 index 0000000000..3a311dcd7e --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/main_byonm.out @@ -0,0 +1,4 @@ +Hello 5 +Hello 5 +Hello 5 +C: Hi! diff --git a/tests/specs/npm/workspace_basic/b/main_global_cache.out b/tests/specs/npm/workspace_basic/b/main_global_cache.out new file mode 100644 index 0000000000..1ca11026a2 --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/main_global_cache.out @@ -0,0 +1,6 @@ +Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz +Hello 5 +Hello 5 +Hello 5 +C: Hi! diff --git a/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out new file mode 100644 index 0000000000..82a49b9fe6 --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out @@ -0,0 +1,7 @@ +Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz +Initialize @denotest/esm-basic@1.0.0 +Hello 5 +Hello 5 +Hello 5 +C: Hi! diff --git a/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.out b/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.out new file mode 100644 index 0000000000..f98aa34cb4 --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.out @@ -0,0 +1,3 @@ +[# not the best error, but it did resolve because there was no exports specified] +error: Module not found "file:///[WILDLINE]/c/non-existent". + at file:///[WILDLINE]/b/no-exports-sub-path-not-exists.ts:1:20 diff --git a/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.ts b/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.ts new file mode 100644 index 0000000000..960d32870c --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/no-exports-sub-path-not-exists.ts @@ -0,0 +1,2 @@ +import * as c from "@denotest/c/non-existent"; +console.log(c); diff --git a/tests/specs/npm/workspace_basic/b/package.json b/tests/specs/npm/workspace_basic/b/package.json new file mode 100644 index 0000000000..4b876d5b9a --- /dev/null +++ b/tests/specs/npm/workspace_basic/b/package.json @@ -0,0 +1,8 @@ +{ + "name": "@denotest/b", + "version": "1.0.0", + "dependencies": { + "@denotest/a": "1", + "@denotest/c": "workspace:*" + } +} diff --git a/tests/specs/npm/workspace_basic/c/index.js b/tests/specs/npm/workspace_basic/c/index.js new file mode 100644 index 0000000000..f412f7d4b7 --- /dev/null +++ b/tests/specs/npm/workspace_basic/c/index.js @@ -0,0 +1,3 @@ +export function sayHello() { + console.log("C: Hi!"); +} diff --git a/tests/specs/npm/workspace_basic/c/package.json b/tests/specs/npm/workspace_basic/c/package.json new file mode 100644 index 0000000000..7d5ca9abf8 --- /dev/null +++ b/tests/specs/npm/workspace_basic/c/package.json @@ -0,0 +1,4 @@ +{ + "name": "@denotest/c", + "version": "1.0.0" +} diff --git a/tests/specs/npm/workspace_basic/package.json b/tests/specs/npm/workspace_basic/package.json new file mode 100644 index 0000000000..e3dd981e50 --- /dev/null +++ b/tests/specs/npm/workspace_basic/package.json @@ -0,0 +1,7 @@ +{ + "workspaces": [ + "./a", + "./b", + "./c" + ] +} diff --git a/tests/specs/publish/byonm_dep/publish.out b/tests/specs/publish/byonm_dep/publish.out index a7433f86fc..64cf909218 100644 --- a/tests/specs/publish/byonm_dep/publish.out +++ b/tests/specs/publish/byonm_dep/publish.out @@ -2,6 +2,6 @@ Check file:///[WILDLINE]/mod.ts Checking for slow types in the public API... Check file:///[WILDLINE]/mod.ts Simulating publish of @scope/package@0.0.0 with files: - file:///[WILDLINE]/deno.jsonc (300B) - file:///[WILDLINE]/mod.ts (129B) + file:///[WILDLINE]/deno.jsonc ([WILDLINE]) + file:///[WILDLINE]/mod.ts ([WILDLINE]) Warning Aborting due to --dry-run diff --git a/tests/specs/publish/workspace/__test__.jsonc b/tests/specs/publish/workspace/__test__.jsonc index 7b1c04d568..706b08ccd5 100644 --- a/tests/specs/publish/workspace/__test__.jsonc +++ b/tests/specs/publish/workspace/__test__.jsonc @@ -1,10 +1,13 @@ { - "steps": [{ - "args": "publish --token 'sadfasdf'", - "output": "workspace.out" - }, { - "cwd": "./bar", - "args": "publish --token 'sadfasdf'", - "output": "workspace_individual.out" - }] + "tests": { + "workspace": { + "args": "publish --token 'sadfasdf'", + "output": "workspace.out" + }, + "individual": { + "cwd": "./bar", + "args": "publish --token 'sadfasdf'", + "output": "workspace_individual.out" + } + } } diff --git a/tests/specs/publish/workspace/deno.json b/tests/specs/publish/workspace/deno.json index 57602aab5d..a23790570d 100644 --- a/tests/specs/publish/workspace/deno.json +++ b/tests/specs/publish/workspace/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "foo", "bar" ] diff --git a/tests/specs/publish/workspace/workspace.out b/tests/specs/publish/workspace/workspace.out index 8c57bc2dd8..3114e36dbb 100644 --- a/tests/specs/publish/workspace/workspace.out +++ b/tests/specs/publish/workspace/workspace.out @@ -1,9 +1,9 @@ Publishing a workspace... -Check file:///[WILDCARD]/foo/mod.ts -Check file:///[WILDCARD]/bar/mod.ts +Check file:///[WILDLINE]/bar/mod.ts +Check file:///[WILDLINE]/foo/mod.ts Checking for slow types in the public API... -Check file:///[WILDCARD]/foo/mod.ts -Check file:///[WILDCARD]/bar/mod.ts +Check file:///[WILDLINE]/bar/mod.ts +Check file:///[WILDLINE]/foo/mod.ts Publishing @foo/bar@1.0.0 ... Successfully published @foo/bar@1.0.0 Visit http://127.0.0.1:4250/@foo/bar@1.0.0 for details diff --git a/tests/specs/publish/workspace/workspace_individual.out b/tests/specs/publish/workspace/workspace_individual.out index edb6b53aa3..2cb0717099 100644 --- a/tests/specs/publish/workspace/workspace_individual.out +++ b/tests/specs/publish/workspace/workspace_individual.out @@ -1,6 +1,6 @@ -Check file:///[WILDCARD]/bar/mod.ts +Check file:///[WILDLINE]/bar/mod.ts Checking for slow types in the public API... -Check file:///[WILDCARD]/bar/mod.ts +Check file:///[WILDLINE]/bar/mod.ts Publishing @foo/bar@1.0.0 ... Successfully published @foo/bar@1.0.0 Visit http://127.0.0.1:4250/@foo/bar@1.0.0 for details diff --git a/tests/specs/run/no_deno_json/__test__.jsonc b/tests/specs/run/no_deno_json/__test__.jsonc index 67867f0233..5da0209b90 100644 --- a/tests/specs/run/no_deno_json/__test__.jsonc +++ b/tests/specs/run/no_deno_json/__test__.jsonc @@ -1,36 +1,43 @@ { "tempDir": true, - "steps": [{ - // --no-config - "args": "run -L debug -A --no-config noconfig.ts", - "output": "noconfig.out", - "cwd": "code" - }, { - // --no-npm - "args": "run -L debug -A --no-npm noconfig.ts", - "output": "noconfig.out", - "cwd": "code" - }, { - // not auto-discovered with env var - "args": "run -L debug -A noconfig.ts", - "output": "noconfig.out", - "cwd": "code", - "envs": { - "DENO_NO_PACKAGE_JSON": "1" + "tests": { + "no_config": { + // --no-config + "args": "run -L debug -A --no-config noconfig.ts", + "output": "noconfig.out", + "cwd": "code" + }, + "no_npm": { + // --no-npm + "args": "run -L debug -A --no-npm noconfig.ts", + "output": "noconfig.out", + "cwd": "code" + }, + "no_pkg_json_env_var": { + // not auto-discovered with env var + "args": "run -L debug -A noconfig.ts", + "output": "no_package_json.out", + "cwd": "code", + "envs": { + "DENO_NO_PACKAGE_JSON": "1" + } + }, + "no_pkg_json_imports": { + // this should not use --quiet because we should ensure no package.json install occurs + "args": "run -A no_package_json_imports.ts", + "output": "no_package_json_imports.out", + "cwd": "code" + }, + "auto_discovered": { + // auto-discovered node_modules relative package.json + "args": "run -A main.js", + "output": "code/sub_dir/main.out", + "cwd": "code/sub_dir" + }, + "auto_discovered_arg": { + // auto-discovered for local script arg + "args": "run -L debug -A code/main.ts", // notice this is not in the sub dir + "output": "main.out" } - }, { - // this should not use --quiet because we should ensure no package.json install occurs - "args": "run -A no_package_json_imports.ts", - "output": "no_package_json_imports.out", - "cwd": "code" - }, { - // auto-discovered node_modules relative package.json - "args": "run -A main.js", - "output": "code/sub_dir/main.out", - "cwd": "code/sub_dir" - }, { - // auto-discovered for local script arg - "args": "run -L debug -A code/main.ts", // notice this is not in the sub dir - "output": "main.out" - }] + } } diff --git a/tests/specs/run/no_deno_json/no_package_json.out b/tests/specs/run/no_deno_json/no_package_json.out new file mode 100644 index 0000000000..b9f9a6dea9 --- /dev/null +++ b/tests/specs/run/no_deno_json/no_package_json.out @@ -0,0 +1,4 @@ +[WILDCARD]package.json auto-discovery is disabled +[WILDCARD] +success +[WILDCARD] diff --git a/tests/specs/run/no_deno_json/noconfig.out b/tests/specs/run/no_deno_json/noconfig.out index b9f9a6dea9..000ce402b2 100644 --- a/tests/specs/run/no_deno_json/noconfig.out +++ b/tests/specs/run/no_deno_json/noconfig.out @@ -1,4 +1,3 @@ -[WILDCARD]package.json auto-discovery is disabled [WILDCARD] success [WILDCARD] diff --git a/tests/specs/run/workspaces/basic/deno.json b/tests/specs/run/workspaces/basic/deno.json index b971c4f3d6..a39778a6d6 100644 --- a/tests/specs/run/workspaces/basic/deno.json +++ b/tests/specs/run/workspaces/basic/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "foo", "bar" ], diff --git a/tests/specs/run/workspaces/basic/main.out b/tests/specs/run/workspaces/basic/main.out index 57d8c9f1ea..9806f7d637 100644 --- a/tests/specs/run/workspaces/basic/main.out +++ b/tests/specs/run/workspaces/basic/main.out @@ -2,19 +2,19 @@ "imports": { "chalk": "npm:chalk", "chalk/": "npm:/chalk/", - "qwerqwer": "jsr:qwerqwer@^0.0.0", - "qwerqwer/": "jsr:/qwerqwer@^0.0.0/", "asdfasdfasdf": "jsr:asdfasdfasdf@^0.0.0", - "asdfasdfasdf/": "jsr:/asdfasdfasdf@^0.0.0/" + "asdfasdfasdf/": "jsr:/asdfasdfasdf@^0.0.0/", + "qwerqwer": "jsr:qwerqwer@^0.0.0", + "qwerqwer/": "jsr:/qwerqwer@^0.0.0/" }, "scopes": { - "./foo/": { - "~/": "./foo/", - "foo/": "./foo/bar/" - }, "./bar/": { "@/": "./bar/", "secret_mod/": "./bar/some_mod/" + }, + "./foo/": { + "~/": "./foo/", + "foo/": "./foo/bar/" } } } diff --git a/tests/specs/run/workspaces/member_outside_root_dir/__test__.jsonc b/tests/specs/run/workspaces/member_outside_root_dir/__test__.jsonc index a7669c1ec0..3b7f35c62c 100644 --- a/tests/specs/run/workspaces/member_outside_root_dir/__test__.jsonc +++ b/tests/specs/run/workspaces/member_outside_root_dir/__test__.jsonc @@ -1,5 +1,5 @@ { - "args": "run -A main.ts", + "args": "run -A sub_dir/child/main.ts", "output": "main.out", "tempDir": true, "exitCode": 1 diff --git a/tests/specs/run/workspaces/member_outside_root_dir/main.out b/tests/specs/run/workspaces/member_outside_root_dir/main.out index 205d95aead..72f0b0b450 100644 --- a/tests/specs/run/workspaces/member_outside_root_dir/main.out +++ b/tests/specs/run/workspaces/member_outside_root_dir/main.out @@ -1 +1,3 @@ -error: Workspace member '../other_folder' is outside root configuration directory[WILDCARD] \ No newline at end of file +error: Workspace member must be nested in a directory under the workspace. + Member: file:///[WILDLINE]/sub_dir/other_folder/ + Workspace: file:///[WILDLINE]/sub_dir/child/ diff --git a/tests/specs/run/workspaces/member_outside_root_dir/deno.json b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/deno.json similarity index 82% rename from tests/specs/run/workspaces/member_outside_root_dir/deno.json rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/deno.json index 25feefad8b..189d212b54 100644 --- a/tests/specs/run/workspaces/member_outside_root_dir/deno.json +++ b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "foo", "../other_folder" ], diff --git a/tests/specs/run/workspaces/member_outside_root_dir/foo/bar/hello.ts b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/bar/hello.ts similarity index 100% rename from tests/specs/run/workspaces/member_outside_root_dir/foo/bar/hello.ts rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/bar/hello.ts diff --git a/tests/specs/run/workspaces/member_outside_root_dir/foo/deno.json b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/deno.json similarity index 100% rename from tests/specs/run/workspaces/member_outside_root_dir/foo/deno.json rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/deno.json diff --git a/tests/specs/run/workspaces/member_outside_root_dir/foo/fizz/buzz.ts b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/fizz/buzz.ts similarity index 100% rename from tests/specs/run/workspaces/member_outside_root_dir/foo/fizz/buzz.ts rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/fizz/buzz.ts diff --git a/tests/specs/run/workspaces/member_outside_root_dir/foo/mod.ts b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/mod.ts similarity index 100% rename from tests/specs/run/workspaces/member_outside_root_dir/foo/mod.ts rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/foo/mod.ts diff --git a/tests/specs/run/workspaces/member_outside_root_dir/main.ts b/tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/main.ts similarity index 100% rename from tests/specs/run/workspaces/member_outside_root_dir/main.ts rename to tests/specs/run/workspaces/member_outside_root_dir/sub_dir/child/main.ts diff --git a/tests/specs/run/workspaces/members_are_imports/deno.json b/tests/specs/run/workspaces/members_are_imports/deno.json index 56105365ab..51d1d21dee 100644 --- a/tests/specs/run/workspaces/members_are_imports/deno.json +++ b/tests/specs/run/workspaces/members_are_imports/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "foo", "bar" ], diff --git a/tests/specs/run/workspaces/nested_member/__test__.jsonc b/tests/specs/run/workspaces/nested_member/__test__.jsonc index a7669c1ec0..fe02eacbc1 100644 --- a/tests/specs/run/workspaces/nested_member/__test__.jsonc +++ b/tests/specs/run/workspaces/nested_member/__test__.jsonc @@ -2,5 +2,5 @@ "args": "run -A main.ts", "output": "main.out", "tempDir": true, - "exitCode": 1 + "exitCode": 0 } diff --git a/tests/specs/run/workspaces/nested_member/bar/deno.json b/tests/specs/run/workspaces/nested_member/bar/deno.json index ef3bfc37af..3bc0e52ec0 100644 --- a/tests/specs/run/workspaces/nested_member/bar/deno.json +++ b/tests/specs/run/workspaces/nested_member/bar/deno.json @@ -4,5 +4,6 @@ "imports": { "@/": "./", "secret_mod/": "./some_mod/" - } + }, + "exports": "./mod.ts" } diff --git a/tests/specs/run/workspaces/nested_member/deno.json b/tests/specs/run/workspaces/nested_member/deno.json index 6d9c09d4dd..8108d54fcb 100644 --- a/tests/specs/run/workspaces/nested_member/deno.json +++ b/tests/specs/run/workspaces/nested_member/deno.json @@ -1,5 +1,5 @@ { - "workspaces": [ + "workspace": [ "foo", "foo/bar" ] diff --git a/tests/specs/run/workspaces/nested_member/foo/bar/deno.json b/tests/specs/run/workspaces/nested_member/foo/bar/deno.json index d40328b367..d4948ddbbd 100644 --- a/tests/specs/run/workspaces/nested_member/foo/bar/deno.json +++ b/tests/specs/run/workspaces/nested_member/foo/bar/deno.json @@ -3,5 +3,6 @@ "version": "0.0.0", "imports": { "chalk": "npm:chalk" - } + }, + "exports": "./hello.ts" } diff --git a/tests/specs/run/workspaces/nested_member/foo/deno.json b/tests/specs/run/workspaces/nested_member/foo/deno.json index 68e053b020..6bc959c4db 100644 --- a/tests/specs/run/workspaces/nested_member/foo/deno.json +++ b/tests/specs/run/workspaces/nested_member/foo/deno.json @@ -3,5 +3,6 @@ "version": "0.0.0", "imports": { "~/": "./" - } + }, + "exports": "./mod.ts" } diff --git a/tests/specs/run/workspaces/nested_member/main.out b/tests/specs/run/workspaces/nested_member/main.out index 98598a3065..ec46186a75 100644 --- a/tests/specs/run/workspaces/nested_member/main.out +++ b/tests/specs/run/workspaces/nested_member/main.out @@ -1 +1,4 @@ -error: Workspace member 'foo/bar' is nested within other workspace member 'foo' +Download http://localhost:4260/chalk +Download http://localhost:4260/chalk/chalk-5.0.1.tgz +buzz from foo +[Function: chalk] createChalk { level: 0 } diff --git a/tests/specs/task/workspace/__test__.jsonc b/tests/specs/task/workspace/__test__.jsonc new file mode 100644 index 0000000000..b08f35afca --- /dev/null +++ b/tests/specs/task/workspace/__test__.jsonc @@ -0,0 +1,52 @@ +{ + "tests": { + "root": { + "args": "task", + "output": "root.out", + "exitCode": 1 + }, + "package_a": { + "args": "task", + "cwd": "package-a", + "output": "package-a.out", + "exitCode": 1 + }, + "package_b": { + "args": "task", + "cwd": "package-b", + "output": "package-b.out", + "exitCode": 1 + }, + "scripts": { + "args": "task", + "cwd": "scripts", + "output": "scripts.out", + "exitCode": 1 + }, + "package_b_tasks": { + "steps": [{ + "args": "task --quiet pkg-json-root", + "cwd": "package-b", + // uses the workspace as cwd + "output": "pkg-json [WILDLINE]workspace\n" + }, { + "args": "task --quiet pkg-json-root-2", + "cwd": "package-b", + // uses package-b as cwd + "output": "override [WILDLINE]package-b\n" + }, { + "args": "task --quiet echo-package-b", + "cwd": "package-b", + "output": "hi [WILDLINE]package-b\n" + }, { + "args": "task --quiet echo-root", + "cwd": "package-b", + "output": "override root [WILDLINE]package-b\n" + }, { + "args": "task --quiet echo-root", + "cwd": "package-a", + "output": "[WILDLINE]workspace\n" + }] + } + } +} diff --git a/tests/specs/task/workspace/deno.json b/tests/specs/task/workspace/deno.json new file mode 100644 index 0000000000..aead0490e4 --- /dev/null +++ b/tests/specs/task/workspace/deno.json @@ -0,0 +1,9 @@ +{ + "workspace": [ + "./package-a", + "./package-b" + ], + "tasks": { + "echo-root": "echo $PWD" + } +} diff --git a/tests/specs/task/workspace/package-a.out b/tests/specs/task/workspace/package-a.out new file mode 100644 index 0000000000..f68d5d24b3 --- /dev/null +++ b/tests/specs/task/workspace/package-a.out @@ -0,0 +1,9 @@ +Available tasks: +- echo-package-b + echo 'bye' +- pkg-json-root (workspace package.json) + echo pkg-json $PWD +- pkg-json-root-2 (workspace package.json) + echo hi +- echo-root (workspace) + echo $PWD diff --git a/tests/specs/task/workspace/package-a/deno.json b/tests/specs/task/workspace/package-a/deno.json new file mode 100644 index 0000000000..5167ad857e --- /dev/null +++ b/tests/specs/task/workspace/package-a/deno.json @@ -0,0 +1,5 @@ +{ + "tasks": { + "echo-package-b": "echo 'bye'" + } +} diff --git a/tests/specs/task/workspace/package-b.out b/tests/specs/task/workspace/package-b.out new file mode 100644 index 0000000000..4abd90d235 --- /dev/null +++ b/tests/specs/task/workspace/package-b.out @@ -0,0 +1,11 @@ +Available tasks: +- echo-package-b + echo 'hi' $PWD +- echo-root + echo 'override root' $PWD +- pkg-json-root-2 (package.json) + echo override $PWD +- package-b-json (package.json) + echo 'hi from pkg json' +- pkg-json-root (workspace package.json) + echo pkg-json $PWD diff --git a/tests/specs/task/workspace/package-b/deno.json b/tests/specs/task/workspace/package-b/deno.json new file mode 100644 index 0000000000..0c64208349 --- /dev/null +++ b/tests/specs/task/workspace/package-b/deno.json @@ -0,0 +1,6 @@ +{ + "tasks": { + "echo-package-b": "echo 'hi' $PWD", + "echo-root": "echo 'override root' $PWD" + } +} diff --git a/tests/specs/task/workspace/package-b/package.json b/tests/specs/task/workspace/package-b/package.json new file mode 100644 index 0000000000..bc1bdc7129 --- /dev/null +++ b/tests/specs/task/workspace/package-b/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "pkg-json-root-2": "echo override $PWD", + "package-b-json": "echo 'hi from pkg json'" + } +} diff --git a/tests/specs/task/workspace/package.json b/tests/specs/task/workspace/package.json new file mode 100644 index 0000000000..a468ec4944 --- /dev/null +++ b/tests/specs/task/workspace/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "pkg-json-root": "echo pkg-json $PWD", + "pkg-json-root-2": "echo hi" + } +} diff --git a/tests/specs/task/workspace/root.out b/tests/specs/task/workspace/root.out new file mode 100644 index 0000000000..808106c92f --- /dev/null +++ b/tests/specs/task/workspace/root.out @@ -0,0 +1,7 @@ +Available tasks: +- echo-root + echo $PWD +- pkg-json-root (package.json) + echo pkg-json $PWD +- pkg-json-root-2 (package.json) + echo hi diff --git a/tests/specs/task/workspace/scripts.out b/tests/specs/task/workspace/scripts.out new file mode 100644 index 0000000000..808106c92f --- /dev/null +++ b/tests/specs/task/workspace/scripts.out @@ -0,0 +1,7 @@ +Available tasks: +- echo-root + echo $PWD +- pkg-json-root (package.json) + echo pkg-json $PWD +- pkg-json-root-2 (package.json) + echo hi diff --git a/tests/specs/task/workspace/scripts/main.ts b/tests/specs/task/workspace/scripts/main.ts new file mode 100644 index 0000000000..9c11c78bb3 --- /dev/null +++ b/tests/specs/task/workspace/scripts/main.ts @@ -0,0 +1 @@ +console.log("some file"); diff --git a/tests/specs/test/workspace/__test__.jsonc b/tests/specs/test/workspace/__test__.jsonc new file mode 100644 index 0000000000..87fd3d46d9 --- /dev/null +++ b/tests/specs/test/workspace/__test__.jsonc @@ -0,0 +1,20 @@ +{ + "tests": { + "root": { + "args": "test", + "output": "root.out", + "exitCode": 1 + }, + "package_a": { + "args": "test", + "cwd": "package-a", + "output": "package_a.out" + }, + "package_b": { + "args": "test", + "cwd": "package-b", + "output": "package_b.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/test/workspace/deno.json b/tests/specs/test/workspace/deno.json new file mode 100644 index 0000000000..b72d884428 --- /dev/null +++ b/tests/specs/test/workspace/deno.json @@ -0,0 +1,6 @@ +{ + "workspace": [ + "./package-a", + "./package-b" + ] +} diff --git a/tests/specs/test/workspace/package-a/deno.json b/tests/specs/test/workspace/package-a/deno.json new file mode 100644 index 0000000000..e6e03ae858 --- /dev/null +++ b/tests/specs/test/workspace/package-a/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/a", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/test/workspace/package-a/mod.test.ts b/tests/specs/test/workspace/package-a/mod.test.ts new file mode 100644 index 0000000000..7ef57fbee1 --- /dev/null +++ b/tests/specs/test/workspace/package-a/mod.test.ts @@ -0,0 +1,7 @@ +import { add } from "./mod.ts"; + +Deno.test("add", () => { + if (add(1, 2) !== 3) { + throw new Error("failed"); + } +}); diff --git a/tests/specs/test/workspace/package-a/mod.ts b/tests/specs/test/workspace/package-a/mod.ts new file mode 100644 index 0000000000..8d9b8a22a1 --- /dev/null +++ b/tests/specs/test/workspace/package-a/mod.ts @@ -0,0 +1,3 @@ +export function add(a: number, b: number): number { + return a + b; +} diff --git a/tests/specs/test/workspace/package-b/deno.json b/tests/specs/test/workspace/package-b/deno.json new file mode 100644 index 0000000000..f131c191b6 --- /dev/null +++ b/tests/specs/test/workspace/package-b/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/b", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/test/workspace/package-b/mod.test.ts b/tests/specs/test/workspace/package-b/mod.test.ts new file mode 100644 index 0000000000..f1499a626f --- /dev/null +++ b/tests/specs/test/workspace/package-b/mod.test.ts @@ -0,0 +1,11 @@ +import { addOne } from "./mod.ts"; + +Deno.test("addOne", () => { + if (addOne(1) !== 2) { + throw new Error("failed"); + } +}); + +Deno.test("fail", () => { + throw new Error("failed"); +}); diff --git a/tests/specs/test/workspace/package-b/mod.ts b/tests/specs/test/workspace/package-b/mod.ts new file mode 100644 index 0000000000..53148ac2f2 --- /dev/null +++ b/tests/specs/test/workspace/package-b/mod.ts @@ -0,0 +1,5 @@ +import { add } from "@scope/a"; + +export function addOne(a: number): number { + return add(a, 1); +} diff --git a/tests/specs/test/workspace/package_a.out b/tests/specs/test/workspace/package_a.out new file mode 100644 index 0000000000..6ebcdfb104 --- /dev/null +++ b/tests/specs/test/workspace/package_a.out @@ -0,0 +1,6 @@ +Check file:///[WILDLINE]/package-a/mod.test.ts +running 1 test from ./mod.test.ts +add ... ok ([WILDLINE]) + +ok | 1 passed | 0 failed ([WILDLINE]) + diff --git a/tests/specs/test/workspace/package_b.out b/tests/specs/test/workspace/package_b.out new file mode 100644 index 0000000000..6c13427eea --- /dev/null +++ b/tests/specs/test/workspace/package_b.out @@ -0,0 +1,20 @@ +Check file:///[WILDLINE]/package-b/mod.test.ts +running 2 tests from ./mod.test.ts +addOne ... ok ([WILDLINE]) +fail ... FAILED ([WILDLINE]) + + ERRORS + +fail => ./mod.test.ts:9:6 +error: Error: failed + throw new Error("failed"); + ^ + at file:///[WILDLINE]/package-b/mod.test.ts:10:9 + + FAILURES + +fail => ./mod.test.ts:9:6 + +FAILED | 1 passed | 1 failed ([WILDLINE]) + +error: Test failed diff --git a/tests/specs/test/workspace/root.out b/tests/specs/test/workspace/root.out new file mode 100644 index 0000000000..30bda3ac6a --- /dev/null +++ b/tests/specs/test/workspace/root.out @@ -0,0 +1,23 @@ +Check file:///[WILDLINE]/package-a/mod.test.ts +Check file:///[WILDLINE]/package-b/mod.test.ts +running 1 test from ./package-a/mod.test.ts +add ... ok ([WILDLINE]) +running 2 tests from ./package-b/mod.test.ts +addOne ... ok ([WILDLINE]) +fail ... FAILED ([WILDLINE]) + + ERRORS + +fail => ./package-b/mod.test.ts:9:6 +error: Error: failed + throw new Error("failed"); + ^ + at file:///[WILDLINE]/package-b/mod.test.ts:10:9 + + FAILURES + +fail => ./package-b/mod.test.ts:9:6 + +FAILED | 2 passed | 1 failed ([WILDLINE]) + +error: Test failed diff --git a/tests/specs/workspaces/lockfile/__test__.jsonc b/tests/specs/workspaces/lockfile/__test__.jsonc new file mode 100644 index 0000000000..706b44f018 --- /dev/null +++ b/tests/specs/workspaces/lockfile/__test__.jsonc @@ -0,0 +1,24 @@ +{ + "tempDir": true, + "steps": [{ + "cwd": "pkg", + "args": "test", + "output": "test_pkg.out" + }, { + // the lockfile should always go to the workspace root + "args": [ + "eval", + "try { Deno.readTextFileSync('pkg/deno.lock'); console.log('should not run'); } catch {} console.log(Deno.readTextFileSync('deno.lock'))" + ], + "output": "expected-lock.out" + }, { + "args": "test", + "output": "test_root.out" + }, { + "args": [ + "eval", + "try { Deno.readTextFileSync('pkg/deno.lock'); console.log('should not run'); } catch {} console.log(Deno.readTextFileSync('deno.lock'))" + ], + "output": "expected-lock.out" + }] +} diff --git a/tests/specs/workspaces/lockfile/deno.json b/tests/specs/workspaces/lockfile/deno.json new file mode 100644 index 0000000000..79c36f622a --- /dev/null +++ b/tests/specs/workspaces/lockfile/deno.json @@ -0,0 +1,6 @@ +{ + "workspace": [ + "./pkg", + "./pkg-no-deps" + ] +} diff --git a/tests/specs/workspaces/lockfile/expected-lock.out b/tests/specs/workspaces/lockfile/expected-lock.out new file mode 100644 index 0000000000..dcc479a201 --- /dev/null +++ b/tests/specs/workspaces/lockfile/expected-lock.out @@ -0,0 +1,24 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@denotest/add@1": "jsr:@denotest/add@1.0.0" + }, + "jsr": { + "@denotest/add@1.0.0": { + "integrity": "3b2e675c1ad7fba2a45bc251992e01aff08a3c974ac09079b11e6a5b95d4bfcb" + } + } + }, + "remote": {}, + "workspace": { + "members": { + "pkg": { + "dependencies": [ + "jsr:@denotest/add@1" + ] + } + } + } +} + diff --git a/tests/specs/workspaces/lockfile/integration.test.ts b/tests/specs/workspaces/lockfile/integration.test.ts new file mode 100644 index 0000000000..91e921b334 --- /dev/null +++ b/tests/specs/workspaces/lockfile/integration.test.ts @@ -0,0 +1,7 @@ +import { add } from "@scope/pkg"; + +Deno.test("should add", () => { + if (add(1, 2) !== 3) { + throw new Error("failed"); + } +}); diff --git a/tests/specs/workspaces/lockfile/pkg-no-deps/deno.jsonc b/tests/specs/workspaces/lockfile/pkg-no-deps/deno.jsonc new file mode 100644 index 0000000000..123cc3b9a0 --- /dev/null +++ b/tests/specs/workspaces/lockfile/pkg-no-deps/deno.jsonc @@ -0,0 +1,7 @@ +{ + // this package shouldn't be included in the lockfile members + // because it has no dependencies + "name": "@scope/pkg2", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/workspaces/lockfile/pkg-no-deps/mod.ts b/tests/specs/workspaces/lockfile/pkg-no-deps/mod.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/specs/workspaces/lockfile/pkg/deno.jsonc b/tests/specs/workspaces/lockfile/pkg/deno.jsonc new file mode 100644 index 0000000000..7bd6ab450f --- /dev/null +++ b/tests/specs/workspaces/lockfile/pkg/deno.jsonc @@ -0,0 +1,8 @@ +{ + "name": "@scope/pkg", + "version": "1.0.0", + "exports": "./mod.ts", + "imports": { + "@denotest/add": "jsr:@denotest/add@1" + } +} diff --git a/tests/specs/workspaces/lockfile/pkg/mod.test.ts b/tests/specs/workspaces/lockfile/pkg/mod.test.ts new file mode 100644 index 0000000000..9e7a8c445f --- /dev/null +++ b/tests/specs/workspaces/lockfile/pkg/mod.test.ts @@ -0,0 +1,7 @@ +import { add } from "./mod.ts"; + +Deno.test("should add", () => { + if (add(1, 2) !== 3) { + throw new Error("failed"); + } +}); diff --git a/tests/specs/workspaces/lockfile/pkg/mod.ts b/tests/specs/workspaces/lockfile/pkg/mod.ts new file mode 100644 index 0000000000..f69572b492 --- /dev/null +++ b/tests/specs/workspaces/lockfile/pkg/mod.ts @@ -0,0 +1,5 @@ +import * as denotestAdd from "@denotest/add"; + +export function add(a: number, b: number) { + return denotestAdd.add(a, b); +} diff --git a/tests/specs/workspaces/lockfile/test_pkg.out b/tests/specs/workspaces/lockfile/test_pkg.out new file mode 100644 index 0000000000..da13b7cca1 --- /dev/null +++ b/tests/specs/workspaces/lockfile/test_pkg.out @@ -0,0 +1,9 @@ +Download http://127.0.0.1:4250/@denotest/add/meta.json +Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json +Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts +Check file:///[WILDLINE]/mod.test.ts +running 1 test from ./mod.test.ts +should add ... ok ([WILDLINE]) + +ok | 1 passed | 0 failed ([WILDLINE]) + diff --git a/tests/specs/workspaces/lockfile/test_root.out b/tests/specs/workspaces/lockfile/test_root.out new file mode 100644 index 0000000000..2c62b615b1 --- /dev/null +++ b/tests/specs/workspaces/lockfile/test_root.out @@ -0,0 +1,9 @@ +Check file:///[WILDLINE]/integration.test.ts +Check file:///[WILDLINE]/pkg/mod.test.ts +running 1 test from ./integration.test.ts +should add ... ok ([WILDLINE]) +running 1 test from ./pkg/mod.test.ts +should add ... ok ([WILDLINE]) + +ok | 2 passed | 0 failed ([WILDLINE]) + diff --git a/tests/specs/workspaces/non_fatal_diagnostics/__test__.jsonc b/tests/specs/workspaces/non_fatal_diagnostics/__test__.jsonc new file mode 100644 index 0000000000..ab79054b88 --- /dev/null +++ b/tests/specs/workspaces/non_fatal_diagnostics/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "tests": { + "root": { + "args": "lint", + "output": "lint.out" + }, + "subdir": { + "cwd": "sub", + "args": "lint", + "output": "lint.out" + } + } +} diff --git a/tests/specs/workspaces/non_fatal_diagnostics/deno.json b/tests/specs/workspaces/non_fatal_diagnostics/deno.json new file mode 100644 index 0000000000..983a9a3e9e --- /dev/null +++ b/tests/specs/workspaces/non_fatal_diagnostics/deno.json @@ -0,0 +1,5 @@ +{ + "workspace": [ + "./sub" + ] +} diff --git a/tests/specs/workspaces/non_fatal_diagnostics/lint.out b/tests/specs/workspaces/non_fatal_diagnostics/lint.out new file mode 100644 index 0000000000..28ac6b0eb3 --- /dev/null +++ b/tests/specs/workspaces/non_fatal_diagnostics/lint.out @@ -0,0 +1,5 @@ +The 'compilerOptions' field can only be specified in the root workspace deno.json file. + at file:///[WILDLINE]/sub/deno.json +The 'lint.report' field can only be specified in the root workspace deno.json file. + at file:///[WILDLINE]/sub/deno.json +Checked 1 file diff --git a/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json b/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json new file mode 100644 index 0000000000..0a21df89f7 --- /dev/null +++ b/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json @@ -0,0 +1,8 @@ +{ + "lint": { + "report": "compact" + }, + "compilerOptions": { + "strict": true + } +} diff --git a/tests/specs/workspaces/non_fatal_diagnostics/sub/main.ts b/tests/specs/workspaces/non_fatal_diagnostics/sub/main.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/specs/workspaces/vendor/__test__.jsonc b/tests/specs/workspaces/vendor/__test__.jsonc new file mode 100644 index 0000000000..cd44b361be --- /dev/null +++ b/tests/specs/workspaces/vendor/__test__.jsonc @@ -0,0 +1,13 @@ +{ + "tempDir": true, + "steps": [{ + "args": "run --quiet package-a/mod.ts", + "output": "3\n" + }, { + "args": "run --allow-write=. --allow-read=. modify_vendor.ts", + "output": "[WILDLINE]" + }, { + "args": "run --quiet package-a/mod.ts", + "output": "4\n" + }] +} diff --git a/tests/specs/workspaces/vendor/deno.json b/tests/specs/workspaces/vendor/deno.json new file mode 100644 index 0000000000..62bf7dff9c --- /dev/null +++ b/tests/specs/workspaces/vendor/deno.json @@ -0,0 +1,9 @@ +{ + "vendor": true, + "workspace": [ + "package-a" + ], + "imports": { + "@denotest/add": "jsr:@denotest/add" + } +} diff --git a/tests/specs/workspaces/vendor/modify_vendor.ts b/tests/specs/workspaces/vendor/modify_vendor.ts new file mode 100644 index 0000000000..3b6dafe149 --- /dev/null +++ b/tests/specs/workspaces/vendor/modify_vendor.ts @@ -0,0 +1,7 @@ +Deno.writeTextFileSync( + "./vendor/http_127.0.0.1_4250/@denotest/add/1.0.0/mod.ts", + `export function add(a: number, b: number): number { + return a + b + 1; // evil add +} +`, +); diff --git a/tests/specs/workspaces/vendor/package-a/deno.json b/tests/specs/workspaces/vendor/package-a/deno.json new file mode 100644 index 0000000000..fe4300ad63 --- /dev/null +++ b/tests/specs/workspaces/vendor/package-a/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/pkg", + "version": "1.0.0", + "exports": "./mod.ts" +} diff --git a/tests/specs/workspaces/vendor/package-a/mod.ts b/tests/specs/workspaces/vendor/package-a/mod.ts new file mode 100644 index 0000000000..1ca631410f --- /dev/null +++ b/tests/specs/workspaces/vendor/package-a/mod.ts @@ -0,0 +1,3 @@ +import { add } from "@denotest/add"; + +console.log(add(1, 2)); diff --git a/tests/testdata/compile/dynamic_imports/main_unanalyzable.ts b/tests/testdata/compile/dynamic_imports/main_unanalyzable.ts index d87d917c2d..34fb76dc45 100644 --- a/tests/testdata/compile/dynamic_imports/main_unanalyzable.ts +++ b/tests/testdata/compile/dynamic_imports/main_unanalyzable.ts @@ -14,5 +14,7 @@ const IMPORT_PATH_FILE_PATH = join( setTimeout(async () => { console.log("Dynamic importing"); const importPath = (await Deno.readTextFile(IMPORT_PATH_FILE_PATH)).trim(); - import(importPath).then(() => console.log("Dynamic import done.")); + import(import.meta.resolve(importPath)).then(() => + console.log("Dynamic import done.") + ); }, 0); diff --git a/tests/testdata/compile/node_modules_symlink_outside/main_compile_file.out b/tests/testdata/compile/node_modules_symlink_outside/main_compile_file.out index 1154c3256b..e5b39a7527 100644 --- a/tests/testdata/compile/node_modules_symlink_outside/main_compile_file.out +++ b/tests/testdata/compile/node_modules_symlink_outside/main_compile_file.out @@ -1,2 +1,2 @@ Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD] -Warning Symlink target is outside '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules'. Inlining symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]test.txt' to '[WILDCARD]node_modules_symlink_outside[WILDCARD]test.txt' as file. +Warning Symlink target is outside '[WILDCARD]compile'. Inlining symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]test.txt' to '[WILDCARD]target.txt' as file. diff --git a/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out b/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out index 83db2ef400..2067bf1c60 100644 --- a/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out +++ b/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out @@ -2,5 +2,5 @@ Download http://localhost:4260/@denotest/esm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file:///[WILDCARD]/node_modules_symlink_outside/main.ts -Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD] -Warning Symlink target is outside '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules'. Excluding symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]some_folder' with target '[WILDCARD]node_modules_symlink_outside[WILDCARD]some_folder'. +Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDLINE] +Warning Symlink target is outside '[WILDLINE]compile'. Excluding symlink at '[WILDLINE]node_modules_symlink_outside[WILDLINE]node_modules[WILDLINE]symlink_dir' with target '[WILDLINE]some_folder'. diff --git a/tests/testdata/package_json/invalid_value/error.ts.out b/tests/testdata/package_json/invalid_value/error.ts.out index 2fd0940fe3..80893ede0a 100644 --- a/tests/testdata/package_json/invalid_value/error.ts.out +++ b/tests/testdata/package_json/invalid_value/error.ts.out @@ -1,6 +1,4 @@ -error: Parsing version constraints in the application-level package.json is more strict at the moment. - -Invalid npm version requirement. Unexpected character. +error: Invalid npm version requirement. Unexpected character. invalid stuff that won't parse ~ at file:///[WILDCARD]/error.ts:2:23 diff --git a/tests/testdata/package_json/invalid_value/task.out b/tests/testdata/package_json/invalid_value/task.out index dd4a04b0db..79249d1757 100644 --- a/tests/testdata/package_json/invalid_value/task.out +++ b/tests/testdata/package_json/invalid_value/task.out @@ -1,5 +1,2 @@ -Warning Ignoring dependency '@denotest/cjs-default-export' in package.json because its version requirement failed to parse: Invalid npm version requirement. Unexpected character. - invalid stuff that won't parse - ~ Task test echo 1 1 diff --git a/tests/testdata/run/with_package_json/with_stop/main.out b/tests/testdata/run/with_package_json/with_stop/main.out index b199faf8db..f5eb79ca6d 100644 --- a/tests/testdata/run/with_package_json/with_stop/main.out +++ b/tests/testdata/run/with_package_json/with_stop/main.out @@ -1,5 +1,4 @@ [WILDCARD]Config file found at '[WILDCARD]with_package_json[WILDCARD]with_stop[WILDCARD]some[WILDCARD]nested[WILDCARD]deno.json' -[WILDCARD]No package.json file found [WILDCARD] error: Relative import path "chalk" not prefixed with / or ./ or ../ at file:///[WILDCARD]with_package_json/with_stop/some/nested/dir/main.ts:3:19 diff --git a/tests/util/server/src/builders.rs b/tests/util/server/src/builders.rs index 4130a44e7e..698543f507 100644 --- a/tests/util/server/src/builders.rs +++ b/tests/util/server/src/builders.rs @@ -89,6 +89,7 @@ pub struct TestContextBuilder { use_http_server: bool, use_temp_cwd: bool, use_symlinked_temp_dir: bool, + use_canonicalized_temp_dir: bool, /// Copies the files at the specified directory in the "testdata" directory /// to the temp folder and runs the test from there. This is useful when /// the test creates files in the testdata directory (ex. a node_modules folder) @@ -143,6 +144,23 @@ impl TestContextBuilder { self } + /// Causes the temp directory to go to its canonicalized path instead + /// of being in a symlinked temp dir on the CI. + /// + /// Note: This method is not actually deprecated. It's just deprecated + /// to discourage its use. Use it sparingly and document why you're using + /// it. You better have a good reason other than being lazy! + /// + /// If your tests are failing because the temp dir is symlinked on the CI, + /// then it likely means your code doesn't properly handle when Deno is running + /// in a symlinked directory. That's a bug and you should fix it without using + /// this. + #[deprecated] + pub fn use_canonicalized_temp_dir(mut self) -> Self { + self.use_canonicalized_temp_dir = true; + self + } + /// Copies the files at the specified directory in the "testdata" directory /// to the temp folder and runs the test from there. This is useful when /// the test creates files in the testdata directory (ex. a node_modules folder) @@ -207,13 +225,21 @@ impl TestContextBuilder { panic!("{}", err); } - let temp_dir_path = self - .temp_dir_path - .clone() - .unwrap_or_else(std::env::temp_dir); - let deno_dir = TempDir::new_in(&temp_dir_path); - let temp_dir = TempDir::new_in(&temp_dir_path); + let temp_dir_path = PathRef::new( + self + .temp_dir_path + .clone() + .unwrap_or_else(std::env::temp_dir), + ); + let temp_dir_path = if self.use_canonicalized_temp_dir { + temp_dir_path.canonicalize() + } else { + temp_dir_path + }; + let deno_dir = TempDir::new_in(temp_dir_path.as_path()); + let temp_dir = TempDir::new_in(temp_dir_path.as_path()); let temp_dir = if self.use_symlinked_temp_dir { + assert!(!self.use_canonicalized_temp_dir); // code doesn't handle using both of these TempDir::new_symlinked(temp_dir) } else { temp_dir