diff --git a/eslint.config.mjs b/eslint.config.mjs index 9f5ed2a..1b139fe 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -41,5 +41,5 @@ export default defineConfig([ '@typescript-eslint/no-non-null-assertion': 0, }, }, - globalIgnores(['src/check-watch-panel', 'out']), + globalIgnores(['src/check-watch-panel', 'out', '.vscode-test', 'build']), ]); diff --git a/package.json b/package.json index dc6ed8e..660eaba 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "@trivago/prettier-plugin-sort-imports": "^6.0.0", "@types/mocha": "^10.0.10", "@types/node": "24.10.1", + "@types/semver": "^7.7.1", "@types/vscode": "^1.106.1", "@typescript-eslint/eslint-plugin": "^8.48.1", "@typescript-eslint/parser": "^8.48.1", @@ -143,6 +144,7 @@ "@types/parsimmon": "^1.10.9", "command-exists": "^1.2.9", "parsimmon": "^1.18.1", + "semver": "^7.7.4", "vscode-languageclient": "^9.0.1" } } diff --git a/src/binary.ts b/src/binary.ts index 72a88a3..3b143c9 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,7 +1,14 @@ import * as vscode from 'vscode'; +import { execFile } from 'child_process'; import commandExists from 'command-exists'; import * as fs from 'fs'; +import * as semver from 'semver'; +import { promisify } from 'util'; + +const execFileAsync = promisify(execFile); + +export const MIN_SPICEDB_VERSION = '1.51.1'; export async function languageServerBinaryPath(_context: vscode.ExtensionContext): Promise { const config = vscode.workspace.getConfiguration('spicedb'); @@ -22,7 +29,7 @@ export async function languageServerBinaryPath(_context: vscode.ExtensionContext } const INSTALL_COMMANDS = { - darwin: 'brew install spicedb', + darwin: 'brew install authzed/tap/spicedb', linux: '', win32: 'choco install spicedb', aix: '', @@ -39,3 +46,42 @@ export function getInstallCommand() { const platform = process.platform; return INSTALL_COMMANDS[platform] || 'https://authzed.com/docs/spicedb/getting-started/install/macos'; } + +export async function getSpicedbVersion(binaryPath: string): Promise { + try { + const { stdout } = await execFileAsync(binaryPath, ['version']); + const match = stdout.match(/v?(\d+\.\d+\.\d+)/); + return match?.[1] ?? ''; + } catch (_e) { + return ''; + } +} + +export function checkSpicedbVersion(binaryPath: string): void { + void getSpicedbVersion(binaryPath).then((version) => { + if (!version || !semver.lt(version, MIN_SPICEDB_VERSION)) { + return; + } + + const installCommand = getInstallCommand(); + const message = `Installed version of SpiceDB (v${version}) is less than the required (v${MIN_SPICEDB_VERSION}). Please upgrade for full extension support.`; + + if (installCommand.startsWith('https://')) { + const OpenInstructions = 'Open Upgrade Instructions'; + vscode.window.showWarningMessage(message, OpenInstructions).then((selection) => { + if (selection === OpenInstructions) { + vscode.env.openExternal(vscode.Uri.parse(installCommand)); + } + }); + } else { + const Upgrade = 'Run Upgrade Command'; + vscode.window.showWarningMessage(`${message} You can upgrade with \`${installCommand}\`.`, Upgrade).then((selection) => { + if (selection === Upgrade) { + const terminal = vscode.window.createTerminal('SpiceDB Upgrade'); + terminal.sendText(installCommand); + terminal.show(); + } + }); + } + }); +} diff --git a/src/extension.ts b/src/extension.ts index a3e5bf0..707d491 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,7 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } f import { type ResolvedReference, Resolver, parse } from '@authzed/spicedb-parser-js'; -import { getInstallCommand, languageServerBinaryPath } from './binary'; +import { checkSpicedbVersion, getInstallCommand, languageServerBinaryPath } from './binary'; import { CheckWatchProvider } from './checkwatchprovider'; export function activate(context: vscode.ExtensionContext) { @@ -183,6 +183,8 @@ async function startLanguageServer(context: vscode.ExtensionContext) { return; } + checkSpicedbVersion(serverBinary); + const serverOptions: ServerOptions = { run: { command: serverBinary, diff --git a/yarn.lock b/yarn.lock index 86db6d4..efaf8d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -612,6 +612,11 @@ resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.7.tgz#dab4d16ba7568e9846c454a8764f33c5d98e5524" integrity sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ== +"@types/semver@^7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + "@types/vscode@^1.106.1": version "1.106.1" resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.106.1.tgz#c8c9591b4b9debd8cf67b46d5e5ef54bdec9b805" @@ -2939,6 +2944,11 @@ semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.7.4: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"