mirror of https://github.com/coder/code-server.git
239 lines
8.5 KiB
TypeScript
239 lines
8.5 KiB
TypeScript
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 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 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 web bundle built");
|
|
}
|
|
await buildServerBinaryCopy();
|
|
await dependencyNexeBinary();
|
|
const resp = await runner.execute("npm", ["run", "build:nexe"]);
|
|
if (resp.exitCode !== 0) {
|
|
throw new Error(`Failed to package binary: ${resp.stderr}`);
|
|
}
|
|
});
|
|
|
|
const dependencyNexeBinary = register("dependency:nexe", async (runner) => {
|
|
if (os.platform() === "linux") {
|
|
const nexeDir = path.join(os.homedir(), ".nexe");
|
|
const targetBinaryName = `${os.platform()}-${os.arch()}-${process.version.substr(1)}`;
|
|
const targetBinaryPath = path.join(nexeDir, targetBinaryName);
|
|
if (!fs.existsSync(targetBinaryPath)) {
|
|
/**
|
|
* We create a binary with nexe
|
|
* so we can compress it
|
|
*/
|
|
fse.mkdirpSync(nexeDir);
|
|
runner.cwd = nexeDir;
|
|
await runner.execute("wget", [`https://github.com/nexe/nexe/releases/download/v3.0.0-beta.15/${targetBinaryName}`]);
|
|
await runner.execute("chmod", ["+x", targetBinaryPath]);
|
|
}
|
|
if (fs.statSync(targetBinaryPath).size >= 20000000) {
|
|
// Compress w/ upx
|
|
const upxFolder = path.join(os.tmpdir(), "upx");
|
|
const upxBinary = path.join(upxFolder, "upx");
|
|
if (!fs.existsSync(upxBinary)) {
|
|
fse.mkdirpSync(upxFolder);
|
|
runner.cwd = upxFolder;
|
|
const upxExtract = await runner.execute("bash", ["-c", "curl -L https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz | tar xJ --strip-components=1"]);
|
|
if (upxExtract.exitCode !== 0) {
|
|
throw new Error(`Failed to extract upx: ${upxExtract.stderr}`);
|
|
}
|
|
}
|
|
if (!fs.existsSync(upxBinary)) {
|
|
throw new Error("Not sure how, but the UPX binary does not exist");
|
|
}
|
|
await runner.execute(upxBinary, [targetBinaryPath]);
|
|
}
|
|
}
|
|
});
|
|
|
|
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", "bin", "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", "build", "Release", "pty.node");
|
|
const spdlogModule = path.join(pkgsPath, "server", "node_modules", "spdlog", "build", "Release", "spdlog.node");
|
|
|
|
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");
|
|
}
|
|
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, "modules"));
|
|
fse.copySync(nodePtyModule, path.join(cliBuildPath, "modules", "pty.node"));
|
|
fse.copySync(spdlogModule, path.join(cliBuildPath, "modules", "spdlog.node"));
|
|
});
|
|
|
|
const buildServerBundle = register("build:server:bundle", async (runner) => {
|
|
const cliPath = path.join(pkgsPath, "server");
|
|
runner.cwd = cliPath;
|
|
await runner.execute("npm", ["run", "build:webpack"]);
|
|
});
|
|
|
|
const buildBootstrapFork = register("build:bootstrap-fork", async (runner) => {
|
|
await ensureInstalled();
|
|
await ensurePatched();
|
|
|
|
const vscodePkgPath = path.join(pkgsPath, "vscode");
|
|
runner.cwd = vscodePkgPath;
|
|
await runner.execute("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("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("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();
|
|
fse.removeSync(extDirPath);
|
|
fse.copySync(vscodePath, extDirPath);
|
|
}
|
|
});
|
|
|
|
const buildDefaultExtensions = register("build:default-extensions", async (runner) => {
|
|
if (!fs.existsSync(defaultExtensionsPath)) {
|
|
await copyForDefaultExtensions();
|
|
runner.cwd = extDirPath;
|
|
const resp = await runner.execute("npx", ["gulp", "vscode-linux-x64"]);
|
|
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("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 {
|
|
fs.mkdirSync(libPath);
|
|
runner.cwd = libPath;
|
|
const clone = await runner.execute("git", ["clone", "https://github.com/microsoft/vscode"]);
|
|
if (clone.exitCode !== 0) {
|
|
throw new Error(`Failed to clone: ${clone.exitCode}`);
|
|
}
|
|
}
|
|
|
|
runner.cwd = vscodePath;
|
|
const checkout = await runner.execute("git", ["checkout", "tags/1.31.0"]);
|
|
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 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}`);
|
|
}
|
|
});
|
|
|
|
run();
|