From f6bd3cbd8ab931d627890ed83986c2a25a85f4c8 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 5 Dec 2025 04:53:15 +0800 Subject: [PATCH] Rewrite build script (#18427) --- cspell.json | 2 +- knip.config.js | 4 +- scripts/build/build-package-json.js | 185 ---- scripts/build/build.js | 56 +- scripts/build/builders/copy-file.js | 12 + .../dependencies-license.js} | 59 +- .../javascript-module.js} | 140 +-- scripts/build/builders/package-json.js | 86 ++ .../{build-types.js => builders/types.js} | 23 +- .../{ => hacks}/build-oxc-wasm-parser.js | 10 +- .../{ => hacks}/esmify-typescript-eslint.js | 2 +- .../{ => hacks}/modify-typescript-module.js | 2 +- .../typescript-unused-specifiers.js | 1 - scripts/build/packages/config-helpers.js | 140 +++ scripts/build/packages/index.js | 9 + scripts/build/packages/plugin-hermes.js | 61 ++ scripts/build/packages/plugin-oxc.js | 72 ++ .../build/packages/prettier-package-json.js | 72 ++ .../build/{config.js => packages/prettier.js} | 863 ++++++------------ scripts/build/parse-arguments.js | 4 +- scripts/bundle-eslint-config.js | 37 +- .../generate-unused-typescript-specifiers.js | 6 +- scripts/tools/bundle-test/index.js | 115 +-- scripts/utilities/index.js | 4 + 24 files changed, 990 insertions(+), 975 deletions(-) delete mode 100644 scripts/build/build-package-json.js create mode 100644 scripts/build/builders/copy-file.js rename scripts/build/{build-dependencies-license.js => builders/dependencies-license.js} (70%) rename scripts/build/{build-javascript-module.js => builders/javascript-module.js} (64%) create mode 100644 scripts/build/builders/package-json.js rename scripts/build/{build-types.js => builders/types.js} (78%) rename scripts/build/{ => hacks}/build-oxc-wasm-parser.js (92%) rename scripts/build/{ => hacks}/esmify-typescript-eslint.js (98%) rename scripts/build/{ => hacks}/modify-typescript-module.js (99%) rename scripts/build/{ => hacks}/typescript-unused-specifiers.js (99%) create mode 100644 scripts/build/packages/config-helpers.js create mode 100644 scripts/build/packages/index.js create mode 100644 scripts/build/packages/plugin-hermes.js create mode 100644 scripts/build/packages/plugin-oxc.js create mode 100644 scripts/build/packages/prettier-package-json.js rename scripts/build/{config.js => packages/prettier.js} (55%) diff --git a/cspell.json b/cspell.json index b5cc978a9..bb540d307 100644 --- a/cspell.json +++ b/cspell.json @@ -345,6 +345,6 @@ "website/static/playground.js", "website/src/css/custom.css", "website/blog/authors.yml", - "scripts/build/typescript-unused-specifiers.js" + "scripts/build/hacks/typescript-unused-specifiers.js" ] } diff --git a/knip.config.js b/knip.config.js index 99a925395..c463d3123 100644 --- a/knip.config.js +++ b/knip.config.js @@ -18,8 +18,8 @@ const config = { ], project: ["src/**", "scripts/**"], ignore: [ - "scripts/build/config.js", - "scripts/build/build-javascript-module.js", + "scripts/build/packages/*", + "scripts/build/builders/javascript-module.js", "scripts/tools/**", "src/experimental-cli/**", "src/universal/*.browser.js", diff --git a/scripts/build/build-package-json.js b/scripts/build/build-package-json.js deleted file mode 100644 index 3283b2580..000000000 --- a/scripts/build/build-package-json.js +++ /dev/null @@ -1,185 +0,0 @@ -import path from "node:path"; -import { - PRODUCTION_MINIMAL_NODE_JS_VERSION, - PROJECT_ROOT, - readJson, - writeJson, -} from "../utilities/index.js"; - -const keysToKeep = [ - "name", - "version", - "description", - "bin", - "repository", - "funding", - "homepage", - "author", - "license", - "main", - "browser", - "unpkg", - "exports", - "engines", - "files", - "preferUnplugged", - "sideEffects", -]; - -const publishConfig = { - access: "public", - registry: "https://registry.npmjs.org/", -}; - -async function buildPrettierPackageJson({ packageConfig, file }) { - const { distDirectory, files } = packageConfig; - const packageJson = await readJson(path.join(PROJECT_ROOT, file.input)); - - const bin = files.find( - (file) => - path.join(PROJECT_ROOT, packageJson.bin) === - path.join(PROJECT_ROOT, file.input), - ).output.file; - - const overrides = { - bin: `./${bin}`, - main: "./index.cjs", - engines: { - ...packageJson.engines, - // https://github.com/prettier/prettier/pull/13118#discussion_r922708068 - // Don't delete, event it's the same in package.json - node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`, - }, - type: "commonjs", - exports: { - ".": { - types: "./index.d.ts", - require: "./index.cjs", - browser: { - import: "./standalone.mjs", - default: "./standalone.js", - }, - default: "./index.mjs", - }, - "./*": "./*", - ...Object.fromEntries( - files - .filter((file) => file.output.format === "umd") - .map((file) => { - const basename = path.basename(file.output.file, ".js"); - return [ - file.isPlugin ? `./plugins/${basename}` : `./${basename}`, - { - types: `./${file.output.file.replace(/\.js$/u, ".d.ts")}`, - // `module-sync` condition can prevent CJS plugins from working: https://github.com/prettier/prettier/issues/17139 - // Perform a test before re-adding it. - require: `./${file.output.file}`, - default: `./${file.output.file.replace(/\.js$/u, ".mjs")}`, - }, - ]; - }), - ), - // Legacy entries - // TODO: Remove bellow in v4 - "./esm/standalone.mjs": "./standalone.mjs", - ...Object.fromEntries( - files - .filter( - (file) => - file.isPlugin && - file.output.format === "umd" && - file.output.file !== "plugins/estree.js", - ) - .flatMap((file) => { - let basename = path.basename(file.output.file, ".js"); - if (basename === "acorn") { - basename = "espree"; - } - return [ - [`./parser-${basename}`, `./${file.output.file}`], - [`./parser-${basename}.js`, `./${file.output.file}`], - [ - `./esm/parser-${basename}.mjs`, - `./${file.output.file.replace(/\.js$/u, ".mjs")}`, - ], - ]; - }), - ), - }, - files: files.map(({ output: { file } }) => file).sort(), - scripts: { - prepublishOnly: - "node -e \"assert.equal(require('.').version, require('..').version)\"", - }, - }; - - await writeJson( - path.join(distDirectory, file.output.file), - Object.assign(pick(packageJson, keysToKeep), overrides), - ); -} - -async function buildPluginOxcPackageJson({ packageConfig, file }) { - const { distDirectory, files } = packageConfig; - const packageJson = await readJson(path.join(PROJECT_ROOT, file.input)); - const projectPackageJson = await readJson( - path.join(PROJECT_ROOT, "package.json"), - ); - - const overrides = { - engines: { - ...packageJson.engines, - // https://github.com/prettier/prettier/pull/13118#discussion_r922708068 - // Don't delete, event it's the same in package.json - node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`, - }, - // Use `commonjs` since we may provide browser build in future. - type: "commonjs", - files: files.map(({ output: { file } }) => file).sort(), - dependencies: { - "oxc-parser": projectPackageJson.dependencies["oxc-parser"], - }, - publishConfig, - }; - - await writeJson( - path.join(distDirectory, file.output.file), - Object.assign(pick(packageJson, keysToKeep), overrides), - ); -} - -async function buildPluginHermesPackageJson({ packageConfig, file }) { - const { distDirectory, files } = packageConfig; - const packageJson = await readJson(path.join(PROJECT_ROOT, file.input)); - - const overrides = { - engines: { - ...packageJson.engines, - // https://github.com/prettier/prettier/pull/13118#discussion_r922708068 - // Don't delete, event it's the same in package.json - node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`, - }, - // Use `commonjs` since we may provide browser build in future. - type: "commonjs", - files: files.map(({ output: { file } }) => file).sort(), - publishConfig, - }; - - await writeJson( - path.join(distDirectory, file.output.file), - Object.assign(pick(packageJson, keysToKeep), overrides), - ); -} - -function pick(object, keys) { - keys = new Set(keys); - return Object.fromEntries( - Object.entries(object).filter(([key]) => keys.has(key)), - ); -} - -export { - buildPluginHermesPackageJson, - buildPluginOxcPackageJson, - buildPrettierPackageJson, -}; diff --git a/scripts/build/build.js b/scripts/build/build.js index 584f9623e..9cb8188a4 100644 --- a/scripts/build/build.js +++ b/scripts/build/build.js @@ -3,16 +3,13 @@ import fs from "node:fs/promises"; import path from "node:path"; import readline from "node:readline"; -import createEsmUtils from "esm-utils"; import styleText from "node-style-text"; import prettyBytes from "pretty-bytes"; import prettyMilliseconds from "pretty-ms"; -import { DIST_DIR } from "../utilities/index.js"; -import packageConfigs from "./config.js"; +import { DIST_DIR, PROJECT_ROOT } from "../utilities/index.js"; +import packageConfigs from "./packages/index.js"; import parseArguments from "./parse-arguments.js"; -const { require } = createEsmUtils(import.meta); - const statusConfig = [ { color: "bgGreen", text: "DONE" }, { color: "bgRed", text: "FAIL" }, @@ -47,21 +44,14 @@ const clear = () => { async function buildFile({ packageConfig, file, cliOptions, results }) { const { distDirectory } = packageConfig; - let displayName = file.output.file; - if ( - (file.platform === "universal" && file.output.format !== "esm") || - (file.output.file.startsWith("index.") && file.output.format !== "esm") || - file.output.file.endsWith(".browser.mjs") || - file.kind === "types" - ) { - displayName = ` ${displayName}`; - } + const displayName = ` ${file.output}`; + const fileAbsolutePath = path.join(distDirectory, file.output); process.stdout.write(fitTerminal(displayName)); if ( - (cliOptions.files && !cliOptions.files.has(file.output.file)) || - (cliOptions.playground && !file.playground) + (cliOptions.playground && !file.playground) || + (cliOptions.files && !cliOptions.files.has(fileAbsolutePath)) ) { console.log(status.SKIPPED); return; @@ -76,14 +66,12 @@ async function buildFile({ packageConfig, file, cliOptions, results }) { throw error; } - result ??= {}; - - if (result.skipped) { + if (result?.skipped) { console.log(status.SKIPPED); return; } - const outputFile = cliOptions.saveAs ?? file.output.file; + const outputFile = cliOptions.saveAs ?? file.output; const sizeMessages = []; if (cliOptions.printSize) { @@ -92,9 +80,12 @@ async function buildFile({ packageConfig, file, cliOptions, results }) { } if (cliOptions.compareSize) { - // TODO: Use `import.meta.resolve` when Node.js support - const stablePrettierDirectory = path.dirname(require.resolve("prettier")); + const stablePrettierDirectory = path.join( + PROJECT_ROOT, + `node_modules/${packageConfig.packageName}/`, + ); const stableVersionFile = path.join(stablePrettierDirectory, outputFile); + let stableSize; try { ({ size: stableSize } = await fs.stat(stableVersionFile)); @@ -172,19 +163,22 @@ async function run() { } console.log( - styleText.inverse`[${index + 1}/${packagesToBuild.length}] Building package '${packageConfig.packageName}'`, + styleText.blue`[${index + 1}/${packagesToBuild.length}] Building package '${packageConfig.packageName}'`, ); const startTime = performance.now(); const results = []; - for (const file of packageConfig.files) { - const result = await buildFile({ - packageConfig, - file, - cliOptions, - results, - }); - results.push(result); + for (const module of packageConfig.modules) { + console.log(styleText.gray(module.name)); + for (const file of module.files) { + const result = await buildFile({ + packageConfig, + file, + cliOptions, + results, + }); + results.push(result); + } } console.log( `Build package '${packageConfig.packageName}' success in ${prettyMilliseconds(performance.now() - startTime)}`, diff --git a/scripts/build/builders/copy-file.js b/scripts/build/builders/copy-file.js new file mode 100644 index 000000000..850a29abc --- /dev/null +++ b/scripts/build/builders/copy-file.js @@ -0,0 +1,12 @@ +import path from "node:path"; +import { copyFile } from "../../utilities/index.js"; + +const copyFileBuilder = ({ packageConfig, file }) => + copyFile( + path.isAbsolute(file.input) + ? file.input + : path.join(packageConfig.sourceDirectory, file.input), + path.join(packageConfig.distDirectory, file.output), + ); + +export { copyFileBuilder }; diff --git a/scripts/build/build-dependencies-license.js b/scripts/build/builders/dependencies-license.js similarity index 70% rename from scripts/build/build-dependencies-license.js rename to scripts/build/builders/dependencies-license.js index c9d1b4b80..b73b3028b 100644 --- a/scripts/build/build-dependencies-license.js +++ b/scripts/build/builders/dependencies-license.js @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { outdent } from "outdent"; import rollupPluginLicense from "rollup-plugin-license"; -import { PROJECT_ROOT } from "../utilities/index.js"; +import { PROJECT_ROOT } from "../../utilities/index.js"; const separator = `\n${"-".repeat(40)}\n\n`; @@ -20,7 +20,7 @@ function getDependencies(results) { modules: Object.fromEntries( results .flatMap((result) => - Object.keys(result.esbuildResult?.metafile.inputs ?? {}), + Object.keys(result?.esbuildResult?.metafile.inputs ?? {}), ) .map((file) => [file, { renderedLength: 1 }]), ), @@ -42,7 +42,7 @@ function getDependencies(results) { return dependencies; } -function getLicenseText(packageConfig, dependencies) { +function getLicenseText(packageConfig, dependencies, packageDisplayName) { dependencies = dependencies.filter( (dependency, index) => // Exclude self @@ -72,7 +72,7 @@ function getLicenseText(packageConfig, dependencies) { const head = outdent` # Licenses of bundled dependencies - The published ${packageConfig.packageDisplayName ?? packageConfig.packageName} artifact additionally contains code with the following licenses: + The published ${packageDisplayName ?? packageConfig.packageName} artifact additionally contains code with the following licenses: ${new Intl.ListFormat("en-US", { type: "conjunction" }).format(licenses)}. `; @@ -120,35 +120,40 @@ function getLicenseText(packageConfig, dependencies) { return [head, content].join("\n\n"); } -async function buildDependenciesLicense({ - packageConfig, - file, - results, - cliOptions, -}) { - const { distDirectory, files } = packageConfig; +function createDependenciesLicenseBuilder({ packageDisplayName }) { + return async function buildDependenciesLicense({ + packageConfig, + file, + results, + cliOptions, + }) { + const files = packageConfig.modules.flatMap((module) => module.files); + const fileName = file.output; - const fileName = file.output.file; + if (files.at(-1) !== file) { + throw new Error(`${fileName} should be last file to build.`); + } - if (files.at(-1) !== file) { - throw new Error(`${fileName} should be last file to build.`); - } + const shouldBuildLicense = !cliOptions.files; - const shouldBuildLicense = !cliOptions.files; + if (!shouldBuildLicense) { + return { skipped: true }; + } - if (!shouldBuildLicense) { - return { skipped: true }; - } + const dependencies = getDependencies(results); - const dependencies = getDependencies(results); + if (dependencies.length === 0) { + throw new Error("Fail to collect dependencies."); + } - if (dependencies.length === 0) { - throw new Error("Fail to collect dependencies."); - } + const text = getLicenseText( + packageConfig, + dependencies, + packageDisplayName, + ); - const text = getLicenseText(packageConfig, dependencies); - - await fs.writeFile(path.join(distDirectory, fileName), text); + await fs.writeFile(path.join(packageConfig.distDirectory, fileName), text); + }; } -export default buildDependenciesLicense; +export { createDependenciesLicenseBuilder }; diff --git a/scripts/build/build-javascript-module.js b/scripts/build/builders/javascript-module.js similarity index 64% rename from scripts/build/build-javascript-module.js rename to scripts/build/builders/javascript-module.js index f259cdbc9..27998b6a4 100644 --- a/scripts/build/build-javascript-module.js +++ b/scripts/build/builders/javascript-module.js @@ -3,30 +3,23 @@ import url from "node:url"; import browserslistToEsbuild from "browserslist-to-esbuild"; import esbuild from "esbuild"; import createEsmUtils from "esm-utils"; -import { - PRODUCTION_MINIMAL_NODE_JS_VERSION, - PROJECT_ROOT, -} from "../utilities/index.js"; -import esbuildPluginAddDefaultExport from "./esbuild-plugins/add-default-export.js"; -import esbuildPluginEvaluate from "./esbuild-plugins/evaluate.js"; -import esbuildPluginPrimitiveDefine from "./esbuild-plugins/primitive-define.js"; -import esbuildPluginReplaceModule from "./esbuild-plugins/replace-module.js"; -import esbuildPluginShimCommonjsObjects from "./esbuild-plugins/shim-commonjs-objects.js"; -import esbuildPluginStripNodeProtocol from "./esbuild-plugins/strip-node-protocol.js"; -import esbuildPluginThrowWarnings from "./esbuild-plugins/throw-warnings.js"; -import esbuildPluginUmd from "./esbuild-plugins/umd.js"; -import esbuildPluginVisualizer from "./esbuild-plugins/visualizer.js"; -import transform from "./transform/index.js"; -import { getPackageFile } from "./utilities.js"; +import projectPackageJson from "../../../package.json" with { type: "json" }; +import { PRODUCTION_MINIMAL_NODE_JS_VERSION } from "../../utilities/index.js"; +import esbuildPluginAddDefaultExport from "../esbuild-plugins/add-default-export.js"; +import esbuildPluginEvaluate from "../esbuild-plugins/evaluate.js"; +import esbuildPluginPrimitiveDefine from "../esbuild-plugins/primitive-define.js"; +import esbuildPluginReplaceModule from "../esbuild-plugins/replace-module.js"; +import esbuildPluginShimCommonjsObjects from "../esbuild-plugins/shim-commonjs-objects.js"; +import esbuildPluginStripNodeProtocol from "../esbuild-plugins/strip-node-protocol.js"; +import esbuildPluginThrowWarnings from "../esbuild-plugins/throw-warnings.js"; +import esbuildPluginUmd from "../esbuild-plugins/umd.js"; +import esbuildPluginVisualizer from "../esbuild-plugins/visualizer.js"; +import transform from "../transform/index.js"; +import { getPackageFile } from "../utilities.js"; -const { - readJsonSync, - require, - resolve: importMetaResolve, -} = createEsmUtils(import.meta); -const packageJson = readJsonSync("../../package.json"); +const { require, resolve: importMetaResolve } = createEsmUtils(import.meta); -const universalTarget = browserslistToEsbuild(packageJson.browserslist); +const universalTarget = browserslistToEsbuild(projectPackageJson.browserslist); const getRelativePath = (from, to) => { const relativePath = path.posix.relative(path.dirname(`/${from}`), `/${to}`); if (!relativePath.startsWith(".")) { @@ -36,8 +29,8 @@ const getRelativePath = (from, to) => { return relativePath; }; -function getEsbuildOptions({ packageConfig, file, cliOptions }) { - const { distDirectory, files } = packageConfig; +function getEsbuildOptions({ packageConfig, file, cliOptions, buildOptions }) { + const { sourceDirectory, distDirectory } = packageConfig; // Save dependencies to file file.dependencies = []; @@ -116,11 +109,11 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { ]; const define = { - "process.env.PRETTIER_TARGET": file.platform, + "process.env.PRETTIER_TARGET": buildOptions.platform, "process.env.NODE_ENV": "production", }; - if (file.platform === "universal") { + if (buildOptions.platform === "universal") { // We can't reference `process` in UMD bundles and this is // an undocumented "feature" replaceModule.push({ @@ -144,48 +137,48 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { define.__dirname = "/prettier-security-dirname-placeholder"; } - if (file.platform === "node") { - // External other bundled files - replaceModule.push( - ...files - .filter( - (bundle) => - bundle.input === "package.json" || - (file.input !== bundle.input && bundle.output.format === "esm"), - ) - .map((bundle) => { - let output = bundle.output.file; - if ( - file.output.file === "index.cjs" && - bundle.output.file === "doc.mjs" - ) { - output = "doc.js"; - } + // External other bundled files + if (buildOptions.platform === "node") { + const files = packageConfig.modules + .flatMap((module) => module.files) + .filter( + (bundle) => + bundle.input === "package.json" || + (file.input && + file.input !== bundle.input && + bundle.output.endsWith(".mjs")), + ); + const replacements = files.map((bundle) => { + let { output } = bundle; + if (file.output === "index.cjs" && output === "doc.mjs") { + output = "doc.js"; + } - return { - module: path.join(PROJECT_ROOT, bundle.input), - external: getRelativePath(file.output.file, output), - }; - }), - ); + return { + module: path.join(sourceDirectory, bundle.input), + external: getRelativePath(file.output, output), + }; + }); + replaceModule.push(...replacements); } // Current version of `yaml` is not tree-shakable, // but when we update it, we may reduce size, // since the UMD version don't need expose `__parsePrettierYamlConfig` - if (file.output.format === "umd" && file.output.file === "plugins/yaml.js") { + if (buildOptions.format === "umd" && file.output === "plugins/yaml.js") { replaceModule.push({ - module: path.join(PROJECT_ROOT, file.input), + module: path.join(sourceDirectory, file.input), text: 'export * from "../language-yaml/index.js";', }); } - const { buildOptions } = file; const shouldMinify = - cliOptions.minify ?? buildOptions.minify ?? file.platform === "universal"; + cliOptions.minify ?? + buildOptions.minify ?? + buildOptions.platform === "universal"; const esbuildOptions = { - entryPoints: [path.join(PROJECT_ROOT, file.input)], + entryPoints: [path.join(sourceDirectory, file.input)], bundle: true, metafile: true, plugins: [ @@ -198,8 +191,8 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { cliOptions.reports && esbuildPluginVisualizer({ formats: cliOptions.reports }), esbuildPluginThrowWarnings({ - allowDynamicRequire: file.platform === "node", - allowDynamicImport: file.platform === "node", + allowDynamicRequire: buildOptions.platform === "node", + allowDynamicImport: buildOptions.platform === "node", allowedWarnings: buildOptions.allowedWarnings, }), buildOptions.addDefaultExport && esbuildPluginAddDefaultExport(), @@ -213,10 +206,11 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { ...(buildOptions.target ?? [`node${PRODUCTION_MINIMAL_NODE_JS_VERSION}`]), ], logLevel: "error", - format: file.output.format, - outfile: path.join(distDirectory, cliOptions.saveAs ?? file.output.file), + format: buildOptions.format, + outfile: path.join(distDirectory, cliOptions.saveAs ?? file.output), // https://esbuild.github.io/api/#main-fields - mainFields: file.platform === "node" ? ["module", "main"] : undefined, + mainFields: + buildOptions.platform === "node" ? ["module", "main"] : undefined, supported: { ...buildOptions.supported, // https://github.com/evanw/esbuild/issues/3471 @@ -231,24 +225,23 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { packages: "bundle", }; - if (file.platform === "universal") { + if (buildOptions.platform === "universal") { if (!buildOptions.target) { esbuildOptions.target.push(...universalTarget); } - if (file.output.format === "umd") { + if (buildOptions.format === "umd") { esbuildOptions.plugins.push( esbuildPluginUmd({ - name: file.output.umdVariableName, + name: buildOptions.umdVariableName, }), ); } } else { esbuildOptions.platform = "node"; - esbuildOptions.external.push(...files.map((file) => file.output.file)); // https://github.com/evanw/esbuild/issues/1921 - if (file.output.format === "esm") { + if (buildOptions.format === "esm") { esbuildOptions.plugins.push(esbuildPluginShimCommonjsObjects()); } } @@ -256,9 +249,20 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { return esbuildOptions; } -async function runEsbuild(options) { - const esbuildOptions = getEsbuildOptions(options); - return { esbuildResult: await esbuild.build(esbuildOptions) }; +function createJavascriptModuleBuilder(buildOptions) { + return async function runEsbuild({ packageConfig, file, cliOptions }) { + if (typeof buildOptions === "function") { + buildOptions = await buildOptions(); + } + + const esbuildOptions = getEsbuildOptions({ + packageConfig, + file, + cliOptions, + buildOptions, + }); + return { esbuildResult: await esbuild.build(esbuildOptions) }; + }; } -export default runEsbuild; +export { createJavascriptModuleBuilder }; diff --git a/scripts/build/builders/package-json.js b/scripts/build/builders/package-json.js new file mode 100644 index 000000000..9eec4ef66 --- /dev/null +++ b/scripts/build/builders/package-json.js @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import path from "node:path"; +import projectPackageJson from "../../../package.json" with { type: "json" }; +import { + PRODUCTION_MINIMAL_NODE_JS_VERSION, + readJson, + writeJson, +} from "../../utilities/index.js"; + +const keysToKeep = [ + "name", + "version", + "description", + "bin", + "repository", + "funding", + "homepage", + "author", + "license", + "main", + "browser", + "unpkg", + "exports", + "engines", + "files", + "preferUnplugged", + "sideEffects", +]; + +const publishConfig = { + access: "public", + registry: "https://registry.npmjs.org/", +}; + +function createPackageJsonBuilder({ process }) { + return async function buildPackageJson({ packageConfig, file }) { + const { distDirectory, sourceDirectory } = packageConfig; + const original = await readJson(path.join(sourceDirectory, file.input)); + const packageFiles = packageConfig.modules.flatMap(({ files }) => files); + + let packageJson = { + ...pick(original, keysToKeep), + engines: { + ...original.engines, + // https://github.com/prettier/prettier/pull/13118#discussion_r922708068 + // Don't delete, event it's the same in package.json + node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`, + }, + type: "commonjs", + files: packageFiles.map((file) => file.output).sort(), + publishConfig, + }; + + // Rewrite `bin` + if (original.bin) { + const originalBinFile = path.join(sourceDirectory, original.bin); + const bin = packageFiles.find( + (file) => + file.input && + path.join(sourceDirectory, file.input) === originalBinFile, + ).output; + + assert.ok(typeof bin === "string"); + packageJson.bin = `./${bin}`; + } + + if (process) { + packageJson = await process(packageJson, { + packageConfig, + originalPackageJson: original, + projectPackageJson, + }); + } + + await writeJson(path.join(distDirectory, file.output), packageJson); + }; +} + +function pick(object, keys) { + keys = new Set(keys); + return Object.fromEntries( + Object.entries(object).filter(([key]) => keys.has(key)), + ); +} + +export { createPackageJsonBuilder }; diff --git a/scripts/build/build-types.js b/scripts/build/builders/types.js similarity index 78% rename from scripts/build/build-types.js rename to scripts/build/builders/types.js index 86f1c9ef0..3f0b02734 100644 --- a/scripts/build/build-types.js +++ b/scripts/build/builders/types.js @@ -3,7 +3,7 @@ import path from "node:path"; import url from "node:url"; import { isValidIdentifier } from "@babel/types"; import { outdent } from "outdent"; -import { PROJECT_ROOT, writeFile } from "../utilities/index.js"; +import { writeFile } from "../../utilities/index.js"; async function typesFileBuilder({ packageConfig, file }) { /** @@ -20,10 +20,8 @@ async function typesFileBuilder({ packageConfig, file }) { for (const { from, to } of replacements) { text = text.replaceAll(` from "${from}";`, ` from "${to}";`); } - await writeFile( - path.join(packageConfig.distDirectory, file.output.file), - text, - ); + + await writeFile(path.join(packageConfig.distDirectory, file.output), text); } function toPropertyKey(name) { @@ -32,7 +30,7 @@ function toPropertyKey(name) { async function buildPluginTypes({ packageConfig, file: { input, output } }) { const pluginModule = await import( - url.pathToFileURL(path.join(PROJECT_ROOT, input)) + url.pathToFileURL(path.join(packageConfig.sourceDirectory, input)) ); const plugin = pluginModule.default ?? pluginModule; const parserNames = Object.keys(plugin.parsers ?? {}); @@ -55,16 +53,11 @@ async function buildPluginTypes({ packageConfig, file: { input, output } }) { }; `; - await writeFile( - path.join(packageConfig.distDirectory, output.file), - `${code}\n`, - ); + await writeFile(path.join(packageConfig.distDirectory, output), `${code}\n`); } -function buildTypes(options) { - return options.file.isPlugin - ? buildPluginTypes(options) - : typesFileBuilder(options); +function createTypesBuilder({ isPlugin }) { + return isPlugin ? buildPluginTypes : typesFileBuilder; } -export default buildTypes; +export { createTypesBuilder }; diff --git a/scripts/build/build-oxc-wasm-parser.js b/scripts/build/hacks/build-oxc-wasm-parser.js similarity index 92% rename from scripts/build/build-oxc-wasm-parser.js rename to scripts/build/hacks/build-oxc-wasm-parser.js index 3b235608e..bd36b0546 100644 --- a/scripts/build/build-oxc-wasm-parser.js +++ b/scripts/build/hacks/build-oxc-wasm-parser.js @@ -3,13 +3,12 @@ import { existsSync, promises as fs } from "node:fs"; import url from "node:url"; import spawn from "nano-spawn"; import { outdent } from "outdent"; -import packageJson from "../../package.json" with { type: "json" }; +import packageJson from "../../../package.json" with { type: "json" }; +const TEMPORARY_DIRECTORY = new URL("../../../.tmp/", import.meta.url); const DIRECTORY_NAME = "prettier-oxc-wasm-parser"; const PACKAGE_NAME = "@oxc-parser/binding-wasm32-wasi"; -const TEMPORARY_DIRECTORY = new URL("../../.tmp/", import.meta.url); - const runYarn = (command, options) => spawn("yarn", command.split(" "), options); @@ -60,6 +59,11 @@ async function inlineWasmBinary(directory) { ${text} `; + text = text.replaceAll( + /new URL\(".*?", import\.meta\.url\)/gu, + "{url:undefined}", + ); + text = text.replace( "await fetch(__wasmUrl).then((res) => res.arrayBuffer())", `__base64ToArrayBuffer(${JSON.stringify(wasm)})`, diff --git a/scripts/build/esmify-typescript-eslint.js b/scripts/build/hacks/esmify-typescript-eslint.js similarity index 98% rename from scripts/build/esmify-typescript-eslint.js rename to scripts/build/hacks/esmify-typescript-eslint.js index f2e5ff92d..5fd45bbce 100644 --- a/scripts/build/esmify-typescript-eslint.js +++ b/scripts/build/hacks/esmify-typescript-eslint.js @@ -2,7 +2,7 @@ import * as path from "node:path"; import { outdent } from "outdent"; -import { PROJECT_ROOT, writeFile } from "../utilities/index.js"; +import { PROJECT_ROOT, writeFile } from "../../utilities/index.js"; function esmifyTypescriptEslint(text) { /* diff --git a/scripts/build/modify-typescript-module.js b/scripts/build/hacks/modify-typescript-module.js similarity index 99% rename from scripts/build/modify-typescript-module.js rename to scripts/build/hacks/modify-typescript-module.js index 1aa8d77df..6b1976b94 100644 --- a/scripts/build/modify-typescript-module.js +++ b/scripts/build/hacks/modify-typescript-module.js @@ -2,7 +2,7 @@ import path from "node:path"; import escapeStringRegexp from "escape-string-regexp"; import MagicString from "magic-string"; import { outdent } from "outdent"; -import { PROJECT_ROOT, writeFile } from "../utilities/index.js"; +import { PROJECT_ROOT, writeFile } from "../../utilities/index.js"; import UNUSED_SPECIFIERS from "./typescript-unused-specifiers.js"; function* getModules(text) { diff --git a/scripts/build/typescript-unused-specifiers.js b/scripts/build/hacks/typescript-unused-specifiers.js similarity index 99% rename from scripts/build/typescript-unused-specifiers.js rename to scripts/build/hacks/typescript-unused-specifiers.js index 67ad9e0d6..8450026da 100644 --- a/scripts/build/typescript-unused-specifiers.js +++ b/scripts/build/hacks/typescript-unused-specifiers.js @@ -276,7 +276,6 @@ export default new Set([ "getCommentRange", "getCommonSourceDirectory", "getCommonSourceDirectoryOfConfig", - "getCompilerOptionsDiffValue", "getConditions", "getConfigFileParsingDiagnostics", "getConstantValue", diff --git a/scripts/build/packages/config-helpers.js b/scripts/build/packages/config-helpers.js new file mode 100644 index 000000000..f6fb56951 --- /dev/null +++ b/scripts/build/packages/config-helpers.js @@ -0,0 +1,140 @@ +import fs from "node:fs"; +import path from "node:path"; +import { PROJECT_ROOT } from "../../utilities/index.js"; +import { copyFileBuilder } from "../builders/copy-file.js"; +import { createDependenciesLicenseBuilder } from "../builders/dependencies-license.js"; +import { createJavascriptModuleBuilder } from "../builders/javascript-module.js"; +import { createPackageJsonBuilder } from "../builders/package-json.js"; +import { createTypesBuilder } from "../builders/types.js"; + +const extensions = { + esm: ".mjs", + umd: ".js", + cjs: ".cjs", +}; + +function createPackageJsonConfig({ "package.json": process }) { + return { + input: "package.json", + output: "package.json", + build: createPackageJsonBuilder({ process }), + }; +} + +function createReadmeConfig() { + return { + input: "README.md", + output: "README.md", + build: copyFileBuilder, + }; +} + +function createLicenseConfig() { + return { + input: path.join(PROJECT_ROOT, "LICENSE"), + output: "LICENSE", + build: copyFileBuilder, + }; +} + +function createThirdPartyNoticeConfig({ packageDisplayName }) { + return { + output: "THIRD-PARTY-NOTICES.md", + build: createDependenciesLicenseBuilder({ packageDisplayName }), + }; +} + +function createPackageMetaFilesConfig(options = {}) { + return { + name: "Meta files", + files: [ + createPackageJsonConfig, + createLicenseConfig, + createReadmeConfig, + createThirdPartyNoticeConfig, + ].map((create) => create(options)), + }; +} + +function createTypesConfig({ input, outputBaseName, isPlugin }) { + const inputBasename = input.slice(0, -path.extname(input).length); + if (!isPlugin) { + input = `${inputBasename}.d.ts`; + + if (!fs.existsSync(path.join(PROJECT_ROOT, input))) { + return []; + } + } + + return [ + { + input, + output: `${outputBaseName ?? inputBasename}.d.ts`, + build: createTypesBuilder({ isPlugin }), + }, + ]; +} + +function createNodejsFileConfig(file) { + if (typeof file === "string") { + file = { input: file }; + } + let { input, output, outputBaseName, ...buildOptions } = file; + + const format = input.endsWith(".cjs") ? "cjs" : "esm"; + outputBaseName ??= path.basename(input, path.extname(input)); + + return [ + { + input, + output: `${outputBaseName}${extensions[format]}`, + build: createJavascriptModuleBuilder({ + format, + ...buildOptions, + platform: "node", + }), + }, + ...createTypesConfig({ input, outputBaseName }), + ]; +} + +function createUniversalFileConfig(file) { + if (typeof file === "string") { + file = { input: file }; + } + let { input, outputBaseName, umdVariableName, ...buildOptions } = file; + outputBaseName ??= path.basename(input, path.extname(input)); + umdVariableName ??= outputBaseName; + + return [ + ...[ + { + format: "esm", + file: `${outputBaseName}${extensions.esm}`, + }, + { + format: "umd", + file: `${outputBaseName}${extensions.umd}`, + umdVariableName, + }, + ].map((output) => ({ + input, + output: output.file, + build: createJavascriptModuleBuilder({ + platform: "universal", + addDefaultExport: output.format === "esm", + format: output.format, + umdVariableName: output.umdVariableName, + ...buildOptions, + }), + })), + ...createTypesConfig({ input, outputBaseName }), + ]; +} + +export { + createNodejsFileConfig, + createPackageMetaFilesConfig, + createTypesConfig, + createUniversalFileConfig, +}; diff --git a/scripts/build/packages/index.js b/scripts/build/packages/index.js new file mode 100644 index 000000000..78e0dee34 --- /dev/null +++ b/scripts/build/packages/index.js @@ -0,0 +1,9 @@ +import pluginHermesBuildConfig from "./plugin-hermes.js"; +import pluginOxcBuildConfig from "./plugin-oxc.js"; +import prettierBuildConfig from "./prettier.js"; + +export default [ + prettierBuildConfig, + pluginHermesBuildConfig, + pluginOxcBuildConfig, +]; diff --git a/scripts/build/packages/plugin-hermes.js b/scripts/build/packages/plugin-hermes.js new file mode 100644 index 000000000..c1f35f64a --- /dev/null +++ b/scripts/build/packages/plugin-hermes.js @@ -0,0 +1,61 @@ +import path from "node:path"; +import { DIST_DIR, PACKAGES_DIRECTORY } from "../../utilities/index.js"; +import { createJavascriptModuleBuilder } from "../builders/javascript-module.js"; +import { getPackageFile } from "../utilities.js"; +import { + createPackageMetaFilesConfig, + createTypesConfig, +} from "./config-helpers.js"; + +const packageConfig = { + packageName: "@prettier/plugin-hermes", + sourceDirectory: path.join(PACKAGES_DIRECTORY, "plugin-hermes"), + distDirectory: path.join(DIST_DIR, "plugin-hermes"), + modules: [], +}; + +const mainModule = { + name: "Entries", + files: [ + { + input: "index.js", + output: "index.mjs", + build: createJavascriptModuleBuilder({ + platform: "universal", + addDefaultExport: true, + replaceModule: [ + { + module: getPackageFile("hermes-parser/dist/HermesParser.js"), + process(text) { + text = + 'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' + + text; + return text; + }, + }, + { + module: getPackageFile("hermes-parser/dist/HermesParserWASM.js"), + process(text) { + text = text.replaceAll("process.argv", "[]"); + text = text.replaceAll('require("fs")', "undefined"); + text = text.replaceAll('require("path")', "undefined"); + text = + 'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' + + text; + + return text; + }, + }, + ], + format: "esm", + }), + playground: true, + }, + + ...createTypesConfig({ input: "index.js", isPlugin: true }), + ], +}; + +packageConfig.modules.push(mainModule, createPackageMetaFilesConfig()); + +export default packageConfig; diff --git a/scripts/build/packages/plugin-oxc.js b/scripts/build/packages/plugin-oxc.js new file mode 100644 index 000000000..b5ffb5301 --- /dev/null +++ b/scripts/build/packages/plugin-oxc.js @@ -0,0 +1,72 @@ +import path from "node:path"; +import { DIST_DIR, PACKAGES_DIRECTORY } from "../../utilities/index.js"; +import { createJavascriptModuleBuilder } from "../builders/javascript-module.js"; +import buildOxcWasmParser from "../hacks/build-oxc-wasm-parser.js"; +import { getPackageFile } from "../utilities.js"; +import { + createPackageMetaFilesConfig, + createTypesConfig, +} from "./config-helpers.js"; + +const packageConfig = { + packageName: "@prettier/plugin-oxc", + sourceDirectory: path.join(PACKAGES_DIRECTORY, "plugin-oxc"), + distDirectory: path.join(DIST_DIR, "plugin-oxc"), + modules: [], +}; + +const mainModule = { + name: "Entries", + files: [ + { + input: "index.js", + output: "index.mjs", + prepare: buildOxcWasmParser, + build: createJavascriptModuleBuilder({ + input: "index.js", + output: "index.mjs", + format: "esm", + platform: "node", + external: ["oxc-parser"], + addDefaultExport: true, + }), + }, + { + input: "index.js", + output: "index.browser.mjs", + build: createJavascriptModuleBuilder(async () => { + const { entry } = await buildOxcWasmParser(); + return { + format: "esm", + platform: "universal", + addDefaultExport: true, + replaceModule: [ + { + module: getPackageFile("oxc-parser/src-js/wasm.js"), + path: entry, + }, + ], + allowedWarnings: ["indirect-require", "package.json"], + }; + }), + playground: true, + }, + ...createTypesConfig({ input: "index.js", isPlugin: true }), + ], +}; + +packageConfig.modules.push( + mainModule, + createPackageMetaFilesConfig({ + "package.json"(packageJson, { projectPackageJson }) { + return { + ...packageJson, + dependencies: { + "oxc-parser": projectPackageJson.dependencies["oxc-parser"], + }, + }; + }, + }), +); + +export default packageConfig; diff --git a/scripts/build/packages/prettier-package-json.js b/scripts/build/packages/prettier-package-json.js new file mode 100644 index 000000000..5bee07b9c --- /dev/null +++ b/scripts/build/packages/prettier-package-json.js @@ -0,0 +1,72 @@ +import path from "node:path"; + +function generatePackageJson(packageJson, { packageConfig }) { + const files = packageConfig.modules.flatMap((module) => module.files); + + return { + ...packageJson, + main: "./index.cjs", + exports: { + ".": { + types: "./index.d.ts", + require: "./index.cjs", + browser: { + import: "./standalone.mjs", + default: "./standalone.js", + }, + default: "./index.mjs", + }, + "./*": "./*", + ...Object.fromEntries( + files + .filter((file) => file.output.endsWith(".js")) + .map((file) => { + const basename = path.basename(file.output, ".js"); + const isPlugin = file.output.startsWith("plugins/"); + return [ + isPlugin ? `./plugins/${basename}` : `./${basename}`, + { + types: `./${file.output.replace(/\.js$/u, ".d.ts")}`, + // `module-sync` condition can prevent CJS plugins from working: https://github.com/prettier/prettier/issues/17139 + // Perform a test before re-adding it. + require: `./${file.output}`, + default: `./${file.output.replace(/\.js$/u, ".mjs")}`, + }, + ]; + }), + ), + // Legacy entries + // TODO: Remove bellow in v4 + "./esm/standalone.mjs": "./standalone.mjs", + ...Object.fromEntries( + files + .filter( + (file) => + file.output.startsWith("plugins/") && + file.output.endsWith(".js") && + file.output !== "plugins/estree.js", + ) + .flatMap((file) => { + let basename = path.basename(file.output, ".js"); + if (basename === "acorn") { + basename = "espree"; + } + return [ + [`./parser-${basename}`, `./${file.output}`], + [`./parser-${basename}.js`, `./${file.output}`], + [ + `./esm/parser-${basename}.mjs`, + `./${file.output.replace(/\.js$/u, ".mjs")}`, + ], + ]; + }), + ), + }, + scripts: { + prepublishOnly: + "node -e \"assert.equal(require('.').version, require('..').version)\"", + }, + }; +} + +export { generatePackageJson }; diff --git a/scripts/build/config.js b/scripts/build/packages/prettier.js similarity index 55% rename from scripts/build/config.js rename to scripts/build/packages/prettier.js index f26525238..c1d6484c1 100644 --- a/scripts/build/config.js +++ b/scripts/build/packages/prettier.js @@ -1,21 +1,19 @@ -import fs from "node:fs"; import path from "node:path"; import url from "node:url"; import createEsmUtils from "esm-utils"; import { outdent } from "outdent"; -import { copyFile, DIST_DIR, PROJECT_ROOT } from "../utilities/index.js"; -import buildDependenciesLicense from "./build-dependencies-license.js"; -import buildJavascriptModule from "./build-javascript-module.js"; -import buildOxcWasmParser from "./build-oxc-wasm-parser.js"; +import { DIST_DIR, PROJECT_ROOT } from "../../utilities/index.js"; +import { createJavascriptModuleBuilder } from "../builders/javascript-module.js"; +import esmifyTypescriptEslint from "../hacks/esmify-typescript-eslint.js"; +import modifyTypescriptModule from "../hacks/modify-typescript-module.js"; +import { getPackageFile } from "../utilities.js"; import { - buildPluginHermesPackageJson, - buildPluginOxcPackageJson, - buildPrettierPackageJson, -} from "./build-package-json.js"; -import buildTypes from "./build-types.js"; -import esmifyTypescriptEslint from "./esmify-typescript-eslint.js"; -import modifyTypescriptModule from "./modify-typescript-module.js"; -import { getPackageFile } from "./utilities.js"; + createNodejsFileConfig, + createPackageMetaFilesConfig, + createTypesConfig, + createUniversalFileConfig, +} from "./config-helpers.js"; +import { generatePackageJson } from "./prettier-package-json.js"; const { require, @@ -24,56 +22,6 @@ const { } = createEsmUtils(import.meta); const resolveEsmModulePath = (specifier) => url.fileURLToPath(importMetaResolve(specifier)); -const copyFileBuilder = ({ packageConfig, file }) => - copyFile( - path.join(PROJECT_ROOT, file.input), - path.join(packageConfig.distDirectory, file.output.file), - ); -const oxcWasmParser = await buildOxcWasmParser(); - -function getTypesFileConfig({ input: jsFileInput, outputBaseName, isPlugin }) { - let input = jsFileInput; - if (!isPlugin) { - input = jsFileInput.replace(/\.[cm]?js$/u, ".d.ts"); - - if (!fs.existsSync(path.join(PROJECT_ROOT, input))) { - return; - } - } - - return { - input, - output: { - file: `${outputBaseName}.d.ts`, - }, - kind: "types", - isPlugin, - build: buildTypes, - }; -} - -/** - * @typedef {Object} BuildOptions - * @property {object[]?} replaceModule - Module replacements - * @property {string[]?} target - ESBuild targets - * @property {string[]?} external - array of paths that should not be included in the final bundle - * @property {boolean?} minify - disable code minification - * - * @typedef {Object} Output - * @property {"esm" | "umd" | "cjs" | "text" | "json"} format - File format - * @property {string} file - path of the output file in the `dist/` folder - * @property {string?} umdVariableName - name for the UMD file (for plugins, it'll be `prettierPlugins.${name}`) - * - * @typedef {Object} File - * @property {string} input - input of the file - * @property {Output} output - output of the file - * @property {"javascript" | "types" | "meta"} kind - file kind - * @property {function} build - file generate function - * @property {"node" | "universal"} platform - ESBuild platform - * @property {BuildOptions} buildOptions - ESBuild options - * @property {boolean?} isPlugin - file is a plugin - * @property {boolean?} addDefaultExport - add default export to bundle - */ const extensions = { esm: ".mjs", @@ -81,6 +29,233 @@ const extensions = { cjs: ".cjs", }; +const packageConfig = { + packageName: "prettier", + sourceDirectory: PROJECT_ROOT, + distDirectory: path.join(DIST_DIR, "prettier"), + modules: [], +}; + +const mainModule = { + name: "Main", + files: [ + createNodejsFileConfig({ + input: "src/index.js", + replaceModule: [ + // `editorconfig` use a older version of `semver` and only uses `semver.gte` + { + module: require.resolve("editorconfig"), + find: 'var semver = __importStar(require("semver"));', + replacement: outdent` + var semver = { + gte: require(${JSON.stringify( + require.resolve("semver/functions/gte"), + )}) + }; + `, + }, + { + module: require.resolve("n-readlines"), + find: "const readBuffer = new Buffer(this.options.readChunk);", + replacement: + "const readBuffer = Buffer.alloc(this.options.readChunk);", + }, + // `parse-json` use another copy of `@babel/code-frame` + { + module: require.resolve("@babel/code-frame", { + paths: [require.resolve("parse-json")], + }), + path: require.resolve("@babel/code-frame"), + }, + { + module: getPackageFile("json5/dist/index.mjs"), + find: "export default lib;", + replacement: "export default { parse };", + }, + ], + addDefaultExport: true, + reuseDocModule: true, + format: "esm", + }), + createNodejsFileConfig("src/index.cjs"), + createUniversalFileConfig({ + input: "src/standalone.js", + umdVariableName: "prettier", + replaceModule: [ + { + module: require.resolve("@babel/code-frame"), + process(text) { + text = text.replaceAll( + "var picocolors = require('picocolors');", + "", + ); + text = text.replaceAll("var jsTokens = require('js-tokens');", ""); + text = text.replaceAll( + "var helperValidatorIdentifier = require('@babel/helper-validator-identifier');", + "", + ); + + text = text.replaceAll( + /(?<=\n)let tokenize;\n\{\n.*?\n\}(?=\n)/gsu, + "", + ); + + text = text.replaceAll( + /(?<=\n)function highlight\(text\) \{\n.*?\n\}(?=\n)/gsu, + "function highlight(text) {return text}", + ); + + text = text.replaceAll( + /(?<=\n)function getDefs\(enabled\) \{\n.*?\n\}(?=\n)/gsu, + outdent` + function getDefs() { + return new Proxy({}, {get: () => (text) => text}) + } + `, + ); + + text = text.replaceAll( + "const defsOn = buildDefs(picocolors.createColors(true));", + "", + ); + text = text.replaceAll( + "const defsOff = buildDefs(picocolors.createColors(false));", + "", + ); + + text = text.replaceAll( + "const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;", + "const shouldHighlight = false;", + ); + + text = text.replaceAll("exports.default = index;", ""); + text = text.replaceAll("exports.highlight = highlight;", ""); + + return text; + }, + }, + // Smaller size + { + module: getPackageFile("picocolors/picocolors.browser.js"), + path: path.join(dirname, "../shims/colors.js"), + }, + ], + }), + ].flat(), +}; +for (const file of mainModule.files) { + if (file.output === "standalone.mjs") { + file.playground = true; + } +} + +const cliModule = { + name: "CLI", + files: [ + { + input: "bin/prettier.cjs", + outputBaseName: "bin/prettier", + target: ["node0.10"], + replaceModule: [ + { + module: path.join(PROJECT_ROOT, "bin/prettier.cjs"), + process(text) { + text = text.replace( + "../src/cli/index.js", + "../internal/legacy-cli.mjs", + ); + text = text.replace( + "../src/experimental-cli/index.js", + "../internal/experimental-cli.mjs", + ); + return text; + }, + }, + ], + }, + { + input: "src/cli/index.js", + outputBaseName: "internal/legacy-cli", + external: ["tinybench"], + // TODO: Remove this when we drop support for Node.js v16 + replaceModule: [ + { + module: resolveEsmModulePath("@cacheable/memory"), + process: (text) => + outdent` + const structuredClone = + globalThis.structuredClone ?? + ((value) => JSON.parse(JSON.stringify(value))); + + ${text} + `, + }, + ], + }, + ...[ + { + input: "src/experimental-cli/index.js", + outputBaseName: "internal/experimental-cli", + replaceModule: [ + { + module: getPackageFile("@prettier/cli/dist/prettier_serial.js"), + external: "./experimental-cli-worker.mjs", + }, + { + module: getPackageFile("@prettier/cli/dist/prettier_parallel.js"), + find: 'new URL("./prettier_serial.js", import.meta.url)', + replacement: + 'new URL("./experimental-cli-worker.mjs", import.meta.url)', + }, + { + module: getPackageFile("json5/dist/index.mjs"), + find: "export default lib;", + replacement: "export default { parse };", + }, + { + module: getPackageFile("js-yaml/dist/js-yaml.mjs"), + find: "var dump = dumper.dump;", + replacement: "var dump;", + }, + ], + }, + { + input: "src/experimental-cli/worker.js", + outputBaseName: "internal/experimental-cli-worker", + }, + ].map(({ input, outputBaseName, replaceModule = [] }) => ({ + input, + outputBaseName, + replaceModule: [ + ...replaceModule, + { + module: getPackageFile("@prettier/cli/dist/constants.js"), + path: path.join( + PROJECT_ROOT, + "src/experimental-cli/constants.evaluate.js", + ), + }, + ...["package.json", "index.mjs"].map((file) => ({ + module: getPackageFile(`prettier/${file}`), + external: `../${file}`, + })), + ], + })), + ].flatMap((file) => createNodejsFileConfig(file)), +}; + +const docModule = { + name: "Doc", + files: [ + createUniversalFileConfig({ + input: "src/document/public.js", + outputBaseName: "doc", + umdVariableName: "doc", + minify: false, + }), + ].flat(), +}; + const pluginFiles = [ "src/plugins/estree.js", { @@ -272,7 +447,7 @@ const pluginFiles = [ // Only needed if `range`/`loc` in parse options is `false` { module: getPackageFile("debug/src/browser.js"), - path: path.join(dirname, "./shims/debug.js"), + path: path.join(dirname, "../shims/debug.js"), }, { module: require.resolve("ts-api-utils"), @@ -614,521 +789,67 @@ const pluginFiles = [ }, "src/plugins/html.js", "src/plugins/yaml.js", -].map((file) => { +]; + +function createPluginModule(file) { if (typeof file === "string") { file = { input: file }; } - let { input, umdPropertyName, outputBaseName, ...buildOptions } = file; + const { input, ...buildOptions } = file; + const { pluginName } = input.match( + /\/plugins\/(?.*?)\.js$/u, + ).groups; - outputBaseName ??= input.match(/\/plugins\/(?.*?)\.js$/u) - .groups.outputBaseName; + const umdVariableName = `prettierPlugins.${pluginName}`; - const umdVariableName = `prettierPlugins.${ - umdPropertyName ?? outputBaseName - }`; + const files = [ + { + format: "esm", + file: `${pluginName}${extensions.esm}`, + }, + { + format: "umd", + file: `${pluginName}${extensions.umd}`, + umdVariableName, + }, + ].map((output) => ({ + input, + output: `plugins/${output.file}`, + build: createJavascriptModuleBuilder({ + platform: "universal", + addDefaultExport: output.format === "esm", + ...buildOptions, + format: output.format, + umdVariableName: output.umdVariableName, + }), + playground: output.format === "esm", + })); return { - input, - outputBaseName: `plugins/${outputBaseName}`, - umdVariableName, - buildOptions, - isPlugin: true, - }; -}); - -const nonPluginUniversalFiles = [ - { - input: "src/document/public.js", - outputBaseName: "doc", - umdVariableName: "doc", - minify: false, - }, - { - input: "src/standalone.js", - umdVariableName: "prettier", - replaceModule: [ - { - module: require.resolve("@babel/code-frame"), - process(text) { - text = text.replaceAll("var picocolors = require('picocolors');", ""); - text = text.replaceAll("var jsTokens = require('js-tokens');", ""); - text = text.replaceAll( - "var helperValidatorIdentifier = require('@babel/helper-validator-identifier');", - "", - ); - - text = text.replaceAll( - /(?<=\n)let tokenize;\n\{\n.*?\n\}(?=\n)/gsu, - "", - ); - - text = text.replaceAll( - /(?<=\n)function highlight\(text\) \{\n.*?\n\}(?=\n)/gsu, - "function highlight(text) {return text}", - ); - - text = text.replaceAll( - /(?<=\n)function getDefs\(enabled\) \{\n.*?\n\}(?=\n)/gsu, - outdent` - function getDefs() { - return new Proxy({}, {get: () => (text) => text}) - } - `, - ); - - text = text.replaceAll( - "const defsOn = buildDefs(picocolors.createColors(true));", - "", - ); - text = text.replaceAll( - "const defsOff = buildDefs(picocolors.createColors(false));", - "", - ); - - text = text.replaceAll( - "const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;", - "const shouldHighlight = false;", - ); - - text = text.replaceAll("exports.default = index;", ""); - text = text.replaceAll("exports.highlight = highlight;", ""); - - return text; - }, - }, - // Smaller size - { - module: getPackageFile("picocolors/picocolors.browser.js"), - path: path.join(dirname, "./shims/colors.js"), - }, - ], - }, -].map((file) => { - const { - input, - outputBaseName = path.basename(input, ".js"), - umdVariableName, - ...buildOptions - } = file; - - return { - input, - outputBaseName, - umdVariableName, - buildOptions, - }; -}); - -const universalFiles = [...nonPluginUniversalFiles, ...pluginFiles].flatMap( - (file) => { - let { input, outputBaseName, umdVariableName, buildOptions, isPlugin } = - file; - - outputBaseName ??= path.basename(input); - - return [ - ...[ - { - format: "esm", - file: `${outputBaseName}${extensions.esm}`, - }, - { - format: "umd", - file: `${outputBaseName}${extensions.umd}`, - umdVariableName, - }, - ].map((output) => ({ + name: `Plugin[${pluginName}]`, + files: [ + ...files, + ...createTypesConfig({ input, - output, - platform: "universal", - buildOptions: { - addDefaultExport: output.format === "esm", - ...buildOptions, - }, - isPlugin, - build: buildJavascriptModule, - kind: "javascript", - playground: output.format === "esm" && outputBaseName !== "doc", - })), - getTypesFileConfig({ input, outputBaseName, isPlugin }), - ]; - }, + outputBaseName: `plugins/${pluginName}`, + isPlugin: true, + }), + ], + }; +} + +const pluginModules = pluginFiles.map((file) => createPluginModule(file)); + +packageConfig.modules.push( + mainModule, + cliModule, + docModule, + ...pluginModules, + createPackageMetaFilesConfig({ + "package.json": generatePackageJson, + packageDisplayName: "Prettier", + }), ); -const nodejsFiles = [ - { - input: "src/index.js", - replaceModule: [ - // `editorconfig` use a older version of `semver` and only uses `semver.gte` - { - module: require.resolve("editorconfig"), - find: 'var semver = __importStar(require("semver"));', - replacement: ` - var semver = { - gte: require(${JSON.stringify( - require.resolve("semver/functions/gte"), - )}) - }; - `, - }, - { - module: require.resolve("n-readlines"), - find: "const readBuffer = new Buffer(this.options.readChunk);", - replacement: "const readBuffer = Buffer.alloc(this.options.readChunk);", - }, - // `parse-json` use another copy of `@babel/code-frame` - { - module: require.resolve("@babel/code-frame", { - paths: [require.resolve("parse-json")], - }), - path: require.resolve("@babel/code-frame"), - }, - { - module: getPackageFile("json5/dist/index.mjs"), - find: "export default lib;", - replacement: "export default { parse };", - }, - ], - addDefaultExport: true, - reuseDocModule: true, - }, - { - input: "src/index.cjs", - }, - { - input: "bin/prettier.cjs", - outputBaseName: "bin/prettier", - target: ["node0.10"], - replaceModule: [ - { - module: path.join(PROJECT_ROOT, "bin/prettier.cjs"), - process(text) { - text = text.replace( - "../src/cli/index.js", - "../internal/legacy-cli.mjs", - ); - text = text.replace( - "../src/experimental-cli/index.js", - "../internal/experimental-cli.mjs", - ); - return text; - }, - }, - ], - }, - { - input: "src/cli/index.js", - outputBaseName: "internal/legacy-cli", - external: ["tinybench"], - // TODO: Remove this when we drop support for Node.js v16 - replaceModule: [ - { - module: resolveEsmModulePath("@cacheable/memory"), - process: (text) => - outdent` - const structuredClone = - globalThis.structuredClone ?? - ((value) => JSON.parse(JSON.stringify(value))); - - ${text} - `, - }, - ], - }, - ...[ - { - input: "src/experimental-cli/index.js", - outputBaseName: "internal/experimental-cli", - replaceModule: [ - { - module: getPackageFile("@prettier/cli/dist/prettier_serial.js"), - external: "./experimental-cli-worker.mjs", - }, - { - module: getPackageFile("@prettier/cli/dist/prettier_parallel.js"), - find: 'new URL("./prettier_serial.js", import.meta.url)', - replacement: - 'new URL("./experimental-cli-worker.mjs", import.meta.url)', - }, - { - module: getPackageFile("json5/dist/index.mjs"), - find: "export default lib;", - replacement: "export default { parse };", - }, - { - module: getPackageFile("js-yaml/dist/js-yaml.mjs"), - find: "var dump = dumper.dump;", - replacement: "var dump;", - }, - ], - }, - { - input: "src/experimental-cli/worker.js", - outputBaseName: "internal/experimental-cli-worker", - }, - ].map(({ input, outputBaseName, replaceModule = [] }) => ({ - input, - outputBaseName, - replaceModule: [ - ...replaceModule, - { - module: getPackageFile("@prettier/cli/dist/constants.js"), - path: path.join( - PROJECT_ROOT, - "src/experimental-cli/constants.evaluate.js", - ), - }, - ...["package.json", "index.mjs"].map((file) => ({ - module: getPackageFile(`prettier/${file}`), - external: `../${file}`, - })), - ], - })), -].flatMap((file) => { - let { input, output, outputBaseName, ...buildOptions } = file; - - const format = input.endsWith(".cjs") ? "cjs" : "esm"; - outputBaseName ??= path.basename(input, path.extname(input)); - - return [ - { - input, - output: { - format, - file: `${outputBaseName}${extensions[format]}`, - }, - platform: "node", - buildOptions, - build: buildJavascriptModule, - kind: "javascript", - }, - getTypesFileConfig({ input, outputBaseName }), - ]; -}); - -const metaFiles = [ - { - input: "package.json", - output: { - format: "json", - }, - build: buildPrettierPackageJson, - }, - { - input: "README.md", - build: copyFileBuilder, - }, - { - input: "LICENSE", - build: copyFileBuilder, - }, - { - output: { - file: "THIRD-PARTY-NOTICES.md", - }, - build: buildDependenciesLicense, - }, -].map((file) => ({ - ...file, - output: { file: file.input, ...file.output }, - kind: "meta", -})); - -/** @type {Files[]} */ -const files = [...nodejsFiles, ...universalFiles, ...metaFiles].filter(Boolean); -export default [ - { - packageName: "prettier", - // Used in THIRD-PARTY-NOTICES.md - packageDisplayName: "Prettier", - distDirectory: path.join(DIST_DIR, "prettier"), - files, - }, - { - packageName: "@prettier/plugin-oxc", - distDirectory: path.join(DIST_DIR, "plugin-oxc"), - files: [ - ...[ - { - input: "packages/plugin-oxc/index.js", - addDefaultExport: true, - }, - ].flatMap((file) => { - let { input, output, outputBaseName, ...buildOptions } = file; - - const format = input.endsWith(".cjs") ? "cjs" : "esm"; - outputBaseName ??= path.basename(input, path.extname(input)); - - return [ - { - input, - output: { - format, - file: `${outputBaseName}${extensions[format]}`, - }, - platform: "node", - buildOptions: { - ...buildOptions, - external: ["oxc-parser"], - }, - build: buildJavascriptModule, - kind: "javascript", - }, - { - input, - output: { - format, - file: `${outputBaseName}.browser${extensions[format]}`, - }, - platform: "universal", - buildOptions: { - ...buildOptions, - replaceModule: [ - ...(buildOptions.replaceModule ?? []), - { - module: getPackageFile("oxc-parser/src-js/wasm.js"), - path: oxcWasmParser.entry, - }, - ], - allowedWarnings: ["indirect-require", "package.json"], - }, - build: buildJavascriptModule, - kind: "javascript", - playground: true, - }, - getTypesFileConfig({ input, outputBaseName, isPlugin: true }), - ]; - }), - ...[ - { - input: "packages/plugin-oxc/package.json", - output: { - file: "package.json", - format: "json", - }, - build: buildPluginOxcPackageJson, - }, - { - input: "packages/plugin-oxc/README.md", - output: { - file: "README.md", - }, - build: copyFileBuilder, - }, - { - input: "LICENSE", - output: { - file: "LICENSE", - }, - build: copyFileBuilder, - }, - { - output: { - file: "THIRD-PARTY-NOTICES.md", - }, - build: buildDependenciesLicense, - }, - ].map((file) => ({ - ...file, - output: { file: file.input, ...file.output }, - kind: "meta", - })), - ], - }, - { - packageName: "@prettier/plugin-hermes", - distDirectory: path.join(DIST_DIR, "plugin-hermes"), - files: [ - ...[ - { - input: "packages/plugin-hermes/index.js", - addDefaultExport: true, - replaceModule: [ - { - module: require.resolve("hermes-parser/dist/HermesParser.js"), - process(text) { - text = - 'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' + - text; - return text; - }, - }, - { - module: require.resolve("hermes-parser/dist/HermesParserWASM.js"), - process(text) { - text = text.replaceAll("process.argv", "[]"); - text = text.replaceAll('require("fs")', "undefined"); - text = text.replaceAll('require("path")', "undefined"); - text = - 'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' + - text; - - return text; - }, - }, - ], - }, - ].flatMap((file) => { - let { input, output, outputBaseName, ...buildOptions } = file; - - outputBaseName ??= path.basename(input, path.extname(input)); - const format = input.endsWith(".cjs") ? "cjs" : "esm"; - - return [ - { - input, - output: { - format, - file: `${outputBaseName}${extensions[format]}`, - }, - platform: "universal", - buildOptions: { - addDefaultExport: true, - ...buildOptions, - }, - isPlugin: true, - build: buildJavascriptModule, - playground: true, - kind: "javascript", - }, - getTypesFileConfig({ input, outputBaseName, isPlugin: true }), - ]; - }), - ...[ - { - input: "packages/plugin-hermes/package.json", - output: { - file: "package.json", - format: "json", - }, - build: buildPluginHermesPackageJson, - }, - { - input: "packages/plugin-hermes/README.md", - output: { - file: "README.md", - }, - build: copyFileBuilder, - }, - { - input: "LICENSE", - output: { - file: "LICENSE", - }, - build: copyFileBuilder, - }, - { - output: { - file: "THIRD-PARTY-NOTICES.md", - }, - build: buildDependenciesLicense, - }, - ].map((file) => ({ - ...file, - output: { file: file.input, ...file.output }, - kind: "meta", - })), - ], - }, -]; +export default packageConfig; diff --git a/scripts/build/parse-arguments.js b/scripts/build/parse-arguments.js index fb0b6a99d..2f3743658 100644 --- a/scripts/build/parse-arguments.js +++ b/scripts/build/parse-arguments.js @@ -25,7 +25,9 @@ function parseArguments() { } const result = { - files: Array.isArray(values.file) ? new Set(values.file) : undefined, + files: Array.isArray(values.file) + ? new Set(values.file.map((file) => path.join(DIST_DIR, file))) + : undefined, playground: values.playground, printSize: values["print-size"], compareSize: values["compare-size"], diff --git a/scripts/bundle-eslint-config.js b/scripts/bundle-eslint-config.js index 972005bb6..83c25cba1 100644 --- a/scripts/bundle-eslint-config.js +++ b/scripts/bundle-eslint-config.js @@ -2,27 +2,36 @@ import assert from "node:assert"; import fs from "node:fs/promises"; import path from "node:path"; import eslintPluginCompat from "eslint-plugin-compat"; -import buildConfig from "./build/config.js"; +import packageBuildConfigs from "./build/packages/index.js"; import { DIST_DIR } from "./utilities/index.js"; const { browserslist: targets } = JSON.parse( await fs.readFile(new URL("../package.json", import.meta.url)), ); -function getProductionFiles(platform) { - return buildConfig - .flatMap((project) => - project.files - .filter( - (file) => file.kind === "javascript" && file.platform === platform, - ) - .map((file) => path.join(project.distDirectory, file.output.file)), - ) - .map((file) => path.relative(DIST_DIR, file).replaceAll("\\", "/")); -} +const jsFiles = packageBuildConfigs + .flatMap((packageConfig) => + packageConfig.modules.flatMap((module) => + module.files + .filter((file) => /\.[cm]?js$/u.test(file.output)) + .map((file) => path.join(packageConfig.distDirectory, file.output)), + ), + ) + .map((file) => path.relative(DIST_DIR, file).replaceAll("\\", "/")); -const browserFiles = getProductionFiles("universal"); -const nodejsFiles = getProductionFiles("node"); +const nodejsFiles = jsFiles.filter( + (file) => + file.endsWith(".cjs") || + [ + "prettier/index.mjs", + "prettier/internal/legacy-cli.mjs", + "prettier/internal/experimental-cli.mjs", + "prettier/internal/experimental-cli-worker.mjs", + "plugin-oxc/index.mjs", + ].includes(file), +); + +const browserFiles = jsFiles.filter((file) => !nodejsFiles.includes(file)); assert.ok(browserFiles.length > 0); assert.ok(nodejsFiles.length > 0); diff --git a/scripts/generate-unused-typescript-specifiers.js b/scripts/generate-unused-typescript-specifiers.js index 808561a1e..f4cbb9e40 100644 --- a/scripts/generate-unused-typescript-specifiers.js +++ b/scripts/generate-unused-typescript-specifiers.js @@ -4,8 +4,8 @@ import * as importMetaResolve from "import-meta-resolve"; import { outdent } from "outdent"; import * as prettier from "prettier"; import * as typescript from "typescript"; -import { modifyTypescriptModule } from "./build/modify-typescript-module.js"; -import UNUSED_SPECIFIERS from "./build/typescript-unused-specifiers.js"; +import { modifyTypescriptModule } from "./build/hacks/modify-typescript-module.js"; +import UNUSED_SPECIFIERS from "./build/hacks/typescript-unused-specifiers.js"; async function getRemovedSpecifiers(code, exports) { let errors = []; @@ -47,7 +47,7 @@ async function main() { .sort(); await fs.writeFile( - new URL("./build/typescript-unused-specifiers.js", import.meta.url), + new URL("./build/hacks/typescript-unused-specifiers.js", import.meta.url), await prettier.format( outdent` export default new Set(${JSON.stringify(specifiers, undefined, 2)}); diff --git a/scripts/tools/bundle-test/index.js b/scripts/tools/bundle-test/index.js index 13bfe0a41..078175795 100644 --- a/scripts/tools/bundle-test/index.js +++ b/scripts/tools/bundle-test/index.js @@ -2,7 +2,7 @@ import { createRequire } from "node:module"; import path from "node:path"; import url from "node:url"; import webpack from "webpack"; -import packageConfigs from "../../build/config.js"; +import packageBuildConfigs from "../../build/packages/index.js"; function runWebpack(config) { return new Promise((resolve, reject) => { @@ -29,60 +29,73 @@ const TEMPORARY_DIRECTORY = url.fileURLToPath( new URL("./.tmp", import.meta.url), ); -for (const packageConfig of packageConfigs) { - const { packageName, distDirectory, files } = packageConfig; +// Node.js only files +const exclude = new Map([ + [ + "prettier", + [ + "index.mjs", + "internal/legacy-cli.mjs", + "internal/experimental-cli.mjs", + "internal/experimental-cli-worker.mjs", + ], + ], + ["@prettier/plugin-oxc", ["index.mjs"]], +]); - if (packageName === "@prettier/plugin-oxc") { - continue; - } +for (const packageConfig of packageBuildConfigs) { + const { packageName, distDirectory, modules } = packageConfig; + const excludedFiles = new Set(exclude.get(packageName)); /* `require` in `parser-typescript.js`, #12338 */ - for (const file of files) { - if (file.platform !== "universal") { - continue; - } - console.log(`${file.output.file}: `); - - const stats = await runWebpack({ - mode: "production", - entry: path.join(distDirectory, file.output.file), - output: { - path: TEMPORARY_DIRECTORY, - filename: `${file.output.file}.[contenthash:7].${ - file.output.format === "esm" ? "mjs" : "cjs" - }`, - }, - performance: { hints: false }, - optimization: { minimize: false }, - }); - const result = stats.toJson(); - const { warnings, assets } = result; - - if (warnings.length > 0) { - console.log(warnings); - throw new Error("Unexpected webpack warning."); - } - - if (assets.length > 1) { - console.log(assets); - throw new Error("Unexpected assets."); - } - - const outputFileName = assets[0].name; - const outputFile = path.join(TEMPORARY_DIRECTORY, outputFileName); - const require = createRequire(import.meta.url); - - try { - if (file.output.format !== "esm") { - require(outputFile); - } else { - await import(url.pathToFileURL(outputFile)); + for (const module of modules) { + for (const file of module.files) { + if (!/\.m?js$/u.test(file.output) || excludedFiles.has(file.output)) { + continue; } - } catch (error) { - console.log(`'${outputFileName}' is not functional.`); - throw error; - } + console.log(`${file.output}: `); - console.log(" Passed."); + const stats = await runWebpack({ + mode: "production", + entry: path.join(distDirectory, file.output), + output: { + path: TEMPORARY_DIRECTORY, + filename: `${file.output}.[contenthash:7].${ + file.output.endsWith(".mjs") ? "mjs" : "cjs" + }`, + }, + performance: { hints: false }, + optimization: { minimize: false }, + }); + const result = stats.toJson(); + const { warnings, assets } = result; + + if (warnings.length > 0) { + console.log(warnings); + throw new Error("Unexpected webpack warning."); + } + + if (assets.length > 1) { + console.log(assets); + throw new Error("Unexpected assets."); + } + + const outputFileName = assets[0].name; + const outputFile = path.join(TEMPORARY_DIRECTORY, outputFileName); + const require = createRequire(import.meta.url); + + try { + if (file.output.endsWith(".mjs")) { + await import(url.pathToFileURL(outputFile)); + } else { + require(outputFile); + } + } catch (error) { + console.log(`'${outputFileName}' is not functional.`); + throw error; + } + + console.log(" Passed."); + } } } diff --git a/scripts/utilities/index.js b/scripts/utilities/index.js index 386889472..b00343323 100644 --- a/scripts/utilities/index.js +++ b/scripts/utilities/index.js @@ -36,15 +36,19 @@ const PROJECT_ROOT = url.fileURLToPath(new URL("../../", import.meta.url)); const DIST_DIR = path.join(PROJECT_ROOT, "dist"); const WEBSITE_DIR = path.join(PROJECT_ROOT, "website"); const SOURCE_DIR = path.join(PROJECT_ROOT, "src"); +const TEMPORARY_DIRECTORY = path.join(PROJECT_ROOT, ".tmp"); +const PACKAGES_DIRECTORY = path.join(PROJECT_ROOT, "packages"); const PRODUCTION_MINIMAL_NODE_JS_VERSION = "14"; export { copyFile, DIST_DIR, + PACKAGES_DIRECTORY, PRODUCTION_MINIMAL_NODE_JS_VERSION, PROJECT_ROOT, readJson, SOURCE_DIR, + TEMPORARY_DIRECTORY, WEBSITE_DIR, writeFile, writeJson,