Skip to content

Commit 3142cfd

Browse files
committed
Add VSCODE_OPTIONS and --vscode-option for Code flags (#1528)
1 parent c22dc74 commit 3142cfd

2 files changed

Lines changed: 110 additions & 2 deletions

File tree

src/node/cli.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
9797
"abs-proxy-base-path"?: string
9898
i18n?: string
9999
"idle-timeout-seconds"?: number
100+
"vscode-option"?: string[]
100101
/* Positional arguments. */
101102
_?: string[]
102103
}
@@ -322,6 +323,13 @@ export const options: Options<Required<UserProvidedArgs>> = {
322323
"Override the reconnection grace time in seconds. Clients who disconnect for longer than this duration will need to \n" +
323324
"reload the window. Defaults to 10800 (3 hours).",
324325
},
326+
"vscode-option": {
327+
type: "string[]",
328+
description:
329+
"Pass an option straight through to the VS Code server as flag=value, or as a bare flag for a \n" +
330+
"boolean. Repeatable; repeating the same flag builds an array. Use this to reach VS Code options \n" +
331+
"code-server does not model itself, e.g. --vscode-option enable-sandbox --vscode-option agents=true.",
332+
},
325333
}
326334

327335
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
@@ -643,6 +651,15 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
643651
args["reconnection-grace-time"] = process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
644652
}
645653

654+
// Space-separated, like NODE_OPTIONS. Appended to any flags rather than
655+
// replacing them so the two can be combined.
656+
if (process.env.VSCODE_OPTIONS) {
657+
args["vscode-option"] = [
658+
...(args["vscode-option"] ?? []),
659+
...process.env.VSCODE_OPTIONS.split(/\s+/).filter((option) => option),
660+
]
661+
}
662+
646663
if (process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS) {
647664
if (isNaN(Number(process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS))) {
648665
logger.info("CODE_SERVER_IDLE_TIMEOUT_SECONDS must be a number")
@@ -909,17 +926,58 @@ export interface CodeArgs extends UserProvidedCodeArgs {
909926
log?: string[]
910927
}
911928

929+
/**
930+
* Expand --vscode-option entries into VS Code server arguments.
931+
*
932+
* An entry is `flag=value`, or a bare `flag` meaning true. A leading `--` on
933+
* the flag is optional, so both spellings people reach for work. Repeating a
934+
* flag collects the values into an array, since several VS Code options take
935+
* one.
936+
*
937+
* `true` and `false` become booleans rather than strings. VS Code tests these
938+
* flags for truthiness and the string "false" is truthy, so passing it along
939+
* verbatim would quietly do the opposite of what was asked.
940+
*/
941+
export const parseVscodeOptions = (entries: string[]): Record<string, string | boolean | string[]> => {
942+
const parsed: Record<string, string | boolean | string[]> = {}
943+
944+
for (const entry of entries) {
945+
const [flag, rawValue] = splitOnFirstEquals(entry.replace(/^--/, ""))
946+
if (!flag) {
947+
throw new Error(`--vscode-option requires a flag name (got "${entry}")`)
948+
}
949+
950+
const value: string | boolean =
951+
typeof rawValue === "undefined" || rawValue === "true" ? true : rawValue === "false" ? false : rawValue
952+
953+
const existing = parsed[flag]
954+
if (typeof existing === "undefined") {
955+
parsed[flag] = value
956+
} else if (Array.isArray(existing)) {
957+
existing.push(String(value))
958+
} else {
959+
parsed[flag] = [String(existing), String(value)]
960+
}
961+
}
962+
963+
return parsed
964+
}
965+
912966
/**
913967
* Convert our arguments to equivalent VS Code server arguments.
914968
* Does not add any extra arguments.
915969
*/
916970
export const toCodeArgs = async (args: DefaultedArgs): Promise<CodeArgs> => {
971+
// The passthrough option is ours; VS Code has no idea what it is.
972+
const { "vscode-option": vscodeOptions, ...rest } = args
917973
return {
918-
...args,
974+
...rest,
919975
/** Type casting. */
920976
help: !!args.help,
921977
version: !!args.version,
922978
port: args.port?.toString(),
923979
log: args.log ? [args.log] : undefined,
924-
}
980+
// Last, so that reaching an option code-server does model still works.
981+
...parseVscodeOptions(vscodeOptions ?? []),
982+
} as CodeArgs
925983
}

test/unit/node/cli.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe("parser", () => {
5151
delete process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
5252
delete process.env.VSCODE_PROXY_URI
5353
delete process.env.CS_DISABLE_PROXY
54+
delete process.env.VSCODE_OPTIONS
5455
console.log = jest.fn()
5556
})
5657

@@ -413,6 +414,17 @@ describe("parser", () => {
413414
})
414415
})
415416

417+
it("should use env var VSCODE_OPTIONS", async () => {
418+
process.env.VSCODE_OPTIONS = "--enable-sandbox agents=true"
419+
const args = parse(["--vscode-option", "verbose-logging"])
420+
421+
const defaultArgs = await setDefaults(args)
422+
expect(defaultArgs).toEqual({
423+
...defaults,
424+
"vscode-option": ["verbose-logging", "--enable-sandbox", "agents=true"],
425+
})
426+
})
427+
416428
it("should use env var CS_DISABLE_GETTING_STARTED_OVERRIDE", async () => {
417429
process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE = "1"
418430
const args = parse([])
@@ -1006,6 +1018,44 @@ describe("toCodeArgs", () => {
10061018
_: [file],
10071019
})
10081020
})
1021+
1022+
it("should pass through --vscode-option", async () => {
1023+
const args = parse([
1024+
"--vscode-option",
1025+
"enable-sandbox",
1026+
"--vscode-option",
1027+
"agents=true",
1028+
"--vscode-option",
1029+
"enable-smoke-test-driver=false",
1030+
])
1031+
expect(await toCodeArgs(await setDefaults(args))).toStrictEqual({
1032+
...vscodeDefaults,
1033+
"enable-sandbox": true,
1034+
agents: true,
1035+
"enable-smoke-test-driver": false,
1036+
})
1037+
})
1038+
1039+
it("should collect a repeated --vscode-option into an array", async () => {
1040+
const args = parse([
1041+
"--vscode-option",
1042+
"locate-extension=a",
1043+
"--vscode-option",
1044+
"locate-extension=b",
1045+
"--vscode-option",
1046+
"locate-extension=c",
1047+
])
1048+
expect(await toCodeArgs(await setDefaults(args))).toStrictEqual({
1049+
...vscodeDefaults,
1050+
"locate-extension": ["a", "b", "c"],
1051+
})
1052+
})
1053+
1054+
it("should error if --vscode-option has no flag", async () => {
1055+
await expect(toCodeArgs(await setDefaults(parse(["--vscode-option", "=nothing"])))).rejects.toThrow(
1056+
"--vscode-option requires a flag name",
1057+
)
1058+
})
10091059
})
10101060

10111061
describe("optionDescriptions", () => {

0 commit comments

Comments
 (0)