Archived
1
0

Implement new structure

This commit is contained in:
Asher
2020-02-04 13:27:46 -06:00
parent ef8da3864f
commit b29346ecdf
94 changed files with 12427 additions and 4936 deletions

View File

@ -1,391 +1,451 @@
import { Binary } from "@coder/nbin";
import * as cp from "child_process";
// import * as crypto from "crypto";
import * as fs from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as util from "util";
import { Binary } from "@coder/nbin"
import * as cp from "child_process"
import * as fs from "fs-extra"
import * as os from "os"
import Bundler from "parcel-bundler"
import * as path from "path"
import * as util from "util"
enum Task {
/**
* Use before running anything that only works inside VS Code.
*/
EnsureInVscode = "ensure-in-vscode",
Binary = "binary",
Package = "package",
Build = "build",
Binary = "binary",
Package = "package",
Build = "build",
Watch = "watch",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..");
private readonly outPath = process.env.OUT || this.rootPath;
private _target?: "darwin" | "alpine" | "linux";
private currentTask?: Task;
private readonly rootPath = path.resolve(__dirname, "..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private readonly binariesPath = path.join(this.rootPath, "binaries")
private readonly buildPath = path.join(this.rootPath, "build")
private readonly codeServerVersion: string
private _target?: "darwin" | "alpine" | "linux"
private currentTask?: Task
public run(task: Task | undefined, args: string[]): void {
this.currentTask = task;
this.doRun(task, args).catch((error) => {
console.error(error.message);
process.exit(1);
});
}
public constructor() {
this.ensureArgument("rootPath", this.rootPath)
this.codeServerVersion = this.ensureArgument(
"codeServerVersion",
process.env.VERSION || require(path.join(this.rootPath, "package.json")).version
)
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now();
this.log(`${message}...`, true);
try {
const t = await fn();
process.stdout.write(`took ${Date.now() - time}ms\n`);
return t;
} catch (error) {
process.stdout.write("failed\n");
throw error;
}
}
public run(task: Task | undefined): void {
this.currentTask = task
this.doRun(task).catch((error) => {
console.error(error.message)
process.exit(1)
})
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline: boolean = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`);
if (!skipNewline) {
process.stdout.write("\n");
}
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now()
this.log(`${message}...`, true)
try {
const t = await fn()
process.stdout.write(`took ${Date.now() - time}ms\n`)
return t
} catch (error) {
process.stdout.write("failed\n")
throw error
}
}
private async doRun(task: Task | undefined, args: string[]): Promise<void> {
if (!task) {
throw new Error("No task provided");
}
/**
* Writes to stdout with an optional newline.
*/
private log(message: string, skipNewline = false): void {
process.stdout.write(`[${this.currentTask || "default"}] ${message}`)
if (!skipNewline) {
process.stdout.write("\n")
}
}
if (task === Task.EnsureInVscode) {
return process.exit(this.isInVscode(this.rootPath) ? 0 : 1);
}
private async doRun(task: Task | undefined): Promise<void> {
if (!task) {
throw new Error("No task provided")
}
// If we're inside VS Code assume we want to develop. In that case we should
// set an OUT directory and not build in this directory, otherwise when you
// build/watch VS Code the build directory will be included.
if (this.isInVscode(this.outPath)) {
throw new Error("Should not build inside VS Code; set the OUT environment variable");
}
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"))
const target = this.ensureArgument("target", await this.target())
const binaryName = `code-server-${this.codeServerVersion}-${target}-${arch}`
this.ensureArgument("rootPath", this.rootPath);
this.ensureArgument("outPath", this.outPath);
switch (task) {
case Task.Watch:
return this.watch()
case Task.Binary:
return this.binary(binaryName)
case Task.Package:
return this.package(binaryName)
case Task.Build:
return this.build()
default:
throw new Error(`No task matching "${task}"`)
}
}
const arch = this.ensureArgument("arch", os.arch().replace(/^x/, "x86_"));
const target = this.ensureArgument("target", await this.target());
const vscodeVersion = this.ensureArgument("vscodeVersion", args[0]);
const codeServerVersion = this.ensureArgument("codeServerVersion", args[1]);
/**
* Get the target of the system.
*/
private async target(): Promise<"darwin" | "alpine" | "linux"> {
if (!this._target) {
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
this._target = "darwin"
} else {
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
// (like --version) it outputs the version to stderr and exits with 1.
const result = await util
.promisify(cp.exec)("ldd --version")
.catch((error) => ({ stderr: error.message, stdout: "" }))
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
this._target = "alpine"
} else {
this._target = "linux"
}
}
}
return this._target
}
const vscodeSourcePath = path.join(this.outPath, "source", `vscode-${vscodeVersion}-source`);
const binariesPath = path.join(this.outPath, "binaries");
const binaryName = `code-server${codeServerVersion}-vsc${vscodeVersion}-${target}-${arch}`;
const finalBuildPath = path.join(this.outPath, "build", `${binaryName}-built`);
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
throw new Error(`${name} is missing`)
}
this.log(`${name} is "${arg}"`)
return arg
}
switch (task) {
case Task.Binary:
return this.binary(finalBuildPath, binariesPath, binaryName);
case Task.Package:
return this.package(vscodeSourcePath, binariesPath, binaryName);
case Task.Build:
return this.build(vscodeSourcePath, vscodeVersion, codeServerVersion, finalBuildPath);
default:
throw new Error(`No task matching "${task}"`);
}
}
/**
* Build VS Code and code-server.
*/
private async build(): Promise<void> {
process.env.NODE_OPTIONS = "--max-old-space-size=32384 " + (process.env.NODE_OPTIONS || "")
process.env.NODE_ENV = "production"
/**
* Get the target of the system.
*/
private async target(): Promise<"darwin" | "alpine" | "linux"> {
if (!this._target) {
if (os.platform() === "darwin" || (process.env.OSTYPE && /^darwin/.test(process.env.OSTYPE))) {
this._target = "darwin";
} else {
// Alpine's ldd doesn't have a version flag but if you use an invalid flag
// (like --version) it outputs the version to stderr and exits with 1.
const result = await util.promisify(cp.exec)("ldd --version")
.catch((error) => ({ stderr: error.message, stdout: "" }));
if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
this._target = "alpine";
} else {
this._target = "linux";
}
}
}
return this._target;
}
await this.task("cleaning up old build", async () => {
if (!process.env.SKIP_VSCODE) {
return fs.remove(this.buildPath)
}
// If skipping VS Code, keep the existing build if any.
try {
const files = await fs.readdir(this.buildPath)
return Promise.all(files.filter((f) => f !== "lib").map((f) => fs.remove(path.join(this.buildPath, f))))
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
})
/**
* Make sure the argument is set. Display the value if it is.
*/
private ensureArgument(name: string, arg?: string): string {
if (!arg) {
this.log(`${name} is missing`);
throw new Error("Usage: <vscodeVersion> <codeServerVersion>");
}
this.log(`${name} is "${arg}"`);
return arg;
}
const commit = require(path.join(this.vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath) as string
if (!process.env.SKIP_VSCODE) {
await this.buildVscode(commit)
} else {
this.log("skipping vs code build")
}
await this.buildCodeServer(commit)
/**
* Return true if it looks like we're inside VS Code. This is used to prevent
* accidentally building inside VS Code while developing which causes issues
* because the watcher will try compiling those built files.
*/
private isInVscode(pathToCheck: string): boolean {
let inside = false;
const maybeVsCode = path.join(pathToCheck, "../../../");
try {
// If it has a package.json with the right name it's probably VS Code.
inside = require(path.join(maybeVsCode, "package.json")).name === "code-oss-dev";
} catch (error) {}
this.log(
inside
? `Running inside VS Code ([${maybeVsCode}]${path.relative(maybeVsCode, pathToCheck)})`
: "Not running inside VS Code"
);
return inside;
}
this.log(`final build: ${this.buildPath}`)
}
/**
* Build code-server within VS Code.
*/
private async build(vscodeSourcePath: string, vscodeVersion: string, codeServerVersion: string, finalBuildPath: string): Promise<void> {
// Install dependencies (should be cached by CI).
await this.task("Installing code-server dependencies", async () => {
await util.promisify(cp.exec)("yarn", { cwd: this.rootPath });
});
private async buildCodeServer(commit: string): Promise<void> {
await this.task("building code-server", async () => {
return util.promisify(cp.exec)("tsc --outDir ./out-build --tsBuildInfoFile ./.prod.tsbuildinfo", {
cwd: this.rootPath,
})
})
// Download and prepare VS Code if necessary (should be cached by CI).
if (fs.existsSync(vscodeSourcePath)) {
this.log("Using existing VS Code clone");
} else {
await this.task("Cloning VS Code", () => {
return util.promisify(cp.exec)(
"git clone https://github.com/microsoft/vscode"
+ ` --quiet --branch "${vscodeVersion}"`
+ ` --single-branch --depth=1 "${vscodeSourcePath}"`);
});
}
await this.task("bundling code-server", async () => {
return this.createBundler("dist-build", commit).bundle()
})
await this.task("Installing VS Code dependencies", () => {
return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
});
await this.task("copying code-server into build directory", async () => {
await fs.mkdirp(this.buildPath)
await Promise.all([
fs.copy(path.join(this.rootPath, "out-build"), path.join(this.buildPath, "out")),
fs.copy(path.join(this.rootPath, "dist-build"), path.join(this.buildPath, "dist")),
// For source maps and images.
fs.copy(path.join(this.rootPath, "src"), path.join(this.buildPath, "src")),
])
})
if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) {
this.log("Using existing built-in-extensions");
} else {
await this.task("Building default extensions", () => {
return util.promisify(cp.exec)(
"yarn gulp compile-extensions-build --max-old-space-size=32384",
{ cwd: vscodeSourcePath },
);
});
}
await this.copyDependencies("code-server", this.rootPath, this.buildPath)
}
// Clean before patching or it could fail if already patched.
await this.task("Patching VS Code", async () => {
await util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
await util.promisify(cp.exec)(`git apply ${this.rootPath}/scripts/vscode.patch`, { cwd: vscodeSourcePath });
});
private async buildVscode(commit: string): Promise<void> {
await this.task("building vs code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build", { cwd: this.vscodeSourcePath })
})
const serverPath = path.join(vscodeSourcePath, "src/vs/server");
await this.task("Copying code-server into VS Code", async () => {
await fs.remove(serverPath);
await fs.mkdirp(serverPath);
await Promise.all(["main.js", "node_modules", "src", "typings"].map((fileName) => {
return fs.copy(path.join(this.rootPath, fileName), path.join(serverPath, fileName));
}));
});
await this.task("building builtin extensions", async () => {
const exists = await fs.pathExists(path.join(this.vscodeSourcePath, ".build/extensions"))
if (exists) {
process.stdout.write("already built, skipping...")
} else {
await util.promisify(cp.exec)("yarn gulp compile-extensions-build", { cwd: this.vscodeSourcePath })
}
})
await this.task("Building VS Code", () => {
return util.promisify(cp.exec)("yarn gulp compile-build --max-old-space-size=32384", { cwd: vscodeSourcePath });
});
await this.task("optimizing vs code", async () => {
return util.promisify(cp.exec)("yarn gulp optimize --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
await this.task("Optimizing VS Code", async () => {
await fs.copyFile(path.join(this.rootPath, "scripts/optimize.js"), path.join(vscodeSourcePath, "coder.js"));
await util.promisify(cp.exec)(`yarn gulp optimize --max-old-space-size=32384 --gulpfile ./coder.js`, { cwd: vscodeSourcePath });
});
if (process.env.MINIFY) {
await this.task("minifying vs code", () => {
return util.promisify(cp.exec)("yarn gulp minify --gulpfile ./coder.js", { cwd: this.vscodeSourcePath })
})
}
const { productJson, packageJson } = await this.task("Generating final package.json and product.json", async () => {
const merge = async (name: string, extraJson: { [key: string]: string } = {}): Promise<{ [key: string]: string }> => {
const [aJson, bJson] = (await Promise.all([
fs.readFile(path.join(vscodeSourcePath, `${name}.json`), "utf8"),
fs.readFile(path.join(this.rootPath, `scripts/${name}.json`), "utf8"),
])).map((raw) => {
const json = JSON.parse(raw);
delete json.scripts;
delete json.dependencies;
delete json.devDependencies;
delete json.optionalDependencies;
return json;
});
const { productJson, packageJson } = await this.task("generating vs code product configuration", async () => {
const merge = async (name: string, json: { [key: string]: string } = {}): Promise<{ [key: string]: string }> => {
return {
...JSON.parse(await fs.readFile(path.join(this.vscodeSourcePath, `${name}.json`), "utf8")),
...json,
}
}
return { ...aJson, ...bJson, ...extraJson };
};
const date = new Date().toISOString()
const [packageJson, productJson] = await Promise.all([merge("package", {}), merge("product", { commit, date })])
const date = new Date().toISOString();
const commit = require(path.join(vscodeSourcePath, "build/lib/util")).getVersion(this.rootPath);
return { productJson, packageJson }
})
const [productJson, packageJson] = await Promise.all([
merge("product", { commit, date }),
merge("package", { codeServerVersion: `${codeServerVersion}-vsc${vscodeVersion}` }),
]);
await this.task("inserting vs code product configuration", async () => {
const filePath = path.join(this.vscodeSourcePath, "out-build/vs/platform/product/common/product.js")
return fs.writeFile(
filePath,
(await fs.readFile(filePath, "utf8")).replace(
"{ /*BUILD->INSERT_PRODUCT_CONFIGURATION*/}",
JSON.stringify({
version: packageJson.version,
...productJson,
})
)
)
})
// We could do this before the optimization but then it'd be copied into
// three files and unused in two which seems like a waste of bytes.
const apiPath = path.join(vscodeSourcePath, "out-vscode/vs/workbench/workbench.web.api.js");
await fs.writeFile(apiPath, (await fs.readFile(apiPath, "utf8")).replace('{ /*BUILD->INSERT_PRODUCT_CONFIGURATION*/}', JSON.stringify({
version: packageJson.version,
codeServerVersion: packageJson.codeServerVersion,
...productJson,
})));
const vscodeBuildPath = path.join(this.buildPath, "lib/vscode")
await this.task("copying vs code into build directory", async () => {
await fs.mkdirp(vscodeBuildPath)
await Promise.all([
(async (): Promise<void> => {
await fs.move(
path.join(this.vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`),
path.join(vscodeBuildPath, "out")
)
await fs.remove(path.join(vscodeBuildPath, "out/vs/server/browser/workbench.html"))
await fs.move(
path.join(vscodeBuildPath, "out/vs/server/browser/workbench-build.html"),
path.join(vscodeBuildPath, "out/vs/server/browser/workbench.html")
)
})(),
await fs.copy(path.join(this.vscodeSourcePath, ".build/extensions"), path.join(vscodeBuildPath, "extensions")),
])
})
return { productJson, packageJson };
});
await this.copyDependencies("vs code", this.vscodeSourcePath, vscodeBuildPath)
if (process.env.MINIFY) {
await this.task("Minifying VS Code", () => {
return util.promisify(cp.exec)("yarn gulp minify --max-old-space-size=32384 --gulpfile ./coder.js", { cwd: vscodeSourcePath });
});
}
await this.task("writing final vs code product.json", () => {
return fs.writeFile(path.join(vscodeBuildPath, "product.json"), JSON.stringify(productJson, null, 2))
})
}
const finalServerPath = path.join(finalBuildPath, "out/vs/server");
await this.task("Copying into final build directory", async () => {
await fs.remove(finalBuildPath);
await fs.mkdirp(finalBuildPath);
await Promise.all([
fs.copy(path.join(vscodeSourcePath, "remote/node_modules"), path.join(finalBuildPath, "node_modules")),
fs.copy(path.join(vscodeSourcePath, ".build/extensions"), path.join(finalBuildPath, "extensions")),
fs.copy(path.join(vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`), path.join(finalBuildPath, "out")).then(() => {
return Promise.all([
fs.remove(path.join(finalServerPath, "node_modules")).then(() => {
return fs.copy(path.join(serverPath, "node_modules"), path.join(finalServerPath, "node_modules"));
}),
fs.copy(path.join(finalServerPath, "src/browser/workbench-build.html"), path.join(finalServerPath, "src/browser/workbench.html")),
]);
}),
]);
});
private async copyDependencies(name: string, sourcePath: string, buildPath: string): Promise<void> {
await this.task(`copying ${name} dependencies`, async () => {
return Promise.all(
["node_modules", "package.json", "yarn.lock"].map((fileName) => {
return fs.copy(path.join(sourcePath, fileName), path.join(buildPath, fileName))
})
)
})
if (process.env.MINIFY) {
await this.task("Restricting to production dependencies", async () => {
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
Promise.all([
fs.copy(path.join(this.rootPath, fileName), path.join(finalServerPath, fileName)),
fs.copy(path.join(path.join(vscodeSourcePath, "remote"), fileName), path.join(finalBuildPath, fileName)),
]);
}));
if (process.env.MINIFY) {
await this.task(`restricting ${name} to production dependencies`, async () => {
return util.promisify(cp.exec)("yarn --production --ignore-scripts", { cwd: buildPath })
})
}
}
await Promise.all([finalServerPath, finalBuildPath].map((cwd) => {
return util.promisify(cp.exec)("yarn --production", { cwd });
}));
/**
* Bundles the built code into a binary.
*/
private async binary(binaryName: string): Promise<void> {
const bin = new Binary({
mainFile: path.join(this.buildPath, "out/node/entry.js"),
target: await this.target(),
})
await Promise.all(["package.json", "yarn.lock"].map((fileName) => {
return Promise.all([
fs.remove(path.join(finalServerPath, fileName)),
fs.remove(path.join(finalBuildPath, fileName)),
]);
}));
});
}
bin.writeFiles(path.join(this.buildPath, "**"))
await this.task("Writing final package.json and product.json", () => {
return Promise.all([
fs.writeFile(path.join(finalBuildPath, "package.json"), JSON.stringify(packageJson, null, 2)),
fs.writeFile(path.join(finalBuildPath, "product.json"), JSON.stringify(productJson, null, 2)),
]);
});
await fs.mkdirp(this.binariesPath)
// Prevent needless cache changes.
await this.task("Cleaning for smaller cache", () => {
return Promise.all([
fs.remove(serverPath),
fs.remove(path.join(vscodeSourcePath, "out-vscode")),
fs.remove(path.join(vscodeSourcePath, "out-vscode-min")),
fs.remove(path.join(vscodeSourcePath, "out-build")),
util.promisify(cp.exec)("git reset --hard", { cwd: vscodeSourcePath }).then(() => {
return util.promisify(cp.exec)("git clean -fd", { cwd: vscodeSourcePath });
}),
]);
});
const binaryPath = path.join(this.binariesPath, binaryName)
await fs.writeFile(binaryPath, await bin.build())
await fs.chmod(binaryPath, "755")
// Prepend code to the target which enables finding files within the binary.
const prependLoader = async (relativeFilePath: string): Promise<void> => {
const filePath = path.join(finalBuildPath, relativeFilePath);
const shim = `
if (!global.NBIN_LOADED) {
try {
const nbin = require("nbin");
nbin.shimNativeFs("${finalBuildPath}");
global.NBIN_LOADED = true;
const path = require("path");
const rg = require("vscode-ripgrep");
rg.binaryRgPath = rg.rgPath;
rg.rgPath = path.join(require("os").tmpdir(), "code-server", path.basename(rg.binaryRgPath));
} catch (error) { /* Not in the binary. */ }
}
`;
await fs.writeFile(filePath, shim + (await fs.readFile(filePath, "utf8")));
};
this.log(`binary: ${binaryPath}`)
}
await this.task("Prepending nbin loader", () => {
return Promise.all([
prependLoader("out/vs/server/main.js"),
prependLoader("out/bootstrap-fork.js"),
prependLoader("extensions/node_modules/typescript/lib/tsserver.js"),
]);
});
/**
* Package the binary into a release archive.
*/
private async package(binaryName: string): Promise<void> {
const releasePath = path.join(this.rootPath, "release")
const archivePath = path.join(releasePath, binaryName)
this.log(`Final build: ${finalBuildPath}`);
}
await fs.remove(archivePath)
await fs.mkdirp(archivePath)
/**
* Bundles the built code into a binary.
*/
private async binary(targetPath: string, binariesPath: string, binaryName: string): Promise<void> {
const bin = new Binary({
mainFile: path.join(targetPath, "out/vs/server/main.js"),
target: await this.target(),
});
await fs.copyFile(path.join(this.binariesPath, binaryName), path.join(archivePath, "code-server"))
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"))
await fs.copyFile(path.join(this.vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"))
await fs.copyFile(
path.join(this.vscodeSourcePath, "ThirdPartyNotices.txt"),
path.join(archivePath, "ThirdPartyNotices.txt")
)
bin.writeFiles(path.join(targetPath, "**"));
if ((await this.target()) === "darwin") {
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath })
this.log(`archive: ${archivePath}.zip`)
} else {
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath })
this.log(`archive: ${archivePath}.tar.gz`)
}
}
await fs.mkdirp(binariesPath);
private async watch(): Promise<void> {
let server: cp.ChildProcess | undefined
const restartServer = (): void => {
if (server) {
server.kill()
}
const s = cp.fork(path.join(this.rootPath, "out/node/entry.js"))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const binaryPath = path.join(binariesPath, binaryName);
await fs.writeFile(binaryPath, await bin.build());
await fs.chmod(binaryPath, "755");
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const bundler = this.createBundler()
this.log(`Binary: ${binaryPath}`);
}
const cleanup = (code?: number | null): void => {
this.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
/**
* Package the binary into a release archive.
*/
private async package(vscodeSourcePath: string, binariesPath: string, binaryName: string): Promise<void> {
const releasePath = path.join(this.outPath, "release");
const archivePath = path.join(releasePath, binaryName);
this.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
await fs.remove(archivePath);
await fs.mkdirp(archivePath);
if (server) {
this.log("killing server")
server.removeAllListeners()
server.kill()
}
await fs.copyFile(path.join(binariesPath, binaryName), path.join(archivePath, "code-server"));
await fs.copyFile(path.join(this.rootPath, "README.md"), path.join(archivePath, "README.md"));
await fs.copyFile(path.join(vscodeSourcePath, "LICENSE.txt"), path.join(archivePath, "LICENSE.txt"));
await fs.copyFile(path.join(vscodeSourcePath, "ThirdPartyNotices.txt"), path.join(archivePath, "ThirdPartyNotices.txt"));
this.log("killing bundler")
process.exit(code || 0)
}
if ((await this.target()) === "darwin") {
await util.promisify(cp.exec)(`zip -r "${binaryName}.zip" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.zip`);
} else {
await util.promisify(cp.exec)(`tar -czf "${binaryName}.tar.gz" "${binaryName}"`, { cwd: releasePath });
this.log(`Archive: ${archivePath}.tar.gz`);
}
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
this.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
this.log("tsc terminated unexpectedly")
cleanup(code)
})
const bundle = bundler.bundle().catch(() => {
this.log("parcel watcher terminated unexpectedly")
cleanup(1)
})
bundler.on("buildEnd", () => {
console.log("[parcel] bundled")
})
vscode.stderr.on("data", (d) => process.stderr.write(d))
tsc.stderr.on("data", (d) => process.stderr.write(d))
// From https://github.com/chalk/ansi-regex
const pattern = [
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
].join("|")
const re = new RegExp(pattern, "g")
/**
* Split stdout on newlines and strip ANSI codes.
*/
const onLine = (proc: cp.ChildProcess, callback: (strippedLine: string, originalLine: string) => void): void => {
let buffer = ""
if (!proc.stdout) {
throw new Error("no stdout")
}
proc.stdout.setEncoding("utf8")
proc.stdout.on("data", (d) => {
const data = buffer + d
const split = data.split("\n")
const last = split.length - 1
for (let i = 0; i < last; ++i) {
callback(split[i].replace(re, ""), split[i])
}
// The last item will either be an empty string (the data ended with a
// newline) or a partial line (did not end with a newline) and we must
// wait to parse it until we get a full line.
buffer = split[last]
})
}
let startingVscode = false
onLine(vscode, (line, original) => {
console.log("[vscode]", original)
// Wait for watch-client since "Finished compilation" will appear multiple
// times before the client starts building.
if (!startingVscode && line.includes("Starting watch-client")) {
startingVscode = true
} else if (startingVscode && line.includes("Finished compilation") && process.env.AUTO_PATCH) {
cp.exec("yarn patch:generate", { cwd: this.rootPath }, (error, _, stderr) => {
if (error || stderr) {
console.error(error ? error.message : stderr)
}
})
}
})
onLine(tsc, (line, original) => {
// tsc outputs blank lines; skip them.
if (line !== "") {
console.log("[tsc]", original)
}
if (line.includes("Watching for file changes")) {
bundle.then(restartServer)
}
})
}
private createBundler(out = "dist", commit?: string): Bundler {
return new Bundler(path.join(this.rootPath, "src/browser/index.tsx"), {
cache: true,
cacheDir: path.join(this.rootPath, ".cache"),
detailedReport: true,
minify: !!process.env.MINIFY,
hmr: false,
logLevel: 1,
outDir: path.join(this.rootPath, out),
publicUrl: `/static-${commit}/dist`,
target: "browser",
})
}
}
const builder = new Builder();
builder.run(process.argv[2] as Task, process.argv.slice(3));
const builder = new Builder()
builder.run(process.argv[2] as Task)

View File

@ -8,46 +8,48 @@ set -eu
# Try restoring from each argument in turn until we get something.
restore() {
for branch in "$@" ; do
if [ -n "$branch" ] ; then
cache_path="https://codesrv-ci.cdr.sh/cache/$branch/$tar.tar.gz"
if wget "$cache_path" ; then
tar xzvf "$tar.tar.gz"
break
fi
fi
done
for branch in "$@" ; do
if [ -n "$branch" ] ; then
cache_path="https://codesrv-ci.cdr.sh/cache/$branch/$tar.tar.gz"
if wget "$cache_path" ; then
tar xzvf "$tar.tar.gz"
break
fi
fi
done
}
# We need to cache the built-in extensions and Node modules. Everything inside
# the cache-upload directory will be uploaded as-is to the code-server bucket.
package() {
mkdir -p "cache-upload/cache/$1"
tar czfv "cache-upload/cache/$1/$tar.tar.gz" node_modules source yarn-cache
mkdir -p "cache-upload/cache/$1"
tar czfv "cache-upload/cache/$1/$tar.tar.gz" node_modules yarn-cache \
lib/vscode/.build \
lib/vscode/node_modules
}
main() {
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/.."
# Get the branch for this build.
branch=${DRONE_BRANCH:-${DRONE_SOURCE_BRANCH:-${DRONE_TAG:-}}}
# Get the branch for this build.
branch=${DRONE_BRANCH:-${DRONE_SOURCE_BRANCH:-${DRONE_TAG:-}}}
# The cache will be named based on the arch, platform, and libc.
arch=$DRONE_STAGE_ARCH
platform=${PLATFORM:-linux}
case $DRONE_STAGE_NAME in
*alpine*) libc=musl ;;
* ) libc=glibc ;;
esac
# The cache will be named based on the arch, platform, and libc.
arch=$DRONE_STAGE_ARCH
platform=${PLATFORM:-linux}
case $DRONE_STAGE_NAME in
*alpine*) libc=musl ;;
* ) libc=glibc ;;
esac
tar="$platform-$arch-$libc"
tar="$platform-$arch-$libc"
# The action is determined by the name of the step.
case $DRONE_STEP_NAME in
*restore*) restore "$branch" "$DRONE_REPO_BRANCH" ;;
*rebuild*|*package*) package "$branch" ;;
*) exit 1 ;;
esac
# The action is determined by the name of the step.
case $DRONE_STEP_NAME in
*restore*) restore "$branch" "$DRONE_REPO_BRANCH" ;;
*rebuild*|*package*) package "$branch" ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@ -3,71 +3,62 @@
set -euo pipefail
function target() {
local os=$(uname | tr '[:upper:]' '[:lower:]')
if [[ "$os" == "linux" ]]; then
# Using the same strategy to detect Alpine as build.ts.
local ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
os="alpine"
fi
fi
echo "${os}-$(uname -m)"
}
function main() {
cd "$(dirname "${0}")/.."
cd "$(dirname "${0}")/.."
# Get the version information. If a specific version wasn't set, generate it
# from the tag and VS Code version.
local vscode_version=${VSCODE_VERSION:-1.41.1}
local code_server_version=${VERSION:-${TRAVIS_TAG:-${DRONE_TAG:-daily}}}
local code_server_version=${VERSION:-${TRAVIS_TAG:-${DRONE_TAG:-}}}
if [[ -z $code_server_version ]] ; then
code_server_version=$(grep version ./package.json | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[:space:]')
fi
export VERSION=$code_server_version
# Remove everything that isn't the current VS Code source for caching
# (otherwise the cache will contain old versions).
if [[ -d "source/vscode-$vscode_version-source" ]] ; then
mv "source/vscode-$vscode_version-source" "vscode-$vscode_version-source"
fi
rm -rf source/vscode-*-source
if [[ -d "vscode-$vscode_version-source" ]] ; then
mv "vscode-$vscode_version-source" "source/vscode-$vscode_version-source"
fi
YARN_CACHE_FOLDER="$(pwd)/yarn-cache"
export YARN_CACHE_FOLDER
YARN_CACHE_FOLDER="$(pwd)/yarn-cache"
export YARN_CACHE_FOLDER
# Always minify and package on tags since that's when releases are pushed.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
export MINIFY="true"
export PACKAGE="true"
fi
# Always minify and package on tags since that's when releases are pushed.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
export MINIFY="true"
export PACKAGE="true"
fi
if [[ -z ${SKIP_YARN:-} ]] ; then
yarn
fi
function run-yarn() {
yarn "$1" "$vscode_version" "$code_server_version"
}
yarn build
yarn binary
if [[ -n ${PACKAGE:-} ]] ; then
yarn package
fi
run-yarn build
run-yarn binary
if [[ -n ${PACKAGE:-} ]] ; then
run-yarn package
fi
cd binaries
# In this case provide a plainly named "code-server" binary.
if [[ -n ${BINARY:-} ]] ; then
mv binaries/code-server*-vsc* binaries/code-server
fi
if [[ -n ${STRIP_BIN_TARGET:-} ]] ; then
# In this case provide plainly named binaries.
for binary in code-server* ; do
echo "Moving $binary to code-server"
mv "$binary" code-server
done
elif [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
# Prepare directory for uploading binaries on release.
for binary in code-server* ; do
mkdir -p "../binary-upload"
# Prepare GCS bucket directory on release.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
local gcp_dir="gcs_bucket/releases/$code_server_version/$(target)"
local prefix="code-server-$code_server_version-"
local target="${binary#$prefix}"
if [[ $target == "linux-x86_64" ]] ; then
echo "Copying $binary to ../binary-upload/latest-linux"
cp "$binary" "../binary-upload/latest-linux"
fi
mkdir -p "$gcp_dir"
mv binaries/code-server*-vsc* "$gcp_dir"
if [[ "$(target)" == "linux-x86_64" ]] ; then
mv binaries/code-server*-vsc* "gcs_bucket/latest-linux"
fi
fi
local gcp_dir
gcp_dir="../binary-upload/releases/$code_server_version/$target"
mkdir -p "$gcp_dir"
echo "Copying $binary to $gcp_dir/code-server"
cp "$binary" "$gcp_dir/code-server"
done
fi
}
main "$@"

View File

@ -2,24 +2,24 @@
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget
openssl \
net-tools \
git \
locales \
sudo \
dumb-init \
vim \
curl \
wget
RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables
# configured in /etc/default/locale so we need to set it manually.
ENV LC_ALL=en_US.UTF-8 \
SHELL=/bin/bash
SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder
# Create first so these directories will be owned by coder instead of root

11
scripts/lint.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env sh
# lint.sh -- Lint CSS and JS files.
set -eu
main() {
yarn lint:css "$@"
yarn lint:js "$@"
}
main "$@"

View File

@ -1,71 +0,0 @@
// This must be ran from VS Code's root.
const gulp = require("gulp");
const path = require("path");
const _ = require("underscore");
const buildfile = require("./src/buildfile");
const common = require("./build/lib/optimize");
const util = require("./build/lib/util");
const deps = require("./build/dependencies");
const vscodeEntryPoints = _.flatten([
buildfile.entrypoint("vs/workbench/workbench.web.api"),
buildfile.entrypoint("vs/server/src/node/cli"),
buildfile.base,
buildfile.workbenchWeb,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.entrypoint('vs/platform/files/node/watcher/unix/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/platform/files/node/watcher/nsfw/watcherApp', ["vs/css", "vs/nls"]),
buildfile.entrypoint('vs/workbench/services/extensions/node/extensionHostProcess', ["vs/css", "vs/nls"]),
]);
const vscodeResources = [
"out-build/vs/server/main.js",
"out-build/vs/server/src/node/uriTransformer.js",
"!out-build/vs/server/doc/**",
"out-build/vs/server/src/media/*",
"out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js",
"out-build/bootstrap.js",
"out-build/bootstrap-fork.js",
"out-build/bootstrap-amd.js",
"out-build/paths.js",
'out-build/vs/**/*.{svg,png,html}',
"!out-build/vs/code/browser/workbench/*.html",
'!out-build/vs/code/electron-browser/**',
"out-build/vs/base/common/performance.js",
"out-build/vs/base/node/languagePacks.js",
"out-build/vs/base/browser/ui/octiconLabel/octicons/**",
"out-build/vs/base/browser/ui/codiconLabel/codicon/**",
"out-build/vs/workbench/browser/media/*-theme.css",
"out-build/vs/workbench/contrib/debug/**/*.json",
"out-build/vs/workbench/contrib/externalTerminal/**/*.scpt",
"out-build/vs/workbench/contrib/webview/browser/pre/*.js",
"out-build/vs/**/markdown.css",
"out-build/vs/workbench/contrib/tasks/**/*.json",
"out-build/vs/platform/files/**/*.md",
"!**/test/**"
];
const rootPath = __dirname;
const nodeModules = ["electron", "original-fs"]
.concat(_.uniq(deps.getProductionDependencies(rootPath).map((d) => d.name)))
.concat(_.uniq(deps.getProductionDependencies(path.join(rootPath, "src/vs/server")).map((d) => d.name)))
.concat(Object.keys(process.binding("natives")).filter((n) => !/^_|\//.test(n)));
gulp.task("optimize", gulp.series(
util.rimraf("out-vscode"),
common.optimizeTask({
src: "out-build",
entryPoints: vscodeEntryPoints,
resources: vscodeResources,
loaderConfig: common.loaderConfig(nodeModules),
out: "out-vscode",
inlineAmdImages: true,
bundleInfo: undefined
}),
));
gulp.task("minify", gulp.series(
util.rimraf("out-vscode-min"),
common.minifyTask("out-vscode")
));

View File

@ -1,5 +0,0 @@
{
"name": "code-server",
"main": "out/vs/server/main",
"desktopName": "code-server-url-handler.desktop"
}

10
scripts/postinstall.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env sh
# postinstall.sh - Does nothing at the moment.
set -eu
main() {
cd "$(dirname "${0}")/.."
}
main "$@"

21
scripts/preinstall.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env sh
# preinstall.sh -- Prepare VS Code.
set -eu
main() {
cd "$(dirname "${0}")/.."
# Ensure submodules are cloned and up to date.
git submodule update --init
# Try patching but don't worry too much if it fails. It's possible VS Code has
# already been patched.
yarn patch:apply || echo "Unable to patch; assuming already patched"
# Install VS Code dependencies.
cd ./lib/vscode
yarn
}
main "$@"

View File

@ -1,21 +0,0 @@
{
"nameShort": "code-server",
"nameLong": "code-server",
"applicationName": "code-server",
"dataFolderName": ".code-server",
"win32MutexName": "codeserver",
"win32DirName": "Code Server",
"win32NameVersion": "Code Server",
"win32RegValueName": "CodeServer",
"win32AppId": "",
"win32x64AppId": "",
"win32UserAppId": "",
"win32x64UserAppId": "",
"win32AppUserModelId": "CodeServer",
"win32ShellNameShort": "C&ode Server",
"darwinBundleIdentifier": "com.code.server",
"linuxIconName": "com.code.server",
"urlProtocol": "code-server",
"updateUrl": "https://api.github.com/repos/cdr/code-server/releases",
"quality": "latest"
}

View File

@ -1,19 +0,0 @@
#!/usr/bin/env sh
# test.sh -- Simple test for CI.
# We'll have more involved tests eventually. This just ensures the binary has
# been built and runs.
set -eu
main() {
cd "$(dirname "$0")/.."
version=$(./binaries/code-server* --version | head -1)
echo "Got '$version' for the version"
case $version in
*-vsc1.41.1) exit 0 ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@ -1,17 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"target": "esnext"
}
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./out",
"declaration": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"sourceMap": true
},
"include": [
"./**/*.ts"
]
}

File diff suppressed because it is too large Load Diff