From 3391f3a5dd03ed3d157bfd17033238760c750bd3 Mon Sep 17 00:00:00 2001 From: Gordon Leigh Date: Mon, 4 Mar 2024 11:48:51 +0100 Subject: [PATCH] docs: add comments to tsbuild.mjs --- tsbuild.mjs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tsbuild.mjs b/tsbuild.mjs index 6ec0d0c..77cf814 100644 --- a/tsbuild.mjs +++ b/tsbuild.mjs @@ -1,3 +1,15 @@ +/** + * This script loads the packages in the npm workspace by inspecting the + * "workspaces" field in the root package.json. It finds cross-references + * between each of the workspace package.json, and then updates each package's + * tsconfig.json with a project reference for each cross reference. Finally, all + * discovered TypeScript projects are added to the root tsconfig.json. + * + * The tsconfig project references are used by the tsc compiler to build the + * projects in the correct order. + * + * @see {@link https://www.typescriptlang.org/docs/handbook/project-references.html} + */ import { spawnSync } from "child_process"; import { readFile, writeFile } from "fs/promises"; import * as glob from "glob"; @@ -9,14 +21,17 @@ const WORKSPACE_ROOT = dirname(fileURLToPath(import.meta.url)); const PACKAGE_JSON = "package.json"; const TS_CONFIG = "tsconfig.json"; +// load the prettier config from the workspace root const prettierConfig = await Prettier.resolveConfig( join(WORKSPACE_ROOT, TS_CONFIG), ); +// read the root package.json const rootPkg = JSON.parse( await readFile(join(WORKSPACE_ROOT, PACKAGE_JSON), "utf-8"), ); +// we expect there to be workspaces defined in the root package. if ( !rootPkg.workspaces || !Array.isArray(rootPkg.workspaces) || @@ -26,10 +41,13 @@ if ( process.exit(1); } +// the workspaces array might contain globs, so expand them +// see https://docs.npmjs.com/cli/v7/using-npm/workspaces const packages = rootPkg.workspaces.flatMap((x) => glob.sync(x, { onlyDirectories: true }).map((p) => resolve(p)), ); +// bad workspace definition, no projects actually found if (!packages.length) { console.error( `ERROR: no packages found in workspaces [${rootPkg.workspaces}]`, @@ -39,17 +57,21 @@ if (!packages.length) { const refs = {}; +// look through each package we found for (const pkg of packages) { const packageJsonPath = join(pkg, PACKAGE_JSON); let packageJson; try { + // read/parse the package.json packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); } catch (err) { + // couldn't parse the package.json console.warn(`WARN: skipping ${relative(WORKSPACE_ROOT, pkg)}: %o`, err); continue; } + // collect all candidate dependencies const deps = []; if (packageJson.dependencies) { deps.push(...Object.keys(packageJson.dependencies)); @@ -62,14 +84,19 @@ for (const pkg of packages) { const packageTsConfigPath = join(pkg, TS_CONFIG); try { + // now load the tsconfig.json tsConfig = JSON.parse(await readFile(packageTsConfigPath)); + if (!tsConfig.compilerOptions?.composite) { + // composite option is required for build mode to work + // https://www.typescriptlang.org/tsconfig#composite console.warn( `skipping ${packageJson.name} because composite is not turned on`, ); continue; } } catch { + // couldn't parse the tsconfig, or couldn't find it continue; } @@ -81,20 +108,24 @@ for (const pkg of packages) { }; } +// for each package we found, update the tsconfig with the cross-refs for (const pkg of Object.values(refs)) { await addRefsToTsConfig( join(pkg.path, TS_CONFIG), + // filter the candidate refs to only include cross-refs pkg.deps.filter((x) => x in refs).map((x) => refs[x].path), pkg.tsConfig, ); } +// save all the packages we found to the root tsconfig const rootTsConfigPath = join(WORKSPACE_ROOT, TS_CONFIG); await addRefsToTsConfig( rootTsConfigPath, Object.values(refs).map((x) => x.path), ); +// run tsc and forward any arguments from this script const result = spawnSync( join(WORKSPACE_ROOT, "node_modules/.bin/tsc"), ["-b", rootTsConfigPath, ...process.argv.slice(2)], @@ -112,11 +143,13 @@ async function addRefsToTsConfig(configPath, references, contents) { try { contents = JSON.parse(await readFile(configPath, "utf-8")); } catch (err) { + // file wasn't found, nothing to add then if (err?.code !== "ENOENT") { throw err; } } } + // save the project references for each cross-ref const output = JSON.stringify( { ...contents, @@ -127,6 +160,7 @@ async function addRefsToTsConfig(configPath, references, contents) { null, 2, ); + // format the output with prettier to avoid lint problems await writeFile( configPath, await Prettier.format(output, {