2018-04-18 00:19:42 +03:00
// @ts-check
2023-02-03 01:36:21 +03:00
import {
CancelToken ,
} from "@esfx/canceltoken" ;
import chalk from "chalk" ;
import chokidar from "chokidar" ;
import esbuild from "esbuild" ;
import {
EventEmitter ,
} from "events" ;
import fs from "fs" ;
2022-10-09 23:15:45 +03:00
import _glob from "glob" ;
2023-02-03 01:36:21 +03:00
import {
task ,
} from "hereby" ;
import path from "path" ;
2022-10-09 23:15:45 +03:00
import util from "util" ;
2023-02-03 01:36:21 +03:00
2022-09-14 02:21:03 +03:00
import {
localizationDirectories ,
} from "./scripts/build/localization.mjs" ;
2022-10-07 19:50:46 +03:00
import cmdLineOptions from "./scripts/build/options.mjs" ;
2023-02-03 01:36:21 +03:00
import {
buildProject ,
cleanProject ,
watchProject ,
} from "./scripts/build/projects.mjs" ;
2023-08-08 00:35:15 +03:00
import {
localBaseline ,
refBaseline ,
runConsoleTests ,
} from "./scripts/build/tests.mjs" ;
2023-03-14 19:23:51 +03:00
import {
Debouncer ,
Deferred ,
exec ,
getDiffTool ,
memoize ,
needsUpdate ,
readJson ,
2023-08-24 02:42:37 +03:00
rimraf ,
2023-03-14 19:23:51 +03:00
} from "./scripts/build/utils.mjs" ;
2022-10-07 19:50:46 +03:00
2022-10-09 23:15:45 +03:00
const glob = util . promisify ( _glob ) ;
2016-06-10 03:44:59 +03:00
2022-10-09 23:15:45 +03:00
/** @typedef {ReturnType<typeof task>} Task */
void 0 ;
2022-12-07 00:42:54 +03:00
const copyrightFilename = "./scripts/CopyrightNotice.txt" ;
2022-10-09 23:15:45 +03:00
const copyright = memoize ( async ( ) => {
const contents = await fs . promises . readFile ( copyrightFilename , "utf-8" ) ;
return contents . replace ( /\r\n/g , "\n" ) ;
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const buildScripts = task ( {
name : "scripts" ,
description : "Builds files in the 'scripts' folder." ,
run : ( ) => buildProject ( "scripts" ) ,
} ) ;
2020-03-17 22:10:55 +03:00
2022-10-09 23:15:45 +03:00
const libs = memoize ( ( ) => {
/** @type {{ libs: string[]; paths: Record<string, string | undefined>; }} */
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 ;
2019-01-28 08:56:56 +03:00
} ) ;
2022-10-09 23:15:45 +03:00
export const generateLibs = task ( {
name : "lib" ,
description : "Builds the library targets" ,
run : async ( ) => {
await fs . promises . mkdir ( "./built/local" , { recursive : true } ) ;
2022-10-10 05:37:09 +03:00
for ( const lib of libs ( ) ) {
let output = await copyright ( ) ;
for ( const source of lib . sources ) {
const contents = await fs . promises . readFile ( source , "utf-8" ) ;
2023-02-01 20:33:01 +03:00
output += "\n" + contents . replace ( /\r\n/g , "\n" ) ;
2022-10-09 23:15:45 +03:00
}
2022-10-10 05:37:09 +03:00
await fs . promises . writeFile ( lib . target , output ) ;
2022-10-09 23:15:45 +03:00
}
} ,
} ) ;
2019-01-28 08:56:56 +03:00
2018-06-19 08:45:13 +03:00
const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts" ;
const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json" ;
const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json" ;
2022-10-09 23:15:45 +03:00
export const generateDiagnostics = task ( {
name : "generate-diagnostics" ,
description : "Generates a diagnostic file in TypeScript based on an input JSON file" ,
run : async ( ) => {
2022-10-10 05:37:09 +03:00
await exec ( process . execPath , [ "scripts/processDiagnosticMessages.mjs" , diagnosticMessagesJson ] ) ;
2018-06-19 08:45:13 +03:00
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2018-06-19 08:45:13 +03:00
2022-10-09 23:15:45 +03:00
const cleanDiagnostics = task ( {
name : "clean-diagnostics" ,
description : "Generates a diagnostic file in TypeScript based on an input JSON file" ,
hiddenFromTaskList : true ,
2023-08-24 02:42:37 +03:00
run : async ( ) => {
await rimraf ( diagnosticInformationMapTs ) ;
await rimraf ( diagnosticMessagesGeneratedJson ) ;
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2016-06-10 03:44:59 +03:00
2018-06-19 08:45:13 +03:00
// Localize diagnostics
2017-10-19 01:46:09 +03:00
/ * *
* . lcg file is what localization team uses to know what messages to localize .
2018-06-19 08:45:13 +03:00
* The file is always generated in 'enu/diagnosticMessages.generated.json.lcg'
2017-10-19 01:46:09 +03:00
* /
2018-06-19 08:45:13 +03:00
const generatedLCGFile = "built/local/enu/diagnosticMessages.generated.json.lcg" ;
2017-10-19 01:46:09 +03:00
/ * *
* The localization target produces the two following transformations :
* 1. 'src\loc\lcl\<locale>\diagnosticMessages.generated.json.lcl' => 'built\local\<locale>\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
* /
2022-09-14 02:21:03 +03:00
const localizationTargets = localizationDirectories
2018-06-19 08:45:13 +03:00
. map ( f => ` built/local/ ${ f } /diagnosticMessages.generated.json ` )
2017-11-04 01:08:50 +03:00
. concat ( generatedLCGFile ) ;
2017-10-19 01:46:09 +03:00
2022-10-09 23:15:45 +03:00
const localize = task ( {
name : "localize" ,
dependencies : [ generateDiagnostics ] ,
run : async ( ) => {
if ( needsUpdate ( diagnosticMessagesGeneratedJson , generatedLCGFile ) ) {
2022-11-09 19:00:58 +03:00
await exec ( process . execPath , [ "scripts/generateLocalizedDiagnosticMessages.mjs" , "src/loc/lcl" , "built/local" , diagnosticMessagesGeneratedJson ] , { ignoreExitCode : true } ) ;
2022-10-09 23:15:45 +03:00
}
2017-10-03 03:16:08 +03:00
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const buildSrc = task ( {
name : "build-src" ,
description : "Builds the src project (all code)" ,
dependencies : [ generateDiagnostics ] ,
run : ( ) => buildProject ( "src" ) ,
} ) ;
2022-05-31 21:00:25 +03:00
2022-10-09 23:15:45 +03:00
export const watchSrc = task ( {
name : "watch-src" ,
description : "Watches the src project (all code)" ,
hiddenFromTaskList : true ,
dependencies : [ generateDiagnostics ] ,
run : ( ) => watchProject ( "src" ) ,
} ) ;
2022-05-31 21:00:25 +03:00
2022-10-09 23:15:45 +03:00
export const cleanSrc = task ( {
name : "clean-src" ,
hiddenFromTaskList : true ,
run : ( ) => cleanProject ( "src" ) ,
} ) ;
2022-05-31 21:00:25 +03:00
2022-09-03 02:39:54 +03:00
/ * *
* @ param { string } entrypoint
* @ param { string } output
* /
async function runDtsBundler ( entrypoint , output ) {
await exec ( process . execPath , [
"./scripts/dtsBundler.mjs" ,
"--entrypoint" ,
entrypoint ,
"--output" ,
output ,
] ) ;
}
2022-09-14 02:21:03 +03:00
/ * *
* @ param { string } entrypoint
* @ param { string } outfile
2022-10-09 23:15:45 +03:00
* @ param { BundlerTaskOptions } [ taskOptions ]
*
* @ typedef BundlerTaskOptions
* @ property { boolean } [ exportIsTsObject ]
* @ property { boolean } [ treeShaking ]
2023-01-18 21:51:53 +03:00
* @ property { ( ) => void } [ onWatchRebuild ]
2022-09-14 02:21:03 +03:00
* /
2022-10-09 23:15:45 +03:00
function createBundler ( entrypoint , outfile , taskOptions = { } ) {
const getOptions = memoize ( async ( ) => {
/** @type {esbuild.BuildOptions} */
const options = {
entryPoints : [ entrypoint ] ,
banner : { js : await copyright ( ) } ,
bundle : true ,
outfile ,
platform : "node" ,
2023-04-15 05:36:13 +03:00
target : [ "es2020" , "node14.17" ] ,
2022-10-09 23:15:45 +03:00
format : "cjs" ,
sourcemap : "linked" ,
sourcesContent : false ,
treeShaking : taskOptions . treeShaking ,
2022-12-13 23:47:36 +03:00
packages : "external" ,
2022-10-09 23:15:45 +03:00
logLevel : "warning" ,
// legalComments: "none", // If we add copyright headers to the source files, uncomment.
} ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
if ( taskOptions . exportIsTsObject ) {
2022-11-07 05:43:25 +03:00
// We use an IIFE so we can inject the footer, and so that "ts" is global if not loaded as a module.
options . format = "iife" ;
// Name the variable ts, matching our old big bundle and so we can use the code below.
options . globalName = "ts" ;
2022-10-09 23:15:45 +03:00
// If we are in a CJS context, export the ts namespace.
2022-11-07 05:43:25 +03:00
options . footer = { js : ` \n if (typeof module !== "undefined" && module.exports) { module.exports = ts; } ` } ;
2023-01-31 00:15:34 +03:00
// 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
options . define = { require : "$$require" } ;
options . plugins = [
{
name : "fix-require" ,
setup : build => {
build . onEnd ( async ( ) => {
let contents = await fs . promises . readFile ( outfile , "utf-8" ) ;
contents = contents . replace ( /\$\$require/g , " require" ) ;
await fs . promises . writeFile ( outfile , contents ) ;
} ) ;
} ,
} ,
] ;
2022-10-09 23:15:45 +03:00
}
2017-10-03 03:16:08 +03:00
2022-10-09 23:15:45 +03:00
return options ;
} ) ;
2016-06-10 03:44:59 +03:00
2022-10-09 23:15:45 +03:00
return {
build : async ( ) => esbuild . build ( await getOptions ( ) ) ,
2023-01-18 21:51:53 +03:00
watch : async ( ) => {
/** @type {esbuild.BuildOptions} */
const options = { ... await getOptions ( ) , logLevel : "info" } ;
if ( taskOptions . onWatchRebuild ) {
const onRebuild = taskOptions . onWatchRebuild ;
options . plugins = ( options . plugins ? . slice ( 0 ) ? ? [ ] ) . concat ( [ {
name : "watch" ,
setup : build => {
let firstBuild = true ;
build . onEnd ( ( ) => {
if ( firstBuild ) {
firstBuild = false ;
}
else {
onRebuild ( ) ;
}
} ) ;
} ,
} ] ) ;
}
const ctx = await esbuild . context ( options ) ;
ctx . watch ( ) ;
} ,
2022-10-09 23:15:45 +03:00
} ;
}
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
let printedWatchWarning = false ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
/ * *
* @ 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 ) ;
2022-11-09 02:20:22 +03:00
await fs . promises . writeFile ( options . output , ` module.exports = require("./ ${ moduleSpecifier . replace ( /[\\/]/g , "/" ) } ") ` ) ;
2022-10-09 23:15:45 +03:00
} ,
} ) ;
2022-11-17 23:44:39 +03:00
const mainDeps = options . mainDeps ? . slice ( 0 ) ? ? [ ] ;
if ( cmdLineOptions . bundle ) {
mainDeps . push ( bundle ) ;
if ( cmdLineOptions . typecheck ) {
mainDeps . push ( build ) ;
}
}
else {
mainDeps . push ( build , shim ) ;
}
2022-10-09 23:15:45 +03:00
const main = task ( {
name : options . name ,
description : options . description ,
2022-11-17 23:44:39 +03:00
dependencies : mainDeps ,
2022-10-09 23:15:45 +03:00
} ) ;
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 ;
}
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
if ( ! cmdLineOptions . bundle ) {
return watchProject ( options . project ) ;
}
return bundler . watch ( ) ;
} ,
} ) ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
return { build , bundle , shim , main , watch } ;
}
2019-01-28 08:56:56 +03:00
2022-11-17 23:44:39 +03:00
const { main : tsc , watch : watchTsc } = entrypointBuildTask ( {
2022-10-09 23:15:45 +03:00
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" ,
2022-10-15 10:03:22 +03:00
mainDeps : [ generateLibs ] ,
2022-10-09 23:15:45 +03:00
} ) ;
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" ,
2022-10-15 10:03:22 +03:00
mainDeps : [ generateLibs ] ,
2022-10-09 23:15:45 +03:00
bundlerOptions : { exportIsTsObject : true } ,
} ) ;
export { services , watchServices } ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
export const dtsServices = task ( {
name : "dts-services" ,
description : "Bundles typescript.d.ts" ,
dependencies : [ buildServices ] ,
2022-10-10 05:37:09 +03:00
run : async ( ) => {
if ( needsUpdate ( "./built/local/typescript/tsconfig.tsbuildinfo" , [ "./built/local/typescript.d.ts" , "./built/local/typescript.internal.d.ts" ] ) ) {
2022-11-09 19:00:58 +03:00
await runDtsBundler ( "./built/local/typescript/typescript.d.ts" , "./built/local/typescript.d.ts" ) ;
2022-10-10 05:37:09 +03:00
}
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-11-17 23:44:39 +03:00
const { main : tsserver , watch : watchTsserver } = entrypointBuildTask ( {
2022-10-09 23:15:45 +03:00
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" ,
2022-10-15 10:03:22 +03:00
mainDeps : [ generateLibs ] ,
2022-10-09 23:15:45 +03:00
} ) ;
export { tsserver , watchTsserver } ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const min = task ( {
name : "min" ,
description : "Builds only tsc and tsserver" ,
2022-11-17 23:44:39 +03:00
dependencies : [ tsc , tsserver ] ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const watchMin = task ( {
name : "watch-min" ,
description : "Watches only tsc and tsserver" ,
hiddenFromTaskList : true ,
dependencies : [ watchTsc , watchTsserver ] ,
} ) ;
2019-01-28 08:56:56 +03:00
2023-08-14 22:45:27 +03:00
// This is technically not enough to make tsserverlibrary loadable in the
// browser, but it's unlikely that anyone has actually been doing that.
const lsslJs = `
if ( typeof module !== "undefined" && module . exports ) {
module . exports = require ( "./typescript.js" ) ;
}
else {
throw new Error ( "tsserverlibrary requires CommonJS; use typescript.js instead" ) ;
}
` ;
const lsslDts = `
import ts = require ( "./typescript.js" ) ;
export = ts ;
` ;
const lsslDtsInternal = `
import ts = require ( "./typescript.internal.js" ) ;
export = ts ;
` ;
/ * *
* @ param { string } contents
* /
async function fileContentsWithCopyright ( contents ) {
return await copyright ( ) + contents . trim ( ) . replace ( /\r\n/g , "\n" ) + "\n" ;
}
const lssl = task ( {
2022-10-09 23:15:45 +03:00
name : "lssl" ,
description : "Builds language service server library" ,
2023-08-14 22:45:27 +03:00
dependencies : [ services ] ,
run : async ( ) => {
await fs . promises . writeFile ( "./built/local/tsserverlibrary.js" , await fileContentsWithCopyright ( lsslJs ) ) ;
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const dtsLssl = task ( {
name : "dts-lssl" ,
description : "Bundles tsserverlibrary.d.ts" ,
2023-08-14 22:45:27 +03:00
dependencies : [ dtsServices ] ,
2022-10-10 05:37:09 +03:00
run : async ( ) => {
2023-08-14 22:45:27 +03:00
await fs . promises . writeFile ( "./built/local/tsserverlibrary.d.ts" , await fileContentsWithCopyright ( lsslDts ) ) ;
await fs . promises . writeFile ( "./built/local/tsserverlibrary.internal.d.ts" , await fileContentsWithCopyright ( lsslDtsInternal ) ) ;
2022-10-10 05:37:09 +03:00
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const dts = task ( {
name : "dts" ,
dependencies : [ dtsServices , dtsLssl ] ,
} ) ;
2022-09-03 02:39:54 +03:00
2022-09-14 02:21:03 +03:00
const testRunner = "./built/local/run.js" ;
2022-11-09 23:07:08 +03:00
const watchTestsEmitter = new EventEmitter ( ) ;
2022-10-09 23:15:45 +03:00
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 ,
2022-10-15 10:03:22 +03:00
mainDeps : [ generateLibs ] ,
2022-10-09 23:15:45 +03:00
bundlerOptions : {
// Ensure we never drop any dead code, which might be helpful while debugging.
treeShaking : false ,
2023-01-18 21:51:53 +03:00
onWatchRebuild ( ) {
watchTestsEmitter . emit ( "rebuild" ) ;
2022-11-09 23:07:08 +03:00
} ,
2022-10-09 23:15:45 +03:00
} ,
} ) ;
export { tests , watchTests } ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
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 ) ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
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 ) ;
2019-06-13 13:37:14 +03:00
} ,
2023-08-17 00:26:38 +03:00
} ) ;
2022-10-09 23:15:45 +03:00
export const format = task ( {
name : "format" ,
description : "Formats the codebase." ,
run : ( ) => exec ( process . execPath , [ "node_modules/dprint/bin.js" , "fmt" ] ) ,
2023-08-17 00:26:38 +03:00
} ) ;
2022-10-09 23:15:45 +03:00
export const checkFormat = task ( {
name : "check-format" ,
description : "Checks that the codebase is formatted." ,
2022-11-09 19:00:58 +03:00
run : ( ) => exec ( process . execPath , [ "node_modules/dprint/bin.js" , "check" ] , { ignoreStdout : true } ) ,
2022-10-09 23:15:45 +03:00
} ) ;
2019-07-24 07:17:16 +03:00
2022-10-09 23:15:45 +03:00
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" ,
} ) ;
2019-02-26 03:33:20 +03:00
2022-10-09 23:15:45 +03:00
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" ,
} ) ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
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" ,
} ) ;
2019-02-26 03:33:20 +03:00
2022-10-09 23:15:45 +03:00
export const generateTypesMap = task ( {
name : "generate-types-map" ,
run : async ( ) => {
2023-01-27 23:55:46 +03:00
await fs . promises . mkdir ( "./built/local" , { recursive : true } ) ;
2022-10-09 23:15:45 +03:00
const source = "src/server/typesMap.json" ;
const target = "built/local/typesMap.json" ;
2022-10-10 05:37:09 +03:00
const contents = await fs . promises . readFile ( source , "utf-8" ) ;
JSON . parse ( contents ) ; // Validates that the JSON parses.
await fs . promises . writeFile ( target , contents ) ;
2022-10-09 23:15:45 +03:00
} ,
} ) ;
2019-02-26 03:33:20 +03:00
2020-02-20 22:41:53 +03:00
// Drop a copy of diagnosticMessages.generated.json into the built/local folder. This allows
2020-09-08 21:32:52 +03:00
// it to be synced to the Azure DevOps repo, so that it can get picked up by the build
2020-02-20 22:41:53 +03:00
// pipeline that generates the localization artifacts that are then fed into the translation process.
const builtLocalDiagnosticMessagesGeneratedJson = "built/local/diagnosticMessages.generated.json" ;
2022-10-09 23:15:45 +03:00
const copyBuiltLocalDiagnosticMessages = task ( {
name : "copy-built-local-diagnostic-messages" ,
dependencies : [ generateDiagnostics ] ,
run : async ( ) => {
2022-10-10 05:37:09 +03:00
const contents = await fs . promises . readFile ( diagnosticMessagesGeneratedJson , "utf-8" ) ;
JSON . parse ( contents ) ; // Validates that the JSON parses.
await fs . promises . writeFile ( builtLocalDiagnosticMessagesGeneratedJson , contents ) ;
2022-10-09 23:15:45 +03:00
} ,
} ) ;
2019-02-26 03:33:20 +03:00
2022-10-09 23:15:45 +03:00
export const otherOutputs = task ( {
name : "other-outputs" ,
description : "Builds miscelaneous scripts and documents distributed with the LKG" ,
dependencies : [ cancellationToken , typingsInstaller , watchGuard , generateTypesMap , copyBuiltLocalDiagnosticMessages ] ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
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 ] ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const local = task ( {
name : "local" ,
description : "Builds the full compiler and services" ,
2022-11-17 23:44:39 +03:00
dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] ,
2022-10-09 23:15:45 +03:00
} ) ;
export default local ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const watchLocal = task ( {
name : "watch-local" ,
description : "Watches the full compiler and services" ,
hiddenFromTaskList : true ,
2023-08-14 22:45:27 +03:00
dependencies : [ localize , watchTsc , watchTsserver , watchServices , lssl , watchOtherOutputs , dts , watchSrc ] ,
2022-10-09 23:15:45 +03:00
} ) ;
2020-02-13 22:19:33 +03:00
2022-11-17 23:44:39 +03:00
const runtestsDeps = [ tests , generateLibs ] . concat ( cmdLineOptions . typecheck ? [ dts ] : [ ] ) ;
2020-02-13 22:19:33 +03:00
2022-10-09 23:15:45 +03:00
export const runTests = task ( {
name : "runtests" ,
description : "Runs the tests using the built run.js file." ,
2022-11-09 02:39:04 +03:00
dependencies : runtestsDeps ,
2022-10-09 23:15:45 +03:00
run : ( ) => runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false ) ,
} ) ;
// task("runtests").flags = {
// "-t --tests=<regex>": "Pattern for tests to run.",
// " --failed": "Runs tests listed in '.failed-tests'.",
2023-06-02 23:00:47 +03:00
// " --coverage": "Generate test coverage using c8",
2022-10-09 23:15:45 +03:00
// "-r --reporter=<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=<limit>": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.",
// " --no-color": "Disables color",
// " --timeout=<ms>": "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)",
// };
2022-11-09 23:07:08 +03:00
export const runTestsAndWatch = task ( {
name : "runtests-watch" ,
dependencies : [ watchTests ] ,
run : async ( ) => {
if ( ! cmdLineOptions . tests && ! cmdLineOptions . failed ) {
console . log ( chalk . redBright ( ` You must specifiy either --tests/-t or --failed to use 'runtests-watch'. ` ) ) ;
return ;
}
let watching = true ;
let running = true ;
let lastTestChangeTimeMs = Date . now ( ) ;
let testsChangedDeferred = /** @type {Deferred<void>} */ ( new Deferred ( ) ) ;
let testsChangedCancelSource = CancelToken . source ( ) ;
const testsChangedDebouncer = new Debouncer ( 1_000 , endRunTests ) ;
const testCaseWatcher = chokidar . watch ( [
"tests/cases/**/*.*" ,
"tests/lib/**/*.*" ,
"tests/projects/**/*.*" ,
] , {
ignorePermissionErrors : true ,
alwaysStat : true ,
} ) ;
process . on ( "SIGINT" , endWatchMode ) ;
process . on ( "beforeExit" , endWatchMode ) ;
watchTestsEmitter . on ( "rebuild" , onRebuild ) ;
testCaseWatcher . on ( "all" , onChange ) ;
while ( watching ) {
const promise = testsChangedDeferred . promise ;
const token = testsChangedCancelSource . token ;
if ( ! token . signaled ) {
running = true ;
try {
await runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false , { token , watching : true } ) ;
}
catch {
// ignore
}
running = false ;
}
if ( watching ) {
console . log ( chalk . yellowBright ( ` [watch] test run complete, waiting for changes... ` ) ) ;
await promise ;
}
}
function onRebuild ( ) {
beginRunTests ( testRunner ) ;
}
/ * *
* @ param { 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir' } eventName
* @ param { string } path
* @ param { fs . Stats | undefined } stats
* /
function onChange ( eventName , path , stats ) {
switch ( eventName ) {
case "change" :
case "unlink" :
case "unlinkDir" :
break ;
case "add" :
case "addDir" :
// skip files that are detected as 'add' but haven't actually changed since the last time tests were
// run.
if ( stats && stats . mtimeMs <= lastTestChangeTimeMs ) {
return ;
}
break ;
}
beginRunTests ( path ) ;
}
/ * *
* @ param { string } path
* /
function beginRunTests ( path ) {
if ( testsChangedDebouncer . empty ) {
console . log ( chalk . yellowBright ( ` [watch] tests changed due to ' ${ path } ', restarting... ` ) ) ;
if ( running ) {
console . log ( chalk . yellowBright ( "[watch] aborting in-progress test run..." ) ) ;
}
testsChangedCancelSource . cancel ( ) ;
testsChangedCancelSource = CancelToken . source ( ) ;
}
testsChangedDebouncer . enqueue ( ) ;
}
function endRunTests ( ) {
lastTestChangeTimeMs = Date . now ( ) ;
testsChangedDeferred . resolve ( ) ;
testsChangedDeferred = /** @type {Deferred<void>} */ ( new Deferred ( ) ) ;
}
function endWatchMode ( ) {
if ( watching ) {
watching = false ;
console . log ( chalk . yellowBright ( "[watch] exiting watch mode..." ) ) ;
testsChangedCancelSource . cancel ( ) ;
testCaseWatcher . close ( ) ;
watchTestsEmitter . off ( "rebuild" , onRebuild ) ;
}
}
} ,
} ) ;
2023-02-27 22:06:20 +03:00
const doRunTestsParallel = task ( {
name : "do-runtests-parallel" ,
2022-10-09 23:15:45 +03:00
description : "Runs all the tests in parallel using the built run.js file." ,
2022-11-09 02:39:04 +03:00
dependencies : runtestsDeps ,
2022-10-09 23:15:45 +03:00
run : ( ) => runConsoleTests ( testRunner , "min" , /*runInParallel*/ cmdLineOptions . workers > 1 ) ,
} ) ;
2023-02-27 22:06:20 +03:00
export const runTestsParallel = task ( {
name : "runtests-parallel" ,
description : "Runs all the tests in parallel using the built run.js file, linting in parallel if --lint=true." ,
dependencies : [ doRunTestsParallel ] . concat ( cmdLineOptions . lint ? [ lint ] : [ ] ) ,
} ) ;
2022-10-09 23:15:45 +03:00
// task("runtests-parallel").flags = {
2023-06-02 23:00:47 +03:00
// " --coverage": "Generate test coverage using c8",
2022-10-09 23:15:45 +03:00
// " --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=<limit>": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.",
// " --workers=<number>": "The number of parallel workers to use.",
// " --timeout=<ms>": "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" ] ) ,
} ) ;
2020-02-13 22:19:33 +03:00
2022-10-09 23:15:45 +03:00
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 } ) ,
} ) ;
2019-01-28 08:56:56 +03:00
2019-02-23 00:12:11 +03:00
/ * *
* @ param { string } localBaseline Path to the local copy of the baselines
* @ param { string } refBaseline Path to the reference copy of the baselines
* /
2022-10-09 23:15:45 +03:00
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 ) {
2022-12-01 01:01:03 +03:00
const out = localPathToRefPath ( p ) . replace ( /\.delete$/ , "" ) ;
2023-08-24 02:42:37 +03:00
await rimraf ( out ) ;
2022-10-09 23:15:45 +03:00
}
} ;
}
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 ) ,
} ) ;
2019-01-28 08:56:56 +03:00
// TODO(rbuckton): Determine if we still need this task. Depending on a relative
// path here seems like a bad idea.
2022-10-09 23:15:45 +03:00
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 ) ) ) ;
}
} ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const produceLKG = task ( {
name : "LKG" ,
description : "Makes a new LKG out of the built js files" ,
2022-11-10 00:46:28 +03:00
dependencies : [ local ] ,
2022-10-09 23:15:45 +03:00
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" ) ) ;
}
2023-03-08 02:34:47 +03:00
2022-10-09 23:15:45 +03:00
await exec ( process . execPath , [ "scripts/produceLKG.mjs" ] ) ;
2022-09-14 02:21:03 +03:00
} ,
2022-10-09 23:15:45 +03:00
} ) ;
2022-09-14 02:21:03 +03:00
2022-10-09 23:15:45 +03:00
export const lkg = task ( {
name : "lkg" ,
hiddenFromTaskList : true ,
dependencies : [ produceLKG ] ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const cleanBuilt = task ( {
name : "clean-built" ,
hiddenFromTaskList : true ,
2023-08-24 02:42:37 +03:00
run : ( ) => fs . promises . rm ( "built" , { recursive : true , force : true } ) ,
2018-06-19 08:45:13 +03:00
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
export const clean = task ( {
name : "clean" ,
description : "Cleans build outputs" ,
dependencies : [ cleanBuilt , cleanDiagnostics ] ,
} ) ;
2019-01-28 08:56:56 +03:00
2022-10-09 23:15:45 +03:00
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 } ) ,
} ) ;