ensureExternals

tcmts.loader.packages.ensureExternals · #kwssshhy · term · runs on the server

uses node:fs

Install what exts import: each package at its locked version (picking one for ranges not locked yet), under its range's alias, with DefinitelyTyped types (@types/react) beside packages that ship none.

import { compareVersions, compatRange, satisfies as satisfies_ } from "#codebase/tcmts/core/semver";
import { HOST_MODULES } from "#codebase/tcmts/core/types";
import type { ExtRef } from "#codebase/tcmts/core/types";
import type { Layout } from "#codebase/tcmts/loader";
import {
  candidates, checkIntegrity, hasTypes, install, installedVersion, linkPeers, lockOf, newestVersion, npmAlias,
  packageManager, parseModule, pickVersions, shippedPackage, shippedPins, viewed, writeIfChanged,
} from "#codebase/tcmts/loader/packages";
import type { CachePackageJson } from "#codebase/tcmts/loader/packages";
import fs from "node:fs";
import path from "node:path";

export function ensureExternals(lay: Layout, exts: Iterable<ExtRef>): void {
  const lock = lockOf(lay);
  const wanted = new Map<string, { name: string; range: string }>();
  for (const e of exts) {
    // a module where the code runs provides (`vscode`): only its types, installed under their own name, where
    // TypeScript looks for them; at the newest range the codebase has locked them at, else the newest release
    const types = HOST_MODULES.get(e.module);
    if (types) {
      if (wanted.has(types)) continue;
      const locked = lock
        .all()
        .filter((l) => l.name === types)
        .sort((x, y) => compareVersions(y.version, x.version))[0];
      const latest = locked ? undefined : newestVersion(types);
      const range = locked?.range ?? (latest && compatRange(latest));
      if (range) wanted.set(types, { name: types, range });
      continue;
    }
    const module = e.module;
    if (!module.startsWith("npm:")) continue;
    const { name, version } = parseModule(module);
    const range = compatRange(version);
    wanted.set(npmAlias(module), { name, range });
  }
  if (!wanted.size) return;
  // a package tcm ships (Vitest, which tests import), at a version in the range, unless the codebase chose another: tcm's
  // own, linked into the cache, not installed (one copy: the one tcm runs tests with is the one they import)
  const pins = shippedPins.get(lay.cacheDir);
  const linked = new Map<string, string>();
  // (nor one installed in this cache: a tcm run from its codebase ships what it installed there, which linked to
  // itself would leave the install, and be pruned)
  const cache = fs.existsSync(lay.cacheDir) ? fs.realpathSync(lay.cacheDir) + path.sep : undefined;
  for (const [alias, w] of wanted) {
    const own = shippedPackage(w.name);
    const pin = pins?.(w.name);
    if (
      !own ||
      (cache && own.dir.startsWith(cache)) ||
      !satisfies_(own.version, w.range) ||
      (pin && !satisfies_(own.version, pin))
    )
      continue;
    if (lock.get(w.name, w.range) !== own.version) lock.set(w.name, w.range, own.version);
    linked.set(alias, own.dir);
  }
  const unlocked = [...wanted.values()].filter((w) => !lock.get(w.name, w.range));
  if (unlocked.length) pickVersions(unlocked, lock);

  const file = path.join(lay.cacheDir, "package.json");
  const pkg = (
    fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {}
  ) as CachePackageJson;
  const deps = { ...pkg.dependencies };
  const untyped = new Set(pkg.tcmUntyped ?? []);
  const write = () =>
    writeIfChanged(
      file,
      JSON.stringify(
        {
          name: "tcm-cache",
          private: true,
          type: "module",
          dependencies: deps,
          ...(untyped.size ? { tcmUntyped: [...untyped].sort() } : {}),
        },
        null,
        2,
      ) + "\n",
    );
  const stale = (alias: string, version: string) => installedVersion(lay, alias) !== version;

  const missing: string[] = [];
  for (const [alias, w] of wanted) {
    const own = linked.get(alias);
    if (own) {
      // (npm has no `link:`: its `file:` of a directory is a link too)
      deps[alias] = `${packageManager() === "pnpm" ? "link" : "file"}:${own}`;
      const at = path.join(lay.cacheDir, "node_modules", alias);
      let there: string | undefined;
      try {
        there = fs.realpathSync(at);
      } catch {
        there = undefined;
      }
      if (there !== fs.realpathSync(own)) {
        fs.rmSync(at, { recursive: true, force: true });
        fs.mkdirSync(path.dirname(at), { recursive: true });
        fs.symlinkSync(own, at, "dir");
      }
      continue;
    }
    const version = lock.get(w.name, w.range)!;
    deps[alias] = `npm:${w.name}@${version}`;
    if (stale(alias, version)) missing.push(`${w.name}@${version}`);
  }
  // a package being installed whose release names no types of its own (as the registry said when its version was
  // picked): its DefinitelyTyped ones come in the same install, and what it turns out to have decides, below
  const guessed = new Set<string>();
  const noTypes = new Set<string>();
  for (const [alias, w] of wanted) {
    const version = lock.get(w.name, w.range)!;
    if (
      linked.has(alias) ||
      deps[`@types/${alias}`] ||
      untyped.has(alias) ||
      !stale(alias, version)
    )
      continue;
    if (viewed.get(`${w.name}@${w.range}`)?.find((c) => c.version === version)?.typed !== false)
      continue;
    const typesName = `@types/${w.name.replace(/^@/, "").replace("/", "__")}`;
    const typesVersion = lock.get(typesName, w.range) ?? candidates(typesName, w.range)[0]?.version;
    if (!typesVersion) {
      noTypes.add(alias);
      continue;
    }
    // (locked once it's found to be needed, below)
    deps[`@types/${alias}`] = `npm:${typesName}@${typesVersion}`;
    if (stale(`@types/${alias}`, typesVersion)) missing.push(`${typesName}@${typesVersion}`);
    guessed.add(alias);
  }
  write();
  if (missing.length) install(lay, missing);

  // types for packages without their own, under the alias's name: where TypeScript looks for the alias's types
  const typeMissing: string[] = [];
  for (const [alias, w] of wanted) {
    const typesAlias = `@types/${alias}`;
    // (what tcm ships has its types)
    if (linked.has(alias)) continue;
    // (one with types of its own after all: the ones installed with it aren't its)
    if (guessed.has(alias) && hasTypes(lay, alias)) {
      delete deps[typesAlias];
      continue;
    }
    if (noTypes.has(alias) && !hasTypes(lay, alias)) {
      untyped.add(alias);
      continue;
    }
    if (guessed.has(alias)) {
      const typesName = `@types/${w.name.replace(/^@/, "").replace("/", "__")}`;
      if (!lock.get(typesName, w.range))
        lock.set(
          typesName,
          w.range,
          deps[typesAlias]!.slice(deps[typesAlias]!.lastIndexOf("@") + 1),
        );
      continue;
    }
    if (deps[typesAlias] || untyped.has(alias) || hasTypes(lay, alias)) {
      if (
        deps[typesAlias] &&
        stale(typesAlias, deps[typesAlias]!.slice(deps[typesAlias]!.lastIndexOf("@") + 1))
      )
        typeMissing.push(deps[typesAlias]!.slice(4));
      continue;
    }
    const typesName = `@types/${w.name.replace(/^@/, "").replace("/", "__")}`;
    let version = lock.get(typesName, w.range);
    if (!version) {
      version = candidates(typesName, w.range)[0]?.version;
      if (version) lock.set(typesName, w.range, version);
    }
    if (!version) {
      untyped.add(alias);
      continue;
    }
    deps[typesAlias] = `npm:${typesName}@${version}`;
    if (stale(typesAlias, version)) typeMissing.push(`${typesName}@${version}`);
  }
  write();
  if (typeMissing.length) install(lay, typeMissing);
  if (missing.length || typeMissing.length) {
    checkIntegrity(lay, lock);
    linkPeers(lay, lock);
  }
}

uses

used by