Add NPM package, debs, rpms and refactor CI/build process

Closes many issues that I'll prune after adding more docs
for users.
This commit is contained in:
Anmol Sethi 2020-04-30 07:52:54 -04:00
parent 4875f6aa87
commit be032cf735
No known key found for this signature in database
GPG Key ID: 8CEF1878FF10ADEB
42 changed files with 867 additions and 631 deletions

View File

@ -1,2 +1,3 @@
** **
!release !release-github
!ci

8
.gitignore vendored
View File

@ -1,9 +1,9 @@
*.tsbuildinfo .tsbuildinfo
.cache .cache
build
dist* dist*
out* out*
release/ release/
release-upload/ release-static/
release-github/
release-gcp/
node_modules node_modules
binaries

View File

@ -1,28 +1,33 @@
language: minimal language: node_js
node_js: node
jobs: jobs:
include: include:
- name: Test - name: Test
if: tag IS blank if: tag IS blank
script: ./ci/image/run.sh "yarn && git submodule update --init && yarn vscode:patch && ./ci/ci.sh" script: ./ci/container/exec.sh ./ci/steps/test.sh
deploy: null deploy: null
install: null
- name: Linux Release - name: Linux Release
if: tag IS present if: tag IS present
script: script: ./ci/steps/linux-release.sh
- travis_wait 60 ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh && ./ci/build-test.sh" install: null
- ./ci/release-image/push.sh - name: Linux Release
- name: Linux ARM64 Release
if: tag IS present if: tag IS present
script:
- ./ci/image/run.sh "yarn && yarn vscode && ci/release.sh && ./ci/build-test.sh"
- ./ci/release-image/push.sh
arch: arm64 arch: arm64
script: |
sudo apt-get update && sudo apt-get install -y jq || exit 1
./ci/steps/linux-release.sh
install: null
- name: MacOS Release - name: MacOS Release
if: tag IS present if: tag IS present
os: osx os: osx
language: node_js # node 13/14 crashes in the build process for some reason.
node_js: 12 node_js: 12
script: yarn && yarn vscode && travis_wait 60 ci/release.sh && ./ci/build-test.sh script: |
HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 brew install jq || exit 1
travis_wait 60 ./ci/steps/static-release.sh || exit 1
install: null
before_deploy: before_deploy:
- echo "$JSON_KEY" | base64 --decode > ./ci/key.json - echo "$JSON_KEY" | base64 --decode > ./ci/key.json
@ -36,8 +41,10 @@ deploy:
target_commitish: $TRAVIS_COMMIT target_commitish: $TRAVIS_COMMIT
name: $TRAVIS_TAG name: $TRAVIS_TAG
file: file:
- release/*.tar.gz - release-github/*.tar.gz
- release/*.zip - release-github/*.zip
- release-github/*.deb
- release-github/*.rpm
on: on:
tags: true tags: true
- provider: gcs - provider: gcs
@ -45,14 +52,25 @@ deploy:
bucket: "codesrv-ci.cdr.sh" bucket: "codesrv-ci.cdr.sh"
upload_dir: "releases" upload_dir: "releases"
key_file: ./ci/key.json key_file: ./ci/key.json
local_dir: release-upload local_dir: ./release-gcp
on: on:
tags: true tags: true
# TODO: The gcs provider fails to install on arm64. # TODO: The gcs provider fails to install on arm64.
condition: $TRAVIS_CPU_ARCH = amd64 condition: $TRAVIS_CPU_ARCH == amd64
- provider: script
edge: true
# We do not use the travis npm deploy integration as it does not allow us to
# deploy a subpath and and v2 which should, just errors out that the src does not exist
script: ./ci/steps/publish-npm.sh
on:
tags: true
condition: $TRAVIS_CPU_ARCH == amd64 && $TRAVIS_OS_NAME == linux
cache: cache:
timeout: 600 timeout: 600
yarn: true yarn: true
directories: directories:
- .cache
- out
- dist
- lib/vscode/.build/extensions - lib/vscode/.build/extensions

82
ci/README.md Normal file
View File

@ -0,0 +1,82 @@
# ci
This directory contains scripts used for code-server's continuous integration infrastructure.
Many of these scripts contain more detailed documentation and options in comments at the top.
Any file and directory added into this tree should be documented here.
## dev
This directory contains scripts used for the development of code-server.
- [./dev/container](./dev/container)
- See [CONTRIBUTING.md](../doc/CONTRIBUTING.md) for docs on the development container
- [./dev/ci.sh](./dev/ci.sh) (`yarn ci`)
- Runs formatters, linters and tests
- [./dev/fmt.sh](./dev/fmt.sh) (`yarn fmt`)
- Runs formatters
- [./dev/lint.sh](./dev/lint.sh) (`yarn lint`)
- Runs linters
- [./dev/test.sh](./dev/test.sh) (`yarn test`)
- Runs tests
- [./dev/vscode.sh](./dev/vscode.sh) (`yarn vscode`)
- Ensures `lib/vscode` is cloned, patched and dependencies are installed
- [./dev/vscode.patch](./dev/vscode.patch)
- Our patch of VS Code to enable remote browser access
- Generate it with `yarn vscode:diff` and apply with `yarn vscode:patch`
- [./dev/watch.ts](./dev/watch.ts) (`yarn watch`)
- Starts a process to build and launch code-server and restart on any code changes
- Example usage in [CONTRIBUTING.md](../doc/CONTRIBUTING.md)
## build
This directory contains the scripts used to build code-server.
- [./build/build-code-server.sh](./build/build-code-server.sh) (`yarn build`)
- Builds code-server into ./out and bundles the frontend into ./dist.
- [./build/build-vscode.sh](./build/build-vscode.sh) (`yarn build:vscode`)
- Builds vscode into ./lib/vscode/out-vscode.
- [./build/build-release.sh](./build/build-release.sh) (`yarn release`)
- Bundles the output of the above two scripts into a single node module at ./release.
- Will build a static release with node/node_modules into `./release-static`
if `STATIC=1` is set.
- [./build/clean.sh](./build/clean.sh) (`yarn clean`)
- Removes all git ignored files like build artifacts.
- Will also `git reset --hard lib/vscode`
- Useful to do a clean build.
- [./build/code-server.sh](./build/code-server.sh)
- Copied into static releases to run code-server with the bundled node binary.
- [./build/archive-static-release.sh](./build/archive-static-release.sh)
- Archives `./release-static` into a tar/zip for CI with the proper directory name scheme
- [./build/test-release.sh](./build/test-static-release.sh)
- Ensures code-server in the `./release-static` directory runs
- [./build/build-static-pkgs.sh](./build/build-static-pkgs.sh) (`yarn pkg`)
- Uses [nfpm](https://github.com/goreleaser/nfpm) to generate .deb and .rpm from a static release
- [./build/nfpm.yaml](./build/nfpm.yaml)
- Used to configure [nfpm](https://github.com/goreleaser/nfpm) to generate .deb and .rpm
- [./build/code-server-nfpm.sh](./build/code-server-nfpm.sh)
- Entrypoint script for code-server for .deb and .rpm
## release-container
This directory contains the release docker container.
## container
This directory contains the container for CI.
## steps
This directory contains a few scripts used in CI. Just helps avoid clobbering .travis.yml.
- [./steps/test.sh](./steps/test.sh)
- Runs `yarn ci` after ensuring VS Code is patched
- [./steps/static-release.sh](./steps/static-release.sh)
- Runs the full static build process for CI
- [./steps/linux-release.sh](./steps/linux-release.sh)
- Runs the full static build process for CI
- Packages the release into a .deb and .rpm
- Builds and pushes a docker release
- [./steps/publish-npm.sh](./steps/publish-npm.sh)
- Authenticates yarn and publishes the built package from `./release`

View File

@ -1,372 +0,0 @@
import * as cp from "child_process"
import * as fs from "fs-extra"
import Bundler from "parcel-bundler"
import * as path from "path"
import * as util from "util"
enum Task {
Build = "build",
Watch = "watch",
}
class Builder {
private readonly rootPath = path.resolve(__dirname, "..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private readonly buildPath = path.join(this.rootPath, "build")
private readonly codeServerVersion: string
private currentTask?: Task
public constructor() {
this.ensureArgument("rootPath", this.rootPath)
this.codeServerVersion = this.ensureArgument(
"codeServerVersion",
process.env.VERSION || require(path.join(this.rootPath, "package.json")).version,
)
}
public run(task: Task | undefined): void {
this.currentTask = task
this.doRun(task).catch((error) => {
console.error(error.message)
process.exit(1)
})
}
private async task<T>(message: string, fn: () => Promise<T>): Promise<T> {
const time = Date.now()
this.log(`${message}...`, !process.env.CI)
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
}
}
/**
* 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")
}
}
private async doRun(task: Task | undefined): Promise<void> {
if (!task) {
throw new Error("No task provided")
}
switch (task) {
case Task.Watch:
return this.watch()
case Task.Build:
return this.build()
default:
throw new Error(`No task matching "${task}"`)
}
}
/**
* 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
}
/**
* 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"
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
}
}
})
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)
this.log(`final build: ${this.buildPath}`)
}
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,
})
})
await this.task("bundling code-server", async () => {
return this.createBundler("dist-build", commit).bundle()
})
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")),
])
})
await this.copyDependencies("code-server", this.rootPath, this.buildPath, false, {
commit,
version: this.codeServerVersion,
})
}
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 })
})
await this.task("building builtin extensions", async () => {
const exists = await fs.pathExists(path.join(this.vscodeSourcePath, ".build/extensions"))
if (exists && !process.env.CI) {
process.stdout.write("already built, skipping...")
} else {
await util.promisify(cp.exec)("yarn gulp compile-extensions-build", { cwd: this.vscodeSourcePath })
}
})
await this.task("optimizing vs code", async () => {
return util.promisify(cp.exec)("yarn gulp optimize --gulpfile ./coder.js", { cwd: this.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 vscodeBuildPath = path.join(this.buildPath, "lib/vscode")
await this.task("copying vs code into build directory", async () => {
await fs.mkdirp(path.join(vscodeBuildPath, "resources/linux"))
await Promise.all([
fs.move(
path.join(this.vscodeSourcePath, `out-vscode${process.env.MINIFY ? "-min" : ""}`),
path.join(vscodeBuildPath, "out"),
),
fs.copy(path.join(this.vscodeSourcePath, ".build/extensions"), path.join(vscodeBuildPath, "extensions")),
fs.copy(
path.join(this.vscodeSourcePath, "resources/linux/code.png"),
path.join(vscodeBuildPath, "resources/linux/code.png"),
),
])
})
await this.copyDependencies("vs code", this.vscodeSourcePath, vscodeBuildPath, true, {
commit,
date: new Date().toISOString(),
})
}
private async copyDependencies(
name: string,
sourcePath: string,
buildPath: string,
ignoreScripts: boolean,
merge: object,
): 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))
}),
)
})
const fileName = name === "code-server" ? "package" : "product"
await this.task(`writing final ${name} ${fileName}.json`, async () => {
const json = JSON.parse(await fs.readFile(path.join(sourcePath, `${fileName}.json`), "utf8"))
return fs.writeFile(
path.join(buildPath, `${fileName}.json`),
JSON.stringify(
{
...json,
...merge,
},
null,
2,
),
)
})
if (process.env.MINIFY) {
await this.task(`restricting ${name} to production dependencies`, async () => {
await util.promisify(cp.exec)(`yarn --production ${ignoreScripts ? "--ignore-scripts" : ""}`, {
cwd: buildPath,
})
})
}
}
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"), process.argv.slice(3))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const bundler = this.createBundler()
const cleanup = (code?: number | null): void => {
this.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
this.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (server) {
this.log("killing server")
server.removeAllListeners()
server.kill()
}
this.log("killing bundler")
process.exit(code || 0)
}
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")
})
bundler.on("buildError", (error) => {
console.error("[parcel]", error)
})
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
let startedVscode = 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")) {
if (startedVscode) {
bundle.then(restartServer)
}
startedVscode = true
}
})
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/pages/app.ts"),
path.join(this.rootPath, "src/browser/register.ts"),
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
],
{
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 || "development"}/dist`,
target: "browser",
},
)
}
}
const builder = new Builder()
builder.run(process.argv[2] as Task)

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
# Generates static code-server releases for CI.
# This script assumes that a static release is built already.
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
VERSION="$(pkg_json_version)"
local OS
OS="$(os)"
local ARCH
ARCH="$(arch)"
local archive_name="code-server-$VERSION-$OS-$ARCH"
mkdir -p release-github
local ext
if [[ $OS == "linux" ]]; then
ext=".tar.gz"
tar -czf "release-github/$archive_name$ext" --transform "s/^\.\/release-static/$archive_name/" ./release-static
else
mv ./release-static "./$archive_name"
ext=".zip"
zip -r "release-github/$archive_name$ext" "./$archive_name"
mv "./$archive_name" ./release-static
fi
echo "done (release-github/$archive_name)"
mkdir -p "release-gcp/$VERSION"
cp "release-github/$archive_name$ext" "./release-gcp/$VERSION/$OS-$ARCH$ext"
mkdir -p "release-gcp/latest"
cp "./release-github/$archive_name$ext" "./release-gcp/latest/$OS-$ARCH$ext"
}
main "$@"

29
ci/build/build-code-server.sh Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
# Builds code-server into out and the frontend into dist.
# MINIFY controls whether parcel minifies dist.
MINIFY=${MINIFY-true}
main() {
cd "$(dirname "${0}")/../.."
npx tsc --outDir out --tsBuildInfoFile .cache/out.tsbuildinfo
# If out/node/entry.js does not already have the shebang,
# we make sure to add it and make it executable.
if ! grep -q -m1 "^#!/usr/bin/env node" out/node/entry.js; then
sed -i.bak "1s;^;#!/usr/bin/env node\n;" out/node/entry.js && rm out/node/entry.js.bak
chmod +x out/node/entry.js
fi
npx parcel build \
--public-url "/static/$(git rev-parse HEAD)/dist" \
--out-dir dist \
$([[ $MINIFY ]] || echo --no-minify) \
src/browser/pages/app.ts \
src/browser/register.ts \
src/browser/serviceWorker.ts
}
main "$@"

127
ci/build/build-release.sh Executable file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -euo pipefail
# This script requires code-server and vscode to be built with
# matching MINIFY.
# RELEASE_PATH is the destination directory for the release from the root.
# Defaults to release
RELEASE_PATH="${RELEASE_PATH-release}"
# STATIC controls whether node and node_modules are packaged into the release.
# Disabled by default.
STATIC="${STATIC-}"
# MINIFY controls whether minified vscode is bundled and whether
# any included node_modules are pruned for production.
MINIFY="${MINIFY-true}"
VSCODE_SRC_PATH="lib/vscode"
VSCODE_OUT_PATH="$RELEASE_PATH/lib/vscode"
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
mkdir -p "$RELEASE_PATH"
bundle_code_server
bundle_vscode
rsync README.md "$RELEASE_PATH"
rsync LICENSE.txt "$RELEASE_PATH"
rsync ./lib/vscode/ThirdPartyNotices.txt "$RELEASE_PATH"
if [[ $STATIC ]]; then
rsync "$RELEASE_PATH/" "$RELEASE_PATH-static"
RELEASE_PATH+=-static
VSCODE_OUT_PATH="$RELEASE_PATH/lib/vscode"
bundle_node
else
rm -Rf "$VSCODE_OUT_PATH/extensions/node_modules"
fi
}
rsync() {
command rsync -a --del "$@"
}
bundle_code_server() {
rsync out dist "$RELEASE_PATH"
# For source maps and images.
mkdir -p "$RELEASE_PATH/src/browser"
rsync src/browser/media/ "$RELEASE_PATH/src/browser/media"
mkdir -p "$RELEASE_PATH/src/browser/pages"
rsync src/browser/pages/*.html "$RELEASE_PATH/src/browser/pages"
rsync yarn.lock "$RELEASE_PATH"
# Adds the commit to package.json
jq --slurp '.[0] * .[1]' package.json <(
cat << EOF
{
"commit": "$(git rev-parse HEAD)",
"scripts": {
"postinstall": "cd lib/vscode && yarn --production && cd extensions && yarn --production"
}
}
EOF
) > "$RELEASE_PATH/package.json"
}
bundle_vscode() {
mkdir -p "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/out-vscode${MINIFY+-min}/" "$VSCODE_OUT_PATH/out"
rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
mkdir -p "$VSCODE_OUT_PATH/resources/linux"
rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png"
rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH"
# Adds the commit and date to product.json
jq --slurp '.[0] * .[1]' "$VSCODE_SRC_PATH/product.json" <(
cat << EOF
{
"commit": "$(git rev-parse HEAD)",
"date": $(jq -n 'now | todate')
}
EOF
) > "$VSCODE_OUT_PATH/product.json"
# We remove the scripts field so that later on we can run
# yarn to fetch node_modules if necessary without build scripts
# being ran.
# We cannot use --no-scripts because we still want dependant package scripts to run
# for native modules to be rebuilt.
jq 'del(.scripts)' < "$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json"
}
bundle_node() {
# We cannot find the path to node from $PATH because yarn shims a script to ensure
# we use the same version it's using so we instead run a script with yarn that
# will print the path to node.
local node_path
node_path="$(yarn -s node <<< 'console.info(process.execPath)')"
mkdir -p "$RELEASE_PATH/bin"
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
rsync "$node_path" "$RELEASE_PATH/lib/node"
rsync node_modules "$RELEASE_PATH"
rsync "$VSCODE_SRC_PATH/node_modules" "$VSCODE_OUT_PATH"
if [[ $MINIFY ]]; then
pushd "$RELEASE_PATH"
yarn --production
popd
fi
}
main "$@"

24
ci/build/build-static-pkgs.sh Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
# Generates deb and rpm packages for CI.
# Assumes a static release has already been built.
main() {
cd "$(dirname "${0}")/../.."
source ./ci/lib.sh
VERSION="$(pkg_json_version)"
export VERSION
ARCH="$(arch)"
export ARCH
local nfpm_config
nfpm_config=$(envsubst < ./ci/build/nfpm.yaml)
nfpm pkg -f <(echo "$nfpm_config") --target release-github/code-server-"$VERSION-$ARCH.deb"
nfpm pkg -f <(echo "$nfpm_config") --target release-github/code-server-"$VERSION-$ARCH.rpm"
}
main "$@"

21
ci/build/build-vscode.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
# Builds vscode into lib/vscode/out-vscode.
# MINIFY controls whether a minified version of vscode is built.
MINIFY=${MINIFY-true}
main() {
cd "$(dirname "${0}")/../.."
cd lib/vscode
yarn gulp compile-build
yarn gulp compile-extensions-build
yarn gulp optimize --gulpfile ./coder.js
if [[ $MINIFY ]]; then
yarn gulp minify --gulpfile ./coder.js
fi
}
main "$@"

View File

@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
main() { main() {
cd "$(dirname "${0}")/../.."
git clean -Xffd git clean -Xffd
git submodule foreach --recursive git clean -xffd git submodule foreach --recursive git clean -xffd
git submodule foreach --recursive git reset --hard git submodule foreach --recursive git reset --hard

3
ci/build/code-server-nfpm.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env sh
exec /usr/lib/code-server/bin/code-server "$@"

View File

@ -1,18 +1,20 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# This script is intended to be bundled into the static releases.
# Runs code-server with the bundled Node binary. # Runs code-server with the bundled Node binary.
# More complicated than readlink -f or realpath to support macOS. # More complicated than readlink -f or realpath to support macOS.
# See https://github.com/cdr/code-server/issues/1537 # See https://github.com/cdr/code-server/issues/1537
get_installation_dir() { bin_dir() {
# We read the symlink, which may be relative from $0. # We read the symlink, which may be relative from $0.
dst="$(readlink "$0")" dst="$(readlink "$0")"
# We cd into the $0 directory. # We cd into the $0 directory.
cd "$(dirname "$0")" cd "$(dirname "$0")" || exit 1
# Now we can cd into the dst directory. # Now we can cd into the dst directory.
cd "$(dirname "$dst")" cd "$(dirname "$dst")" || exit 1
# Finally we use pwd -P to print the absolute path of the directory of $dst. # Finally we use pwd -P to print the absolute path of the directory of $dst.
pwd -P pwd -P || exit 1
} }
dir=$(get_installation_dir) BIN_DIR=$(bin_dir)
exec "$dir/node" "$dir/out/node/entry.js" "$@" exec "$BIN_DIR/../lib/node" "$BIN_DIR/.." "$@"

16
ci/build/nfpm.yaml Normal file
View File

@ -0,0 +1,16 @@
name: "code-server"
arch: "${ARCH}"
platform: "linux"
version: "v${VERSION}"
section: "devel"
priority: "optional"
maintainer: "Anmol Sethi <hi@nhooyr.io>"
description: |
Run VS Code in the browser.
vendor: "Coder"
homepage: "https://github.com/cdr/code-server"
license: "MIT"
bindir: "/usr/bin"
files:
./ci/build/code-server-nfpm.sh: /usr/bin/code-server
./release-static/**/*: "/usr/lib/code-server/"

View File

@ -1,21 +1,20 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# build-test.bash -- Make sure the build worked.
# This is to make sure we don't have Node version errors or any other
# compilation-related errors.
set -euo pipefail set -euo pipefail
function main() { # Makes sure the release works.
cd "$(dirname "${0}")/.." || exit 1 # This is to make sure we don't have Node version errors or any other
# compilation-related errors.
main() {
cd "$(dirname "${0}")/../.."
local output local output
output=$(node ./build/out/node/entry.js --list-extensions 2>&1) output=$(./release-static/bin/code-server --list-extensions 2>&1)
if echo "$output" | grep 'was compiled against a different Node.js version'; then if echo "$output" | grep 'was compiled against a different Node.js version'; then
echo "$output" echo "$output"
exit 1 exit 1
else
echo "Build ran successfully"
fi fi
echo "Build ran successfully"
} }
main "$@" main "$@"

30
ci/container/Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM centos:7
RUN yum update -y && yum install -y \
devtoolset-6 \
gcc-c++ \
xz \
ccache \
git \
wget \
openssl \
libxkbfile-devel \
libsecret-devel \
libx11-devel \
gettext
RUN yum install -y epel-release && \
yum install -y ShellCheck jq golang
RUN go get github.com/goreleaser/nfpm/cmd/nfpm
ENV PATH=$PATH:/root/go/bin
RUN mkdir /usr/share/node && cd /usr/share/node \
&& curl "https://nodejs.org/dist/v12.16.3/node-v12.16.3-linux-$(uname -m | sed 's/86_//; s/aarch/arm/').tar.xz" | tar xJ --strip-components=1 --
ENV PATH "$PATH:/usr/share/node/bin"
RUN npm install -g yarn@1.22.4
RUN curl -L "https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_$(uname -m | sed 's/x86_/amd/; s/aarch64/arm/')" > /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/shfmt
ENTRYPOINT ["/bin/bash", "-c"]

24
ci/container/exec.sh Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
docker build ci/container
imageTag="$(docker build -q ci/container)"
docker run \
--rm \
-e CI \
-e GITHUB_TOKEN \
-e TRAVIS_TAG \
-e NPM_TOKEN \
-v "$(yarn cache dir):/usr/local/share/.cache/yarn/v6" \
$(if [[ -f ~/.npmrc ]]; then echo -v "$HOME/.npmrc:/root/.npmrc"; fi) \
-v "$PWD:/repo" \
-w /repo \
$(if [[ -t 0 ]]; then echo -it; fi) \
"$imageTag" \
"$*"
}
main "$@"

View File

@ -1,49 +0,0 @@
#!/usr/bin/env bash
# exec.sh opens an interactive bash session inside of a docker container
# for improved isolation during development
# if the container exists it is restarted if necessary, then reused
set -euo pipefail
cd "$(dirname "$0")"
# Ensure submodules are cloned and up to date.
git submodule update --init
container_name=code-server-dev
enter() {
echo "--- Entering $container_name"
docker exec -it $container_id /bin/bash
}
run() {
echo "--- Spawning $container_name"
container_id=$(docker run \
-it \
--name $container_name \
"-v=$PWD:/code-server" \
"-w=/code-server" \
"-p=127.0.0.1:8080:8080" \
$([[ -t 0 ]] && echo -it || true) \
$container_name)
}
build() {
echo "--- Building $container_name"
cd ../../
docker build -t $container_name -f ./ci/dev-image/Dockerfile . > /dev/null
}
container_id=$(docker container inspect --format="{{.Id}}" $container_name 2> /dev/null) || true
if [ "$container_id" != "" ]; then
echo "-- Starting container"
docker start $container_id > /dev/null
enter
exit 0
fi
build
run
enter

View File

@ -2,7 +2,7 @@
set -euo pipefail set -euo pipefail
main() { main() {
cd "$(dirname "$0")/.." cd "$(dirname "$0")/../.."
yarn fmt yarn fmt
yarn lint yarn lint

48
ci/dev/container/exec.sh Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
# Opens an interactive bash session inside of a docker container
# for improved isolation during development.
# If the container exists it is restarted if necessary, then reused.
main() {
cd "$(dirname "${0}")/../../.."
local container_name=code-server-dev
if docker inspect $container_name &> /dev/null; then
echo "-- Starting container"
docker start "$container_name" > /dev/null
enter
exit 0
fi
build
run
enter
}
enter() {
echo "--- Entering $container_name"
docker exec -it "$container_name" /bin/bash
}
run() {
echo "--- Spawning $container_name"
docker run \
-it \
--name $container_name \
"-v=$PWD:/code-server" \
"-w=/code-server" \
"-p=127.0.0.1:8080:8080" \
$(if [[ -t 0 ]]; then echo -it; fi) \
"$container_name"
}
build() {
echo "--- Building $container_name"
docker build -t $container_name ./ci/dev/container > /dev/null
}
main "$@"

View File

@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.."
shfmt -i 2 -w -s -sr $(git ls-files "*.sh") shfmt -i 2 -w -s -sr $(git ls-files "*.sh")
local prettierExts local prettierExts

View File

@ -1,11 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.."
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js") eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js")
stylelint $(git ls-files "*.css") stylelint $(git ls-files "*.css")
tsc --noEmit tsc --noEmit
shellcheck -e SC2046,SC2164 $(git ls-files "*.sh")
} }
main "$@" main "$@"

10
ci/dev/test.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
mocha -r ts-node/register ./test/*.test.ts
}
main "$@"

View File

@ -50,7 +50,7 @@ index 7a2320d828..5768890636 100644
yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron
diff --git a/coder.js b/coder.js diff --git a/coder.js b/coder.js
new file mode 100644 new file mode 100644
index 0000000000..d0a8f37714 index 0000000000..0170b47241
--- /dev/null --- /dev/null
+++ b/coder.js +++ b/coder.js
@@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
@ -83,7 +83,7 @@ index 0000000000..d0a8f37714
+ "out-build/bootstrap-fork.js", + "out-build/bootstrap-fork.js",
+ "out-build/bootstrap-amd.js", + "out-build/bootstrap-amd.js",
+ "out-build/paths.js", + "out-build/paths.js",
+ 'out-build/vs/**/*.{svg,png,html}', + 'out-build/vs/**/*.{svg,png,html,ttf}',
+ "!out-build/vs/code/browser/workbench/*.html", + "!out-build/vs/code/browser/workbench/*.html",
+ '!out-build/vs/code/electron-browser/**', + '!out-build/vs/code/electron-browser/**',
+ "out-build/vs/base/common/performance.js", + "out-build/vs/base/common/performance.js",

View File

@ -5,7 +5,7 @@ set -euo pipefail
# 2. Patches it. # 2. Patches it.
# 3. Installs it. # 3. Installs it.
main() { main() {
cd "$(dirname "$0")/.." cd "$(dirname "$0")/../.."
git submodule update --init git submodule update --init

163
ci/dev/watch.ts Normal file
View File

@ -0,0 +1,163 @@
import * as cp from "child_process"
import Bundler from "parcel-bundler"
import * as path from "path"
async function main(): Promise<void> {
try {
const watcher = new Watcher()
await watcher.watch()
} catch (error) {
console.error(error.message)
process.exit(1)
}
}
class Watcher {
private readonly rootPath = path.resolve(__dirname, "../..")
private readonly vscodeSourcePath = path.join(this.rootPath, "lib/vscode")
private static log(message: string, skipNewline = false): void {
process.stdout.write(message)
if (!skipNewline) {
process.stdout.write("\n")
}
}
public 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"), process.argv.slice(2))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const bundler = this.createBundler()
const cleanup = (code?: number | null): void => {
Watcher.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
Watcher.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (server) {
Watcher.log("killing server")
server.removeAllListeners()
server.kill()
}
Watcher.log("killing bundler")
process.exit(code || 0)
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
Watcher.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
Watcher.log("tsc terminated unexpectedly")
cleanup(code)
})
const bundle = bundler.bundle().catch(() => {
Watcher.log("parcel watcher terminated unexpectedly")
cleanup(1)
})
bundler.on("buildEnd", () => {
console.log("[parcel] bundled")
})
bundler.on("buildError", (error) => {
console.error("[parcel]", error)
})
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
let startedVscode = 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")) {
if (startedVscode) {
bundle.then(restartServer)
}
startedVscode = true
}
})
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"): Bundler {
return new Bundler(
[
path.join(this.rootPath, "src/browser/pages/app.ts"),
path.join(this.rootPath, "src/browser/register.ts"),
path.join(this.rootPath, "src/browser/serviceWorker.ts"),
],
{
outDir: path.join(this.rootPath, out),
cacheDir: path.join(this.rootPath, ".cache"),
minify: !!process.env.MINIFY,
logLevel: 1,
publicUrl: "/static/development/dist",
},
)
}
}
main()

View File

@ -1,23 +0,0 @@
FROM centos:7
RUN yum update -y && yum install -y \
devtoolset-6 \
gcc-c++ \
xz \
ccache \
git \
wget \
openssl \
libxkbfile-devel \
libsecret-devel \
libx11-devel
RUN mkdir /usr/share/node && cd /usr/share/node \
&& curl "https://nodejs.org/dist/v12.16.3/node-v12.16.3-linux-$(uname -m | sed 's/86_//; s/aarch/arm/').tar.xz" | tar xJ --strip-components=1 --
ENV PATH "$PATH:/usr/share/node/bin"
RUN npm install -g yarn@1.22.4
RUN curl -L "https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_$(uname -m | sed 's/x86_/amd/; s/aarch64/arm/')" > /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/shfmt
ENTRYPOINT ["/bin/bash", "-c"]

View File

@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
# This, strangely enough, fixes the arm build being terminated for not having
# output on Travis. It's as if output is buffered and only displayed once a
# certain amount is collected. Five seconds didn't work but one second seems
# to generate enough output to make it work.
local pid
while true; do
echo 'Still running...'
sleep 1
done &
pid=$!
docker build ci/image
imageTag="$(docker build -q ci/image)"
docker run -t --rm -e CI -e GITHUB_TOKEN -e TRAVIS_TAG -v "$(yarn cache dir):/usr/local/share/.cache/yarn/v6" -v "$PWD:/repo" -w /repo "$imageTag" "$*"
kill $pid
}
main "$@"

47
ci/lib.sh Normal file → Executable file
View File

@ -1,10 +1,43 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail
set_version() { pushd() {
local code_server_version=${VERSION:-${TRAVIS_TAG:-}} builtin pushd "$@" > /dev/null
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 popd() {
export VERSION=$code_server_version builtin popd > /dev/null
}
pkg_json_version() {
jq -r .version package.json
}
os() {
local os
os=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $os == "linux" ]]; then
# 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.
local ldd_output
ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
os="alpine"
fi
fi
echo "$os"
}
arch() {
case "$(uname -m)" in
aarch64)
echo arm64
;;
x86_64)
echo amd64
;;
*)
echo "unknown architecture $(uname -a)"
exit 1
;;
esac
} }

View File

@ -13,6 +13,7 @@ RUN apt-get update \
ssh \ ssh \
sudo \ sudo \
vim \ vim \
lsb-release \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# https://wiki.debian.org/Locale#Manually # https://wiki.debian.org/Locale#Manually
@ -26,18 +27,20 @@ ENV SHELL=/bin/bash
RUN adduser --gecos '' --disabled-password coder && \ 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
RUN curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.4/fixuid-0.4-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \ SHELL ["/bin/bash", "-c"]
COPY ci/lib.sh /tmp/lib.sh
RUN source /tmp/lib.sh && rm /tmp/lib.sh && \
curl -L "https://github.com/boxboat/fixuid/releases/download/v0.4.1/fixuid-0.4.1-linux-$(arch).tar.gz" | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \ chown root:root /usr/local/bin/fixuid && \
chmod 4755 /usr/local/bin/fixuid && \ chmod 4755 /usr/local/bin/fixuid && \
mkdir -p /etc/fixuid && \ mkdir -p /etc/fixuid && \
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
COPY release/code-server*.tar.gz /tmp/ COPY release-github/code-server*.deb /tmp/
RUN cd /tmp && tar -xzf code-server*.tar.gz && rm code-server*.tar.gz && \ RUN dpkg -i /tmp/code-server*.deb && rm /tmp/code-server*.deb
mv code-server* /usr/local/lib/code-server && \
ln -s /usr/local/lib/code-server/code-server /usr/local/bin/code-server
EXPOSE 8080 EXPOSE 8080
USER coder USER coder
WORKDIR /home/coder WORKDIR /home/coder
ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/local/bin/code-server", "--bind-addr", "0.0.0.0:8080", "."] ENTRYPOINT ["dumb-init", "fixuid", "-q", "/usr/bin/code-server", "--bind-addr", "0.0.0.0:8080", "."]

View File

@ -5,18 +5,21 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
set_version VERSION="$(pkg_json_version)"
if [[ ${CI:-} ]]; then if [[ ${CI-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi fi
imageTag="codercom/code-server:$VERSION" imageTag="codercom/code-server:$VERSION"
if [[ ${TRAVIS_CPU_ARCH:-} == "arm64" ]]; then if [[ $(arch) == "arm64" ]]; then
imageTag+="-arm64" imageTag+="-arm64"
fi fi
docker build -t "$imageTag" -f ./ci/release-image/Dockerfile .
docker push codercom/code-server docker build \
-t "$imageTag" \
-f ./ci/release-container/Dockerfile .
docker push "$imageTag"
} }
main "$@" main "$@"

View File

@ -1,76 +0,0 @@
#!/usr/bin/env bash
# ci.bash -- Build code-server in the CI.
set -euo pipefail
function package() {
local target
target=$(uname | tr '[:upper:]' '[:lower:]')
if [[ $target == "linux" ]]; then
# 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.
local ldd_output
ldd_output=$(ldd --version 2>&1 || true)
if echo "$ldd_output" | grep -iq musl; then
target="alpine"
fi
fi
local arch
arch=$(uname -m | sed 's/aarch/arm/')
echo -n "Creating release..."
cp "$(command -v node)" ./build
cp README.md ./build
cp LICENSE.txt ./build
cp ./lib/vscode/ThirdPartyNotices.txt ./build
cp ./ci/code-server.sh ./build/code-server
local archive_name="code-server-$VERSION-$target-$arch"
mkdir -p ./release
local ext
if [[ $target == "linux" ]]; then
ext=".tar.gz"
tar -czf "release/$archive_name$ext" --transform "s/^\.\/build/$archive_name/" ./build
else
mv ./build "./$archive_name"
ext=".zip"
zip -r "release/$archive_name$ext" "./$archive_name"
mv "./$archive_name" ./build
fi
echo "done (release/$archive_name)"
# release-upload is for uploading to the GCP bucket whereas release is used for GitHub.
mkdir -p "./release-upload/$VERSION"
cp "./release/$archive_name$ext" "./release-upload/$VERSION/$target-$arch$ext"
mkdir -p "./release-upload/latest"
cp "./release/$archive_name$ext" "./release-upload/latest/$target-$arch$ext"
}
# This script assumes that yarn has already ran.
function build() {
# Always minify and package on CI.
if [[ ${CI:-} ]]; then
export MINIFY="true"
fi
yarn build
}
function main() {
cd "$(dirname "${0}")/.."
source ./ci/lib.sh
set_version
build
if [[ ${CI:-} ]]; then
package
fi
}
main "$@"

26
ci/steps/linux-release.sh Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
source ./ci/lib.sh
if [[ $(arch) == arm64 ]]; then
# This, strangely enough, fixes the arm build being terminated for not having
# output on Travis. It's as if output is buffered and only displayed once a
# certain amount is collected. Five seconds didn't work but one second seems
# to generate enough output to make it work.
while true; do
echo 'Still running...'
sleep 1
done &
trap "exit" INT TERM
trap "kill 0" EXIT
fi
./ci/container/exec.sh ./ci/steps/static-release.sh
./ci/container/exec.sh yarn pkg
./ci/release-container/push.sh
}
main "$@"

11
ci/steps/publish-npm.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
./ci/container/exec.sh yarn publish --non-interactive release
}
main "$@"

16
ci/steps/static-release.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn
yarn vscode
yarn build
yarn build:vscode
STATIC=1 yarn release
./ci/build/test-static-release.sh
./ci/build/archive-static-release.sh
}
main "$@"

17
ci/steps/test.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
main() {
cd "$(dirname "$0")/../.."
yarn
git submodule update --init
# We do not `yarn vscode` to make test.sh faster.
# If the patch fails to apply, then it's likely already applied
yarn vscode:patch &> /dev/null || true
yarn ci
}
main "$@"

View File

@ -1,4 +0,0 @@
{
"extends": "../tsconfig.json",
"include": ["./**/*.ts"]
}

View File

@ -1,5 +1,8 @@
# Contributing # Contributing
- [Detailed CI and build process docs](../ci)
- [Our VS Code Web docs](../src/node/app)
## Development Workflow ## Development Workflow
- [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites) - [VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
@ -13,7 +16,7 @@ yarn watch # Visit http://localhost:8080 once completed.
To develop inside of an isolated docker container: To develop inside of an isolated docker container:
```shell ```shell
./ci/dev-image/exec.sh ./ci/dev/container/exec.sh
root@12345:/code-server# yarn root@12345:/code-server# yarn
root@12345:/code-server# yarn vscode root@12345:/code-server# yarn vscode
@ -36,5 +39,7 @@ works internally.
yarn yarn
yarn vscode yarn vscode
yarn build yarn build
node ./build/out/node/entry.js # Run the built JavaScript with Node. yarn build:vscode
yarn release
node ./release # Run the built JavaScript with Node.
``` ```

View File

@ -1,19 +1,30 @@
{ {
"name": "code-server", "name": "code-server",
"license": "MIT", "license": "MIT",
"version": "3.3.0", "version": "3.3.0-rc.7",
"scripts": { "description": "Run VS Code on a remote server.",
"clean": "ci/clean.sh", "homepage": "https://github.com/cdr/code-server",
"vscode": "ci/vscode.sh", "bugs": {
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/vscode.patch", "url": "https://github.com/cdr/code-server/issues"
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/vscode.patch",
"test": "mocha -r ts-node/register ./test/*.test.ts",
"lint": "ci/lint.sh",
"fmt": "ci/fmt.sh",
"runner": "cd ./ci && NODE_OPTIONS=--max_old_space_size=32384 ts-node ./build.ts",
"build": "yarn runner build",
"watch": "yarn runner watch"
}, },
"repository": "https://github.com/cdr/code-server",
"scripts": {
"clean": "./ci/build/clean.sh",
"vscode": "./ci/dev/vscode.sh",
"vscode:patch": "cd ./lib/vscode && git apply ../../ci/dev/vscode.patch",
"vscode:diff": "cd ./lib/vscode && git diff HEAD > ../../ci/dev/vscode.patch",
"build": "./ci/build/build-code-server.sh",
"build:vscode": "./ci/build/build-vscode.sh",
"release": "./ci/build/build-release.sh",
"pkg": "./ci/build/build-static-pkgs.sh",
"_____": "",
"fmt": "./ci/dev/fmt.sh",
"lint": "./ci/dev/lint.sh",
"test": "./ci/dev/test.sh",
"ci": "./ci/dev/ci.sh",
"watch": "NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts"
},
"main": "out/node/entry.js",
"devDependencies": { "devDependencies": {
"@types/adm-zip": "^0.4.32", "@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
@ -40,7 +51,8 @@
"stylelint": "^13.0.0", "stylelint": "^13.0.0",
"stylelint-config-recommended": "^3.0.0", "stylelint-config-recommended": "^3.0.0",
"ts-node": "^8.4.1", "ts-node": "^8.4.1",
"typescript": "3.7.2" "typescript": "3.7.2",
"yarn": "^1.22.4"
}, },
"resolutions": { "resolutions": {
"@types/node": "^12.12.7", "@types/node": "^12.12.7",
@ -48,7 +60,7 @@
"vfile-message": "^2.0.2" "vfile-message": "^2.0.2"
}, },
"dependencies": { "dependencies": {
"@coder/logger": "1.1.11", "@coder/logger": "1.1.14",
"adm-zip": "^0.4.14", "adm-zip": "^0.4.14",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"http-proxy": "^1.18.0", "http-proxy": "^1.18.0",
@ -60,5 +72,16 @@
"tar": "^6.0.1", "tar": "^6.0.1",
"tar-fs": "^2.0.0", "tar-fs": "^2.0.0",
"ws": "^7.2.0" "ws": "^7.2.0"
} },
"bin": {
"code-server": "out/node/entry.js"
},
"keywords": [
"vscode",
"development",
"ide",
"coder",
"vscode-remote",
"browser-ide"
]
} }

View File

@ -1,3 +1,5 @@
# app
Implementation of [VS Code](https://code.visualstudio.com/) remote/web for use Implementation of [VS Code](https://code.visualstudio.com/) remote/web for use
in `code-server`. in `code-server`.

View File

@ -792,10 +792,10 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@coder/logger@1.1.11": "@coder/logger@1.1.14":
version "1.1.11" version "1.1.14"
resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.11.tgz#e6f36dba9436ae61e66e3f66787d75c768617605" resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.1.14.tgz#0242da33e0245834361dd078e31280fc1c976b7e"
integrity sha512-EEh1dqSU0AaqjjjMsVqumgZGbrZimKFKIb4t5E6o3FLfVUxJCReSME78Yj2N1xWUVAHMnqafDCxLostpuIotzw== integrity sha512-NuTvsOH3dqrXn/8Pbs5zy7l0gLqOSC/TPRl3nexdP/897lgG/vtHNQHrUwTBTzTzihH1ON4lklDxJjY0hD4UPg==
"@iarna/toml@^2.2.0": "@iarna/toml@^2.2.0":
version "2.2.5" version "2.2.5"
@ -7471,6 +7471,11 @@ yargs@^14.0.0:
y18n "^4.0.0" y18n "^4.0.0"
yargs-parser "^15.0.1" yargs-parser "^15.0.1"
yarn@^1.22.4:
version "1.22.4"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.4.tgz#01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e"
integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA==
yn@3.1.1: yn@3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"