// @ts-check import path from "path"; import fs from "fs"; import log from "fancy-log"; import newer from "gulp-newer"; import del from "del"; import rename from "gulp-rename"; import concat from "gulp-concat"; import merge2 from "merge2"; import gulp from "gulp"; import { transform } from "gulp-insert"; import { exec, readJson, needsUpdate, getDiffTool, getDirSize, rm } 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"; const { src, dest, task, parallel, series, watch } = gulp; const copyright = "CopyrightNotice.txt"; const cleanTasks = []; // TODO(jakebailey): This is really gross. Waiting on https://github.com/microsoft/TypeScript/issues/25613, // or at least control over noEmit / emitDeclarationOnly in build mode. let currentlyBuilding = 0; let oldTsconfigBase; /** @type {typeof realBuildProject} */ const buildProjectWithEmit = async (...args) => { const tsconfigBasePath = "./src/tsconfig-base.json"; 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; const buildScripts = () => buildProject("scripts"); task("scripts", buildScripts); task("scripts").description = "Builds files in the 'scripts' folder."; /** @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, relativeTarget, sources }; }); const generateLibs = () => { return merge2(libs.map(({ sources, target, relativeTarget }) => src([copyright, ...sources]) .pipe(newer(target)) .pipe(concat(relativeTarget, { newLine: "\n\n" })) .pipe(dest("built/local")))); }; task("lib", generateLibs); task("lib").description = "Builds the library targets"; const cleanLib = () => del(libs.map(lib => lib.target)); cleanTasks.push(cleanLib); const watchLib = () => watch(["src/lib/**/*"], generateLibs); const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; const generateDiagnostics = async () => { if (needsUpdate(diagnosticMessagesJson, [diagnosticMessagesGeneratedJson, diagnosticInformationMapTs])) { await exec(process.execPath, ["scripts/processDiagnosticMessages.mjs", diagnosticMessagesJson]); } }; task("generate-diagnostics", generateDiagnostics); task("generate-diagnostics").description = "Generates a diagnostic file in TypeScript based on an input JSON file"; const cleanDiagnostics = () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]); cleanTasks.push(cleanDiagnostics); const watchDiagnostics = () => watch(["src/compiler/diagnosticMessages.json"], task("generate-diagnostics")); // 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 = async () => { if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.mjs", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); } }; const preSrc = parallel(generateLibs, series(buildScripts, generateDiagnostics, localize)); const buildSrc = () => buildProject("src"); task("build-src", series(preSrc, buildSrc)); const cleanSrc = () => cleanProject("src"); task("clean-src", cleanSrc); /** * @param {string} entrypoint * @param {string} output */ async function runDtsBundler(entrypoint, output) { await exec(process.execPath, [ "./scripts/dtsBundler.mjs", "--entrypoint", entrypoint, "--output", output, ]); } /** @type {string | undefined} */ let copyrightHeader; function getCopyrightHeader() { if (copyrightHeader === undefined) { copyrightHeader = fs.readFileSync(copyright, "utf-8"); } return copyrightHeader; } /** * @param {string} entrypoint * @param {string} outfile * @param {boolean} exportIsTsObject True if this file exports the TS object and should have relevant code injected. */ function esbuildTask(entrypoint, outfile, exportIsTsObject = false) { // Note: we do not use --minify, as that would hide function names from user backtraces // (we don't ship our sourcemaps), and would break consumers like monaco which modify // typescript.js for their own needs. Also, using --sourcesContent=false doesn't help, // as even though it's a smaller source map that could be shipped to users for better // stack traces via names, the maps are bigger than the actual source files themselves. /** @type {esbuild.BuildOptions} */ const options = { entryPoints: [entrypoint], banner: { js: getCopyrightHeader() }, bundle: true, outfile, platform: "node", target: "es2018", format: "cjs", sourcemap: "linked", external: ["./node_modules/*"], conditions: ["require"], // legalComments: "none", // If we add copyright headers to the source files, uncomment. plugins: [ { 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. // // Note that this step breaks source maps, but only for lines that reference // "__require", which is an okay tradeoff for the performance of not running // the output through transpileModule/babel/etc. // // 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 (exportIsTsObject) { options.format = "iife"; // We use an IIFE so we can inject the code below. options.globalName = "ts"; // Name the variable ts, matching our old big bundle and so we can use the code below. options.footer = { // These snippets cannot appear in the actual source files, otherwise they will be rewritten // to things like exports or requires. js: ` if (typeof module !== "undefined" && module.exports) { // If we are in a CJS context, export the ts namespace. module.exports = ts; } if (ts.server) { // If we are in a server bundle, inject the dynamicImport function. ts.server.dynamicImport = id => import(id); }` }; } return { build: () => esbuild.build(options), clean: () => del([outfile, `${outfile}.map`]), watch: () => esbuild.build({ ...options, watch: true }), }; } /** * Writes a CJS module that reexports another CJS file via * `module.exports = require("...")`. * * @param {string} infile Relative path from the repo root to the file to be required. * @param {string} outfile Relative path from the repo root to the output file. */ function writeCJSReexport(infile, outfile) { const outDir = path.dirname(outfile); fs.mkdirSync(outDir, { recursive: true }); const inRelativeToOut = path.relative(outDir, infile); fs.writeFileSync(outfile, `module.exports = require("./${inRelativeToOut}")`); } const esbuildDebugTools = esbuildTask("./src/debug/compilerDebug.ts", "./built/local/compiler-debug.js"); const buildDebugTools = () => { if (cmdLineOptions.bundle) return esbuildDebugTools.build(); writeCJSReexport("./built/local/debug/compilerDebug.js", "./built/local/compiler-debug.js") return buildProject("src/debug") }; const cleanDebugTools = () => cmdLineOptions.bundle ? esbuildDebugTools.build() : cleanProject("src/debug"); cleanTasks.push(cleanDebugTools); // Pre-build steps when targeting the LKG compiler const lkgPreBuild = parallel(generateLibs, series(generateDiagnostics, buildDebugTools)); const esbuildTsc = esbuildTask("./src/tsc/tsc.ts", "./built/local/tsc.js", /* exportIsTsObject */ true); const writeTscCJSShim = () => writeCJSReexport("./built/local/tsc/tsc.js", "./built/local/tsc.js"); const buildTsc = () => { if (cmdLineOptions.bundle) return esbuildTsc.build(); writeTscCJSShim(); return buildProject("src/tsc"); }; task("tsc", series(lkgPreBuild, buildTsc)); task("tsc").description = "Builds the command-line compiler"; const cleanTsc = () => cmdLineOptions.bundle ? esbuildTsc.clean() : cleanProject("src/tsc"); cleanTasks.push(cleanTsc); task("clean-tsc", cleanTsc); task("clean-tsc").description = "Cleans outputs for the command-line compiler"; const watchTsc = () => { if (cmdLineOptions.bundle) return esbuildTsc.watch(); writeTscCJSShim(); return watchProject("src/tsc"); }; task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watchTsc))); task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only."; // Pre-build steps when targeting the built/local compiler. const localPreBuild = parallel(generateLibs, series(generateDiagnostics, buildDebugTools, buildTsc)); // Pre-build steps to use based on supplied options. const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild; const esbuildServices = esbuildTask("./src/typescript/typescript.ts", "./built/local/typescript.js", /* exportIsTsObject */ true); const writeServicesCJSShim = () => writeCJSReexport("./built/local/typescript/typescript.js", "./built/local/typescript.js"); const buildServicesProject = () => buildProject("src/typescript"); // TODO(jakebailey): rename this; no longer "services". const buildServices = () => { if (cmdLineOptions.bundle) return esbuildServices.build(); writeServicesCJSShim(); return buildServicesProject(); }; task("services", series(preBuild, buildServices)); task("services").description = "Builds the language service"; task("services").flags = { " --built": "Compile using the built version of the compiler." }; const cleanServices = () => cmdLineOptions.bundle ? esbuildServices.clean() : cleanProject("src/typescript"); cleanTasks.push(cleanServices); task("clean-services", cleanServices); task("clean-services").description = "Cleans outputs for the language service"; const watchServices = () => { if (cmdLineOptions.bundle) return esbuildServices.watch(); writeServicesCJSShim(); return watchProject("src/typescript"); }; task("watch-services", series(preBuild, parallel(watchLib, watchDiagnostics, watchServices))); task("watch-services").description = "Watches for changes and rebuild language service only"; task("watch-services").flags = { " --built": "Compile using the built version of the compiler." }; const dtsServices = () => runDtsBundler("./built/local/typescript/typescript.d.ts", "./built/local/typescript.d.ts"); task("dts-services", series(preBuild, buildServicesProject, dtsServices)); task("dts-services").description = "Builds typescript.d.ts"; const esbuildServer = esbuildTask("./src/tsserver/server.ts", "./built/local/tsserver.js", /* exportIsTsObject */ true); const writeServerCJSShim = () => writeCJSReexport("./built/local/tsserver/server.js", "./built/local/tsserver.js"); const buildServer = () => { if (cmdLineOptions.bundle) return esbuildServer.build(); writeServerCJSShim(); return buildProject("src/tsserver"); }; buildServer.displayName = "buildServer"; task("tsserver", series(preBuild, buildServer)); task("tsserver").description = "Builds the language server"; task("tsserver").flags = { " --built": "Compile using the built version of the compiler." }; const cleanServer = () => cmdLineOptions.bundle ? esbuildServer.clean() : cleanProject("src/tsserver"); cleanServer.displayName = "cleanServer"; cleanTasks.push(cleanServer); task("clean-tsserver", cleanServer); task("clean-tsserver").description = "Cleans outputs for the language server"; const watchServer = () => { if (cmdLineOptions.bundle) return esbuildServer.watch(); writeServerCJSShim(); return watchProject("src/tsserver"); }; task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchServer))); task("watch-tsserver").description = "Watch for changes and rebuild the language server only"; task("watch-tsserver").flags = { " --built": "Compile using the built version of the compiler." }; task("min", series(preBuild, parallel(buildTsc, buildServer))); task("min").description = "Builds only tsc and tsserver"; task("min").flags = { " --built": "Compile using the built version of the compiler." }; task("clean-min", series(cleanTsc, cleanServer)); task("clean-min").description = "Cleans outputs for tsc and tsserver"; task("watch-min", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServer))); task("watch-min").description = "Watches for changes to a tsc and tsserver only"; task("watch-min").flags = { " --built": "Compile using the built version of the compiler." }; const esbuildLssl = esbuildTask("./src/tsserverlibrary/tsserverlibrary.ts", "./built/local/tsserverlibrary.js", /* exportIsTsObject */ true); const writeLsslCJSShim = () => writeCJSReexport("./built/local/tsserverlibrary/tsserverlibrary.js", "./built/local/tsserverlibrary.js"); const buildLsslProject = () => buildProject("src/tsserverlibrary"); const buildLssl = () => { if (cmdLineOptions.bundle) return esbuildLssl.build(); writeLsslCJSShim(); return buildLsslProject(); }; task("lssl", series(preBuild, buildLssl)); task("lssl").description = "Builds language service server library"; task("lssl").flags = { " --built": "Compile using the built version of the compiler." }; const cleanLssl = () => cmdLineOptions.bundle ? esbuildLssl.clean() : cleanProject("src/tsserverlibrary"); cleanTasks.push(cleanLssl); task("clean-lssl", cleanLssl); task("clean-lssl").description = "Clean outputs for the language service server library"; const watchLssl = () => { if (cmdLineOptions.bundle) return esbuildLssl.watch(); writeLsslCJSShim(); return watchProject("src/tsserverlibrary"); }; task("watch-lssl", series(preBuild, parallel(watchLib, watchDiagnostics, watchLssl))); task("watch-lssl").description = "Watch for changes and rebuild tsserverlibrary only"; task("watch-lssl").flags = { " --built": "Compile using the built version of the compiler." }; const dtsLssl = () => runDtsBundler("./built/local/tsserverlibrary/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.d.ts"); task("dts-lssl", series(preBuild, buildLsslProject, dtsLssl)); task("dts-lssl").description = "Builds tsserverlibrary.d.ts"; // TODO(jakebailey): this is probably not efficient, but, gulp. const dts = series(preBuild, parallel(buildServicesProject, buildLsslProject), parallel(dtsServices, dtsLssl)); task("dts", dts); const testRunner = "./built/local/run.js"; const esbuildTests = esbuildTask("./src/testRunner/_namespaces/Harness.ts", testRunner); const writeTestsCJSShim = () => writeCJSReexport("./built/local/testRunner/runner.js", testRunner); const buildTests = () => { if (cmdLineOptions.bundle) return esbuildTests.build(); writeTestsCJSShim(); return buildProject("src/testRunner"); }; task("tests", series(preBuild, parallel(buildLssl, buildTests))); task("tests").description = "Builds the test infrastructure"; task("tests").flags = { " --built": "Compile using the built version of the compiler." }; const cleanTests = () => cmdLineOptions.bundle ? esbuildTests.clean() : cleanProject("src/testRunner"); cleanTasks.push(cleanTests); task("clean-tests", cleanTests); task("clean-tests").description = "Cleans the outputs for the test infrastructure"; const watchTests = () => { if (cmdLineOptions.bundle) return esbuildTests.watch(); writeTestsCJSShim(); return watchProject("src/testRunner"); }; const runEslintRulesTests = () => runConsoleTests("scripts/eslint/tests", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); task("run-eslint-rules-tests", runEslintRulesTests); task("run-eslint-rules-tests").description = "Runs the eslint rule tests"; /** @type { (folder: string) => { (): Promise; displayName?: string } } */ const eslint = (folder) => async () => { 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); log(`Linting: ${args.join(" ")}`); return exec(process.execPath, args); }; const lint = eslint("."); lint.displayName = "lint"; task("lint", lint); task("lint").description = "Runs eslint on the compiler and scripts sources."; const esbuildCancellationToken = esbuildTask("./src/cancellationToken/cancellationToken.ts", "./built/local/cancellationToken.js"); const buildCancellationToken = () => { if (cmdLineOptions.bundle) return esbuildCancellationToken.build() writeCJSReexport("./built/local/cancellationToken/cancellationToken.js", "./built/local/cancellationToken.js"); return buildProject("src/cancellationToken") }; const cleanCancellationToken = () => cmdLineOptions.bundle ? esbuildCancellationToken.clean() : cleanProject("src/cancellationToken"); cleanTasks.push(cleanCancellationToken); const esbuildTypingsInstaller = esbuildTask("./src/typingsInstaller/nodeTypingsInstaller.ts", "./built/local/typingsInstaller.js"); const buildTypingsInstaller = () => { if (cmdLineOptions.bundle) return esbuildTypingsInstaller.build(); writeCJSReexport("./built/local/typingsInstaller/nodeTypingsInstaller.js", "./built/local/typingsInstaller.js"); return buildProject("src/typingsInstaller"); }; const cleanTypingsInstaller = () => cmdLineOptions.bundle ? esbuildTypingsInstaller.clean() : cleanProject("src/typingsInstaller"); cleanTasks.push(cleanTypingsInstaller); const esbuildWatchGuard = esbuildTask("./src/watchGuard/watchGuard.ts", "./built/local/watchGuard.js"); const buildWatchGuard = () => { if (cmdLineOptions.bundle) return esbuildWatchGuard.build(); writeCJSReexport("./built/local/watchGuard/watchGuard.js", "./built/local/watchGuard.js"); return buildProject("src/watchGuard"); }; const cleanWatchGuard = () => cmdLineOptions.bundle ? esbuildWatchGuard.clean() : cleanProject("src/watchGuard"); cleanTasks.push(cleanWatchGuard); const generateTypesMap = () => src("src/server/typesMap.json") .pipe(newer("built/local/typesMap.json")) .pipe(transform(contents => (JSON.parse(contents), contents))) // validates typesMap.json is valid JSON .pipe(dest("built/local")); task("generate-types-map", generateTypesMap); const cleanTypesMap = () => del("built/local/typesMap.json"); cleanTasks.push(cleanTypesMap); // 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 = () => src(diagnosticMessagesGeneratedJson) .pipe(newer(builtLocalDiagnosticMessagesGeneratedJson)) .pipe(dest("built/local")); const cleanBuiltLocalDiagnosticMessages = () => del(builtLocalDiagnosticMessagesGeneratedJson); cleanTasks.push(cleanBuiltLocalDiagnosticMessages); const buildOtherOutputs = parallel(buildCancellationToken, buildTypingsInstaller, buildWatchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages); task("other-outputs", series(preBuild, buildOtherOutputs)); task("other-outputs").description = "Builds miscelaneous scripts and documents distributed with the LKG"; task("local", series(preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, dts))); task("local").description = "Builds the full compiler and services"; task("local").flags = { " --built": "Compile using the built version of the compiler." }; task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl))); task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests)."; task("watch-local").flags = { " --built": "Compile using the built version of the compiler." }; const preTest = parallel(buildTsc, buildTests, buildServices, buildLssl); preTest.displayName = "preTest"; const runTests = () => runConsoleTests(testRunner, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); task("runtests", series(preBuild, preTest, dts, runTests)); task("runtests").description = "Runs the tests using the built run.js file."; 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)", }; const runTestsParallel = () => runConsoleTests(testRunner, "min", /*runInParallel*/ cmdLineOptions.workers > 1, /*watchMode*/ false); task("runtests-parallel", series(preBuild, preTest, dts, runTestsParallel)); task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; 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)", }; task("test-browser-integration", () => exec(process.execPath, ["scripts/browserIntegrationTest.mjs"])); task("test-browser-integration").description = "Runs scripts/browserIntegrationTest.mjs which tests that typescript.js loads in a browser"; task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true, waitForExit: false })); task("diff").description = "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable"; task("diff-rwc", () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true, waitForExit: false })); task("diff-rwc").description = "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable"; /** * @param {string} localBaseline Path to the local copy of the baselines * @param {string} refBaseline Path to the reference copy of the baselines */ const baselineAccept = (localBaseline, refBaseline) => merge2( src([`${localBaseline}/**`, `!${localBaseline}/**/*.delete`], { base: localBaseline }) .pipe(dest(refBaseline)), src([`${localBaseline}/**/*.delete`], { base: localBaseline, read: false }) .pipe(rm()) .pipe(rename({ extname: "" })) .pipe(rm(refBaseline))); task("baseline-accept", () => baselineAccept(localBaseline, refBaseline)); task("baseline-accept").description = "Makes the most recent test results the new baseline, overwriting the old baseline"; task("baseline-accept-rwc", () => baselineAccept(localRwcBaseline, refRwcBaseline)); task("baseline-accept-rwc").description = "Makes the most recent rwc test results the new baseline, overwriting the old baseline"; // TODO(rbuckton): Determine if we still need this task. Depending on a relative // path here seems like a bad idea. const updateSublime = () => src(["built/local/tsserver.js", "built/local/tsserver.js.map"]) .pipe(dest("../TypeScript-Sublime-Plugin/tsserver/")); task("update-sublime", updateSublime); task("update-sublime").description = "Updates the sublime plugin's tsserver"; // TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? const importDefinitelyTypedTests = () => exec(process.execPath, ["scripts/importDefinitelyTypedTests.mjs", "./", "../DefinitelyTyped"]); task("importDefinitelyTypedTests", importDefinitelyTypedTests); task("importDefinitelyTypedTests").description = "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests"; const cleanBuilt = () => del("built"); const produceLKG = 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."); } }; task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, dts), produceLKG)); task("LKG").description = "Makes a new LKG out of the built js files"; task("LKG").flags = { " --built": "Compile using the built version of the compiler.", }; task("lkg", series("LKG")); const generateSpec = () => exec("cscript", ["//nologo", "scripts/word2md.mjs", path.resolve("doc/TypeScript Language Specification - ARCHIVED.docx"), path.resolve("doc/spec-ARCHIVED.md")]); task("generate-spec", generateSpec); task("generate-spec").description = "Generates a Markdown version of the Language Specification"; task("clean", series(parallel(cleanTasks), cleanBuilt)); task("clean").description = "Cleans build outputs"; const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "dev", "package.json", "src/compiler/corePublic.ts"]); task("configure-nightly", series(buildScripts, configureNightly)); task("configure-nightly").description = "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing"; const configureInsiders = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "insiders", "package.json", "src/compiler/corePublic.ts"]); task("configure-insiders", configureInsiders); task("configure-insiders").description = "Runs scripts/configurePrerelease.mjs to prepare a build for insiders publishing"; const configureExperimental = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "experimental", "package.json", "src/compiler/corePublic.ts"]); task("configure-experimental", configureExperimental); task("configure-experimental").description = "Runs scripts/configurePrerelease.mjs to prepare a build for experimental publishing"; const publishNightly = () => exec("npm", ["publish", "--tag", "next"]); task("publish-nightly", series(task("clean"), task("LKG"), task("clean"), task("runtests-parallel"), publishNightly)); task("publish-nightly").description = "Runs `npm publish --tag next` to create a new nightly build on npm"; // TODO(rbuckton): The problem with watching in this way is that a change in compiler/ will result // in cascading changes in other projects that may take differing amounts of times to complete. As // a result, the watch may accidentally trigger early, so we have to set a significant delay. An // alternative approach would be to leverage a builder API, or to have 'tsc -b' have an option to // write some kind of trigger file that indicates build completion that we could listen for instead. const watchRuntests = () => watch(["built/local/*.js", "tests/cases/**/*.ts", "tests/cases/**/tsconfig.json"], { delay: 5000 }, async () => { if (cmdLineOptions.tests || cmdLineOptions.failed) { await runConsoleTests(testRunner, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true); } else { await runConsoleTests(testRunner, "min", /*runInParallel*/ true, /*watchMode*/ true); } }); task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchLssl, watchTests, watchRuntests))); task("watch").description = "Watches for changes and rebuilds and runs tests in parallel."; task("watch").flags = { "-t --tests=": "Pattern for tests to run. Forces tests to be run in a single worker.", " --failed": "Runs tests listed in '.failed-tests'. Forces tests to be run in a single worker.", "-r --reporter=": "The mocha reporter to use.", " --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.", " --workers=": "The number of parallel workers to use.", " --built": "Compile using the built version of the compiler.", }; task("default", series("local")); task("default").description = "Runs 'local'"; task("help", () => exec("gulp", ["--tasks", "--depth", "1", "--sort-tasks"], { hidePrompt: true })); task("help").description = "Prints the top-level tasks.";