Skip to content

Commit 9afefcb

Browse files
authored
Merge a882be5 into fa461ff
2 parents fa461ff + a882be5 commit 9afefcb

File tree

9 files changed

+204
-173
lines changed

9 files changed

+204
-173
lines changed

src/node/cli.ts

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import * as path from "path"
66
import { Args as VsArgs } from "../../typings/ipc"
77
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util"
88

9+
export enum Feature {
10+
/** Web socket compression. */
11+
PermessageDeflate = "permessage-deflate",
12+
}
13+
914
export enum AuthType {
1015
Password = "password",
1116
None = "none",
@@ -35,6 +40,7 @@ export interface Args extends VsArgs {
3540
"cert-key"?: string
3641
"disable-telemetry"?: boolean
3742
"disable-update-check"?: boolean
43+
enable?: string[]
3844
help?: boolean
3945
host?: string
4046
json?: boolean
@@ -128,6 +134,9 @@ const options: Options<Required<Args>> = {
128134
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
129135
"then notifies you once every week that a new release is available.",
130136
},
137+
// --enable can be used to enable experimental features. These features
138+
// provide no guarantees.
139+
enable: { type: "string[]" },
131140
help: { type: "boolean", short: "h", description: "Show this output." },
132141
json: { type: "boolean" },
133142
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },

src/node/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export const version = pkg.version || "development"
2020
export const commit = pkg.commit || "development"
2121
export const rootPath = path.resolve(__dirname, "../..")
2222
export const tmpdir = path.join(os.tmpdir(), "code-server")
23+
export const isDevMode = commit === "development"

src/node/entry.ts

+4-149
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,17 @@
1-
import { field, logger } from "@coder/logger"
2-
import * as cp from "child_process"
3-
import http from "http"
4-
import * as path from "path"
5-
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc"
6-
import { plural } from "../common/util"
7-
import { createApp, ensureAddress } from "./app"
1+
import { logger } from "@coder/logger"
82
import {
9-
AuthType,
10-
DefaultedArgs,
113
optionDescriptions,
124
parse,
135
readConfigFile,
146
setDefaults,
157
shouldOpenInExistingInstance,
168
shouldRunVsCodeCli,
179
} from "./cli"
18-
import { coderCloudBind } from "./coder_cloud"
1910
import { commit, version } from "./constants"
11+
import { openInExistingInstance, runCodeServer, runVsCodeCli } from "./main"
2012
import * as proxyAgent from "./proxy_agent"
21-
import { register } from "./routes"
22-
import { humanPath, isFile, open } from "./util"
2313
import { isChild, wrapper } from "./wrapper"
2414

25-
export const runVsCodeCli = (args: DefaultedArgs): void => {
26-
logger.debug("forking vs code cli...")
27-
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
28-
env: {
29-
...process.env,
30-
CODE_SERVER_PARENT_PID: process.pid.toString(),
31-
},
32-
})
33-
vscode.once("message", (message: any) => {
34-
logger.debug("got message from VS Code", field("message", message))
35-
if (message.type !== "ready") {
36-
logger.error("Unexpected response waiting for ready response", field("type", message.type))
37-
process.exit(1)
38-
}
39-
const send: CliMessage = { type: "cli", args }
40-
vscode.send(send)
41-
})
42-
vscode.once("error", (error) => {
43-
logger.error("Got error from VS Code", field("error", error))
44-
process.exit(1)
45-
})
46-
vscode.on("exit", (code) => process.exit(code || 0))
47-
}
48-
49-
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
50-
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
51-
type: "open",
52-
folderURIs: [],
53-
fileURIs: [],
54-
forceReuseWindow: args["reuse-window"],
55-
forceNewWindow: args["new-window"],
56-
}
57-
58-
for (let i = 0; i < args._.length; i++) {
59-
const fp = path.resolve(args._[i])
60-
if (await isFile(fp)) {
61-
pipeArgs.fileURIs.push(fp)
62-
} else {
63-
pipeArgs.folderURIs.push(fp)
64-
}
65-
}
66-
67-
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
68-
logger.error("--new-window can only be used with folder paths")
69-
process.exit(1)
70-
}
71-
72-
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
73-
logger.error("Please specify at least one file or folder")
74-
process.exit(1)
75-
}
76-
77-
const vscode = http.request(
78-
{
79-
path: "/",
80-
method: "POST",
81-
socketPath,
82-
},
83-
(response) => {
84-
response.on("data", (message) => {
85-
logger.debug("got message from VS Code", field("message", message.toString()))
86-
})
87-
},
88-
)
89-
vscode.on("error", (error: unknown) => {
90-
logger.error("got error from VS Code", field("error", error))
91-
})
92-
vscode.write(JSON.stringify(pipeArgs))
93-
vscode.end()
94-
}
95-
96-
const main = async (args: DefaultedArgs): Promise<void> => {
97-
logger.info(`code-server ${version} ${commit}`)
98-
99-
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
100-
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
101-
102-
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
103-
throw new Error(
104-
"Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
105-
)
106-
}
107-
108-
const [app, wsApp, server] = await createApp(args)
109-
const serverAddress = ensureAddress(server)
110-
await register(app, wsApp, server, args)
111-
112-
logger.info(`Using config file ${humanPath(args.config)}`)
113-
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
114-
115-
if (args.auth === AuthType.Password) {
116-
logger.info(" - Authentication is enabled")
117-
if (args.usingEnvPassword) {
118-
logger.info(" - Using password from $PASSWORD")
119-
} else if (args.usingEnvHashedPassword) {
120-
logger.info(" - Using password from $HASHED_PASSWORD")
121-
} else {
122-
logger.info(` - Using password from ${humanPath(args.config)}`)
123-
}
124-
} else {
125-
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
126-
}
127-
128-
if (args.cert) {
129-
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
130-
} else {
131-
logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
132-
}
133-
134-
if (args["proxy-domain"].length > 0) {
135-
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
136-
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
137-
}
138-
139-
if (args.link) {
140-
try {
141-
await coderCloudBind(serverAddress.replace(/^https?:\/\//, ""), args.link.value)
142-
logger.info(" - Connected to cloud agent")
143-
} catch (err) {
144-
logger.error(err.message)
145-
wrapper.exit(1)
146-
}
147-
}
148-
149-
if (!args.socket && args.open) {
150-
// The web socket doesn't seem to work if browsing with 0.0.0.0.
151-
const openAddress = serverAddress.replace("://0.0.0.0", "://localhost")
152-
try {
153-
await open(openAddress)
154-
logger.info(`Opened ${openAddress}`)
155-
} catch (error) {
156-
logger.error("Failed to open", field("address", openAddress), field("error", error))
157-
}
158-
}
159-
}
160-
16115
async function entry(): Promise<void> {
16216
proxyAgent.monkeyPatch(false)
16317

@@ -170,7 +24,8 @@ async function entry(): Promise<void> {
17024
if (isChild(wrapper)) {
17125
const args = await wrapper.handshake()
17226
wrapper.preventExit()
173-
return main(args)
27+
await runCodeServer(args)
28+
return
17429
}
17530

17631
const cliArgs = parse(process.argv.slice(2))

src/node/main.ts

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { field, logger } from "@coder/logger"
2+
import * as cp from "child_process"
3+
import http from "http"
4+
import * as path from "path"
5+
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc"
6+
import { plural } from "../common/util"
7+
import { createApp, ensureAddress } from "./app"
8+
import { AuthType, DefaultedArgs, Feature } from "./cli"
9+
import { coderCloudBind } from "./coder_cloud"
10+
import { commit, version } from "./constants"
11+
import { register } from "./routes"
12+
import { humanPath, isFile, open } from "./util"
13+
14+
export const runVsCodeCli = (args: DefaultedArgs): void => {
15+
logger.debug("forking vs code cli...")
16+
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
17+
env: {
18+
...process.env,
19+
CODE_SERVER_PARENT_PID: process.pid.toString(),
20+
},
21+
})
22+
vscode.once("message", (message: any) => {
23+
logger.debug("got message from VS Code", field("message", message))
24+
if (message.type !== "ready") {
25+
logger.error("Unexpected response waiting for ready response", field("type", message.type))
26+
process.exit(1)
27+
}
28+
const send: CliMessage = { type: "cli", args }
29+
vscode.send(send)
30+
})
31+
vscode.once("error", (error) => {
32+
logger.error("Got error from VS Code", field("error", error))
33+
process.exit(1)
34+
})
35+
vscode.on("exit", (code) => process.exit(code || 0))
36+
}
37+
38+
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
39+
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
40+
type: "open",
41+
folderURIs: [],
42+
fileURIs: [],
43+
forceReuseWindow: args["reuse-window"],
44+
forceNewWindow: args["new-window"],
45+
}
46+
47+
for (let i = 0; i < args._.length; i++) {
48+
const fp = path.resolve(args._[i])
49+
if (await isFile(fp)) {
50+
pipeArgs.fileURIs.push(fp)
51+
} else {
52+
pipeArgs.folderURIs.push(fp)
53+
}
54+
}
55+
56+
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
57+
logger.error("--new-window can only be used with folder paths")
58+
process.exit(1)
59+
}
60+
61+
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
62+
logger.error("Please specify at least one file or folder")
63+
process.exit(1)
64+
}
65+
66+
const vscode = http.request(
67+
{
68+
path: "/",
69+
method: "POST",
70+
socketPath,
71+
},
72+
(response) => {
73+
response.on("data", (message) => {
74+
logger.debug("got message from VS Code", field("message", message.toString()))
75+
})
76+
},
77+
)
78+
vscode.on("error", (error: unknown) => {
79+
logger.error("got error from VS Code", field("error", error))
80+
})
81+
vscode.write(JSON.stringify(pipeArgs))
82+
vscode.end()
83+
}
84+
85+
export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> => {
86+
logger.info(`code-server ${version} ${commit}`)
87+
88+
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
89+
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
90+
91+
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
92+
throw new Error(
93+
"Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
94+
)
95+
}
96+
97+
const [app, wsApp, server] = await createApp(args)
98+
const serverAddress = ensureAddress(server)
99+
await register(app, wsApp, server, args)
100+
101+
logger.info(`Using config file ${humanPath(args.config)}`)
102+
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
103+
if (args.auth === AuthType.Password) {
104+
logger.info(" - Authentication is enabled")
105+
if (args.usingEnvPassword) {
106+
logger.info(" - Using password from $PASSWORD")
107+
} else if (args.usingEnvHashedPassword) {
108+
logger.info(" - Using password from $HASHED_PASSWORD")
109+
} else {
110+
logger.info(` - Using password from ${humanPath(args.config)}`)
111+
}
112+
} else {
113+
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
114+
}
115+
116+
if (args.cert) {
117+
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
118+
} else {
119+
logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
120+
}
121+
122+
if (args["proxy-domain"].length > 0) {
123+
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
124+
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
125+
}
126+
127+
if (args.link) {
128+
await coderCloudBind(serverAddress.replace(/^https?:\/\//, ""), args.link.value)
129+
logger.info(" - Connected to cloud agent")
130+
}
131+
132+
if (args.enable && args.enable.length > 0) {
133+
logger.info("Enabling the following experimental features:")
134+
args.enable.forEach((feature) => {
135+
if (Object.values(Feature).includes(feature as Feature)) {
136+
logger.info(` - "${feature}"`)
137+
} else {
138+
logger.error(` X "${feature}" (unknown feature)`)
139+
}
140+
})
141+
// TODO: Could be nice to add wrapping to the logger?
142+
logger.info(
143+
" The code-server project does not provide stability guarantees or commit to fixing bugs relating to these experimental features. When filing bug reports, please ensure that you can reproduce the bug with all experimental features turned off.",
144+
)
145+
}
146+
147+
if (!args.socket && args.open) {
148+
// The web socket doesn't seem to work if browsing with 0.0.0.0.
149+
const openAddress = serverAddress.replace("://0.0.0.0", "://localhost")
150+
try {
151+
await open(openAddress)
152+
logger.info(`Opened ${openAddress}`)
153+
} catch (error) {
154+
logger.error("Failed to open", field("address", openAddress), field("error", error))
155+
}
156+
}
157+
158+
return server
159+
}

0 commit comments

Comments
 (0)