Skip to content

Instantly share code, notes, and snippets.

@jeremyben
Last active March 22, 2024 20:26
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyben/4de4fdc40175d0f76892209e00ece98f to your computer and use it in GitHub Desktop.
Save jeremyben/4de4fdc40175d0f76892209e00ece98f to your computer and use it in GitHub Desktop.
Typescript programmatic build with tsconfig.json (run with `ts-node -T`)
import * as path from 'path'
import ts from 'typescript'
function build(
override: {
compilerOptions?: ts.CompilerOptions
include?: string[]
exclude?: string[]
files?: string[]
extends?: string
} = {},
currentDir = process.cwd()
) {
const configFile = ts.findConfigFile(currentDir, ts.sys.fileExists, 'tsconfig.json')
if (!configFile) throw Error('tsconfig.json not found')
const { config } = ts.readConfigFile(configFile, ts.sys.readFile)
config.compilerOptions = Object.assign({}, config.compilerOptions, override.compilerOptions)
if (override.include) config.include = override.include
if (override.exclude) config.exclude = override.exclude
if (override.files) config.files = override.files
if (override.extends) config.files = override.extends
const { options, fileNames, errors } = ts.parseJsonConfigFileContent(config, ts.sys, currentDir)
const program = ts.createProgram({ options, rootNames: fileNames, configFileParsingDiagnostics: errors })
const { diagnostics, emitSkipped } = program.emit()
const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(diagnostics, errors)
if (allDiagnostics.length) {
const formatHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
}
const message = ts.formatDiagnostics(allDiagnostics, formatHost)
console.warn(message)
}
if (emitSkipped) process.exit(1)
}
import { exec, ExecOptions } from 'child_process'
import { join } from 'path'
function build(flags: string[] = [], directory = process.cwd()) {
const tsc = join(__dirname, 'node_modules', 'typescript', 'lib', 'tsc.js')
const tsConfig = join(directory, 'tsconfig.json')
const cmd = encodeArgs(process.execPath, tsc, '--project', tsConfig, ...flags).join(' ')
return execAsync(cmd, { inherit: true })
function execAsync(cmd: string, opts: ExecOptions & { inherit?: boolean } = {}): Promise<string> {
return new Promise((resolve, reject) => {
const child = exec(cmd, opts, (error, stdout, stderr) => {
if (error) reject({ ...error, stdout, stderr })
else resolve(stdout)
})
if (opts.inherit) {
child.stdout.pipe(process.stdout)
child.stderr.pipe(process.stderr)
}
})
}
function encodeArgs(...args: string[]) {
// Taken from https://github.com/xxorax/node-shell-escape/blob/master/shell-escape.js
// However, we needed to use double quotes because that's the norm in more platforms
if (!args) return args
return args.map((arg) => {
if (/[^\w/:=-]/.test(arg)) {
arg = `"${arg.replace(/"/g, '"\\"')}"`
arg = arg.replace(/^(?:"")+/g, '').replace(/\\"""/g, '\\"')
}
return arg
})
}
}
@jeremyben
Copy link
Author

Check out my project https://github.com/jeremyben/tsc-prog 😉

@WeijieZhu0204
Copy link

const formatHost = {
  getCanonicalFileName: path => path,
  getNewLine: () => ts.sys.newLine,
  getCurrentDirectory: () => root,
};
const message = ts.formatDiagnosticsWithColorAndContext(
  allDiagnostics,
  formatHost
);

This usage is better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment