ThomasReggi
ThomasReggi

Reputation: 59485

How do I get the path of a downloaded deno module?

Given a downloaded deno module like:

import x from "https://deno.land/x/[email protected]/calendar/denodb/fresh/island.ts"

How can I find where this specific module is located on the host machine?

āžœ  deno.land cd x
cd: no such file or directory: x
āžœ  deno.land pwd
/Users/thomasreggi/Library/Caches/deno/deps/https/deno.land

There seems to be no "x" dir here šŸ‘†

Where is the file stored locally? How can I create the local file path from url via code / api?

Upvotes: 0

Views: 1116

Answers (1)

jsejcksn
jsejcksn

Reputation: 33796

Notes:

  • The Linking to Third Party Code section in the manual will be a great entrypoint reference on this topic.

  • The below information was written when Deno's latest release was version 1.25.1.


Deno has a dependency inspector tool:

deno info [URL] will inspect an ES module and all of its dependencies.

This tool prints all of the dependencies of a module to stdout, and the output includes the module's remote origin as well as its cached path on your local storage device.

It also has a (currently unstable) argument --json which will print the dependencies in JSON format (which is easier to parse programmatically if that's your goal).

You can create a subprocess in your program that uses the above command and argument, then parse its output in order to programmatically determine a module's cached location. (Also note that the subprocess API is changing and the currently unstable APIs Deno.spawn and Deno.spawnChild will replace the current one.)

Here's some example output from a module with fewer dependencies than the one in your question:

% deno info https://deno.land/[email protected]/testing/asserts.ts
local: /Users/deno/.deno/deps/https/deno.land/ce1734220fb8c205d2de1b52a59b20401c59d0707abcc9765dbeb60c25483df9
type: TypeScript
dependencies: 3 unique (total 46.65KB)

https://deno.land/[email protected]/testing/asserts.ts (23.22KB)
ā”œā”€ā”€ https://deno.land/[email protected]/fmt/colors.ts (11.62KB)
ā”œā”€ā”¬ https://deno.land/[email protected]/testing/_diff.ts (11.11KB)
ā”‚ ā””ā”€ā”€ https://deno.land/[email protected]/fmt/colors.ts *
ā””ā”€ā”€ https://deno.land/[email protected]/testing/_format.ts (705B)

Output when using the --json argument:

% deno info --json https://deno.land/[email protected]/testing/asserts.ts
{
  "roots": [
    "https://deno.land/[email protected]/testing/asserts.ts"
  ],
  "modules": [
    {
      "kind": "esm",
      "local": "/Users/deno/.deno/deps/https/deno.land/02d8068ecd90393c6bf5c8f69b02882b789681b5c638c210545b2d71e604b585",
      "emit": null,
      "map": null,
      "size": 11904,
      "mediaType": "TypeScript",
      "specifier": "https://deno.land/[email protected]/fmt/colors.ts"
    },
    {
      "dependencies": [
        {
          "specifier": "../fmt/colors.ts",
          "code": {
            "specifier": "https://deno.land/[email protected]/fmt/colors.ts",
            "span": {
              "start": {
                "line": 11,
                "character": 7
              },
              "end": {
                "line": 11,
                "character": 25
              }
            }
          }
        }
      ],
      "kind": "esm",
      "local": "/Users/deno/.deno/deps/https/deno.land/62cb97c1d18d022406d28b201c22805c58600e9a6d837b0fc4b71621ed21e30d",
      "emit": null,
      "map": null,
      "size": 11380,
      "mediaType": "TypeScript",
      "specifier": "https://deno.land/[email protected]/testing/_diff.ts"
    },
    {
      "kind": "esm",
      "local": "/Users/deno/.deno/deps/https/deno.land/3f50b09108fe404c8274e994b417a0802863842e740c1d7ca43c119c0ee0f14b",
      "emit": null,
      "map": null,
      "size": 705,
      "mediaType": "TypeScript",
      "specifier": "https://deno.land/[email protected]/testing/_format.ts"
    },
    {
      "dependencies": [
        {
          "specifier": "../fmt/colors.ts",
          "code": {
            "specifier": "https://deno.land/[email protected]/fmt/colors.ts",
            "span": {
              "start": {
                "line": 10,
                "character": 32
              },
              "end": {
                "line": 10,
                "character": 50
              }
            }
          }
        },
        {
          "specifier": "./_diff.ts",
          "code": {
            "specifier": "https://deno.land/[email protected]/testing/_diff.ts",
            "span": {
              "start": {
                "line": 11,
                "character": 44
              },
              "end": {
                "line": 11,
                "character": 56
              }
            }
          }
        },
        {
          "specifier": "./_format.ts",
          "code": {
            "specifier": "https://deno.land/[email protected]/testing/_format.ts",
            "span": {
              "start": {
                "line": 12,
                "character": 23
              },
              "end": {
                "line": 12,
                "character": 37
              }
            }
          }
        }
      ],
      "kind": "esm",
      "local": "/Users/deno/.deno/deps/https/deno.land/ce1734220fb8c205d2de1b52a59b20401c59d0707abcc9765dbeb60c25483df9",
      "emit": null,
      "map": null,
      "size": 23776,
      "mediaType": "TypeScript",
      "specifier": "https://deno.land/[email protected]/testing/asserts.ts"
    }
  ],
  "redirects": {}
}

However, if your ultimate goal is to cache modules (that come from a remote origin) to your local storage device and import them from that location rather than use Deno's built-in cache, I recommend using the built-in tool for that: deno vendor. From its manual page:

deno vendor <specifiers>... will download all remote dependencies of the specified modules into a local vendor folder.


Update: Here's an example script demonstrating the method I described above:

so-73596066.ts:

/// <reference lib="deno.unstable" />

import { assertExists } from "https://deno.land/[email protected]/testing/asserts.ts";

/*
`deno info --json` is unstable, and I didn't find any mention of schema for its
output in the docs, but here's a (conservative) partial type for the bits
that are relevant to this example, derived from looking at just a couple
of outputs from Deno v1.25.1:
*/

type ModuleInfo =
  & Record<"kind" | "local" | "mediaType" | "specifier", string>
  & Record<"emit" | "map", string | null>
  & {
    dependencies?: unknown[];
    size: number;
  };

type DependencyInspectorResult = {
  modules: ModuleInfo[];
  roots: string[];
};

/**
 * Creates a formatted error message and allows for improved error handling by
 * discriminating error instances
 */
class ProcessError extends Error {
  override name = "ProcessError";

  constructor(status: Deno.ChildStatus, stdErr?: string) {
    let msg = `The process exited with status code ${status.code}`;
    if (stdErr) msg += `. stderr:\n${stdErr}`;
    super(msg);
  }
}

/**
 * Parses output from `deno info --json`. The resulting command will look like:
 * `deno info --json [...denoInfoArgs] specifier`
 * @param specifier local/remote path/URL
 * @param denoInfoArgs optional, additional arguments to be used with `deno info --json`
 */
async function getCachedModuleInfo(
  specifier: string | URL,
  denoInfoArgs?: string[],
): Promise<ModuleInfo> {
  const decoder = new TextDecoder();
  const specifierStr = String(specifier);

  const args = ["info", "--json"];
  if (denoInfoArgs?.length) args.push(...denoInfoArgs);
  args.push(specifierStr);

  const { stderr, stdout, ...status } = await Deno.spawn("deno", { args });

  if (!status.success) {
    const stdErr = decoder.decode(stderr).trim();
    throw new ProcessError(status, stdErr);
  }

  const result = JSON.parse(
    decoder.decode(stdout),
  ) as DependencyInspectorResult;

  const moduleInfo = result.modules.find((info) =>
    info.specifier === specifierStr
  );

  assertExists(moduleInfo, "Module not found in output");
  return moduleInfo;
}

/**
 * `console.log` truncates long strings and deep object properties by default.
 * This overrides that behavior.
 */
function print(value: unknown): void {
  const inspectOpts: Deno.InspectOptions = {
    colors: true,
    depth: Infinity,
    strAbbreviateSize: Infinity,
  };

  const formattedOutput = Deno.inspect(value, inspectOpts);
  console.log(formattedOutput);
}

async function main() {
  const moduleInfo = await getCachedModuleInfo(
    "https://deno.land/[email protected]/testing/asserts.ts",
  );

  const { local, specifier } = moduleInfo;
  print({ specifier, local });
}

if (import.meta.main) main();

% deno --version
deno 1.25.1 (release, x86_64-apple-darwin)
v8 10.6.194.5
typescript 4.7.4

% deno run --allow-run=deno --unstable so-73596066.ts
{
  specifier: "https://deno.land/[email protected]/testing/asserts.ts",
  local: "/Users/deno/.deno/deps/https/deno.land/ce1734220fb8c205d2de1b52a59b20401c59d0707abcc9765dbeb60c25483df9"
}

Upvotes: 2

Related Questions