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"; import * as https from "https"; import * as tar from "tar"; const isWin = os.platform() === "win32"; const libPath = path.join(__dirname, "../lib"); const vscodePath = path.join(libPath, "vscode"); const defaultExtensionsPath = path.join(libPath, "extensions"); const pkgsPath = path.join(__dirname, "../packages"); const vscodeVersion = process.env.VSCODE_VERSION || "1.33.0"; const vsSourceUrl = `https://codesrv-ci.cdr.sh/vstar-${vscodeVersion}.tar.gz`; const buildServerBinary = register("build:server:binary", async (runner) => { await ensureInstalled(); await Promise.all([ buildBootstrapFork(), buildWeb(), 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 ensureInstalled = register("vscode:install", async (runner) => { runner.cwd = libPath; if (fs.existsSync(vscodePath) && fs.existsSync(defaultExtensionsPath)) { const pkgVersion = JSON.parse(fs.readFileSync(path.join(vscodePath, "package.json")).toString("utf8")).version; if (pkgVersion === vscodeVersion) { runner.cwd = vscodePath; const reset = await runner.execute("git", ["reset", "--hard"]); if (reset.exitCode !== 0) { throw new Error(`Failed to clean git repository: ${reset.stderr}`); } return; } } fse.removeSync(libPath); fse.mkdirpSync(libPath); await new Promise<void>((resolve, reject): void => { https.get(vsSourceUrl, (res) => { if (res.statusCode !== 200) { return reject(res.statusMessage); } res.pipe(tar.x({ C: libPath, }).on("finish", () => { resolve(); }).on("error", (err: Error) => { reject(err); })); }).on("error", (err) => { reject(err); }); }); }); const ensurePatched = register("vscode:patch", async (runner) => { if (!fs.existsSync(vscodePath)) { throw new Error("vscode must be cloned to patch"); } await ensureInstalled(); 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();