Add VSCODE_OPTIONS and --vscode-option for Code flags (#1528) (#7952)

This commit is contained in:
Fernando Softov
2026-08-19 17:44:48 -04:00
committed by GitHub
parent 2edeb1c991
commit 04ce2301a7
2 changed files with 110 additions and 2 deletions

View File

@@ -97,6 +97,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
"abs-proxy-base-path"?: string "abs-proxy-base-path"?: string
i18n?: string i18n?: string
"idle-timeout-seconds"?: number "idle-timeout-seconds"?: number
"vscode-option"?: string[]
/* Positional arguments. */ /* Positional arguments. */
_?: string[] _?: string[]
} }
@@ -322,6 +323,13 @@ export const options: Options<Required<UserProvidedArgs>> = {
"Override the reconnection grace time in seconds. Clients who disconnect for longer than this duration will need to \n" + "Override the reconnection grace time in seconds. Clients who disconnect for longer than this duration will need to \n" +
"reload the window. Defaults to 10800 (3 hours).", "reload the window. Defaults to 10800 (3 hours).",
}, },
"vscode-option": {
type: "string[]",
description:
"Pass an option straight through to the VS Code server as flag=value, or as a bare flag for a \n" +
"boolean. Repeatable; repeating the same flag builds an array. Use this to reach VS Code options \n" +
"code-server does not model itself, e.g. --vscode-option enable-sandbox --vscode-option agents=true.",
},
} }
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => { export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
@@ -643,6 +651,15 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["reconnection-grace-time"] = process.env.CODE_SERVER_RECONNECTION_GRACE_TIME args["reconnection-grace-time"] = process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
} }
// Space-separated, like NODE_OPTIONS. Appended to any flags rather than
// replacing them so the two can be combined.
if (process.env.VSCODE_OPTIONS) {
args["vscode-option"] = [
...(args["vscode-option"] ?? []),
...process.env.VSCODE_OPTIONS.split(/\s+/).filter((option) => option),
]
}
if (process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS) { if (process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS) {
if (isNaN(Number(process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS))) { if (isNaN(Number(process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS))) {
logger.info("CODE_SERVER_IDLE_TIMEOUT_SECONDS must be a number") logger.info("CODE_SERVER_IDLE_TIMEOUT_SECONDS must be a number")
@@ -909,17 +926,58 @@ export interface CodeArgs extends UserProvidedCodeArgs {
log?: string[] log?: string[]
} }
/**
* Expand --vscode-option entries into VS Code server arguments.
*
* An entry is `flag=value`, or a bare `flag` meaning true. A leading `--` on
* the flag is optional, so both spellings people reach for work. Repeating a
* flag collects the values into an array, since several VS Code options take
* one.
*
* `true` and `false` become booleans rather than strings. VS Code tests these
* flags for truthiness and the string "false" is truthy, so passing it along
* verbatim would quietly do the opposite of what was asked.
*/
export const parseVscodeOptions = (entries: string[]): Record<string, string | boolean | string[]> => {
const parsed: Record<string, string | boolean | string[]> = {}
for (const entry of entries) {
const [flag, rawValue] = splitOnFirstEquals(entry.replace(/^--/, ""))
if (!flag) {
throw new Error(`--vscode-option requires a flag name (got "${entry}")`)
}
const value: string | boolean =
typeof rawValue === "undefined" || rawValue === "true" ? true : rawValue === "false" ? false : rawValue
const existing = parsed[flag]
if (typeof existing === "undefined") {
parsed[flag] = value
} else if (Array.isArray(existing)) {
existing.push(String(value))
} else {
parsed[flag] = [String(existing), String(value)]
}
}
return parsed
}
/** /**
* Convert our arguments to equivalent VS Code server arguments. * Convert our arguments to equivalent VS Code server arguments.
* Does not add any extra arguments. * Does not add any extra arguments.
*/ */
export const toCodeArgs = async (args: DefaultedArgs): Promise<CodeArgs> => { export const toCodeArgs = async (args: DefaultedArgs): Promise<CodeArgs> => {
// The passthrough option is ours; VS Code has no idea what it is.
const { "vscode-option": vscodeOptions, ...rest } = args
return { return {
...args, ...rest,
/** Type casting. */ /** Type casting. */
help: !!args.help, help: !!args.help,
version: !!args.version, version: !!args.version,
port: args.port?.toString(), port: args.port?.toString(),
log: args.log ? [args.log] : undefined, log: args.log ? [args.log] : undefined,
} // Last, so that reaching an option code-server does model still works.
...parseVscodeOptions(vscodeOptions ?? []),
} as CodeArgs
} }

View File

@@ -51,6 +51,7 @@ describe("parser", () => {
delete process.env.CODE_SERVER_RECONNECTION_GRACE_TIME delete process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
delete process.env.VSCODE_PROXY_URI delete process.env.VSCODE_PROXY_URI
delete process.env.CS_DISABLE_PROXY delete process.env.CS_DISABLE_PROXY
delete process.env.VSCODE_OPTIONS
console.log = jest.fn() console.log = jest.fn()
}) })
@@ -413,6 +414,17 @@ describe("parser", () => {
}) })
}) })
it("should use env var VSCODE_OPTIONS", async () => {
process.env.VSCODE_OPTIONS = "--enable-sandbox agents=true"
const args = parse(["--vscode-option", "verbose-logging"])
const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"vscode-option": ["verbose-logging", "--enable-sandbox", "agents=true"],
})
})
it("should use env var CS_DISABLE_GETTING_STARTED_OVERRIDE", async () => { it("should use env var CS_DISABLE_GETTING_STARTED_OVERRIDE", async () => {
process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE = "1" process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE = "1"
const args = parse([]) const args = parse([])
@@ -1006,6 +1018,44 @@ describe("toCodeArgs", () => {
_: [file], _: [file],
}) })
}) })
it("should pass through --vscode-option", async () => {
const args = parse([
"--vscode-option",
"enable-sandbox",
"--vscode-option",
"agents=true",
"--vscode-option",
"enable-smoke-test-driver=false",
])
expect(await toCodeArgs(await setDefaults(args))).toStrictEqual({
...vscodeDefaults,
"enable-sandbox": true,
agents: true,
"enable-smoke-test-driver": false,
})
})
it("should collect a repeated --vscode-option into an array", async () => {
const args = parse([
"--vscode-option",
"locate-extension=a",
"--vscode-option",
"locate-extension=b",
"--vscode-option",
"locate-extension=c",
])
expect(await toCodeArgs(await setDefaults(args))).toStrictEqual({
...vscodeDefaults,
"locate-extension": ["a", "b", "c"],
})
})
it("should error if --vscode-option has no flag", async () => {
await expect(toCodeArgs(await setDefaults(parse(["--vscode-option", "=nothing"])))).rejects.toThrow(
"--vscode-option requires a flag name",
)
})
}) })
describe("optionDescriptions", () => { describe("optionDescriptions", () => {