From a3dbff8c5a81de5d2bb90eba5dd1a4d6a051e164 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 | 41 +- .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 | 1027 ++++++++--------- cli/args/package_json.rs | 128 +- cli/factory.rs | 205 ++-- 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 | 341 +++--- cli/schemas/config-file.v1.json | 2 +- cli/standalone/binary.rs | 313 ++--- cli/standalone/mod.rs | 449 ++++--- 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 | 51 +- cli/tools/fmt.rs | 471 ++++---- cli/tools/info.rs | 23 +- cli/tools/lint/mod.rs | 429 ++++--- 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 | 319 ++--- 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 | 62 +- 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, 4435 insertions(+), 2440 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 f9cb60c74d4c67..475e4b141a1f83 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", + "ext/websocket/autobahn/reports", + "gh-pages", + "target", + "tests/ffi/tests/test.js", + "tests/node_compat/runner/suite", + "tests/node_compat/runner/TODO.md", "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/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/lint/glob/", - "tests/testdata/test/glob/", + "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/malformed_config/", + "tests/testdata/test/glob/", "tests/testdata/test/markdown_windows.md", - "cli/tsc/*typescript.js", - "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/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 53308a3f7cc5d1..ba2e067f2422ea 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 c0ddcaf1533469..8f1014451d8cc7 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 7afafcb95dc4f4..85f63ba37b05c2 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 dc4fa13027f016..275aa653c28d54 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 ff82fc3cca624b..31232e093eaa3e 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 5f58911c2cce30..56fb4f09d2614b 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 2dc5a21d1a81a2..7a16ab21565bea 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 bf52c460f783f7..f747271b811b2b 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 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( - maybe_bench_config: Option, - maybe_bench_flags: Option, - initial_cwd: &Path, + bench_config: BenchConfig, + bench_flags: &BenchFlags, + maybe_flags_base: Option<&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, + bench_config.files, + &bench_flags.files, + maybe_flags_base, )?, - filter: bench_flags.filter, - json: bench_flags.json, - no_run: bench_flags.no_run, }) } } #[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(fmt_flags) = fmt_flags { - if let Some(use_tabs) = fmt_flags.use_tabs { - options.use_tabs = Some(use_tabs); - } + 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 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"); + } + 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 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 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(), + )) + } } - } else { - maybe_package_json = discover_package_json(&flags, None, &initial_cwd)?; + 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, + )?); + } + 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(maybe_files_config) + 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 b6ccb33a4d773a..eb1c41c5df7489 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, + } + } + + pub fn remote_pkg_reqs(&self) -> &Vec { + &self.remote_pkg_reqs } - log::debug!("No package.json file found"); - Ok(None) + pub fn workspace_pkgs(&self) -> &Vec { + &self.workspace_pkgs + } } diff --git a/cli/factory.rs b/cli/factory.rs index 56a28b6d9bc028..62ab251f1686d9 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, + }) + .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(false) + .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(), + 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_import_map: self.maybe_import_map().await?.clone(), maybe_vendor_dir: self.options.vendor_dir_path(), - bare_node_builtins_enabled: self - .options - .unstable_bare_node_builtins(), }))) } .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 40ccda9b20bd29..d439f93609439d 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 f1e98e7c6688bf..2f9ee8d93d8256 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 e1f3e320719705..4b96511c03c0aa 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 27983867a60544..9b500567d4a8c6 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 0d9cd4fbbf161e..48cfebfcc40d74 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 25782b95c4f12a..cfc58439d55366 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 5cf7f82b157489..18d22afade7d1c 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 bab9766eb4285e..cc88a081127652 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 0e81736e5bc23f..4786b742f55b54 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 0d4b9d4d462319..bbd5da8ec0bdc2 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 393fc8632c1ce2..467703b0524251 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 d338720b67a4a7..e8fffa0cd1d115 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 2d812a2be2d07a..a7f54591605c4f 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 9305cd1c922491..26cf16ba9579a7 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 sloppy_imports_resolver: Option, + pub workspace_resolver: Arc, + pub bare_node_builtins_enabled: bool, pub maybe_jsx_import_source_config: Option, - pub maybe_import_map: Option>, pub maybe_vendor_dir: Option<&'a PathBuf>, - pub bare_node_builtins_enabled: bool, } 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 bfcae271b015bb..84e65fc771df4e 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 98af5fa771b98d..bf035577c94c07 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 24ba7c9dbe3735..cbd14db4fd3638 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(); - - // 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, - &referrer, - NodeResolutionMode::Execution, - ) - .map(|res| res.into_url()); - } + let mapped_resolution = + self.shared.workspace_resolver.resolve(specifier, &referrer); - let specifier = match maybe_mapped { - Some(resolved) => resolved, - None => deno_core::resolve_import(specifier, referrer.as_str())?, - }; + 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); + } + } - if specifier.scheme() == "jsr" { - if let Some(module) = self.shared.eszip.get_module(specifier.as_str()) { - return Ok(ModuleSpecifier::parse(&module.specifier).unwrap()); + 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, + ); + 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 3e6823d506b21b..ee91b9f7fe8438 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 0378d6ae21cc27..d801b908cdf05f 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 6eb7a071c5affc..4ec677f8f31f24 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 b7aa946914de59..e395c351b7b043 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 f123fc55a24f50..79765a91d46b72 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 = 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 - }; + 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>>()?; + + 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 b37a8e06b4a208..c16be9fb2d1f33 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, + 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, + }); + } + } + 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 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?; + let formatter: Box = if fmt_flags.check { + Box::new(CheckFormatter::default()) } else { - format_source_files(paths, fmt_options.options, incremental_cache.clone()) + 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; } - incremental_cache.wait_completion().await; - Ok(()) + + 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)); - - // prevent threads outputting at the same time - let output_lock = Arc::new(Mutex::new(0)); - - 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; - - // skip checking the file if we know it's formatted - if incremental_cache.is_file_same(&file_path, &file_text) { - return Ok(()); - } +#[async_trait] +trait Formatter { + async fn handle_files( + &self, + paths: Vec, + fmt_options: FmtOptionsConfig, + incremental_cache: Arc, + ) -> Result<(), AnyError>; - 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); + fn finish(&self) -> Result<(), AnyError>; +} + +#[derive(Default)] +struct CheckFormatter { + not_formatted_files_count: Arc, + checked_files_count: Arc, +} + +#[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(()); } - 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") - ); + + 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?; + + Ok(()) + } + + 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}", + ))) } - }) - .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}", - ))) } } -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 - - 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)?; - - // skip formatting the file if we know it's formatted - if incremental_cache.is_file_same(&file_path, &file_contents.text) { - return Ok(()); - } +#[derive(Default)] +struct RealFormatter { + formatted_files_count: Arc, + checked_files_count: Arc, +} - 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); +#[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 + + 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(()); } - Err(e) => { - let _g = output_lock.lock(); - log::error!("Error formatting: {}", file_path.to_string_lossy()); - log::error!(" {e}"); + + 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(()) } - Ok(()) - } - }) - .await?; - - 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) - ); + }) + .await?; + Ok(()) + } - Ok(()) + 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 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 76951b13dc58e1..18a4bed57cb15d 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 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 resolver = factory.workspace_resolver().await?; + + 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 0d9868cf2b39d6..e3f2844a7bfed2 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 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 - }; - - lint_files(factory, lint_options, lint_config, lint_paths).await?; + 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 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(); + 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()); - - 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 + options: LintOptions, +} + +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, + } + } + + 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(); + + 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 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)); + .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(()) } - } - Ok(()) - })); - } - } - - 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)?); - - // 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(()); - } + .boxed_local(), + ); + } + } - 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(), - ) + 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 success = handle_lint_result( - &file_path.to_string_lossy(), - r, - reporter_lock.clone(), - ); - if !success { - has_error.raise(); - } + 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(), + ) + } + } - Ok(()) - } - }) - .await - }) - }); + let success = handle_lint_result( + &file_path.to_string_lossy(), + r, + reporter_lock.clone(), + ); + if !success { + has_error.raise(); + } - deno_core::futures::future::try_join_all(futures).await?; + Ok(()) + } + }) + .await + } + .boxed_local() + }); - incremental_cache.wait_completion().await; - reporter_lock.lock().close(target_files_len); + deno_core::futures::future::try_join_all(futures).await?; - Ok(!has_error.is_raised()) + incremental_cache.wait_completion().await; + Ok(()) + } + + 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 d300e5eafd468a..134a973f7d9925 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 4fdc0255055938..e3e2f1b558a493 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 ad0f72272b83d4..ad77a56bb187bf 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 36bff64bbbc531..147b59f30c5dc9 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 a44dc8dbba64f4..2905134f4f149f 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,146 +44,124 @@ 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 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 + std::env::remove_var(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME); + 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 + }; let task_name = match &task_flags.task { Some(task) => task, None => { print_available_tasks( &mut std::io::stdout(), + &cli_options.workspace, &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 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 - std::env::remove_var(USE_PKG_JSON_HIDDEN_ENV_VAR_NAME); - v == "1" - }) - .unwrap_or(false); - - 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, - ); - } - } - } - - // 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 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 88b539470cff93..7042a82b977069 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 5aef631928f184..a4424e3f32f506 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 68f2530d713e24..644e84a7b39ddd 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 a8d8000d8f52cd..2dfa71c44c6d14 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 830d5f8f0c9dc0..ac07c47d175e48 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 00000000000000..21f73024b1f120 --- /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 b2628760b41a1a..176ca43f040b90 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 89df7bb9867797..69cdc77c344ade 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 00a20ab4debaf8..987d65192ab77c 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 0d94d436710ebd..c902adfb2e69fe 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"); - - // 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"), - ); + 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 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 d0df5e6e8aae23..f66fc97bed231c 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 b8263be29118f1..d4d1fea2eb2f7e 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 252411627d1f21..7864938f87b71e 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 00000000000000..677c5d24a5d0b8 --- /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 00000000000000..5bf86f4ce7d799 --- /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 00000000000000..fa1bd69da65e25 --- /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 00000000000000..b72d884428637d --- /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 00000000000000..e6e03ae8580ee4 --- /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 00000000000000..5fbf79e66c3b25 --- /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 00000000000000..8d9b8a22a101a9 --- /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 00000000000000..f131c191b67bce --- /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 00000000000000..ca972c39e12ebb --- /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 00000000000000..53148ac2f27927 --- /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 00000000000000..bb452e3e9fef9d --- /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 00000000000000..897cd7d3c669a0 --- /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 00000000000000..5df2fd70ec6383 --- /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 00000000000000..b72d884428637d --- /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 00000000000000..e6e03ae8580ee4 --- /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 00000000000000..8d9b8a22a101a9 --- /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 00000000000000..f131c191b67bce --- /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 00000000000000..554ba5674e8b67 --- /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 00000000000000..faecec870e7569 --- /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 00000000000000..8db6c5476c0e59 --- /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 00000000000000..21ae7acd3ffa66 --- /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 00000000000000..f4ba8ee2830944 --- /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 00000000000000..176354f98fadaf --- /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 00000000000000..13552ad61f0bf8 --- /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 00000000000000..66b39363600109 --- /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 00000000000000..bbe210bdbc6684 --- /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 00000000000000..274d1ed7f437b8 --- /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 00000000000000..80e3639f9d108b --- /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 00000000000000..7b2a3460115642 --- /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 00000000000000..0dd8856d774ac5 --- /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 00000000000000..150f18b2e66564 --- /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 00000000000000..18da23175c2b9d --- /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 00000000000000..8609d075540eb4 --- /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 00000000000000..388b147499db06 --- /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 00000000000000..2b030605de50e3 --- /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 00000000000000..9300c8169dba15 --- /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 00000000000000..323f43f3497a47 --- /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 00000000000000..306ecada650aad --- /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 be6fcab9720144..e646164c6f8efa 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 adb8b459898827..58cd88ada16a15 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 2ba55540b5d9d9..00000000000000 --- 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 f3b6cb7be1a466..57493f5569f189 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 ca351ee0dfe6a8..657cdab8039c4c 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 f3a394c6ff6669..b1933f536a0e3d 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 00000000000000..6268d713625799 --- /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 e3dd981e509ce4..499731c1ec3b36 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 00000000000000..6581222c9da693 --- /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 00000000000000..52f05af990899f --- /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 00000000000000..2dab3a4ecde1ec --- /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 00000000000000..52bd0cbc000b94 --- /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 00000000000000..34130b647d9f9a --- /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 00000000000000..52bd0cbc000b94 --- /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 00000000000000..93fdf6ca7960d9 --- /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 00000000000000..1d892a93f05b15 --- /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 00000000000000..52bd0cbc000b94 --- /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 00000000000000..489ee52ab9195a --- /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 00000000000000..12c6715befc5f6 --- /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 00000000000000..6db944d6c52b85 --- /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 00000000000000..9547d1bd96fbeb --- /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 00000000000000..e3e2d575dd12b9 --- /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 00000000000000..05cb0862888362 --- /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 00000000000000..a27c1e5cd545cf --- /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 00000000000000..3f5f46171519ec --- /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 00000000000000..618250b98f1c67 --- /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 00000000000000..499731c1ec3b36 --- /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 00000000000000..50fda50c36e96d --- /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 ed3827ef640ad6..8c4d0fb2061437 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 00000000000000..37d41aae295ff5 --- /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 8774bdbfc2bda6..f28d132d1a853b 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 afe623e003b6bd..00000000000000 --- 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 b6abfd8ba5f38d..00000000000000 --- 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 603b710ba36794..00000000000000 --- 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 f8bfd5d988d5bd..00000000000000 --- 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 ea3b2d26f40078..a812a973e2dfb4 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 00000000000000..7b7a429f4d4d1e --- /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 00000000000000..37d41aae295ff5 --- /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 00000000000000..adc164ea5fc7be --- /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 00000000000000..af47e13ac0171e --- /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 00000000000000..e8079d1127447f --- /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 00000000000000..79e059ca186e7d --- /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 00000000000000..9f8d1c594afefc --- /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 00000000000000..1467823cff0855 --- /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 00000000000000..5e61cdfc3c10ef --- /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 00000000000000..716f0f97d30071 --- /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 00000000000000..03956388c3a7ad --- /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 00000000000000..3a311dcd7e9ba8 --- /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 00000000000000..1ca11026a25f69 --- /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 00000000000000..82a49b9fe626f8 --- /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 00000000000000..f98aa34cb405a4 --- /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 00000000000000..960d32870c2b6a --- /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 00000000000000..4b876d5b9a90ad --- /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 00000000000000..f412f7d4b78ec9 --- /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 00000000000000..7d5ca9abf85b99 --- /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 00000000000000..e3dd981e509ce4 --- /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 a7433f86fc3552..64cf9092187707 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 7b1c04d568a758..706b08ccd55396 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 57602aab5d88cf..a23790570de581 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 8c57bc2dd84aab..3114e36dbb3a35 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 edb6b53aa350b9..2cb0717099c22b 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 67867f023373e4..5da0209b901ec2 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 00000000000000..b9f9a6dea9029b --- /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 b9f9a6dea9029b..000ce402b2a8ff 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 b971c4f3d6d7f0..a39778a6d68d75 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 57d8c9f1ea5091..9806f7d6379188 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 a7669c1ec07554..3b7f35c62c8256 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 205d95aead30e9..72f0b0b4505d64 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 25feefad8b874c..189d212b542202 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 56105365aba80d..51d1d21deef618 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 a7669c1ec07554..fe02eacbc1a3fe 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 ef3bfc37af7b45..3bc0e52ec08d18 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 6d9c09d4dde2e8..8108d54fcb29f7 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 d40328b367b98e..d4948ddbbd574e 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 68e053b020e41c..6bc959c4db1633 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 98598a30654095..ec46186a756c81 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 00000000000000..b08f35afca20c1 --- /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 00000000000000..aead0490e4d5f1 --- /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 00000000000000..f68d5d24b33e66 --- /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 00000000000000..5167ad857e243e --- /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 00000000000000..4abd90d23599da --- /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 00000000000000..0c64208349f3ef --- /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 00000000000000..bc1bdc712960b2 --- /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 00000000000000..a468ec4944c38d --- /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 00000000000000..808106c92f9389 --- /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 00000000000000..808106c92f9389 --- /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 00000000000000..9c11c78bb3899a --- /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 00000000000000..87fd3d46d9d7d8 --- /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 00000000000000..b72d884428637d --- /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 00000000000000..e6e03ae8580ee4 --- /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 00000000000000..7ef57fbee1e9be --- /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 00000000000000..8d9b8a22a101a9 --- /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 00000000000000..f131c191b67bce --- /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 00000000000000..f1499a626f3373 --- /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 00000000000000..53148ac2f27927 --- /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 00000000000000..6ebcdfb104933d --- /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 00000000000000..6c13427eea99bf --- /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 00000000000000..30bda3ac6a9310 --- /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 00000000000000..706b44f018c9ce --- /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 00000000000000..79c36f622ab3a8 --- /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 00000000000000..dcc479a201bbd2 --- /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 00000000000000..91e921b334934d --- /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 00000000000000..123cc3b9a0a848 --- /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 00000000000000..e69de29bb2d1d6 diff --git a/tests/specs/workspaces/lockfile/pkg/deno.jsonc b/tests/specs/workspaces/lockfile/pkg/deno.jsonc new file mode 100644 index 00000000000000..7bd6ab450fc5e4 --- /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 00000000000000..9e7a8c445f719d --- /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 00000000000000..f69572b492e370 --- /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 00000000000000..da13b7cca17ebc --- /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 00000000000000..2c62b615b1e42f --- /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 00000000000000..ab79054b88ad7c --- /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 00000000000000..983a9a3e9ec40c --- /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 00000000000000..28ac6b0eb30df6 --- /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 00000000000000..0a21df89f7b628 --- /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 00000000000000..e69de29bb2d1d6 diff --git a/tests/specs/workspaces/vendor/__test__.jsonc b/tests/specs/workspaces/vendor/__test__.jsonc new file mode 100644 index 00000000000000..cd44b361be57fc --- /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 00000000000000..62bf7dff9c0825 --- /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 00000000000000..3b6dafe149b4f6 --- /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 00000000000000..fe4300ad63f053 --- /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 00000000000000..1ca631410fdfde --- /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 d87d917c2d0259..34fb76dc45dc77 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 1154c3256b0019..e5b39a75274ea3 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 83db2ef40024a9..2067bf1c606b68 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 2fd0940fe3e14d..80893ede0a8d8d 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 dd4a04b0dbd92d..79249d1757c382 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 b199faf8db5782..f5eb79ca6d6082 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 4130a44e7e6730..698543f507fd59 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