Add plugin system
This commit is contained in:
parent
1c8eede1aa
commit
bac948ea6f
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ release-gcp/
|
|||||||
release-images/
|
release-images/
|
||||||
node_modules
|
node_modules
|
||||||
node-*
|
node-*
|
||||||
|
/plugins
|
||||||
|
@ -1306,17 +1306,16 @@ index 0000000000..56331ff1fc
|
|||||||
+require('../../bootstrap-amd').load('vs/server/entry');
|
+require('../../bootstrap-amd').load('vs/server/entry');
|
||||||
diff --git a/src/vs/server/ipc.d.ts b/src/vs/server/ipc.d.ts
|
diff --git a/src/vs/server/ipc.d.ts b/src/vs/server/ipc.d.ts
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000..0a9c95d50e
|
index 0000000000..5cc3e1f0f4
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/vs/server/ipc.d.ts
|
+++ b/src/vs/server/ipc.d.ts
|
||||||
@@ -0,0 +1,117 @@
|
@@ -0,0 +1,116 @@
|
||||||
+/**
|
+/**
|
||||||
+ * External interfaces for integration into code-server over IPC. No vs imports
|
+ * External interfaces for integration into code-server over IPC. No vs imports
|
||||||
+ * should be made in this file.
|
+ * should be made in this file.
|
||||||
+ */
|
+ */
|
||||||
+export interface Options {
|
+export interface Options {
|
||||||
+ base: string
|
+ base: string
|
||||||
+ commit: string
|
|
||||||
+ disableTelemetry: boolean
|
+ disableTelemetry: boolean
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
|
@ -200,8 +200,6 @@ export class VscodeHttpProvider extends HttpProvider {
|
|||||||
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
|
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
|
||||||
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`)
|
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`)
|
||||||
return this.replaceTemplates<Options>(route, response, {
|
return this.replaceTemplates<Options>(route, response, {
|
||||||
base: this.base(route),
|
|
||||||
commit: this.options.commit,
|
|
||||||
disableTelemetry: !!this.args["disable-telemetry"],
|
disableTelemetry: !!this.args["disable-telemetry"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import { UpdateHttpProvider } from "./app/update"
|
|||||||
import { VscodeHttpProvider } from "./app/vscode"
|
import { VscodeHttpProvider } from "./app/vscode"
|
||||||
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
|
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
|
||||||
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
||||||
import { generateCertificate, hash, open, humanPath } from "./util"
|
import { loadPlugins } from "./plugin"
|
||||||
|
import { generateCertificate, hash, humanPath, open } from "./util"
|
||||||
import { ipcMain, wrap } from "./wrapper"
|
import { ipcMain, wrap } from "./wrapper"
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
@ -77,6 +78,8 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
|
|||||||
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
||||||
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
||||||
|
|
||||||
|
await loadPlugins(httpServer, args)
|
||||||
|
|
||||||
ipcMain().onDispose(() => {
|
ipcMain().onDispose(() => {
|
||||||
httpServer.dispose().then((errors) => {
|
httpServer.dispose().then((errors) => {
|
||||||
errors.forEach((error) => logger.error(error.message))
|
errors.forEach((error) => logger.error(error.message))
|
||||||
|
@ -235,30 +235,22 @@ export abstract class HttpProvider {
|
|||||||
/**
|
/**
|
||||||
* Replace common templates strings.
|
* Replace common templates strings.
|
||||||
*/
|
*/
|
||||||
protected replaceTemplates(route: Route, response: HttpStringFileResponse, sessionId?: string): HttpStringFileResponse
|
|
||||||
protected replaceTemplates<T extends object>(
|
protected replaceTemplates<T extends object>(
|
||||||
route: Route,
|
route: Route,
|
||||||
response: HttpStringFileResponse,
|
response: HttpStringFileResponse,
|
||||||
options: T,
|
extraOptions?: Omit<T, "base" | "csStaticBase" | "logLevel">,
|
||||||
): HttpStringFileResponse
|
|
||||||
protected replaceTemplates(
|
|
||||||
route: Route,
|
|
||||||
response: HttpStringFileResponse,
|
|
||||||
sessionIdOrOptions?: string | object,
|
|
||||||
): HttpStringFileResponse {
|
): HttpStringFileResponse {
|
||||||
if (typeof sessionIdOrOptions === "undefined" || typeof sessionIdOrOptions === "string") {
|
const options: Options = {
|
||||||
sessionIdOrOptions = {
|
|
||||||
base: this.base(route),
|
base: this.base(route),
|
||||||
commit: this.options.commit,
|
commit: this.options.commit,
|
||||||
logLevel: logger.level,
|
logLevel: logger.level,
|
||||||
sessionID: sessionIdOrOptions,
|
...extraOptions,
|
||||||
} as Options
|
|
||||||
}
|
}
|
||||||
response.content = response.content
|
response.content = response.content
|
||||||
.replace(/{{COMMIT}}/g, this.options.commit)
|
.replace(/{{COMMIT}}/g, this.options.commit)
|
||||||
.replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard")
|
.replace(/{{TO}}/g, Array.isArray(route.query.to) ? route.query.to[0] : route.query.to || "/dashboard")
|
||||||
.replace(/{{BASE}}/g, this.base(route))
|
.replace(/{{BASE}}/g, this.base(route))
|
||||||
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(sessionIdOrOptions)}'`)
|
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,7 +656,7 @@ export class HttpServer {
|
|||||||
e = new HttpError("Not found", HttpCode.NotFound)
|
e = new HttpError("Not found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
||||||
logger.debug("Request error", field("url", request.url), field("code", code))
|
logger.debug("Request error", field("url", request.url), field("code", code), field("error", error))
|
||||||
if (code >= HttpCode.ServerError) {
|
if (code >= HttpCode.ServerError) {
|
||||||
logger.error(error.stack)
|
logger.error(error.stack)
|
||||||
}
|
}
|
||||||
|
52
src/node/plugin.ts
Normal file
52
src/node/plugin.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { field, logger } from "@coder/logger"
|
||||||
|
import * as fs from "fs"
|
||||||
|
import * as path from "path"
|
||||||
|
import * as util from "util"
|
||||||
|
import { Args } from "./cli"
|
||||||
|
import { HttpServer } from "./http"
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
export type Activate = (httpServer: HttpServer, args: Args) => void
|
||||||
|
|
||||||
|
export interface Plugin {
|
||||||
|
activate: Activate
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalLoad = require("module")._load
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
require("module")._load = function (request: string, parent: object, isMain: boolean): any {
|
||||||
|
return originalLoad.apply(this, [request.replace(/^code-server/, path.resolve(__dirname, "../..")), parent, isMain])
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const plugin: Plugin = require(pluginPath)
|
||||||
|
plugin.activate(httpServer, args)
|
||||||
|
logger.debug("Loaded plugin", field("name", path.basename(pluginPath)))
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "MODULE_NOT_FOUND") {
|
||||||
|
logger.warn(error.message)
|
||||||
|
} else {
|
||||||
|
logger.debug(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _loadPlugins = async (httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
const pluginPath = path.resolve(__dirname, "../../plugins")
|
||||||
|
const files = await util.promisify(fs.readdir)(pluginPath, {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
await Promise.all(files.map((file) => loadPlugin(path.join(pluginPath, file.name), httpServer, args)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await _loadPlugins(httpServer, args)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "ENOENT") {
|
||||||
|
logger.warn(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user