Add separate function for VS Code arguments (#4599)
The problem before was that the pop() caused the open in existing instance functionality to break because the arguments no longer contained the file. We could simply remove the pop() but since `workspace` and `folder` are not CLI arguments I think it makes sense to handle them in a separate function which can be called at the point where they are needed. This also lets us de-duplicate some logic since we create these arguments in two spots and lets us skip this logic when we do not need it. The pop() is still avoided because manipulating a passed-in object in-place seems like a risky move. If we really need to do this we should copy the positional argument array instead.
This commit is contained in:
parent
3b91cffae5
commit
9e583fa562
|
@ -405,7 +405,10 @@ export const parse = (
|
||||||
throw new Error("--cert-key is missing")
|
throw new Error("--cert-key is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(() => ["parsed command line", field("args", { ...args, password: undefined })])
|
logger.debug(() => [
|
||||||
|
`parsed ${opts?.configFile ? "config" : "command line"}`,
|
||||||
|
field("args", { ...args, password: undefined }),
|
||||||
|
])
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
@ -430,8 +433,6 @@ export interface DefaultedArgs extends ConfigArgs {
|
||||||
"user-data-dir": string
|
"user-data-dir": string
|
||||||
/* Positional arguments. */
|
/* Positional arguments. */
|
||||||
_: []
|
_: []
|
||||||
folder: string
|
|
||||||
workspace: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -539,25 +540,8 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||||
args._ = []
|
args._ = []
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace = ""
|
|
||||||
let folder = ""
|
|
||||||
if (args._.length) {
|
|
||||||
const lastEntry = path.resolve(process.cwd(), args._[args._.length - 1])
|
|
||||||
const entryIsFile = await isFile(lastEntry)
|
|
||||||
|
|
||||||
if (entryIsFile && path.extname(lastEntry) === ".code-workspace") {
|
|
||||||
workspace = lastEntry
|
|
||||||
args._.pop()
|
|
||||||
} else if (!entryIsFile) {
|
|
||||||
folder = lastEntry
|
|
||||||
args._.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...args,
|
...args,
|
||||||
workspace,
|
|
||||||
folder,
|
|
||||||
usingEnvPassword,
|
usingEnvPassword,
|
||||||
usingEnvHashedPassword,
|
usingEnvHashedPassword,
|
||||||
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
|
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
|
||||||
|
@ -760,3 +744,34 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert our arguments to VS Code server arguments.
|
||||||
|
*/
|
||||||
|
export const toVsCodeArgs = async (args: DefaultedArgs): Promise<CodeServerLib.ServerParsedArgs> => {
|
||||||
|
let workspace = ""
|
||||||
|
let folder = ""
|
||||||
|
if (args._.length) {
|
||||||
|
const lastEntry = path.resolve(args._[args._.length - 1])
|
||||||
|
const entryIsFile = await isFile(lastEntry)
|
||||||
|
if (entryIsFile && path.extname(lastEntry) === ".code-workspace") {
|
||||||
|
workspace = lastEntry
|
||||||
|
} else if (!entryIsFile) {
|
||||||
|
folder = lastEntry
|
||||||
|
}
|
||||||
|
// Otherwise it is a regular file. Spawning VS Code with a file is not yet
|
||||||
|
// supported but it can be done separately after code-server spawns.
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"connection-token": "0000",
|
||||||
|
...args,
|
||||||
|
workspace,
|
||||||
|
folder,
|
||||||
|
"accept-server-license-terms": true,
|
||||||
|
/** Type casting. */
|
||||||
|
help: !!args.help,
|
||||||
|
version: !!args.version,
|
||||||
|
port: args.port?.toString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import path from "path"
|
||||||
import { Disposable } from "../common/emitter"
|
import { Disposable } from "../common/emitter"
|
||||||
import { plural } from "../common/util"
|
import { plural } from "../common/util"
|
||||||
import { createApp, ensureAddress } from "./app"
|
import { createApp, ensureAddress } from "./app"
|
||||||
import { AuthType, DefaultedArgs, Feature, UserProvidedArgs } from "./cli"
|
import { AuthType, DefaultedArgs, Feature, toVsCodeArgs, UserProvidedArgs } from "./cli"
|
||||||
import { coderCloudBind } from "./coder_cloud"
|
import { coderCloudBind } from "./coder_cloud"
|
||||||
import { commit, version } from "./constants"
|
import { commit, version } from "./constants"
|
||||||
import { register } from "./routes"
|
import { register } from "./routes"
|
||||||
|
@ -35,14 +35,7 @@ export const runVsCodeCli = async (args: DefaultedArgs): Promise<void> => {
|
||||||
const spawnCli = await loadAMDModule<CodeServerLib.SpawnCli>("vs/server/remoteExtensionHostAgent", "spawnCli")
|
const spawnCli = await loadAMDModule<CodeServerLib.SpawnCli>("vs/server/remoteExtensionHostAgent", "spawnCli")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await spawnCli({
|
await spawnCli(await toVsCodeArgs(args))
|
||||||
...args,
|
|
||||||
/** Type casting. */
|
|
||||||
"accept-server-license-terms": true,
|
|
||||||
help: !!args.help,
|
|
||||||
version: !!args.version,
|
|
||||||
port: args.port?.toString(),
|
|
||||||
})
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error("Got error from VS Code", error)
|
logger.error("Got error from VS Code", error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as express from "express"
|
||||||
import { WebsocketRequest } from "../../../typings/pluginapi"
|
import { WebsocketRequest } from "../../../typings/pluginapi"
|
||||||
import { logError } from "../../common/util"
|
import { logError } from "../../common/util"
|
||||||
import { isDevMode } from "../constants"
|
import { isDevMode } from "../constants"
|
||||||
|
import { toVsCodeArgs } from "../cli"
|
||||||
import { ensureAuthenticated, authenticated, redirect } from "../http"
|
import { ensureAuthenticated, authenticated, redirect } from "../http"
|
||||||
import { loadAMDModule, readCompilationStats } from "../util"
|
import { loadAMDModule, readCompilationStats } from "../util"
|
||||||
import { Router as WsRouter } from "../wsRouter"
|
import { Router as WsRouter } from "../wsRouter"
|
||||||
|
@ -87,15 +88,7 @@ export class CodeServerRouteWrapper {
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._codeServerMain = await createVSServer(null, {
|
this._codeServerMain = await createVSServer(null, await toVsCodeArgs(args))
|
||||||
"connection-token": "0000",
|
|
||||||
"accept-server-license-terms": true,
|
|
||||||
...args,
|
|
||||||
/** Type casting. */
|
|
||||||
help: !!args.help,
|
|
||||||
version: !!args.version,
|
|
||||||
port: args.port?.toString(),
|
|
||||||
})
|
|
||||||
} catch (createServerError) {
|
} catch (createServerError) {
|
||||||
logError(logger, "CodeServerRouteWrapper", createServerError)
|
logError(logger, "CodeServerRouteWrapper", createServerError)
|
||||||
return next(createServerError)
|
return next(createServerError)
|
||||||
|
|
|
@ -12,22 +12,16 @@ import {
|
||||||
setDefaults,
|
setDefaults,
|
||||||
shouldOpenInExistingInstance,
|
shouldOpenInExistingInstance,
|
||||||
splitOnFirstEquals,
|
splitOnFirstEquals,
|
||||||
|
toVsCodeArgs,
|
||||||
} from "../../../src/node/cli"
|
} from "../../../src/node/cli"
|
||||||
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
||||||
import { generatePassword, paths } from "../../../src/node/util"
|
import { generatePassword, paths } from "../../../src/node/util"
|
||||||
import { useEnv, tmpdir } from "../../utils/helpers"
|
import { clean, useEnv, tmpdir } from "../../utils/helpers"
|
||||||
|
|
||||||
describe("parser", () => {
|
// The parser should not set any defaults so the caller can determine what
|
||||||
beforeEach(() => {
|
// values the user actually set. These are only set after explicitly calling
|
||||||
delete process.env.LOG_LEVEL
|
// `setDefaults`.
|
||||||
delete process.env.PASSWORD
|
const defaults = {
|
||||||
console.log = jest.fn()
|
|
||||||
})
|
|
||||||
|
|
||||||
// The parser should not set any defaults so the caller can determine what
|
|
||||||
// values the user actually set. These are only set after explicitly calling
|
|
||||||
// `setDefaults`.
|
|
||||||
const defaults = {
|
|
||||||
auth: "password",
|
auth: "password",
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
@ -37,9 +31,14 @@ describe("parser", () => {
|
||||||
"extensions-dir": path.join(paths.data, "extensions"),
|
"extensions-dir": path.join(paths.data, "extensions"),
|
||||||
"user-data-dir": paths.data,
|
"user-data-dir": paths.data,
|
||||||
_: [],
|
_: [],
|
||||||
workspace: "",
|
}
|
||||||
folder: "",
|
|
||||||
}
|
describe("parser", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.LOG_LEVEL
|
||||||
|
delete process.env.PASSWORD
|
||||||
|
console.log = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
it("should parse nothing", async () => {
|
it("should parse nothing", async () => {
|
||||||
expect(parse([])).toStrictEqual({})
|
expect(parse([])).toStrictEqual({})
|
||||||
|
@ -667,3 +666,59 @@ describe("readSocketPath", () => {
|
||||||
expect(contents2).toBe(contents1)
|
expect(contents2).toBe(contents1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("toVsCodeArgs", () => {
|
||||||
|
const vscodeDefaults = {
|
||||||
|
...defaults,
|
||||||
|
"connection-token": "0000",
|
||||||
|
"accept-server-license-terms": true,
|
||||||
|
help: false,
|
||||||
|
port: "8080",
|
||||||
|
version: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Clean up temporary directories from the previous run.
|
||||||
|
await clean("vscode-args")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should convert empty args", async () => {
|
||||||
|
expect(await toVsCodeArgs(await setDefaults(parse([])))).toStrictEqual({
|
||||||
|
...vscodeDefaults,
|
||||||
|
folder: "",
|
||||||
|
workspace: "",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should convert with workspace", async () => {
|
||||||
|
const workspace = path.join(await tmpdir("vscode-args"), "test.code-workspace")
|
||||||
|
await fs.writeFile(workspace, "foobar")
|
||||||
|
expect(await toVsCodeArgs(await setDefaults(parse([workspace])))).toStrictEqual({
|
||||||
|
...vscodeDefaults,
|
||||||
|
workspace,
|
||||||
|
folder: "",
|
||||||
|
_: [workspace],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should convert with folder", async () => {
|
||||||
|
const folder = await tmpdir("vscode-args")
|
||||||
|
expect(await toVsCodeArgs(await setDefaults(parse([folder])))).toStrictEqual({
|
||||||
|
...vscodeDefaults,
|
||||||
|
folder,
|
||||||
|
workspace: "",
|
||||||
|
_: [folder],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should ignore regular file", async () => {
|
||||||
|
const file = path.join(await tmpdir("vscode-args"), "file")
|
||||||
|
await fs.writeFile(file, "foobar")
|
||||||
|
expect(await toVsCodeArgs(await setDefaults(parse([file])))).toStrictEqual({
|
||||||
|
...vscodeDefaults,
|
||||||
|
folder: "",
|
||||||
|
workspace: "",
|
||||||
|
_: [file],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -42,8 +42,6 @@ describe("plugin", () => {
|
||||||
usingEnvHashedPassword: false,
|
usingEnvHashedPassword: false,
|
||||||
"extensions-dir": "",
|
"extensions-dir": "",
|
||||||
"user-data-dir": "",
|
"user-data-dir": "",
|
||||||
workspace: "",
|
|
||||||
folder: "",
|
|
||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue