import { register, run } from "@coder/runner"; import * as fs from "fs"; import * as fse from "fs-extra"; import * as os from "os"; import * as path from "path"; import * as zlib from "zlib"; const isWin = os.platform() === "win32"; const libPath = path.join(__dirname, "../lib"); const vscodePath = path.join(libPath, "vscode"); const pkgsPath = path.join(__dirname, "../packages"); const defaultExtensionsPath = path.join(libPath, "VSCode-linux-x64/resources/app/extensions"); const vscodeVersion = "1.32.0"; const buildServerBinary = register("build:server:binary", async (runner) => { await ensureInstalled(); await copyForDefaultExtensions(); await Promise.all([ buildBootstrapFork(), buildWeb(), buildDefaultExtensions(), buildServerBundle(), buildAppBrowser(), ]); await buildServerBinaryPackage(); }); const buildServerBinaryPackage = register("build:server:binary:package", async (runner) => { const cliPath = path.join(pkgsPath, "server"); runner.cwd = cliPath; if (!fs.existsSync(path.join(cliPath, "out"))) { throw new Error("Cannot build binary without server bundle built"); } await buildServerBinaryCopy(); const resp = await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:binary"]); if (resp.exitCode !== 0) { throw new Error(`Failed to package binary: ${resp.stderr}`); } }); const buildServerBinaryCopy = register("build:server:binary:copy", async (runner) => { const cliPath = path.join(pkgsPath, "server"); const cliBuildPath = path.join(cliPath, "build"); fse.removeSync(cliBuildPath); fse.mkdirpSync(path.join(cliBuildPath, "extensions")); const bootstrapForkPath = path.join(pkgsPath, "vscode", "out", "bootstrap-fork.js"); const webOutputPath = path.join(pkgsPath, "web", "out"); const browserAppOutputPath = path.join(pkgsPath, "app", "browser", "out"); const nodePtyModule = path.join(pkgsPath, "protocol", "node_modules", "node-pty-prebuilt", "build", "Release", "pty.node"); const spdlogModule = path.join(pkgsPath, "protocol", "node_modules", "spdlog", "build", "Release", "spdlog.node"); let ripgrepPath = path.join(pkgsPath, "..", "lib", "vscode", "node_modules", "vscode-ripgrep", "bin", "rg"); if (isWin) { ripgrepPath += ".exe"; } if (!fs.existsSync(nodePtyModule)) { throw new Error("Could not find pty.node. Ensure all packages have been installed"); } if (!fs.existsSync(spdlogModule)) { throw new Error("Could not find spdlog.node. Ensure all packages have been installed"); } if (!fs.existsSync(webOutputPath)) { throw new Error("Web bundle must be built"); } if (!fs.existsSync(defaultExtensionsPath)) { throw new Error("Default extensions must be built"); } if (!fs.existsSync(bootstrapForkPath)) { throw new Error("Bootstrap fork must exist"); } if (!fs.existsSync(ripgrepPath)) { throw new Error("Ripgrep must exist"); } fse.copySync(defaultExtensionsPath, path.join(cliBuildPath, "extensions")); fs.writeFileSync(path.join(cliBuildPath, "bootstrap-fork.js.gz"), zlib.gzipSync(fs.readFileSync(bootstrapForkPath))); const cpDir = (dir: string, subdir: "auth" | "unauth", rootPath: string): void => { const stat = fs.statSync(dir); if (stat.isDirectory()) { const paths = fs.readdirSync(dir); paths.forEach((p) => cpDir(path.join(dir, p), subdir, rootPath)); } else if (stat.isFile()) { const newPath = path.join(cliBuildPath, "web", subdir, path.relative(rootPath, dir)); fse.mkdirpSync(path.dirname(newPath)); fs.writeFileSync(newPath + ".gz", zlib.gzipSync(fs.readFileSync(dir))); } else { // Nothing } }; cpDir(webOutputPath, "auth", webOutputPath); cpDir(browserAppOutputPath, "unauth", browserAppOutputPath); fse.mkdirpSync(path.join(cliBuildPath, "dependencies")); fse.copySync(nodePtyModule, path.join(cliBuildPath, "dependencies", "pty.node")); fse.copySync(spdlogModule, path.join(cliBuildPath, "dependencies", "spdlog.node")); fse.copySync(ripgrepPath, path.join(cliBuildPath, "dependencies", "rg")); }); const buildServerBundle = register("build:server:bundle", async (runner) => { const cliPath = path.join(pkgsPath, "server"); runner.cwd = cliPath; await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]); }); const buildBootstrapFork = register("build:bootstrap-fork", async (runner) => { await ensureInstalled(); await ensurePatched(); const vscodePkgPath = path.join(pkgsPath, "vscode"); runner.cwd = vscodePkgPath; await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:bootstrap-fork"]); }); const buildAppBrowser = register("build:app:browser", async (runner) => { await ensureInstalled(); const appPath = path.join(pkgsPath, "app/browser"); runner.cwd = appPath; fse.removeSync(path.join(appPath, "out")); await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]); }); const buildWeb = register("build:web", async (runner) => { await ensureInstalled(); await ensurePatched(); const webPath = path.join(pkgsPath, "web"); runner.cwd = webPath; fse.removeSync(path.join(webPath, "out")); await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]); }); const extDirPath = path.join("lib", "vscode-default-extensions"); const copyForDefaultExtensions = register("build:copy-vscode", async (runner) => { if (!fs.existsSync(defaultExtensionsPath)) { await ensureClean(); await ensureInstalled(); await new Promise((resolve, reject): void => { fse.remove(extDirPath, (err) => { if (err) { return reject(err); } resolve(); }); }); await new Promise((resolve, reject): void => { fse.copy(vscodePath, extDirPath, (err) => { if (err) { return reject(err); } resolve(); }); }); } }); const buildDefaultExtensions = register("build:default-extensions", async (runner) => { if (!fs.existsSync(defaultExtensionsPath)) { await copyForDefaultExtensions(); runner.cwd = extDirPath; const resp = await runner.execute(isWin ? "npx.cmd" : "npx", [isWin ? "gulp.cmd" : "gulp", "vscode-linux-x64", "--max-old-space-size=32384"]); if (resp.exitCode !== 0) { throw new Error(`Failed to build default extensions: ${resp.stderr}`); } } }); const ensureInstalled = register("vscode:install", async (runner) => { await ensureCloned(); runner.cwd = vscodePath; const install = await runner.execute(isWin ? "yarn.cmd" : "yarn", []); if (install.exitCode !== 0) { throw new Error(`Failed to install vscode dependencies: ${install.stderr}`); } }); const ensureCloned = register("vscode:clone", async (runner) => { if (fs.existsSync(vscodePath)) { await ensureClean(); } else { fse.mkdirpSync(libPath); runner.cwd = libPath; const clone = await runner.execute("git", ["clone", "https://github.com/microsoft/vscode", "--branch", vscodeVersion, "--single-branch", "--depth=1"]); if (clone.exitCode !== 0) { throw new Error(`Failed to clone: ${clone.exitCode}`); } } runner.cwd = vscodePath; const checkout = await runner.execute("git", ["checkout", vscodeVersion]); if (checkout.exitCode !== 0) { throw new Error(`Failed to checkout: ${checkout.stderr}`); } }); const ensureClean = register("vscode:clean", async (runner) => { runner.cwd = vscodePath; const status = await runner.execute("git", ["status", "--porcelain"]); if (status.stdout.trim() !== "") { const clean = await runner.execute("git", ["clean", "-f", "-d", "-X"]); if (clean.exitCode !== 0) { throw new Error(`Failed to clean git repository: ${clean.stderr}`); } const removeUnstaged = await runner.execute("git", ["checkout", "--", "."]); if (removeUnstaged.exitCode !== 0) { throw new Error(`Failed to remove unstaged files: ${removeUnstaged.stderr}`); } } const fetch = await runner.execute("git", ["fetch", "--prune"]); if (fetch.exitCode !== 0) { throw new Error(`Failed to fetch latest changes: ${fetch.stderr}`); } }); const ensurePatched = register("vscode:patch", async (runner) => { if (!fs.existsSync(vscodePath)) { throw new Error("vscode must be cloned to patch"); } await ensureClean(); runner.cwd = vscodePath; const patchPath = path.join(__dirname, "../scripts/vscode.patch"); const apply = await runner.execute("git", ["apply", "--unidiff-zero", patchPath]); if (apply.exitCode !== 0) { throw new Error(`Failed to apply patches: ${apply.stderr}`); } }); register("package", async (runner, releaseTag) => { if (!releaseTag) { throw new Error("Please specify the release tag."); } const releasePath = path.resolve(__dirname, "../release"); const archiveName = `code-server-${releaseTag}-${os.platform()}-${os.arch()}`; const archiveDir = path.join(releasePath, archiveName); fse.removeSync(archiveDir); fse.mkdirpSync(archiveDir); const binaryPath = path.join(__dirname, `../packages/server/cli-${os.platform()}-${os.arch()}`); const binaryDestination = path.join(archiveDir, "code-server"); fse.copySync(binaryPath, binaryDestination); fs.chmodSync(binaryDestination, "755"); ["README.md", "LICENSE"].forEach((fileName) => { fse.copySync(path.resolve(__dirname, `../${fileName}`), path.join(archiveDir, fileName)); }); runner.cwd = releasePath; await os.platform() === "linux" ? runner.execute("tar", ["-cvzf", `${archiveName}.tar.gz`, `${archiveName}`]) : runner.execute("zip", ["-r", `${archiveName}.zip`, `${archiveName}`]); }); run();