Skip to content

Commit 7371891

Browse files
committed
Refactor wrapper
- Immediately create ipcMain so it doesn't have to be a function which I think feels cleaner. - Move exit handling to a separate function to compensate (otherwise the VS Code CLI for example won't be able to exit on its own). - New isChild prop that is clearer than checking for parentPid (IMO). - Skip all the checks that aren't necessary for the child process (like --help, --version, etc). - Since we check if we're the child in entry go ahead and move the wrap code into entry as well since that's basically what it does. - Use a single catch at the end of the entry. - Split out the VS Code CLI and existing instance code into separate functions.
1 parent 6bdaada commit 7371891

File tree

3 files changed

+199
-154
lines changed

3 files changed

+199
-154
lines changed

src/node/cli.ts

+20
Original file line numberDiff line numberDiff line change
@@ -496,3 +496,23 @@ async function copyOldMacOSDataDir(): Promise<void> {
496496
await fs.copy(oldDataDir, paths.data)
497497
}
498498
}
499+
500+
export const shouldRunVsCodeCli = (args: Args): boolean => {
501+
return !!args["list-extensions"] || !!args["install-extension"] || !!args["uninstall-extension"]
502+
}
503+
504+
/**
505+
* Determine if it looks like the user is trying to open a file or folder in an
506+
* existing instance. The arguments here should be the arguments the user
507+
* explicitly passed on the command line, not defaults or the configuration.
508+
*/
509+
export const shouldOpenInExistingInstance = async (args: Args): Promise<string | undefined> => {
510+
// Always use the existing instance if we're running from VS Code's terminal.
511+
if (process.env.VSCODE_IPC_HOOK_CLI) {
512+
return process.env.VSCODE_IPC_HOOK_CLI
513+
}
514+
515+
// TODO: implement
516+
517+
return undefined
518+
}

src/node/entry.ts

+135-96
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ import { ProxyHttpProvider } from "./app/proxy"
1111
import { StaticHttpProvider } from "./app/static"
1212
import { UpdateHttpProvider } from "./app/update"
1313
import { VscodeHttpProvider } from "./app/vscode"
14-
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
1514
import { coderCloudBind } from "./coder-cloud"
15+
import {
16+
Args,
17+
bindAddrFromAllSources,
18+
optionDescriptions,
19+
parse,
20+
readConfigFile,
21+
setDefaults,
22+
shouldOpenInExistingInstance,
23+
shouldRunVsCodeCli,
24+
} from "./cli"
1625
import { AuthType, HttpServer, HttpServerOptions } from "./http"
1726
import { loadPlugins } from "./plugin"
1827
import { generateCertificate, hash, humanPath, open } from "./util"
19-
import { ipcMain, wrap } from "./wrapper"
28+
import { ipcMain, WrapperProcess } from "./wrapper"
2029

2130
let pkg: { version?: string; commit?: string } = {}
2231
try {
@@ -28,6 +37,86 @@ try {
2837
const version = pkg.version || "development"
2938
const commit = pkg.commit || "development"
3039

40+
export const runVsCodeCli = (args: Args): void => {
41+
logger.debug("forking vs code cli...")
42+
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
43+
env: {
44+
...process.env,
45+
CODE_SERVER_PARENT_PID: process.pid.toString(),
46+
},
47+
})
48+
vscode.once("message", (message: any) => {
49+
logger.debug("got message from VS Code", field("message", message))
50+
if (message.type !== "ready") {
51+
logger.error("Unexpected response waiting for ready response", field("type", message.type))
52+
process.exit(1)
53+
}
54+
const send: CliMessage = { type: "cli", args }
55+
vscode.send(send)
56+
})
57+
vscode.once("error", (error) => {
58+
logger.error("Got error from VS Code", field("error", error))
59+
process.exit(1)
60+
})
61+
vscode.on("exit", (code) => process.exit(code || 0))
62+
}
63+
64+
export const openInExistingInstance = async (args: Args, socketPath: string): Promise<void> => {
65+
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
66+
type: "open",
67+
folderURIs: [],
68+
fileURIs: [],
69+
forceReuseWindow: args["reuse-window"],
70+
forceNewWindow: args["new-window"],
71+
}
72+
73+
const isDir = async (path: string): Promise<boolean> => {
74+
try {
75+
const st = await fs.stat(path)
76+
return st.isDirectory()
77+
} catch (error) {
78+
return false
79+
}
80+
}
81+
82+
for (let i = 0; i < args._.length; i++) {
83+
const fp = path.resolve(args._[i])
84+
if (await isDir(fp)) {
85+
pipeArgs.folderURIs.push(fp)
86+
} else {
87+
pipeArgs.fileURIs.push(fp)
88+
}
89+
}
90+
91+
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
92+
logger.error("--new-window can only be used with folder paths")
93+
process.exit(1)
94+
}
95+
96+
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
97+
logger.error("Please specify at least one file or folder")
98+
process.exit(1)
99+
}
100+
101+
const vscode = http.request(
102+
{
103+
path: "/",
104+
method: "POST",
105+
socketPath,
106+
},
107+
(response) => {
108+
response.on("data", (message) => {
109+
logger.debug("got message from VS Code", field("message", message.toString()))
110+
})
111+
},
112+
)
113+
vscode.on("error", (error: unknown) => {
114+
logger.error("got error from VS Code", field("error", error))
115+
})
116+
vscode.write(JSON.stringify(pipeArgs))
117+
vscode.end()
118+
}
119+
31120
const main = async (args: Args, configArgs: Args): Promise<void> => {
32121
if (args.link) {
33122
// If we're being exposed to the cloud, we listen on a random address and disable auth.
@@ -92,7 +181,7 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
92181

93182
await loadPlugins(httpServer, args)
94183

95-
ipcMain().onDispose(() => {
184+
ipcMain.onDispose(() => {
96185
httpServer.dispose().then((errors) => {
97186
errors.forEach((error) => logger.error(error.message))
98187
})
@@ -132,7 +221,9 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
132221
if (serverAddress && !options.socket && args.open) {
133222
// The web socket doesn't seem to work if browsing with 0.0.0.0.
134223
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
135-
await open(openAddress).catch(console.error)
224+
await open(openAddress).catch((error: Error) => {
225+
logger.error("Failed to open", field("address", openAddress), field("error", error))
226+
})
136227
logger.info(`Opened ${openAddress}`)
137228
}
138229

@@ -141,27 +232,32 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
141232
await coderCloudBind(serverAddress!, args.link.value)
142233
} catch (err) {
143234
logger.error(err.message)
144-
ipcMain().exit(1)
235+
ipcMain.exit(1)
145236
}
146237
}
147238
}
148239

149240
async function entry(): Promise<void> {
150-
const tryParse = async (): Promise<[Args, Args]> => {
151-
try {
152-
const cliArgs = parse(process.argv.slice(2))
153-
const configArgs = await readConfigFile(cliArgs.config)
154-
// This prioritizes the flags set in args over the ones in the config file.
155-
let args = Object.assign(configArgs, cliArgs)
156-
args = await setDefaults(args)
157-
return [args, configArgs]
158-
} catch (error) {
159-
console.error(error.message)
160-
process.exit(1)
161-
}
241+
const tryParse = async (): Promise<[Args, Args, Args]> => {
242+
const cliArgs = parse(process.argv.slice(2))
243+
const configArgs = await readConfigFile(cliArgs.config)
244+
// This prioritizes the flags set in args over the ones in the config file.
245+
let args = Object.assign(configArgs, cliArgs)
246+
args = await setDefaults(args)
247+
return [args, cliArgs, configArgs]
248+
}
249+
250+
const [args, cliArgs, configArgs] = await tryParse()
251+
252+
// There's no need to check flags like --help or to spawn in an existing
253+
// instance for the child process because these would have already happened in
254+
// the parent and the child wouldn't have been spawned.
255+
if (ipcMain.isChild) {
256+
await ipcMain.handshake()
257+
ipcMain.preventExit()
258+
return main(args, configArgs)
162259
}
163260

164-
const [args, configArgs] = await tryParse()
165261
if (args.help) {
166262
console.log("code-server", version, commit)
167263
console.log("")
@@ -171,7 +267,10 @@ async function entry(): Promise<void> {
171267
optionDescriptions().forEach((description) => {
172268
console.log("", description)
173269
})
174-
} else if (args.version) {
270+
return
271+
}
272+
273+
if (args.version) {
175274
if (args.json) {
176275
console.log({
177276
codeServer: version,
@@ -181,83 +280,23 @@ async function entry(): Promise<void> {
181280
} else {
182281
console.log(version, commit)
183282
}
184-
process.exit(0)
185-
} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) {
186-
logger.debug("forking vs code cli...")
187-
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
188-
env: {
189-
...process.env,
190-
CODE_SERVER_PARENT_PID: process.pid.toString(),
191-
},
192-
})
193-
vscode.once("message", (message: any) => {
194-
logger.debug("Got message from VS Code", field("message", message))
195-
if (message.type !== "ready") {
196-
logger.error("Unexpected response waiting for ready response")
197-
process.exit(1)
198-
}
199-
const send: CliMessage = { type: "cli", args }
200-
vscode.send(send)
201-
})
202-
vscode.once("error", (error) => {
203-
logger.error(error.message)
204-
process.exit(1)
205-
})
206-
vscode.on("exit", (code) => process.exit(code || 0))
207-
} else if (process.env.VSCODE_IPC_HOOK_CLI) {
208-
const pipeArgs: OpenCommandPipeArgs = {
209-
type: "open",
210-
folderURIs: [],
211-
forceReuseWindow: args["reuse-window"],
212-
forceNewWindow: args["new-window"],
213-
}
214-
const isDir = async (path: string): Promise<boolean> => {
215-
try {
216-
const st = await fs.stat(path)
217-
return st.isDirectory()
218-
} catch (error) {
219-
return false
220-
}
221-
}
222-
for (let i = 0; i < args._.length; i++) {
223-
const fp = path.resolve(args._[i])
224-
if (await isDir(fp)) {
225-
pipeArgs.folderURIs.push(fp)
226-
} else {
227-
if (!pipeArgs.fileURIs) {
228-
pipeArgs.fileURIs = []
229-
}
230-
pipeArgs.fileURIs.push(fp)
231-
}
232-
}
233-
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs && pipeArgs.fileURIs.length > 0) {
234-
logger.error("new-window can only be used with folder paths")
235-
process.exit(1)
236-
}
237-
if (pipeArgs.folderURIs.length === 0 && (!pipeArgs.fileURIs || pipeArgs.fileURIs.length === 0)) {
238-
logger.error("Please specify at least one file or folder argument")
239-
process.exit(1)
240-
}
241-
const vscode = http.request(
242-
{
243-
path: "/",
244-
method: "POST",
245-
socketPath: process.env["VSCODE_IPC_HOOK_CLI"],
246-
},
247-
(res) => {
248-
res.on("data", (message) => {
249-
logger.debug("Got message from VS Code", field("message", message.toString()))
250-
})
251-
},
252-
)
253-
vscode.on("error", (err) => {
254-
logger.debug("Got error from VS Code", field("error", err))
255-
})
256-
vscode.write(JSON.stringify(pipeArgs))
257-
vscode.end()
258-
} else {
259-
wrap(() => main(args, configArgs))
283+
return
284+
}
285+
286+
if (shouldRunVsCodeCli(args)) {
287+
return runVsCodeCli(args)
260288
}
289+
290+
const socketPath = await shouldOpenInExistingInstance(cliArgs)
291+
if (socketPath) {
292+
return openInExistingInstance(args, socketPath)
293+
}
294+
295+
const wrapper = new WrapperProcess(require("../../package.json").version)
296+
return wrapper.start()
261297
}
262298

263-
entry()
299+
entry().catch((error) => {
300+
logger.error(error.message)
301+
ipcMain.exit(error)
302+
})

0 commit comments

Comments
 (0)