packPackage
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
- tcmts.cli.codebase.Codebase
- tcmts.cli.manifests.absolute
- tcmts.cli.manifests.exportsOf
- tcmts.cli.manifests.findPackage
- tcmts.cli.manifests.owns
- tcmts.cli.manifests.packagesIn
- tcmts.cli.pack.Sibling
- tcmts.cli.pack.pack
- tcmts.cli.pack.packApp
- tcmts.cli.pack.packLibraries
- tcmts.cli.releases.ReleaseError
- tcmts.cli.releases.changelog
- tcmts.cli.releases.packageState
- tcmts.cli.releases.rowsAt