4b4ec37880
* Add tests for relativeRoot * Remove path.posix.join Since this is for file system paths it feels incorrect to use it on URL paths as they are different in many ways. * Rewrite cookie path logic Before we relied on the client to resolve the base given to it by the backend against the path. Instead have the client pass that information along so we can resolve it on the backend. This means the client has to do less work. * Do not remove out directory before watch This is re-used for incremental compilation. Also remove del since that was the only use (and we can use fs.rmdir in the future if we need something like this). * Remove unused function resolveBase
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
import { spawn, fork, ChildProcess } from "child_process"
|
|
import { promises as fs } from "fs"
|
|
import * as path from "path"
|
|
import { CompilationStats, onLine, OnLineCallback } from "../../src/node/util"
|
|
|
|
interface DevelopmentCompilers {
|
|
[key: string]: ChildProcess | undefined
|
|
vscode: ChildProcess
|
|
vscodeWebExtensions: ChildProcess
|
|
codeServer: ChildProcess
|
|
plugins: ChildProcess | undefined
|
|
}
|
|
|
|
class Watcher {
|
|
private rootPath = path.resolve(process.cwd())
|
|
private readonly paths = {
|
|
/** Path to uncompiled VS Code source. */
|
|
vscodeDir: path.join(this.rootPath, "vendor", "modules", "code-oss-dev"),
|
|
compilationStatsFile: path.join(this.rootPath, "out", "watcher.json"),
|
|
pluginDir: process.env.PLUGIN_DIR,
|
|
}
|
|
|
|
//#region Web Server
|
|
|
|
/** Development web server. */
|
|
private webServer: ChildProcess | undefined
|
|
|
|
private reloadWebServer = (): void => {
|
|
if (this.webServer) {
|
|
this.webServer.kill()
|
|
}
|
|
|
|
// Pass CLI args, save for `node` and the initial script name.
|
|
const args = process.argv.slice(2)
|
|
this.webServer = fork(path.join(this.rootPath, "out/node/entry.js"), args)
|
|
const { pid } = this.webServer
|
|
|
|
this.webServer.on("exit", () => console.log("[Code Server]", `Web process ${pid} exited`))
|
|
|
|
console.log("\n[Code Server]", `Spawned web server process ${pid}`)
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Compilers
|
|
|
|
private readonly compilers: DevelopmentCompilers = {
|
|
codeServer: spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath }),
|
|
vscode: spawn("yarn", ["watch"], { cwd: this.paths.vscodeDir }),
|
|
vscodeWebExtensions: spawn("yarn", ["watch-web"], { cwd: this.paths.vscodeDir }),
|
|
plugins: this.paths.pluginDir ? spawn("yarn", ["build", "--watch"], { cwd: this.paths.pluginDir }) : undefined,
|
|
}
|
|
|
|
public async initialize(): Promise<void> {
|
|
for (const event of ["SIGINT", "SIGTERM"]) {
|
|
process.on(event, () => this.dispose(0))
|
|
}
|
|
|
|
for (const [processName, devProcess] of Object.entries(this.compilers)) {
|
|
if (!devProcess) continue
|
|
|
|
devProcess.on("exit", (code) => {
|
|
console.log(`[${processName}]`, "Terminated unexpectedly")
|
|
this.dispose(code)
|
|
})
|
|
|
|
if (devProcess.stderr) {
|
|
devProcess.stderr.on("data", (d: string | Uint8Array) => process.stderr.write(d))
|
|
}
|
|
}
|
|
|
|
onLine(this.compilers.vscode, this.parseVSCodeLine)
|
|
onLine(this.compilers.codeServer, this.parseCodeServerLine)
|
|
|
|
if (this.compilers.plugins) {
|
|
onLine(this.compilers.plugins, this.parsePluginLine)
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Line Parsers
|
|
|
|
private parseVSCodeLine: OnLineCallback = (strippedLine, originalLine) => {
|
|
if (!strippedLine.length) return
|
|
|
|
console.log("[VS Code]", originalLine)
|
|
|
|
if (strippedLine.includes("Finished compilation with")) {
|
|
console.log("[VS Code] ✨ Finished compiling! ✨", "(Refresh your web browser ♻️)")
|
|
this.emitCompilationStats()
|
|
this.reloadWebServer()
|
|
}
|
|
}
|
|
|
|
private parseCodeServerLine: OnLineCallback = (strippedLine, originalLine) => {
|
|
if (!strippedLine.length) return
|
|
|
|
console.log("[Compiler][Code Server]", originalLine)
|
|
|
|
if (strippedLine.includes("Watching for file changes")) {
|
|
console.log("[Compiler][Code Server]", "Finished compiling!", "(Refresh your web browser ♻️)")
|
|
this.reloadWebServer()
|
|
}
|
|
}
|
|
|
|
private parsePluginLine: OnLineCallback = (strippedLine, originalLine) => {
|
|
if (!strippedLine.length) return
|
|
|
|
console.log("[Compiler][Plugin]", originalLine)
|
|
|
|
if (strippedLine.includes("Watching for file changes...")) {
|
|
this.reloadWebServer()
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Utilities
|
|
|
|
/**
|
|
* Emits a file containing compilation data.
|
|
* This is especially useful when Express needs to determine if VS Code is still compiling.
|
|
*/
|
|
private emitCompilationStats(): Promise<void> {
|
|
const stats: CompilationStats = {
|
|
lastCompiledAt: new Date(),
|
|
}
|
|
|
|
console.log("Writing watcher stats...")
|
|
return fs.writeFile(this.paths.compilationStatsFile, JSON.stringify(stats, null, 2))
|
|
}
|
|
|
|
private dispose(code: number | null): void {
|
|
for (const [processName, devProcess] of Object.entries(this.compilers)) {
|
|
console.log(`[${processName}]`, "Killing...\n")
|
|
devProcess?.removeAllListeners()
|
|
devProcess?.kill()
|
|
}
|
|
process.exit(typeof code === "number" ? code : 0)
|
|
}
|
|
|
|
//#endregion
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
try {
|
|
const watcher = new Watcher()
|
|
await watcher.initialize()
|
|
} catch (error: any) {
|
|
console.error(error.message)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
main()
|