├── .gitignore
├── tests
├── resolve_error
│ ├── testdata
│ │ ├── main.ts
│ │ ├── deno.json
│ │ ├── package.json
│ │ └── node_modules
│ │ │ ├── optional-dep
│ │ │ ├── index.js
│ │ │ ├── sub
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ │ ├── optional-peer
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ │ ├── open-package
│ │ │ ├── package.json
│ │ │ └── index.js
│ │ │ └── export-package
│ │ │ ├── index.js
│ │ │ └── package.json
│ └── main.test.ts
├── invalid_graph
│ ├── testdata
│ │ ├── deno.json
│ │ └── main.ts
│ └── main.test.ts
├── jsx
│ ├── testdata
│ │ ├── main.jsx
│ │ ├── main.tsx
│ │ └── deno.json
│ └── main.test.ts
├── platform_browser
│ ├── testdata
│ │ ├── deno.json
│ │ ├── package.json
│ │ └── node_modules
│ │ │ ├── package
│ │ │ ├── browser.js
│ │ │ └── package.json
│ │ │ └── browser-main
│ │ │ ├── browser.js
│ │ │ └── package.json
│ └── main.test.ts
├── bytes_and_text_npm
│ ├── testdata
│ │ ├── package.json
│ │ ├── data_utf8_bom.txt
│ │ ├── node_modules
│ │ │ └── package
│ │ │ │ ├── data.txt
│ │ │ │ └── package.json
│ │ ├── deno.json
│ │ └── main.ts
│ └── main.test.ts
├── bytes_and_text
│ ├── testdata
│ │ ├── data_utf8_bom.txt
│ │ ├── deno.json
│ │ └── main.ts
│ └── main.test.ts
├── resolve_async
│ ├── testdata
│ │ ├── deno.json
│ │ └── deno.manual_install.json
│ └── main.test.ts
├── link_jsr_entrypoint
│ ├── testdata
│ │ ├── main
│ │ │ ├── deno.json
│ │ │ └── main.ts
│ │ └── add
│ │ │ ├── deno.json
│ │ │ └── mod.ts
│ └── main.test.ts
├── http_no_ext.test.ts
└── helpers.ts
├── src
├── rs_lib
│ ├── .rustfmt.toml
│ ├── rust-toolchain.toml
│ ├── helpers.js
│ ├── Cargo.toml
│ ├── http_client.rs
│ ├── lib.rs
│ └── Cargo.lock
├── mod.test.ts
└── mod.ts
├── .gitmodules
├── README.md
├── .vscode
└── settings.json
├── scripts
├── commit.ts
└── update-deps.ts
├── deno.json
├── LICENSE
├── .github
└── workflows
│ ├── update_deps.yml
│ └── ci.yml
└── deno.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | src/lib
3 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/main.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/invalid_graph/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/jsx/testdata/main.jsx:
--------------------------------------------------------------------------------
1 | console.log(
);
2 |
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/package.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/package.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/invalid_graph/testdata/main.ts:
--------------------------------------------------------------------------------
1 | import "unknown";
2 |
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/package.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/tests/bytes_and_text/testdata/data_utf8_bom.txt:
--------------------------------------------------------------------------------
1 | Hello there!
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/data_utf8_bom.txt:
--------------------------------------------------------------------------------
1 | Hello there!
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/node_modules/package/browser.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-dep/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/node_modules/browser-main/browser.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-dep/sub/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-peer/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/resolve_async/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "lock": false
3 | }
4 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-dep/sub/package.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/node_modules/package/data.txt:
--------------------------------------------------------------------------------
1 | Hello there!
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/node_modules/package/package.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/open-package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/src/rs_lib/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 80
2 | tab_spaces = 2
3 | edition = "2024"
4 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/open-package/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/src/rs_lib/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.89.0"
3 | profile = "default"
4 |
--------------------------------------------------------------------------------
/tests/jsx/testdata/main.tsx:
--------------------------------------------------------------------------------
1 | const value: string = "";
2 | console.log(, value);
3 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/export-package/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/tests/resolve_async/testdata/deno.manual_install.json:
--------------------------------------------------------------------------------
1 | {
2 | "nodeModulesDir": "manual"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/bytes_and_text/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "unstable": [
3 | "raw-imports"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/tests/link_jsr_entrypoint/testdata/main/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "links": [
3 | "../add"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "unstable": [
3 | "raw-imports"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/node_modules/browser-main/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "browser": "./browser.js"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/link_jsr_entrypoint/testdata/add/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@denotest/add",
3 | "exports": "./mod.ts"
4 | }
5 |
--------------------------------------------------------------------------------
/tests/link_jsr_entrypoint/testdata/add/mod.ts:
--------------------------------------------------------------------------------
1 | export function add(a: number, b: number) {
2 | return a + b;
3 | }
4 |
--------------------------------------------------------------------------------
/tests/link_jsr_entrypoint/testdata/main/main.ts:
--------------------------------------------------------------------------------
1 | import { add } from "@denotest/add";
2 |
3 | console.log(add(1, 2));
4 |
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/export-package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "exports": {
3 | "./test": "./index.js"
4 | }
5 | }
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-dep/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "optionalDependencies": {
3 | "optional": "*"
4 | }
5 | }
--------------------------------------------------------------------------------
/tests/resolve_error/testdata/node_modules/optional-peer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "peerDependenciesMeta": {
3 | "optional": {
4 | "optional": true
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/tests/platform_browser/testdata/node_modules/package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "exports": {
3 | ".": {
4 | "node": "./node.js",
5 | "import": "./browser.js"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/jsx/testdata/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "precompile",
4 | "jsxImportSource": "react"
5 | },
6 | "lock": false,
7 | "imports": {
8 | "react": "npm:react@^19.1.0"
9 | }
10 | }
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/testdata/main.ts:
--------------------------------------------------------------------------------
1 | import dataBytes from "package/data.txt" with { type: "bytes" };
2 | import dataText from "package/data.txt" with { type: "text" };
3 |
4 | console.log(dataBytes);
5 | console.log(dataText);
6 |
--------------------------------------------------------------------------------
/tests/bytes_and_text/testdata/main.ts:
--------------------------------------------------------------------------------
1 | import dataBytes from "./data_utf8_bom.txt" with { type: "bytes" };
2 | import dataText from "./data_utf8_bom.txt" with { type: "text" };
3 |
4 | console.log(dataBytes);
5 | console.log(dataText);
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | # We're using a submodule instead of a git dependency because cargo errors
2 | # on Windows when installing the Deno repo due to the submodules within
3 | # the Deno repo due to long paths
4 | [submodule "deno"]
5 | path = deno
6 | url = https://github.com/denoland/deno
7 | shallow = true
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `@deno/loader`
2 |
3 | [](https://jsr.io/@deno/loader)
4 |
5 | Resolver and loader for Deno code.
6 |
7 | This can be used to create bundler plugins or libraries that use deno
8 | resolution.
9 |
10 | [Docs](https://jsr.io/@deno/loader)
11 |
--------------------------------------------------------------------------------
/src/rs_lib/helpers.js:
--------------------------------------------------------------------------------
1 | export async function fetch_specifier(specifier, headers) {
2 | try {
3 | console.error("Downloading", specifier);
4 | const response = await fetch(specifier, {
5 | headers,
6 | redirect: "manual",
7 | });
8 | const status = response.status;
9 | const body = await response.bytes();
10 | return {
11 | status,
12 | body,
13 | headers: response.headers,
14 | };
15 | } catch (err) {
16 | return {
17 | error: err.toString(),
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "[rust]": {
4 | "editor.defaultFormatter": "rust-lang.rust-analyzer"
5 | },
6 | "[typescript]": {
7 | "editor.defaultFormatter": "denoland.vscode-deno"
8 | },
9 | "[json]": {
10 | "editor.defaultFormatter": "denoland.vscode-deno"
11 | },
12 | "[yaml]": {
13 | "editor.defaultFormatter": "denoland.vscode-deno"
14 | },
15 | // generally I wouldn't commit this file to source control, but
16 | // this setting is necessary in order to get this repo working
17 | // in vscode
18 | "rust-analyzer.linkedProjects": [
19 | "./src/rs_lib/Cargo.toml"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/scripts/commit.ts:
--------------------------------------------------------------------------------
1 | import $ from "@david/dax";
2 |
3 | const rootDir = $.path(import.meta.dirname!).parentOrThrow();
4 | const denoDir = rootDir.join("deno");
5 |
6 | const hasGitChanges =
7 | (await $`git status --porcelain`.text()).trim().length > 0;
8 | if (!hasGitChanges) {
9 | $.log("Had no git changes. Exiting.");
10 | Deno.exit(0);
11 | }
12 |
13 | const newCommit = (await $`git rev-parse HEAD`.cwd(denoDir).text()).trim();
14 | $.logStep("Updating to", newCommit);
15 |
16 | await $`git add .`.cwd(rootDir);
17 | const commitMessage = `chore: Deno ${newCommit.substring(0, 7)}`;
18 | await $`git commit -m ${commitMessage}`;
19 | await $`git push`;
20 |
--------------------------------------------------------------------------------
/tests/invalid_graph/main.test.ts:
--------------------------------------------------------------------------------
1 | import { assertEquals } from "@std/assert";
2 | import { createLoaderWithDiagnostics } from "../helpers.ts";
3 |
4 | Deno.test("surfaces graph diagnostic", async () => {
5 | const mainFile = import.meta.dirname + "/testdata/main.ts";
6 | const { diagnostics } = await createLoaderWithDiagnostics({
7 | configPath: import.meta.dirname + "/testdata/deno.json",
8 | }, {
9 | entrypoints: [mainFile],
10 | });
11 |
12 | assertEquals(diagnostics.length, 1);
13 | const expectedMessage = 'Import "unknown" not a dependency';
14 | assertEquals(
15 | diagnostics[0].message.substring(0, expectedMessage.length),
16 | expectedMessage,
17 | );
18 | });
19 |
--------------------------------------------------------------------------------
/tests/http_no_ext.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | assertResponseText,
3 | createLoader,
4 | RequestedModuleType,
5 | } from "./helpers.ts";
6 |
7 | Deno.test("loads from http server", async () => {
8 | await using server = Deno.serve((_request) => {
9 | return new Response("console.log(1);", {
10 | headers: {
11 | "content-type": "application/javascript",
12 | },
13 | });
14 | });
15 |
16 | const url = `http://localhost:${server.addr.port}/no-extension`;
17 | const { loader } = await createLoader({}, {
18 | entrypoints: [url],
19 | });
20 |
21 | const response = await loader.load(url, RequestedModuleType.Default);
22 | assertResponseText(
23 | response,
24 | `console.log(1);`,
25 | );
26 | });
27 |
--------------------------------------------------------------------------------
/tests/link_jsr_entrypoint/main.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | assertResponseText,
3 | createLoader,
4 | RequestedModuleType,
5 | ResolutionMode,
6 | } from "../helpers.ts";
7 |
8 | Deno.test("loads linked entrypoint", async () => {
9 | const mainFile = import.meta.dirname + "/testdata/main/main.ts";
10 | const { loader } = await createLoader({
11 | configPath: import.meta.resolve("./testdata/main/deno.json"),
12 | }, {
13 | entrypoints: [mainFile],
14 | });
15 |
16 | const response = await loader.load(
17 | loader.resolveSync("@denotest/add", undefined, ResolutionMode.Import),
18 | RequestedModuleType.Default,
19 | );
20 | assertResponseText(
21 | response,
22 | `export function add(a, b) {
23 | return a + b;
24 | }
25 | `,
26 | { skipSourceMap: true },
27 | );
28 | });
29 |
--------------------------------------------------------------------------------
/tests/platform_browser/main.test.ts:
--------------------------------------------------------------------------------
1 | import { ResolutionMode } from "@deno/loader";
2 | import { createLoader } from "../helpers.ts";
3 | import { assert } from "@std/assert";
4 |
5 | Deno.test("resolves to browser locations", async () => {
6 | const { loader } = await createLoader({
7 | configPath: import.meta.dirname + "/testdata/deno.json",
8 | platform: "browser",
9 | }, {
10 | entrypoints: [],
11 | });
12 |
13 | assert(
14 | loader.resolveSync(
15 | "package",
16 | import.meta.resolve("./testdata/main.js"),
17 | ResolutionMode.Import,
18 | ).endsWith("browser.js"),
19 | );
20 | assert(
21 | loader.resolveSync(
22 | "browser-main",
23 | import.meta.resolve("./testdata/main.js"),
24 | ResolutionMode.Import,
25 | ).endsWith("browser.js"),
26 | );
27 | });
28 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deno/loader",
3 | "tasks": {
4 | "build": "cd src/rs_lib && deno run -A jsr:@deno/wasmbuild@0.20.0 --out ../lib"
5 | },
6 | "fmt": {
7 | "exclude": [
8 | "src/lib"
9 | ]
10 | },
11 | "lint": {
12 | "rules": {
13 | "exclude": ["no-explicit-any"]
14 | }
15 | },
16 | "publish": {
17 | "exclude": [
18 | ".github",
19 | ".vscode",
20 | ".gitmodules",
21 | "deno.lock",
22 | "!./src/lib",
23 | "scripts",
24 | "tests/"
25 | ]
26 | },
27 | "exports": "./src/mod.ts",
28 | "exclude": [
29 | "deno",
30 | "src/rs_lib/target",
31 | "target",
32 | "tests/**/testdata"
33 | ],
34 | "imports": {
35 | "@david/dax": "jsr:@david/dax@^0.43.2",
36 | "@std/assert": "jsr:@std/assert@^1.0.13",
37 | "@std/toml": "jsr:@std/toml@^1.0.7"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2025 the Deno authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/bytes_and_text/main.test.ts:
--------------------------------------------------------------------------------
1 | import { assertEquals } from "@std/assert";
2 | import {
3 | assertResponseText,
4 | createLoader,
5 | RequestedModuleType,
6 | ResolutionMode,
7 | type WorkspaceOptions,
8 | } from "../helpers.ts";
9 |
10 | Deno.test("loads bytes and text", async () => {
11 | const mainTs = import.meta.dirname + "/testdata/main.ts";
12 | const createWorkspace = async (options?: WorkspaceOptions) => {
13 | return await createLoader({
14 | configPath: import.meta.dirname + "/testdata/deno.json",
15 | ...(options ?? {}),
16 | }, {
17 | entrypoints: [mainTs],
18 | });
19 | };
20 | const { loader } = await createWorkspace();
21 |
22 | const mainTsUrl = loader.resolveSync(
23 | mainTs,
24 | undefined,
25 | ResolutionMode.Import,
26 | );
27 | const dataFileUrl = loader.resolveSync(
28 | "./data_utf8_bom.txt",
29 | mainTsUrl,
30 | ResolutionMode.Import,
31 | );
32 |
33 | assertResponseText(
34 | await loader.load(dataFileUrl, RequestedModuleType.Text),
35 | `Hello there!`,
36 | );
37 | const bytesResponse = await loader.load(
38 | dataFileUrl,
39 | RequestedModuleType.Bytes,
40 | );
41 | if (bytesResponse.kind !== "module") {
42 | throw new Error("Fail");
43 | }
44 | assertEquals(bytesResponse.code, Deno.readFileSync(new URL(dataFileUrl)));
45 | });
46 |
--------------------------------------------------------------------------------
/tests/bytes_and_text_npm/main.test.ts:
--------------------------------------------------------------------------------
1 | import { assertEquals } from "@std/assert";
2 | import {
3 | assertResponseText,
4 | createLoader,
5 | RequestedModuleType,
6 | ResolutionMode,
7 | type WorkspaceOptions,
8 | } from "../helpers.ts";
9 |
10 | Deno.test("loads bytes and text in npm packages", async () => {
11 | const mainTs = import.meta.dirname + "/testdata/main.ts";
12 | const createWorkspace = async (options?: WorkspaceOptions) => {
13 | return await createLoader({
14 | configPath: import.meta.dirname + "/testdata/deno.json",
15 | ...(options ?? {}),
16 | }, {
17 | entrypoints: [mainTs],
18 | });
19 | };
20 | const { loader } = await createWorkspace();
21 |
22 | const mainTsUrl = loader.resolveSync(
23 | mainTs,
24 | undefined,
25 | ResolutionMode.Import,
26 | );
27 | const dataFileUrl = loader.resolveSync(
28 | "package/data.txt",
29 | mainTsUrl,
30 | ResolutionMode.Import,
31 | );
32 |
33 | assertResponseText(
34 | await loader.load(dataFileUrl, RequestedModuleType.Text),
35 | `Hello there!`,
36 | );
37 | const bytesResponse = await loader.load(
38 | dataFileUrl,
39 | RequestedModuleType.Bytes,
40 | );
41 | if (bytesResponse.kind !== "module") {
42 | throw new Error("Fail");
43 | }
44 | assertEquals(bytesResponse.code, Deno.readFileSync(new URL(dataFileUrl)));
45 | });
46 |
--------------------------------------------------------------------------------
/.github/workflows/update_deps.yml:
--------------------------------------------------------------------------------
1 | name: check_updates
2 |
3 | on:
4 | workflow_dispatch:
5 | # run this monday to thursday
6 | schedule:
7 | - cron: "0 11 * * 1-4"
8 |
9 | jobs:
10 | build:
11 | name: check updates
12 | if: github.repository == 'denoland/deno-js-loader'
13 | runs-on: ubuntu-latest
14 | timeout-minutes: 45
15 |
16 | steps:
17 | - name: Clone repository
18 | uses: actions/checkout@v4
19 | with:
20 | submodules: true
21 | token: ${{ secrets.DENOBOT_PAT }}
22 |
23 | - uses: denoland/setup-deno@v2
24 |
25 | - name: Run script
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.DENOBOT_PAT }}
28 | GH_WORKFLOW_ACTOR: ${{ github.actor }}
29 | run: |
30 | git config user.email "denobot@users.noreply.github.com"
31 | git config user.name "denobot"
32 | deno run -A ./scripts/update-deps.ts
33 | deno task build
34 | deno test -A
35 | deno run -A ./scripts/commit.ts
36 |
37 | # This is necessary to prevent GH automatically disabling this workflow after 60 days.
38 | workflow-keepalive:
39 | if: github.event_name == 'schedule'
40 | runs-on: ubuntu-latest
41 | permissions:
42 | actions: write
43 | steps:
44 | - uses: liskin/gh-workflow-keepalive@f72ff1a1336129f29bf0166c0fd0ca6cf1bcb38c
45 |
--------------------------------------------------------------------------------
/tests/resolve_async/main.test.ts:
--------------------------------------------------------------------------------
1 | import { ResolutionMode } from "@deno/loader";
2 | import { createLoader } from "../helpers.ts";
3 | import { assert, assertRejects } from "@std/assert";
4 |
5 | Deno.test("resolves npm specifiers and jsr specifiers on demand with resolveAsync", async () => {
6 | const { loader } = await createLoader({
7 | configPath: import.meta.dirname + "/testdata/deno.json",
8 | }, {
9 | entrypoints: [],
10 | });
11 |
12 | {
13 | const jsrUrl = await loader.resolve(
14 | "jsr:@david/code-block-writer",
15 | import.meta.url,
16 | ResolutionMode.Import,
17 | );
18 | assert(jsrUrl.startsWith("https://"));
19 | }
20 | {
21 | const npmUrl = await loader.resolve(
22 | "npm:code-block-writer",
23 | import.meta.url,
24 | ResolutionMode.Import,
25 | );
26 | assert(npmUrl.startsWith("file:///"));
27 | }
28 | });
29 |
30 | Deno.test("errors when using nodeModulesDir: manual and npm package is not installed", async () => {
31 | const { loader } = await createLoader({
32 | configPath: import.meta.dirname + "/testdata/deno.manual_install.json",
33 | }, {
34 | entrypoints: [],
35 | });
36 |
37 | {
38 | await assertRejects(
39 | () =>
40 | loader.resolve(
41 | "npm:code-block-writer",
42 | import.meta.url,
43 | ResolutionMode.Import,
44 | ),
45 | Error,
46 | "Could not find a matching package for 'npm:code-block-writer' in the node_modules directory.",
47 | );
48 | }
49 | });
50 |
--------------------------------------------------------------------------------
/tests/helpers.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type LoadResponse,
3 | type ModuleLoadResponse,
4 | Workspace,
5 | type WorkspaceOptions,
6 | } from "@deno/loader";
7 | import { assertEquals } from "@std/assert";
8 |
9 | export * from "@deno/loader";
10 |
11 | export async function createLoader(
12 | workspaceOptions: WorkspaceOptions,
13 | loaderOptions: { entrypoints: string[] },
14 | ) {
15 | const { loader, workspace, diagnostics } = await createLoaderWithDiagnostics(
16 | workspaceOptions,
17 | loaderOptions,
18 | );
19 | assertEquals(diagnostics, []);
20 | return {
21 | loader,
22 | workspace,
23 | };
24 | }
25 |
26 | export async function createLoaderWithDiagnostics(
27 | workspaceOptions: WorkspaceOptions,
28 | loaderOptions: { entrypoints: string[] },
29 | ) {
30 | const workspace = new Workspace(workspaceOptions);
31 | const loader = await workspace.createLoader();
32 | const diagnostics = await loader.addEntrypoints(loaderOptions.entrypoints);
33 | return {
34 | loader,
35 | workspace,
36 | diagnostics,
37 | };
38 | }
39 |
40 | export function assertResponseText(
41 | response: LoadResponse,
42 | text: string,
43 | opts?: { skipSourceMap?: boolean },
44 | ) {
45 | assertEquals(response.kind, "module");
46 | const moduleResponse = response as ModuleLoadResponse;
47 | let actualText = new TextDecoder().decode(moduleResponse.code);
48 | if (opts?.skipSourceMap) {
49 | actualText = actualText.replace(
50 | /\/\/# sourceMappingURL=.*$/,
51 | "",
52 | );
53 | }
54 | assertEquals(actualText, text);
55 | }
56 |
--------------------------------------------------------------------------------
/src/rs_lib/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rs_lib"
3 | version = "0.0.0"
4 | edition = "2024"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 | path = "lib.rs"
9 |
10 | # update this by running ./scripts/update-deps.ts
11 | [dependencies]
12 | anyhow = "1.0.57"
13 | console_error_panic_hook = "0.1.6"
14 | js-sys = "=0.3.82"
15 | log = "0.4.28"
16 | serde = "1.0.149"
17 | serde-wasm-bindgen = "=0.6.5"
18 | wasm-bindgen = "=0.2.105"
19 | wasm-bindgen-futures = "=0.4.55"
20 | async-trait = "0.1.73"
21 | deno_error = "=0.7.1"
22 | deno_path_util = "=0.6.4"
23 | deno_semver = "=0.9.1"
24 | url = "2.5"
25 |
26 | [dependencies.chrono]
27 | version = "0.4"
28 | default-features = false
29 |
30 | [dependencies.flate2]
31 | version = "1.0.30"
32 | default-features = false
33 | features = ["rust_backend"]
34 |
35 | [dependencies.deno_ast]
36 | version = "=0.52.0"
37 | features = ["transpiling"]
38 |
39 | [dependencies.deno_cache_dir]
40 | version = "=0.26.3"
41 | features = ["sync"]
42 |
43 | [dependencies.deno_config]
44 | path = "../../deno/libs/config"
45 | features = ["workspace","sync"]
46 |
47 | [dependencies.deno_graph]
48 | version = "=0.105.0"
49 | features = ["swc"]
50 | default-features = false
51 |
52 | [dependencies.deno_npm_cache]
53 | path = "../../deno/libs/npm_cache"
54 |
55 | [dependencies.deno_npm_installer]
56 | path = "../../deno/libs/npm_installer"
57 | default-features = false
58 |
59 | [dependencies.deno_resolver]
60 | path = "../../deno/libs/resolver"
61 | features = ["deno_ast","graph","sync"]
62 |
63 | [dependencies.deno_unsync]
64 | version = "0.4.4"
65 | default-features = false
66 |
67 | [dependencies.node_resolver]
68 | path = "../../deno/libs/node_resolver"
69 | features = ["sync"]
70 |
71 | [dependencies.sys_traits]
72 | version = "=0.1.17"
73 | features = ["real"]
74 |
75 | [target."cfg(target_arch = \"wasm32\")".dependencies.sys_traits]
76 | version = "=0.1.17"
77 | features = ["real","wasm"]
78 |
79 | [profile.release]
80 | codegen-units = 1
81 | incremental = true
82 | lto = true
83 | opt-level = "z"
84 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request]
4 |
5 | concurrency:
6 | group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
7 | cancel-in-progress: true
8 |
9 | jobs:
10 | deno:
11 | if: |
12 | github.event_name == 'push' ||
13 | !startsWith(github.event.pull_request.head.label, 'denoland:')
14 | runs-on: ${{ matrix.os }}
15 | timeout-minutes: 30
16 |
17 | strategy:
18 | matrix:
19 | os: [macOS-latest, ubuntu-latest, windows-latest]
20 |
21 | steps:
22 | - name: Change Windows git settings
23 | if: contains(matrix.os, 'windows')
24 | run: |
25 | git config --global core.autocrlf false
26 | git config --global core.eol native
27 | - uses: actions/checkout@v4
28 | with:
29 | submodules: true
30 |
31 | - uses: denoland/setup-deno@v2
32 | with:
33 | cache: true
34 | deno-version: canary
35 |
36 | - uses: Swatinem/rust-cache@v2
37 | with:
38 | workspaces: src/rs_lib
39 |
40 | - name: build
41 | run: deno task build
42 |
43 | - name: fmt
44 | if: contains(matrix.os, 'ubuntu')
45 | run: deno fmt --check
46 |
47 | - name: lint
48 | if: contains(matrix.os, 'ubuntu')
49 | run: |
50 | deno lint
51 | cd src/rs_lib && cargo clippy
52 |
53 | - name: check
54 | run: deno check --doc
55 |
56 | - name: test
57 | run: deno test -A
58 |
59 | jsr:
60 | runs-on: ubuntu-latest
61 | permissions:
62 | contents: read
63 | id-token: write
64 | steps:
65 | - uses: actions/checkout@v4
66 | with:
67 | submodules: true
68 | - uses: denoland/setup-deno@v2
69 | with:
70 | deno-version: canary
71 | - uses: Swatinem/rust-cache@v2
72 | with:
73 | workspaces: src/rs_lib
74 | - name: build
75 | run: deno task build
76 | - name: Publish to JSR on tag
77 | run: deno run -A jsr:@david/publish-on-tag@0.2.0
78 |
--------------------------------------------------------------------------------
/scripts/update-deps.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S deno run -A
2 | import $ from "@david/dax";
3 | import * as toml from "@std/toml";
4 |
5 | const rootDir = $.path(import.meta.dirname!).parentOrThrow();
6 | const denoDir = rootDir.join("deno");
7 |
8 | const oldCommit = (await $`git rev-parse HEAD`.cwd(denoDir).text()).trim();
9 | $.logLight("Previous commit", oldCommit);
10 | await $`git fetch --depth=1 origin`.cwd(denoDir);
11 | await $`git checkout origin/HEAD`.cwd(denoDir);
12 | const newCommit = (await $`git rev-parse HEAD`.cwd(denoDir).text()).trim();
13 | $.logLight("New commit", newCommit);
14 |
15 | const denoCargoTomlPath = denoDir.join("Cargo.toml");
16 | const denoCargoToml = toml.parse(denoCargoTomlPath.readTextSync()) as any;
17 | const denoDependencies = denoCargoToml.workspace.dependencies;
18 |
19 | const localCargoTomlPath = rootDir.join("src/rs_lib/Cargo.toml");
20 | const localCargoToml = toml.parse(localCargoTomlPath.readTextSync()) as any;
21 |
22 | for (const [key, value] of Object.entries(localCargoToml.dependencies)) {
23 | const newVersion = getVersion(denoDependencies[key]);
24 | if (newVersion == null) {
25 | continue;
26 | }
27 | if (typeof value === "string") {
28 | if (value !== newVersion) {
29 | $.logLight(`Updating ${key}@${value} to ${newVersion}`);
30 | localCargoToml.dependencies[key] = newVersion;
31 | }
32 | } else if (value != null && typeof value === "object" && "version" in value) {
33 | if (value.version !== newVersion) {
34 | $.logLight(`Updating ${key}@${value.version} to ${newVersion}`);
35 | value.version = newVersion;
36 | }
37 | }
38 | }
39 |
40 | localCargoTomlPath.writeTextSync(
41 | toml.stringify(localCargoToml)
42 | .trimStart()
43 | .replace(
44 | "[dependencies]",
45 | "# update this by running ./scripts/update-deps.ts\n[dependencies]",
46 | ),
47 | );
48 |
49 | function getVersion(dep: any): string | undefined {
50 | if (typeof dep === "string") {
51 | return dep;
52 | } else if (dep != null && typeof dep.version === "string") {
53 | return dep.version;
54 | } else {
55 | return undefined;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/mod.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MediaType,
3 | RequestedModuleType,
4 | ResolutionMode,
5 | Workspace,
6 | } from "./mod.ts";
7 | import { assert, assertEquals, assertRejects } from "@std/assert";
8 |
9 | Deno.test("should resolve, load and get graph", async () => {
10 | const workspace = new Workspace({
11 | nodeConditions: undefined, // ensure doesn't error
12 | });
13 | const modFileUrl = import.meta.resolve("./mod.ts");
14 | const loader = await workspace.createLoader();
15 | const diagnostics = await loader.addEntrypoints([modFileUrl]);
16 | assertEquals(diagnostics.length, 0);
17 | const graph = loader.getGraphUnstable();
18 | assertEquals((graph as any).roots[0], modFileUrl);
19 | const resolvedUrl = loader.resolveSync(
20 | "./mod.test.ts",
21 | modFileUrl,
22 | ResolutionMode.Import,
23 | );
24 | assertEquals(resolvedUrl, import.meta.url);
25 | {
26 | const loadResponse = await loader.load(
27 | import.meta.url,
28 | RequestedModuleType.Default,
29 | );
30 | if (loadResponse.kind !== "module") {
31 | throw new Error("Fail");
32 | }
33 | assertEquals(typeof loadResponse.specifier, "string");
34 | assert(loadResponse.code instanceof Uint8Array);
35 | assertEquals(loadResponse.mediaType, MediaType.TypeScript);
36 | }
37 | // node: specifier
38 | {
39 | const loadResponse = await loader.load(
40 | "node:events",
41 | RequestedModuleType.Default,
42 | );
43 | if (loadResponse.kind !== "external") {
44 | throw new Error("Fail");
45 | }
46 | assertEquals(typeof loadResponse.specifier, "string");
47 | assertEquals(loadResponse.specifier, "node:events");
48 | }
49 | });
50 |
51 | Deno.test("resolving a jsr specifier should fail with explanatory message", async () => {
52 | const workspace = new Workspace({});
53 | const modFileUrl = import.meta.resolve("./mod.ts");
54 | const loader = await workspace.createLoader();
55 | const diagnostics = await loader.addEntrypoints([modFileUrl]);
56 | assertEquals(diagnostics.length, 0);
57 | assertRejects(
58 | async () => {
59 | await loader.load(
60 | "jsr:@scope/version",
61 | RequestedModuleType.Default,
62 | );
63 | },
64 | Error,
65 | "Failed loading 'jsr:@scope/version'. jsr: specifiers must be resolved to an https: specifier before being loaded.",
66 | );
67 | });
68 |
--------------------------------------------------------------------------------
/tests/resolve_error/main.test.ts:
--------------------------------------------------------------------------------
1 | import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert";
2 | import { createLoader, ResolutionMode, ResolveError } from "../helpers.ts";
3 |
4 | Deno.test("error has extra properties", async (t) => {
5 | const mainFile = import.meta.dirname + "/testdata/main.ts";
6 | const { loader } = await createLoader({
7 | configPath: import.meta.dirname + "/testdata/deno.json",
8 | }, {
9 | entrypoints: [mainFile],
10 | });
11 |
12 | await t.step("code", () => {
13 | const err = assertThrows(() =>
14 | loader.resolveSync(
15 | "export-package/non-existent",
16 | import.meta.resolve("./testdata/main.ts"),
17 | ResolutionMode.Import,
18 | ), ResolveError);
19 | assertEquals(err.code, "ERR_PACKAGE_PATH_NOT_EXPORTED");
20 | assert(!err.isOptionalDependency);
21 | });
22 |
23 | await t.step("specifier", async () => {
24 | const err = await assertRejects(
25 | () =>
26 | loader.resolve(
27 | "open-package/non-existent.js",
28 | import.meta.resolve("./testdata/main.ts"),
29 | ResolutionMode.Import,
30 | ),
31 | ResolveError,
32 | );
33 | assertEquals(err.code, "ERR_MODULE_NOT_FOUND");
34 | assertEquals(
35 | err.specifier,
36 | import.meta.resolve(
37 | "./testdata/node_modules/open-package/non-existent.js",
38 | ),
39 | );
40 | assert(!err.isOptionalDependency);
41 | });
42 |
43 | await t.step("isOptionalDependency - optional dep", async () => {
44 | const err = await assertRejects(
45 | () =>
46 | loader.resolve(
47 | "optional",
48 | import.meta.resolve("./testdata/node_modules/optional-dep/index.js"),
49 | ResolutionMode.Import,
50 | ),
51 | ResolveError,
52 | );
53 | assertEquals(err.code, "ERR_MODULE_NOT_FOUND");
54 | assert(err.isOptionalDependency);
55 | });
56 |
57 | await t.step("isOptionalDependency - optional peer", async () => {
58 | const err = await assertRejects(
59 | () =>
60 | loader.resolve(
61 | "optional",
62 | import.meta.resolve("./testdata/node_modules/optional-peer/index.js"),
63 | ResolutionMode.Import,
64 | ),
65 | ResolveError,
66 | );
67 | assertEquals(err.code, "ERR_MODULE_NOT_FOUND");
68 | assert(err.isOptionalDependency);
69 | });
70 |
71 | await t.step("isOptionalDependency - folder package json", () => {
72 | const err = assertThrows(
73 | () =>
74 | loader.resolveSync(
75 | "optional",
76 | import.meta.resolve(
77 | "./testdata/node_modules/optional-dep/sub/index.js",
78 | ),
79 | ResolutionMode.Import,
80 | ),
81 | ResolveError,
82 | );
83 | assertEquals(err.code, "ERR_MODULE_NOT_FOUND");
84 | assert(err.isOptionalDependency);
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/tests/jsx/main.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | assertResponseText,
3 | createLoader,
4 | RequestedModuleType,
5 | ResolutionMode,
6 | type WorkspaceOptions,
7 | } from "../helpers.ts";
8 | import { assert, assertEquals } from "@std/assert";
9 |
10 | Deno.test("loads jsx transpiled", async () => {
11 | const mainJsx = import.meta.dirname + "/testdata/main.jsx";
12 | const mainTsx = import.meta.dirname + "/testdata/main.tsx";
13 | const createWorkspace = async (options?: WorkspaceOptions) => {
14 | return await createLoader({
15 | configPath: import.meta.dirname + "/testdata/deno.json",
16 | ...(options ?? {}),
17 | }, {
18 | entrypoints: [mainJsx],
19 | });
20 | };
21 | const { loader } = await createWorkspace();
22 |
23 | const mainJsxSourceMappingURL =
24 | "//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanN4Il0sInNvdXJjZXNDb250ZW50IjpbImNvbnNvbGUubG9nKDxkaXYgLz4pO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSxRQUFRLEdBQUcifQ==";
25 | const mainTsxSourceMappingURL =
26 | "//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4udHN4Il0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHZhbHVlOiBzdHJpbmcgPSBcIlwiO1xuY29uc29sZS5sb2coPGRpdiAvPiwgdmFsdWUpO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sUUFBZ0I7QUFDdEIsUUFBUSxHQUFHLEVBQUUsT0FBUSJ9";
27 | const mainJsxUrl = loader.resolveSync(
28 | mainJsx,
29 | undefined,
30 | ResolutionMode.Import,
31 | );
32 | const mainTsxUrl = loader.resolveSync(
33 | mainTsx,
34 | undefined,
35 | ResolutionMode.Import,
36 | );
37 |
38 | assertResponseText(
39 | await loader.load(mainJsxUrl, RequestedModuleType.Default),
40 | `import { jsxTemplate as _jsxTemplate } from "react/jsx-runtime";
41 | const $$_tpl_1 = [
42 | ""
43 | ];
44 | console.log(_jsxTemplate($$_tpl_1));
45 | ${mainJsxSourceMappingURL}`,
46 | );
47 |
48 | // resolves jsx-dev
49 | const jsx = loader.resolveSync(
50 | "react/jsx-dev-runtime",
51 | mainTsx,
52 | ResolutionMode.Import,
53 | );
54 | assert(jsx.startsWith("file:"));
55 |
56 | {
57 | const { workspace } = await createWorkspace({ preserveJsx: true });
58 | const newLoader = await workspace.createLoader();
59 | const diagnostics = await newLoader.addEntrypoints([mainJsx, mainTsxUrl]);
60 | assertEquals(diagnostics, []);
61 | assertResponseText(
62 | await newLoader.load(mainJsxUrl, RequestedModuleType.Default),
63 | "console.log();\n",
64 | );
65 | assertResponseText(
66 | await newLoader.load(mainTsxUrl, RequestedModuleType.Default),
67 | `const value = "";\nconsole.log(, value);\n${mainTsxSourceMappingURL}`,
68 | );
69 | }
70 | {
71 | const { workspace } = await createWorkspace({ noTranspile: true });
72 | const newLoader = await workspace.createLoader();
73 | const diagnostics = await newLoader.addEntrypoints([mainJsx, mainTsx]);
74 | assertEquals(diagnostics, []);
75 | assertResponseText(
76 | await newLoader.load(mainJsxUrl, RequestedModuleType.Default),
77 | `console.log();\n`,
78 | );
79 | assertResponseText(
80 | await newLoader.load(mainTsxUrl, RequestedModuleType.Default),
81 | `const value: string = "";\nconsole.log(, value);\n`,
82 | );
83 | }
84 | });
85 |
--------------------------------------------------------------------------------
/src/rs_lib/http_client.rs:
--------------------------------------------------------------------------------
1 | use deno_cache_dir::file_fetcher::HeaderMap;
2 | use deno_cache_dir::file_fetcher::HeaderName;
3 | use deno_cache_dir::file_fetcher::HeaderValue;
4 | use deno_cache_dir::file_fetcher::SendError;
5 | use deno_cache_dir::file_fetcher::SendResponse;
6 | use deno_cache_dir::file_fetcher::StatusCode;
7 | use deno_error::JsErrorBox;
8 | use deno_npm_cache::NpmCacheHttpClientResponse;
9 | use js_sys::Array;
10 | use js_sys::Object;
11 | use js_sys::Reflect;
12 | use serde::Deserialize;
13 | use url::Url;
14 | use wasm_bindgen::JsCast;
15 | use wasm_bindgen::JsValue;
16 | use wasm_bindgen::prelude::wasm_bindgen;
17 |
18 | #[wasm_bindgen(module = "/helpers.js")]
19 | extern "C" {
20 | async fn fetch_specifier(specifier: String, headers: JsValue) -> JsValue;
21 | }
22 |
23 | enum FetchResult {
24 | Response(Response),
25 | Error(FetchError),
26 | }
27 |
28 | #[derive(Deserialize)]
29 | struct FetchError {
30 | pub error: String,
31 | }
32 |
33 | struct Response {
34 | pub status: u16,
35 | pub body: Vec,
36 | pub headers: HeaderMap,
37 | }
38 |
39 | async fn fetch_specifier_typed(
40 | specifier: &str,
41 | headers: Vec<(String, String)>,
42 | ) -> Result {
43 | let headers = headers_to_js_object(&headers);
44 | let response = fetch_specifier(specifier.to_string(), headers).await;
45 | parse_fetch_result(response).map_err(|err| {
46 | if let Some(s) = err.as_string() {
47 | anyhow::anyhow!(s)
48 | } else {
49 | // Optionally stringify complex JS error objects
50 | anyhow::anyhow!(format!("{:?}", err))
51 | }
52 | })
53 | }
54 |
55 | #[derive(Debug, Default, Clone)]
56 | pub struct WasmHttpClient {
57 | pub cached_only: bool,
58 | }
59 |
60 | #[async_trait::async_trait(?Send)]
61 | impl deno_cache_dir::file_fetcher::HttpClient for WasmHttpClient {
62 | async fn send_no_follow(
63 | &self,
64 | url: &Url,
65 | headers: HeaderMap,
66 | ) -> Result {
67 | if self.cached_only {
68 | return Err(SendError::Failed(Box::new(std::io::Error::other(
69 | "Cannot download because --cached-only was specified.",
70 | ))));
71 | }
72 | let headers = headers
73 | .into_iter()
74 | .filter_map(|(k, v)| Some((k?.to_string(), v.to_str().ok()?.to_string())))
75 | .collect::>();
76 | let result =
77 | fetch_specifier_typed(url.as_str(), headers)
78 | .await
79 | .map_err(|err| {
80 | SendError::Failed(Box::new(std::io::Error::other(
81 | err.to_string(),
82 | )))
83 | })?;
84 | let response = match result {
85 | FetchResult::Response(response) => response,
86 | FetchResult::Error(fetch_error) => {
87 | return Err(SendError::Failed(fetch_error.error.into()));
88 | }
89 | };
90 | match response.status {
91 | 304 => Ok(SendResponse::NotModified),
92 | 300..=399 => Ok(SendResponse::Redirect(response.headers)),
93 | 404 => Err(SendError::NotFound),
94 | 200..=299 => Ok(SendResponse::Success(response.headers, response.body)),
95 | _ => Err(SendError::StatusCode(
96 | StatusCode::from_u16(response.status).unwrap(),
97 | )),
98 | }
99 | }
100 | }
101 |
102 | #[async_trait::async_trait(?Send)]
103 | impl deno_npm_cache::NpmCacheHttpClient for WasmHttpClient {
104 | // todo: implement retrying
105 | async fn download_with_retries_on_any_tokio_runtime(
106 | &self,
107 | url: Url,
108 | maybe_auth: Option,
109 | maybe_etag: Option,
110 | ) -> Result {
111 | let mut headers = Vec::new();
112 | if let Some(auth) = maybe_auth {
113 | headers.push(("authorization".to_string(), auth));
114 | }
115 | if let Some(etag) = maybe_etag {
116 | headers.push(("if-none-match".to_string(), etag));
117 | }
118 |
119 | let result =
120 | fetch_specifier_typed(url.as_str(), headers)
121 | .await
122 | .map_err(|err| deno_npm_cache::DownloadError {
123 | status_code: None,
124 | error: JsErrorBox::generic(err.to_string()),
125 | })?;
126 |
127 | let response = match result {
128 | FetchResult::Response(res) => res,
129 | FetchResult::Error(fetch_error) => {
130 | return Err(deno_npm_cache::DownloadError {
131 | status_code: None,
132 | error: JsErrorBox::generic(fetch_error.error),
133 | });
134 | }
135 | };
136 |
137 | match response.status {
138 | 200..=299 => {
139 | let etag = response.headers.iter().find_map(|(k, v)| {
140 | if k.as_str().eq_ignore_ascii_case("etag") {
141 | Some(v.to_str().ok()?.to_string())
142 | } else {
143 | None
144 | }
145 | });
146 |
147 | Ok(NpmCacheHttpClientResponse::Bytes(
148 | deno_npm_cache::NpmCacheHttpClientBytesResponse {
149 | etag,
150 | bytes: response.body,
151 | },
152 | ))
153 | }
154 | 304 => Ok(NpmCacheHttpClientResponse::NotModified),
155 | 404 => Ok(NpmCacheHttpClientResponse::NotFound),
156 | code => Err(deno_npm_cache::DownloadError {
157 | status_code: Some(code),
158 | error: JsErrorBox::generic(format!("Unexpected status: {code}")),
159 | }),
160 | }
161 | }
162 | }
163 |
164 | fn headers_to_js_object(headers: &[(String, String)]) -> JsValue {
165 | let obj = Object::new();
166 | for (key, value) in headers {
167 | Reflect::set(&obj, &JsValue::from_str(key), &JsValue::from_str(value))
168 | .unwrap();
169 | }
170 | obj.into()
171 | }
172 |
173 | fn parse_fetch_result(js_value: JsValue) -> Result {
174 | let has_error = Reflect::has(&js_value, &JsValue::from_str("error"))?;
175 | if has_error {
176 | let error: FetchError = serde_wasm_bindgen::from_value(js_value)?;
177 | return Ok(FetchResult::Error(error));
178 | }
179 | Ok(FetchResult::Response(parse_response(js_value)?))
180 | }
181 |
182 | fn parse_response(js_value: JsValue) -> Result {
183 | let status = Reflect::get(&js_value, &JsValue::from_str("status"))?
184 | .as_f64()
185 | .ok_or_else(|| JsValue::from_str("status must be a number"))?
186 | as u16;
187 |
188 | let body_js = Reflect::get(&js_value, &JsValue::from_str("body"))?;
189 | let body: Vec = serde_wasm_bindgen::from_value(body_js)?;
190 |
191 | let headers_js = Reflect::get(&js_value, &JsValue::from_str("headers"))?;
192 | let headers = response_headers_to_headermap(headers_js);
193 |
194 | Ok(Response {
195 | status,
196 | body,
197 | headers,
198 | })
199 | }
200 |
201 | fn response_headers_to_headermap(headers: JsValue) -> HeaderMap {
202 | let mut map = HeaderMap::new();
203 | let entries_fn = Reflect::get(&headers, &JsValue::from_str("entries"));
204 | let Ok(entries_fn) = entries_fn else {
205 | return map;
206 | };
207 |
208 | let entries_iter = js_sys::Function::from(entries_fn)
209 | .call0(&headers)
210 | .ok()
211 | .and_then(|iter| iter.dyn_into::().ok());
212 |
213 | let Some(iter) = entries_iter else {
214 | return map;
215 | };
216 |
217 | while let Ok(next) = iter.next() {
218 | if next.done() {
219 | break;
220 | }
221 |
222 | let val = next.value();
223 | let pair = Array::from(&val);
224 | if pair.length() != 2 {
225 | continue;
226 | }
227 |
228 | let key = pair.get(0).as_string();
229 | let value = pair.get(1).as_string();
230 |
231 | if let (Some(k), Some(v)) = (key, value)
232 | && let (Ok(name), Ok(val)) = (
233 | HeaderName::from_bytes(k.as_bytes()),
234 | HeaderValue::from_str(&v),
235 | ) {
236 | map.append(name, val);
237 | }
238 | }
239 |
240 | map
241 | }
242 |
--------------------------------------------------------------------------------
/src/mod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Resolver and loader for Deno code.
3 | *
4 | * This can be used to create bundler plugins or libraries that use deno resolution.
5 | *
6 | * @example
7 | * ```ts
8 | * import { Workspace, ResolutionMode, type LoadResponse, RequestedModuleType } from "@deno/loader";
9 | *
10 | * const workspace = new Workspace({
11 | * // optional options
12 | * });
13 | * const loader = await workspace.createLoader();
14 | * const diagnostics = await loader.addEntrypoints(["./mod.ts"])
15 | * if (diagnostics.length > 0) {
16 | * throw new Error(diagnostics[0].message);
17 | * }
18 | * // alternatively use resolve to resolve npm/jsr specifiers not found
19 | * // in the entrypoints or if not being able to provide entrypoints
20 | * const resolvedUrl = loader.resolveSync(
21 | * "./mod.test.ts",
22 | * "https://deno.land/mod.ts", // referrer
23 | * ResolutionMode.Import,
24 | * );
25 | * const response = await loader.load(resolvedUrl, RequestedModuleType.Default);
26 | * if (response.kind === "module") {
27 | * console.log(response.specifier);
28 | * console.log(response.code);
29 | * console.log(response.mediaType);
30 | * } else if (response.kind === "external") {
31 | * console.log(response.specifier)
32 | * } else {
33 | * const _assertNever = response;
34 | * throw new Error(`Unhandled kind: ${(response as LoadResponse).kind}`);
35 | * }
36 | * ```
37 | * @module
38 | */
39 |
40 | import {
41 | DenoLoader as WasmLoader,
42 | DenoWorkspace as WasmWorkspace,
43 | } from "./lib/rs_lib.js";
44 |
45 | /** Options for creating a workspace. */
46 | export interface WorkspaceOptions {
47 | /** Do not do config file discovery. */
48 | noConfig?: boolean;
49 | /** Do not respect the lockfile. */
50 | noLock?: boolean;
51 | /** Path or file: URL to the config file if you do not want to do config file discovery. */
52 | configPath?: string;
53 | /** Node resolution conditions to use for resolving package.json exports. */
54 | nodeConditions?: string[];
55 | /** Date for the newest allowed dependency. */
56 | newestDependencyDate?: Date;
57 | /**
58 | * Platform to bundle for.
59 | * @default "node"
60 | */
61 | platform?: "node" | "browser";
62 | /** Whether to force using the cache. */
63 | cachedOnly?: boolean;
64 | /**
65 | * Enable debug logs.
66 | *
67 | * @remarks Note that the Rust debug logs are enabled globally
68 | * and can only be enabled by the first workspace that gets
69 | * created. This is a limitation of how the Rust logging works.
70 | */
71 | debug?: boolean;
72 | /** Whether to preserve JSX syntax in the loaded output. */
73 | preserveJsx?: boolean;
74 | /** Skip transpiling TypeScript and JSX. */
75 | noTranspile?: boolean;
76 | }
77 |
78 | export class ResolveError extends Error {
79 | /**
80 | * Possible specifier this would resolve to if the error did not occur.
81 | *
82 | * This is useful for implementing something like `import.meta.resolve` where
83 | * you want the resolution to always occur and not error.
84 | */
85 | specifier?: string;
86 | /** Node.js error code. */
87 | code?: string;
88 | /**
89 | * If the specifier being resolved was an optional npm dependency.
90 | *
91 | * @remarks This will only be true when the error code is
92 | * `ERR_MODULE_NOT_FOUND`.
93 | */
94 | isOptionalDependency?: boolean;
95 | }
96 |
97 | /** File type. */
98 | export enum MediaType {
99 | JavaScript = 0,
100 | Jsx = 1,
101 | Mjs = 2,
102 | Cjs = 3,
103 | TypeScript = 4,
104 | Mts = 5,
105 | Cts = 6,
106 | Dts = 7,
107 | Dmts = 8,
108 | Dcts = 9,
109 | Tsx = 10,
110 | Css = 11,
111 | Json = 12,
112 | Jsonc = 13,
113 | Json5 = 14,
114 | Html = 15,
115 | Sql = 16,
116 | Wasm = 17,
117 | SourceMap = 18,
118 | Unknown = 19,
119 | }
120 |
121 | /** A response received from a load. */
122 | export type LoadResponse = ModuleLoadResponse | ExternalLoadResponse;
123 |
124 | /** A response that indicates the module is external.
125 | *
126 | * This will occur for `node:` specifiers for example.
127 | */
128 | export interface ExternalLoadResponse {
129 | /** Kind of response. */
130 | kind: "external";
131 | /**
132 | * Fully resolved URL.
133 | *
134 | * This may be different than the provided specifier. For example, during loading
135 | * it may encounter redirects and this specifier is the redirected to final specifier.
136 | */
137 | specifier: string;
138 | }
139 |
140 | /** A response that loads a module. */
141 | export interface ModuleLoadResponse {
142 | /** Kind of response. */
143 | kind: "module";
144 | /**
145 | * Fully resolved URL.
146 | *
147 | * This may be different than the provided specifier. For example, during loading
148 | * it may encounter redirects and this specifier is the redirected to final specifier.
149 | */
150 | specifier: string;
151 | /** Content that was loaded. */
152 | mediaType: MediaType;
153 | /** Code that was loaded. */
154 | code: Uint8Array;
155 | }
156 |
157 | /** Kind of resolution. */
158 | export enum ResolutionMode {
159 | /** Resolving from an ESM file. */
160 | Import = 0,
161 | /** Resolving from a CJS file. */
162 | Require = 1,
163 | }
164 |
165 | /** Resolves the workspace. */
166 | export class Workspace implements Disposable {
167 | #inner: WasmWorkspace;
168 | #debug: boolean;
169 |
170 | /** Creates a `DenoWorkspace` with the provided options. */
171 | constructor(options: WorkspaceOptions = {}) {
172 | this.#inner = new WasmWorkspace(options);
173 | this.#debug = options.debug ?? false;
174 | }
175 |
176 | [Symbol.dispose]() {
177 | this.#inner.free();
178 | }
179 |
180 | /** Creates a loader that uses this this workspace. */
181 | async createLoader(): Promise {
182 | const wasmLoader = await this.#inner.create_loader();
183 | return new Loader(wasmLoader, this.#debug);
184 | }
185 | }
186 |
187 | export enum RequestedModuleType {
188 | Default = 0,
189 | Json = 1,
190 | Text = 2,
191 | Bytes = 3,
192 | }
193 |
194 | export interface EntrypointDiagnostic {
195 | message: string;
196 | }
197 |
198 | /** A loader for resolving and loading urls. */
199 | export class Loader implements Disposable {
200 | #inner: WasmLoader;
201 | #debug: boolean;
202 |
203 | /** @internal */
204 | constructor(loader: WasmLoader, debug: boolean) {
205 | if (!(loader instanceof WasmLoader)) {
206 | throw new Error("Get the loader from the workspace.");
207 | }
208 | this.#inner = loader;
209 | this.#debug = debug;
210 | }
211 |
212 | [Symbol.dispose]() {
213 | this.#inner.free();
214 | }
215 |
216 | /** Adds entrypoints to the loader.
217 | *
218 | * It's useful to specify entrypoints so that the loader can resolve
219 | * npm: and jsr: specifiers the same way that Deno does when not using
220 | * a lockfile.
221 | */
222 | async addEntrypoints(
223 | entrypoints: string[],
224 | ): Promise {
225 | const messages = await this.#inner.add_entrypoints(entrypoints);
226 | return messages.map((message) => ({ message }));
227 | }
228 |
229 | /** Synchronously resolves a specifier using the given referrer and resolution mode.
230 | * @throws {ResolveError}
231 | */
232 | resolveSync(
233 | specifier: string,
234 | referrer: string | undefined,
235 | resolutionMode: ResolutionMode,
236 | ): string {
237 | if (this.#debug) {
238 | console.error(
239 | `DEBUG - Resolving '${specifier}' from '${
240 | referrer ?? ""
241 | }' (${resolutionModeToString(resolutionMode)})`,
242 | );
243 | }
244 | try {
245 | const value = this.#inner.resolve_sync(
246 | specifier,
247 | referrer,
248 | resolutionMode,
249 | );
250 | if (this.#debug) {
251 | console.error(`DEBUG - Resolved to '${value}'`);
252 | }
253 | return value;
254 | } catch (err) {
255 | Object.setPrototypeOf(err, ResolveError.prototype);
256 | throw err;
257 | }
258 | }
259 |
260 | /** Asynchronously resolves a specifier using the given referrer and resolution mode.
261 | *
262 | * This is useful for resolving `jsr:` and `npm:` specifiers on the fly when they can't
263 | * be figured out from entrypoints, but it may cause multiple "npm install"s and different
264 | * npm or jsr resolution than Deno. For that reason it's better to provide the list of
265 | * entrypoints up front so the loader can create the npm and jsr graph, and then after use
266 | * synchronous resolution to resolve jsr and npm specifiers.
267 | *
268 | * @throws {ResolveError}
269 | */
270 | async resolve(
271 | specifier: string,
272 | referrer: string | undefined,
273 | resolutionMode: ResolutionMode,
274 | ): Promise {
275 | if (this.#debug) {
276 | console.error(
277 | `DEBUG - Resolving '${specifier}' from '${
278 | referrer ?? ""
279 | }' (${resolutionModeToString(resolutionMode)})`,
280 | );
281 | }
282 | try {
283 | const value = await this.#inner.resolve(
284 | specifier,
285 | referrer,
286 | resolutionMode,
287 | );
288 | if (this.#debug) {
289 | console.error(`DEBUG - Resolved to '${value}'`);
290 | }
291 | return value;
292 | } catch (err) {
293 | Object.setPrototypeOf(err, ResolveError.prototype);
294 | throw err;
295 | }
296 | }
297 |
298 | /** Loads a specifier. */
299 | load(
300 | specifier: string,
301 | requestedModuleType: RequestedModuleType,
302 | ): Promise {
303 | if (this.#debug) {
304 | console.error(
305 | `DEBUG - Loading '${specifier}' with type '${
306 | requestedModuleTypeToString(requestedModuleType) ?? ""
307 | }'`,
308 | );
309 | }
310 | return this.#inner.load(specifier, requestedModuleType);
311 | }
312 |
313 | /** Gets the module graph.
314 | *
315 | * WARNING: This function is very unstable and the output may change between
316 | * patch releases.
317 | */
318 | getGraphUnstable(): unknown {
319 | return this.#inner.get_graph();
320 | }
321 | }
322 |
323 | function requestedModuleTypeToString(moduleType: RequestedModuleType) {
324 | switch (moduleType) {
325 | case RequestedModuleType.Bytes:
326 | return "bytes";
327 | case RequestedModuleType.Text:
328 | return "text";
329 | case RequestedModuleType.Json:
330 | return "json";
331 | case RequestedModuleType.Default:
332 | return undefined;
333 | default: {
334 | const _never: never = moduleType;
335 | return undefined;
336 | }
337 | }
338 | }
339 |
340 | function resolutionModeToString(mode: ResolutionMode) {
341 | switch (mode) {
342 | case ResolutionMode.Import:
343 | return "import";
344 | case ResolutionMode.Require:
345 | return "require";
346 | default: {
347 | const _assertNever: never = mode;
348 | return "unknown";
349 | }
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/deno.lock:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "specifiers": {
4 | "jsr:@david/console-static-text@0.3": "0.3.0",
5 | "jsr:@david/dax@~0.43.2": "0.43.2",
6 | "jsr:@david/path@0.2": "0.2.0",
7 | "jsr:@david/which@~0.4.1": "0.4.1",
8 | "jsr:@std/assert@^1.0.13": "1.0.13",
9 | "jsr:@std/bytes@^1.0.5": "1.0.6",
10 | "jsr:@std/collections@^1.1.1": "1.1.1",
11 | "jsr:@std/fmt@1": "1.0.8",
12 | "jsr:@std/fs@1": "1.0.18",
13 | "jsr:@std/internal@^1.0.6": "1.0.8",
14 | "jsr:@std/io@0.225": "0.225.2",
15 | "jsr:@std/path@1": "1.1.0",
16 | "jsr:@std/path@^1.1.0": "1.1.0",
17 | "jsr:@std/toml@^1.0.7": "1.0.7",
18 | "npm:@types/jscodeshift@*": "17.3.0",
19 | "npm:@types/node@*": "22.15.15",
20 | "npm:jscodeshift@0.15.2": "0.15.2_@babel+core@7.27.4"
21 | },
22 | "jsr": {
23 | "@david/console-static-text@0.3.0": {
24 | "integrity": "2dfb46ecee525755f7989f94ece30bba85bd8ffe3e8666abc1bf926e1ee0698d"
25 | },
26 | "@david/dax@0.43.2": {
27 | "integrity": "8c7df175465d994c0e3568e3eb91102768c2f1c86d2a513d7fc4cab13f9cb328",
28 | "dependencies": [
29 | "jsr:@david/console-static-text",
30 | "jsr:@david/path",
31 | "jsr:@david/which",
32 | "jsr:@std/fmt",
33 | "jsr:@std/fs",
34 | "jsr:@std/io",
35 | "jsr:@std/path@1"
36 | ]
37 | },
38 | "@david/path@0.2.0": {
39 | "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd",
40 | "dependencies": [
41 | "jsr:@std/fs",
42 | "jsr:@std/path@1"
43 | ]
44 | },
45 | "@david/which@0.4.1": {
46 | "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e"
47 | },
48 | "@std/assert@1.0.13": {
49 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
50 | "dependencies": [
51 | "jsr:@std/internal"
52 | ]
53 | },
54 | "@std/bytes@1.0.6": {
55 | "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"
56 | },
57 | "@std/collections@1.1.1": {
58 | "integrity": "eff6443fbd9d5a6697018fb39c5d13d5f662f0045f21392d640693d0008ab2af"
59 | },
60 | "@std/fmt@1.0.8": {
61 | "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
62 | },
63 | "@std/fs@1.0.18": {
64 | "integrity": "24bcad99eab1af4fde75e05da6e9ed0e0dce5edb71b7e34baacf86ffe3969f3a",
65 | "dependencies": [
66 | "jsr:@std/path@^1.1.0"
67 | ]
68 | },
69 | "@std/internal@1.0.8": {
70 | "integrity": "fc66e846d8d38a47cffd274d80d2ca3f0de71040f855783724bb6b87f60891f5"
71 | },
72 | "@std/io@0.225.2": {
73 | "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7",
74 | "dependencies": [
75 | "jsr:@std/bytes"
76 | ]
77 | },
78 | "@std/path@1.1.0": {
79 | "integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886"
80 | },
81 | "@std/toml@1.0.7": {
82 | "integrity": "3c86f8bbde31578da33d2fbe410b80e3cb672b66e008e06cf41afc4d7409921c",
83 | "dependencies": [
84 | "jsr:@std/collections"
85 | ]
86 | }
87 | },
88 | "npm": {
89 | "@ampproject/remapping@2.3.0": {
90 | "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
91 | "dependencies": [
92 | "@jridgewell/gen-mapping",
93 | "@jridgewell/trace-mapping"
94 | ]
95 | },
96 | "@babel/code-frame@7.27.1": {
97 | "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
98 | "dependencies": [
99 | "@babel/helper-validator-identifier",
100 | "js-tokens",
101 | "picocolors"
102 | ]
103 | },
104 | "@babel/compat-data@7.27.5": {
105 | "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg=="
106 | },
107 | "@babel/core@7.27.4": {
108 | "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
109 | "dependencies": [
110 | "@ampproject/remapping",
111 | "@babel/code-frame",
112 | "@babel/generator",
113 | "@babel/helper-compilation-targets",
114 | "@babel/helper-module-transforms",
115 | "@babel/helpers",
116 | "@babel/parser",
117 | "@babel/template",
118 | "@babel/traverse",
119 | "@babel/types",
120 | "convert-source-map",
121 | "debug",
122 | "gensync",
123 | "json5",
124 | "semver@6.3.1"
125 | ]
126 | },
127 | "@babel/generator@7.27.5": {
128 | "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
129 | "dependencies": [
130 | "@babel/parser",
131 | "@babel/types",
132 | "@jridgewell/gen-mapping",
133 | "@jridgewell/trace-mapping",
134 | "jsesc"
135 | ]
136 | },
137 | "@babel/helper-annotate-as-pure@7.27.3": {
138 | "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
139 | "dependencies": [
140 | "@babel/types"
141 | ]
142 | },
143 | "@babel/helper-compilation-targets@7.27.2": {
144 | "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
145 | "dependencies": [
146 | "@babel/compat-data",
147 | "@babel/helper-validator-option",
148 | "browserslist",
149 | "lru-cache",
150 | "semver@6.3.1"
151 | ]
152 | },
153 | "@babel/helper-create-class-features-plugin@7.27.1_@babel+core@7.27.4": {
154 | "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==",
155 | "dependencies": [
156 | "@babel/core",
157 | "@babel/helper-annotate-as-pure",
158 | "@babel/helper-member-expression-to-functions",
159 | "@babel/helper-optimise-call-expression",
160 | "@babel/helper-replace-supers",
161 | "@babel/helper-skip-transparent-expression-wrappers",
162 | "@babel/traverse",
163 | "semver@6.3.1"
164 | ]
165 | },
166 | "@babel/helper-member-expression-to-functions@7.27.1": {
167 | "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
168 | "dependencies": [
169 | "@babel/traverse",
170 | "@babel/types"
171 | ]
172 | },
173 | "@babel/helper-module-imports@7.27.1": {
174 | "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
175 | "dependencies": [
176 | "@babel/traverse",
177 | "@babel/types"
178 | ]
179 | },
180 | "@babel/helper-module-transforms@7.27.3_@babel+core@7.27.4": {
181 | "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
182 | "dependencies": [
183 | "@babel/core",
184 | "@babel/helper-module-imports",
185 | "@babel/helper-validator-identifier",
186 | "@babel/traverse"
187 | ]
188 | },
189 | "@babel/helper-optimise-call-expression@7.27.1": {
190 | "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
191 | "dependencies": [
192 | "@babel/types"
193 | ]
194 | },
195 | "@babel/helper-plugin-utils@7.27.1": {
196 | "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="
197 | },
198 | "@babel/helper-replace-supers@7.27.1_@babel+core@7.27.4": {
199 | "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
200 | "dependencies": [
201 | "@babel/core",
202 | "@babel/helper-member-expression-to-functions",
203 | "@babel/helper-optimise-call-expression",
204 | "@babel/traverse"
205 | ]
206 | },
207 | "@babel/helper-skip-transparent-expression-wrappers@7.27.1": {
208 | "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
209 | "dependencies": [
210 | "@babel/traverse",
211 | "@babel/types"
212 | ]
213 | },
214 | "@babel/helper-string-parser@7.27.1": {
215 | "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
216 | },
217 | "@babel/helper-validator-identifier@7.27.1": {
218 | "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
219 | },
220 | "@babel/helper-validator-option@7.27.1": {
221 | "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
222 | },
223 | "@babel/helpers@7.27.6": {
224 | "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
225 | "dependencies": [
226 | "@babel/template",
227 | "@babel/types"
228 | ]
229 | },
230 | "@babel/parser@7.27.5": {
231 | "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
232 | "dependencies": [
233 | "@babel/types"
234 | ],
235 | "bin": true
236 | },
237 | "@babel/plugin-syntax-flow@7.27.1_@babel+core@7.27.4": {
238 | "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==",
239 | "dependencies": [
240 | "@babel/core",
241 | "@babel/helper-plugin-utils"
242 | ]
243 | },
244 | "@babel/plugin-syntax-jsx@7.27.1_@babel+core@7.27.4": {
245 | "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
246 | "dependencies": [
247 | "@babel/core",
248 | "@babel/helper-plugin-utils"
249 | ]
250 | },
251 | "@babel/plugin-syntax-typescript@7.27.1_@babel+core@7.27.4": {
252 | "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
253 | "dependencies": [
254 | "@babel/core",
255 | "@babel/helper-plugin-utils"
256 | ]
257 | },
258 | "@babel/plugin-transform-class-properties@7.27.1_@babel+core@7.27.4": {
259 | "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
260 | "dependencies": [
261 | "@babel/core",
262 | "@babel/helper-create-class-features-plugin",
263 | "@babel/helper-plugin-utils"
264 | ]
265 | },
266 | "@babel/plugin-transform-flow-strip-types@7.27.1_@babel+core@7.27.4": {
267 | "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==",
268 | "dependencies": [
269 | "@babel/core",
270 | "@babel/helper-plugin-utils",
271 | "@babel/plugin-syntax-flow"
272 | ]
273 | },
274 | "@babel/plugin-transform-modules-commonjs@7.27.1_@babel+core@7.27.4": {
275 | "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
276 | "dependencies": [
277 | "@babel/core",
278 | "@babel/helper-module-transforms",
279 | "@babel/helper-plugin-utils"
280 | ]
281 | },
282 | "@babel/plugin-transform-nullish-coalescing-operator@7.27.1_@babel+core@7.27.4": {
283 | "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
284 | "dependencies": [
285 | "@babel/core",
286 | "@babel/helper-plugin-utils"
287 | ]
288 | },
289 | "@babel/plugin-transform-optional-chaining@7.27.1_@babel+core@7.27.4": {
290 | "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
291 | "dependencies": [
292 | "@babel/core",
293 | "@babel/helper-plugin-utils",
294 | "@babel/helper-skip-transparent-expression-wrappers"
295 | ]
296 | },
297 | "@babel/plugin-transform-private-methods@7.27.1_@babel+core@7.27.4": {
298 | "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==",
299 | "dependencies": [
300 | "@babel/core",
301 | "@babel/helper-create-class-features-plugin",
302 | "@babel/helper-plugin-utils"
303 | ]
304 | },
305 | "@babel/plugin-transform-typescript@7.27.1_@babel+core@7.27.4": {
306 | "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==",
307 | "dependencies": [
308 | "@babel/core",
309 | "@babel/helper-annotate-as-pure",
310 | "@babel/helper-create-class-features-plugin",
311 | "@babel/helper-plugin-utils",
312 | "@babel/helper-skip-transparent-expression-wrappers",
313 | "@babel/plugin-syntax-typescript"
314 | ]
315 | },
316 | "@babel/preset-flow@7.27.1_@babel+core@7.27.4": {
317 | "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==",
318 | "dependencies": [
319 | "@babel/core",
320 | "@babel/helper-plugin-utils",
321 | "@babel/helper-validator-option",
322 | "@babel/plugin-transform-flow-strip-types"
323 | ]
324 | },
325 | "@babel/preset-typescript@7.27.1_@babel+core@7.27.4": {
326 | "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
327 | "dependencies": [
328 | "@babel/core",
329 | "@babel/helper-plugin-utils",
330 | "@babel/helper-validator-option",
331 | "@babel/plugin-syntax-jsx",
332 | "@babel/plugin-transform-modules-commonjs",
333 | "@babel/plugin-transform-typescript"
334 | ]
335 | },
336 | "@babel/register@7.27.1_@babel+core@7.27.4": {
337 | "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==",
338 | "dependencies": [
339 | "@babel/core",
340 | "clone-deep",
341 | "find-cache-dir",
342 | "make-dir",
343 | "pirates",
344 | "source-map-support"
345 | ]
346 | },
347 | "@babel/template@7.27.2": {
348 | "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
349 | "dependencies": [
350 | "@babel/code-frame",
351 | "@babel/parser",
352 | "@babel/types"
353 | ]
354 | },
355 | "@babel/traverse@7.27.4": {
356 | "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
357 | "dependencies": [
358 | "@babel/code-frame",
359 | "@babel/generator",
360 | "@babel/parser",
361 | "@babel/template",
362 | "@babel/types",
363 | "debug",
364 | "globals"
365 | ]
366 | },
367 | "@babel/types@7.27.6": {
368 | "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
369 | "dependencies": [
370 | "@babel/helper-string-parser",
371 | "@babel/helper-validator-identifier"
372 | ]
373 | },
374 | "@jridgewell/gen-mapping@0.3.8": {
375 | "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
376 | "dependencies": [
377 | "@jridgewell/set-array",
378 | "@jridgewell/sourcemap-codec",
379 | "@jridgewell/trace-mapping"
380 | ]
381 | },
382 | "@jridgewell/resolve-uri@3.1.2": {
383 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
384 | },
385 | "@jridgewell/set-array@1.2.1": {
386 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
387 | },
388 | "@jridgewell/sourcemap-codec@1.5.0": {
389 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
390 | },
391 | "@jridgewell/trace-mapping@0.3.25": {
392 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
393 | "dependencies": [
394 | "@jridgewell/resolve-uri",
395 | "@jridgewell/sourcemap-codec"
396 | ]
397 | },
398 | "@types/jscodeshift@17.3.0": {
399 | "integrity": "sha512-ogvGG8VQQqAQQ096uRh+d6tBHrYuZjsumHirKtvBa5qEyTMN3IQJ7apo+sw9lxaB/iKWIhbbLlF3zmAWk9XQIg==",
400 | "dependencies": [
401 | "ast-types",
402 | "recast"
403 | ]
404 | },
405 | "@types/node@22.15.15": {
406 | "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
407 | "dependencies": [
408 | "undici-types"
409 | ]
410 | },
411 | "ansi-styles@4.3.0": {
412 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
413 | "dependencies": [
414 | "color-convert"
415 | ]
416 | },
417 | "ast-types@0.16.1": {
418 | "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
419 | "dependencies": [
420 | "tslib"
421 | ]
422 | },
423 | "babel-core@7.0.0-bridge.0_@babel+core@7.27.4": {
424 | "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==",
425 | "dependencies": [
426 | "@babel/core"
427 | ]
428 | },
429 | "balanced-match@1.0.2": {
430 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
431 | },
432 | "brace-expansion@1.1.12": {
433 | "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
434 | "dependencies": [
435 | "balanced-match",
436 | "concat-map"
437 | ]
438 | },
439 | "braces@3.0.3": {
440 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
441 | "dependencies": [
442 | "fill-range"
443 | ]
444 | },
445 | "browserslist@4.25.0": {
446 | "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
447 | "dependencies": [
448 | "caniuse-lite",
449 | "electron-to-chromium",
450 | "node-releases",
451 | "update-browserslist-db"
452 | ],
453 | "bin": true
454 | },
455 | "buffer-from@1.1.2": {
456 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
457 | },
458 | "caniuse-lite@1.0.30001722": {
459 | "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA=="
460 | },
461 | "chalk@4.1.2": {
462 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
463 | "dependencies": [
464 | "ansi-styles",
465 | "supports-color"
466 | ]
467 | },
468 | "clone-deep@4.0.1": {
469 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
470 | "dependencies": [
471 | "is-plain-object",
472 | "kind-of",
473 | "shallow-clone"
474 | ]
475 | },
476 | "color-convert@2.0.1": {
477 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
478 | "dependencies": [
479 | "color-name"
480 | ]
481 | },
482 | "color-name@1.1.4": {
483 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
484 | },
485 | "commondir@1.0.1": {
486 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
487 | },
488 | "concat-map@0.0.1": {
489 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
490 | },
491 | "convert-source-map@2.0.0": {
492 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
493 | },
494 | "debug@4.4.1": {
495 | "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
496 | "dependencies": [
497 | "ms"
498 | ]
499 | },
500 | "electron-to-chromium@1.5.166": {
501 | "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw=="
502 | },
503 | "escalade@3.2.0": {
504 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
505 | },
506 | "esprima@4.0.1": {
507 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
508 | "bin": true
509 | },
510 | "fill-range@7.1.1": {
511 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
512 | "dependencies": [
513 | "to-regex-range"
514 | ]
515 | },
516 | "find-cache-dir@2.1.0": {
517 | "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
518 | "dependencies": [
519 | "commondir",
520 | "make-dir",
521 | "pkg-dir"
522 | ]
523 | },
524 | "find-up@3.0.0": {
525 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
526 | "dependencies": [
527 | "locate-path"
528 | ]
529 | },
530 | "flow-parser@0.273.0": {
531 | "integrity": "sha512-KHe9AJfT0Zn0TpQ2daDFBpwaE0zqjWWiWLOANxzo/U6Xar5fRpU3Lucnk8iMVNitxo9inz2OmSnger70qHmsLw=="
532 | },
533 | "fs.realpath@1.0.0": {
534 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
535 | },
536 | "gensync@1.0.0-beta.2": {
537 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
538 | },
539 | "glob@7.2.3": {
540 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
541 | "dependencies": [
542 | "fs.realpath",
543 | "inflight",
544 | "inherits",
545 | "minimatch",
546 | "once",
547 | "path-is-absolute"
548 | ],
549 | "deprecated": true
550 | },
551 | "globals@11.12.0": {
552 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
553 | },
554 | "graceful-fs@4.2.11": {
555 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
556 | },
557 | "has-flag@4.0.0": {
558 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
559 | },
560 | "imurmurhash@0.1.4": {
561 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
562 | },
563 | "inflight@1.0.6": {
564 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
565 | "dependencies": [
566 | "once",
567 | "wrappy"
568 | ],
569 | "deprecated": true
570 | },
571 | "inherits@2.0.4": {
572 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
573 | },
574 | "is-number@7.0.0": {
575 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
576 | },
577 | "is-plain-object@2.0.4": {
578 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
579 | "dependencies": [
580 | "isobject"
581 | ]
582 | },
583 | "isobject@3.0.1": {
584 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
585 | },
586 | "js-tokens@4.0.0": {
587 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
588 | },
589 | "jscodeshift@0.15.2_@babel+core@7.27.4": {
590 | "integrity": "sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==",
591 | "dependencies": [
592 | "@babel/core",
593 | "@babel/parser",
594 | "@babel/plugin-transform-class-properties",
595 | "@babel/plugin-transform-modules-commonjs",
596 | "@babel/plugin-transform-nullish-coalescing-operator",
597 | "@babel/plugin-transform-optional-chaining",
598 | "@babel/plugin-transform-private-methods",
599 | "@babel/preset-flow",
600 | "@babel/preset-typescript",
601 | "@babel/register",
602 | "babel-core",
603 | "chalk",
604 | "flow-parser",
605 | "graceful-fs",
606 | "micromatch",
607 | "neo-async",
608 | "node-dir",
609 | "recast",
610 | "temp",
611 | "write-file-atomic"
612 | ],
613 | "bin": true
614 | },
615 | "jsesc@3.1.0": {
616 | "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
617 | "bin": true
618 | },
619 | "json5@2.2.3": {
620 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
621 | "bin": true
622 | },
623 | "kind-of@6.0.3": {
624 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
625 | },
626 | "locate-path@3.0.0": {
627 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
628 | "dependencies": [
629 | "p-locate",
630 | "path-exists"
631 | ]
632 | },
633 | "lru-cache@5.1.1": {
634 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
635 | "dependencies": [
636 | "yallist"
637 | ]
638 | },
639 | "make-dir@2.1.0": {
640 | "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
641 | "dependencies": [
642 | "pify",
643 | "semver@5.7.2"
644 | ]
645 | },
646 | "micromatch@4.0.8": {
647 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
648 | "dependencies": [
649 | "braces",
650 | "picomatch"
651 | ]
652 | },
653 | "minimatch@3.1.2": {
654 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
655 | "dependencies": [
656 | "brace-expansion"
657 | ]
658 | },
659 | "ms@2.1.3": {
660 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
661 | },
662 | "neo-async@2.6.2": {
663 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
664 | },
665 | "node-dir@0.1.17": {
666 | "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==",
667 | "dependencies": [
668 | "minimatch"
669 | ]
670 | },
671 | "node-releases@2.0.19": {
672 | "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
673 | },
674 | "once@1.4.0": {
675 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
676 | "dependencies": [
677 | "wrappy"
678 | ]
679 | },
680 | "p-limit@2.3.0": {
681 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
682 | "dependencies": [
683 | "p-try"
684 | ]
685 | },
686 | "p-locate@3.0.0": {
687 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
688 | "dependencies": [
689 | "p-limit"
690 | ]
691 | },
692 | "p-try@2.2.0": {
693 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
694 | },
695 | "path-exists@3.0.0": {
696 | "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="
697 | },
698 | "path-is-absolute@1.0.1": {
699 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
700 | },
701 | "picocolors@1.1.1": {
702 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
703 | },
704 | "picomatch@2.3.1": {
705 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
706 | },
707 | "pify@4.0.1": {
708 | "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
709 | },
710 | "pirates@4.0.7": {
711 | "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="
712 | },
713 | "pkg-dir@3.0.0": {
714 | "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
715 | "dependencies": [
716 | "find-up"
717 | ]
718 | },
719 | "recast@0.23.11": {
720 | "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
721 | "dependencies": [
722 | "ast-types",
723 | "esprima",
724 | "source-map",
725 | "tiny-invariant",
726 | "tslib"
727 | ]
728 | },
729 | "rimraf@2.6.3": {
730 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
731 | "dependencies": [
732 | "glob"
733 | ],
734 | "deprecated": true,
735 | "bin": true
736 | },
737 | "semver@5.7.2": {
738 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
739 | "bin": true
740 | },
741 | "semver@6.3.1": {
742 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
743 | "bin": true
744 | },
745 | "shallow-clone@3.0.1": {
746 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
747 | "dependencies": [
748 | "kind-of"
749 | ]
750 | },
751 | "signal-exit@3.0.7": {
752 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
753 | },
754 | "source-map-support@0.5.21": {
755 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
756 | "dependencies": [
757 | "buffer-from",
758 | "source-map"
759 | ]
760 | },
761 | "source-map@0.6.1": {
762 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
763 | },
764 | "supports-color@7.2.0": {
765 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
766 | "dependencies": [
767 | "has-flag"
768 | ]
769 | },
770 | "temp@0.8.4": {
771 | "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==",
772 | "dependencies": [
773 | "rimraf"
774 | ]
775 | },
776 | "tiny-invariant@1.3.3": {
777 | "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
778 | },
779 | "to-regex-range@5.0.1": {
780 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
781 | "dependencies": [
782 | "is-number"
783 | ]
784 | },
785 | "tslib@2.8.1": {
786 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
787 | },
788 | "undici-types@6.21.0": {
789 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
790 | },
791 | "update-browserslist-db@1.1.3_browserslist@4.25.0": {
792 | "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
793 | "dependencies": [
794 | "browserslist",
795 | "escalade",
796 | "picocolors"
797 | ],
798 | "bin": true
799 | },
800 | "wrappy@1.0.2": {
801 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
802 | },
803 | "write-file-atomic@2.4.3": {
804 | "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
805 | "dependencies": [
806 | "graceful-fs",
807 | "imurmurhash",
808 | "signal-exit"
809 | ]
810 | },
811 | "yallist@3.1.1": {
812 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
813 | }
814 | },
815 | "workspace": {
816 | "dependencies": [
817 | "jsr:@david/dax@~0.43.2",
818 | "jsr:@std/assert@^1.0.13",
819 | "jsr:@std/toml@^1.0.7"
820 | ]
821 | }
822 | }
823 |
--------------------------------------------------------------------------------
/src/rs_lib/lib.rs:
--------------------------------------------------------------------------------
1 | mod http_client;
2 |
3 | use std::borrow::Cow;
4 | use std::cell::RefCell;
5 | use std::error::Error;
6 | use std::path::Path;
7 | use std::path::PathBuf;
8 | use std::rc::Rc;
9 | use std::sync::Arc;
10 | use std::sync::OnceLock;
11 |
12 | use anyhow::Context;
13 | use anyhow::bail;
14 | use deno_ast::ModuleKind;
15 | use deno_cache_dir::file_fetcher::CacheSetting;
16 | use deno_cache_dir::file_fetcher::NullBlobStore;
17 | use deno_config::deno_json::NewestDependencyDate;
18 | use deno_error::JsErrorBox;
19 | use deno_graph::CheckJsOption;
20 | use deno_graph::GraphKind;
21 | use deno_graph::JsrMetadataStore;
22 | use deno_graph::MediaType;
23 | use deno_graph::ModuleGraph;
24 | use deno_graph::Position;
25 | use deno_graph::WalkOptions;
26 | use deno_graph::analysis::ModuleAnalyzer;
27 | use deno_graph::ast::CapturingEsParser;
28 | use deno_graph::ast::DefaultEsParser;
29 | use deno_graph::ast::EsParser;
30 | use deno_graph::ast::ParsedSourceStore;
31 | use deno_npm_installer::NpmInstallerFactory;
32 | use deno_npm_installer::NpmInstallerFactoryOptions;
33 | use deno_npm_installer::Reporter;
34 | use deno_npm_installer::lifecycle_scripts::NullLifecycleScriptsExecutor;
35 | use deno_resolver::DenoResolveError;
36 | use deno_resolver::DenoResolveErrorKind;
37 | use deno_resolver::cache::ParsedSourceCache;
38 | use deno_resolver::cjs::CjsTrackerRc;
39 | use deno_resolver::deno_json::CompilerOptionsOverrides;
40 | use deno_resolver::deno_json::CompilerOptionsResolver;
41 | use deno_resolver::deno_json::JsxImportSourceConfigResolver;
42 | use deno_resolver::emit::Emitter;
43 | use deno_resolver::factory::ConfigDiscoveryOption;
44 | use deno_resolver::factory::NpmSystemInfo;
45 | use deno_resolver::factory::ResolverFactory;
46 | use deno_resolver::factory::ResolverFactoryOptions;
47 | use deno_resolver::factory::WorkspaceFactory;
48 | use deno_resolver::factory::WorkspaceFactoryOptions;
49 | use deno_resolver::file_fetcher::DenoGraphLoader;
50 | use deno_resolver::file_fetcher::DenoGraphLoaderOptions;
51 | use deno_resolver::file_fetcher::PermissionedFileFetcher;
52 | use deno_resolver::file_fetcher::PermissionedFileFetcherOptions;
53 | use deno_resolver::graph::DefaultDenoResolverRc;
54 | use deno_resolver::graph::ResolveWithGraphError;
55 | use deno_resolver::graph::ResolveWithGraphErrorKind;
56 | use deno_resolver::graph::ResolveWithGraphOptions;
57 | use deno_resolver::loader::AllowJsonImports;
58 | use deno_resolver::loader::LoadCodeSourceErrorKind;
59 | use deno_resolver::loader::LoadedModuleOrAsset;
60 | use deno_resolver::loader::MemoryFilesRc;
61 | use deno_resolver::loader::ModuleLoader;
62 | use deno_resolver::loader::RequestedModuleType;
63 | use deno_resolver::npm::DenoInNpmPackageChecker;
64 | use deno_resolver::workspace::MappedResolutionError;
65 | use deno_semver::SmallStackString;
66 | use deno_semver::jsr::JsrPackageReqReference;
67 | use deno_semver::npm::NpmPackageReqReference;
68 | use js_sys::Object;
69 | use js_sys::Uint8Array;
70 | use log::LevelFilter;
71 | use log::Metadata;
72 | use log::Record;
73 | use node_resolver::NodeConditionOptions;
74 | use node_resolver::NodeResolverOptions;
75 | use node_resolver::PackageJsonThreadLocalCache;
76 | use node_resolver::analyze::NodeCodeTranslatorMode;
77 | use node_resolver::cache::NodeResolutionThreadLocalCache;
78 | use node_resolver::errors::NodeJsErrorCode;
79 | use node_resolver::errors::NodeJsErrorCoded;
80 | use serde::Deserialize;
81 | use serde::Serialize;
82 | use sys_traits::EnvCurrentDir;
83 | use sys_traits::impls::RealSys;
84 | use url::Url;
85 | use wasm_bindgen::JsValue;
86 | use wasm_bindgen::prelude::wasm_bindgen;
87 |
88 | use self::http_client::WasmHttpClient;
89 |
90 | #[wasm_bindgen]
91 | extern "C" {
92 | #[wasm_bindgen(thread_local_v2, js_name = process)]
93 | static PROCESS_GLOBAL: JsValue;
94 | #[wasm_bindgen(js_namespace = console)]
95 | fn error(s: &JsValue);
96 | }
97 |
98 | static GLOBAL_LOGGER: OnceLock = OnceLock::new();
99 |
100 | struct Logger {
101 | debug: bool,
102 | }
103 |
104 | impl log::Log for Logger {
105 | fn enabled(&self, metadata: &Metadata) -> bool {
106 | metadata.level() <= log::Level::Info
107 | || metadata.level() == log::Level::Debug && self.debug
108 | }
109 |
110 | fn log(&self, record: &Record) {
111 | if self.enabled(record.metadata()) {
112 | error(&JsValue::from(format!(
113 | "{} RS - {}",
114 | record.level(),
115 | record.args()
116 | )));
117 | }
118 | }
119 |
120 | fn flush(&self) {}
121 | }
122 |
123 | #[derive(Debug, Clone)]
124 | pub struct ConsoleLogReporter;
125 |
126 | impl Reporter for ConsoleLogReporter {
127 | type Guard = ();
128 | type ClearGuard = ();
129 |
130 | fn on_blocking(&self, message: &str) -> Self::Guard {
131 | error(&JsValue::from(format!(
132 | "{} {}",
133 | "Blocking", // todo: cyan
134 | message
135 | )));
136 | }
137 |
138 | fn on_initializing(&self, message: &str) -> Self::Guard {
139 | error(&JsValue::from(format!(
140 | "{} {}",
141 | "Initialize", // todo: green
142 | message
143 | )));
144 | }
145 |
146 | fn clear_guard(&self) -> Self::ClearGuard {}
147 | }
148 |
149 | #[derive(Serialize)]
150 | #[serde(rename_all = "camelCase")]
151 | pub struct LoadResponse {
152 | pub specifier: String,
153 | pub media_type: u8,
154 | pub code: Arc<[u8]>,
155 | }
156 |
157 | #[derive(Deserialize)]
158 | #[serde(rename_all = "camelCase")]
159 | pub struct DenoWorkspaceOptions {
160 | // make all these optional to support someone providing `undefined`
161 | #[serde(default)]
162 | pub no_config: Option,
163 | #[serde(default)]
164 | pub no_lock: Option,
165 | #[serde(default)]
166 | pub platform: Option,
167 | #[serde(default)]
168 | pub config_path: Option,
169 | #[serde(default)]
170 | pub node_conditions: Option>,
171 | #[serde(default)]
172 | pub newest_dependency_date: Option>,
173 | #[serde(default)]
174 | pub cached_only: Option,
175 | #[serde(default)]
176 | pub preserve_jsx: Option,
177 | #[serde(default)]
178 | pub no_transpile: Option,
179 | #[serde(default)]
180 | pub debug: Option,
181 | }
182 |
183 | #[wasm_bindgen]
184 | pub struct DenoWorkspace {
185 | http_client: WasmHttpClient,
186 | npm_installer_factory:
187 | Rc>,
188 | resolver_factory: Arc>,
189 | workspace_factory: Arc>,
190 | }
191 |
192 | impl Drop for DenoWorkspace {
193 | fn drop(&mut self) {
194 | PackageJsonThreadLocalCache::clear();
195 | }
196 | }
197 |
198 | #[wasm_bindgen]
199 | impl DenoWorkspace {
200 | #[wasm_bindgen(constructor)]
201 | pub fn new(options: JsValue) -> Result {
202 | console_error_panic_hook::set_once();
203 | let options = serde_wasm_bindgen::from_value(options).map_err(|err| {
204 | create_js_error(
205 | &anyhow::anyhow!("{}", err)
206 | .context("Failed deserializing workspace options."),
207 | )
208 | })?;
209 | Self::new_inner(options).map_err(|e| create_js_error(&e))
210 | }
211 |
212 | fn new_inner(options: DenoWorkspaceOptions) -> Result {
213 | fn resolve_is_browser_platform(
214 | options: &DenoWorkspaceOptions,
215 | ) -> Result {
216 | Ok(match options.platform.as_deref() {
217 | Some("node" | "deno") => false,
218 | Some("browser") => true,
219 | Some(value) => bail!("Unknown platform '{}'", value),
220 | None => false,
221 | })
222 | }
223 |
224 | let debug = options.debug.unwrap_or(false);
225 | let logger = GLOBAL_LOGGER.get_or_init(|| Logger { debug });
226 | _ = log::set_logger(logger).map(|()| {
227 | log::set_max_level(if debug {
228 | LevelFilter::Debug
229 | } else {
230 | LevelFilter::Info
231 | })
232 | });
233 |
234 | let sys = RealSys;
235 | let cwd = sys.env_current_dir()?;
236 | let is_browser_platform = resolve_is_browser_platform(&options)?;
237 | let config_discovery = if options.no_config.unwrap_or_default() {
238 | ConfigDiscoveryOption::Disabled
239 | } else if let Some(config_path) = options.config_path {
240 | ConfigDiscoveryOption::Path(
241 | resolve_absolute_path(config_path, &cwd)
242 | .context("Failed resolving config path.")?,
243 | )
244 | } else {
245 | ConfigDiscoveryOption::DiscoverCwd
246 | };
247 | let workspace_factory = Arc::new(WorkspaceFactory::new(
248 | sys.clone(),
249 | cwd,
250 | WorkspaceFactoryOptions {
251 | additional_config_file_names: &[],
252 | config_discovery,
253 | is_package_manager_subcommand: false,
254 | frozen_lockfile: None, // provide this via config
255 | lock_arg: None, // supports the default only
256 | lockfile_skip_write: false,
257 | maybe_custom_deno_dir_root: None,
258 | node_modules_dir: None, // provide this via config
259 | no_lock: options.no_lock.unwrap_or_default(),
260 | no_npm: false,
261 | npm_process_state: None,
262 | root_node_modules_dir_override: None,
263 | vendor: None, // provide this via the config
264 | },
265 | ));
266 | let resolver_factory = Arc::new(ResolverFactory::new(
267 | workspace_factory.clone(),
268 | ResolverFactoryOptions {
269 | allow_json_imports: AllowJsonImports::Always,
270 | compiler_options_overrides: CompilerOptionsOverrides {
271 | no_transpile: options.no_transpile.unwrap_or(false),
272 | source_map_base: Some(
273 | workspace_factory
274 | .workspace_directory()?
275 | .workspace
276 | .root_dir_url()
277 | .as_ref()
278 | .clone(),
279 | ),
280 | preserve_jsx: options.preserve_jsx.unwrap_or(false),
281 | },
282 | // todo: make this configurable
283 | is_cjs_resolution_mode:
284 | deno_resolver::cjs::IsCjsResolutionMode::ExplicitTypeCommonJs,
285 | unstable_sloppy_imports: true,
286 | npm_system_info: npm_system_info()?,
287 | node_resolver_options: NodeResolverOptions {
288 | is_browser_platform,
289 | bundle_mode: true,
290 | conditions: NodeConditionOptions {
291 | conditions: options
292 | .node_conditions
293 | .unwrap_or_default()
294 | .into_iter()
295 | .map(|c| c.into())
296 | .collect(),
297 | import_conditions_override: None,
298 | require_conditions_override: None,
299 | },
300 | typescript_version: None,
301 | },
302 | node_analysis_cache: None,
303 | node_code_translator_mode: NodeCodeTranslatorMode::Disabled,
304 | node_resolution_cache: Some(Arc::new(NodeResolutionThreadLocalCache)),
305 | package_json_cache: Some(Arc::new(PackageJsonThreadLocalCache)),
306 | package_json_dep_resolution: None,
307 | require_modules: Vec::new(),
308 | specified_import_map: None,
309 | bare_node_builtins: true,
310 | newest_dependency_date: options
311 | .newest_dependency_date
312 | .map(NewestDependencyDate::Enabled),
313 | // todo: report these
314 | on_mapped_resolution_diagnostic: None,
315 | },
316 | ));
317 | let http_client = WasmHttpClient::default();
318 | let npm_installer_factory = Rc::new(NpmInstallerFactory::new(
319 | resolver_factory.clone(),
320 | Arc::new(http_client.clone()),
321 | Arc::new(NullLifecycleScriptsExecutor),
322 | ConsoleLogReporter,
323 | None,
324 | NpmInstallerFactoryOptions {
325 | cache_setting: if options.cached_only.unwrap_or_default() {
326 | deno_npm_cache::NpmCacheSetting::Only
327 | } else {
328 | deno_npm_cache::NpmCacheSetting::Use
329 | },
330 | caching_strategy: deno_npm_installer::graph::NpmCachingStrategy::Eager,
331 | lifecycle_scripts_config: deno_npm_installer::LifecycleScriptsConfig {
332 | allowed: deno_npm_installer::PackagesAllowedScripts::None,
333 | denied: Vec::new(),
334 | initial_cwd: workspace_factory.initial_cwd().clone(),
335 | root_dir: workspace_factory
336 | .workspace_directory()?
337 | .workspace
338 | .root_dir_path(),
339 | explicit_install: false,
340 | },
341 | resolve_npm_resolution_snapshot: Box::new(|| Ok(None)),
342 | },
343 | ));
344 | Ok(Self {
345 | http_client,
346 | npm_installer_factory,
347 | resolver_factory,
348 | workspace_factory,
349 | })
350 | }
351 |
352 | pub async fn create_loader(&self) -> Result {
353 | self
354 | .create_loader_inner()
355 | .await
356 | .map_err(|e| create_js_error(&e))
357 | }
358 |
359 | async fn create_loader_inner(&self) -> Result {
360 | self
361 | .npm_installer_factory
362 | .initialize_npm_resolution_if_managed()
363 | .await?;
364 | let file_fetcher = Arc::new(PermissionedFileFetcher::new(
365 | NullBlobStore,
366 | Arc::new(self.workspace_factory.http_cache()?.clone()),
367 | self.http_client.clone(),
368 | MemoryFilesRc::default(),
369 | self.workspace_factory.sys().clone(),
370 | PermissionedFileFetcherOptions {
371 | allow_remote: true,
372 | cache_setting: CacheSetting::Use,
373 | },
374 | ));
375 | Ok(DenoLoader {
376 | cjs_tracker: self.resolver_factory.cjs_tracker()?.clone(),
377 | compiler_options_resolver: self
378 | .resolver_factory
379 | .compiler_options_resolver()?
380 | .clone(),
381 | file_fetcher,
382 | emitter: self.resolver_factory.emitter()?.clone(),
383 | resolver: self.resolver_factory.deno_resolver().await?.clone(),
384 | workspace_factory: self.workspace_factory.clone(),
385 | resolver_factory: self.resolver_factory.clone(),
386 | npm_installer_factory: self.npm_installer_factory.clone(),
387 | parsed_source_cache: self.resolver_factory.parsed_source_cache().clone(),
388 | module_loader: self.resolver_factory.module_loader()?.clone(),
389 | task_queue: Default::default(),
390 | graph: ModuleGraphCell::new(deno_graph::ModuleGraph::new(
391 | deno_graph::GraphKind::CodeOnly,
392 | )),
393 | jsr_metadata_store: Rc::new(JsrMetadataStore::default()),
394 | })
395 | }
396 | }
397 |
398 | #[wasm_bindgen]
399 | pub struct DenoLoader {
400 | cjs_tracker: CjsTrackerRc,
401 | compiler_options_resolver: Arc,
402 | resolver: DefaultDenoResolverRc,
403 | file_fetcher:
404 | Arc>,
405 | emitter: Arc>,
406 | npm_installer_factory:
407 | Rc>,
408 | parsed_source_cache: Arc,
409 | module_loader: Arc>,
410 | resolver_factory: Arc>,
411 | workspace_factory: Arc>,
412 | graph: ModuleGraphCell,
413 | task_queue: Rc,
414 | jsr_metadata_store: Rc,
415 | }
416 |
417 | impl Drop for DenoLoader {
418 | fn drop(&mut self) {
419 | NodeResolutionThreadLocalCache::clear();
420 | }
421 | }
422 |
423 | #[wasm_bindgen]
424 | impl DenoLoader {
425 | pub fn get_graph(&self) -> JsValue {
426 | let serializer =
427 | serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true);
428 | self.graph.get().serialize(&serializer).unwrap()
429 | }
430 |
431 | pub async fn add_entrypoints(
432 | &self,
433 | entrypoints: Vec,
434 | ) -> Result, JsValue> {
435 | self
436 | .add_entrypoints_internal(entrypoints)
437 | .await
438 | .map_err(|e| create_js_error(&e))
439 | }
440 |
441 | async fn add_entrypoints_internal(
442 | &self,
443 | entrypoints: Vec,
444 | ) -> Result, anyhow::Error> {
445 | let urls = entrypoints
446 | .into_iter()
447 | .map(|e| {
448 | self.resolve_entrypoint(
449 | Cow::Owned(e),
450 | node_resolver::ResolutionMode::Import,
451 | )
452 | })
453 | .collect::, _>>()?;
454 | self.add_entrypoint_urls(urls.clone()).await?;
455 | let errors = self
456 | .graph
457 | .get()
458 | .walk(
459 | urls.iter(),
460 | WalkOptions {
461 | check_js: CheckJsOption::True,
462 | kind: GraphKind::CodeOnly,
463 | follow_dynamic: false,
464 | prefer_fast_check_graph: false,
465 | },
466 | )
467 | .errors()
468 | .map(|e| e.to_string_with_range())
469 | .collect();
470 | Ok(errors)
471 | }
472 |
473 | async fn add_entrypoint_urls(
474 | &self,
475 | entrypoints: Vec,
476 | ) -> Result<(), anyhow::Error> {
477 | // only allow one async task to modify the graph at a time
478 | let task_queue = self.task_queue.clone();
479 | task_queue
480 | .run(async {
481 | let npm_package_info_provider = self
482 | .npm_installer_factory
483 | .lockfile_npm_package_info_provider()?;
484 | let lockfile = self
485 | .workspace_factory
486 | .maybe_lockfile(npm_package_info_provider)
487 | .await?;
488 | let jsx_config =
489 | JsxImportSourceConfigResolver::from_compiler_options_resolver(
490 | &self.compiler_options_resolver,
491 | )?;
492 |
493 | let graph_resolver = self
494 | .resolver
495 | .as_graph_resolver(&self.cjs_tracker, &jsx_config);
496 | let loader = DenoGraphLoader::new(
497 | self.file_fetcher.clone(),
498 | self.workspace_factory.global_http_cache()?.clone(),
499 | self.resolver_factory.in_npm_package_checker()?.clone(),
500 | self.workspace_factory.sys().clone(),
501 | DenoGraphLoaderOptions {
502 | file_header_overrides: Default::default(),
503 | permissions: None,
504 | reporter: None,
505 | },
506 | );
507 |
508 | let mut locker = lockfile.as_ref().map(|l| l.as_deno_graph_locker());
509 | let npm_resolver =
510 | self.npm_installer_factory.npm_deno_graph_resolver().await?;
511 | let module_analyzer = CapturingModuleAnalyzerRef {
512 | store: self.parsed_source_cache.as_ref(),
513 | parser: &DefaultEsParser,
514 | };
515 | let mut graph = self.graph.deep_clone();
516 | if graph.roots.is_empty()
517 | && let Some(lockfile) = lockfile
518 | {
519 | lockfile.fill_graph(&mut graph);
520 | }
521 | let jsr_version_resolver =
522 | self.resolver_factory.jsr_version_resolver()?;
523 | graph
524 | .build(
525 | entrypoints,
526 | Vec::new(),
527 | &loader,
528 | deno_graph::BuildOptions {
529 | is_dynamic: false,
530 | skip_dynamic_deps: false,
531 | module_info_cacher: Default::default(),
532 | executor: Default::default(),
533 | locker: locker.as_mut().map(|l| l as _),
534 | file_system: self.workspace_factory.sys(),
535 | jsr_url_provider: Default::default(),
536 | jsr_version_resolver: Cow::Borrowed(
537 | jsr_version_resolver.as_ref(),
538 | ),
539 | passthrough_jsr_specifiers: false,
540 | module_analyzer: &module_analyzer,
541 | npm_resolver: Some(npm_resolver.as_ref()),
542 | reporter: None,
543 | resolver: Some(&graph_resolver),
544 | unstable_bytes_imports: true,
545 | unstable_text_imports: true,
546 | jsr_metadata_store: Some(self.jsr_metadata_store.clone()),
547 | },
548 | )
549 | .await;
550 | self.graph.set(Rc::new(graph));
551 | Ok(())
552 | })
553 | .await
554 | }
555 |
556 | pub fn resolve_sync(
557 | &self,
558 | specifier: String,
559 | importer: Option,
560 | resolution_mode: u8,
561 | ) -> Result {
562 | let importer = self
563 | .resolve_provided_referrer(importer)
564 | .map_err(|e| create_js_error(&e))?;
565 | self
566 | .resolve_sync_inner(
567 | &specifier,
568 | importer.as_ref(),
569 | parse_resolution_mode(resolution_mode),
570 | )
571 | .map_err(|err| {
572 | self.create_resolve_js_error(&err, &specifier, importer.as_ref())
573 | })
574 | }
575 |
576 | fn resolve_sync_inner(
577 | &self,
578 | specifier: &str,
579 | importer: Option<&Url>,
580 | resolution_mode: node_resolver::ResolutionMode,
581 | ) -> Result {
582 | let (specifier, referrer) = self.resolve_specifier_and_referrer(
583 | specifier,
584 | importer,
585 | resolution_mode,
586 | )?;
587 | let resolved = self.resolver.resolve_with_graph(
588 | &self.graph.get(),
589 | &specifier,
590 | &referrer,
591 | deno_graph::Position::zeroed(),
592 | ResolveWithGraphOptions {
593 | mode: resolution_mode,
594 | kind: node_resolver::NodeResolutionKind::Execution,
595 | maintain_npm_specifiers: false,
596 | },
597 | )?;
598 | Ok(resolved.into())
599 | }
600 |
601 | pub async fn resolve(
602 | &self,
603 | specifier: String,
604 | importer: Option,
605 | resolution_mode: u8,
606 | ) -> Result {
607 | let importer = self
608 | .resolve_provided_referrer(importer)
609 | .map_err(|e| create_js_error(&e))?;
610 | self
611 | .resolve_inner(
612 | &specifier,
613 | importer.as_ref(),
614 | parse_resolution_mode(resolution_mode),
615 | )
616 | .await
617 | .map_err(|err| {
618 | self.create_resolve_js_error(&err, &specifier, importer.as_ref())
619 | })
620 | }
621 |
622 | async fn resolve_inner(
623 | &self,
624 | specifier: &str,
625 | importer: Option<&Url>,
626 | resolution_mode: node_resolver::ResolutionMode,
627 | ) -> Result {
628 | let (specifier, referrer) = self.resolve_specifier_and_referrer(
629 | specifier,
630 | importer,
631 | resolution_mode,
632 | )?;
633 | let resolved = self.resolver.resolve_with_graph(
634 | &self.graph.get(),
635 | &specifier,
636 | &referrer,
637 | deno_graph::Position::zeroed(),
638 | ResolveWithGraphOptions {
639 | mode: resolution_mode,
640 | kind: node_resolver::NodeResolutionKind::Execution,
641 | maintain_npm_specifiers: true,
642 | },
643 | )?;
644 | if NpmPackageReqReference::from_specifier(&resolved).is_ok()
645 | || JsrPackageReqReference::from_specifier(&resolved).is_ok()
646 | {
647 | self.add_entrypoint_urls(vec![resolved.clone()]).await?;
648 | self.resolve_sync_inner(&specifier, importer, resolution_mode)
649 | } else {
650 | Ok(resolved.into())
651 | }
652 | }
653 |
654 | fn resolve_specifier_and_referrer<'a>(
655 | &self,
656 | specifier: &'a str,
657 | referrer: Option<&'a Url>,
658 | resolution_mode: node_resolver::ResolutionMode,
659 | ) -> Result<(Cow<'a, str>, Cow<'a, Url>), anyhow::Error> {
660 | Ok(match referrer {
661 | Some(referrer) => (Cow::Borrowed(specifier), Cow::Borrowed(referrer)),
662 | None => {
663 | let entrypoint = Cow::Owned(
664 | self
665 | .resolve_entrypoint(Cow::Borrowed(specifier), resolution_mode)?
666 | .into(),
667 | );
668 | (
669 | entrypoint,
670 | Cow::Owned(deno_path_util::url_from_directory_path(
671 | self.workspace_factory.initial_cwd(),
672 | )?),
673 | )
674 | }
675 | })
676 | }
677 |
678 | fn resolve_provided_referrer(
679 | &self,
680 | importer: Option,
681 | ) -> Result