5.2 KB175 lines
Blame
1/**
2 * Verify the as-built tarballs can be imported into a fresh, out-of-tree TypeScript project.
3 */
4
5/* eslint-disable no-console */
6import { mkdtemp, mkdir, writeFile, readFile, readdir, copyFile, rm } from 'node:fs/promises';
7import { execFileSync } from 'child_process';
8import * as path from 'path';
9import { fileURLToPath } from 'url';
10import { tmpdir } from 'node:os';
11
12const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
13const __dirname = path.dirname(__filename); // get the name of the directory
14
15/**
16 * Packages to build and import
17 */
18const PACKAGES = {
19 mermaid: 'mermaid',
20 '@mermaid-js/layout-elk': 'mermaid-layout-elk',
21 // TODO: these don't import cleanly yet due to exotic tsconfig.json requirements
22 // '@mermaid-js/mermaid-zenuml': 'mermaid-zenuml',
23 // '@mermaid-js/parser': 'parser',
24};
25
26/**
27 * Files to create in the temporary package.
28 */
29const SRC = {
30 // a minimal description of a buildable package
31 'package.json': (tarballs: Record<string, string>) =>
32 JSON.stringify(
33 {
34 dependencies: tarballs,
35 scripts: { build: 'tsc -b --verbose' },
36 devDependencies: {
37 // these are somewhat-unexpectedly required, and a downstream would need
38 // to match the real `package.json` values
39 'type-fest': '*',
40 '@types/d3': '^7.4.3',
41 typescript: '*',
42 },
43 },
44 null,
45 2
46 ),
47 // a fairly strict TypeScript configuration
48 'tsconfig.json': () =>
49 JSON.stringify(
50 {
51 compilerOptions: {
52 allowSyntheticDefaultImports: true,
53 composite: true,
54 declaration: true,
55 esModuleInterop: true,
56 incremental: true,
57 lib: ['dom', 'es2020'],
58 module: 'esnext',
59 moduleResolution: 'node',
60 noEmitOnError: true,
61 noImplicitAny: true,
62 noUnusedLocals: true,
63 sourceMap: true,
64 target: 'es2020',
65 rootDir: './src',
66 outDir: './lib',
67 strict: true,
68 tsBuildInfoFile: 'lib/.tsbuildinfo',
69 },
70 },
71 null,
72 2
73 ),
74 // the simplest possible script: will everything even import?
75 'src/index.ts': (tarballs) => {
76 const imports: string[] = [];
77 const outputs: string[] = [];
78 let i = 0;
79 for (const pkg of Object.keys(tarballs)) {
80 imports.push(`import * as pkg_${i} from '${pkg}';`);
81 outputs.push(`console.log(pkg_${i});`);
82 i++;
83 }
84 return [...imports, ...outputs].join('\n');
85 },
86};
87
88/**
89 * Commands to run after source files are created.
90 *
91 * `npm` is used to detect any unwanted `pnpm`-specific runtime "features".
92 */
93const COMMANDS = [
94 ['npm', 'install'],
95 ['npm', 'run', 'build'],
96];
97
98/**
99 * Built files to expect after commands are executed.
100 */
101const LIB = ['lib/index.js', 'lib/index.js.map', 'lib/index.d.ts', 'lib/.tsbuildinfo'];
102
103/**
104 * Run a small out-of-tree build.
105 */
106async function main() {
107 console.warn('Checking out-of-tree TypeScript build using', Object.keys(PACKAGES).join('\n'));
108 const cwd = await mkdtemp(path.join(tmpdir(), 'mermaid-tsc-check-'));
109 console.warn('... creating temporary folder', cwd);
110 const tarballs = await buildTarballs(cwd);
111
112 for (const [filename, generate] of Object.entries(SRC)) {
113 const dest = path.join(cwd, filename);
114 await mkdir(path.dirname(dest), { recursive: true });
115 console.warn('... creating', dest);
116 const text = generate(tarballs);
117 await writeFile(dest, text);
118 console.info(text);
119 }
120
121 for (const argv of COMMANDS) {
122 console.warn('... in', cwd);
123 console.warn('>>>', ...argv);
124 execFileSync(argv[0], argv.slice(1), { cwd });
125 }
126
127 for (const lib of LIB) {
128 const checkLib = path.join(cwd, lib);
129 console.warn('... checking built file', checkLib);
130 await readFile(checkLib, 'utf-8');
131 }
132
133 console.warn('... deleting', cwd);
134 await rm(cwd, { recursive: true, force: true });
135 console.warn('... tsc-check OK for\n', Object.keys(PACKAGES).join('\n'));
136}
137
138/** Build all the tarballs. */
139async function buildTarballs(tmp: string): Promise<Record<string, string>> {
140 const dist = path.join(tmp, 'dist');
141 await mkdir(dist);
142 const promises: Promise<void>[] = [];
143 const tarballs: Record<string, string> = {};
144 for (const [pkg, srcPath] of Object.entries(PACKAGES)) {
145 promises.push(buildOneTarball(pkg, srcPath, dist, tarballs));
146 }
147 await Promise.all(promises);
148 return tarballs;
149}
150
151/** Build a single tarball. */
152async function buildOneTarball(
153 pkg: string,
154 srcPath: string,
155 dist: string,
156 tarballs: Record<string, string>
157): Promise<void> {
158 const cwd = await mkdtemp(path.join(dist, 'pack-'));
159 const pkgDir = path.join(__dirname, '../packages', srcPath);
160 const argv = ['pnpm', 'pack', '--pack-destination', cwd];
161 console.warn('>>>', ...argv);
162 execFileSync(argv[0], argv.slice(1), { cwd: pkgDir });
163 const built = await readdir(cwd);
164 const dest = path.join(dist, built[0]);
165 await copyFile(path.join(cwd, built[0]), dest);
166 await rm(cwd, { recursive: true, force: true });
167 tarballs[pkg] = dest;
168}
169
170void main().catch((err) => {
171 console.error(err);
172 console.error('!!! tsc-check FAIL: temp folder left in place. see logs above for failure notes');
173 process.exit(1);
174});
175