packPackage

tcmts.cli.releases.packPackage · #iesxn2eh · term · runs on the server

uses node:fs

Pack a package: a release of it (version), the branch as it was at a history entry (at, its hash: what tcm builds make packs), or the branch as it is. What it exports and its commands come from its manifest; what it uses of the codebase's other packages comes from their releases, as dependencies (but a tcm library's, npm: false, which it carries). A tcm library isn't packed.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { absolute, exportsOf, findPackage, owns, packagesIn } from "#codebase/tcmts/cli/manifests";
import { pack, packApp, packLibraries } from "#codebase/tcmts/cli/pack";
import type { Sibling } from "#codebase/tcmts/cli/pack";
import { changelog, packageState, ReleaseError, rowsAt } from "#codebase/tcmts/cli/releases";
import fs from "node:fs";
import path from "node:path";

export function packPackage(
  cb: Codebase,
  name: string | undefined,
  version: string | undefined,
  out: string | undefined,
  at?: string,
) {
  // the branch exactly where a release of it is: that release
  const head = at ?? cb.store.head();
  const atRelease =
    version === undefined
      ? cb.store.releases(name).find((r) => r.causal === head && (!name || r.package === name))
      : undefined;
  const release =
    atRelease ?? (version === undefined ? undefined : cb.store.release(name ?? "", version));
  if (version !== undefined && !release)
    throw new ReleaseError(`no release ${name}@${version} (see \`tcm releases\`)`);
  const rows = release ? rowsAt(cb, release.causal) : at ? rowsAt(cb, at) : cb.store.names();
  const all = packagesIn(cb, rows);
  const pkg = findPackage(all, name);
  if (pkg.manifest.npm === false)
    throw new ReleaseError(
      `${pkg.manifest.name} is a tcm library (npm: false): it's shared by \`tcm lib add\`, not packed for npm`,
    );
  const exported = exportsOf(pkg, all, rows);
  // a definition the package has a name for is its own, even if another package has the same one (same hash)
  const mine = new Set(rows.filter((r) => owns(pkg, all, r.path)).map((r) => r.hash));
  // (a tcm library, npm: false, isn't one: what this uses of it is carried, as its own code is)
  const siblings: Sibling[] = all
    .filter((o) => o !== pkg && o.manifest.npm !== false)
    .map((o) => ({
      name: o.name,
      owned: new Map(
        rows.filter((r) => owns(o, all, r.path) && !mine.has(r.hash)).map((r) => [r.hash, r.path]),
      ),
      exports: ((ex) => new Set(rows.filter((r) => ex.has(r.path)).map((r) => r.hash)))(
        exportsOf(o, all, rows),
      ),
      releases: cb.store.releases(o.name).flatMap((r) => {
        const st = packageState(cb, rowsAt(cb, r.causal), o.name);
        if (!st) return [];
        const byHash = new Map<string, { subpath: string; name: string }>();
        for (const row of st.rows) {
          const sub = st.exported.get(row.path);
          if (sub !== undefined && !byHash.has(row.hash))
            byHash.set(row.hash, {
              subpath: sub,
              name: row.path.slice(row.path.lastIndexOf(".") + 1),
            });
        }
        return [{ version: r.version, exported: byHash }];
      }),
    }));
  const hasReleases = cb.store.releases(pkg.name).length > 0;
  // the fields its package.json starts from, from a JSON file of the codebase
  const jsonAt = pkg.manifest.packageJson && absolute(pkg.namespace, pkg.manifest.packageJson);
  const json = jsonAt
    ? cb.store
        .hashesAt(jsonAt)
        .map((h) => cb.store.getDef(h)!)
        .find((d) => d.kind === "file")
    : undefined;
  if (jsonAt && !json)
    throw new Error(
      `${pkg.manifestPath}: packageJson names ${jsonAt}, which isn't a file of the codebase`,
    );
  const packageJson = json && (JSON.parse(json.sourceText) as Record<string, unknown>);
  const r = pack(cb, {
    name: pkg.name,
    version: release?.version ?? "0.0.0",
    description: pkg.manifest.description,
    entries: [...exported].map(([target, sub]) => ({
      subpath: sub === "." ? "." : sub.replace(/^\.\//, ""),
      target,
    })),
    bins: Object.entries(pkg.manifest.bin).map(([command, rel]) => ({
      command,
      target: absolute(pkg.namespace, rel),
    })),
    with: pkg.manifest.with,
    // a path, or a namespace of the codebase's files, relative to the package's
    files: pkg.manifest.files.map((f) =>
      fs.existsSync(path.resolve(cb.lay.root, f)) ? f : absolute(pkg.namespace, f),
    ),
    out: out ?? path.join(".tcm", "pack", pkg.name.replace(/^@/, "").replace("/", "__")),
    names: rows,
    changelog: release
      ? changelog(cb, pkg.name, release.version)
      : hasReleases
        ? changelog(cb, pkg.name)
        : undefined,
    siblings,
    ...(packageJson ? { packageJson } : {}),
  });
  // the apps it ships, each a codebase of its own beside the code (`apps/<name>`), which `tcm start` runs
  for (const [name, rel] of Object.entries(pkg.manifest.apps))
    packApp(cb, absolute(pkg.namespace, rel), path.join(r.dir, "apps", name));
  // and the tcm libraries it ships, a codebase of their releases (`libraries`), which `tcm lib add tcm <name>` reads
  if (pkg.manifest.libraries.length)
    packLibraries(cb, pkg.manifest.libraries, path.join(r.dir, "libraries"));
  return { ...r, pkg, version: release?.version ?? "0.0.0" };
}

uses

used by