diff --git a/src/node/cli.ts b/src/node/cli.ts index 6e6fb5de0..51dc14d09 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -1,6 +1,7 @@ import { field, Level, logger } from "@coder/logger" import * as fs from "fs-extra" import yaml from "js-yaml" +import * as os from "os" import * as path from "path" import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc" import { AuthType } from "./http" @@ -151,12 +152,12 @@ export const optionDescriptions = (): string[] => { ) } -export const parse = ( +export const parse = async ( argv: string[], opts?: { configFile: string }, -): Args => { +): Promise => { const error = (msg: string): Error => { if (opts?.configFile) { msg = `error reading ${opts.configFile}: ${msg}` @@ -300,6 +301,7 @@ export const parse = ( } if (!args["user-data-dir"]) { + await copyOldMacOSDataDir() args["user-data-dir"] = paths.data } @@ -351,7 +353,7 @@ export async function readConfigFile(configPath?: string): Promise { } return `--${optName}=${opt}` }) - const args = parse(configFileArgv, { + const args = await parse(configFileArgv, { configFile: configPath, }) return { @@ -400,3 +402,18 @@ export function bindAddrFromAllSources(cliArgs: Args, configArgs: Args): [string return [addr.host, addr.port] } + +async function copyOldMacOSDataDir(): Promise { + if (os.platform() !== "darwin") { + return + } + if (await fs.pathExists(paths.data)) { + return + } + + // If the old data directory exists, we copy it in. + const oldDataDir = path.join(os.homedir(), "Library/Application Support", "code-server") + if (await fs.pathExists(oldDataDir)) { + await fs.copy(oldDataDir, paths.data) + } +} diff --git a/src/node/entry.ts b/src/node/entry.ts index 755125d3a..d493b1313 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -125,58 +125,62 @@ const main = async (cliArgs: Args): Promise => { } } -const tryParse = (): Args => { - try { - return parse(process.argv.slice(2)) - } catch (error) { - console.error(error.message) - process.exit(1) +async function entry(): Promise { + const tryParse = async (): Promise => { + try { + return await parse(process.argv.slice(2)) + } catch (error) { + console.error(error.message) + process.exit(1) + } + } + + const args = await tryParse() + if (args.help) { + console.log("code-server", version, commit) + console.log("") + console.log(`Usage: code-server [options] [path]`) + console.log("") + console.log("Options") + optionDescriptions().forEach((description) => { + console.log("", description) + }) + } else if (args.version) { + if (args.json) { + console.log({ + codeServer: version, + commit, + vscode: require("../../lib/vscode/package.json").version, + }) + } else { + console.log(version, commit) + } + process.exit(0) + } else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { + logger.debug("forking vs code cli...") + const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { + env: { + ...process.env, + CODE_SERVER_PARENT_PID: process.pid.toString(), + }, + }) + vscode.once("message", (message) => { + logger.debug("Got message from VS Code", field("message", message)) + if (message.type !== "ready") { + logger.error("Unexpected response waiting for ready response") + process.exit(1) + } + const send: CliMessage = { type: "cli", args } + vscode.send(send) + }) + vscode.once("error", (error) => { + logger.error(error.message) + process.exit(1) + }) + vscode.on("exit", (code) => process.exit(code || 0)) + } else { + wrap(() => main(args)) } } -const args = tryParse() -if (args.help) { - console.log("code-server", version, commit) - console.log("") - console.log(`Usage: code-server [options] [path]`) - console.log("") - console.log("Options") - optionDescriptions().forEach((description) => { - console.log("", description) - }) -} else if (args.version) { - if (args.json) { - console.log({ - codeServer: version, - commit, - vscode: require("../../lib/vscode/package.json").version, - }) - } else { - console.log(version, commit) - } - process.exit(0) -} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) { - logger.debug("forking vs code cli...") - const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], { - env: { - ...process.env, - CODE_SERVER_PARENT_PID: process.pid.toString(), - }, - }) - vscode.once("message", (message) => { - logger.debug("Got message from VS Code", field("message", message)) - if (message.type !== "ready") { - logger.error("Unexpected response waiting for ready response") - process.exit(1) - } - const send: CliMessage = { type: "cli", args } - vscode.send(send) - }) - vscode.once("error", (error) => { - logger.error(error.message) - process.exit(1) - }) - vscode.on("exit", (code) => process.exit(code || 0)) -} else { - wrap(() => main(args)) -} +entry() diff --git a/test/cli.test.ts b/test/cli.test.ts index 52c9fb61f..0c7df366b 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -16,13 +16,13 @@ describe("cli", () => { "user-data-dir": paths.data, } - it("should set defaults", () => { - assert.deepEqual(parse([]), defaults) + it("should set defaults", async () => { + assert.deepEqual(await await parse([]), defaults) }) - it("should parse all available options", () => { + it("should parse all available options", async () => { assert.deepEqual( - parse([ + await await parse([ "--bind-addr=192.169.0.1:8080", "--auth", "none", @@ -84,8 +84,8 @@ describe("cli", () => { ) }) - it("should work with short options", () => { - assert.deepEqual(parse(["-vvv", "-v"]), { + it("should work with short options", async () => { + assert.deepEqual(await parse(["-vvv", "-v"]), { ...defaults, log: "trace", verbose: true, @@ -95,9 +95,9 @@ describe("cli", () => { assert.equal(logger.level, Level.Trace) }) - it("should use log level env var", () => { + it("should use log level env var", async () => { process.env.LOG_LEVEL = "debug" - assert.deepEqual(parse([]), { + assert.deepEqual(await parse([]), { ...defaults, log: "debug", }) @@ -105,7 +105,7 @@ describe("cli", () => { assert.equal(logger.level, Level.Debug) process.env.LOG_LEVEL = "trace" - assert.deepEqual(parse([]), { + assert.deepEqual(await parse([]), { ...defaults, log: "trace", verbose: true, @@ -114,9 +114,9 @@ describe("cli", () => { assert.equal(logger.level, Level.Trace) }) - it("should prefer --log to env var and --verbose to --log", () => { + it("should prefer --log to env var and --verbose to --log", async () => { process.env.LOG_LEVEL = "debug" - assert.deepEqual(parse(["--log", "info"]), { + assert.deepEqual(await parse(["--log", "info"]), { ...defaults, log: "info", }) @@ -124,7 +124,7 @@ describe("cli", () => { assert.equal(logger.level, Level.Info) process.env.LOG_LEVEL = "trace" - assert.deepEqual(parse(["--log", "info"]), { + assert.deepEqual(await parse(["--log", "info"]), { ...defaults, log: "info", }) @@ -132,7 +132,7 @@ describe("cli", () => { assert.equal(logger.level, Level.Info) process.env.LOG_LEVEL = "warn" - assert.deepEqual(parse(["--log", "info", "--verbose"]), { + assert.deepEqual(await parse(["--log", "info", "--verbose"]), { ...defaults, log: "trace", verbose: true, @@ -141,31 +141,34 @@ describe("cli", () => { assert.equal(logger.level, Level.Trace) }) - it("should ignore invalid log level env var", () => { + it("should ignore invalid log level env var", async () => { process.env.LOG_LEVEL = "bogus" - assert.deepEqual(parse([]), defaults) + assert.deepEqual(await parse([]), defaults) }) - it("should error if value isn't provided", () => { - assert.throws(() => parse(["--auth"]), /--auth requires a value/) - assert.throws(() => parse(["--auth=", "--log=debug"]), /--auth requires a value/) - assert.throws(() => parse(["--auth", "--log"]), /--auth requires a value/) - assert.throws(() => parse(["--auth", "--invalid"]), /--auth requires a value/) - assert.throws(() => parse(["--bind-addr"]), /--bind-addr requires a value/) + it("should error if value isn't provided", async () => { + await assert.rejects(async () => await parse(["--auth"]), /--auth requires a value/) + await assert.rejects(async () => await parse(["--auth=", "--log=debug"]), /--auth requires a value/) + await assert.rejects(async () => await parse(["--auth", "--log"]), /--auth requires a value/) + await assert.rejects(async () => await parse(["--auth", "--invalid"]), /--auth requires a value/) + await assert.rejects(async () => await parse(["--bind-addr"]), /--bind-addr requires a value/) }) - it("should error if value is invalid", () => { - assert.throws(() => parse(["--port", "foo"]), /--port must be a number/) - assert.throws(() => parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/) - assert.throws(() => parse(["--log", "invalid"]), /--log valid values: \[trace, debug, info, warn, error\]/) + it("should error if value is invalid", async () => { + await assert.rejects(async () => await parse(["--port", "foo"]), /--port must be a number/) + await assert.rejects(async () => await parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/) + await assert.rejects( + async () => await parse(["--log", "invalid"]), + /--log valid values: \[trace, debug, info, warn, error\]/, + ) }) - it("should error if the option doesn't exist", () => { - assert.throws(() => parse(["--foo"]), /Unknown option --foo/) + it("should error if the option doesn't exist", async () => { + await assert.rejects(async () => await parse(["--foo"]), /Unknown option --foo/) }) - it("should not error if the value is optional", () => { - assert.deepEqual(parse(["--cert"]), { + it("should not error if the value is optional", async () => { + assert.deepEqual(await parse(["--cert"]), { ...defaults, cert: { value: undefined, @@ -173,30 +176,33 @@ describe("cli", () => { }) }) - it("should not allow option-like values", () => { - assert.throws(() => parse(["--socket", "--socket-path-value"]), /--socket requires a value/) + it("should not allow option-like values", async () => { + await assert.rejects(async () => await parse(["--socket", "--socket-path-value"]), /--socket requires a value/) // If you actually had a path like this you would do this instead: - assert.deepEqual(parse(["--socket", "./--socket-path-value"]), { + assert.deepEqual(await parse(["--socket", "./--socket-path-value"]), { ...defaults, socket: path.resolve("--socket-path-value"), }) - assert.throws(() => parse(["--cert", "--socket-path-value"]), /Unknown option --socket-path-value/) + await assert.rejects( + async () => await parse(["--cert", "--socket-path-value"]), + /Unknown option --socket-path-value/, + ) }) - it("should allow positional arguments before options", () => { - assert.deepEqual(parse(["foo", "test", "--auth", "none"]), { + it("should allow positional arguments before options", async () => { + assert.deepEqual(await parse(["foo", "test", "--auth", "none"]), { ...defaults, _: ["foo", "test"], auth: "none", }) }) - it("should support repeatable flags", () => { - assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), { + it("should support repeatable flags", async () => { + assert.deepEqual(await parse(["--proxy-domain", "*.coder.com"]), { ...defaults, "proxy-domain": ["*.coder.com"], }) - assert.deepEqual(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), { + assert.deepEqual(await parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), { ...defaults, "proxy-domain": ["*.coder.com", "test.com"], })