mirror of https://github.com/coder/code-server.git
Set session socket into environment variable (#6282)
* Avoid spawning code-server with --reuse-window and --new-window These flags mean the user explicitly wants to open in an existing instance so if the socket is down it should error rather than try to spawn code-server normally. * Set session socket into environment variable While I was at it I added a CLI flag to override the default. I also swapped the default to --user-data-dir. The value is set on an environment variable so it can be used by the extension host similar to VSCODE_IPC_HOOK_CLI. * Add e2e test for opening files externally
This commit is contained in:
parent
56d10d82bf
commit
5c19962930
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -24,6 +24,16 @@ Code v99.99.999
|
||||||
|
|
||||||
Code v1.79.2
|
Code v1.79.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix being unable to launch multiple instances of code-server for different
|
||||||
|
users.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `--session-socket` CLI flag to configure the location of the session socket.
|
||||||
|
By default it will be placed in `--user-data-dir`.
|
||||||
|
|
||||||
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
|
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
|
||||||
|
|
||||||
Code v1.79.2
|
Code v1.79.2
|
||||||
|
|
|
@ -15,18 +15,16 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
|
||||||
===================================================================
|
===================================================================
|
||||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||||
@@ -2,7 +2,9 @@
|
@@ -2,7 +2,7 @@
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
-
|
-
|
||||||
+import * as os from 'os';
|
|
||||||
+import * as _http from 'http';
|
+import * as _http from 'http';
|
||||||
+import * as path from 'vs/base/common/path';
|
|
||||||
import * as performance from 'vs/base/common/performance';
|
import * as performance from 'vs/base/common/performance';
|
||||||
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
||||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||||
@@ -17,6 +19,7 @@ import { ExtensionRuntime } from 'vs/wor
|
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
|
||||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||||
import { realpathSync } from 'vs/base/node/extpath';
|
import { realpathSync } from 'vs/base/node/extpath';
|
||||||
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
|
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
|
||||||
|
@ -34,13 +32,14 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
|
||||||
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
|
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
|
||||||
|
|
||||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||||
@@ -83,6 +86,52 @@ export class ExtHostExtensionService ext
|
@@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
|
||||||
await interceptor.install();
|
await interceptor.install();
|
||||||
performance.mark('code/extHost/didInitAPI');
|
performance.mark('code/extHost/didInitAPI');
|
||||||
|
|
||||||
+ (async () => {
|
+ (async () => {
|
||||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||||
+ if (!socketPath) {
|
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||||
|
+ if (!socketPath || !codeServerSocketPath) {
|
||||||
+ return;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ const workspace = this._instaService.invokeFunction((accessor) => {
|
+ const workspace = this._instaService.invokeFunction((accessor) => {
|
||||||
|
@ -52,7 +51,6 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
|
||||||
+ socketPath
|
+ socketPath
|
||||||
+ };
|
+ };
|
||||||
+ const message = JSON.stringify({entry});
|
+ const message = JSON.stringify({entry});
|
||||||
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
|
|
||||||
+ await new Promise<void>((resolve, reject) => {
|
+ await new Promise<void>((resolve, reject) => {
|
||||||
+ const opts: _http.RequestOptions = {
|
+ const opts: _http.RequestOptions = {
|
||||||
+ path: '/add-session',
|
+ path: '/add-session',
|
||||||
|
@ -91,17 +89,15 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||||
===================================================================
|
===================================================================
|
||||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||||
@@ -3,6 +3,9 @@
|
@@ -3,6 +3,7 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
+import * as os from 'os';
|
|
||||||
+import * as _http from 'http';
|
+import * as _http from 'http';
|
||||||
+import * as path from 'vs/base/common/path';
|
|
||||||
import * as nativeWatchdog from 'native-watchdog';
|
import * as nativeWatchdog from 'native-watchdog';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
import * as minimist from 'minimist';
|
import * as minimist from 'minimist';
|
||||||
@@ -400,7 +403,28 @@ async function startExtensionHostProcess
|
@@ -400,7 +401,28 @@ async function startExtensionHostProcess
|
||||||
);
|
);
|
||||||
|
|
||||||
// rewrite onTerminate-function to be a proper shutdown
|
// rewrite onTerminate-function to be a proper shutdown
|
||||||
|
@ -110,11 +106,11 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||||
+ extensionHostMain.terminate(reason);
|
+ extensionHostMain.terminate(reason);
|
||||||
+
|
+
|
||||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||||
+ if (!socketPath) {
|
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||||
|
+ if (!socketPath || !codeServerSocketPath) {
|
||||||
+ return;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ const message = JSON.stringify({socketPath});
|
+ const message = JSON.stringify({socketPath});
|
||||||
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
|
|
||||||
+ const opts: _http.RequestOptions = {
|
+ const opts: _http.RequestOptions = {
|
||||||
+ path: '/delete-session',
|
+ path: '/delete-session',
|
||||||
+ socketPath: codeServerSocketPath,
|
+ socketPath: codeServerSocketPath,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as util from "../common/util"
|
||||||
import { DefaultedArgs } from "./cli"
|
import { DefaultedArgs } from "./cli"
|
||||||
import { disposer } from "./http"
|
import { disposer } from "./http"
|
||||||
import { isNodeJSErrnoException } from "./util"
|
import { isNodeJSErrnoException } from "./util"
|
||||||
import { DEFAULT_SOCKET_PATH, EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
|
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
|
||||||
import { handleUpgrade } from "./wsRouter"
|
import { handleUpgrade } from "./wsRouter"
|
||||||
|
|
||||||
type SocketOptions = { socket: string; "socket-mode"?: string }
|
type SocketOptions = { socket: string; "socket-mode"?: string }
|
||||||
|
@ -88,7 +88,7 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
|
||||||
handleUpgrade(wsRouter, server)
|
handleUpgrade(wsRouter, server)
|
||||||
|
|
||||||
const editorSessionManager = new EditorSessionManager()
|
const editorSessionManager = new EditorSessionManager()
|
||||||
const editorSessionManagerServer = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, editorSessionManager)
|
const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
|
||||||
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
|
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
|
||||||
|
|
||||||
const dispose = async () => {
|
const dispose = async () => {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { load } from "js-yaml"
|
||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
|
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
|
||||||
import { DEFAULT_SOCKET_PATH, EditorSessionManagerClient } from "./vscodeSocket"
|
import { EditorSessionManagerClient } from "./vscodeSocket"
|
||||||
|
|
||||||
export enum Feature {
|
export enum Feature {
|
||||||
// No current experimental features!
|
// No current experimental features!
|
||||||
|
@ -51,6 +51,7 @@ export interface UserProvidedCodeArgs {
|
||||||
"disable-file-downloads"?: boolean
|
"disable-file-downloads"?: boolean
|
||||||
"disable-workspace-trust"?: boolean
|
"disable-workspace-trust"?: boolean
|
||||||
"disable-getting-started-override"?: boolean
|
"disable-getting-started-override"?: boolean
|
||||||
|
"session-socket"?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,6 +161,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||||
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
|
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
|
||||||
"then notifies you once every week that a new release is available.",
|
"then notifies you once every week that a new release is available.",
|
||||||
},
|
},
|
||||||
|
"session-socket": {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
"disable-file-downloads": {
|
"disable-file-downloads": {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
description:
|
description:
|
||||||
|
@ -459,6 +463,7 @@ export interface DefaultedArgs extends ConfigArgs {
|
||||||
usingEnvHashedPassword: boolean
|
usingEnvHashedPassword: boolean
|
||||||
"extensions-dir": string
|
"extensions-dir": string
|
||||||
"user-data-dir": string
|
"user-data-dir": string
|
||||||
|
"session-socket": string
|
||||||
/* Positional arguments. */
|
/* Positional arguments. */
|
||||||
_: string[]
|
_: string[]
|
||||||
}
|
}
|
||||||
|
@ -479,6 +484,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||||
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
|
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!args["session-socket"]) {
|
||||||
|
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
|
||||||
|
}
|
||||||
|
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
|
||||||
|
|
||||||
// --verbose takes priority over --log and --log takes priority over the
|
// --verbose takes priority over --log and --log takes priority over the
|
||||||
// environment variable.
|
// environment variable.
|
||||||
if (args.verbose) {
|
if (args.verbose) {
|
||||||
|
@ -739,7 +749,10 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
|
||||||
* existing instance. The arguments here should be the arguments the user
|
* existing instance. The arguments here should be the arguments the user
|
||||||
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
|
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
|
||||||
*/
|
*/
|
||||||
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
|
export const shouldOpenInExistingInstance = async (
|
||||||
|
args: UserProvidedArgs,
|
||||||
|
sessionSocket: string,
|
||||||
|
): Promise<string | undefined> => {
|
||||||
// Always use the existing instance if we're running from VS Code's terminal.
|
// Always use the existing instance if we're running from VS Code's terminal.
|
||||||
if (process.env.VSCODE_IPC_HOOK_CLI) {
|
if (process.env.VSCODE_IPC_HOOK_CLI) {
|
||||||
logger.debug("Found VSCODE_IPC_HOOK_CLI")
|
logger.debug("Found VSCODE_IPC_HOOK_CLI")
|
||||||
|
@ -747,21 +760,22 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
|
||||||
}
|
}
|
||||||
|
|
||||||
const paths = getResolvedPathsFromArgs(args)
|
const paths = getResolvedPathsFromArgs(args)
|
||||||
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
|
const client = new EditorSessionManagerClient(sessionSocket)
|
||||||
|
|
||||||
// If we can't connect to the socket then there's no existing instance.
|
|
||||||
if (!(await client.canConnect())) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// If these flags are set then assume the user is trying to open in an
|
// If these flags are set then assume the user is trying to open in an
|
||||||
// existing instance since these flags have no effect otherwise.
|
// existing instance since these flags have no effect otherwise. That means
|
||||||
|
// if there is no existing instance we should error rather than falling back
|
||||||
|
// to spawning code-server normally.
|
||||||
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
|
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
|
||||||
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
|
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
|
||||||
}, 0)
|
}, 0)
|
||||||
if (openInFlagCount > 0) {
|
if (openInFlagCount > 0) {
|
||||||
logger.debug("Found --reuse-window or --new-window")
|
logger.debug("Found --reuse-window or --new-window")
|
||||||
return await client.getConnectedSocketPath(paths[0])
|
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||||
|
if (!socketPath) {
|
||||||
|
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
|
||||||
|
}
|
||||||
|
return socketPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible the user is trying to spawn another instance of code-server.
|
// It's possible the user is trying to spawn another instance of code-server.
|
||||||
|
@ -769,7 +783,11 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
|
||||||
// code-server is invoked exactly like this: `code-server my-file`).
|
// code-server is invoked exactly like this: `code-server my-file`).
|
||||||
// 2. That a file or directory was passed.
|
// 2. That a file or directory was passed.
|
||||||
// 3. That the socket is active.
|
// 3. That the socket is active.
|
||||||
|
// 4. That an instance exists to handle the path (implied by #3).
|
||||||
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
|
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
|
||||||
|
if (!(await client.canConnect())) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
const socketPath = await client.getConnectedSocketPath(paths[0])
|
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||||
if (socketPath) {
|
if (socketPath) {
|
||||||
logger.debug("Found existing code-server socket")
|
logger.debug("Found existing code-server socket")
|
||||||
|
|
|
@ -51,7 +51,7 @@ async function entry(): Promise<void> {
|
||||||
return runCodeCli(args)
|
return runCodeCli(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
const socketPath = await shouldOpenInExistingInstance(cliArgs)
|
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
|
||||||
if (socketPath) {
|
if (socketPath) {
|
||||||
logger.debug("Trying to open in existing instance")
|
logger.debug("Trying to open in existing instance")
|
||||||
return openInExistingInstance(args, socketPath)
|
return openInExistingInstance(args, socketPath)
|
||||||
|
|
|
@ -4,10 +4,7 @@ import * as http from "http"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { HttpCode } from "../common/http"
|
import { HttpCode } from "../common/http"
|
||||||
import { listen } from "./app"
|
import { listen } from "./app"
|
||||||
import { canConnect, paths } from "./util"
|
import { canConnect } from "./util"
|
||||||
|
|
||||||
// Socket path of the daemonized code-server instance.
|
|
||||||
export const DEFAULT_SOCKET_PATH = path.join(paths.data, `code-server-ipc.sock`)
|
|
||||||
|
|
||||||
export interface EditorSessionEntry {
|
export interface EditorSessionEntry {
|
||||||
workspace: {
|
workspace: {
|
||||||
|
|
|
@ -117,40 +117,26 @@ export class CodeServer {
|
||||||
* directories.
|
* directories.
|
||||||
*/
|
*/
|
||||||
private async spawn(): Promise<CodeServerProcess> {
|
private async spawn(): Promise<CodeServerProcess> {
|
||||||
// This will be used both as the workspace and data directory to ensure
|
|
||||||
// instances don't bleed into each other.
|
|
||||||
const dir = await this.createWorkspace()
|
const dir = await this.createWorkspace()
|
||||||
|
const args = await this.argsWithDefaults([
|
||||||
|
"--auth",
|
||||||
|
"none",
|
||||||
|
// The workspace to open.
|
||||||
|
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
||||||
|
...this.args,
|
||||||
|
// Using port zero will spawn on a random port.
|
||||||
|
"--bind-addr",
|
||||||
|
"127.0.0.1:0",
|
||||||
|
])
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const args = [
|
|
||||||
this.entry,
|
|
||||||
"--extensions-dir",
|
|
||||||
path.join(dir, "extensions"),
|
|
||||||
"--auth",
|
|
||||||
"none",
|
|
||||||
// The workspace to open.
|
|
||||||
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
|
||||||
...this.args,
|
|
||||||
// Using port zero will spawn on a random port.
|
|
||||||
"--bind-addr",
|
|
||||||
"127.0.0.1:0",
|
|
||||||
// Setting the XDG variables would be easier and more thorough but the
|
|
||||||
// modules we import ignores those variables for non-Linux operating
|
|
||||||
// systems so use these flags instead.
|
|
||||||
"--config",
|
|
||||||
path.join(dir, "config.yaml"),
|
|
||||||
"--user-data-dir",
|
|
||||||
dir,
|
|
||||||
]
|
|
||||||
this.logger.debug("spawning `node " + args.join(" ") + "`")
|
this.logger.debug("spawning `node " + args.join(" ") + "`")
|
||||||
const proc = cp.spawn("node", args, {
|
const proc = cp.spawn("node", args, {
|
||||||
cwd: path.join(__dirname, "../../.."),
|
cwd: path.join(__dirname, "../../.."),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...this.env,
|
...this.env,
|
||||||
// Set to empty string to prevent code-server from
|
// Prevent code-server from using the existing instance when running
|
||||||
// using the existing instance when running the e2e tests
|
// the e2e tests from an integrated terminal.
|
||||||
// from an integrated terminal.
|
|
||||||
VSCODE_IPC_HOOK_CLI: "",
|
VSCODE_IPC_HOOK_CLI: "",
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
},
|
},
|
||||||
|
@ -173,11 +159,15 @@ export class CodeServer {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Tracks when the HTTP and session servers are ready.
|
||||||
|
let httpAddress: string | undefined
|
||||||
|
let sessionAddress: string | undefined
|
||||||
|
|
||||||
let resolved = false
|
let resolved = false
|
||||||
proc.stdout.setEncoding("utf8")
|
proc.stdout.setEncoding("utf8")
|
||||||
onLine(proc, (line) => {
|
onLine(proc, (line) => {
|
||||||
// As long as we are actively getting input reset the timer. If we stop
|
// As long as we are actively getting input reset the timer. If we stop
|
||||||
// getting input and still have not found the address the timer will
|
// getting input and still have not found the addresses the timer will
|
||||||
// reject.
|
// reject.
|
||||||
timer.reset()
|
timer.reset()
|
||||||
|
|
||||||
|
@ -186,20 +176,69 @@ export class CodeServer {
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
|
||||||
|
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
||||||
if (match) {
|
if (match) {
|
||||||
// Cookies don't seem to work on IP address so swap to localhost.
|
// Cookies don't seem to work on IP addresses so swap to localhost.
|
||||||
// TODO: Investigate whether this is a bug with code-server.
|
// TODO: Investigate whether this is a bug with code-server.
|
||||||
const address = match[1].replace("127.0.0.1", "localhost")
|
httpAddress = match[1].replace("127.0.0.1", "localhost")
|
||||||
this.logger.debug(`spawned on ${address}`)
|
}
|
||||||
|
|
||||||
|
match = line.trim().match(/Session server listening on (.+)$/)
|
||||||
|
if (match) {
|
||||||
|
sessionAddress = match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
|
||||||
resolved = true
|
resolved = true
|
||||||
timer.dispose()
|
timer.dispose()
|
||||||
resolve({ process: proc, address })
|
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
|
||||||
|
resolve({ process: proc, address: httpAddress })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a short-lived command.
|
||||||
|
*/
|
||||||
|
async run(args: string[]): Promise<void> {
|
||||||
|
args = await this.argsWithDefaults(args)
|
||||||
|
this.logger.debug("executing `node " + args.join(" ") + "`")
|
||||||
|
await util.promisify(cp.exec)("node " + args.join(" "), {
|
||||||
|
cwd: path.join(__dirname, "../../.."),
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...this.env,
|
||||||
|
// Prevent code-server from using the existing instance when running
|
||||||
|
// the e2e tests from an integrated terminal.
|
||||||
|
VSCODE_IPC_HOOK_CLI: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine arguments with defaults.
|
||||||
|
*/
|
||||||
|
private async argsWithDefaults(args: string[]): Promise<string[]> {
|
||||||
|
// This will be used both as the workspace and data directory to ensure
|
||||||
|
// instances don't bleed into each other.
|
||||||
|
const dir = await this.workspaceDir
|
||||||
|
return [
|
||||||
|
this.entry,
|
||||||
|
"--extensions-dir",
|
||||||
|
path.join(dir, "extensions"),
|
||||||
|
...args,
|
||||||
|
// Setting the XDG variables would be easier and more thorough but the
|
||||||
|
// modules we import ignores those variables for non-Linux operating
|
||||||
|
// systems so use these flags instead.
|
||||||
|
"--config",
|
||||||
|
path.join(dir, "config.yaml"),
|
||||||
|
"--user-data-dir",
|
||||||
|
dir,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the code-server process.
|
* Close the code-server process.
|
||||||
*/
|
*/
|
||||||
|
@ -364,6 +403,13 @@ export class CodeServerPage {
|
||||||
await this.waitForTab(file)
|
await this.waitForTab(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file through an external command.
|
||||||
|
*/
|
||||||
|
async openFileExternally(file: string) {
|
||||||
|
await this.codeServer.run(["--reuse-window", file])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for a tab to open for the specified file.
|
* Wait for a tab to open for the specified file.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -35,13 +35,19 @@ describe("Integrated Terminal", ["--disable-workspace-trust"], {}, () => {
|
||||||
const tmpFolderPath = await tmpdir(testName)
|
const tmpFolderPath = await tmpdir(testName)
|
||||||
const tmpFile = path.join(tmpFolderPath, "test-file")
|
const tmpFile = path.join(tmpFolderPath, "test-file")
|
||||||
await fs.writeFile(tmpFile, "test")
|
await fs.writeFile(tmpFile, "test")
|
||||||
const fileName = path.basename(tmpFile)
|
|
||||||
|
|
||||||
await codeServerPage.focusTerminal()
|
await codeServerPage.focusTerminal()
|
||||||
|
|
||||||
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
|
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
|
||||||
await codeServerPage.page.keyboard.press("Enter")
|
await codeServerPage.page.keyboard.press("Enter")
|
||||||
|
|
||||||
await codeServerPage.waitForTab(fileName)
|
await codeServerPage.waitForTab(path.basename(tmpFile))
|
||||||
|
|
||||||
|
const externalTmpFile = path.join(tmpFolderPath, "test-external-file")
|
||||||
|
await fs.writeFile(externalTmpFile, "foobar")
|
||||||
|
|
||||||
|
await codeServerPage.openFileExternally(externalTmpFile)
|
||||||
|
|
||||||
|
await codeServerPage.waitForTab(path.basename(externalTmpFile))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
||||||
import { generatePassword, paths } from "../../../src/node/util"
|
import { generatePassword, paths } from "../../../src/node/util"
|
||||||
import {
|
import {
|
||||||
DEFAULT_SOCKET_PATH,
|
|
||||||
EditorSessionManager,
|
EditorSessionManager,
|
||||||
EditorSessionManagerClient,
|
EditorSessionManagerClient,
|
||||||
makeEditorSessionManagerServer,
|
makeEditorSessionManagerServer,
|
||||||
|
@ -37,6 +36,7 @@ const defaults = {
|
||||||
usingEnvHashedPassword: false,
|
usingEnvHashedPassword: false,
|
||||||
"extensions-dir": path.join(paths.data, "extensions"),
|
"extensions-dir": path.join(paths.data, "extensions"),
|
||||||
"user-data-dir": paths.data,
|
"user-data-dir": paths.data,
|
||||||
|
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
|
||||||
_: [],
|
_: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +103,8 @@ describe("parser", () => {
|
||||||
|
|
||||||
"--disable-getting-started-override",
|
"--disable-getting-started-override",
|
||||||
|
|
||||||
|
["--session-socket", "/tmp/override-code-server-ipc-socket"],
|
||||||
|
|
||||||
["--host", "0.0.0.0"],
|
["--host", "0.0.0.0"],
|
||||||
"4",
|
"4",
|
||||||
"--",
|
"--",
|
||||||
|
@ -136,6 +138,7 @@ describe("parser", () => {
|
||||||
"welcome-text": "welcome to code",
|
"welcome-text": "welcome to code",
|
||||||
version: true,
|
version: true,
|
||||||
"bind-addr": "192.169.0.1:8080",
|
"bind-addr": "192.169.0.1:8080",
|
||||||
|
"session-socket": "/tmp/override-code-server-ipc-socket",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -504,22 +507,23 @@ describe("cli", () => {
|
||||||
it("should use existing if inside code-server", async () => {
|
it("should use existing if inside code-server", async () => {
|
||||||
process.env.VSCODE_IPC_HOOK_CLI = "test"
|
process.env.VSCODE_IPC_HOOK_CLI = "test"
|
||||||
const args: UserProvidedArgs = {}
|
const args: UserProvidedArgs = {}
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||||
|
|
||||||
args.port = 8081
|
args.port = 8081
|
||||||
args._ = ["./file"]
|
args._ = ["./file"]
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should use existing if --reuse-window is set", async () => {
|
it("should use existing if --reuse-window is set", async () => {
|
||||||
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager())
|
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||||
|
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||||
|
|
||||||
const args: UserProvidedArgs = {}
|
const args: UserProvidedArgs = {}
|
||||||
args["reuse-window"] = true
|
args["reuse-window"] = true
|
||||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined)
|
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||||
|
|
||||||
const socketPath = path.join(tmpDirPath, "socket")
|
const socketPath = path.join(tmpDirPath, "socket")
|
||||||
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
|
const client = new EditorSessionManagerClient(sessionSocket)
|
||||||
await client.addSession({
|
await client.addSession({
|
||||||
entry: {
|
entry: {
|
||||||
workspace: {
|
workspace: {
|
||||||
|
@ -537,24 +541,25 @@ describe("cli", () => {
|
||||||
})
|
})
|
||||||
const vscodeSockets = listenOn(socketPath)
|
const vscodeSockets = listenOn(socketPath)
|
||||||
|
|
||||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(socketPath)
|
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||||
|
|
||||||
args.port = 8081
|
args.port = 8081
|
||||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(socketPath)
|
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||||
|
|
||||||
server.close()
|
server.close()
|
||||||
vscodeSockets.close()
|
vscodeSockets.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should use existing if --new-window is set", async () => {
|
it("should use existing if --new-window is set", async () => {
|
||||||
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager())
|
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||||
|
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||||
|
|
||||||
const args: UserProvidedArgs = {}
|
const args: UserProvidedArgs = {}
|
||||||
args["new-window"] = true
|
args["new-window"] = true
|
||||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined)
|
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||||
|
|
||||||
const socketPath = path.join(tmpDirPath, "socket")
|
const socketPath = path.join(tmpDirPath, "socket")
|
||||||
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
|
const client = new EditorSessionManagerClient(sessionSocket)
|
||||||
await client.addSession({
|
await client.addSession({
|
||||||
entry: {
|
entry: {
|
||||||
workspace: {
|
workspace: {
|
||||||
|
@ -572,25 +577,26 @@ describe("cli", () => {
|
||||||
})
|
})
|
||||||
const vscodeSockets = listenOn(socketPath)
|
const vscodeSockets = listenOn(socketPath)
|
||||||
|
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||||
|
|
||||||
args.port = 8081
|
args.port = 8081
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||||
|
|
||||||
server.close()
|
server.close()
|
||||||
vscodeSockets.close()
|
vscodeSockets.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
|
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
|
||||||
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager())
|
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||||
|
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||||
|
|
||||||
const args: UserProvidedArgs = {}
|
const args: UserProvidedArgs = {}
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||||
|
|
||||||
args._ = ["./file"]
|
args._ = ["./file"]
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||||
|
|
||||||
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
|
const client = new EditorSessionManagerClient(sessionSocket)
|
||||||
const socketPath = path.join(tmpDirPath, "socket")
|
const socketPath = path.join(tmpDirPath, "socket")
|
||||||
await client.addSession({
|
await client.addSession({
|
||||||
entry: {
|
entry: {
|
||||||
|
@ -609,18 +615,19 @@ describe("cli", () => {
|
||||||
})
|
})
|
||||||
const vscodeSockets = listenOn(socketPath)
|
const vscodeSockets = listenOn(socketPath)
|
||||||
|
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||||
|
|
||||||
args.port = 8081
|
args.port = 8081
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||||
|
|
||||||
server.close()
|
server.close()
|
||||||
vscodeSockets.close()
|
vscodeSockets.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should prefer matching sessions for only the first path", async () => {
|
it("should prefer matching sessions for only the first path", async () => {
|
||||||
const server = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, new EditorSessionManager())
|
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||||
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
|
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||||
|
const client = new EditorSessionManagerClient(sessionSocket)
|
||||||
await client.addSession({
|
await client.addSession({
|
||||||
entry: {
|
entry: {
|
||||||
workspace: {
|
workspace: {
|
||||||
|
@ -655,7 +662,7 @@ describe("cli", () => {
|
||||||
|
|
||||||
const args: UserProvidedArgs = {}
|
const args: UserProvidedArgs = {}
|
||||||
args._ = ["/aaa/file", "/bbb/file"]
|
args._ = ["/aaa/file", "/bbb/file"]
|
||||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
||||||
|
|
||||||
server.close()
|
server.close()
|
||||||
})
|
})
|
||||||
|
|
|
@ -43,6 +43,7 @@ describe("plugin", () => {
|
||||||
usingEnvHashedPassword: false,
|
usingEnvHashedPassword: false,
|
||||||
"extensions-dir": "",
|
"extensions-dir": "",
|
||||||
"user-data-dir": "",
|
"user-data-dir": "",
|
||||||
|
"session-socket": "",
|
||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
import { logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
import * as app from "../../../src/node/app"
|
import * as app from "../../../src/node/app"
|
||||||
import { paths } from "../../../src/node/util"
|
import { EditorSessionManager, makeEditorSessionManagerServer } from "../../../src/node/vscodeSocket"
|
||||||
import {
|
|
||||||
DEFAULT_SOCKET_PATH,
|
|
||||||
EditorSessionManager,
|
|
||||||
makeEditorSessionManagerServer,
|
|
||||||
} from "../../../src/node/vscodeSocket"
|
|
||||||
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
|
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
|
||||||
|
|
||||||
describe("DEFAULT_SOCKET_PATH", () => {
|
|
||||||
it("should be a unique path per user", () => {
|
|
||||||
expect(DEFAULT_SOCKET_PATH.startsWith(paths.data)).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("makeEditorSessionManagerServer", () => {
|
describe("makeEditorSessionManagerServer", () => {
|
||||||
let tmpDirPath: string
|
let tmpDirPath: string
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue