refactor: remove folder/workspace from vsCodeCliArgs (#4932)

* refactor: remove folder/workspace from vsCodeCliArgs

Since we handle this in the vscode.ts route, we no longer need to pass it to VS
Code as a CLI arg since it's deprecated on that side.

* feat(vscode): redirect to folder from cli

* Update src/node/routes/vscode.ts

Co-authored-by: Asher <ash@coder.com>

* fixup!: update _: type

* fixup!: move vars to lower if block

* fixup!: share redirect block

* fixup!: mmove req.query.ew block into if

* fixup!: refactor vscode tests

* refactor: make vscode.ts logic easier to read

* fixup!: fix broken tests and clean up logic

* chore: upgrade vscode version

* fixup!: delete unnecessary if closed block

* Update src/node/routes/vscode.ts

Co-authored-by: Asher <ash@coder.com>

* fixup!: rename to FOLDER_OR_WORKSPACE_WAS_CLOSED

Co-authored-by: Asher <ash@coder.com>
This commit is contained in:
Joe Previte 2022-03-02 15:36:38 -07:00 committed by GitHub
parent b0181120d4
commit 78658f1cf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 128 deletions

View File

@ -3,15 +3,7 @@ import { promises as fs } from "fs"
import yaml from "js-yaml" import yaml from "js-yaml"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
import { import { canConnect, generateCertificate, generatePassword, humanPath, paths, isNodeJSErrnoException } from "./util"
canConnect,
generateCertificate,
generatePassword,
humanPath,
paths,
isNodeJSErrnoException,
isFile,
} from "./util"
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc") const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
@ -448,7 +440,7 @@ export interface DefaultedArgs extends ConfigArgs {
"extensions-dir": string "extensions-dir": string
"user-data-dir": string "user-data-dir": string
/* Positional arguments. */ /* Positional arguments. */
_: [] _: string[]
} }
/** /**
@ -770,25 +762,9 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
* Convert our arguments to VS Code server arguments. * Convert our arguments to VS Code server arguments.
*/ */
export const toVsCodeArgs = async (args: DefaultedArgs): Promise<CodeServerLib.ServerParsedArgs> => { export const toVsCodeArgs = async (args: DefaultedArgs): Promise<CodeServerLib.ServerParsedArgs> => {
let workspace = ""
let folder = ""
if (args._.length) {
const lastEntry = path.resolve(args._[args._.length - 1])
const entryIsFile = await isFile(lastEntry)
if (entryIsFile && path.extname(lastEntry) === ".code-workspace") {
workspace = lastEntry
} else if (!entryIsFile) {
folder = lastEntry
}
// Otherwise it is a regular file. Spawning VS Code with a file is not yet
// supported but it can be done separately after code-server spawns.
}
return { return {
"connection-token": "0000", "connection-token": "0000",
...args, ...args,
workspace,
folder,
"accept-server-license-terms": true, "accept-server-license-terms": true,
/** Type casting. */ /** Type casting. */
help: !!args.help, help: !!args.help,

View File

@ -1,12 +1,13 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import * as express from "express" import * as express from "express"
import * as path from "path"
import { WebsocketRequest } from "../../../typings/pluginapi" import { WebsocketRequest } from "../../../typings/pluginapi"
import { logError } from "../../common/util" import { logError } from "../../common/util"
import { toVsCodeArgs } from "../cli" import { toVsCodeArgs } from "../cli"
import { isDevMode } from "../constants" import { isDevMode } from "../constants"
import { authenticated, ensureAuthenticated, redirect, self } from "../http" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { SocketProxyProvider } from "../socket" import { SocketProxyProvider } from "../socket"
import { loadAMDModule } from "../util" import { isFile, loadAMDModule } from "../util"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
import { errorHandler } from "./errors" import { errorHandler } from "./errors"
@ -25,6 +26,9 @@ export class CodeServerRouteWrapper {
private $root: express.Handler = async (req, res, next) => { private $root: express.Handler = async (req, res, next) => {
const isAuthenticated = await authenticated(req) const isAuthenticated = await authenticated(req)
const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace
// Ew means the workspace was closed so clear the last folder/workspace.
const FOLDER_OR_WORKSPACE_WAS_CLOSED = req.query.ew
if (!isAuthenticated) { if (!isAuthenticated) {
const to = self(req) const to = self(req)
@ -33,25 +37,38 @@ export class CodeServerRouteWrapper {
}) })
} }
const { query } = await req.settings.read() if (NO_FOLDER_OR_WORKSPACE_QUERY && !FOLDER_OR_WORKSPACE_WAS_CLOSED) {
if (query) { const settings = await req.settings.read()
// Ew means the workspace was closed so clear the last folder/workspace. const lastOpened = settings.query || {}
if (req.query.ew) { // This flag disables the last opened behavior
delete query.folder const IGNORE_LAST_OPENED = req.args["ignore-last-opened"]
delete query.workspace const HAS_LAST_OPENED_FOLDER_OR_WORKSPACE = lastOpened.folder || lastOpened.workspace
} const HAS_FOLDER_OR_WORKSPACE_FROM_CLI = req.args._.length > 0
const to = self(req)
let folder = undefined
let workspace = undefined
// Redirect to the last folder/workspace if nothing else is opened. // Redirect to the last folder/workspace if nothing else is opened.
if ( if (HAS_LAST_OPENED_FOLDER_OR_WORKSPACE && !IGNORE_LAST_OPENED) {
!req.query.folder && folder = lastOpened.folder
!req.query.workspace && workspace = lastOpened.workspace
(query.folder || query.workspace) && } else if (HAS_FOLDER_OR_WORKSPACE_FROM_CLI) {
!req.args["ignore-last-opened"] // This flag disables this behavior. const lastEntry = path.resolve(req.args._[req.args._.length - 1])
) { const entryIsFile = await isFile(lastEntry)
const to = self(req) const IS_WORKSPACE_FILE = entryIsFile && path.extname(lastEntry) === ".code-workspace"
if (IS_WORKSPACE_FILE) {
workspace = lastEntry
} else if (!entryIsFile) {
folder = lastEntry
}
}
if (folder || workspace) {
return redirect(req, res, to, { return redirect(req, res, to, {
folder: query.folder, folder,
workspace: query.workspace, workspace,
}) })
} }
} }

View File

@ -726,29 +726,6 @@ describe("toVsCodeArgs", () => {
it("should convert empty args", async () => { it("should convert empty args", async () => {
expect(await toVsCodeArgs(await setDefaults(parse([])))).toStrictEqual({ expect(await toVsCodeArgs(await setDefaults(parse([])))).toStrictEqual({
...vscodeDefaults, ...vscodeDefaults,
folder: "",
workspace: "",
})
})
it("should convert with workspace", async () => {
const workspace = path.join(await tmpdir(testName), "test.code-workspace")
await fs.writeFile(workspace, "foobar")
expect(await toVsCodeArgs(await setDefaults(parse([workspace])))).toStrictEqual({
...vscodeDefaults,
workspace,
folder: "",
_: [workspace],
})
})
it("should convert with folder", async () => {
const folder = await tmpdir(testName)
expect(await toVsCodeArgs(await setDefaults(parse([folder])))).toStrictEqual({
...vscodeDefaults,
folder,
workspace: "",
_: [folder],
}) })
}) })
@ -757,8 +734,6 @@ describe("toVsCodeArgs", () => {
await fs.writeFile(file, "foobar") await fs.writeFile(file, "foobar")
expect(await toVsCodeArgs(await setDefaults(parse([file])))).toStrictEqual({ expect(await toVsCodeArgs(await setDefaults(parse([file])))).toStrictEqual({
...vscodeDefaults, ...vscodeDefaults,
folder: "",
workspace: "",
_: [file], _: [file],
}) })
}) })

View File

@ -1,19 +1,9 @@
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { Response } from "node-fetch"
import * as path from "path" import * as path from "path"
import { clean, tmpdir } from "../../../utils/helpers" import { clean, tmpdir } from "../../../utils/helpers"
import * as httpserver from "../../../utils/httpserver" import * as httpserver from "../../../utils/httpserver"
import * as integration from "../../../utils/integration" import * as integration from "../../../utils/integration"
interface WorkbenchConfig {
folderUri?: {
path: string
}
workspaceUri?: {
path: string
}
}
describe("vscode", () => { describe("vscode", () => {
let codeServer: httpserver.HttpServer | undefined let codeServer: httpserver.HttpServer | undefined
@ -39,7 +29,7 @@ describe("vscode", () => {
expect(resp.status).toBe(200) expect(resp.status).toBe(200)
const html = await resp.text() const html = await resp.text()
const url = new URL(resp.url) // Check there were no redirections. const url = new URL(resp.url) // Check there were no redirections.
expect(url.pathname + decodeURIComponent(url.search)).toBe(route) expect(url.pathname + url.search).toBe(route)
switch (route) { switch (route) {
case "/": case "/":
case "/vscode/": case "/vscode/":
@ -52,52 +42,25 @@ describe("vscode", () => {
} }
}) })
/** it("should redirect to the passed in workspace using human-readable query", async () => {
* Get the workbench config from the provided response. const workspace = path.join(await tmpdir(testName), "test.code-workspace")
*/ await fs.writeFile(workspace, "")
const getConfig = async (resp: Response): Promise<WorkbenchConfig> => { codeServer = await integration.setup(["--auth=none", workspace], "")
expect(resp.status).toBe(200)
const html = await resp.text()
const match = html.match(/<meta id="vscode-workbench-web-configuration" data-settings="(.+)">/)
if (!match || !match[1]) {
throw new Error("Unable to find workbench configuration")
}
const config = match[1].replace(/&quot;/g, '"')
try {
return JSON.parse(config)
} catch (error) {
console.error("Failed to parse workbench configuration", config)
throw error
}
}
it("should have no default folder or workspace", async () => { const resp = await codeServer.fetch("/")
codeServer = await integration.setup(["--auth=none"], "") const url = new URL(resp.url)
expect(url.pathname).toBe("/")
const config = await getConfig(await codeServer.fetch("/")) expect(url.search).toBe(`?workspace=${workspace}`)
expect(config.folderUri).toBeUndefined()
expect(config.workspaceUri).toBeUndefined()
}) })
it("should have a default folder", async () => { it("should redirect to the passed in folder using human-readable query", async () => {
const defaultDir = await tmpdir(testName) const folder = await tmpdir(testName)
codeServer = await integration.setup(["--auth=none", defaultDir], "") codeServer = await integration.setup(["--auth=none", folder], "")
// At first it will load the directory provided on the command line. const resp = await codeServer.fetch("/")
const config = await getConfig(await codeServer.fetch("/")) const url = new URL(resp.url)
expect(config.folderUri?.path).toBe(defaultDir) expect(url.pathname).toBe("/")
expect(config.workspaceUri).toBeUndefined() expect(url.search).toBe(`?folder=${folder}`)
})
it("should have a default workspace", async () => {
const defaultWorkspace = path.join(await tmpdir(testName), "test.code-workspace")
await fs.writeFile(defaultWorkspace, "")
codeServer = await integration.setup(["--auth=none", defaultWorkspace], "")
// At first it will load the workspace provided on the command line.
const config = await getConfig(await codeServer.fetch("/"))
expect(config.folderUri).toBeUndefined()
expect(config.workspaceUri?.path).toBe(defaultWorkspace)
}) })
it("should redirect to last query folder/workspace", async () => { it("should redirect to last query folder/workspace", async () => {
@ -105,6 +68,7 @@ describe("vscode", () => {
const folder = await tmpdir(testName) const folder = await tmpdir(testName)
const workspace = path.join(await tmpdir(testName), "test.code-workspace") const workspace = path.join(await tmpdir(testName), "test.code-workspace")
await fs.writeFile(workspace, "")
let resp = await codeServer.fetch("/", undefined, { let resp = await codeServer.fetch("/", undefined, {
folder, folder,
workspace, workspace,
@ -118,7 +82,7 @@ describe("vscode", () => {
resp = await codeServer.fetch(route) resp = await codeServer.fetch(route)
const url = new URL(resp.url) const url = new URL(resp.url)
expect(url.pathname).toBe(route) expect(url.pathname).toBe(route)
expect(decodeURIComponent(url.search)).toBe(`?folder=${folder}&workspace=${workspace}`) expect(url.search).toBe(`?folder=${folder}&workspace=${workspace}`)
await resp.text() await resp.text()
} }
@ -126,13 +90,24 @@ describe("vscode", () => {
resp = await codeServer.fetch("/", undefined, { ew: "true" }) resp = await codeServer.fetch("/", undefined, { ew: "true" })
let url = new URL(resp.url) let url = new URL(resp.url)
expect(url.pathname).toBe("/") expect(url.pathname).toBe("/")
expect(decodeURIComponent(url.search)).toBe("?ew=true") expect(url.search).toBe("?ew=true")
await resp.text() await resp.text()
resp = await codeServer.fetch("/") resp = await codeServer.fetch("/")
url = new URL(resp.url) url = new URL(resp.url)
expect(url.pathname).toBe("/") expect(url.pathname).toBe("/")
expect(decodeURIComponent(url.search)).toBe("") expect(url.search).toBe("")
await resp.text()
})
it("should do nothing when nothing is passed in", async () => {
codeServer = await integration.setup(["--auth=none"], "")
let resp = await codeServer.fetch("/", undefined)
expect(resp.status).toBe(200)
const url = new URL(resp.url)
expect(url.search).toBe("")
await resp.text() await resp.text()
}) })
@ -141,6 +116,8 @@ describe("vscode", () => {
const folder = await tmpdir(testName) const folder = await tmpdir(testName)
const workspace = path.join(await tmpdir(testName), "test.code-workspace") const workspace = path.join(await tmpdir(testName), "test.code-workspace")
await fs.writeFile(workspace, "")
let resp = await codeServer.fetch("/", undefined, { let resp = await codeServer.fetch("/", undefined, {
folder, folder,
workspace, workspace,
@ -152,7 +129,7 @@ describe("vscode", () => {
resp = await codeServer.fetch("/") resp = await codeServer.fetch("/")
const url = new URL(resp.url) const url = new URL(resp.url)
expect(url.pathname).toBe("/") expect(url.pathname).toBe("/")
expect(decodeURIComponent(url.search)).toBe("") expect(url.search).toBe("")
await resp.text() await resp.text()
}) })
}) })

2
vendor/package.json vendored
View File

@ -7,6 +7,6 @@
"postinstall": "./postinstall.sh" "postinstall": "./postinstall.sh"
}, },
"devDependencies": { "devDependencies": {
"code-oss-dev": "coder/vscode#0fd21e4078ac1dddb26be024f5d4224a4b86da93" "code-oss-dev": "coder/vscode#bd734e3d9f21b1bce4dabab2514177e90c090ee6"
} }
} }

4
vendor/yarn.lock vendored
View File

@ -274,9 +274,9 @@ clone-response@^1.0.2:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
code-oss-dev@coder/vscode#0fd21e4078ac1dddb26be024f5d4224a4b86da93: code-oss-dev@coder/vscode#bd734e3d9f21b1bce4dabab2514177e90c090ee6:
version "1.63.0" version "1.63.0"
resolved "https://codeload.github.com/coder/vscode/tar.gz/0fd21e4078ac1dddb26be024f5d4224a4b86da93" resolved "https://codeload.github.com/coder/vscode/tar.gz/bd734e3d9f21b1bce4dabab2514177e90c090ee6"
dependencies: dependencies:
"@microsoft/applicationinsights-web" "^2.6.4" "@microsoft/applicationinsights-web" "^2.6.4"
"@parcel/watcher" "2.0.3" "@parcel/watcher" "2.0.3"