// @ts-check import assert from "assert"; import path from "path"; import fs from "fs"; import del from "del"; import { task } from "hereby"; import _glob from "glob"; import util from "util"; import chalk from "chalk"; import { exec, readJson, getDiffTool, getDirSize, memoize, needsUpdate } from "./scripts/build/utils.mjs"; import { runConsoleTests, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } from "./scripts/build/tests.mjs"; import { buildProject as realBuildProject, cleanProject, watchProject } from "./scripts/build/projects.mjs"; import { localizationDirectories } from "./scripts/build/localization.mjs"; import cmdLineOptions from "./scripts/build/options.mjs"; import esbuild from "esbuild"; import { fileURLToPath } from "url"; const glob = util.promisify(_glob); /** @typedef {ReturnType} Task */ void 0; const copyrightFilename = "CopyrightNotice.txt"; const getCopyrightHeader = memoize(async () => { const contents = await fs.promises.readFile(copyrightFilename, "utf-8"); return contents.replace(/\r\n/g, "\n"); }); // TODO(jakebailey): This is really gross. If the build is cancelled (i.e. Ctrl+C), the modification will persist. // Waiting on: https://github.com/microsoft/TypeScript/issues/51164 let currentlyBuilding = 0; let oldTsconfigBase; /** @type {typeof realBuildProject} */ const buildProjectWithEmit = async (...args) => { const tsconfigBasePath = "./src/tsconfig-base.json"; // Not using fs.promises here, to ensure we are synchronous until running the real build. if (currentlyBuilding === 0) { oldTsconfigBase = fs.readFileSync(tsconfigBasePath, "utf-8"); fs.writeFileSync(tsconfigBasePath, oldTsconfigBase.replace(`"emitDeclarationOnly": true,`, `"emitDeclarationOnly": false, // DO NOT COMMIT`)); } currentlyBuilding++; await realBuildProject(...args); currentlyBuilding--; if (currentlyBuilding === 0) { fs.writeFileSync(tsconfigBasePath, oldTsconfigBase); } }; const buildProject = cmdLineOptions.bundle ? realBuildProject : buildProjectWithEmit; export const buildScripts = task({ name: "scripts", description: "Builds files in the 'scripts' folder.", run: () => buildProject("scripts") }); const libs = memoize(() => { /** @type {{ libs: string[]; paths: Record; }} */ const libraries = readJson("./src/lib/libs.json"); const libs = libraries.libs.map(lib => { const relativeSources = ["header.d.ts", lib + ".d.ts"]; const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts"); const sources = relativeSources.map(s => path.posix.join("src/lib", s)); const target = `built/local/${relativeTarget}`; return { target, sources }; }); return libs; }); export const generateLibs = task({ name: "lib", description: "Builds the library targets", run: async () => { await fs.promises.mkdir("./built/local", { recursive: true }); for (const lib of libs()) { let output = await getCopyrightHeader(); for (const source of lib.sources) { const contents = await fs.promises.readFile(source, "utf-8"); // TODO(jakebailey): "\n\n" is for compatibility with our current tests; our test baselines // are sensitive to the positions of things in the lib files. Eventually remove this, // or remove lib.d.ts line numbers from our baselines. output += "\n\n" + contents.replace(/\r\n/g, "\n"); } await fs.promises.writeFile(lib.target, output); } }, }); const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; export const generateDiagnostics = task({ name: "generate-diagnostics", description: "Generates a diagnostic file in TypeScript based on an input JSON file", run: async () => { await exec(process.execPath, ["scripts/processDiagnosticMessages.mjs", diagnosticMessagesJson]); } }); const cleanDiagnostics = task({ name: "clean-diagnostics", description: "Generates a diagnostic file in TypeScript based on an input JSON file", hiddenFromTaskList: true, run: () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]), }); // Localize diagnostics /** * .lcg file is what localization team uses to know what messages to localize. * The file is always generated in 'enu/diagnosticMessages.generated.json.lcg' */ const generatedLCGFile = "built/local/enu/diagnosticMessages.generated.json.lcg"; /** * The localization target produces the two following transformations: * 1. 'src\loc\lcl\\diagnosticMessages.generated.json.lcl' => 'built\local\\diagnosticMessages.generated.json' * convert localized resources into a .json file the compiler can understand * 2. 'src\compiler\diagnosticMessages.generated.json' => 'built\local\ENU\diagnosticMessages.generated.json.lcg' * generate the lcg file (source of messages to localize) from the diagnosticMessages.generated.json */ const localizationTargets = localizationDirectories .map(f => `built/local/${f}/diagnosticMessages.generated.json`) .concat(generatedLCGFile); const localize = task({ name: "localize", dependencies: [generateDiagnostics], run: async () => { if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.mjs", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); } } }); export const buildSrc = task({ name: "build-src", description: "Builds the src project (all code)", dependencies: [generateDiagnostics], run: () => buildProject("src"), }); export const watchSrc = task({ name: "watch-src", description: "Watches the src project (all code)", hiddenFromTaskList: true, dependencies: [generateDiagnostics], run: () => watchProject("src"), }); export const cleanSrc = task({ name: "clean-src", hiddenFromTaskList: true, run: () => cleanProject("src"), }); /** * @param {string} entrypoint * @param {string} output */ async function runDtsBundler(entrypoint, output) { await exec(process.execPath, [ "./scripts/dtsBundler.mjs", "--entrypoint", entrypoint, "--output", output, ]); } /** * @param {string} entrypoint * @param {string} outfile * @param {BundlerTaskOptions} [taskOptions] * * @typedef BundlerTaskOptions * @property {string[]} [external] * @property {boolean} [exportIsTsObject] * @property {boolean} [treeShaking] */ function createBundler(entrypoint, outfile, taskOptions = {}) { const getOptions = memoize(async () => { const copyright = await getCopyrightHeader(); const banner = taskOptions.exportIsTsObject ? "var ts = {}; ((module) => {" : ""; /** @type {esbuild.BuildOptions} */ const options = { entryPoints: [entrypoint], banner: { js: copyright + banner }, bundle: true, outfile, platform: "node", target: "es2018", format: "cjs", sourcemap: "linked", sourcesContent: false, treeShaking: taskOptions.treeShaking, external: [ ...(taskOptions.external ?? []), "source-map-support", ], logLevel: "warning", // legalComments: "none", // If we add copyright headers to the source files, uncomment. plugins: [ { name: "no-node-modules", setup: (build) => { build.onLoad({ filter: /[\\/]node_modules[\\/]/ }, () => { // Ideally, we'd use "--external:./node_modules/*" here, but that doesn't work; we // will instead end up with paths to node_modules rather than the package names. // Instead, we'll return a load error when we see that we're trying to bundle from // node_modules, then explicitly declare which external dependencies we rely on, which // ensures that the correct module specifier is kept in the output (the non-wildcard // form works properly). It also helps us keep tabs on what external dependencies we // may be importing, which is handy. // // See: https://github.com/evanw/esbuild/issues/1958 return { errors: [{ text: 'Attempted to bundle from node_modules; ensure "external" is set correctly.' }] }; }); } }, { name: "fix-require", setup: (build) => { build.onEnd(async () => { // esbuild converts calls to "require" to "__require"; this function // calls the real require if it exists, or throws if it does not (rather than // throwing an error like "require not defined"). But, since we want typescript // to be consumable by other bundlers, we need to convert these calls back to // require so our imports are visible again. // // The leading spaces are to keep the offsets the same within the files to keep // source maps working (though this only really matters for the line the require is on). // // See: https://github.com/evanw/esbuild/issues/1905 let contents = await fs.promises.readFile(outfile, "utf-8"); contents = contents.replace(/__require\(/g, " require("); await fs.promises.writeFile(outfile, contents); }); }, } ] }; if (taskOptions.exportIsTsObject) { // Monaco bundles us as ESM by wrapping our code with something that defines module.exports // but then does not use it, instead using the `ts` variable. Ensure that if we think we're CJS // that we still set `ts` to the module.exports object. options.footer = { js: `})(typeof module !== "undefined" && module.exports ? module : { exports: ts });\nif (typeof module !== "undefined" && module.exports) { ts = module.exports; }` }; // esbuild converts calls to "require" to "__require"; this function // calls the real require if it exists, or throws if it does not (rather than // throwing an error like "require not defined"). But, since we want typescript // to be consumable by other bundlers, we need to convert these calls back to // require so our imports are visible again. // // To fix this, we redefine "require" to a name we're unlikely to use with the // same length as "require", then replace it back to "require" after bundling, // ensuring that source maps still work. // // See: https://github.com/evanw/esbuild/issues/1905 const require = "require"; const fakeName = "Q".repeat(require.length); const fakeNameRegExp = new RegExp(fakeName, "g"); options.define = { [require]: fakeName }; // For historical reasons, TypeScript does not set __esModule. Hack esbuild's __toCommonJS to be a noop. // We reference `__copyProps` to ensure the final bundle doesn't have any unreferenced code. const toCommonJsRegExp = /var __toCommonJS .*/; const toCommonJsRegExpReplacement = "var __toCommonJS = (mod) => (__copyProps, mod); // Modified helper to skip setting __esModule."; options.plugins = [ { name: "post-process", setup: build => { build.onEnd(async () => { let contents = await fs.promises.readFile(outfile, "utf-8"); contents = contents.replace(fakeNameRegExp, require); let matches = 0; contents = contents.replace(toCommonJsRegExp, () => { matches++; return toCommonJsRegExpReplacement; }); assert(matches === 1, "Expected exactly one match for __toCommonJS"); await fs.promises.writeFile(outfile, contents); }); }, }, ]; } return options; }); return { build: async () => esbuild.build(await getOptions()), watch: async () => esbuild.build({ ...await getOptions(), watch: true, logLevel: "info" }), }; } let printedWatchWarning = false; /** * @param {object} options * @param {string} options.name * @param {string} [options.description] * @param {Task[]} [options.buildDeps] * @param {string} options.project * @param {string} options.srcEntrypoint * @param {string} options.builtEntrypoint * @param {string} options.output * @param {Task[]} [options.mainDeps] * @param {BundlerTaskOptions} [options.bundlerOptions] */ function entrypointBuildTask(options) { const build = task({ name: `build-${options.name}`, dependencies: options.buildDeps, run: () => buildProject(options.project), }); const bundler = createBundler(options.srcEntrypoint, options.output, options.bundlerOptions); // If we ever need to bundle our own output, change this to depend on build // and run esbuild on builtEntrypoint. const bundle = task({ name: `bundle-${options.name}`, dependencies: options.buildDeps, run: () => bundler.build(), }); /** * Writes a CJS module that reexports another CJS file. E.g. given * `options.builtEntrypoint = "./built/local/tsc/tsc.js"` and * `options.output = "./built/local/tsc.js"`, this will create a file * named "./built/local/tsc.js" containing: * * ``` * module.exports = require("./tsc/tsc.js") * ``` */ const shim = task({ name: `shim-${options.name}`, run: async () => { const outDir = path.dirname(options.output); await fs.promises.mkdir(outDir, { recursive: true }); const moduleSpecifier = path.relative(outDir, options.builtEntrypoint); await fs.promises.writeFile(options.output, `module.exports = require("./${moduleSpecifier}")`); }, }); const main = task({ name: options.name, description: options.description, dependencies: (options.mainDeps ?? []).concat(cmdLineOptions.bundle ? [bundle] : [build, shim]), }); const watch = task({ name: `watch-${options.name}`, hiddenFromTaskList: true, // This is best effort. dependencies: (options.buildDeps ?? []).concat(options.mainDeps ?? []).concat(cmdLineOptions.bundle ? [] : [shim]), run: () => { // These watch functions return promises that resolve once watch mode has started, // allowing them to operate as regular tasks, while creating unresolved promises // in the background that keep the process running after all tasks have exited. if (!printedWatchWarning) { console.error(chalk.yellowBright("Warning: watch mode is incomplete and may not work as expected. Use at your own risk.")); printedWatchWarning = true; } if (!cmdLineOptions.bundle) { return watchProject(options.project); } return bundler.watch(); } }); return { build, bundle, shim, main, watch }; } const { main: arkTsLinter } = entrypointBuildTask({ name: "linter", buildDeps: [generateDiagnostics], project: "src/linter", srcEntrypoint: "./src/linter/linter.ts", builtEntrypoint: "./built/local/linter/linter.js", output: "./built/local/linter.js", bundlerOptions: { // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, // These are directly imported via import statements and should not be bundled. external: [ "json5", ], }, }); const { main: tsc, watch: watchTsc } = entrypointBuildTask({ name: "tsc", description: "Builds the command-line compiler", buildDeps: [generateDiagnostics], project: "src/tsc", srcEntrypoint: "./src/tsc/tsc.ts", builtEntrypoint: "./built/local/tsc/tsc.js", output: "./built/local/tsc.js", mainDeps: [generateLibs, arkTsLinter], bundlerOptions: { // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, // These are directly imported via import statements and should not be bundled. external: [ "json5", ], }, }); export { tsc, watchTsc }; const { main: services, build: buildServices, watch: watchServices } = entrypointBuildTask({ name: "services", description: "Builds the typescript.js library", buildDeps: [generateDiagnostics], project: "src/typescript", srcEntrypoint: "./src/typescript/typescript.ts", builtEntrypoint: "./built/local/typescript/typescript.js", output: "./built/local/typescript.js", mainDeps: [generateLibs, arkTsLinter], bundlerOptions: { exportIsTsObject: true, // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, // These are directly imported via import statements and should not be bundled. external: [ "json5", ], }, }); export { services, watchServices }; export const dtsServices = task({ name: "dts-services", description: "Bundles typescript.d.ts", dependencies: [buildServices], run: async () => { if (needsUpdate("./built/local/typescript/tsconfig.tsbuildinfo", ["./built/local/typescript.d.ts", "./built/local/typescript.internal.d.ts"])) { runDtsBundler("./built/local/typescript/typescript.d.ts", "./built/local/typescript.d.ts"); } }, }); const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({ name: "tsserver", description: "Builds the language server", buildDeps: [generateDiagnostics], project: "src/tsserver", srcEntrypoint: "./src/tsserver/server.ts", builtEntrypoint: "./built/local/tsserver/server.js", output: "./built/local/tsserver.js", mainDeps: [generateLibs, arkTsLinter], // Even though this seems like an exectuable, so could be the default CJS, // this is used in the browser too. Do the same thing that we do for our // libraries and generate an IIFE with name `ts`, as to not pollute the global // scope. bundlerOptions: { exportIsTsObject: true, external: [ "json5", ], }, }); export { tsserver, watchTsserver }; export const min = task({ name: "min", description: "Builds only tsc and tsserver", dependencies: [tsc, tsserver], }); export const watchMin = task({ name: "watch-min", description: "Watches only tsc and tsserver", hiddenFromTaskList: true, dependencies: [watchTsc, watchTsserver], }); const { main: lssl, build: buildLssl, watch: watchLssl } = entrypointBuildTask({ name: "lssl", description: "Builds language service server library", buildDeps: [generateDiagnostics], project: "src/tsserverlibrary", srcEntrypoint: "./src/tsserverlibrary/tsserverlibrary.ts", builtEntrypoint: "./built/local/tsserverlibrary/tsserverlibrary.js", output: "./built/local/tsserverlibrary.js", mainDeps: [generateLibs, arkTsLinter], bundlerOptions: { exportIsTsObject: true, external: [ "json5", ], }, }); export { lssl, watchLssl }; export const dtsLssl = task({ name: "dts-lssl", description: "Bundles tsserverlibrary.d.ts", dependencies: [buildLssl], run: async () => { if (needsUpdate("./built/local/tsserverlibrary/tsconfig.tsbuildinfo", ["./built/local/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.internal.d.ts"])) { await runDtsBundler("./built/local/tsserverlibrary/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.d.ts"); } } }); export const dts = task({ name: "dts", dependencies: [dtsServices, dtsLssl], }); const testRunner = "./built/local/run.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ohTestCasesGeneration = task({ name: "ohTestCasesGeneration", description: "Generate oh test cases", run: async () => { if (!fs.existsSync(path.join(__dirname, "tests/cases/fourslash/oh/")) || !fs.existsSync(path.join(__dirname, "tests/cases/compiler-oh/"))) { await exec(process.execPath, ["scripts/ohTestCasesGenerationScript.js", diagnosticMessagesJson]); } } }); const { main: tests, watch: watchTests } = entrypointBuildTask({ name: "tests", description: "Builds the test infrastructure", buildDeps: [generateDiagnostics], project: "src/testRunner", srcEntrypoint: "./src/testRunner/_namespaces/Harness.ts", builtEntrypoint: "./built/local/testRunner/runner.js", output: testRunner, mainDeps: [generateLibs, arkTsLinter, ohTestCasesGeneration], bundlerOptions: { // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, // These are directly imported via import statements and should not be bundled. external: [ "chai", "del", "diff", "mocha", "ms", "json5", ], }, }); export { tests, watchTests }; export const runEslintRulesTests = task({ name: "run-eslint-rules-tests", description: "Runs the eslint rule tests", run: () => runConsoleTests("scripts/eslint/tests", "mocha-fivemat-progress-reporter", /*runInParallel*/ false), }); export const lint = task({ name: "lint", description: "Runs eslint on the compiler and scripts sources.", run: async () => { const folder = "."; const formatter = cmdLineOptions.ci ? "stylish" : "autolinkable-stylish"; const args = [ "node_modules/eslint/bin/eslint", "--cache", "--cache-location", `${folder}/.eslintcache`, "--format", formatter, ]; if (cmdLineOptions.fix) { args.push("--fix"); } args.push(folder); console.log(`Linting: ${args.join(" ")}`); return exec(process.execPath, args); } }); const { main: cancellationToken, watch: watchCancellationToken } = entrypointBuildTask({ name: "cancellation-token", project: "src/cancellationToken", srcEntrypoint: "./src/cancellationToken/cancellationToken.ts", builtEntrypoint: "./built/local/cancellationToken/cancellationToken.js", output: "./built/local/cancellationToken.js", }); const { main: typingsInstaller, watch: watchTypingsInstaller } = entrypointBuildTask({ name: "typings-installer", buildDeps: [generateDiagnostics], project: "src/typingsInstaller", srcEntrypoint: "./src/typingsInstaller/nodeTypingsInstaller.ts", builtEntrypoint: "./built/local/typingsInstaller/nodeTypingsInstaller.js", output: "./built/local/typingsInstaller.js", bundlerOptions: { external: [ "json5", ], }, }); const { main: watchGuard, watch: watchWatchGuard } = entrypointBuildTask({ name: "watch-guard", project: "src/watchGuard", srcEntrypoint: "./src/watchGuard/watchGuard.ts", builtEntrypoint: "./built/local/watchGuard/watchGuard.js", output: "./built/local/watchGuard.js", }); export const generateTypesMap = task({ name: "generate-types-map", run: async () => { const source = "src/server/typesMap.json"; const target = "built/local/typesMap.json"; const contents = await fs.promises.readFile(source, "utf-8"); JSON.parse(contents); // Validates that the JSON parses. await fs.promises.writeFile(target, contents); } }); // Drop a copy of diagnosticMessages.generated.json into the built/local folder. This allows // it to be synced to the Azure DevOps repo, so that it can get picked up by the build // pipeline that generates the localization artifacts that are then fed into the translation process. const builtLocalDiagnosticMessagesGeneratedJson = "built/local/diagnosticMessages.generated.json"; const copyBuiltLocalDiagnosticMessages = task({ name: "copy-built-local-diagnostic-messages", dependencies: [generateDiagnostics], run: async () => { const contents = await fs.promises.readFile(diagnosticMessagesGeneratedJson, "utf-8"); JSON.parse(contents); // Validates that the JSON parses. await fs.promises.writeFile(builtLocalDiagnosticMessagesGeneratedJson, contents); } }); export const otherOutputs = task({ name: "other-outputs", description: "Builds miscelaneous scripts and documents distributed with the LKG", dependencies: [cancellationToken, typingsInstaller, watchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages], }); export const watchOtherOutputs = task({ name: "watch-other-outputs", description: "Builds miscelaneous scripts and documents distributed with the LKG", hiddenFromTaskList: true, dependencies: [watchCancellationToken, watchTypingsInstaller, watchWatchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages], }); export const local = task({ name: "local", description: "Builds the full compiler and services", dependencies: [localize, tsc, tsserver, services, lssl, otherOutputs, dts], }); export default local; export const watchLocal = task({ name: "watch-local", description: "Watches the full compiler and services", hiddenFromTaskList: true, dependencies: [localize, watchTsc, watchTsserver, watchServices, watchLssl, watchOtherOutputs, dts], }); export const runTests = task({ name: "runtests", description: "Runs the tests using the built run.js file.", dependencies: [tests, generateLibs, dts], run: () => runConsoleTests(testRunner, "mocha-fivemat-progress-reporter", /*runInParallel*/ false), }); // task("runtests").flags = { // "-t --tests=": "Pattern for tests to run.", // " --failed": "Runs tests listed in '.failed-tests'.", // "-r --reporter=": "The mocha reporter to use.", // "-i --break": "Runs tests in inspector mode (NodeJS 8 and later)", // " --keepFailed": "Keep tests in .failed-tests even if they pass", // " --light": "Run tests in light mode (fewer verifications, but tests run faster)", // " --dirty": "Run tests without first cleaning test output directories", // " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", // " --no-color": "Disables color", // " --timeout=": "Overrides the default test timeout.", // " --built": "Compile using the built version of the compiler.", // " --shards": "Total number of shards running tests (default: 1)", // " --shardId": "1-based ID of this shard (default: 1)", // }; export const runTestsParallel = task({ name: "runtests-parallel", description: "Runs all the tests in parallel using the built run.js file.", dependencies: [tests, generateLibs, dts], run: () => runConsoleTests(testRunner, "min", /*runInParallel*/ cmdLineOptions.workers > 1), }); // task("runtests-parallel").flags = { // " --light": "Run tests in light mode (fewer verifications, but tests run faster).", // " --keepFailed": "Keep tests in .failed-tests even if they pass.", // " --dirty": "Run tests without first cleaning test output directories.", // " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", // " --workers=": "The number of parallel workers to use.", // " --timeout=": "Overrides the default test timeout.", // " --built": "Compile using the built version of the compiler.", // " --shards": "Total number of shards running tests (default: 1)", // " --shardId": "1-based ID of this shard (default: 1)", // }; export const testBrowserIntegration = task({ name: "test-browser-integration", description: "Runs scripts/browserIntegrationTest.mjs which tests that typescript.js loads in a browser", dependencies: [services], run: () => exec(process.execPath, ["scripts/browserIntegrationTest.mjs"]), }); export const diff = task({ name: "diff", description: "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable", run: () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true, waitForExit: false }), }); export const diffRwc = task({ name: "diff-rwc", description: "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable", run: () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true, waitForExit: false }), }); /** * @param {string} localBaseline Path to the local copy of the baselines * @param {string} refBaseline Path to the reference copy of the baselines */ function baselineAcceptTask(localBaseline, refBaseline) { /** * @param {string} p */ function localPathToRefPath(p) { const relative = path.relative(localBaseline, p); return path.join(refBaseline, relative); } return async () => { const toCopy = await glob(`${localBaseline}/**`, { nodir: true, ignore: `${localBaseline}/**/*.delete` }); for (const p of toCopy) { const out = localPathToRefPath(p); await fs.promises.mkdir(path.dirname(out), { recursive: true }); await fs.promises.copyFile(p, out); } const toDelete = await glob(`${localBaseline}/**/*.delete`, { nodir: true }); for (const p of toDelete) { const out = localPathToRefPath(p); await fs.promises.rm(out); } }; } export const baselineAccept = task({ name: "baseline-accept", description: "Makes the most recent test results the new baseline, overwriting the old baseline", run: baselineAcceptTask(localBaseline, refBaseline), }); export const baselineAcceptRwc = task({ name: "baseline-accept-rwc", description: "Makes the most recent rwc test results the new baseline, overwriting the old baseline", run: baselineAcceptTask(localRwcBaseline, refRwcBaseline), }); // TODO(rbuckton): Determine if we still need this task. Depending on a relative // path here seems like a bad idea. export const updateSublime = task({ name: "update-sublime", description: "Updates the sublime plugin's tsserver", dependencies: [tsserver], run: async () => { for (const file of ["built/local/tsserver.js", "built/local/tsserver.js.map"]) { await fs.promises.copyFile(file, path.resolve("../TypeScript-Sublime-Plugin/tsserver/", path.basename(file))); } } }); // TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? export const importDefinitelyTypedTests = task({ name: "importDefinitelyTypedTests", description: "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests", run: () => exec(process.execPath, ["scripts/importDefinitelyTypedTests.mjs", "./", "../DefinitelyTyped"]), }); export const produceLKG = task({ name: "LKG", description: "Makes a new LKG out of the built js files", dependencies: [localize, tsc, tsserver, services, lssl, otherOutputs, dts], run: async () => { if (!cmdLineOptions.bundle) { throw new Error("LKG cannot be created when --bundle=false"); } const expectedFiles = [ "built/local/cancellationToken.js", "built/local/tsc.js", "built/local/tsserver.js", "built/local/tsserverlibrary.js", "built/local/tsserverlibrary.d.ts", "built/local/typescript.js", "built/local/typescript.d.ts", "built/local/typingsInstaller.js", "built/local/watchGuard.js", ].concat(libs().map(lib => lib.target)); const missingFiles = expectedFiles .concat(localizationTargets) .filter(f => !fs.existsSync(f)); if (missingFiles.length > 0) { throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n")); } const sizeBefore = getDirSize("lib"); await exec(process.execPath, ["scripts/produceLKG.mjs"]); const sizeAfter = getDirSize("lib"); if (sizeAfter > (sizeBefore * 1.10)) { throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); } } }); export const lkg = task({ name: "lkg", hiddenFromTaskList: true, dependencies: [produceLKG], }); export const generateSpec = task({ name: "generate-spec", description: "Generates a Markdown version of the Language Specification", hiddenFromTaskList: true, run: () => exec("cscript", ["//nologo", "scripts/word2md.mjs", path.resolve("doc/TypeScript Language Specification - ARCHIVED.docx"), path.resolve("doc/spec-ARCHIVED.md")]), }); export const cleanBuilt = task({ name: "clean-built", hiddenFromTaskList: true, run: () => del("built"), }); export const clean = task({ name: "clean", description: "Cleans build outputs", dependencies: [cleanBuilt, cleanDiagnostics], }); export const configureNightly = task({ name: "configure-nightly", description: "Runs scripts/configurePrerelease.mjs to prepare a build for nightly publishing", run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "dev", "package.json", "src/compiler/corePublic.ts"]), }); export const configureInsiders = task({ name: "configure-insiders", description: "Runs scripts/configurePrerelease.mjs to prepare a build for insiders publishing", run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "insiders", "package.json", "src/compiler/corePublic.ts"]), }); export const configureExperimental = task({ name: "configure-experimental", description: "Runs scripts/configurePrerelease.mjs to prepare a build for experimental publishing", run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "experimental", "package.json", "src/compiler/corePublic.ts"]), }); export const help = task({ name: "help", description: "Prints the top-level tasks.", hiddenFromTaskList: true, run: () => exec("hereby", ["--tasks"], { hidePrompt: true }), });