testPackIntoPackage
Pack definitions into a package: bins, entry points, types, docs, dependencies at their locked versions.
import { installedPackage, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import { setUp } from "#codebase/tcmts/cli/test/e2e/pack"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testPackIntoPackage = () => { const { dir, spawn, tcm } = setUp(); const out = tcm( "pack", "greet", "ui=ui", "--name", "greeter@1.2.0", "--bin", "greeter=greet.main", "--files", "README.md", ); assert.match( out, /packed greeter@1\.2\.0 into \.tcm\/pack\/greeter: \d+ module\(s\), depends on @types\/react, escape-string-regexp, react/, ); const pkgDir = path.join(dir, ".tcm", "pack", "greeter"); const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")); const locked = (name: string) => /\d+\.\d+\.\d+/.exec( tcm("packages") .split("\n") .find((l) => l.trimStart().startsWith(`${name} `))!, )![0]; assert.deepEqual(pkg.dependencies, { "@types/react": locked("@types/react"), "escape-string-regexp": locked("escape-string-regexp"), react: locked("react"), }); assert.deepEqual(pkg.bin, { greeter: "bin/greeter.js" }); assert.deepEqual(Object.keys(pkg.exports), [".", "./ui"]); assert.equal(fs.readFileSync(path.join(pkgDir, "README.md"), "utf8"), "# greeter\n"); // packages by name, not the cache's aliases; documentation in the declarations const modules = fs .readdirSync(path.join(pkgDir, "modules")) .map((f) => fs.readFileSync(path.join(pkgDir, "modules", f), "utf8")) .join("\n"); assert.match(modules, /from "escape-string-regexp"/); assert.match(modules, /from "react\/jsx-runtime"/); assert.match(modules, /import\("react"\)\.JSX\.Element/); assert.doesNotMatch(modules, /npm__|\.tsx?"/); assert.match( modules, /\/\*\*\n \* A greeting, and a pattern that finds it\.\n \*\/\ndeclare function greet/, ); // installed from its tarball, like any package const user = tempDir("pack user"); const packed = spawn("npm", ["pack", "--silent", "--pack-destination", user], pkgDir); assert.equal(packed.status, 0, packed.out); fs.writeFileSync( path.join(user, "package.json"), JSON.stringify({ name: "user", private: true, type: "module" }), ); const installed = spawn( "npm", ["install", "./greeter-1.2.0.tgz", "--no-audit", "--no-fund", "--loglevel=error"], user, ); assert.equal(installed.status, 0, installed.out); // the bin runs the definition as tcm run does; a number it returns is the exit code const greeter = path.join(user, "node_modules", ".bin", "greeter"); assert.deepEqual(spawn(greeter, ["a.b"], user), { status: 0, out: "hello, a.b hello, a\\.b\n" }); assert.equal(spawn(greeter, [], user).status, 2); // the definitions by name, typed fs.writeFileSync( path.join(user, "use.ts"), `import { greet, finder, type Greeting } from "greeter";\nimport { Badge } from "greeter/ui";\nconst g: Greeting = greet("x");\nconsole.log(finder(g).test("hello, x"), typeof Badge);\n`, ); assert.deepEqual(spawn(process.execPath, ["use.ts"], user), { status: 0, out: "true function\n", }); fs.writeFileSync(path.join(user, "bad.ts"), `import { greet } from "greeter";\ngreet(1);\n`); // checked the way a user's project would check it: tsc const compilerOptions = { module: "nodenext", strict: true, noEmit: true, allowImportingTsExtensions: true, skipLibCheck: true, types: [], }; fs.writeFileSync( path.join(user, "tsconfig.json"), JSON.stringify({ compilerOptions, files: ["use.ts", "bad.ts"] }), ); const tsc = path.join(installedPackage("typescript"), "bin", "tsc"); const errors = spawn(process.execPath, [tsc, "-p", ".", "--pretty", "false"], user) .out.trim() .split("\n"); assert.deepEqual(errors, [ "bad.ts(2,7): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.", ]); }