From 55f6f52bdac4386f0c59024f82bb2e3951fc6840 Mon Sep 17 00:00:00 2001 From: Tim Pavlik <4960530+fantavlik@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:46:30 -0700 Subject: [PATCH] SPL2 notebooks download/use the latest SPL2 language server by default (#115) * Enable SPL2 notebooks to download/use the latest SPL2 language server by default. Also scope machine-specific installation settings (e.g. local paths) to only be tracked per-machine as opposed to across all users' machines. * Adding version to logs * Revert to latest LSP version as backup value --- out/extension.js | 2 +- out/notebooks/spl2/initializer.ts | 25 ++++++----- out/notebooks/spl2/installer.ts | 73 +++++++++++++++++++++++-------- package.json | 13 ++++-- 4 files changed, 78 insertions(+), 35 deletions(-) diff --git a/out/extension.js b/out/extension.js index bf856e2..26baec6 100644 --- a/out/extension.js +++ b/out/extension.js @@ -12,7 +12,7 @@ const splunkCustomCommand = require('./customCommand.js'); const globalConfigPreview = require('./globalConfigPreview') const splunkCustomRESTHandler = require('./customRESTHandler.js') const splunkSpec = require("./spec.js"); -const PLACEHOLDER_REGEX = /\<([^\>]+)\>/g +const PLACEHOLDER_REGEX = /\<([^\>]+)\>/g; let specConfigs = {}; let timeout; let diagnosticCollection; diff --git a/out/notebooks/spl2/initializer.ts b/out/notebooks/spl2/initializer.ts index 2c70a1f..ffe05ae 100644 --- a/out/notebooks/spl2/initializer.ts +++ b/out/notebooks/spl2/initializer.ts @@ -77,8 +77,7 @@ export async function startSpl2ClientAndServer(globalStoragePath: string, progre return; } const lspPath = path.join(getLocalLspDir(globalStoragePath), getLspFilename(lspVersion)); - - const server = new Spl2ClientServer(progressBar, javaPath, lspPath, portToAttempt, onClose); + const server = new Spl2ClientServer(progressBar, javaPath, lspVersion, lspPath, portToAttempt, onClose); await server.initialize(); resolve(server); } catch (err) { @@ -90,6 +89,7 @@ export async function startSpl2ClientAndServer(globalStoragePath: string, progre export class Spl2ClientServer { progressBar: StatusBarItem; javaPath: string; + lspVersion: string; lspPath: string; retries: number; restarting: boolean; @@ -101,9 +101,10 @@ export class Spl2ClientServer { serverProcess: child_process.ChildProcess; socket: Socket; - constructor(progressBar: StatusBarItem, javaPath: string, lspPath: string, portToAttempt: number, onClose: (nextPort: number) => void) { + constructor(progressBar: StatusBarItem, javaPath: string, lspVersion: string, lspPath: string, portToAttempt: number, onClose: (nextPort: number) => void) { this.progressBar = progressBar; this.javaPath = javaPath; + this.lspVersion = lspVersion; this.lspPath = lspPath; this.retries = 0; this.restarting = false; @@ -116,15 +117,15 @@ export class Spl2ClientServer { } async initialize(): Promise { - this.progressBar.text = 'Starting SPL2 Language Server'; + this.progressBar.text = `Starting SPL2 Language Server v${this.lspVersion}`; this.progressBar.show(); return new Promise(async (resolve, reject) => { this.lspPort = await getNextAvailablePort(this.portToAttempt, 10) .catch((err) => { - reject(`Unable to find available port for SPL2 language server, err: ${err}`); + reject(`Unable to find available port for SPL2 language server v${this.lspVersion}, err: ${err}`); }) || -1; if (this.lspPort === -1) { - reject(`Unable to find available port for SPL2 language server`); + reject(`Unable to find available port for SPL2 language server v${this.lspVersion}`); return; } @@ -160,11 +161,11 @@ export class Spl2ClientServer { // TODO: implement module resolution return; }); - this.progressBar.text = 'SPL2 Language Server Running'; + this.progressBar.text = `SPL2 Language Server v${this.lspVersion} Running`; } else if (event.newState === State.Starting) { - this.progressBar.text = 'SPL2 Language Server Starting'; + this.progressBar.text = `SPL2 Language Server v${this.lspVersion} Starting`; } else { - this.progressBar.text = 'SPL2 Language Server Stopped'; + this.progressBar.text = `SPL2 Language Server v${this.lspVersion} Stopped`; } this.progressBar.show(); }); @@ -192,10 +193,10 @@ export class Spl2ClientServer { ]; this.serverProcess = child_process.spawn(this.javaPath, javaArgs); if (!this.serverProcess || !this.serverProcess.pid) { - reject(`Launching server with ${this.javaPath} ${javaArgs.join(' ')} failed.`); + reject(`Launching language server v${this.lspVersion} with ${this.javaPath} ${javaArgs.join(' ')} failed.`); return; } else { - console.log(`SPL2 Language Server launched with pid: ${this.serverProcess.pid} and listening on port: ${this.lspPort}`); + console.log(`SPL2 Language Server v${this.lspVersion} launched with pid: ${this.serverProcess.pid} and listening on port: ${this.lspPort}`); } this.serverProcess.stderr.on('data', stderr => { console.warn(`[SPL2 Server]: ${stderr}`); @@ -218,7 +219,7 @@ export class Spl2ClientServer { console.log(`[SPL2 Server]: ${stdout}`); const lspLog: LSPLog = JSON.parse(stdout); if (lspLog.message.includes('started listening on port')) { - console.log('SPL2 Server is up, starting client...'); + console.log(`SPL2 Server v${this.lspVersion} is up, starting client...`); // Ready for client this.socket = new Socket(); diff --git a/out/notebooks/spl2/installer.ts b/out/notebooks/spl2/installer.ts index f611804..5a1f19a 100644 --- a/out/notebooks/spl2/installer.ts +++ b/out/notebooks/spl2/installer.ts @@ -15,9 +15,7 @@ export const configKeyAcceptedTerms = 'splunk.spl2.acceptedTerms'; export const configKeyJavaPath = 'splunk.spl2.javaPath'; export const configKeyLspDirectory = 'splunk.spl2.languageServerDirectory'; export const configKeyLspVersion = 'splunk.spl2.languageServerVersion'; - -export const stateKeyLatestLspVersion = 'splunk.spl2.latestLspVersion'; -export const stateKeyLastLspCheck = 'splunk.spl2.lastLspCheck'; +export const configKeyDownloadLatestSPL2 = 'splunk.spl2.downloadLatestSPL2'; // Minimum version of Java needed for SPL2 Language Server const minimumMajorJavaVersion = 17; @@ -518,17 +516,28 @@ async function promptToDownloadLsp(alsoInstallJava: boolean): Promise { } /** - * Checks if the installed SPL2 Language Server version is the latest and prompt for - * upgrade if not, or automatically upgrade if user has that setting enabled. + * Checks if the installed SPL2 Language Server version is the latest and + * automatically upgrade if user has that setting enabled. */ export async function getLatestSpl2Release(globalStoragePath: string, progressBar: StatusBarItem): Promise { return new Promise(async (resolve, reject) => { const lspArtifactPath = getLocalLspDir(globalStoragePath); - // TODO: Remove this hardcoded version/update time and check for updates - let latestLspVersion: string = '2.0.385'; - const lastUpdateMs: number = Date.now(); - // Don't check for new version of SPL2 Language Server if less than 24 hours since last check - if (Date.now() - lastUpdateMs > 24 * 60 * 60 * 1000) { + const shouldDownloadLatest = workspace.getConfiguration().get(configKeyDownloadLatestSPL2); + // Used as backup if latest version can't be determined or current version is invalid + let lspVersionToInstall = '2.0.402' + // If user has unchecked the option to always download the latest LSP then + // check if the current specified version is installed, if not then download it + const currentLspVersion: string = workspace.getConfiguration().get(configKeyLspVersion); + const parsedCurrentVersion = parseLspVersion(currentLspVersion); + + if (!shouldDownloadLatest && (parsedCurrentVersion.length >= 3)) { + // If the current version is valid and the user has elected not to download a new one + // then we are done + lspVersionToInstall = currentLspVersion; + } + // If not using current version, determine latest available + if (lspVersionToInstall !== currentLspVersion) { + // Determine the latest LSP version using maven metadata from jfrog const metaPath = path.join(lspArtifactPath, 'maven-metadata.xml'); try { await downloadWithProgress( @@ -540,27 +549,35 @@ export async function getLatestSpl2Release(globalStoragePath: string, progressBa const parser = new XMLParser(); const metadata = fs.readFileSync(metaPath); const metaParsed = parser.parse(metadata); - latestLspVersion = metaParsed?.metadata?.versioning?.release; + let latestCandidate = metaParsed?.metadata?.versioning?.release; + const parsedCandidate = parseLspVersion(latestCandidate); + if (parsedCandidate.length >= 3) { + // Valid latest version found, update to use this + lspVersionToInstall = latestCandidate; + } } catch (err) { console.warn(`Error retrieving latest SPL2 version, err: ${err}`); } } - const currentLspVersion = workspace.getConfiguration().get(configKeyLspVersion); - if (currentLspVersion === latestLspVersion) { - resolve(); - return Promise.resolve(); - } // Check if latest version has already been downloaded - const lspFilename = getLspFilename(latestLspVersion); + const lspFilename = getLspFilename(lspVersionToInstall); const localLspPath = path.join(getLocalLspDir(globalStoragePath), lspFilename); // Check if local file exists before downloading if (fs.existsSync(localLspPath)) { + if (lspVersionToInstall !== currentLspVersion) { + try { + await workspace.getConfiguration().update(configKeyLspVersion, lspVersionToInstall, true); + } catch (err) { + reject(`Error updating configuration '${configKeyLspVersion}', err: ${err}`); + return Promise.resolve(); + } + } resolve(); return Promise.resolve(); } try { await downloadWithProgress( - `https://splunk.jfrog.io/splunk/maven-splunk/spl2/com/splunk/spl/spl-lang-server-sockets/${latestLspVersion}/${lspFilename}`, + `https://splunk.jfrog.io/splunk/maven-splunk/spl2/com/splunk/spl/spl-lang-server-sockets/${lspVersionToInstall}/${lspFilename}`, localLspPath, progressBar, 'Downloading SPL2 Language Server', @@ -571,7 +588,7 @@ export async function getLatestSpl2Release(globalStoragePath: string, progressBa } // Update this setting to indicate that this version is ready-to-use try { - await workspace.getConfiguration().update(configKeyLspVersion, latestLspVersion, true); + await workspace.getConfiguration().update(configKeyLspVersion, lspVersionToInstall, true); } catch (err) { reject(`Error updating configuration '${configKeyLspVersion}', err: ${err}`); return Promise.resolve(); @@ -580,6 +597,24 @@ export async function getLatestSpl2Release(globalStoragePath: string, progressBa }); } +/** + * Helper function to parse language server version to array of nums. + * Example: '2.0.401' -> [2, 0, 401] + * @param ver Expected format: X.Y.Z[-*] which returns [X,Y,Z] and ignores [-*] + */ +function parseLspVersion(ver: string): Number[] { + const regex = /^\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.?([0-9]+)?(?:\-.*)?$/gm; + const match = regex.exec(ver); + if (match && match.length === 5) { + if (match[4] !== undefined) { + return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4])]; + } else { + return [Number(match[1]), Number(match[2]), Number(match[3])]; + } + } + return []; +} + /** * Helper function to get all files nested under a given directory * and subdirectories. diff --git a/package.json b/package.json index 0555dcb..a6d6e2e 100644 --- a/package.json +++ b/package.json @@ -191,24 +191,31 @@ }, "splunk.spl2.javaPath": { "type": "string", - "scope": "resource", + "scope": "machine", "default": "", "order": 11, "description": "[SPL2] Java Path\nSpecify the full path to a Java executable (./java for Mac/Linux or java.exe for Windows). For example, the full path of $JAVA_HOME/bin/java." }, "splunk.spl2.languageServerDirectory": { "type": "string", - "scope": "resource", + "scope": "machine", "default": "", "order": 12, "description": "[SPL2] Language Server Directory\nSpecify the full path of the directory containing SPL2 (Websockets) Langauge Server. Trailing slash not required. Example:\n/Users//Library/Application Support/Code/User/globalStorage/splunk.splunk/spl2/lsp" }, "splunk.spl2.languageServerVersion": { "type": "string", - "scope": "resource", + "scope": "machine", "default": "", "order": 13, "description": "[SPL2] Language Server Version\nSpecify the version of the SPL2 language server which will be used to create the path to invoke the server. Example, a value of '2.0.362' will invoke spl-lang-server-sockets-2.0.362-all.jar" + }, + "splunk.spl2.downloadLatestSPL2": { + "type": "boolean", + "scope": "machine", + "default": true, + "order": 14, + "description": "[SPL2] Automatically update to the latest version of the SPL2 language server." } } },