Skip to content

Commit b64ae51

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 32e4c1b commit b64ae51

File tree

3 files changed

+196
-151
lines changed

3 files changed

+196
-151
lines changed

src/node/cli.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,23 @@ async function copyOldMacOSDataDir(): Promise<void> {
453453
await fs.copy(oldDataDir, paths.data)
454454
}
455455
}
456+
457+
export const shouldRunVsCodeCli = (args: Args): boolean => {
458+
return !!args["list-extensions"] || !!args["install-extension"] || !!args["uninstall-extension"]
459+
}
460+
461+
/**
462+
* Determine if it looks like the user is trying to open a file or folder in an
463+
* existing instance. The arguments here should be the arguments the user
464+
* explicitly passed on the command line, not defaults or the configuration.
465+
*/
466+
export const shouldOpenInExistingInstance = async (args: Args): Promise<string | undefined> => {
467+
// Always use the existing instance if we're running from VS Code's terminal.
468+
if (process.env.VSCODE_IPC_HOOK_CLI) {
469+
return process.env.VSCODE_IPC_HOOK_CLI
470+
}
471+
472+
// TODO: implement
473+
474+
return undefined
475+
}

src/node/entry.ts

Lines changed: 132 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ 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"
14+
import {
15+
Args,
16+
bindAddrFromAllSources,
17+
optionDescriptions,
18+
parse,
19+
readConfigFile,
20+
setDefaults,
21+
shouldOpenInExistingInstance,
22+
shouldRunVsCodeCli,
23+
} from "./cli"
1524
import { AuthType, HttpServer, HttpServerOptions } from "./http"
1625
import { loadPlugins } from "./plugin"
1726
import { generateCertificate, hash, humanPath, open } from "./util"
18-
import { ipcMain, wrap } from "./wrapper"
27+
import { ipcMain, WrapperProcess } from "./wrapper"
1928

2029
let pkg: { version?: string; commit?: string } = {}
2130
try {
@@ -27,6 +36,86 @@ try {
2736
const version = pkg.version || "development"
2837
const commit = pkg.commit || "development"
2938

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

79168
await loadPlugins(httpServer, args)
80169

81-
ipcMain().onDispose(() => {
170+
ipcMain.onDispose(() => {
82171
httpServer.dispose().then((errors) => {
83172
errors.forEach((error) => logger.error(error.message))
84173
})
@@ -118,27 +207,34 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
118207
if (serverAddress && !options.socket && args.open) {
119208
// The web socket doesn't seem to work if browsing with 0.0.0.0.
120209
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
121-
await open(openAddress).catch(console.error)
210+
await open(openAddress).catch((error: Error) => {
211+
logger.error("Failed to open", field("address", openAddress), field("error", error))
212+
})
122213
logger.info(`Opened ${openAddress}`)
123214
}
124215
}
125216

126217
async function entry(): Promise<void> {
127218
const tryParse = async (): Promise<[Args, Args, Args]> => {
128-
try {
129-
const cliArgs = parse(process.argv.slice(2))
130-
const configArgs = await readConfigFile(cliArgs.config)
131-
// This prioritizes the flags set in args over the ones in the config file.
132-
let args = Object.assign(configArgs, cliArgs)
133-
args = await setDefaults(args)
134-
return [args, cliArgs, configArgs]
135-
} catch (error) {
136-
console.error(error.message)
137-
process.exit(1)
138-
}
219+
const cliArgs = parse(process.argv.slice(2))
220+
const configArgs = await readConfigFile(cliArgs.config)
221+
// This prioritizes the flags set in args over the ones in the config file.
222+
let args = Object.assign(configArgs, cliArgs)
223+
args = await setDefaults(args)
224+
return [args, cliArgs, configArgs]
139225
}
140226

141227
const [args, cliArgs, configArgs] = await tryParse()
228+
229+
// There's no need to check flags like --help or to spawn in an existing
230+
// instance for the child process because these would have already happened in
231+
// the parent and the child wouldn't have been spawned.
232+
if (ipcMain.isChild) {
233+
await ipcMain.handshake()
234+
ipcMain.preventExit()
235+
return main(args, cliArgs, configArgs)
236+
}
237+
142238
if (args.help) {
143239
console.log("code-server", version, commit)
144240
console.log("")
@@ -148,7 +244,10 @@ async function entry(): Promise<void> {
148244
optionDescriptions().forEach((description) => {
149245
console.log("", description)
150246
})
151-
} else if (args.version) {
247+
return
248+
}
249+
250+
if (args.version) {
152251
if (args.json) {
153252
console.log({
154253
codeServer: version,
@@ -158,83 +257,23 @@ async function entry(): Promise<void> {
158257
} else {
159258
console.log(version, commit)
160259
}
161-
process.exit(0)
162-
} else if (args["list-extensions"] || args["install-extension"] || args["uninstall-extension"]) {
163-
logger.debug("forking vs code cli...")
164-
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
165-
env: {
166-
...process.env,
167-
CODE_SERVER_PARENT_PID: process.pid.toString(),
168-
},
169-
})
170-
vscode.once("message", (message: any) => {
171-
logger.debug("Got message from VS Code", field("message", message))
172-
if (message.type !== "ready") {
173-
logger.error("Unexpected response waiting for ready response")
174-
process.exit(1)
175-
}
176-
const send: CliMessage = { type: "cli", args }
177-
vscode.send(send)
178-
})
179-
vscode.once("error", (error) => {
180-
logger.error(error.message)
181-
process.exit(1)
182-
})
183-
vscode.on("exit", (code) => process.exit(code || 0))
184-
} else if (process.env.VSCODE_IPC_HOOK_CLI) {
185-
const pipeArgs: OpenCommandPipeArgs = {
186-
type: "open",
187-
folderURIs: [],
188-
forceReuseWindow: args["reuse-window"],
189-
forceNewWindow: args["new-window"],
190-
}
191-
const isDir = async (path: string): Promise<boolean> => {
192-
try {
193-
const st = await fs.stat(path)
194-
return st.isDirectory()
195-
} catch (error) {
196-
return false
197-
}
198-
}
199-
for (let i = 0; i < args._.length; i++) {
200-
const fp = path.resolve(args._[i])
201-
if (await isDir(fp)) {
202-
pipeArgs.folderURIs.push(fp)
203-
} else {
204-
if (!pipeArgs.fileURIs) {
205-
pipeArgs.fileURIs = []
206-
}
207-
pipeArgs.fileURIs.push(fp)
208-
}
209-
}
210-
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs && pipeArgs.fileURIs.length > 0) {
211-
logger.error("new-window can only be used with folder paths")
212-
process.exit(1)
213-
}
214-
if (pipeArgs.folderURIs.length === 0 && (!pipeArgs.fileURIs || pipeArgs.fileURIs.length === 0)) {
215-
logger.error("Please specify at least one file or folder argument")
216-
process.exit(1)
217-
}
218-
const vscode = http.request(
219-
{
220-
path: "/",
221-
method: "POST",
222-
socketPath: process.env["VSCODE_IPC_HOOK_CLI"],
223-
},
224-
(res) => {
225-
res.on("data", (message) => {
226-
logger.debug("Got message from VS Code", field("message", message.toString()))
227-
})
228-
},
229-
)
230-
vscode.on("error", (err) => {
231-
logger.debug("Got error from VS Code", field("error", err))
232-
})
233-
vscode.write(JSON.stringify(pipeArgs))
234-
vscode.end()
235-
} else {
236-
wrap(() => main(args, cliArgs, configArgs))
260+
return
261+
}
262+
263+
if (shouldRunVsCodeCli(args)) {
264+
return runVsCodeCli(args)
237265
}
266+
267+
const socketPath = await shouldOpenInExistingInstance(cliArgs)
268+
if (socketPath) {
269+
return openInExistingInstance(args, socketPath)
270+
}
271+
272+
const wrapper = new WrapperProcess(require("../../package.json").version)
273+
return wrapper.start()
238274
}
239275

240-
entry()
276+
entry().catch((error) => {
277+
logger.error(error.message)
278+
ipcMain.exit(error)
279+
})

0 commit comments

Comments
 (0)