From 91c5cba07a3e6cbf8e07565903ca654f80b60dc0 Mon Sep 17 00:00:00 2001 From: Christoph Fricke Date: Thu, 2 Sep 2021 13:21:44 +0200 Subject: [PATCH 1/3] klighd-cli: add serve command and UX improvements --- applications/klighd-cli/package.json | 3 +- applications/klighd-cli/server/klighd.ts | 143 ++++++++++++++-------- applications/klighd-cli/server/version.ts | 21 ++++ yarn.lock | 7 +- 4 files changed, 124 insertions(+), 50 deletions(-) create mode 100644 applications/klighd-cli/server/version.ts diff --git a/applications/klighd-cli/package.json b/applications/klighd-cli/package.json index a177f7fe..42ae68b3 100644 --- a/applications/klighd-cli/package.json +++ b/applications/klighd-cli/package.json @@ -25,13 +25,14 @@ "watch": "run-p --print-label \"watch:*\"", "watch:app": "webpack --watch", "watch:server": "tsc -w -p ./tsconfig.server.json", + "prepackage": "echo 'exports.VERSION = \"'\"$npm_package_version\"'\";' > ./lib/version.js", "package": "pkg -c pkg.json ./lib/klighd.js", "start": "node ./lib/main.js", "socket": "node ./lib/main.js --ls_port=5007" }, "dependencies": { "@kieler/klighd-core": "0.1.0", - "commander": "^7.2.0", + "commander": "^8.1.0", "fastify": "^3.15.0", "fastify-static": "^4.0.1", "fastify-websocket": "^3.1.0", diff --git a/applications/klighd-cli/server/klighd.ts b/applications/klighd-cli/server/klighd.ts index bab6439e..55b27715 100755 --- a/applications/klighd-cli/server/klighd.ts +++ b/applications/klighd-cli/server/klighd.ts @@ -18,59 +18,77 @@ // link to view the diagram for a given file. import { Command } from "commander"; -import open from "open"; -import { pathToFileURL } from "url"; import getPort from "get-port"; +import openUrl from "open"; +import { pathToFileURL } from "url"; import { parseIntOrUndefined } from "./helpers"; import { createServer } from "./server"; +import { VERSION } from "./version"; -const program = new Command(); -program - .version("0.1.0") - .description( - "Starts a webserver to view a diagram for a given file. The diagram is generated by a separate language server which is not bundled with this executable." - ) - .addHelpText( - "after", - ` -Alternatively the path to a language server jar can be configured with the environment -variable LS_PATH. The path option takes precedence over the environment variable. -If both a path and a port for a language server is provided, the webserver will -use the port to connect to the listening language server. +const setupInformation = ` +Alternatively, the path to a language server jar can be configured with the +environment variable LS_PATH. The path option takes precedence over the environment +variable. If both a path and a port for a language server is provided, the webserver +will use the port to connect to the listening language server. Example calls: - $ klighd --ls_port 5007 ./ABRO.sctx - $ klighd --ls_path ../language-server.jar ./example.elkt` - ); + $ ${process.title} --ls_port 5007 ./ABRO.sctx + $ ${process.title} --ls_path ../language-server.jar ./example.elkt + $ ${process.title} serve -p 8000 +`; + +const program = new Command(process.title); program - .arguments("") - .option("--clear", "Clear persisted data. Resets all previously stored diagram options.") - .option( - "--ls_port ", - "Uses a language server for the synthesis that listens on the given port for connections." - ) - .option( - "--ls_path ", - "Starts a language server for the synthesis located as a jar at the given path." + .version(VERSION) + .description( + "Starts a web server to view a KLighD synthesized diagram.\nThe KLighD synthesis must be provided by a separate language server." ) - .option("--no-fit", "Do not resize the diagram to fit the viewport when it is redrawn.") - .action(async (file, options, program: Command) => { - // A path to a language can be configured by an environment variable. - // The lsp_path option for the command takes a higher preference. - const lsPath: string | undefined = options.ls_path ?? process.env.LS_PATH; - const lsPort = parseIntOrUndefined(options.ls_port); - - // Fail fast if no possible LS connection is provided - if (!lsPath && !lsPort) { - console.error( - "Please provide either a path to a language server jar or a port for a listening language server!\n" - ); - program.help({ error: true }); + .addHelpText("after", setupInformation) + .configureHelp({ sortOptions: true, sortSubcommands: true }); + +// Global options +program + .option("--ls_port ", "port used to connect to a language server") + .option("--ls_path ", "path to a language server jar") + .option("-p, --port ", "port used for the diagram-view server"); + +const serve = new Command("serve") + .description("start the diagram-view server without any specific file") + .action(async () => { + // The global options only exists on the program object and not on the program passed in as the last argument. + // In case these Commands are refactored into another file, a factory function must be used to receive the "original program". + const port = parseIntOrUndefined(program.opts().port); + const [lsPath, lsPort] = getLanguageServerAccess(program); + + try { + const finalPort = await findPort(port); + const server = createServer({ logging: "error", lsPort, lsPath }); + + const url = await server.listen(finalPort); + + // Minimal CLI output so it is still understandable but also parsable in scripts. + console.log("Server listening at:", url); + } catch (error) { + console.error(error); + process.exit(1); } + }); + +const open = new Command("open") + .description("start and open diagram view with the given file") + .arguments("") + .option("--clear", "clear persisted data") + .option("--no-fit", "do not resize the diagram to fit the viewport") + .action(async (file, options) => { + // The global options only exists on the program object and not on the program passed in as the last argument. + // In case these Commands are refactored into another file, a factory function must be used to receive the "original program". + const port = parseIntOrUndefined(program.opts().port); + const [lsPath, lsPort] = getLanguageServerAccess(program); // options.fit is true when the --no-fit flag is absent. // See https://www.npmjs.com/package/commander#other-option-types-negatable-boolean-and-booleanvalue + // Local command options const resizeToFit = options.fit; const clearData = options.clear; @@ -79,15 +97,12 @@ program clearData ? "&clearData=true" : "" }`; - const fileUrl = pathToFileURL(file); - const server = createServer({ logging: "warn", lsPort, lsPath }); - - // Listen on a random, available port try { - // Find an available port. Persisted options (localStorage) are stored per origin, which is port dependent. - const port = await getPort({ port: [7000, 7001, 7002, 7003, 7004, 7005] }); - const addr = await server.listen(port); + const fileUrl = pathToFileURL(file); + const finalPort = await findPort(port); + const server = createServer({ logging: "warn", lsPort, lsPath }); + const addr = await server.listen(finalPort); const url = `${addr}?source=${fileUrl}${preferences}`; console.log("\n==============================\n"); @@ -96,11 +111,43 @@ program console.log(url); console.log("\n==============================\n"); - open(url); + openUrl(url); } catch (error) { console.error(error); process.exit(1); } }); +// Add commands +program.addCommand(serve); +program.addCommand(open, { isDefault: true }); + program.parse(process.argv); + +//////////////////////////////////////////////////////////////////////////////// +// Helper Functions + +/** Returns an available ports. A provided port overwrites the result regardless of whether or not it is available. */ +async function findPort(port?: number): Promise { + return port ?? (await getPort({ port: [7000, 7001, 7002, 7003, 7004, 7005] })); +} + +/** Ensures that either a path or port is specified for the LS. Reads the options from the global options. */ +function getLanguageServerAccess( + global: typeof program +): [path: string | undefined, port: number | undefined] { + // A path to a language can be configured by an environment variable. + // The lsp_path option for the command takes a higher preference. + const lsPath: string | undefined = global.opts().ls_path ?? process.env.LS_PATH; + const lsPort = parseIntOrUndefined(global.opts().ls_port); + + // Fail fast if no possible LS connection is provided + if (!lsPath && !lsPort) { + console.error( + "Please provide either a path to a language server jar or a port for a listening language server!\n" + ); + global.help({ error: true }); + } + + return [lsPath, lsPort]; +} diff --git a/applications/klighd-cli/server/version.ts b/applications/klighd-cli/server/version.ts new file mode 100644 index 00000000..3fa698e7 --- /dev/null +++ b/applications/klighd-cli/server/version.ts @@ -0,0 +1,21 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2021 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +// THE CONTENT OF THIS FILE IS A PLACEHOLDER FOR TYPESCRIPT. IT WILL BE REPLACED +// WITH THE ACTUAL PACKAGE VERSION WHEN THE CLI IS PACKAGED. + +export const VERSION = "unknown"; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e4e50482..9667a08b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2252,11 +2252,16 @@ commander@^6.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^7.0.0, commander@^7.2.0: +commander@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.1.0.tgz#db36e3e66edf24ff591d639862c6ab2c52664362" + integrity sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" From eee57fdabbde64f21c277a48e1d822235c5062fc Mon Sep 17 00:00:00 2001 From: Christoph Fricke Date: Thu, 2 Sep 2021 13:41:15 +0200 Subject: [PATCH 2/3] klighd-cli: add newline to version.ts --- applications/klighd-cli/server/version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/klighd-cli/server/version.ts b/applications/klighd-cli/server/version.ts index 3fa698e7..925be803 100644 --- a/applications/klighd-cli/server/version.ts +++ b/applications/klighd-cli/server/version.ts @@ -18,4 +18,4 @@ // THE CONTENT OF THIS FILE IS A PLACEHOLDER FOR TYPESCRIPT. IT WILL BE REPLACED // WITH THE ACTUAL PACKAGE VERSION WHEN THE CLI IS PACKAGED. -export const VERSION = "unknown"; \ No newline at end of file +export const VERSION = "unknown"; From f885204bafe3ace68fc614b9d263b55cd7a28d08 Mon Sep 17 00:00:00 2001 From: Christoph Fricke Date: Fri, 3 Sep 2021 12:41:13 +0200 Subject: [PATCH 3/3] klighd-cli: change wording of the "open" description --- applications/klighd-cli/server/klighd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/klighd-cli/server/klighd.ts b/applications/klighd-cli/server/klighd.ts index 55b27715..1566c7ed 100755 --- a/applications/klighd-cli/server/klighd.ts +++ b/applications/klighd-cli/server/klighd.ts @@ -76,7 +76,7 @@ const serve = new Command("serve") }); const open = new Command("open") - .description("start and open diagram view with the given file") + .description("start and open a diagram view for the given file") .arguments("") .option("--clear", "clear persisted data") .option("--no-fit", "do not resize the diagram to fit the viewport")