From 6208cee6bbb446d4729207cbf516ada4540ed2e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 17:28:50 +0100 Subject: [PATCH 01/62] Bump fast-uri from 3.1.0 to 3.1.2 (#1779) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5b5d6f6..25afa150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2965,9 +2965,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { From 45a36009aa485afc939a6d8d6b8f805750852afc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 16:25:49 -0400 Subject: [PATCH 02/62] Bump axios from 1.15.0 to 1.15.2 (#1778) Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.15.0...v1.15.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.15.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kuang-Chen (KC) Lu --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25afa150..2f6193d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.1", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.15.0", + "axios": "^1.15.2", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", @@ -1534,9 +1534,9 @@ } }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", diff --git a/package.json b/package.json index 8fb3d2ef..2d7407c4 100644 --- a/package.json +++ b/package.json @@ -1754,7 +1754,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.1", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.15.0", + "axios": "^1.15.2", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", From 2915e1693dd396e335f99d0e89595bf595a5cfef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 07:28:59 +0100 Subject: [PATCH 03/62] Bump @nevware21/ts-utils from 0.12.5 to 0.14.0 (#1785) Bumps [@nevware21/ts-utils](https://github.com/nevware21/ts-utils) from 0.12.5 to 0.14.0. - [Release notes](https://github.com/nevware21/ts-utils/releases) - [Changelog](https://github.com/nevware21/ts-utils/blob/main/CHANGELOG.md) - [Commits](https://github.com/nevware21/ts-utils/compare/0.12.5...0.14.0) --- updated-dependencies: - dependency-name: "@nevware21/ts-utils" dependency-version: 0.14.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f6193d2..6979cbfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -566,9 +566,19 @@ } }, "node_modules/@nevware21/ts-utils": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.12.5.tgz", - "integrity": "sha512-JPQZWPKQJjj7kAftdEZL0XDFfbMgXCGiUAZe0d7EhLC3QlXTlZdSckGqqRIQ2QNl0VTEZyZUvRBw6Ednw089Fw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", + "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nevware21" + }, + { + "type": "other", + "url": "https://buymeacoffee.com/nevware21" + } + ], "license": "MIT" }, "node_modules/@pkgjs/parseargs": { From 0ba1a946afcfc19fd51613d445352eede798b608 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 07:33:26 +0100 Subject: [PATCH 04/62] Bump ws from 8.20.0 to 8.20.1 (#1786) Bumps [ws](https://github.com/websockets/ws) from 8.20.0 to 8.20.1. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.20.0...8.20.1) --- updated-dependencies: - dependency-name: ws dependency-version: 8.20.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6979cbfd..9711c6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", "node-cmd": "^5.0.0", - "ws": "^8.20.0" + "ws": "^8.20.1" }, "devDependencies": { "@eslint/js": "^9.39.2", @@ -6638,9 +6638,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 2d7407c4..9ff07221 100644 --- a/package.json +++ b/package.json @@ -1759,7 +1759,7 @@ "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", "node-cmd": "^5.0.0", - "ws": "^8.20.0" + "ws": "^8.20.1" }, "extensionDependencies": [ "intersystems-community.servermanager" From 6cbd46308c39d49eb7f85c422db6e82c993c9ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 07:45:01 -0400 Subject: [PATCH 05/62] Bump axios from 1.15.2 to 1.16.0 (#1789) Bumps [axios](https://github.com/axios/axios) from 1.15.2 to 1.16.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.15.2...v1.16.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9711c6cd..5193b245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.1", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.15.2", + "axios": "^1.16.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", @@ -1544,12 +1544,12 @@ } }, "node_modules/axios": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", - "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } diff --git a/package.json b/package.json index 9ff07221..81b9ff16 100644 --- a/package.json +++ b/package.json @@ -1754,7 +1754,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.1", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.15.2", + "axios": "^1.16.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", From daa40106c766fe0f1124ef1dd7da881695a090fc Mon Sep 17 00:00:00 2001 From: John Murray Date: Mon, 1 Jun 2026 13:55:52 +0100 Subject: [PATCH 06/62] Reinstate reconnect option from status bar panel of isfs-type workspace (fix #1787) (#1788) --- src/commands/serverActions.ts | 2 +- src/extension.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index ffa73a07..a9a14fdb 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -41,7 +41,7 @@ export async function serverActions(): Promise { label: "Toggle Connection", }); } - if (active) { + if (active || api.externalServer) { actions.push({ id: "refreshConnection", label: "Refresh Connection", diff --git a/src/extension.ts b/src/extension.ts index 2b29057d..a0a741e0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -375,7 +375,7 @@ export async function checkConnection( return; } let connInfo = api.connInfo; - if (!active) { + if (!active && !api.externalServer) { panel.text = `${PANEL_LABEL} $(warning)`; panel.tooltip = new vscode.MarkdownString( `Connection to${ @@ -426,7 +426,11 @@ export async function checkConnection( api.clearCookies(); } - // Why must this be recreated here? Maybe in case something has updated connection details since we last fetched them. + // Before recreating the api object (in case something has updated connection details since we last fetched them?) + // if this is an external server, remove it from the inactive list because its presence there would block the reconnect that we want to be attempting + if (api.externalServer) { + inactiveServerIds.delete(api.serverId); + } api = new AtelierAPI(apiTarget, false); if (!api.config.host || !api.config.port || !api.config.ns) { @@ -460,9 +464,9 @@ export async function checkConnection( .serverInfo(true, serverInfoTimeout) .then(gotServerInfo) .catch(async (error) => { - let message = error.message; + let message = error ? error.message : "Unknown error"; let errorMessage; - if (error.statusCode === 401) { + if (error?.statusCode === 401) { let success = false; message = "Not Authorized."; errorMessage = `Authorization error: Check your credentials in Settings, and that you have sufficient privileges on the /api/atelier web application on ${connInfo}`; From ec98d49838382a434fce1d8f00d14459fec5830c Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 3 Jun 2026 10:01:19 -0400 Subject: [PATCH 07/62] Update status of document after running server-side source control action on it when `respectEditableStatus` is set (#1792) --- src/commands/studio.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/commands/studio.ts b/src/commands/studio.ts index 49f2c8b9..787b28ec 100644 --- a/src/commands/studio.ts +++ b/src/commands/studio.ts @@ -353,7 +353,7 @@ export class StudioActions { const actionToProcess: UserAction = data.result.content.pop(); if (actionToProcess.reload && !isPrj) { - await vscode.commands.executeCommand("workbench.action.files.revert", this.uri); + await vscode.commands.executeCommand("workbench.action.files.revert"); } const attemptedEditLabel = getOtherStudioActionLabel(OtherStudioAction.AttemptedEdit); @@ -364,7 +364,7 @@ export class StudioActions { this.projectEditAnswer = "-1"; } else if (this.uri) { // Only revert if we have a URI - await vscode.commands.executeCommand("workbench.action.files.revert", this.uri); + await vscode.commands.executeCommand("workbench.action.files.revert"); } } outputChannel.appendLine(actionToProcess.errorText); @@ -376,7 +376,7 @@ export class StudioActions { if (action.label === attemptedEditLabel) { if (answer != "1" && this.uri) { // Only revert if we have a URI - await vscode.commands.executeCommand("workbench.action.files.revert", this.uri); + await vscode.commands.executeCommand("workbench.action.files.revert"); } if (isPrj) { // Store the answer. No answer means "allow the edit". @@ -388,6 +388,20 @@ export class StudioActions { ? this.userAction(action, true, answer.answer, answer.msg, type) : this.userAction(action, true, answer, "", type); } + } else if (action.id != "6" && !isPrj && this.uri) { + // This action was run on a document. If the user is respecting + // editable status force VS Code to check the status of the + // document in case it was changed by the action. + const activeDoc = vscode.window.activeTextEditor?.document; + if ( + vscode.workspace + .getConfiguration("objectscript.serverSourceControl", this.uri) + ?.get("respectEditableStatus") && + activeDoc?.uri.toString() == this.uri.toString() && + !activeDoc.isDirty + ) { + vscode.commands.executeCommand("workbench.action.files.revert"); + } } }) .then(() => resolve()) From 0c66f6f58ea3f0ee56b057e9e87c15a0fb14c2f2 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 3 Jun 2026 10:44:33 -0400 Subject: [PATCH 08/62] Ensure cookies are always fresh before starting a Lite Terminal (#1793) --- src/commands/webSocketTerminal.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index 6fe329df..9b71df36 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -796,7 +796,7 @@ export async function launchWebSocketTerminal(targetUri?: vscode.Uri, nsOverride } const api = new AtelierAPI(targetUri); - // Guarantee we know the apiVersion of the server + // Guarantee that we know the apiVersion of the server and that cookies are fresh await api.serverInfo(); // Get the terminal configuration @@ -814,8 +814,11 @@ export class WebSocketTerminalProfileProvider implements vscode.TerminalProfileP const uri: vscode.Uri = await getWsServerConnection("2023.2.0"); if (uri) { + const api = new AtelierAPI(uri); + // Ensure cookies aren't stale because a 401 error will kill the terminal with no error log + await api.serverInfo(); // Get the terminal configuration. Will throw if there's an error. - const terminalOpts = terminalConfigForUri(new AtelierAPI(uri), uri, true); + const terminalOpts = terminalConfigForUri(api, uri, true); return new vscode.TerminalProfile(terminalOpts); } else if (uri === undefined) { throw new Error(NO_ELIGIBLE_CONNECTIONS); From ede9905b0c3d8328301e72c1ddbccc485a686327 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 3 Jun 2026 17:27:21 +0100 Subject: [PATCH 09/62] Update release CI script to use `softprops/action-gh-release@v3` for Node 24 compatibility (#1790) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4eb8a5e..95d1fb86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -145,7 +145,7 @@ jobs: npx vsce package -o ${{ steps.set-version.outputs.name }}.vsix - name: Upload Release Asset id: upload-release-asset - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.release.tag_name }} files: ${{ steps.set-version.outputs.name }}.vsix From 7faaa223e6c1d061d9592ae4ace50a5b89b332a8 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 4 Jun 2026 07:31:34 -0400 Subject: [PATCH 10/62] Prepare 3.8.2 release (#1794) --- .github/workflows/main.yml | 2 +- .github/workflows/prerelease.yml | 4 +- CHANGELOG.md | 15 +++ README.md | 4 +- package-lock.json | 189 ++++++++++++++++++------------- package.json | 6 +- 6 files changed, 135 insertions(+), 85 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95d1fb86..f90ba390 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -101,7 +101,7 @@ jobs: name: ${{ steps.set-version.outputs.name }}.vsix - name: Create Release id: create-release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: tag_name: v${{ steps.set-version.outputs.version }} prerelease: ${{ github.event_name != 'release' }} diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index b5d3ba15..15215c7a 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -96,7 +96,7 @@ jobs: name: ${{ steps.set-version.outputs.name }}.vsix - name: Create Pre-Release id: create-release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: tag_name: v${{ steps.set-version.outputs.version }} prerelease: ${{ github.event_name != 'release' }} @@ -140,7 +140,7 @@ jobs: npx vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix - name: Upload Release Asset id: upload-release-asset - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.release.tag_name }} files: ${{ steps.set-version.outputs.name }}.vsix diff --git a/CHANGELOG.md b/CHANGELOG.md index cbcb02a5..d5da05b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [3.8.2] 04-Jun-2026 +- Enhancements + - Add `Import Files...` command that supports many file types (#1764) + - Add `objectscript.insertStubContent` setting (#1765) +- Fixes + - Allow webapp file creation on older IRIS servers (#1768) + - Show detailed error messages when a server-side URI is invalid (#1772) + - Support `Save As...` for server-side documents (#1774) + - Fix issue where unit tests are always run in the first workspace folder (#1775) + - Fix a spelling error (#1777) + - Reinstate reconnect option from status bar panel of server-side workspace folder (#1788) + - Update status of document after running server-side source control action on it when `respectEditableStatus` is set (#1792) + - Ensure cookies are always fresh before starting a Lite Terminal (#1793) + - Upgrade dependencies + ## [3.8.1] 15-Apr-2026 - Fixes - Fix issue that causes client-side connections to be forced active (#1748) diff --git a/README.md b/README.md index ae432b52..548d7657 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ To unlock these features (optional): 1. Download and install a beta version from GitHub. This is necessary because Marketplace does not allow publication of extensions that use proposed APIs. - Go to https://github.com/intersystems-community/vscode-objectscript/releases - - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.1`, look for `3.8.2-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. - - Download the VSIX file (for example `vscode-objectscript-3.8.2-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. + - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.2`, look for `3.8.3-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. + - Download the VSIX file (for example `vscode-objectscript-3.8.3-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. 2. From [Command Palette](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) choose `Preferences: Configure Runtime Arguments`. 3. In the argv.json file that opens, add this line (required for both Stable and Insiders versions of VS Code): diff --git a/package-lock.json b/package-lock.json index 5193b245..dcedf679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,14 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/debugprotocol": "^1.68.0", - "@vscode/extension-telemetry": "^1.5.1", + "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.16.0", + "axios": "^1.17.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", "node-cmd": "^5.0.0", - "ws": "^8.20.1" + "ws": "^8.21.0" }, "devDependencies": { "@eslint/js": "^9.39.2", @@ -448,73 +448,72 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.10.tgz", - "integrity": "sha512-5fSZmkGwWkH+mrIA5M1GYPZdPM+SjXwCCl2Am7VhFoVwOBJNhRnwvIpAdzw6sFjiebN/rz+/YH0NdxztGZSa9Q==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.4.1.tgz", + "integrity": "sha512-utqwacfUkiGJROn4WC7aNdRBsRxwhNWXuqaJM2B0N0WHmv1+IhSuI7RQ3FHwxRP1dxZi/xn9aELMZ7HMStsW1w==", "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.10", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.10.tgz", - "integrity": "sha512-VSLjc9cT+Y+eTiSfYltJHJCejn8oYr0E6Pq2BMhOEO7F6IyLGYIxzKKvo78ze9x+iHX7KPTATcZ+PFgjGXuNqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.4.1.tgz", + "integrity": "sha512-CkFEhDY7X8E2JLr6HsEvRiC0DaLOCsA7vlbq/9DJP65gAumgw2NnFNIAOg6Je5Geq1LDu76/nb2hP34p8eGggw==", "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.3.10", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.10.tgz", - "integrity": "sha512-iolFLz1ocWAzIQqHIEjjov3gNTPkgFQ4ArHnBcJEYoffOGWlJt6copaevS5YPI5rHzmbySsengZ8cLJJBBrXzQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.1.tgz", + "integrity": "sha512-QS1k6iwVwR1MznGAB1H0F9raqpevbFNbadLS5O1419pz9OEWBfF9wRQLnENCyo8QS9Q0IdiqnGAON/D8IywpWg==", "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.3.10", - "@microsoft/applicationinsights-core-js": "3.3.10", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.10.tgz", - "integrity": "sha512-RVIenPIvNgZCbjJdALvLM4rNHgAFuHI7faFzHCgnI6S2WCUNGHeXlQTs9EUUrL+n2TPp9/cd0KKMILU5VVyYiA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.1.tgz", + "integrity": "sha512-CTbD0g/68tiv2yCItsodDQBYxyHdfQkG7VhvVU8OHenukpl/7W4wEuxZuOntqhv5m9Nx/DFncbz+T83nvYTG3g==", "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.10", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.10.tgz", - "integrity": "sha512-5yKeyassZTq2l+SAO4npu6LPnbS++UD+M+Ghjm9uRzoBwD8tumFx0/F8AkSVqbniSREd+ztH/2q2foewa2RZyg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.1.tgz", + "integrity": "sha512-eXIHZ1+nvBiJgVpufBiTP801Vtr5FEwjWZioUsb44NC/z/UcsZh2MDJ1mBpjaDO73LVYUw/ZZmDCCo6Pg/61kA==", "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" @@ -530,45 +529,54 @@ } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.10.tgz", - "integrity": "sha512-AZib5DAT3NU0VT0nLWEwXrnoMDDgZ/5S4dso01CNU5ELNxLdg+1fvchstlVdMy4FrAnxzs8Wf/GIQNFYOVgpAw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.1.tgz", + "integrity": "sha512-V/hSlauFp1thJa57+TMv5mAYinJAQUi4zOmDmpahnDgs8g1zrQ0D8QYDmu0Zfi+9GhoD80B4yJez2+ydJPJz2w==", "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.3.10", - "@microsoft/applicationinsights-common": "3.3.10", - "@microsoft/applicationinsights-core-js": "3.3.10", + "@microsoft/applicationinsights-channel-js": "3.4.1", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.4 < 2.x", - "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", - "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.5.tgz", + "integrity": "sha512-V+Zr7PDKIEaItVwF/OyWQlKeugNRYg7KJJ+RhEIL2FMW6NlG8FN2l4XA9Z42hNtsjwJFlcUiF38pmM/AaXsF7g==", "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.4 < 2.x" + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", - "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.6.1.tgz", + "integrity": "sha512-W2kFiT5oPuxTrB3NrxUId/U+1AuAhIaiDQkLC4HcxkjNc+85GfELYdPQXnsDWDG8yji24F5qk6QpBDxZX3/0+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nevware21" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/nevware21" + } + ], "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.11.6 < 2.x" + "@nevware21/ts-utils": ">= 0.15.0 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", - "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.15.0.tgz", + "integrity": "sha512-+bUMKIiKAgoW5uNEb5xxzBzdwdLS9SKRcOy8SxLE+KqSlIdUYV5O9nxJVq1RUYcO2DtL5DlrK1GbgcVEHv6GVA==", "funding": [ { "type": "github", @@ -1029,14 +1037,16 @@ } }, "node_modules/@vscode/extension-telemetry": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.1.tgz", - "integrity": "sha512-rnRRQIRCwRdbcQ0QV5ajKJRz8noEIoQA2hX9VjAlVAVB85+ClbaPNhljobFXgW31ue69FRO6KPE4XJ/lLgKt/Q==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.2.tgz", + "integrity": "sha512-fO4huHz5apb5RtddC8DuUeSbBqYQw1EiBaOOGngq57nGbsDgcvm0jAibTY/kigJyjY0fQ4Vx7owQcCJRUrkT4g==", "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.3.10", - "@microsoft/1ds-post-js": "^4.3.10", - "@microsoft/applicationinsights-web-basic": "^3.3.10" + "@microsoft/1ds-core-js": "^4.4.1", + "@microsoft/1ds-post-js": "^4.4.1", + "@microsoft/applicationinsights-common": "^3.4.1", + "@microsoft/applicationinsights-core-js": "^3.4.1", + "@microsoft/applicationinsights-web-basic": "^3.4.1" }, "engines": { "vscode": "^1.75.0" @@ -1274,9 +1284,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1544,16 +1554,42 @@ } }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2050,7 +2086,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4487,9 +4522,9 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -4517,9 +4552,9 @@ } }, "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", "dev": true, "license": "MIT", "dependencies": { @@ -4661,7 +4696,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/natural-compare": { @@ -6156,9 +6190,10 @@ } }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", "peer": true }, "node_modules/type-check": { @@ -6638,9 +6673,9 @@ } }, "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 81b9ff16..e426eca1 100644 --- a/package.json +++ b/package.json @@ -1752,14 +1752,14 @@ "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/debugprotocol": "^1.68.0", - "@vscode/extension-telemetry": "^1.5.1", + "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.16.0", + "axios": "^1.17.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", "node-cmd": "^5.0.0", - "ws": "^8.20.1" + "ws": "^8.21.0" }, "extensionDependencies": [ "intersystems-community.servermanager" From 641eb3784c60daca613fb520918d4829d22b4f64 Mon Sep 17 00:00:00 2001 From: ProjectBot Date: Thu, 4 Jun 2026 12:39:53 +0000 Subject: [PATCH 11/62] auto bump version with release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e426eca1..35a041ad 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-objectscript", "displayName": "InterSystems ObjectScript", "description": "InterSystems ObjectScript language support for Visual Studio Code", - "version": "3.8.2-SNAPSHOT", + "version": "3.8.3-SNAPSHOT", "icon": "images/logo.png", "aiKey": "InstrumentationKey=9cd75d51-697c-406c-a929-2bcf46e97c64;IngestionEndpoint=https://eastus2-4.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=a431c56f-8ccc-4b99-b5e9-fce3763215b1", "categories": [ From 9ec7e86ea2f7d25ccc7a6d945049ca1175698eb3 Mon Sep 17 00:00:00 2001 From: John Murray Date: Thu, 4 Jun 2026 15:55:37 +0100 Subject: [PATCH 12/62] CI: bump actions/download-artifact to v8 for Node 24 compatibility (#1795) --- .github/workflows/main.yml | 6 +++--- .github/workflows/prerelease.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f90ba390..e2131603 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -86,7 +86,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: meta path: . @@ -96,7 +96,7 @@ jobs: set -x echo "version=`cat meta.version`" >> $GITHUB_OUTPUT echo "name=`cat meta.name`" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: ${{ steps.set-version.outputs.name }}.vsix - name: Create Release @@ -116,7 +116,7 @@ jobs: with: ref: master token: ${{ secrets.TOKEN }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: meta path: . diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 15215c7a..c78d801f 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: meta path: . @@ -91,7 +91,7 @@ jobs: set -x echo "version=`cat meta.version`" >> $GITHUB_OUTPUT echo "name=`cat meta.name`" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: ${{ steps.set-version.outputs.name }}.vsix - name: Create Pre-Release @@ -111,7 +111,7 @@ jobs: with: ref: prerelease token: ${{ secrets.TOKEN }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: meta path: . From 87a82eee1990e8e8cb1f8027174a3dc631dd25ae Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 17 Jun 2026 07:50:58 -0400 Subject: [PATCH 13/62] Always overwrite entire class for `Pull Server Changes` when there's a conflict between the local copy and server copy (#1797) --- src/commands/compile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/compile.ts b/src/commands/compile.ts index 285a284e..a66bacf3 100644 --- a/src/commands/compile.ts +++ b/src/commands/compile.ts @@ -186,7 +186,7 @@ What do you want to do?`, // Overwrite return importFile(file, willCompile, true, true); case "Pull Server Changes": - loadChanges([file]); + loadChanges([file], true); return Promise.reject(); case "Cancel": return Promise.reject(); From 319e0eee03ce77c2e60700ae0a70c1fa5e067d36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:18:29 -0400 Subject: [PATCH 14/62] Bump form-data from 4.0.5 to 4.0.6 (#1798) --- package-lock.json | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcedf679..64feffc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-objectscript", - "version": "3.8.2-SNAPSHOT", + "version": "3.8.3-SNAPSHOT", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-objectscript", - "version": "3.8.2-SNAPSHOT", + "version": "3.8.3-SNAPSHOT", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3168,16 +3168,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -3451,9 +3451,10 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4470,19 +4471,21 @@ } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" From 05f2cd0237c132ccb14367f4e4f65943e772e806 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:20:57 -0400 Subject: [PATCH 15/62] Bump js-yaml from 4.1.1 to 4.2.0 (#1800) --- package-lock.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64feffc7..6057e773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4234,10 +4234,20 @@ } }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" From 8a94d8128fb9a89b770be079071433a13124497b Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Mon, 20 Jul 2026 11:11:22 -0400 Subject: [PATCH 16/62] Improve performance of server-side project query (#1809) --- src/utils/FileProviderUtil.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/utils/FileProviderUtil.ts b/src/utils/FileProviderUtil.ts index 099a0eb8..451c6e2a 100644 --- a/src/utils/FileProviderUtil.ts +++ b/src/utils/FileProviderUtil.ts @@ -125,13 +125,10 @@ export async function projectContentsFromUri(uri: vscode.Uri, flat = false): Pro query = `SELECT DISTINCT BY (${nameCol}) ${nameCol} ` + "Name, Type FROM %Studio.Project_ProjectItemsList(?,1) AS pil WHERE " + - "(Type = 'MAC' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.mac,*.int,*.inc,*.bas,*.mvi',1,1,1,1,0,1) AS sod WHERE pil.Name = sod.Name)) OR " + - "(Type = 'CSP' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1) AS sod WHERE '/'||pil.Name = sod.Name)) OR " + - "(Type NOT IN ('CLS','PKG','MAC','CSP','DIR','GBL') AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.other',1,1,1,1,0,1) AS sod WHERE " + - "$PIECE(sod.Name,'.',1,$LENGTH(sod.Name,'.')-1) = $PIECE(pil.Name,'.',1,$LENGTH(pil.Name,'.')-1) AND UPPER($PIECE(sod.Name,'.',$LENGTH(sod.Name,'.'))) = $PIECE(pil.Name,'.',$LENGTH(pil.Name,'.')))) OR " + + "(Type NOT IN ('CLS','PKG','DIR','GBL') AND EXISTS (SELECT Size FROM %Library.RoutineMgr_StudioOpenDialog(pil.Name,1,1,1,1,0,1))) OR " + "(Type = 'CLS' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = pil.Name)) OR " + - "(Type = 'PKG' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID %STARTSWITH pil.Name||'.')) OR " + - "(Type = 'DIR' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,0,0,1) AS sod WHERE pil.Name %STARTSWITH sod.Name||'/' OR pil.Name = sod.Name))"; + "(Type = 'PKG' AND EXISTS (SELECT TOP 1 dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID %STARTSWITH pil.Name||'.')) OR " + + "(Type = 'DIR' AND EXISTS (SELECT TOP 1 Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,0,0,1,'Name %STARTSWITH '''||pil.Name||'/''')))"; parameters = [project]; } } From 7a5e4f52407eb06a7e74ac4d7dea568ba6ffb135 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:03:01 -0400 Subject: [PATCH 17/62] Bump axios from 1.17.0 to 1.18.0 (#1811) Bumps [axios](https://github.com/axios/axios) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.17.0...v1.18.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6057e773..1b140e9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.17.0", + "axios": "^1.18.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", @@ -1554,9 +1554,9 @@ } }, "node_modules/axios": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", - "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", diff --git a/package.json b/package.json index 35a041ad..088da6b1 100644 --- a/package.json +++ b/package.json @@ -1754,7 +1754,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.17.0", + "axios": "^1.18.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", From f42635faae463924de8e2ccf081942ea2d18e90e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:07:15 -0400 Subject: [PATCH 18/62] Bump fast-uri from 3.1.2 to 3.1.4 (#1812) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.4. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b140e9f..d544e950 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3010,9 +3010,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { From adbdf9b3c4ff6469eb74ccee434e56cf1ac0416b Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 23 Jul 2026 11:22:14 -0400 Subject: [PATCH 19/62] Update issue templates with new support statement (#1813) --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + .github/ISSUE_TEMPLATE/feature_request.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5e8e50dc..f52378a3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,7 @@ assignees: '' --- + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7927858a..3c446f2c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,6 +7,7 @@ assignees: '' --- + From 37fc63f265d387d763a7a1bdebc051a55d4c8505 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 23 Jul 2026 11:24:47 -0400 Subject: [PATCH 20/62] Improve management of web sessions when connection changes (#1808) --- src/api/index.ts | 40 ++++++++++++----- src/extension.ts | 112 ++++++++++++++++++++++------------------------- 2 files changed, 80 insertions(+), 72 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 4e1d5dd2..1ba3aacb 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -20,11 +20,31 @@ const DEFAULT_SERVER_VERSION = "2016.2.0"; import * as Atelier from "./atelier"; import { isfsConfig } from "../utils/FileProviderUtil"; -// Map of the authRequest promises for each username@host:port/pathPrefix target to avoid concurrency issues +// Map of the authRequest promises for each `username@http(s)://host:port/pathPrefix` target to avoid concurrency issues const authRequestMap = new Map>(); -/** Map of `username@host:port/pathPrefix` to cookies */ -const cookiesMap = new Map(); +/** Map of `username@http(s)://host:port/pathPrefix` to cookies */ +export const cookiesMap = new Map(); + +/** Log out of CSP sessions specified in the `sessions` array, or all known sessions if no argument was passed. */ +export async function logoutOfSessions(sessions?: string[]): Promise { + const httpsAgent = new httpsModule.Agent({ + rejectUnauthorized: vscode.workspace.getConfiguration("http").get("proxyStrictSSL"), + }); + return Promise.allSettled( + (sessions ?? Array.from(cookiesMap.keys())).map((session) => { + const cookie = cookiesMap.get(session); + if (!cookie) return; + const url = session.slice(session.indexOf("@") + 1); + return axios.head(`${url}/api/atelier/?CacheLogout=end`, { + headers: { + Cookie: cookie, + }, + httpsAgent, + }); + }) + ).then(() => {}); // Returned object isn't needed +} interface ConnectionSettings { serverName: string; @@ -175,10 +195,6 @@ export class AtelierAPI { return cookiesMap.get(this.mapKey()) ?? []; } - public clearCookies(): void { - cookiesMap.delete(this.mapKey()); - } - public xdebugUrl(): string { const { host, https, port, apiVersion, pathPrefix } = this.config; const proto = https ? "wss" : "ws"; @@ -208,13 +224,13 @@ export class AtelierAPI { } /** Return the key for getting values from connection-specific Maps for this connection */ - private mapKey(): string { - const { host, port, username } = this.config; + public mapKey(): string { + const { host, https, port, username } = this.config; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; } - return `${username}@${host}:${port}${pathPrefix}`; + return `${username}@http${https ? "s" : ""}://${host}:${port}${pathPrefix}`; } private setConnection(workspaceFolderName: string, namespace?: string): void { @@ -307,7 +323,7 @@ export class AtelierAPI { return serverName && serverName !== "" ? serverName : `${host}:${port}${pathPrefix}`; } - public async request( + private async request( minVersion: number, method: string, path?: string, @@ -457,7 +473,7 @@ export class AtelierAPI { } throw { statusCode: response.status, message: response.statusText }; } - await this.updateCookies(response.headers["set-cookie"] || []); + this.updateCookies(response.headers["set-cookie"] || []); if (method === "HEAD") { if (!originalPath) { authRequestMap.delete(mapKey); diff --git a/src/extension.ts b/src/extension.ts index a0a741e0..a6613af6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,7 +71,7 @@ import { ObjectScriptRoutineSymbolProvider } from "./providers/ObjectScriptRouti import { ObjectScriptCodeLensProvider } from "./providers/ObjectScriptCodeLensProvider"; import { XmlContentProvider } from "./providers/XmlContentProvider"; -import { AtelierAPI } from "./api"; +import { AtelierAPI, cookiesMap, logoutOfSessions } from "./api"; import { ObjectScriptDebugAdapterDescriptorFactory } from "./debug/debugAdapterFactory"; import { ObjectScriptConfigurationProvider } from "./debug/debugConfProvider"; import { ProjectsExplorerProvider } from "./explorer/projectsExplorer"; @@ -340,11 +340,7 @@ export function getResolvedConnectionSpec(key: string, dflt: any): any { /** The `api.serverId`s of all servers that are known to be inactive */ export const inactiveServerIds: Set = new Set(); -export async function checkConnection( - clearCookies = false, - uri?: vscode.Uri, - triggerRefreshes?: boolean -): Promise { +export async function checkConnection(clearState = false, uri?: vscode.Uri, triggerRefreshes?: boolean): Promise { // Do nothing if already checking the connection if (checkingConnection) { return; @@ -352,8 +348,8 @@ export async function checkConnection( const { apiTarget, configName } = connectionTarget(uri); const wsKey = configName.toLowerCase(); - if (clearCookies) { - /// clean-up cached values + if (clearState) { + // clean-up cached values await workspaceState.update(wsKey + ":host", undefined); await workspaceState.update(wsKey + ":port", undefined); await workspaceState.update(wsKey + ":superserverPort", undefined); @@ -422,10 +418,6 @@ export async function checkConnection( } } - if (clearCookies) { - api.clearCookies(); - } - // Before recreating the api object (in case something has updated connection details since we last fetched them?) // if this is an external server, remove it from the inactive list because its presence there would block the reconnect that we want to be attempting if (api.externalServer) { @@ -1521,38 +1513,57 @@ export async function activate(context: vscode.ExtensionContext): Promise { supportsMultipleEditorsPerDocument: false, }), vscode.workspace.onDidChangeConfiguration(async ({ affectsConfiguration }) => { - if (affectsConfiguration("objectscript.conn") || affectsConfiguration("intersystems.servers")) { - if (affectsConfiguration("intersystems.servers")) { - // Gather the server names previously resolved - const resolvedServers: string[] = []; - resolvedConnSpecs.forEach((v, k) => resolvedServers.push(k)); - // Clear the cache - resolvedConnSpecs.clear(); - // Resolve them again, sequentially in case user needs to be prompted for credentials - for await (const serverName of resolvedServers) { - await resolveConnectionSpec(serverName); - } - } - // Check connections sequentially for each workspace folder - let refreshFilesExplorer = false; - for await (const folder of vscode.workspace.workspaceFolders ?? []) { - if (schemas.includes(folder.uri.scheme)) { - refreshFilesExplorer = true; - } - try { - await checkConnection(true, folder.uri, true); - } catch (_) { - continue; + // Loop through all ws folder connections and see if any changed + // Find all "sessions" that are new or orphaned. + // For new ones, establish a connection? + // For orphans, logout and clear the cookies. + let refreshFilesExplorer = false; + const newConnections = new Set(); + const affectedWsFolders: vscode.WorkspaceFolder[] = []; + for (const wsFolder of vscode.workspace.workspaceFolders ?? []) { + const api = new AtelierAPI(wsFolder.uri); + newConnections.add(api.mapKey()); + const { serverName } = api.config; + if (notIsfs(wsFolder.uri) && affectsConfiguration("objectscript.conn", wsFolder)) { + // Connection info changed + affectedWsFolders.push(wsFolder); + } else if (serverName && affectsConfiguration("intersystems.servers", wsFolder)) { + const oldSpec = getResolvedConnectionSpec(serverName, undefined); + const newSpec = vscode.workspace.getConfiguration("intersystems.servers", wsFolder).get(serverName); + if ( + !oldSpec || + !( + oldSpec.webServer.host == newSpec.webServer.host && + oldSpec.webServer.port == newSpec.webServer.port && + oldSpec.webServer.scheme == newSpec.webServer.scheme && + oldSpec.webServer.pathPrefix == newSpec.webServer.pathPrefix + ) + ) { + // Connection info changed + resolvedConnSpecs.delete(serverName); + affectedWsFolders.push(wsFolder); + if (filesystemSchemas.includes(wsFolder.uri.scheme)) refreshFilesExplorer = true; } } - explorerProvider.refresh(); - projectsExplorerProvider.refresh(); - if (refreshFilesExplorer) { - // This unavoidably switches to the File Explorer view, so only do it if isfs folders were found - vscode.commands.executeCommand("workbench.files.action.refreshFilesExplorer"); + } + // Log out of any sessions that are orphaned + await logoutOfSessions(Array.from(cookiesMap.keys()).filter((k) => !newConnections.has(k))); + // Update the connection info for affected workspace folders + // This should create new CSP sessions if needed + for (const wsFolder of affectedWsFolders) { + try { + await checkConnection(true, wsFolder.uri, true); + } catch { + // Errors are handled by checkConnection() } - updateWebAndAbstractDocsCaches(vscode.workspace.workspaceFolders); } + explorerProvider.refresh(); + projectsExplorerProvider.refresh(); + if (refreshFilesExplorer) { + // This unavoidably switches to the File Explorer view, so only do it if isfs folders were found + vscode.commands.executeCommand("workbench.files.action.refreshFilesExplorer"); + } + updateWebAndAbstractDocsCaches(affectedWsFolders); if (affectsConfiguration("objectscript.commentToken")) { // Update the language configuration for "objectscript" and "objectscript-macros" macLangConf?.dispose(); @@ -2037,24 +2048,5 @@ export async function deactivate(): Promise { intLangConf?.dispose(); disposeDocumentIndex(); // Log out of all CSP sessions - const loggedOut: Set = new Set(); - const promises: Promise[] = []; - for (const f of vscode.workspace.workspaceFolders ?? []) { - const api = new AtelierAPI(f.uri); - if (!api.active || !api.cookies.length) continue; - const sessionCookie = api.cookies.find((c) => c.startsWith("CSPSESSIONID-")); - if (!sessionCookie || loggedOut.has(sessionCookie)) continue; - loggedOut.add(sessionCookie); - promises.push( - api.request( - 0, - "HEAD", - undefined, - undefined, - // Prefer IRISLogout for servers that support it - semver.lt(api.config.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" } - ) - ); - } - await Promise.allSettled(promises); + await logoutOfSessions(); } From be8b125c00f1ac7ddf9d2d6fabd912fb81cc0130 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 23 Jul 2026 12:03:06 -0400 Subject: [PATCH 21/62] Only timeout server connection check during extension activation (#1814) --- src/extension.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index a6613af6..617639a5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -340,7 +340,12 @@ export function getResolvedConnectionSpec(key: string, dflt: any): any { /** The `api.serverId`s of all servers that are known to be inactive */ export const inactiveServerIds: Set = new Set(); -export async function checkConnection(clearState = false, uri?: vscode.Uri, triggerRefreshes?: boolean): Promise { +export async function checkConnection( + clearState = false, + uri?: vscode.Uri, + triggerRefreshes?: boolean, + inActivate = false +): Promise { // Do nothing if already checking the connection if (checkingConnection) { return; @@ -451,7 +456,9 @@ export async function checkConnection(clearState = false, uri?: vscode.Uri, trig }; // Do the check - const serverInfoTimeout = 5000; + // Only time out requests when called from activate() + // Timeout is needed in that case to prevent extension activation from hanging + const serverInfoTimeout = inActivate ? 5000 : undefined; return api .serverInfo(true, serverInfoTimeout) .then(gotServerInfo) @@ -898,7 +905,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { const serverName = notIsfs(uri) && !conn["docker-compose"] ? conn.server : configName; toCheck.set(serverName, uri); }); - for await (const oneToCheck of toCheck) { + for (const oneToCheck of toCheck) { const serverName = oneToCheck[0]; const uri = oneToCheck[1]; try { @@ -907,7 +914,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { // Necessary because we are in our activate method, so its call to the Server Manager API cannot call back to our API to do that. await resolveConnectionSpec(serverName, uri); } finally { - await checkConnection(true, uri, true); + await checkConnection(true, uri, true, true); } } catch (_) { // Ignore any failure From 8a79785bf1229c2b3ecdf75776abd9fac765b560 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Fri, 24 Jul 2026 09:54:04 -0400 Subject: [PATCH 22/62] Update `Open InterSystems Document...` command to show a flat list of documents (#1804) --- src/utils/documentPicker.ts | 217 ++++++++++++++---------------------- 1 file changed, 84 insertions(+), 133 deletions(-) diff --git a/src/utils/documentPicker.ts b/src/utils/documentPicker.ts index 3f8a4f77..e9388a41 100644 --- a/src/utils/documentPicker.ts +++ b/src/utils/documentPicker.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { AtelierAPI } from "../api"; -import { cspAppsForApi, handleError } from "."; +import { cspAppsForApi, handleError, notIsfs } from "."; +import { isfsConfig } from "./FileProviderUtil"; interface DocumentPickerItem extends vscode.QuickPickItem { /** The full name of this item, including its parent(s). */ @@ -53,31 +54,23 @@ function createMultiSelectItem( return result; } -function createSingleSelectItem( - item: { Name: string; Type: number }, - parent?: string, - delimiter?: string -): DocumentPickerItem { - const result: DocumentPickerItem = { label: item.Name, fullName: item.Name }; - // Add the icon +function createSingleSelectItem(item: { Name: string; Type: number }): vscode.QuickPickItem { + // Determine the icon + let icon = "$(symbol-file)"; if (item.Type == 0) { if (item.Name.endsWith(".inc")) { - result.label = "$(file-symlink-file) " + result.label; + icon = "$(file-symlink-file)"; } else if (item.Name.endsWith(".int") || item.Name.endsWith(".mac")) { - result.label = "$(note) " + result.label; + icon = "$(note)"; } else { - result.label = "$(symbol-misc) " + result.label; + icon = "$(symbol-misc)"; } } else if (item.Type == 4) { - result.label = "$(symbol-class) " + result.label; - } else if (![9, 10].includes(item.Type)) { - result.label = "$(symbol-file) " + result.label; - } - if (parent) { - // Update the full name if this is a nested item - result.fullName = parent + delimiter + item.Name; + icon = "$(symbol-class)"; + } else { + icon = "$(symbol-file)"; } - return result; + return { label: `${icon} ${item.Name}` }; } /** @@ -300,19 +293,15 @@ export async function pickDocument( let sys: "0" | "1" = "0"; let gen: "0" | "1" = "0"; let map: "0" | "1" = "1"; - const query = "SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,?,0,0,?,,0,?)"; - const webApps = (typeSuffix ?? "csp") == "csp" ? cspAppsForApi(api) : []; - const webAppRootItems = webApps.map((app: string) => { - return { - label: app, - fullName: app, - }; - }); + const query = "SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,?,1,0,?,,0,?)"; + // Only show web app files in the list if we're in a server-side web app folder. + // Hiding these files in other cases will improve performance. + const showWeb = typeof api.wsOrFile == "object" && !notIsfs(api.wsOrFile) && isfsConfig(api.wsOrFile).csp; return new Promise((resolve) => { - const quickPick = vscode.window.createQuickPick(); + const quickPick = vscode.window.createQuickPick(); quickPick.prompt = - "Select a package or folder to view its contents. Selecting '..' shows the previous level's contents. You may also type a full document name into the filter box and press 'Enter' to select that document."; + "You may also type a full document name with extension into the filter box and press 'Enter' to select it."; quickPick.title = `${prompt ? prompt : "Select a document"} ${numberOfSteps ? `(${step}/${numberOfSteps})` : `in namespace '${api.ns}' on server '${api.serverId}'`}`; quickPick.ignoreFocusOut = true; quickPick.buttons = [ @@ -337,32 +326,23 @@ export async function pickDocument( }, ]; - const getRootItems = (): Promise => { + const getItems = (): Promise => { + quickPick.busy = true; return api - .actionQuery(`${query} WHERE Type != 5 AND Type != 10`, [ - typeSuffix ? `*.${typeSuffix}` : "*,'*.prj", + .actionQuery(query, [ + typeSuffix + ? `*.${typeSuffix}` + : showWeb + ? "*,'*.prj,'*.bpl,'*.dtl" + : "*.cls,*.mac,*.int,*.inc,*.other,'*.bpl,'*.dtl", sys, gen, map, ]) .then((data) => { - const rootitems: DocumentPickerItem[] = data.result.content.map((i) => createSingleSelectItem(i)); - const findLastIndex = (): number => { - let l = rootitems.length; - while (l--) { - if (!rootitems[l].label.startsWith("$(")) return l; - } - return -1; - }; - rootitems.splice(findLastIndex() + 1, 0, ...webAppRootItems); - return rootitems; - }) - .then((items) => { - quickPick.items = items; + quickPick.items = data.result.content.map((i) => createSingleSelectItem(i)); quickPick.selectedItems = []; - quickPick.value = ""; quickPick.busy = false; - quickPick.enabled = true; }) .catch((error) => { quickPick.hide(); @@ -371,8 +351,6 @@ export async function pickDocument( }; quickPick.onDidTriggerButton((button) => { - quickPick.busy = true; - quickPick.enabled = false; if (button === vscode.QuickInputButtons.Back) { resolve(""); // signal "go back" to the caller quickPick.hide(); @@ -385,108 +363,81 @@ export async function pickDocument( map = button.toggle.checked ? "1" : "0"; } // Refresh the items list - getRootItems(); + getItems(); }); quickPick.onDidAccept(() => { quickPick.busy = true; quickPick.enabled = false; const item = quickPick.selectedItems[0]; - if (!item || item.label.startsWith("$(")) { - let doc = item?.fullName ?? quickPick.value.trim(); - if (!item) { - // The document name came from the value text, so validate it first - // Normalize the file extension case for classes and routines - doc = [".cls", ".mac", ".int", ".inc"].includes(doc.slice(-4).toLowerCase()) - ? doc.slice(0, -3) + doc.slice(-3).toLowerCase() - : doc; - // Expand the short form of %Library classes to the long form - doc = - doc.startsWith("%") && doc.split(".").length == 2 && doc.slice(-4) == ".cls" - ? `%Library.${doc.slice(1)}` - : doc; - if (doc.endsWith(".cls")) { - // Use StudioOpenDialog for classes so we don't expose Hidden ones - api - .actionQuery("SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,0,1,,0,1)", [doc]) - .then((data) => { - if (data.result.content?.length) { - // doc is the name of a class that exists and is visible by the user - resolve(doc); - } else { - vscode.window.showErrorMessage( - `Class '${doc.slice(0, -4)}' does not exist, or is Hidden.`, - "Dismiss" - ); - resolve(undefined); - } - }) - .catch(() => { - vscode.window.showErrorMessage( - `Internal Server Error encountered trying to validate class name '${doc.slice(0, -4)}'.`, - "Dismiss" - ); - resolve(undefined); - }) - .finally(() => quickPick.hide()); - } else { - api - .headDoc(doc) - .then(() => resolve(doc)) - .catch((error) => { - vscode.window.showErrorMessage( - error?.statusCode == 400 - ? `'${doc}' is an invalid document name.` - : error?.statusCode == 404 - ? `Document '${doc}' does not exist.` - : `Internal Server Error encountered trying to validate document '${doc}'.`, - "Dismiss" - ); - resolve(undefined); - }) - .finally(() => quickPick.hide()); - } - } else { - // The document name came from an item so no validation is required - resolve(doc); - quickPick.hide(); + let doc = item?.label.slice(item?.label.indexOf(" ") + 1) ?? quickPick.value.trim(); + if (!item) { + if (typeSuffix && !new RegExp(`\\.${typeSuffix}$`, "i").test(doc)) { + // The text the user entered does not match the required type + vscode.window.showErrorMessage( + `Selected document '${doc}' does not have required extension '${typeSuffix}'.`, + "Dismiss" + ); + resolve(undefined); } - } else { - // Replace the items with the folder's contents - if (item.fullName == "") { - getRootItems(); - } else { + // The document name came from the value text, so validate it first + // Normalize the file extension case for classes and routines + doc = [".cls", ".mac", ".int", ".inc"].includes(doc.slice(-4).toLowerCase()) + ? doc.slice(0, -3) + doc.slice(-3).toLowerCase() + : doc; + // Expand the short form of %Library classes to the long form + doc = + doc.startsWith("%") && doc.split(".").length == 2 && doc.slice(-4) == ".cls" + ? `%Library.${doc.slice(1)}` + : doc; + if (doc.endsWith(".cls")) { + // Use StudioOpenDialog for classes so we don't expose Hidden ones api - .actionQuery(query, [`${item.fullName}/*${typeSuffix ? `.${typeSuffix}` : ""}`, sys, gen, map]) + .actionQuery("SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,0,1,,0,1)", [doc]) .then((data) => { - const delim = item.fullName.includes("/") ? "/" : "."; - const newItems: DocumentPickerItem[] = data.result.content.map((i) => - createSingleSelectItem(i, item.fullName, delim) + if (data.result.content?.length) { + // doc is the name of a class that exists and is visible by the user + resolve(doc); + } else { + vscode.window.showErrorMessage(`Class '${doc.slice(0, -4)}' does not exist, or is Hidden.`, "Dismiss"); + resolve(undefined); + } + }) + .catch(() => { + vscode.window.showErrorMessage( + `Internal Server Error encountered trying to validate class name '${doc.slice(0, -4)}'.`, + "Dismiss" ); - let parentFullName = - delim == "/" && webApps.includes(item.fullName) - ? "" - : item.fullName.split(delim).slice(0, -1).join(delim); - if (parentFullName == "/") parentFullName = ""; - quickPick.items = [{ label: "..", fullName: parentFullName }].concat(newItems); - quickPick.value = ""; - quickPick.selectedItems = []; - quickPick.busy = false; - quickPick.enabled = true; + resolve(undefined); }) + .finally(() => quickPick.hide()); + } else { + api + .headDoc(doc) + .then(() => resolve(doc)) .catch((error) => { - quickPick.hide(); - handleError(error, "Failed to get namespace contents."); - }); + vscode.window.showErrorMessage( + error?.statusCode == 400 + ? `'${doc}' is an invalid document name.` + : error?.statusCode == 404 + ? `Document '${doc}' does not exist.` + : `Internal Server Error encountered trying to validate document '${doc}'.`, + "Dismiss" + ); + resolve(undefined); + }) + .finally(() => quickPick.hide()); } + } else { + // The document name came from an item so no validation is required + resolve(doc); + quickPick.hide(); } }); quickPick.onDidHide(() => { resolve(undefined); quickPick.dispose(); }); - quickPick.busy = true; - quickPick.enabled = false; quickPick.show(); - getRootItems(); + getItems(); }); } From 8db90364e95337a2dcc48e63e38816158e51608d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 07:42:56 +0100 Subject: [PATCH 23/62] Bump brace-expansion (#1816) Bumps and [brace-expansion](https://github.com/juliangruber/brace-expansion). These dependencies needed to be updated together. Updates `brace-expansion` from 5.0.6 to 5.0.9 - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.9) Updates `brace-expansion` from 1.1.13 to 1.1.18 - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.9) Updates `brace-expansion` from 2.0.3 to 2.1.4 - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.9) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 5.0.9 dependency-type: indirect - dependency-name: brace-expansion dependency-version: 1.1.18 dependency-type: indirect - dependency-name: brace-expansion dependency-version: 2.1.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index d544e950..e0d12e4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -914,9 +914,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -1674,9 +1674,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -4535,15 +4535,15 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/minimist": { @@ -4602,9 +4602,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { From fc5f39ed1a5559c62a9fdfae4dff3d5bea117bf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:57:41 +0100 Subject: [PATCH 24/62] Bump fast-uri from 3.1.4 to 3.1.5 (#1817) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0d12e4d..834bdd2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3010,9 +3010,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { From 50e9ca91258ed6270faab726d2448a40dd7f3c67 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Tue, 4 Aug 2026 13:16:22 -0400 Subject: [PATCH 25/62] Sync server-side web app files as raw bytes (#1818) --- src/api/index.ts | 27 ++++++++++--------- .../FileSystemProvider/FileSystemProvider.ts | 14 ++-------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 1ba3aacb..2d0763e6 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -647,9 +647,10 @@ export class AtelierAPI { name: string, scope: vscode.Uri | string, mtime?: number, - storageOnly: boolean = false + storageOnly = false, + forceBinary = false ): Promise> { - let params, headers; + const params: Record = {}; name = this.transformNameIfCsp(name); if ( this.config.apiVersion >= 4 && @@ -663,17 +664,19 @@ export class AtelierAPI { ) .get("multilineMethodArgs") ) { - params = { format: "udl-multiline" }; - } else { - params = {}; - } - if (storageOnly) { - params["storageOnly"] = "1"; - } - if (mtime && mtime > 0) { - headers = { "IF-NONE-MATCH": new Date(mtime).toISOString().replace(/T|Z/g, " ").trim() }; + params.format = "udl-multiline"; } - return this.request(1, "GET", `${this.ns}/doc/${name}`, null, params, headers); + if (storageOnly) params.storageOnly = "1"; + if (forceBinary) params.binary = "1"; + return this.request( + 1, + "GET", + `${this.ns}/doc/${name}`, + null, + params, + // headers + mtime && mtime > 0 ? { "IF-NONE-MATCH": new Date(mtime).toISOString().replace(/T|Z/g, " ").trim() } : undefined + ); } // api v1+ diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 709d03f4..30e3b518 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -1,6 +1,5 @@ import * as path from "path"; import * as vscode from "vscode"; -import { isText } from "istextorbinary"; import { AtelierAPI } from "../../api"; import { fireOtherStudioAction, OtherStudioAction } from "../../commands/studio"; import { isfsConfig, projectContentsFromUri, studioOpenDialogFromURI } from "../../utils/FileProviderUtil"; @@ -541,7 +540,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { .then( async (entry: File) => { const contentBuffer = Buffer.from(content); - const putContent = isText(uri.path.split("/").pop(), contentBuffer) + const putContent = !csp // Web app files must always be written as raw bytes ? { content: new TextDecoder().decode(content).split(/\r?\n/), enc: false, @@ -579,15 +578,6 @@ export class FileSystemProvider implements vscode.FileSystemProvider { update = true; } } - if ( - csp && - !putContent.enc && - putContent.content.length > 1 && - putContent.content[putContent.content.length - 1] == "" - ) { - // Avoid appending a blank line on every save, which would cause a web app file to grow each time - putContent.content.pop(); - } // By the time we get here VS Code's built-in conflict resolution mechanism will already have interacted with the user. // Therefore, it's safe to ignore any conflicts. return api @@ -1075,7 +1065,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const fileName = isfsDocumentName(uri, csp); const api = new AtelierAPI(uri); return api - .getDoc(fileName, uri, cachedFile?.mtime) + .getDoc(fileName, uri, cachedFile?.mtime, undefined, csp) // Web app files must always be read as raw bytes .then((data) => data.result) .then( ({ ts, content }) => From 83d7a12be2457cf5d31350088bd9ac7f459a4a11 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 7 Aug 2026 14:16:28 -0400 Subject: [PATCH 26/62] OAuth2 VSCode (#1799) --- package-lock.json | 8 +- package.json | 2 +- src/api/index.ts | 72 ++++----- .../connectFolderToServerNamespace.ts | 4 +- src/commands/restDebugPanel.ts | 15 +- src/commands/serverActions.ts | 9 +- src/commands/webSocketTerminal.ts | 6 +- src/extension.ts | 153 +++++++++--------- src/providers/DocumentContentProvider.ts | 4 +- .../FileSystemProvider/FileSystemProvider.ts | 8 +- src/providers/LowCodeEditorProvider.ts | 20 ++- src/utils/index.ts | 7 +- 12 files changed, 154 insertions(+), 154 deletions(-) diff --git a/package-lock.json b/package-lock.json index 834bdd2a..e61b8a50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "@intersystems-community/intersystems-servermanager": "^3.10.2", + "@intersystems-community/intersystems-servermanager": "^3.14.0", "@types/istextorbinary": "2.3.1", "@types/minimatch": "6.0.0", "@types/mocha": "^10.0.10", @@ -301,9 +301,9 @@ } }, "node_modules/@intersystems-community/intersystems-servermanager": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@intersystems-community/intersystems-servermanager/-/intersystems-servermanager-3.10.2.tgz", - "integrity": "sha512-gwXxtvh+22MbCg3ZS8+evMCOuMvoDmApeYIghhFe3+NePIHhEC0na/UxeoA65xe4Rc97CJ+B2bCca/UF6iDI1A==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@intersystems-community/intersystems-servermanager/-/intersystems-servermanager-3.14.0.tgz", + "integrity": "sha512-VKitwu5OTCHUT5ABs13/pldNLn4rdjfVBSM0gKnEfR+pPHBTaTgtCARoqiPqGIkLghHl2LToFunCRlTdQhDV+A==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 088da6b1..6ec17600 100644 --- a/package.json +++ b/package.json @@ -1718,7 +1718,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "@intersystems-community/intersystems-servermanager": "^3.10.2", + "@intersystems-community/intersystems-servermanager": "^3.14.0", "@types/istextorbinary": "2.3.1", "@types/minimatch": "6.0.0", "@types/mocha": "^10.0.10", diff --git a/src/api/index.ts b/src/api/index.ts index 2d0763e6..9f81253d 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -19,6 +19,7 @@ const DEFAULT_API_VERSION = 1; const DEFAULT_SERVER_VERSION = "2016.2.0"; import * as Atelier from "./atelier"; import { isfsConfig } from "../utils/FileProviderUtil"; +import { Authorization, IServerSpec } from "@intersystems-community/intersystems-servermanager"; // Map of the authRequest promises for each `username@http(s)://host:port/pathPrefix` target to avoid concurrency issues const authRequestMap = new Map>(); @@ -46,20 +47,19 @@ export async function logoutOfSessions(sessions?: string[]): Promise { ).then(() => {}); // Returned object isn't needed } -interface ConnectionSettings { +export interface ConnectionSettings { serverName: string; active: boolean; - apiVersion: number; - serverVersion: string; + apiVersion?: number; + serverVersion?: string; https: boolean; host: string; port: number; superserverPort?: number; - pathPrefix: string; + pathPrefix?: string; ns: string; - username: string; - password: string; - docker: boolean; + auth: Authorization; + docker?: boolean; dockerService?: string; } @@ -79,7 +79,7 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", username } = this._config; + const { serverName, active = false, https = false, pathPrefix = "", auth } = this._config; const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -87,7 +87,7 @@ export class AtelierAPI { const superserverPort = this.externalServer ? this._config.superserverPort : workspaceState.get(wsKey + ":superserverPort", this._config.superserverPort); - const password = workspaceState.get(wsKey + ":password", this._config.password); + auth.resolve({ accessToken: workspaceState.get(wsKey + ":password", undefined) }); const apiVersion = workspaceState.get(wsKey + ":apiVersion", DEFAULT_API_VERSION); const serverVersion = workspaceState.get(wsKey + ":serverVersion", DEFAULT_SERVER_VERSION); const docker = workspaceState.get(wsKey + ":docker", false); @@ -103,8 +103,7 @@ export class AtelierAPI { superserverPort, pathPrefix, ns, - username, - password, + auth, docker, dockerService, }; @@ -167,14 +166,12 @@ export class AtelierAPI { * Manually set the connection spec for this object, * where `connSpec` is the return value of `getResolvedConnectionSpec()`. */ - public setConnSpec(serverName: string, connSpec: any): void { + public setConnSpec(serverName: string, connSpec: IServerSpec): void { const { webServer: { scheme, host, port, pathPrefix = "" }, - username, - password, + auth, } = connSpec; - this._config.username = username; - this._config.password = password; + this._config.auth = auth; this._config.https = scheme == "https"; this._config.host = host; this._config.port = port; @@ -225,12 +222,12 @@ export class AtelierAPI { /** Return the key for getting values from connection-specific Maps for this connection */ public mapKey(): string { - const { host, https, port, username } = this.config; + const { host, https, port, auth } = this.config; let pathPrefix = this._config.pathPrefix || ""; if (pathPrefix.length && !pathPrefix.startsWith("/")) { pathPrefix = "/" + pathPrefix; } - return `${username}@http${https ? "s" : ""}://${host}:${port}${pathPrefix}`; + return `${auth.username}@http${https ? "s" : ""}://${host}:${port}${pathPrefix}`; } private setConnection(workspaceFolderName: string, namespace?: string): void { @@ -255,8 +252,7 @@ export class AtelierAPI { if (serverName !== "") { const { webServer: { scheme, host, port, pathPrefix = "" }, - username, - password, + auth, superServer, } = getResolvedConnectionSpec(serverName, config("intersystems.servers", workspaceFolderName).get(serverName)); this._config = { @@ -269,8 +265,7 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - username, - password, + auth, pathPrefix, docker: false, }; @@ -280,8 +275,7 @@ export class AtelierAPI { if (resolvedSpec) { const { webServer: { scheme, host, port, pathPrefix = "" }, - username, - password, + auth, superServer, } = resolvedSpec; this._config = { @@ -294,21 +288,24 @@ export class AtelierAPI { host, port, superserverPort: superServer?.port, - username, - password, + auth, pathPrefix, docker: true, dockerService: conn["docker-compose"].service, }; } else { - this._config = conn; - this._config.ns = ns; - this._config.serverName = ""; + this._config = { + ...conn, + ns, + serverName: "", + }; } } else { - this._config = conn; - this._config.ns = ns; - this._config.serverName = ""; + this._config = { + ...conn, + ns, + serverName: "", + }; } } @@ -332,7 +329,7 @@ export class AtelierAPI { headers?: any, options?: any ): Promise { - const { active, apiVersion, host, port, username, password, https } = this.config; + const { active, apiVersion, host, port, https } = this.config; if (!active || !port || !host) { return Promise.reject(); } @@ -385,12 +382,11 @@ export class AtelierAPI { let auth: Promise; let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { - auth = Promise.resolve(cookies); - // Only send basic authorization if username and password specified (including blank, for unauthenticated access) - if (typeof username === "string" && typeof password === "string") { - headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`; + if (!cookies.length && this.config.auth.resolved()) { + headers["Authorization"] = this.config.auth.httpAuthorizationHeader; } + auth = Promise.resolve(cookies); } else if (!cookies.length) { if (!authRequest) { // Recursion point @@ -465,7 +461,7 @@ export class AtelierAPI { if (this.wsOrFile && !checkingConnection) { setTimeout(() => { checkConnection( - password ? true : false, + this.config.auth.resolved(), typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, true ); diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index 1cd26e16..b4076888 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -7,7 +7,7 @@ import { serverManagerApi, resolveUsernameAndPassword, } from "../extension"; -import { handleError, isUnauthenticated, notIsfs, displayableUri } from "../utils"; +import { handleError, notIsfs, displayableUri } from "../utils"; interface ConnSettings { server: string; @@ -80,7 +80,7 @@ export async function connectFolderToServerNamespace(): Promise { .serverInfo(false) .then((data) => data.result.content.namespaces) .catch(async (error) => { - if (error?.statusCode == 401 && isUnauthenticated(api.config.username)) { + if (error?.statusCode == 401 && !api.config.auth.resolved()) { // Attempt to resolve username and password and try again const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); if (newSpec) { diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index ae3344eb..29030211 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -501,8 +501,8 @@ export class RESTDebugPanel { form.onchange = () => sendData(false); button.onclick = () => sendData(true); // Bubble change events up to the form - bodyContent.onchange = headersText.onchange = - paramsText.onchange = path.onchange = + bodyContent.onchange = headersText.onchange = + paramsText.onchange = path.onchange = () => form.dispatchEvent(new Event("change")); @@ -548,16 +548,7 @@ export class RESTDebugPanel { .trim(); } }); - if ( - headers["authorization"] == undefined && - typeof api.config.username === "string" && - typeof api.config.password === "string" - ) { - // Use the server connection's auth if the user didn't specify any - headers["authorization"] = `Basic ${Buffer.from(`${api.config.username}:${api.config.password}`).toString( - "base64" - )}`; - } + headers["authorization"] = headers["authorization"] ?? (api.config.auth.httpAuthorizationHeader || ""); const hasBody = typeof message.bodyContent == "string" && message.bodyContent != "" && message.bodyType != "No Body"; if (hasBody) { diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index a9a14fdb..48a698e0 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -25,7 +25,7 @@ type ServerAction = { detail: string; id: string; label: string; rawLink?: strin export async function serverActions(): Promise { const { apiTarget, configName: workspaceFolder } = connectionTarget(); const api = new AtelierAPI(apiTarget); - const { active, host = "", ns = "", https, port = 0, pathPrefix, username, docker } = api.config; + const { active, host = "", ns = "", https, port = 0, pathPrefix, auth, docker } = api.config; const explorerCount = (await explorerProvider.getChildren()).length; if (!explorerCount && (!docker || host === "")) { await vscode.commands.executeCommand("ObjectScriptExplorer.focus"); @@ -152,7 +152,7 @@ export async function serverActions(): Promise { .replace("${serverAuth}", "") .replace("${ns}", nsEncoded) .replace("${namespace}", ns == "%SYS" ? "sys" : nsEncoded.toLowerCase()) - .replace("${username}", username) + .replace("${username}", auth.username) .replace("${classname}", classname) .replace("${classnameEncoded}", classnameEncoded) .replace("${project}", project); @@ -248,7 +248,10 @@ export async function serverActions(): Promise { if (addin) { sendStudioAddinTelemetryEvent(addin.label); let params = `Namespace=${nsEncoded}`; - params += `&User=${encodeURIComponent(username)}`; + const username = auth.username; + if (!username.includes("*")) { + params += `&User=${encodeURIComponent(username)}`; + } if (project != "") { params += `&Project=${encodeURIComponent(project)}`; } diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index 9b71df36..12dc2fbf 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -224,8 +224,12 @@ class WebSocketTerminal implements vscode.Pseudoterminal { // Set terminal properties this._hideCursorWrite("\x1b]633;P;HasRichCommandDetection=True\x07"); // Print the opening message + const username = api.config.auth.username; + const identity = username.includes("*") + ? `using \x1b[0m\x1b[3m${username.slice(1, -1)}\x1b[0m\r\n` + : `as \x1b[0m\x1b[3m${username}\x1b[0m\r\n`; this._hideCursorWrite( - `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.username}\x1b[0m\r\n` + `\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m ${identity}` ); // Add event handlers to the socket this._socket diff --git a/src/extension.ts b/src/extension.ts index 617639a5..f025985b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -71,7 +71,7 @@ import { ObjectScriptRoutineSymbolProvider } from "./providers/ObjectScriptRouti import { ObjectScriptCodeLensProvider } from "./providers/ObjectScriptCodeLensProvider"; import { XmlContentProvider } from "./providers/XmlContentProvider"; -import { AtelierAPI, cookiesMap, logoutOfSessions } from "./api"; +import { AtelierAPI, cookiesMap, logoutOfSessions, ConnectionSettings } from "./api"; import { ObjectScriptDebugAdapterDescriptorFactory } from "./debug/debugAdapterFactory"; import { ObjectScriptConfigurationProvider } from "./debug/debugConfProvider"; import { ProjectsExplorerProvider } from "./explorer/projectsExplorer"; @@ -85,7 +85,6 @@ import { portFromDockerCompose, notNull, currentFile, - isUnauthenticated, notIsfs, handleError, cspApps, @@ -156,7 +155,15 @@ const lowCodeEditorViewType = packageJson.contributes.customEditors[0].viewType; const _onDidChangeConnection = new vscode.EventEmitter(); -export const config = (setting?: string, workspaceFolderName?: string): vscode.WorkspaceConfiguration | any => { +type ConnConfig = Pick & { + "docker-compose"?: any; + server?: any; + links?: any; +}; + +export function config(setting: "conn", workspaceFolderName?: string): ConnConfig; +export function config(setting?: string, workspaceFolderName?: string): any; +export function config(setting?: string, workspaceFolderName?: string): any { workspaceFolderName = workspaceFolderName || currentWorkspaceFolder(); if ( vscode.workspace.workspaceFolders?.length && @@ -182,16 +189,17 @@ export const config = (setting?: string, workspaceFolderName?: string): vscode.W const { port, hostname: host, auth, query } = url.parse("http://" + workspaceFolderName, true); const { ns = "USER", https = false } = query; const [username, password] = (auth || "_SYSTEM:SYS").split(":"); + const authorization = serverManagerApi.defaultAuth(); + authorization.resolve({ username, accessToken: password }); if (setting == "conn") { return { active: true, https, ns, host, - port, - username, - password, - }; + port: +port, + auth: authorization, + } as ConnConfig; } else if (setting == "export") { return {}; } @@ -200,7 +208,7 @@ export const config = (setting?: string, workspaceFolderName?: string): vscode.W } const result = vscode.workspace.getConfiguration(prefix, workspaceFolder?.uri); return setting && setting.length ? result.get(setting) : result; -}; +} let reporter: TelemetryReporter; @@ -208,8 +216,12 @@ export let checkingConnection = false; export let serverManagerApi: serverManager.ServerManagerAPI; +interface ResolvedConnSpec extends serverManager.IServerSpec { + auth: serverManager.ResolvedAuthorization; +} + /** Map of the intersystems.server connection specs we have resolved via the API to that extension */ -const resolvedConnSpecs = new Map(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -255,28 +267,27 @@ export async function resolveConnectionSpec( superServer: { port: serverForUri.superserverPort, }, - username: serverForUri.username, - password: serverForUri.password ? serverForUri.password : undefined, description: `Server for workspace folder '${serverName}'`, + auth: serverManagerApi.defaultAuth(), }; } } } if (connSpec) { - await resolvePassword(connSpec); - resolvedConnSpecs.set(serverName, connSpec); + const accessToken = await resolvePassword(connSpec); + if (connSpec.auth.resolve({ accessToken })) { + resolvedConnSpecs.set(serverName, connSpec); + } } } -async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promise { - if ( - // Connection isn't unauthenticated - (!isUnauthenticated(serverSpec.username) || ignoreUnauthenticated) && - // A password is missing - typeof serverSpec.password == "undefined" - ) { - const scopes = [serverSpec.name, serverSpec.username || ""]; +async function resolvePassword( + serverSpec: serverManager.IServerSpec, + ignoreUnauthenticated = false +): Promise { + if (!(serverSpec.auth.resolved() as boolean) || ignoreUnauthenticated) { + const scopes = [serverSpec.name, serverSpec.auth?.username || ""]; // Handle Server Manager extension version < 3.8.0 const account = serverManagerApi.getAccount ? serverManagerApi.getAccount(serverSpec) : undefined; @@ -292,33 +303,33 @@ async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promi }); } if (session) { - // If original spec lacked username use the one obtained from the user by the authprovider (exact case) - serverSpec.username = serverSpec.username || session.scopes[1]; - serverSpec.password = session.accessToken; + return session.accessToken; } } } /** Resolve credentials for `serverName` and returned the complete connection spec if successful */ -export async function resolveUsernameAndPassword(serverName: string, oldSpec: any): Promise { - const newSpec: { name: string; username?: string; password?: string } = { - name: serverName, - username: oldSpec?.username, - }; - await resolvePassword(newSpec, true); - if (newSpec.password) { +export async function resolveUsernameAndPassword( + serverName: string, + oldSpec: serverManager.IServerSpec +): Promise { + const { auth: _auth, ...newSpec } = oldSpec; + newSpec.name = serverName; + const auth = _auth?.clone(); + + const accessToken = await resolvePassword({ ...newSpec, auth }, true); + if (auth?.resolve({ accessToken })) { // Update the connection spec resolvedConnSpecs.set(serverName, { ...oldSpec, - username: newSpec.username, - password: newSpec.password, + auth, }); return resolvedConnSpecs.get(serverName); } } /** Accessor for the cache of resolved connection specs */ -export function getResolvedConnectionSpec(key: string, dflt: any): any { +export function getResolvedConnectionSpec(key: string, dflt: ResolvedConnSpec): ResolvedConnSpec { let spec = resolvedConnSpecs.get(key); if (spec) { return spec; @@ -365,7 +376,7 @@ export async function checkConnection( _onDidChangeConnection.fire(); } let api = new AtelierAPI(apiTarget, false); - const { active, host = "", port = 0, superserverPort = 0, username, ns = "" } = api.config; + const { active, host = "", port = 0, superserverPort = 0, ns = "", auth } = api.config; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { panel.text = `${PANEL_LABEL}`; @@ -441,14 +452,17 @@ export async function checkConnection( } checkingConnection = true; + const username = auth.username || "UnknownUser"; + const identity = username.startsWith("*") ? `using ${username.slice(1, -1)}` : `as user '\`${username}\`'`; + // What we do when api.serverInfo call succeeds const gotServerInfo = async (info: Response>) => { panel.text = api.connInfo; const { serverName, host, port, pathPrefix } = api.config; if (serverName) { - panel.tooltip = new vscode.MarkdownString(`Connected to \`${host}:${port}${pathPrefix}\` as \`${username}\``); + panel.tooltip = new vscode.MarkdownString(`Connected to \`${host}:${port}${pathPrefix}\` ${identity}`); } else { - panel.tooltip = new vscode.MarkdownString(`Connected as \`${username}\``); + panel.tooltip = new vscode.MarkdownString(`Connected ${identity}`); } inactiveServerIds.delete(api.serverId); if (!api.externalServer) await setConnectionState(configName, true); @@ -469,8 +483,7 @@ export async function checkConnection( let success = false; message = "Not Authorized."; errorMessage = `Authorization error: Check your credentials in Settings, and that you have sufficient privileges on the /api/atelier web application on ${connInfo}`; - const username = api.config.username; - if (isUnauthenticated(username)) { + if (!api.config.auth.resolved()) { vscode.window.showErrorMessage( `Unauthenticated access rejected by '${api.serverId}'.${ !api.config.serverName ? " Connection has been disabled." : "" @@ -486,7 +499,7 @@ export async function checkConnection( const newSpec = await resolveUsernameAndPassword(api.config.serverName, oldSpec); if (newSpec) { // We were able to resolve credentials, so try again - await workspaceState.update(wsKey + ":password", newSpec.password); + await workspaceState.update(wsKey + ":password", newSpec.auth?.accessToken); api = new AtelierAPI(apiTarget, false); await api .serverInfo(true, serverInfoTimeout) @@ -514,7 +527,7 @@ export async function checkConnection( vscode.window .showInputBox({ password: true, - title: `Not Authorized. Enter password to connect as user '${username}' to ${connInfo}`, + title: `Not Authorized. Enter password to connect ${identity} to ${connInfo}`, prompt: !api.externalServer ? "If no password is entered the connection will be disabled." : "", ignoreFocusOut: true, }) @@ -849,7 +862,7 @@ let macLangConf: vscode.Disposable; let incLangConf: vscode.Disposable; let intLangConf: vscode.Disposable; -export async function activate(context: vscode.ExtensionContext): Promise { +export async function activate(context: vscode.ExtensionContext): Promise { if (!packageJson.version.includes("-") || packageJson.version.includes("-beta.")) { // Don't send telemetry for development builds try { @@ -1965,7 +1978,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { // This function is exported as one of our API functions but is also used internally // for example to implement the async variant capable of resolving docker port number. -function serverForUri(uri: vscode.Uri): any { +function serverForUri(uri: vscode.Uri): serverManager.ServerForUri { const { apiTarget, configName } = connectionTarget(uri); const configNameLower = configName.toLowerCase(); const api = new AtelierAPI(apiTarget); @@ -1973,44 +1986,34 @@ function serverForUri(uri: vscode.Uri): any { // This function intentionally no longer exposes the password for a named server UNLESS it is already exposed as plaintext in settings. // API client extensions should use Server Manager 3's authentication provider to request a missing password themselves, // which will require explicit user consent to divulge the password to the requesting extension. - const { - serverName, - active, - host = "", - https, - port, - superserverPort, - pathPrefix, - username, - password, - ns = "", - apiVersion, - serverVersion, - } = api.config; + const { serverName, active, host, https, port, superserverPort, pathPrefix, auth, ns, apiVersion, serverVersion } = + api.config; + if (serverName !== "") { + const password = vscode.workspace + .getConfiguration( + `intersystems.servers.${serverName.toLowerCase()}`, + // objectscript(xml):// URIs are not in any workspace folder, + // so make sure we resolve the server definition with the proper + // granularity. This is needed to prevent other extensions like + // Language Server prompting for a passwoord when it's not needed. + [OBJECTSCRIPT_FILE_SCHEMA, OBJECTSCRIPTXML_FILE_SCHEMA].includes(uri.scheme) + ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri + : uri + ) + .get("password") as string | undefined; + if (password !== undefined) { + auth.resolve({ accessToken: password }); + } + } return { serverName, active, - scheme: https ? "https" : "http", + ...(https ? ({ https: true, scheme: "https" } as const) : ({ scheme: "http" } as const)), host, port, superserverPort, pathPrefix, - username, - password: - serverName === "" - ? password - : vscode.workspace - .getConfiguration( - `intersystems.servers.${serverName.toLowerCase()}`, - // objectscript(xml):// URIs are not in any workspace folder, - // so make sure we resolve the server definition with the proper - // granularity. This is needed to prevent other extensions like - // Language Server prompting for a passwoord when it's not needed. - [OBJECTSCRIPT_FILE_SCHEMA, OBJECTSCRIPTXML_FILE_SCHEMA].includes(uri.scheme) - ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri - : uri - ) - .get("password"), + auth, namespace: ns, apiVersion: active ? apiVersion : undefined, serverVersion: active ? serverVersion : undefined, @@ -2019,7 +2022,7 @@ function serverForUri(uri: vscode.Uri): any { // An async variant capable of resolving docker port number. // It is exported as one of our API functions but is also used internally. -async function asyncServerForUri(uri: vscode.Uri): Promise { +async function asyncServerForUri(uri: vscode.Uri): Promise { const server = serverForUri(uri); if (!server.port) { let { apiTarget } = connectionTarget(uri); diff --git a/src/providers/DocumentContentProvider.ts b/src/providers/DocumentContentProvider.ts index 3298681e..6e314b2a 100644 --- a/src/providers/DocumentContentProvider.ts +++ b/src/providers/DocumentContentProvider.ts @@ -9,8 +9,8 @@ import { getUrisForDocument } from "../utils/documentIndex"; import { isfsConfig, IsfsUriParam } from "../utils/FileProviderUtil"; export function compareConns( - conn1: { ns: any; server: any; host: any; port: any; "docker-compose": any }, - conn2: { ns: any; server: any; host: any; port: any; "docker-compose": any } + conn1: { ns: any; server?: any; host: any; port: any; "docker-compose"?: any }, + conn2: { ns: any; server?: any; host: any; port: any; "docker-compose"?: any } ): boolean { if (conn1.ns === conn2.ns) { // Same namespace name diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 30e3b518..ab296899 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -475,10 +475,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider { .catch((error) => { if (error) { if (error.errorText.includes(" #5540:")) { - const message = `User '${api.config.username}' cannot list ${ + const username = api.config.auth.username; + const identity = username.includes("*") ? `Users using ${username.slice(1, -1)}` : `User '${username}'`; + const message = `${identity} cannot list ${ csp ? `web application '${uri.path}'` : "namespace" } contents. If they do not have READ permission on the default code database of the ${api.config.ns.toUpperCase()} namespace then grant it and retry. If the problem remains then execute the following SQL in that namespace:\n\t GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO ${ - api.config.username + username.includes("*") ? "" : username }`; handleError(message); } @@ -996,7 +998,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { private async _lookup(uri: vscode.Uri, fillInPath?: boolean): Promise { const api = new AtelierAPI(uri); const config = api.config; - const rootName = `${config.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; + const rootName = `${config.auth.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; let entry: Entry = this.superRoot.entries.get(rootName); if (!entry) { entry = new Directory(rootName, ""); diff --git a/src/providers/LowCodeEditorProvider.ts b/src/providers/LowCodeEditorProvider.ts index 46009619..296620ad 100644 --- a/src/providers/LowCodeEditorProvider.ts +++ b/src/providers/LowCodeEditorProvider.ts @@ -208,13 +208,19 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { if (!editorCompatible) { this._errorMessage("This low-code editor does not support embedding in VS Code."); } else { - // Editor is compatible so send the credentials - webviewPanel.webview.postMessage({ - direction: "editor", - type: "auth", - username: api.config.username, - password: api.config.password, - }); + const username = api.config.auth.username; + if (username.includes("*")) { + vscode.window.showWarningMessage( + `Cannot automatically log into low-code editors if VS Code is logged in using ${username.slice(1, -1)}.` + ); + } else { + webviewPanel.webview.postMessage({ + direction: "editor", + type: "auth", + username, + password: api.config.auth.password, + }); + } } return; case "changed": diff --git a/src/utils/index.ts b/src/utils/index.ts index ba7ee6cc..ae29465a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -871,11 +871,6 @@ export function methodOffsetToLine( return line; } -/** Return `true` if this username signals unauthenticated access */ -export function isUnauthenticated(username: string): boolean { - return username == undefined || username == "" || username.toLowerCase() == "unknownuser"; -} - /** Returns `true` if `uri.scheme` is neither `isfs` nor `isfs-readonly` */ export function notIsfs(uri: vscode.Uri): boolean { return !filesystemSchemas.includes(uri.scheme); @@ -928,7 +923,7 @@ export async function getWsServerConnection(minVersion?: string): Promise Date: Fri, 7 Aug 2026 14:40:44 -0400 Subject: [PATCH 27/62] Bump js-yaml from 4.2.0 to 4.3.1 (#1819) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.2.0 to 4.3.1. - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e61b8a50..aa8a7734 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4234,9 +4234,9 @@ } }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { From e031512555f23890523667f37fc2f1229199c207 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 10 Aug 2026 12:28:56 -0400 Subject: [PATCH 28/62] Fix incompatibilities with pre-OAuth2 version of Server Manager (#1820) --- package-lock.json | 8 +- package.json | 2 +- src/api/index.ts | 28 +-- src/commands/addServerNamespaceToWorkspace.ts | 24 +-- src/commands/compile.ts | 29 +-- .../connectFolderToServerNamespace.ts | 26 +-- src/commands/delete.ts | 2 +- src/commands/export.ts | 24 +-- src/commands/jumpToTagAndOffset.ts | 2 +- src/commands/newFile.ts | 18 +- src/commands/project.ts | 38 ++-- src/commands/restDebugPanel.ts | 8 +- src/commands/serverActions.ts | 6 +- src/commands/showAllClassMembers.ts | 2 +- src/commands/showPlanPanel.ts | 18 +- src/commands/studio.ts | 10 +- src/commands/studioMigration.ts | 10 +- src/commands/unitTest.ts | 160 ++++++++-------- src/commands/viewOthers.ts | 20 +- src/commands/webSocketTerminal.ts | 38 ++-- src/commands/xmlToUdl.ts | 4 +- src/debug/dbgp.ts | 2 +- src/debug/debugAdapterFactory.ts | 8 +- src/debug/debugSession.ts | 141 +++++++------- src/debug/utils.ts | 2 +- src/debug/xdebugConnection.ts | 126 ++++++------- src/explorer/explorer.ts | 10 +- src/explorer/nodes.ts | 36 ++-- src/explorer/projectsExplorer.ts | 8 +- src/extension.ts | 173 +++++++++++++----- src/providers/DocumentContentProvider.ts | 14 +- .../DocumentFormattingEditProvider.ts | 10 +- src/providers/DocumentLinkProvider.ts | 7 +- .../FileSystemProvider/FileSearchProvider.ts | 48 ++--- .../FileSystemProvider/FileSystemProvider.ts | 66 +++---- .../FileSystemProvider/TextSearchProvider.ts | 48 ++--- src/providers/LowCodeEditorProvider.ts | 8 +- .../ObjectScriptClassSymbolProvider.ts | 14 +- src/providers/ObjectScriptCodeLensProvider.ts | 44 ++--- .../ObjectScriptCompletionItemProvider.ts | 38 ++-- .../ObjectScriptDefinitionProvider.ts | 32 ++-- .../ObjectScriptDiagnosticProvider.ts | 4 +- src/providers/ObjectScriptHoverProvider.ts | 16 +- src/providers/WorkspaceSymbolProvider.ts | 8 +- src/utils/FileProviderUtil.ts | 8 +- src/utils/classDefinition.ts | 20 +- src/utils/documentIndex.ts | 25 +-- src/utils/documentPicker.ts | 24 +-- src/utils/index.ts | 78 ++++---- tsconfig.base.json | 3 +- 50 files changed, 800 insertions(+), 698 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa8a7734..5d869e4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "@intersystems-community/intersystems-servermanager": "^3.14.0", + "@intersystems-community/intersystems-servermanager": "^3.14.1", "@types/istextorbinary": "2.3.1", "@types/minimatch": "6.0.0", "@types/mocha": "^10.0.10", @@ -301,9 +301,9 @@ } }, "node_modules/@intersystems-community/intersystems-servermanager": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@intersystems-community/intersystems-servermanager/-/intersystems-servermanager-3.14.0.tgz", - "integrity": "sha512-VKitwu5OTCHUT5ABs13/pldNLn4rdjfVBSM0gKnEfR+pPHBTaTgtCARoqiPqGIkLghHl2LToFunCRlTdQhDV+A==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@intersystems-community/intersystems-servermanager/-/intersystems-servermanager-3.14.1.tgz", + "integrity": "sha512-sGOq2agsIdN0dO6AwqciSbt8bV/y2gLBuRpiyq7hUR/7liE7s5a2EbYryN6A+h6J2lHLo/akiFz1ipmckIXvvg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 6ec17600..59abf64e 100644 --- a/package.json +++ b/package.json @@ -1718,7 +1718,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "@intersystems-community/intersystems-servermanager": "^3.14.0", + "@intersystems-community/intersystems-servermanager": "^3.14.1", "@types/istextorbinary": "2.3.1", "@types/minimatch": "6.0.0", "@types/mocha": "^10.0.10", diff --git a/src/api/index.ts b/src/api/index.ts index 9f81253d..5fc2b801 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,7 +2,7 @@ import axios from "axios"; import * as httpsModule from "https"; import * as vscode from "vscode"; import * as semver from "semver"; -import { +import BasicAuthorization, { getResolvedConnectionSpec, config, extensionContext, @@ -57,7 +57,7 @@ export interface ConnectionSettings { port: number; superserverPort?: number; pathPrefix?: string; - ns: string; + ns: string | undefined; auth: Authorization; docker?: boolean; dockerService?: string; @@ -171,7 +171,7 @@ export class AtelierAPI { webServer: { scheme, host, port, pathPrefix = "" }, auth, } = connSpec; - this._config.auth = auth; + this._config.auth = auth ?? new BasicAuthorization(); this._config.https = scheme == "https"; this._config.host = host; this._config.port = port; @@ -200,7 +200,7 @@ export class AtelierAPI { public terminalUrl(): string { const { host, https, port, apiVersion, pathPrefix } = this.config; - return apiVersion >= 7 + return apiVersion! >= 7 ? `${https ? "wss" : "ws"}://${host}:${port}${pathPrefix}/api/atelier/v${apiVersion}/%25SYS/terminal` : ""; } @@ -232,7 +232,11 @@ export class AtelierAPI { private setConnection(workspaceFolderName: string, namespace?: string): void { this.configName = workspaceFolderName; - const conn = config("conn", workspaceFolderName); + const rawConn = config("conn", workspaceFolderName); + const conn = { + ...rawConn, + auth: new BasicAuthorization(rawConn.username, rawConn.password), + }; let serverName = workspaceFolderName.toLowerCase(); if (config("intersystems.servers", workspaceFolderName).has(serverName)) { this.externalServer = true; @@ -254,7 +258,7 @@ export class AtelierAPI { webServer: { scheme, host, port, pathPrefix = "" }, auth, superServer, - } = getResolvedConnectionSpec(serverName, config("intersystems.servers", workspaceFolderName).get(serverName)); + } = getResolvedConnectionSpec(serverName, config("intersystems.servers", workspaceFolderName).get(serverName))!; this._config = { serverName, active: this.externalServer ? !inactiveServerIds.has(serverName) : conn.active, @@ -333,7 +337,7 @@ export class AtelierAPI { if (!active || !port || !host) { return Promise.reject(); } - if (minVersion > apiVersion) { + if (minVersion > apiVersion!) { return Promise.reject(`${path} not supported by API version ${apiVersion}`); } const originalPath = path; @@ -348,7 +352,7 @@ export class AtelierAPI { if (!params) { return ""; } - const result = []; + const result: string[] = []; Object.keys(params).forEach((key) => { const value = params[key]; if (typeof value === "boolean") { @@ -379,7 +383,7 @@ export class AtelierAPI { const cookies = this.cookies; const mapKey = this.mapKey(); - let auth: Promise; + let auth: Promise | undefined; let authRequest = authRequestMap.get(mapKey); if (cookies.length || (method === "HEAD" && !originalPath)) { // Only send basic authorization if username and password specified (including blank, for unauthenticated access) @@ -417,7 +421,7 @@ export class AtelierAPI { } }; try { - cookie = await auth; + cookie = await auth!; reqTs = new Date(); const response = await axios.request({ method, @@ -598,7 +602,7 @@ export class AtelierAPI { .slice(data.version.indexOf(") ") + 2) .split(" ") .shift() - ).version; + )!.version; if (this.ns && this.ns.length && !data.namespaces.includes(this.ns) && checkNs) { throw { code: "WrongNamespace", @@ -649,7 +653,7 @@ export class AtelierAPI { const params: Record = {}; name = this.transformNameIfCsp(name); if ( - this.config.apiVersion >= 4 && + this.config.apiVersion! >= 4 && vscode.workspace .getConfiguration( "objectscript", diff --git a/src/commands/addServerNamespaceToWorkspace.ts b/src/commands/addServerNamespaceToWorkspace.ts index 02498848..078dfad6 100644 --- a/src/commands/addServerNamespaceToWorkspace.ts +++ b/src/commands/addServerNamespaceToWorkspace.ts @@ -17,7 +17,9 @@ import { isfsConfig, IsfsUriParam } from "../utils/FileProviderUtil"; * @param message The prefix of the message to show when the server manager API can't be found. * @returns An object containing `serverName` and `namespace`, or `undefined`. */ -async function pickServerAndNamespace(message?: string): Promise<{ serverName: string; namespace: string }> { +async function pickServerAndNamespace( + message?: string +): Promise<{ serverName: string; namespace: string } | undefined> { if (!serverManagerApi) { vscode.window.showErrorMessage( `${ @@ -29,7 +31,7 @@ async function pickServerAndNamespace(message?: string): Promise<{ serverName: s } // Get user's choice of server const options: vscode.QuickPickOptions = { ignoreFocusOut: true }; - const serverName: string = await serverManagerApi.pickServer(undefined, options); + const serverName: string | undefined = await serverManagerApi.pickServer(undefined, options); if (!serverName) { return; } @@ -40,13 +42,13 @@ async function pickServerAndNamespace(message?: string): Promise<{ serverName: s return { serverName, namespace }; } -async function pickNamespaceOnServer(serverName: string): Promise { +async function pickNamespaceOnServer(serverName: string): Promise { // Get its namespace list const uri = vscode.Uri.parse(`isfs://${serverName}:%sys/`); await resolveConnectionSpec(serverName); // Prepare a displayable form of its connection spec as a hint to the user. // This will never return the default value (second parameter) because we only just resolved the connection spec. - const connSpec = getResolvedConnectionSpec(serverName, undefined); + const connSpec = getResolvedConnectionSpec(serverName, undefined)!; const connDisplayString = `${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix}`; // Connect and fetch namespaces const api = new AtelierAPI(uri); @@ -81,9 +83,9 @@ async function pickNamespaceOnServer(serverName: string): Promise { export async function addServerNamespaceToWorkspace(resource?: vscode.Uri): Promise { const TITLE = "Add server namespace to workspace"; let serverName = ""; - let namespace = ""; - if (filesystemSchemas.includes(resource?.scheme)) { - serverName = resource.authority.split(":")[0]; + let namespace: string | undefined = ""; + if (filesystemSchemas.includes(resource?.scheme as string)) { + serverName = resource!.authority.split(":")[0]; if (serverName) { const ANOTHER = "Choose another server"; const choice = await vscode.window.showQuickPick([`Add a '${serverName}' namespace`, ANOTHER], { @@ -111,7 +113,7 @@ export async function addServerNamespaceToWorkspace(resource?: vscode.Uri): Prom } } const wsFolders = vscode.workspace.workspaceFolders ?? []; - let scheme: string; + let scheme: string | undefined; if (wsFolders.length && wsFolders.some((wf) => notIsfs(wf.uri))) { // Don't allow the creation of an editable ISFS folder // if the workspace contains non-ISFS folders already @@ -221,7 +223,7 @@ async function modifyWsFolderUri(uri: vscode.Uri): Promise((resolve) => { - let result: string; + let result: string | undefined; const allItem: vscode.QuickPickItem = { label: "All" }; const quickPick = vscode.window.createQuickPick(); quickPick.title = "Pick a specific web application to show, or show all"; @@ -352,7 +354,7 @@ async function modifyWsFolderUri(uri: vscode.Uri): Promise { - let wsFolder: vscode.WorkspaceFolder; + let wsFolder: vscode.WorkspaceFolder | null | undefined; if (!wsFolderUri) { // Select a workspace folder to modify wsFolder = await getWsFolder("Pick the workspace folder to modify", false, true); diff --git a/src/commands/compile.ts b/src/commands/compile.ts index a66bacf3..a5502833 100644 --- a/src/commands/compile.ts +++ b/src/commands/compile.ts @@ -89,7 +89,7 @@ export async function importFile( if (!file) return; const api = new AtelierAPI(file.uri); if (!api.active) return Promise.reject(); - if (file.name.split(".").pop().toLowerCase() === "cls" && !skipDeplCheck) { + if (file.name.split(".").pop()!.toLowerCase() === "cls" && !skipDeplCheck) { if (await isClassDeployed(file.name, api)) { vscode.window.showErrorMessage(`Cannot import ${file.name} because it is deployed on the server.`, "Dismiss"); return Promise.reject(); @@ -207,10 +207,10 @@ function updateOthers(others: string[], baseUri: vscode.Uri) { } others.forEach((item) => { const uri = DocumentContentProvider.getUri(item, undefined, undefined, undefined, workspaceFolder?.uri); - if (filesystemSchemas.includes(uri.scheme)) { - fileSystemProvider.fireFileChanged(uri); - } else if (uri.scheme == OBJECTSCRIPT_FILE_SCHEMA) { - documentContentProvider.update(uri); + if (filesystemSchemas.includes(uri!.scheme)) { + fileSystemProvider.fireFileChanged(uri!); + } else if (uri!.scheme == OBJECTSCRIPT_FILE_SCHEMA) { + documentContentProvider.update(uri!); } }); } @@ -234,7 +234,7 @@ export async function loadChanges( Promise.allSettled( data.result.content.map(async (doc) => { if (doc.status.length) return; - const file = files.find((f) => f.name == doc.name); + const file = files.find((f) => f.name == doc.name)!; const mtime = Number(new Date(doc.ts + "Z")); workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined); if (notIsfs(file.uri)) { @@ -312,8 +312,8 @@ function updateStorage(content: string[], storage: string[]): string[] { function storageToMap(storage: string[]): Map { const map: Map = new Map(); - let k: string; - let v = []; + let k: string | undefined; + let v: string[] = []; for (const line of storage) { if (line.startsWith("Storage ")) { k = line.slice("Storage ".length, line.length); @@ -412,7 +412,7 @@ export async function importAndCompile(document?: vscode.TextDocument, askFlags } } -export async function compileOnly(document?: vscode.TextDocument, askFlags = false): Promise { +export async function compileOnly(document?: vscode.TextDocument | null, askFlags = false): Promise { document = document || (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document @@ -468,7 +468,7 @@ export async function namespaceCompile(): Promise { .then(() => { // Always fetch server changes, even when compile failed or got cancelled const file = currentFile(); - return loadChanges([file]); + return loadChanges([file!]); }) ); } @@ -526,7 +526,7 @@ export async function compileExplorerItems(nodes: NodeBase[]): Promise { const conf = vscode.workspace.getConfiguration("objectscript", wsFolder); const api = new AtelierAPI(wsFolder.uri); if (namespace) api.setNamespace(namespace); - const docs = []; + const docs: string[] = []; for (const node of nodes) { if (node instanceof PackageNode) { switch (node.category) { @@ -659,7 +659,7 @@ export async function importArbitraryFiles(): Promise { }); if (!uris?.length) return; // Filter out non-importable files - uris = uris.filter((uri) => supportedExts.includes(uri.path.split(".").pop().toLowerCase())); + uris = uris.filter((uri) => supportedExts.includes(uri.path.split(".").pop()!.toLowerCase())); if (uris.length == 0) { vscode.window.showErrorMessage("No selected files are importable.", "Dismiss"); return; @@ -689,6 +689,7 @@ export async function importArbitraryFiles(): Promise { } }) .filter(notNull) + .map((f) => f!) ); if (filesToList.length == 0) { vscode.window.showErrorMessage("Failed to read the text of every selected file.", "Dismiss"); @@ -754,9 +755,9 @@ export async function importArbitraryFiles(): Promise { } }); if (readOnly.length) { - docsToImport = docsToImport.filter((qpi) => { + docsToImport = docsToImport!.filter((qpi) => { const nameSplit = qpi.label.split("."); - return !readOnly.includes(`${nameSplit.slice(0, -1).join(".")}.${nameSplit.pop().toUpperCase()}`); + return !readOnly.includes(`${nameSplit.slice(0, -1).join(".")}.${nameSplit.pop()!.toUpperCase()}`); }); } }); diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index b4076888..2e56ddfb 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -32,7 +32,7 @@ export async function connectFolderToServerNamespace(): Promise { .filter((folder) => notIsfs(folder.uri)) .map((folder) => { const config = vscode.workspace.getConfiguration("objectscript", folder); - const conn: ConnSettings = config.get("conn"); + const conn: ConnSettings = config.get("conn")!; return { label: folder.name, description: folder.uri.fsPath, @@ -47,14 +47,14 @@ export async function connectFolderToServerNamespace(): Promise { return; } const pick = - items.length == 1 && !items[0].detail.startsWith("Currently") + items.length == 1 && !items[0].detail!.startsWith("Currently") ? items[0] : await vscode.window.showQuickPick(items, { title: "Pick a folder" }); if (!pick) return; const folder = vscode.workspace.workspaceFolders.find((el) => el.name === pick.label); // Get user's choice of server const options: vscode.QuickPickOptions = {}; - const serverName: string = await serverManagerApi.pickServer(folder, options); + const serverName: string | undefined = await serverManagerApi.pickServer(folder, options); if (!serverName) { return; } @@ -62,27 +62,27 @@ export async function connectFolderToServerNamespace(): Promise { // Prepare a displayable form of its connection spec as a hint to the user // This will never return the default value (second parameter) because we only just resolved the connection spec. const connSpec = getResolvedConnectionSpec(serverName, undefined); - const connDisplayString = `${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix}`; + const connDisplayString = `${connSpec!.webServer.scheme}://${connSpec!.webServer.host}:${connSpec!.webServer.port}/${connSpec!.webServer.pathPrefix}`; // Connect and fetch namespaces const api = new AtelierAPI(vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`)); const serverConf = vscode.workspace .getConfiguration("intersystems", folder) .inspect<{ [key: string]: any }>("servers"); if ( - serverConf.workspaceFolderValue && - typeof serverConf.workspaceFolderValue[serverName] == "object" && - !(serverConf.workspaceValue && typeof serverConf.workspaceValue[serverName] == "object") + serverConf!.workspaceFolderValue && + typeof serverConf!.workspaceFolderValue[serverName] == "object" && + !(serverConf!.workspaceValue && typeof serverConf!.workspaceValue[serverName] == "object") ) { // Need to manually set connection info if the server is defined at the workspace folder level - api.setConnSpec(serverName, connSpec); + api.setConnSpec(serverName, connSpec!); } - const allNamespaces: string[] = await api + const allNamespaces: string[] | undefined = await api .serverInfo(false) .then((data) => data.result.content.namespaces) .catch(async (error) => { if (error?.statusCode == 401 && !api.config.auth.resolved()) { // Attempt to resolve username and password and try again - const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); + const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec!); if (newSpec) { // We were able to resolve credentials, so try again api.setConnSpec(api.config.serverName, newSpec); @@ -131,7 +131,7 @@ export async function connectFolderToServerNamespace(): Promise { // the server may be configured at the workspace folder level. const answer = await vscode.window.showQuickPick( [ - { label: `Workspace Folder ${folder.name}`, detail: displayableUri(folder.uri) }, + { label: `Workspace Folder ${folder!.name}`, detail: displayableUri(folder!.uri) }, { label: "Workspace File", detail: displayableUri(vscode.workspace.workspaceFile) }, ], { title: "Store the server connection at the workspace or folder level?" } @@ -139,7 +139,7 @@ export async function connectFolderToServerNamespace(): Promise { if (!answer) return; if (answer.label == "Workspace File") { // Enable the connection at the workspace level - const conn: any = config.inspect("conn").workspaceValue; + const conn: any = config.inspect("conn")!.workspaceValue; await config.update( "conn", { ...conn, server: serverName, ns: namespace, active: true }, @@ -149,7 +149,7 @@ export async function connectFolderToServerNamespace(): Promise { } } // Enable the connection at the workspace folder level - const conn: any = config.inspect("conn").workspaceFolderValue; + const conn: any = config.inspect("conn")!.workspaceFolderValue; await config.update( "conn", { ...conn, server: serverName, ns: namespace, active: true }, diff --git a/src/commands/delete.ts b/src/commands/delete.ts index b0ea3051..676886fb 100644 --- a/src/commands/delete.ts +++ b/src/commands/delete.ts @@ -19,7 +19,7 @@ function deleteList(items: string[], wsFolder: vscode.WorkspaceFolder, namespace if (file.result.ext && wsFolder.uri.scheme == FILESYSTEM_SCHEMA) { // Only process source control output if we're in an isfs folder const uri = DocumentContentProvider.getUri(file.result.name); - fireOtherStudioAction(OtherStudioAction.DeletedDocument, uri, file.result.ext); + fireOtherStudioAction(OtherStudioAction.DeletedDocument, uri!, file.result.ext); } }); outputChannel.appendLine(`Deleted items: ${files.filter((el) => el.result).length}`); diff --git a/src/commands/export.ts b/src/commands/export.ts index 29b7cee5..40f5751c 100644 --- a/src/commands/export.ts +++ b/src/commands/export.ts @@ -22,8 +22,8 @@ import { pickDocuments } from "../utils/documentPicker"; import { NodeBase } from "../explorer/nodes"; import { updateIndex } from "../utils/documentIndex"; -export function getCategory(fileName: string, addCategory: any | boolean): string { - const fileExt = fileName.split(".").pop().toLowerCase(); +export function getCategory(fileName: string, addCategory: any | boolean): string | null { + const fileExt = fileName.split(".").pop()!.toLowerCase(); if (typeof addCategory === "object") { for (const pattern of Object.keys(addCategory)) { if (new RegExp(`^${pattern}$`).test(fileName)) { @@ -49,11 +49,13 @@ export function getCategory(fileName: string, addCategory: any | boolean): strin export function getFileName( folder: string, name: string, - split: boolean, - addCategory: boolean, - map: { - [key: string]: string; - }, + split: boolean | undefined, + addCategory: boolean | undefined, + map: + | { + [key: string]: string; + } + | undefined, sep = path.sep ): string { if (name.includes("/")) { @@ -75,7 +77,7 @@ export function getFileName( } } fileNameArray = name.split("."); - fileExt = fileNameArray.pop().toLowerCase(); + fileExt = fileNameArray.pop()!.toLowerCase(); } else { // This is some other type of file (LUT,HL7,...) const lastDot = name.lastIndexOf("."); @@ -200,7 +202,7 @@ export async function exportAll(): Promise { } else if (filterIsValid(filter)) { filters.push(`Name LIKE '%${filter}%'`); } - let files: vscode.QuickPickItem[] = await api + let files: vscode.QuickPickItem[] | undefined = await api .actionQuery("SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog('*',1,1,?,1,0,?,?,0,?)", [ api.ns == "%SYS" ? "1" : "0", generated ? "1" : "0", @@ -268,7 +270,7 @@ export async function exportCurrentFile(): Promise { // Only export files opened from the explorer return; } - return exportList([currentFile(openDoc).name], vscode.workspace.getWorkspaceFolder(openDoc.uri)); + return exportList([currentFile(openDoc)!.name], vscode.workspace.getWorkspaceFolder(openDoc.uri)!); } export async function exportDocumentsToXMLFile(): Promise { @@ -293,7 +295,7 @@ export async function exportDocumentsToXMLFile(): Promise { } const api = new AtelierAPI(wsFolder.uri); // Make sure the server has the xml endpoints - if (api.config.apiVersion < 7) { + if (api.config.apiVersion! < 7) { vscode.window.showErrorMessage( "'Export Documents to XML File...' command requires InterSystems IRIS version 2023.2 or above.", "Dismiss" diff --git a/src/commands/jumpToTagAndOffset.ts b/src/commands/jumpToTagAndOffset.ts index a07782ea..7b60c736 100644 --- a/src/commands/jumpToTagAndOffset.ts +++ b/src/commands/jumpToTagAndOffset.ts @@ -72,7 +72,7 @@ export async function openErrorLocation(): Promise { if (!location) { return; } - const [, label, offset, routine] = location.trim().match(regex); + const [, label, offset, routine] = location.trim().match(regex)!; // Get the uri for the routine const uri = DocumentContentProvider.getUri(`${routine}.int`); if (!uri) { diff --git a/src/commands/newFile.ts b/src/commands/newFile.ts index 7e153daf..0568afd6 100644 --- a/src/commands/newFile.ts +++ b/src/commands/newFile.ts @@ -364,7 +364,7 @@ export async function newFile(type: NewFileType): Promise { } // Check if workspace folder has an active connection - let api = new AtelierAPI(wsFolder.uri); + let api: AtelierAPI | undefined = new AtelierAPI(wsFolder.uri); if (!api.active) { if (wsFolder.uri.scheme == FILESYSTEM_SCHEMA) { vscode.window.showErrorMessage( @@ -538,8 +538,8 @@ export async function newFile(type: NewFileType): Promise { ); // Create the type-specific elements prompts, then use them to generate the content - let clsContent: string; - let cls: string; + let clsContent: string | undefined; + let cls: string | undefined; if (type == NewFileType.BusinessOperation) { // Create the prompts for the invocation style and adapter class inputSteps.push( @@ -809,7 +809,7 @@ ClassMethod Transform(source As ${sourceCls}, ByRef target As ${targetCls}) As % const [, desc, assistCls] = results; // Determine the context class, if possible - let contextClass: string; + let contextClass: string | undefined; switch (assistCls) { case "Ens.Alerting.Rule.CreateAlertAssist": contextClass = "Ens.Alerting.Context.CreateAlert"; @@ -829,9 +829,9 @@ ClassMethod Transform(source As ${sourceCls}, ByRef target As ${targetCls}) As % } // Prompt for the production, if required - let production: string; + let production: string | undefined; if (Object.keys(ruleAssists).length && assistCls in ruleAssists && ruleAssists[assistCls].hasProduction) { - const productions: string[] = await api + const productions: string[] = await api! .getEnsClassList(11) .then((data) => data.result.content) .catch(() => []); @@ -972,7 +972,7 @@ ClassMethod %OnDashboardAction(pAction As %String, pContext As %ZEN.proxyObject) cls = results[0]; const [, desc, msgType] = results; - let respClass: string; + let respClass: string | undefined; if (msgType == "Request") { // Prompt the user for the response type const respClasses: vscode.QuickPickItem[] = api @@ -1079,10 +1079,10 @@ Class ${cls}${superclass ? ` Extends ${superclass}` : ""} let clsUri: vscode.Uri; if (wsFolder.uri.scheme == FILESYSTEM_SCHEMA) { // Generate the URI - clsUri = DocumentContentProvider.getUri(`${cls}.cls`, undefined, undefined, undefined, wsFolder.uri); + clsUri = DocumentContentProvider.getUri(`${cls}.cls`, undefined, undefined, undefined, wsFolder.uri)!; } else { // Try to infer the URI from the document index - clsUri = inferDocUri(`${cls}.cls`, wsFolder) ?? (await promptForDocUri(cls, wsFolder)); + clsUri = inferDocUri(`${cls}.cls`, wsFolder) ?? (await promptForDocUri(cls!, wsFolder))!; } if (clsUri && clsContent) { diff --git a/src/commands/project.ts b/src/commands/project.ts index c3254097..29974e16 100644 --- a/src/commands/project.ts +++ b/src/commands/project.ts @@ -158,7 +158,7 @@ export async function createProject(node: NodeBase | undefined, api?: AtelierAPI export async function deleteProject(node: ProjectNode | undefined): Promise { let api: AtelierAPI; - let project: string; + let project: string | undefined; if (node instanceof ProjectNode) { api = new AtelierAPI(node.wsFolder.uri); if (node.namespace) api.setNamespace(node.namespace); @@ -373,7 +373,7 @@ function sodItemToPickAdditionsItem( delim = "/"; } result.fullName = parent + delim + item.Name; - result.label = " ".repeat(parentPad + 2) + result.label; + result.label = " ".repeat(parentPad! + 2) + result.label; result.description = result.fullName; } if (item.Type && (item.Type == 9 || item.Type == 10)) { @@ -514,7 +514,7 @@ async function pickAdditions( handleError(error, "Failed to get namespace contents."); }); }; - const expandItem = (itemIdx: number): Promise => { + const expandItem = (itemIdx: number): Promise | undefined => { const selected = quickPick.selectedItems; const item = quickPick.items[itemIdx]; quickPick.items[itemIdx].buttons = [ @@ -572,8 +572,8 @@ async function pickAdditions( quickPick.busy = true; // Change value of correct parameter in array if (button.tooltip == "System") { - sys = button.toggle.checked ? "1" : "0"; - if (["RTN", "INC", "OTH"].includes(category)) { + sys = button.toggle!.checked ? "1" : "0"; + if (["RTN", "INC", "OTH"].includes(category!)) { parameters[0] = sys; } else if (category != undefined) { parameters[1] = sys; @@ -582,8 +582,8 @@ async function pickAdditions( parameters[4] = sys; } } else { - gen = button.toggle.checked ? "1" : "0"; - if (["RTN", "INC", "OTH"].includes(category)) { + gen = button.toggle!.checked ? "1" : "0"; + if (["RTN", "INC", "OTH"].includes(category!)) { parameters[1] = gen; } else if (category != undefined) { parameters[2] = gen; @@ -598,7 +598,7 @@ async function pickAdditions( quickPick.onDidTriggerItemButton((event) => { quickPick.busy = true; const itemIdx = quickPick.items.findIndex((i) => i.fullName === event.item.fullName); - if (event.button.tooltip.charAt(0) == "E") { + if (event.button.tooltip!.charAt(0) == "E") { // Expand this item expandItem(itemIdx); } else { @@ -627,8 +627,8 @@ async function pickAdditions( ); if ( itemIdx != -1 && - quickPick.items[itemIdx].buttons.length && - quickPick.items[itemIdx].buttons[0].tooltip.charAt(0) == "E" + quickPick.items[itemIdx].buttons!.length && + quickPick.items[itemIdx].buttons![0].tooltip!.charAt(0) == "E" ) { // Expand this item quickPick.busy = true; @@ -673,7 +673,7 @@ export async function modifyProject( for (const pick of picks) { // Determine the type of this item let type: string; - const ext: string = pick.split(".").pop().toLowerCase(); + const ext: string = pick.split(".").pop()!.toLowerCase(); if (["mac", "int", "inc"].includes(ext)) { type = "MAC"; } else if (ext == "cls") { @@ -681,7 +681,7 @@ export async function modifyProject( } else if (ext == "pkg") { type = "PKG"; } else if (pick.includes("/")) { - if (pick.split("/").pop().includes(".")) { + if (pick.split("/").pop()!.includes(".")) { type = "CSP"; } else { type = "DIR"; @@ -780,7 +780,7 @@ export async function modifyProject( // This is a file, so remove it const fileName = isfsDocumentName(nodeOrUri); let prjFileName = fileName.startsWith("/") ? fileName.slice(1) : fileName; - const ext = prjFileName.split(".").pop().toLowerCase(); + const ext = prjFileName.split(".").pop()!.toLowerCase(); prjFileName = ext == "cls" ? prjFileName.slice(0, -4) : prjFileName; const prjType = fileName.includes("/") ? "CSP" @@ -952,7 +952,7 @@ function isfsFolderForProject(project: string, api: AtelierAPI): number { */ export async function addIsfsFileToProject(project: string, fileName: string, api: AtelierAPI): Promise { let prjFileName = fileName.startsWith("/") ? fileName.slice(1) : fileName; - const ext = prjFileName.split(".").pop().toLowerCase(); + const ext = prjFileName.split(".").pop()!.toLowerCase(); prjFileName = ext == "cls" ? prjFileName.slice(0, -4) : prjFileName; const prjType = fileName.includes("/") ? "CSP" @@ -1031,10 +1031,10 @@ export function addWorkspaceFolderForProject(node: ProjectNode): void { async function handleCommandArg( nodeOrUri: NodeBase | vscode.Uri | undefined -): Promise<{ node: NodeBase; api: AtelierAPI; project: string } | undefined> { - let node: NodeBase; +): Promise<{ node: NodeBase | undefined; api: AtelierAPI; project: string } | undefined> { + let node: NodeBase | undefined; let api: AtelierAPI; - let project: string; + let project: string | undefined; if (nodeOrUri instanceof NodeBase) { // Called from Projects Explorer node = nodeOrUri; @@ -1054,10 +1054,10 @@ async function handleCommandArg( api = new AtelierAPI(connUri); } if (!project) { - project = await pickProject(api); + project = await pickProject(api!); if (!project) return; } - return { node, api, project }; + return { node, api: api!, project }; } export async function modifyProjectMetadata(nodeOrUri: NodeBase | vscode.Uri | undefined): Promise { diff --git a/src/commands/restDebugPanel.ts b/src/commands/restDebugPanel.ts index 29030211..f7234e21 100644 --- a/src/commands/restDebugPanel.ts +++ b/src/commands/restDebugPanel.ts @@ -72,7 +72,7 @@ export class RESTDebugPanel { vscode.window.showErrorMessage("REST service debugging webview requires an active server connection.", "Dismiss"); return; } - if (api.config.apiVersion < 2) { + if (api.config.apiVersion! < 2) { vscode.window.showErrorMessage( "REST service debugging webview requires Atelier API version 2 or above.", "Dismiss" @@ -83,7 +83,7 @@ export class RESTDebugPanel { if (this.currentPanel !== undefined) { // Can only have one panel open at once if (!this.currentPanel._panel.visible) { - if (openEditor.document.uri.toString() == this._file.toString()) { + if (openEditor.document.uri.toString() == this._file!.toString()) { // The open panel is for this document, so show it this.currentPanel._panel.reveal(vscode.ViewColumn.Active); return; @@ -522,7 +522,7 @@ export class RESTDebugPanel { // Make sure the original document is the active text editor this._panel.dispose(); - await vscode.window.showTextDocument(RESTDebugPanel._file, { + await vscode.window.showTextDocument(RESTDebugPanel._file!, { preview: false, viewColumn: vscode.ViewColumn.Active, }); @@ -592,7 +592,7 @@ export class RESTDebugPanel { await new Promise((resolve) => setTimeout(resolve, 500)); // Start the debugging session - await vscode.debug.startDebugging(vscode.workspace.getWorkspaceFolder(RESTDebugPanel._file), { + await vscode.debug.startDebugging(vscode.workspace.getWorkspaceFolder(RESTDebugPanel._file!), { type: "objectscript", request: "attach", name: "REST", diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index 48a698e0..23176c50 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -57,7 +57,7 @@ export async function serverActions(): Promise { }); } } - const connectionActionsHandler = async (action: ServerAction): Promise => { + const connectionActionsHandler = async (action: ServerAction): Promise => { if (!action) { return; } @@ -95,7 +95,7 @@ export async function serverActions(): Promise { } // Filter out the current namespace - allNamespaces = allNamespaces.filter((ns) => ns.toLowerCase() != api.config.ns.toLowerCase()); + allNamespaces = allNamespaces.filter((ns) => ns.toLowerCase() != api.config.ns!.toLowerCase()); if (!allNamespaces.length) { vscode.window.showErrorMessage(`You don't have access to any other namespaces.`, "Dismiss"); return; @@ -204,7 +204,7 @@ export async function serverActions(): Promise { } if ( (!vscode.window.activeTextEditor && wsUri && filesystemSchemas.includes(wsUri.scheme)) || - filesystemSchemas.includes(vscode.window.activeTextEditor?.document.uri.scheme) + filesystemSchemas.includes(vscode.window.activeTextEditor?.document.uri.scheme as string) ) { actions.push({ id: "serverCommandMenu", diff --git a/src/commands/showAllClassMembers.ts b/src/commands/showAllClassMembers.ts index dba84ec4..13b4cfe3 100644 --- a/src/commands/showAllClassMembers.ts +++ b/src/commands/showAllClassMembers.ts @@ -166,7 +166,7 @@ SELECT Name, Origin, 'x' AS MemberType, Parent, Internal, 0 AS NotInheritable, M const position = vscode.extensions.getExtension(lsExtensionId)?.isActive ? symbol.selectionRange.start : symbol.range.start; - await vscode.window.showTextDocument(targetUri, { + await vscode.window.showTextDocument(targetUri!, { selection: new vscode.Range(position, position), preview: false, }); diff --git a/src/commands/showPlanPanel.ts b/src/commands/showPlanPanel.ts index ce87b1d9..ddfb1d7b 100644 --- a/src/commands/showPlanPanel.ts +++ b/src/commands/showPlanPanel.ts @@ -8,7 +8,7 @@ import { iscIcon } from "../extension"; const viewType = "isc-show-plan"; const viewTitle = "Show Plan"; -let panel: vscode.WebviewPanel; +let panel: vscode.WebviewPanel | undefined; /** Escape any HTML characters so they are rendered literally */ function htmlEncode(str: string): string { @@ -82,7 +82,7 @@ export async function showPlanWebview(args: { vscode.window.showErrorMessage("Show Plan requires an active server connection.", "Dismiss"); return; } - if (lt(api.config.serverVersion, "2024.1.0")) { + if (lt(api.config.serverVersion!, "2024.1.0")) { vscode.window.showErrorMessage("Show Plan requires InterSystems IRIS version 2024.1 or above.", "Dismiss"); return; } @@ -111,7 +111,7 @@ export async function showPlanWebview(args: { const planXML: string = await api .actionQuery("SELECT %SYSTEM.QUERY_PLAN(?,,,,,?) XML", [ args.sqlQuery.trimEnd(), - `{${!lt(api.config.serverVersion, "2026.1.0") ? '"format":"LINEAR-XML",' : ""}"selectmode":"${args.selectMode}"${args.imports.length ? `,"packages":"$LFS(\\"${[...new Set(args.imports)].join(",")}\\")"` : ""}${args.includes.length ? `,"includeFiles":"$LFS(\\"${[...new Set(args.includes)].join(",")}\\")"` : ""}}`, + `{${!lt(api.config.serverVersion!, "2026.1.0") ? '"format":"LINEAR-XML",' : ""}"selectmode":"${args.selectMode}"${args.imports.length ? `,"packages":"$LFS(\\"${[...new Set(args.imports)].join(",")}\\")"` : ""}${args.includes.length ? `,"includeFiles":"$LFS(\\"${[...new Set(args.includes)].join(",")}\\")"` : ""}}`, ]) .then((data) => data?.result?.content[0]?.XML) .catch((error) => { @@ -129,7 +129,7 @@ export async function showPlanWebview(args: { // Loop through the child elements of the plan let capturePlan = false; let planText = ""; - let planChild = (planElem.firstChild); + let planChild = (planElem!.firstChild); while (planChild) { switch (planChild.nodeName) { case "sql": @@ -140,16 +140,16 @@ export async function showPlanWebview(args: { planHTML += `\n
\n`; break; case "warning": - planHTML += `

Warning

\n

\n${formatTextBlock(planChild.textContent)}

\n
\n`; + planHTML += `

Warning

\n

\n${formatTextBlock(planChild.textContent!)}

\n
\n`; break; case "info": - planHTML += `

Information

\n${formatTextBlock(planChild.textContent)}
\n`; + planHTML += `

Information

\n${formatTextBlock(planChild.textContent!)}
\n`; break; case "cost": { planHTML += `

Relative Cost `; // The plan might not have a cost const cost = planChild.attributes.getNamedItem("value")?.value; - planHTML += +cost ? `= ${cost}` : "Unavailable"; + planHTML += +cost! ? `= ${cost}` : "Unavailable"; planHTML += "

\n"; capturePlan = true; break; @@ -171,7 +171,7 @@ export async function showPlanWebview(args: { moduleText += moduleChild.textContent; moduleChild = moduleChild.nextSibling; } - planHTML += `

Module ${planChild.attributes.item(0).value}

\n${formatTextBlock(moduleText)}
\n`; + planHTML += `

Module ${planChild.attributes.item(0)!.value}

\n${formatTextBlock(moduleText)}
\n`; break; } case "subquery": { @@ -181,7 +181,7 @@ export async function showPlanWebview(args: { subqueryText += subqueryChild.textContent; subqueryChild = subqueryChild.nextSibling; } - planHTML += `

Subquery ${planChild.attributes.item(0).value}

\n${formatTextBlock(subqueryText)}
\n`; + planHTML += `

Subquery ${planChild.attributes.item(0)!.value}

\n${formatTextBlock(subqueryText)}
\n`; break; } } diff --git a/src/commands/studio.ts b/src/commands/studio.ts index 787b28ec..6543b9ea 100644 --- a/src/commands/studio.ts +++ b/src/commands/studio.ts @@ -239,11 +239,11 @@ export class StudioActions { } } - const fileExt = fileName.split(".").pop().toLowerCase(); + const fileExt = fileName.split(".").pop()!.toLowerCase(); const isCorrectMethod = (text: string) => fileExt === "cls" ? text.match("Method " + method) : text.startsWith(method); - vscode.window.showTextDocument(DocumentContentProvider.getUri(fileName), { preview: false }).then( + vscode.window.showTextDocument(DocumentContentProvider.getUri(fileName)!, { preview: false }).then( (newEditor) => { if (method) { const document = newEditor.document; @@ -289,7 +289,7 @@ export class StudioActions { return Promise.resolve(); } - private userAction(action, afterUserAction = false, answer = "", msg = "", type = 0): Thenable { + private userAction(action, afterUserAction = false, answer = "", msg = "", type = 0): Thenable | undefined { if (!action || action.id == "") { return; } @@ -515,7 +515,7 @@ export class StudioActions { public getServerInfo(): { server: string; namespace: string } { return { server: `${this.api.config.host}:${this.api.config.port}${this.api.config.pathPrefix}`, - namespace: this.api.config.ns, + namespace: this.api.config.ns!, }; } } @@ -574,7 +574,7 @@ export async function fireOtherStudioAction( action: OtherStudioAction, uri: vscode.Uri, userAction?: UserAction -): Promise { +): Promise { if (vscode.workspace.getConfiguration("objectscript.serverSourceControl", uri)?.get("disableOtherActionTriggers")) { return; } diff --git a/src/commands/studioMigration.ts b/src/commands/studioMigration.ts index 3527e15f..65ead120 100644 --- a/src/commands/studioMigration.ts +++ b/src/commands/studioMigration.ts @@ -166,7 +166,7 @@ export async function loadStudioSnippets(): Promise { * * Store the editor background color in the user `settings.json` file under `workbench.colorCustomizations`. * * Activate the modified theme. */ -export async function loadStudioColors(languageServerExt: vscode.Extension | undefined): Promise { +export async function loadStudioColors(languageServerExt: vscode.Extension | null | undefined): Promise { // Check that we're on windows if (process.platform != "win32") { vscode.window.showErrorMessage("Loading Studio syntax colors is only supported on Windows.", "Dismiss"); @@ -202,7 +202,7 @@ export async function loadStudioColors(languageServerExt: vscode.Extension ); await vscode.workspace.fs.writeFile(tempRoutineUri, new TextEncoder().encode("ROUTINE temp\n")); await vscode.workspace.openTextDocument(tempRoutineUri); - let legend: vscode.SemanticTokensLegend = await vscode.commands + let legend: vscode.SemanticTokensLegend | undefined = await vscode.commands .executeCommand("vscode.provideDocumentSemanticTokensLegend", tempRoutineUri) // Swallow any errors .then( @@ -267,7 +267,7 @@ export async function loadStudioColors(languageServerExt: vscode.Extension } if (!line.startsWith(" ")) { // This is a header line so check if it's the start of a Language - const langMatch = lineTrim.split("\\").pop().match(langRegex); + const langMatch = lineTrim.split("\\").pop()!.match(langRegex); if (langMatch != null) { currentLanguage = Number(langMatch[1]); } @@ -325,10 +325,10 @@ export async function loadStudioColors(languageServerExt: vscode.Extension // Modify the theme const editorConfig = vscode.workspace.getConfiguration("editor"); const workbenchConfig = vscode.workspace.getConfiguration("workbench"); - const tokensConfig = editorConfig.get("semanticTokenColorCustomizations"); + const tokensConfig: any = editorConfig.get("semanticTokenColorCustomizations"); tokensConfig[`[${themeName}]`] = { rules }; await editorConfig.update("semanticTokenColorCustomizations", tokensConfig, true); - const colorsConfig = workbenchConfig.get("colorCustomizations"); + const colorsConfig: any = workbenchConfig.get("colorCustomizations"); colorsConfig[`[${themeName}]`] = { "editor.background": editorBackground }; await workbenchConfig.update("colorCustomizations", colorsConfig, true); diff --git a/src/commands/unitTest.ts b/src/commands/unitTest.ts index 3e850c79..6a0ff98e 100644 --- a/src/commands/unitTest.ts +++ b/src/commands/unitTest.ts @@ -61,9 +61,9 @@ const textDecoder = new TextDecoder(); /** Find the root `TestItem` for `uri` */ function rootItemForItem(testController: vscode.TestController, uri: vscode.Uri): vscode.TestItem | undefined { - let rootItem: vscode.TestItem; + let rootItem: vscode.TestItem | undefined; for (const [, i] of testController.items) { - if (uriIsAncestorOf(i.uri, uri)) { + if (uriIsAncestorOf(i.uri!, uri)) { rootItem = i; break; } @@ -80,14 +80,14 @@ async function addTestItemsForClass(testController: vscode.TestController, paren parent.uri ); if (parentSymbols?.length == 1 && parentSymbols[0].kind == vscode.SymbolKind.Class) { - const rootItem = rootItemForItem(testController, parent.uri); + const rootItem = rootItemForItem(testController, parent.uri!); if (rootItem) { // Add this class to our cache // Need to do this here because we need the // DocumentSymbols to accurately determine the class const classes = classesForRoot.get(rootItem); - classes.set(parentSymbols[0].name, parent); - classesForRoot.set(rootItem, classes); + classes!.set(parentSymbols[0].name, parent); + classesForRoot.set(rootItem, classes!); } parent.range = parentSymbols[0].range; // Add an item for each Test* method defined in this class @@ -104,10 +104,10 @@ async function addTestItemsForClass(testController: vscode.TestController, paren parent.children.add(newItem); } }); - if (filesystemSchemas.includes(parent.uri.scheme)) { + if (filesystemSchemas.includes(parent.uri!.scheme)) { // Query the server to find inherited Test* methods - const api = new AtelierAPI(parent.uri); - const workspaceFolder = vscode.workspace.getWorkspaceFolder(parent.uri).name; + const api = new AtelierAPI(parent.uri!); + const workspaceFolder = vscode.workspace.getWorkspaceFolder(parent.uri!)!.name; const methodsMap: Map = new Map(); const inheritedMethods: { Name: string; Origin: string }[] = await api .actionQuery( @@ -156,7 +156,7 @@ async function addTestItemsForClass(testController: vscode.TestController, paren /** Get the array of `objectscript.unitTest.relativeTestRoots` for workspace folder `uri`. */ function relativeTestRootsForUri(uri: vscode.Uri): string[] { - let roots: string[] = vscode.workspace.getConfiguration("objectscript.unitTest", uri).get("relativeTestRoots"); + let roots: string[] = vscode.workspace.getConfiguration("objectscript.unitTest", uri).get("relativeTestRoots")!; roots = roots.map((r) => r.replaceAll("\\", "/")); // VS Code URIs always use / as a separator if (roots.length > 1) { // Filter out any duplicate roots, or roots that are a subdirectory of another root @@ -179,7 +179,7 @@ function createRootItemsForWorkspaceFolder( ? "Server connection is inactive" : api.ns == "%SYS" ? "Connected to the %SYS namespace" - : api.config.apiVersion < 8 + : api.config.apiVersion! < 8 ? "Must be connected to InterSystems IRIS version 2023.3 or above" : filesystemSchemas.includes(folder.uri.scheme) && csp ? "Web application folder" @@ -217,20 +217,20 @@ async function getTestItemForClass( uri: vscode.Uri, create = false ): Promise { - let item: vscode.TestItem; + let item: vscode.TestItem | undefined; const rootItem = rootItemForItem(testController, uri); if (rootItem && !rootItem.error) { // Walk the directory path until we reach a dead end or the TestItem for this class - let docPath = uri.path.slice(rootItem.uri.path.length); + let docPath = uri.path.slice(rootItem.uri!.path.length); docPath = docPath.startsWith("/") ? docPath.slice(1) : docPath; const docPathParts = docPath.split("/"); item = rootItem; for (const part of docPathParts) { - const currUri = item.uri.with({ path: `${item.uri.path}${!item.uri.path.endsWith("/") ? "/" : ""}${part}` }); + const currUri = item.uri!.with({ path: `${item.uri!.path}${!item.uri!.path.endsWith("/") ? "/" : ""}${part}` }); let currItem = item.children.get(currUri.toString()); if (!currItem && create) { // We're allowed to create non-existent directory TestItems as we walk the path - await testController.resolveHandler(item); + await testController.resolveHandler!(item); currItem = item.children.get(currUri.toString()); } item = currItem; @@ -258,10 +258,10 @@ function replaceRootTestItems(testController: vscode.TestController): void { async function childrenForServerSideFolderItem( item: vscode.TestItem ): Promise>> { - const { project, system, generated, mapped } = isfsConfig(item.uri); + const { project, system, generated, mapped } = isfsConfig(item.uri!); let query: string; let parameters: string[]; - let folder = !item.uri.path.endsWith("/") ? item.uri.path + "/" : item.uri.path; + let folder = !item.uri!.path.endsWith("/") ? item.uri!.path + "/" : item.uri!.path; folder = folder.startsWith("/") ? folder.slice(1) : folder; if (folder == "/") { // Treat this the same as an empty folder @@ -269,7 +269,7 @@ async function childrenForServerSideFolderItem( } folder = folder.replace(/\//g, "."); const folderLen = String(folder.length + 1); // Need the + 1 because SUBSTR is 1 indexed - const api = new AtelierAPI(item.uri); + const api = new AtelierAPI(item.uri!); if (project) { query = "SELECT DISTINCT CASE " + @@ -293,7 +293,7 @@ async function childrenForServerSideFolderItem( folderLen, folderLen, folderLen, - fileSpecFromURI(item.uri), + fileSpecFromURI(item.uri!), "1", "1", system ? "1" : "0", @@ -311,8 +311,8 @@ async function childrenForServerSideFolderItem( /** Create a child `TestItem` of `item` with label `child`. */ function addChildItem(testController: vscode.TestController, item: vscode.TestItem, child: string): void { - const newUri = item.uri.with({ - path: `${item.uri.path}${!item.uri.path.endsWith("/") ? "/" : ""}${child}`, + const newUri = item.uri!.with({ + path: `${item.uri!.path}${!item.uri!.path.endsWith("/") ? "/" : ""}${child}`, }); if (!item.children.get(newUri.toString())) { // Only add the item if it doesn't already exist @@ -324,7 +324,7 @@ function addChildItem(testController: vscode.TestController, item: vscode.TestIt /** Determine the class name of `item` in `root` */ function classNameForItem(item: vscode.TestItem, root: vscode.TestItem): string | undefined { - let cls: string; + let cls: string | undefined; const classes = classesForRoot.get(root); if (classes) { for (const element of classes) { @@ -359,7 +359,7 @@ async function addItemForClassUri(testController: vscode.TestController, uri: vs const item = await getTestItemForClass(testController, uri, true); if (item && item.canResolveChildren && !item.children.size) { // Resolve the methods - testController.resolveHandler(item); + testController.resolveHandler!(item); } } } @@ -372,7 +372,7 @@ async function runHandler( debug = false ): Promise { const action = debug ? "debug" : "run"; - let root: vscode.TestItem; + let root: vscode.TestItem | undefined; const asyncRequest: Atelier.AsyncUnitTestRequest = { request: "unittest", tests: [], @@ -382,9 +382,9 @@ async function runHandler( try { // Determine the test root for this run - let roots: vscode.TestItem[]; + let roots: (vscode.TestItem | undefined)[]; if (request.include?.length) { - roots = [...new Set(request.include.map((i) => rootItemForItem(testController, i.uri)))]; + roots = [...new Set(request.include.map((i) => rootItemForItem(testController, i.uri!)))]; } else { // Run was launched from controller's root level // Ignore any roots that have errors @@ -396,8 +396,8 @@ async function runHandler( const picked = await vscode.window.showQuickPick( roots.map((i) => { return { - label: i.label, - detail: displayableUri(i.uri), + label: i!.label, + detail: displayableUri(i!.uri!), item: i, }; }), @@ -417,13 +417,13 @@ async function runHandler( // Need a root to continue return; } - sendUnitTestTelemetryEvent(root.uri, debug); + sendUnitTestTelemetryEvent(root.uri!, debug); // Add the initial items to the queue to process const queue: vscode.TestItem[] = []; if (request.include?.length) { request.include.forEach((i) => { - if (uriIsAncestorOf(root.uri, i.uri)) { + if (uriIsAncestorOf(root!.uri!, i.uri!)) { queue.push(i); } }); @@ -433,16 +433,16 @@ async function runHandler( // Get the autoload configuration for the root const autoload = vscode.workspace.getConfiguration("objectscript.unitTest.autoload", root.uri); - const autoloadFolder: string = autoload.get("folder"); - const autoloadXml: boolean = autoload.get("xml"); - const autoloadUdl: boolean = autoload.get("udl"); - const autoloadEnabled: boolean = autoloadFolder != "" && (autoloadXml || autoloadUdl) && notIsfs(root.uri); + const autoloadFolder: string = autoload.get("folder")!; + const autoloadXml: boolean = autoload.get("xml")!; + const autoloadUdl: boolean = autoload.get("udl")!; + const autoloadEnabled: boolean = autoloadFolder != "" && (autoloadXml || autoloadUdl) && notIsfs(root.uri!); const autoloadProcessed: string[] = []; // Process every test that was queued // Recurse down to leaves (methods) and build a map of their parents (classes) while (queue.length > 0 && !token.isCancellationRequested) { - const test = queue.pop(); + const test = queue.pop()!; // Skip tests the user asked to exclude if (request.exclude?.length && request.exclude.some((excludedTest) => excludedTest.id === test.id)) { @@ -451,8 +451,8 @@ async function runHandler( if (autoloadEnabled) { // Process any autoload folders needed by this item - const basePath = root.uri.path.endsWith("/") ? root.uri.path.slice(0, -1) : root.uri.path; - const directories = ["", ...test.uri.path.slice(basePath.length + 1).split("/")]; + const basePath = root.uri!.path.endsWith("/") ? root.uri!.path.slice(0, -1) : root.uri!.path; + const directories = ["", ...test.uri!.path.slice(basePath.length + 1).split("/")]; if (directories[directories.length - 1].toLowerCase().endsWith(".cls")) { // Remove the class name directories.pop(); @@ -465,7 +465,7 @@ async function runHandler( // Look for XML or UDL files in the autoload folder const files = await vscode.workspace.findFiles( new vscode.RelativePattern( - test.uri.with({ path: `${basePath}${testPath}/${autoloadFolder}` }), + test.uri!.with({ path: `${basePath}${testPath}/${autoloadFolder}` }), `**/*.{${autoloadXml ? "xml,XML" : ""}${autoloadXml && autoloadUdl ? "," : ""}${ autoloadUdl ? "cls,CLS,mac,MAC,int,INT,inc,INC" : "" }}` @@ -488,16 +488,16 @@ async function runHandler( // Resolve children if not already done if (test.canResolveChildren && !test.children.size) { - await testController.resolveHandler(test); + await testController.resolveHandler!(test); } - if (test.uri.path.toLowerCase().endsWith(".cls")) { + if (test.uri!.path.toLowerCase().endsWith(".cls")) { if (test.id.includes(methodIdSeparator)) { // This is a method item // Will only reach this code if this item is in request.include // Look up the name of this class - const cls = classNameForItem(test.parent, root); + const cls = classNameForItem(test.parent!, root); if (cls) { // Check if there's a test object for the parent class already const clsObjIdx = asyncRequest.tests.findIndex((t) => t.class == cls); @@ -514,15 +514,15 @@ async function runHandler( class: cls, methods: [test.label], }); - if (notIsfs(test.parent.uri)) { + if (notIsfs(test.parent!.uri!)) { // Add this class to the list to load if (asyncRequest.load == undefined) asyncRequest.load = []; asyncRequest.load.push({ - file: test.parent.uri.fsPath, - content: textDecoder.decode(await vscode.workspace.fs.readFile(test.parent.uri)).split(/\r?\n/), + file: test.parent!.uri!.fsPath, + content: textDecoder.decode(await vscode.workspace.fs.readFile(test.parent!.uri!)).split(/\r?\n/), }); } - clsItemsRun.push(test.parent); + clsItemsRun.push(test.parent!); } } } else { @@ -538,8 +538,8 @@ async function runHandler( // Determine the methods to run clsObj.methods = []; test.children.forEach((i) => { - if (!request.exclude.some((excludedTest) => excludedTest.id === i.id)) { - clsObj.methods.push(i.label); + if (!request.exclude!.some((excludedTest) => excludedTest.id === i.id)) { + clsObj.methods!.push(i.label); } }); if (clsObj.methods.length == 0) { @@ -551,12 +551,12 @@ async function runHandler( delete clsObj.methods; } } - if (notIsfs(test.uri)) { + if (notIsfs(test.uri!)) { // Add this class to the list to load if (asyncRequest.load == undefined) asyncRequest.load = []; asyncRequest.load.push({ - file: test.uri.fsPath, - content: textDecoder.decode(await vscode.workspace.fs.readFile(test.uri)).split(/\r?\n/), + file: test.uri!.fsPath, + content: textDecoder.decode(await vscode.workspace.fs.readFile(test.uri!)).split(/\r?\n/), }); } asyncRequest.tests.push(clsObj); @@ -586,8 +586,8 @@ async function runHandler( asyncRequest.console = vscode.workspace.getConfiguration("objectscript.unitTest", root.uri).get("showOutput"); // Send the queue request - const api = new AtelierAPI(root.uri); - const queueResp: Atelier.Response = await api.queueAsync(asyncRequest, true).catch((error) => { + const api = new AtelierAPI(root.uri!); + const queueResp: Atelier.Response | undefined = await api.queueAsync(asyncRequest, true).catch((error) => { handleError(error, `Error creating job to ${action} tests.`); return undefined; }); @@ -624,7 +624,7 @@ async function runHandler( let currentOutputItem: vscode.TestItem | undefined; // The workspace folder that we're running tests in - const workspaceFolder = vscode.workspace.getWorkspaceFolder(root.uri); + const workspaceFolder = vscode.workspace.getWorkspaceFolder(root.uri!); // A map of all documents that we've computed symbols for const documentSymbols: Map = new Map(); @@ -642,19 +642,19 @@ async function runHandler( if (indent == 4) { if (consoleLine.endsWith("...")) { // This is the beginning of a class - currentOutputItem = classes.get(consoleLine.trim().split(" ")[0]); + currentOutputItem = classes!.get(consoleLine.trim().split(" ")[0]); } else { // This is the end of a class if (currentOutputItem != undefined && currentOutputItem.id.includes(methodIdSeparator)) { - currentOutputItem = currentOutputItem.parent; + currentOutputItem = currentOutputItem.parent!; } } } else if (indent == 6 && consoleLine.endsWith("...")) { // This is the beginning of a method if (currentOutputItem != undefined) { if (currentOutputItem.id.includes(methodIdSeparator)) { - currentOutputItem = currentOutputItem.parent.children.get( - `${currentOutputItem.parent.id}${methodIdSeparator}${consoleLine.trim().slice(4).split("(")[0]}` + currentOutputItem = currentOutputItem.parent!.children.get( + `${currentOutputItem.parent!.id}${methodIdSeparator}${consoleLine.trim().slice(4).split("(")[0]}` ); } else { currentOutputItem = currentOutputItem.children.get( @@ -669,7 +669,7 @@ async function runHandler( if (currentOutputItem != undefined) { testRun.appendOutput( `${consoleLine}\r\n`, - new vscode.Location(currentOutputItem.uri, currentOutputItem.range), + new vscode.Location(currentOutputItem.uri!, currentOutputItem.range!), currentOutputItem ); } else { @@ -685,7 +685,7 @@ async function runHandler( if (Array.isArray(pollResp.result)) { // Process results for (const testResult of pollResp.result) { - const clsItem = classes.get(testResult.class); + const clsItem = classes!.get(testResult.class); if (clsItem) { if (testResult.method) { // This is a method's result @@ -709,10 +709,10 @@ async function runHandler( ); if (failure.location) { if (failure.location.document.toLowerCase().endsWith(".cls")) { - let locationUri: vscode.Uri; - if (classes.has(failure.location.document.slice(0, -4))) { + let locationUri: vscode.Uri | null | undefined; + if (classes!.has(failure.location.document.slice(0, -4))) { // This is one of the known test classes - locationUri = classes.get(failure.location.document.slice(0, -4)).uri; + locationUri = classes!.get(failure.location.document.slice(0, -4))!.uri; } else { // This is some other class. There's a chance that // the class won't exist after the tests are run @@ -720,7 +720,7 @@ async function runHandler( // because it will often be useful to the user. locationUri = DocumentContentProvider.getUri( failure.location.document, - workspaceFolder.name, + workspaceFolder!.name, failure.location.namespace ); } @@ -749,7 +749,7 @@ async function runHandler( const locationLine = methodOffsetToLine( locationSymbols, fileText, - failure.location.label, + failure.location.label!, failure.location.offset ); if (locationLine != undefined) { @@ -773,7 +773,7 @@ async function runHandler( // because it will often be useful to the user. const locationUri = DocumentContentProvider.getUri( failure.location.document, - workspaceFolder.name, + workspaceFolder!.name, failure.location.namespace ); if (locationUri) { @@ -859,7 +859,7 @@ async function runHandler( } else if (debug && queueResp.result.content?.debugId && pollResp.result?.content?.debugReady) { // Make sure the activeTextEditor's document is in the same workspace folder as the test // root so the debugger connects to the correct server and runs in the correct namespace - const rootWsFolderIdx = vscode.workspace.getWorkspaceFolder(root.uri)?.index; + const rootWsFolderIdx = vscode.workspace.getWorkspaceFolder(root.uri!)?.index; if ( !vscode.window.activeTextEditor?.document.uri || vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri)?.index != rootWsFolderIdx @@ -875,7 +875,9 @@ async function runHandler( } if (!shown) { // Show the first test class. Ugly but necessary. - await vscode.window.showTextDocument(classesForRoot.get(root).get(asyncRequest.tests[0].class)?.uri); + await vscode.window.showTextDocument( + classesForRoot.get(root)!.get(asyncRequest.tests[0].class)?.uri as vscode.Uri + ); } } // Start the debugging session @@ -951,16 +953,16 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di if (!item) return; // Can't resolve "undefined" item.busy = true; try { - if (item.uri.path.toLowerCase().endsWith(".cls")) { + if (item.uri!.path.toLowerCase().endsWith(".cls")) { // Compute items for the Test* methods in this class await addTestItemsForClass(testController, item); } else { - if (notIsfs(item.uri)) { + if (notIsfs(item.uri!)) { // Read the local directory for non-autoload subdirectories and classes const autoload = vscode.workspace.getConfiguration("objectscript.unitTest.autoload", item.uri); - const autoloadFolder: string = autoload.get("folder"); - const autoloadEnabled: boolean = autoloadFolder != "" && (autoload.get("xml") || autoload.get("udl")); - (await vscode.workspace.fs.readDirectory(item.uri)).forEach((element) => { + const autoloadFolder: string = autoload.get("folder")!; + const autoloadEnabled: boolean = autoloadFolder != "" && (autoload.get("xml") || autoload.get("udl"))!; + (await vscode.workspace.fs.readDirectory(item.uri!)).forEach((element) => { if ( (element[1] == vscode.FileType.Directory && !element[0].startsWith("_") && // %UnitTest.Manager skips subfolders that start with _ @@ -990,7 +992,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di // Create new roots replaceRootTestItems(testController); // Resolve children for the roots - testController.items.forEach((item) => testController.resolveHandler(item)); + testController.items.forEach((item) => testController.resolveHandler!(item)); }; // Create the run and debug profiles const runProfile = testController.createRunProfile( @@ -1029,7 +1031,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di : r ); Promise.allSettled([ - vscode.extensions.getExtension(extensionId).activate(), + vscode.extensions.getExtension(extensionId)!.activate(), languageServer && !languageServer.isActive ? Promise.allSettled([languageServer.activate(), waitForResponse(1)]) : Promise.resolve(), @@ -1050,7 +1052,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di // Remove from our cache of classes const classes = classesForRoot.get(rootItem); if (classes) { - let cls: string; + let cls: string | undefined; for (const element of classes) { if (element[1].id == item.id) { cls = element[0]; @@ -1063,7 +1065,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di } } } - item.parent.children.delete(uri.toString()); + item.parent!.children.delete(uri.toString()); result = true; } } @@ -1080,7 +1082,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di // Update root items if needed e.removed.forEach((wf) => { testController.items.forEach((i) => { - if (uriIsAncestorOf(wf.uri, i.uri)) { + if (uriIsAncestorOf(wf.uri, i.uri!)) { // Remove this TestItem classesForRoot.delete(i); testController.items.delete(i.id); @@ -1100,7 +1102,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di const replace: vscode.TestItem[] = []; testController.items.forEach((item) => { if ( - (notIsfs(item.uri) && e.affectsConfiguration("objectscript.unitTest", item.uri)) || + (notIsfs(item.uri!) && e.affectsConfiguration("objectscript.unitTest", item.uri)) || e.affectsConfiguration("objectscript.conn", item.uri) || e.affectsConfiguration("intersystems.servers", item.uri) ) { @@ -1111,7 +1113,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di replace.forEach((item) => { classesForRoot.delete(item); testController.items.delete(item.id); - const folder = vscode.workspace.getWorkspaceFolder(item.uri); + const folder = vscode.workspace.getWorkspaceFolder(item.uri!); if (folder) { const newItems = createRootItemsForWorkspaceFolder(testController, folder); newItems.forEach((i) => { @@ -1137,7 +1139,7 @@ export function setUpTestController(context: vscode.ExtensionContext): vscode.Di testController.invalidateTestResults(item); if (item.canResolveChildren) { // Resolve the methods - testController.resolveHandler(item); + testController.resolveHandler!(item); } } } diff --git a/src/commands/viewOthers.ts b/src/commands/viewOthers.ts index fdce801b..b7a493be 100644 --- a/src/commands/viewOthers.ts +++ b/src/commands/viewOthers.ts @@ -18,9 +18,9 @@ export async function viewOthers(forceEditable = false): Promise { item = item.slice(0, colonidx); let uri: vscode.Uri; if (forceEditable) { - uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable); + uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable)!; } else { - uri = DocumentContentProvider.getUri(item); + uri = DocumentContentProvider.getUri(item)!; } if (item.endsWith(".cls")) { @@ -75,9 +75,9 @@ export async function viewOthers(forceEditable = false): Promise { } else { let uri: vscode.Uri; if (forceEditable) { - uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable); + uri = DocumentContentProvider.getUri(item, undefined, undefined, forceEditable)!; } else { - uri = DocumentContentProvider.getUri(item); + uri = DocumentContentProvider.getUri(item)!; } vscode.window.showTextDocument(uri); } @@ -90,11 +90,11 @@ export async function viewOthers(forceEditable = false): Promise { const api = new AtelierAPI(file.uri); if (!api.active) return; let indexarg: string = file.name; - const cursorpos: vscode.Position = vscode.window.activeTextEditor.selection.active; - const fileExt: string = file.name.split(".").pop().toLowerCase(); + const cursorpos: vscode.Position = vscode.window.activeTextEditor!.selection.active; + const fileExt: string = file.name.split(".").pop()!.toLowerCase(); if ( - api.config.apiVersion >= 4 && + api.config.apiVersion! >= 4 && (fileExt === "cls" || fileExt === "mac" || fileExt === "int") && !/^%sqlcq/i.test(indexarg) ) { @@ -108,7 +108,7 @@ export async function viewOthers(forceEditable = false): Promise { symbols = symbols[0].children; } - let currentSymbol: vscode.DocumentSymbol; + let currentSymbol: vscode.DocumentSymbol | undefined; for (const symbol of symbols) { if (symbol.range.contains(cursorpos)) { currentSymbol = symbol; @@ -128,7 +128,7 @@ export async function viewOthers(forceEditable = false): Promise { let isObjectScript = true; if (fileExt === "cls") { - const memberInfo = parseClassMemberDefinition(vscode.window.activeTextEditor.document, currentSymbol); + const memberInfo = parseClassMemberDefinition(vscode.window.activeTextEditor!.document, currentSymbol); if (memberInfo) { const { defEndLine, language } = memberInfo; offset = cursorpos.line - defEndLine; @@ -157,7 +157,7 @@ export async function viewOthers(forceEditable = false): Promise { open(listOthers[0], forceEditable); } else { vscode.window.showQuickPick(listOthers).then((item) => { - open(item, forceEditable); + open(item!, forceEditable); }); } }) diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index 12dc2fbf..818313d8 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -198,7 +198,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { */ private _moveCursorToLastLine(): void { const currRow = (this._cursorCol - (this._cursorCol % this._cols)) / this._cols; - const newRow = Math.ceil((this._margin + this._input.split("\r\n").pop().length + 1) / this._cols) - 1; + const newRow = Math.ceil((this._margin + this._input.split("\r\n").pop()!.length + 1) / this._cols) - 1; const rowDelta = newRow - currRow; if (rowDelta) this._hideCursorWrite(`\x1b[${rowDelta}B`); } @@ -259,19 +259,19 @@ class WebSocketTerminal implements vscode.Pseudoterminal { // Write the output to the terminal if (this._firstOutputLineSincePrompt) { // Strip leading \r\n since we printed it already - message.text = message.text.startsWith("\r\n") ? message.text.slice(2) : message.text; + message.text = message.text!.startsWith("\r\n") ? message.text!.slice(2) : message.text; this._firstOutputLineSincePrompt = false; } - if (message.text.includes("\x1b[31;1m")) { - if (message.text.includes("\x1b[31;1m")) { + if (message.text!.includes("\x1b[31;1m")) { + if (message.text!.includes("\x1b[31;1m")) { // Report no exit code for interrupts this._promptExitCode = ""; } else { this._promptExitCode = ";1"; } } - this._margin = this._cursorCol = message.text.split("\r\n").pop().length; - this._hideCursorWrite(message.text); + this._margin = this._cursorCol = message.text!.split("\r\n").pop()!.length; + this._hideCursorWrite(message.text!); break; case "prompt": case "read": @@ -280,11 +280,11 @@ class WebSocketTerminal implements vscode.Pseudoterminal { this._hideCursorWrite( `\x1b]633;D${this._promptExitCode}\x07\r\n\x1b]633;A\x07${message.text}\x1b]633;B\x07` ); - this._margin = this._cursorCol = message.text.replace(this._colorsRegex, "").length; - this._prompt = message.text; + this._margin = this._cursorCol = message.text!.replace(this._colorsRegex, "").length; + this._prompt = message.text!; this._promptExitCode = ";0"; // Store the current namespace - this.currentNs = message.ns; + this.currentNs = message.ns!; } // Enable input this._state = message.type; @@ -303,13 +303,13 @@ class WebSocketTerminal implements vscode.Pseudoterminal { case "color": { // Replace the input with the syntax colored text, keeping the cursor at the same spot let cursorLine = Math.ceil((this._cursorCol + 1) / this._cols) - 1; - if (message.text.includes("\r\n")) { - const lines = message.text.replace(this._colorsRegex, "").split("\r\n"); + if (message.text!.includes("\r\n")) { + const lines = message.text!.replace(this._colorsRegex, "").split("\r\n"); lines.pop(); cursorLine += lines.reduce((sum, line) => sum + Math.ceil((line.length + 1) / this._cols), 0); } this._hideCursorWrite( - `\x1b7${cursorLine > 0 ? `\x1b[${cursorLine}A` : ""}\r\x1b[0J${this._prompt}${message.text.replace( + `\x1b7${cursorLine > 0 ? `\x1b[${cursorLine}A` : ""}\r\x1b[0J${this._prompt}${message.text!.replace( /\r\n/g, `\r\n${this.multiLinePrompt}` )}\x1b8` @@ -517,7 +517,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { // User can't move cursor return; } - if (this._cursorCol < this._margin + this._input.split("\r\n").pop().length) { + if (this._cursorCol < this._margin + this._input.split("\r\n").pop()!.length) { this._cursorCol++; if (this._cursorCol % this._cols == 0) { // Move the cursor to the beginning of the next line @@ -553,7 +553,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { case keys.ctrlE: { if (this._state == "prompt") { // Move the cursor to the end of the input - const lineLength = this._input.split("\r\n").pop().length; + const lineLength = this._input.split("\r\n").pop()!.length; if (lineLength > this._cursorCol) { this._moveCursor(lineLength - this._cursorCol); } @@ -658,7 +658,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { // Add a blank "line" to move the cursor to the next viewport row return [""]; } - const chunks = []; + const chunks: string[] = []; for (let i = 0; i < line.length; i += this._cols) { chunks.push(line.slice(i, i + this._cols)); } @@ -759,7 +759,7 @@ function terminalConfigForUri( return; } // Make sure the server has the terminal endpoint - if (api.config.apiVersion < 7) { + if (api.config.apiVersion! < 7) { reportError("Lite Terminal requires InterSystems IRIS version 2023.2 or above.", throwErrors); return; } @@ -782,7 +782,7 @@ function terminalConfigForUri( }; } -export async function launchWebSocketTerminal(targetUri?: vscode.Uri, nsOverride?: string): Promise { +export async function launchWebSocketTerminal(targetUri?: vscode.Uri | null, nsOverride?: string): Promise { // Determine the server to connect to if (targetUri) { // Uri passed as command argument might be for a server we haven't yet resolved @@ -815,7 +815,7 @@ export async function launchWebSocketTerminal(targetUri?: vscode.Uri, nsOverride export class WebSocketTerminalProfileProvider implements vscode.TerminalProfileProvider { async provideTerminalProfile(): Promise { // Determine the server connection to use - const uri: vscode.Uri = await getWsServerConnection("2023.2.0"); + const uri: vscode.Uri | null | undefined = await getWsServerConnection("2023.2.0"); if (uri) { const api = new AtelierAPI(uri); @@ -823,7 +823,7 @@ export class WebSocketTerminalProfileProvider implements vscode.TerminalProfileP await api.serverInfo(); // Get the terminal configuration. Will throw if there's an error. const terminalOpts = terminalConfigForUri(api, uri, true); - return new vscode.TerminalProfile(terminalOpts); + return new vscode.TerminalProfile(terminalOpts!); } else if (uri === undefined) { throw new Error(NO_ELIGIBLE_CONNECTIONS); } else { diff --git a/src/commands/xmlToUdl.ts b/src/commands/xmlToUdl.ts index 66dfbec0..18da70d4 100644 --- a/src/commands/xmlToUdl.ts +++ b/src/commands/xmlToUdl.ts @@ -92,7 +92,7 @@ export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise } try { // Determine the workspace folder - let wsFolder: vscode.WorkspaceFolder; + let wsFolder: vscode.WorkspaceFolder | null | undefined; if (xmlUri) { wsFolder = vscode.workspace.getWorkspaceFolder(xmlUri); } else { @@ -128,7 +128,7 @@ export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise return; } xmlUri = uris[0]; - if (xmlUri.path.split(".").pop().toLowerCase() != "xml") { + if (xmlUri.path.split(".").pop()!.toLowerCase() != "xml") { vscode.window.showErrorMessage("The selected file was not XML.", "Dismiss"); return; } diff --git a/src/debug/dbgp.ts b/src/debug/dbgp.ts index 3a95440d..515bb27a 100644 --- a/src/debug/dbgp.ts +++ b/src/debug/dbgp.ts @@ -68,7 +68,7 @@ export class DbgpConnection extends EventEmitter { private _handleDataChunk() { if (!this._messages.length) return; // Shouldn't ever happen - const data: Buffer = this._messages.shift(); + const data: Buffer = this._messages.shift()!; if (this._parsingState === ParsingState.DataLength) { // does data contain a NULL byte? const separatorIndex = data.indexOf("|"); diff --git a/src/debug/debugAdapterFactory.ts b/src/debug/debugAdapterFactory.ts index 738a8870..0c8a29fe 100644 --- a/src/debug/debugAdapterFactory.ts +++ b/src/debug/debugAdapterFactory.ts @@ -16,12 +16,12 @@ export class ObjectScriptDebugAdapterDescriptorFactory // pickProcess may have added a suffix to inform us which folder's connection it used const workspaceFolderIndex = (session.configuration.processId as string)?.split("@")[1]; const workspaceFolderUri = workspaceFolderIndex - ? vscode.workspace.workspaceFolders[parseInt(workspaceFolderIndex)]?.uri + ? vscode.workspace.workspaceFolders![parseInt(workspaceFolderIndex)]?.uri : undefined; debugSession.setupAPI(workspaceFolderUri); const serverId = debugSession.serverId; - let server = this.serverMap.get(serverId); + let server = this.serverMap.get(serverId!); if (!server) { // start listening on a random port server = net @@ -30,12 +30,12 @@ export class ObjectScriptDebugAdapterDescriptorFactory debugSession.start(socket as NodeJS.ReadableStream, socket); }) .listen(0); - this.serverMap.set(serverId, server); + this.serverMap.set(serverId!, server); } // make VS Code connect to this debug server const address = server.address(); - const port = typeof address !== "string" ? address.port : 9000; + const port = typeof address !== "string" ? address!.port : 9000; return new vscode.DebugAdapterServer(port); } diff --git a/src/debug/debugSession.ts b/src/debug/debugSession.ts index 8e592504..91b07449 100644 --- a/src/debug/debugSession.ts +++ b/src/debug/debugSession.ts @@ -40,9 +40,9 @@ interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { } /** converts a uri from VS Code to a server-side XDebug file URI with respect to source root settings */ -function convertClientPathToDebugger(uri: vscode.Uri, namespace: string): string { +function convertClientPathToDebugger(uri: vscode.Uri, namespace: string): string | undefined { const { scheme, path } = uri; - let fileName: string; + let fileName: string | undefined; if (scheme && schemas.includes(scheme)) { const { ns } = isfsConfig(uri); if (ns) namespace = ns; @@ -69,7 +69,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { private _statuses = new Map(); - private _connection: xdebug.Connection; + private _connection: xdebug.Connection | null | undefined; private _namespace: string; @@ -91,7 +91,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { private _evalResultProperties = new Map(); - private _workspace: string; + private _workspace: string | undefined; /** If this is a CSPDEBUG session */ private _isCsp = false; @@ -164,8 +164,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { /** Check if the target is stopped */ private async _isStopped(): Promise { - return this._connection - .sendStepIntoCommand() + return this._connection!.sendStepIntoCommand() .then((resp: xdebug.StatusResponse) => { if (resp.status == "stopped") { // Target unattached, terminate session @@ -199,16 +198,16 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { }; try { - if (!this._api.active) { + if (!this._api!.active) { throw new Error("Connection not active"); } - this._namespace = this._api.ns; - this._url = this._api.xdebugUrl(); + this._namespace = this._api!.ns; + this._url = this._api!.xdebugUrl(); const socket = new WebSocket(this._url, { rejectUnauthorized: vscode.workspace.getConfiguration("http").get("proxyStrictSSL"), headers: { - cookie: this._api.cookies, + cookie: this._api!.cookies, }, }); @@ -216,8 +215,8 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { if (!this._connection) { return; } - this.sendEvent(new ThreadEvent("exited", this._connection.id)); - this._connection.close(); + this.sendEvent(new ThreadEvent("exited", this._connection!.id)); + this._connection!.close(); this._connection = null; }; this._connection = new xdebug.Connection(socket) @@ -230,15 +229,15 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { this.sendEvent(new OutputEvent(data, "stdout")); }); - await this._connection.waitForInitPacket(); + await this._connection!.waitForInitPacket(); - await this._connection.sendFeatureSetCommand("max_data", 8192); - await this._connection.sendFeatureSetCommand("max_children", 32); - await this._connection.sendFeatureSetCommand("max_depth", 2); - await this._connection.sendFeatureSetCommand("notify_ok", 1); - await this._connection.sendFeatureSetCommand( + await this._connection!.sendFeatureSetCommand("max_data", 8192); + await this._connection!.sendFeatureSetCommand("max_children", 32); + await this._connection!.sendFeatureSetCommand("max_depth", 2); + await this._connection!.sendFeatureSetCommand("notify_ok", 1); + await this._connection!.sendFeatureSetCommand( "step_granularity", - vscode.workspace.getConfiguration("objectscript.debug").get("stepGranularity") + vscode.workspace.getConfiguration("objectscript.debug").get("stepGranularity")! ); this.sendResponse(response); @@ -262,7 +261,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { this._debugTargetSet = false; this._isLaunch = true; const debugTarget = `${this._namespace}:${args.program}`; - await this._connection.sendFeatureSetCommand("debug_target", debugTarget, true); + await this._connection!.sendFeatureSetCommand("debug_target", debugTarget, true); sendDebuggerTelemetryEvent("launch"); } catch (error) { this.sendErrorResponse(response, error); @@ -276,18 +275,18 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { try { this._debugTargetSet = this._isLaunch = false; const debugTarget = - args.cspDebugId != undefined ? `CSPDEBUG:${args.cspDebugId}` : `PID:${args.processId.split("@")[0]}`; - await this._connection.sendFeatureSetCommand("debug_target", debugTarget); + args.cspDebugId != undefined ? `CSPDEBUG:${args.cspDebugId}` : `PID:${args.processId!.split("@")[0]}`; + await this._connection!.sendFeatureSetCommand("debug_target", debugTarget); if (args.cspDebugId != undefined) { if (args.isUnitTest) { // Set a watchpoint so the target breaks after the unit tests have finished - await this._connection.sendBreakpointSetCommand( + await this._connection!.sendBreakpointSetCommand( new xdebug.Watchpoint("QQQZZZDebugWatchpointTriggerVar", this._unitTestWatchpointCondition) ); this._isUnitTest = true; } else { // Set a watchpoint so the target breaks after the REST response is sent - await this._connection.sendBreakpointSetCommand(new xdebug.Watchpoint("ok", this._cspWatchpointCondition)); + await this._connection!.sendBreakpointSetCommand(new xdebug.Watchpoint("ok", this._cspWatchpointCondition)); this._isCsp = true; } this.sendResponse(response); @@ -314,7 +313,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.PauseArguments ): Promise { try { - const xdebugResponse = await this._connection.sendBreakCommand(); + const xdebugResponse = await this._connection!.sendBreakCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -330,15 +329,15 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { if (!this._isLaunch && !this._isCsp) { // The debug agent ignores the first run command // for non-CSP attaches, so send one right away - await this._connection.sendRunCommand(); + await this._connection!.sendRunCommand(); // Tell VS Code that we're stopped this.sendResponse(response); - const event: DebugProtocol.StoppedEvent = new StoppedEvent("entry", this._connection.id); + const event: DebugProtocol.StoppedEvent = new StoppedEvent("entry", this._connection!.id); event.body.allThreadsStopped = false; this.sendEvent(event); } else { // Tell the debugger to run the target process - const xdebugResponse = await this._connection.sendRunCommand(); + const xdebugResponse = await this._connection!.sendRunCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } @@ -357,7 +356,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { // If attach, it will detach from the target // If launch, it will terminate the target try { - const xdebugResponse = await this._connection.sendDetachCommand(); + const xdebugResponse = await this._connection!.sendDetachCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -377,13 +376,13 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { // args.source.path is a file path if the file is local and is a stringified Uri if the file is virtual const uri = - (this._workspaceFolderUri ?? vscode.workspace.workspaceFolders[0]?.uri)?.scheme == "file" - ? vscode.Uri.file(args.source.path) - : vscode.Uri.parse(args.source.path); + (this._workspaceFolderUri ?? vscode.workspace.workspaceFolders![0]?.uri)?.scheme == "file" + ? vscode.Uri.file(args.source.path!) + : vscode.Uri.parse(args.source.path!); const wsFolder = vscode.workspace.getWorkspaceFolder(uri); if (!wsFolder || (this._workspaceFolderUri && wsFolder.uri.toString() != this._workspaceFolderUri.toString())) { response.body = { - breakpoints: args.breakpoints.map(() => { + breakpoints: args.breakpoints!.map(() => { return { verified: false, message: "This file is not from the same workspace folder as the debug target", @@ -397,7 +396,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { const xdebugUri = convertClientPathToDebugger(uri, this._namespace); if (!xdebugUri) { response.body = { - breakpoints: args.breakpoints.map(() => { + breakpoints: args.breakpoints!.map(() => { return { verified: false, message: "Failed to determine the class or routine name of this file", @@ -408,11 +407,11 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { this.sendResponse(response); return; } - const [, fileName] = xdebugUri.match(/\|([^|]+)$/); - const fileExt = fileName.split(".").pop().toLowerCase(); + const [, fileName] = xdebugUri.match(/\|([^|]+)$/)!; + const fileExt = fileName.split(".").pop()!.toLowerCase(); const languageServer: boolean = vscode.extensions.getExtension(lsExtensionId)?.isActive ?? false; - const currentList = await this._connection.sendBreakpointListCommand(); + const currentList = await this._connection!.sendBreakpointListCommand(); currentList.breakpoints .filter((breakpoint) => { if (breakpoint instanceof xdebug.LineBreakpoint) { @@ -421,7 +420,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { return false; }) .map((breakpoint) => { - this._connection.sendBreakpointRemoveCommand(breakpoint); + this._connection!.sendBreakpointRemoveCommand(breakpoint); }); let xdebugBreakpoints: (xdebug.ConditionalBreakpoint | xdebug.ClassLineBreakpoint | xdebug.LineBreakpoint)[] = []; @@ -433,11 +432,11 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { )[0].children; } xdebugBreakpoints = await Promise.all( - args.breakpoints.map(async (breakpoint) => { + args.breakpoints!.map(async (breakpoint) => { const line = breakpoint.line; if (fileExt == "cls") { // Find the class member that this breakpoint is in - let currentSymbol: vscode.DocumentSymbol; + let currentSymbol: vscode.DocumentSymbol | undefined; for (const symbol of symbols) { if (symbol.range.contains(new vscode.Position(line, 0))) { currentSymbol = symbol; @@ -541,7 +540,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { reason: "failed", }; } else { - await this._connection.sendBreakpointSetCommand(breakpoint); + await this._connection!.sendBreakpointSetCommand(breakpoint); vscodeBreakpoints[index] = { verified: true, line: breakpoint.line }; } } catch (error) { @@ -573,7 +572,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { ): void { if ( args.variablesReference !== undefined && - [0, 1].includes(this._contexts.get(args.variablesReference).id) && + [0, 1].includes(this._contexts.get(args.variablesReference)!.id) && !args.name.includes("(") ) { // This is an unsubscripted private or public local variable @@ -600,7 +599,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { try { await this._waitForDebugTarget(); - const currentList = await this._connection.sendBreakpointListCommand(); + const currentList = await this._connection!.sendBreakpointListCommand(); currentList.breakpoints .filter((breakpoint) => { if (breakpoint instanceof xdebug.Watchpoint) { @@ -609,7 +608,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { return false; }) .map((breakpoint) => { - this._connection.sendBreakpointRemoveCommand(breakpoint); + this._connection!.sendBreakpointRemoveCommand(breakpoint); }); let xdebugWatchpoints: xdebug.Watchpoint[] = []; @@ -623,7 +622,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { await Promise.all( xdebugWatchpoints.map(async (breakpoint, index) => { try { - await this._connection.sendBreakpointSetCommand(breakpoint); + await this._connection!.sendBreakpointSetCommand(breakpoint); vscodeWatchpoints[index] = { verified: true, instructionReference: breakpoint.variable }; } catch (error) { vscodeWatchpoints[index] = { @@ -651,7 +650,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { // runtime supports now threads so just return a default thread. response.body = { - threads: [new Thread(this._connection.id, `Thread ${this._connection.id}]`)], + threads: [new Thread(this._connection!.id, `Thread ${this._connection!.id}]`)], }; this.sendResponse(response); } @@ -661,16 +660,16 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.StackTraceArguments ): Promise { try { - const stack = await this._connection.sendStackGetCommand(); + const stack = await this._connection!.sendStackGetCommand(); /** Is set to true if we're at the CSP or unit test ending watchpoint. * We need to do this so VS Code doesn't try to open the source of * a stack frame before the debug session terminates. */ let noStack = false; const stackFrames = await Promise.all( - stack.stack.map(async (stackFrame: xdebug.StackFrame, index): Promise => { + stack.stack.map(async (stackFrame: xdebug.StackFrame, index): Promise => { if (noStack) return; // Stack frames won't be sent - const [, namespace, docName] = decodeURI(stackFrame.fileUri).match(/^dbgp:\/\/\|([^|]+)\|(.*)$/); + const [, namespace, docName] = decodeURI(stackFrame.fileUri).match(/^dbgp:\/\/\|([^|]+)\|(.*)$/)!; const fileUri = DocumentContentProvider.getUri( docName, this._workspace, @@ -678,7 +677,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { undefined, this._workspaceFolderUri ); - const source = new Source(docName, fileUri.toString()); + const source = new Source(docName, fileUri!.toString()); let line = stackFrame.line + 1; const place = `${stackFrame.method}+${stackFrame.methodOffset}`; const stackFrameId = this._stackFrameIdCounter++; @@ -687,19 +686,19 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { const unitTest = this._isUnitTest && source.name.startsWith("%Api.Atelier.v"); if (csp || unitTest) { // Check if we're at our special watchpoint - const { result } = await this._connection.sendEvalCommand( + const { result } = await this._connection!.sendEvalCommand( csp ? this._cspWatchpointCondition : this._unitTestWatchpointCondition ); if (result.type == "int" && result.value == "1") { // Stop the debugging session - const xdebugResponse = await this._connection.sendDetachCommand(); + const xdebugResponse = await this._connection!.sendDetachCommand(); this._checkStatus(xdebugResponse); noStack = true; return; } } } - const fileText = await this._getFileText(fileUri); + const fileText = await this._getFileText(fileUri!); const hasCmdLoc = typeof stackFrame.cmdBeginLine == "number"; if (!fileText.length) { // Can't get the source for the document @@ -738,15 +737,15 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { return { id: stackFrameId, name: place, - source: noSource ? null : source, + source: (noSource ? null : source)!, line, - column: hasCmdLoc ? stackFrame.cmdBeginPos + 1 : 0, - endLine: hasCmdLoc ? stackFrame.cmdEndLine + lineDiff : undefined, + column: hasCmdLoc ? stackFrame.cmdBeginPos! + 1 : 0, + endLine: hasCmdLoc ? stackFrame.cmdEndLine! + lineDiff : undefined, endColumn: hasCmdLoc ? (stackFrame.cmdEndPos == 0 ? // A command that ends at position zero means "rest of this line" - fileText.split(/\r?\n/)[stackFrame.cmdEndLine + lineDiff - 1].length - : stackFrame.cmdEndPos) + 1 + fileText.split(/\r?\n/)[stackFrame.cmdEndLine! + lineDiff - 1]!.length + : stackFrame.cmdEndPos)! + 1 : undefined, }; }) @@ -755,7 +754,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { this._break = false; if (!noStack) { response.body = { - stackFrames, + stackFrames: stackFrames as StackFrame[], }; } this.sendResponse(response); @@ -805,10 +804,10 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { if (this._contexts.has(variablesReference)) { // VS Code is requesting the variables for a SCOPE, so we have to do a context_get const context = this._contexts.get(variablesReference); - properties = await context.getProperties(); + properties = await context!.getProperties(); } else if (this._properties.has(variablesReference)) { // VS Code is requesting the subelements for a variable, so we have to do a property_get - const property = this._properties.get(variablesReference); + const property = this._properties.get(variablesReference)!; if (property.hasChildren) { if (property.children.length === property.numberOfChildren) { properties = property.children; @@ -820,7 +819,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { } } else if (this._evalResultProperties.has(variablesReference)) { // the children of properties returned from an eval command are always inlined, so we simply resolve them - const property = this._evalResultProperties.get(variablesReference); + const property = this._evalResultProperties.get(variablesReference)!; properties = property.hasChildren ? property.children : []; } else { throw new Error("Unknown variable reference"); @@ -900,7 +899,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.ContinueArguments ): Promise { try { - const xdebugResponse = await this._connection.sendRunCommand(); + const xdebugResponse = await this._connection!.sendRunCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -910,7 +909,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { protected async nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): Promise { try { - const xdebugResponse = await this._connection.sendStepOverCommand(); + const xdebugResponse = await this._connection!.sendStepOverCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -923,7 +922,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.StepInArguments ): Promise { try { - const xdebugResponse = await this._connection.sendStepIntoCommand(); + const xdebugResponse = await this._connection!.sendStepIntoCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -936,7 +935,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.StepOutArguments ): Promise { try { - const xdebugResponse = await this._connection.sendStepOutCommand(); + const xdebugResponse = await this._connection!.sendStepOutCommand(); this.sendResponse(response); this._checkStatus(xdebugResponse); } catch (error) { @@ -949,7 +948,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { args: DebugProtocol.EvaluateArguments ): Promise { try { - const { result } = await this._connection.sendEvalCommand(args.expression); + const { result } = await this._connection!.sendEvalCommand(args.expression); if (result) { const displayValue = formatPropertyValue(result); let variablesReference: number; @@ -976,18 +975,18 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { ): Promise { try { const { value, name, variablesReference } = args; - let property = null; + let property: xdebug.Property | null | undefined = null; if (this._contexts.has(variablesReference)) { // VS Code is requesting the variables for a SCOPE, so we have to do a context_get const context = this._contexts.get(variablesReference); - const properties = await context.getProperties(); + const properties = await context!.getProperties(); property = properties.find((el) => el.name === name); } else if (this._properties.has(variablesReference)) { // VS Code is requesting the subelements for a variable, so we have to do a property_get property = this._properties.get(variablesReference); } - property.value = value; - await this._connection.sendPropertySetCommand(property); + property!.value = value; + await this._connection!.sendPropertySetCommand(property!); response.body = { value: args.value, diff --git a/src/debug/utils.ts b/src/debug/utils.ts index 3ba3a3b3..54718ec3 100644 --- a/src/debug/utils.ts +++ b/src/debug/utils.ts @@ -15,7 +15,7 @@ export function formatPropertyValue(property: BaseProperty): string { } } else { // for null, uninitialized, resource, etc. show the type - displayValue = property.value || property.type === "string" ? property.value : property.type; + displayValue = property.value || property.type === "string" ? property.value! : property.type; if (property.type === "string") { displayValue = '"' + displayValue + '"'; } else if (property.type === "bool") { diff --git a/src/debug/xdebugConnection.ts b/src/debug/xdebugConnection.ts index bc513045..d09b121b 100644 --- a/src/debug/xdebugConnection.ts +++ b/src/debug/xdebugConnection.ts @@ -24,11 +24,11 @@ export class InitPacket { */ public constructor(document: XMLDocument, connection: Connection) { const documentElement = document.documentElement; - this.fileUri = documentElement.getAttribute("fileuri"); - this.language = documentElement.getAttribute("language"); - this.protocolVersion = documentElement.getAttribute("protocol_version"); - this.ideKey = documentElement.getAttribute("idekey"); - this.engineVersion = documentElement.getElementsByTagName("engine")[0].getAttribute("version"); + this.fileUri = documentElement.getAttribute("fileuri")!; + this.language = documentElement.getAttribute("language")!; + this.protocolVersion = documentElement.getAttribute("protocol_version")!; + this.ideKey = documentElement.getAttribute("idekey")!; + this.engineVersion = documentElement.getElementsByTagName("engine")[0].getAttribute("version")!; this.connection = connection; } } @@ -62,12 +62,12 @@ export class Response { const documentElement = document.documentElement; if (documentElement.firstChild && documentElement.firstChild.nodeName === "error") { const errorNode = documentElement.firstChild as Element; - const code = parseInt(errorNode.getAttribute("code"), 10); - const message = errorNode.textContent; + const code = parseInt(errorNode.getAttribute("code")!, 10); + const message = errorNode.textContent!; throw new XDebugError(message, code); } - this.transactionId = parseInt(documentElement.getAttribute("transaction_id"), 10); - this.command = documentElement.getAttribute("command"); + this.transactionId = parseInt(documentElement.getAttribute("transaction_id")!, 10); + this.command = documentElement.getAttribute("command")!; this.connection = connection; } } @@ -91,24 +91,24 @@ export class StatusResponse extends Response { public constructor(document: XMLDocument, connection: Connection) { super(document, connection); const documentElement = document.documentElement; - this.status = documentElement.getAttribute("status"); - this.reason = documentElement.getAttribute("reason"); + this.status = documentElement.getAttribute("status")!; + this.reason = documentElement.getAttribute("reason")!; if (documentElement.hasChildNodes()) { const messageNode = documentElement.firstChild as Element; if (messageNode.hasAttribute("exception")) { this.exception = { - message: messageNode.textContent, - name: messageNode.getAttribute("exception"), + message: messageNode.textContent!, + name: messageNode.getAttribute("exception")!, }; if (messageNode.hasAttribute("code")) { - this.exception.code = parseInt(messageNode.getAttribute("code"), 10); + this.exception.code = parseInt(messageNode.getAttribute("code")!, 10); } } if (messageNode.hasAttribute("filename")) { - this.fileUri = messageNode.getAttribute("filename"); + this.fileUri = messageNode.getAttribute("filename")!; } if (messageNode.hasAttribute("lineno")) { - this.line = parseInt(messageNode.getAttribute("lineno"), 10); + this.line = parseInt(messageNode.getAttribute("lineno")!, 10); } } } @@ -121,7 +121,7 @@ export type BreakpointState = "enabled" | "disabled"; export abstract class Breakpoint { /** dynamically detects the type of breakpoint and returns the appropiate object */ public static fromXml(breakpointNode: Element, connection: Connection): Breakpoint { - switch (breakpointNode.getAttribute("type")) { + switch (breakpointNode.getAttribute("type")!) { case "line": return new LineBreakpoint(breakpointNode, connection); case "conditional": @@ -129,7 +129,7 @@ export abstract class Breakpoint { case "watch": return new Watchpoint(breakpointNode, connection); default: - throw new Error(`Invalid type ${breakpointNode.getAttribute("type")}`); + throw new Error(`Invalid type ${breakpointNode.getAttribute("type")!}`); } } /** Unique ID which is used for modifying the breakpoint (only when received through breakpoint_list) */ @@ -151,9 +151,9 @@ export abstract class Breakpoint { // from XML const breakpointNode: Element = rest[0]; this.connection = rest[1]; - this.type = breakpointNode.getAttribute("type") as BreakpointType; - this.id = parseInt(breakpointNode.getAttribute("id"), 10); - this.state = breakpointNode.getAttribute("state") as BreakpointState; + this.type = breakpointNode.getAttribute("type")! as BreakpointType; + this.id = parseInt(breakpointNode.getAttribute("id")!, 10); + this.state = breakpointNode.getAttribute("state")! as BreakpointState; } else { this.type = rest[0]; if (rest[1] !== undefined) { @@ -183,8 +183,8 @@ export class LineBreakpoint extends Breakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.line = parseInt(breakpointNode.getAttribute("lineno"), 10); - this.fileUri = breakpointNode.getAttribute("filename"); + this.line = parseInt(breakpointNode.getAttribute("lineno")!, 10); + this.fileUri = breakpointNode.getAttribute("filename")!; } else { // construct from arguments super("line", rest[2]); @@ -205,8 +205,8 @@ export class ClassLineBreakpoint extends LineBreakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.line = parseInt(breakpointNode.getAttribute("lineno"), 10); - this.fileUri = breakpointNode.getAttribute("filename"); + this.line = parseInt(breakpointNode.getAttribute("lineno")!, 10); + this.fileUri = breakpointNode.getAttribute("filename")!; } else { super(rest[0], rest[1], rest[4]); this.method = rest[2]; @@ -226,8 +226,8 @@ export class RoutineLineBreakpoint extends LineBreakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.line = parseInt(breakpointNode.getAttribute("lineno"), 10); - this.fileUri = breakpointNode.getAttribute("filename"); + this.line = parseInt(breakpointNode.getAttribute("lineno")!, 10); + this.fileUri = breakpointNode.getAttribute("filename")!; } else { super(rest[0], rest[1], rest[4]); this.method = rest[2]; @@ -254,7 +254,7 @@ export class ConditionalBreakpoint extends Breakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded? + this.expression = breakpointNode.getAttribute("expression")!; // Base64 encoded? } else { // from arguments super("conditional", rest[3]); @@ -283,7 +283,7 @@ export class ClassConditionalBreakpoint extends ConditionalBreakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded? + this.expression = breakpointNode.getAttribute("expression")!; // Base64 encoded? } else { super(rest[0], rest[1], rest[2], rest[5]); this.method = rest[3]; @@ -310,7 +310,7 @@ export class RoutineConditionalBreakpoint extends ConditionalBreakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - this.expression = breakpointNode.getAttribute("expression"); // Base64 encoded? + this.expression = breakpointNode.getAttribute("expression")!; // Base64 encoded? } else { super(rest[0], rest[1], rest[2], rest[5]); this.method = rest[3]; @@ -335,7 +335,7 @@ export class Watchpoint extends Breakpoint { const breakpointNode: Element = rest[0]; const connection: Connection = rest[1]; super(breakpointNode, connection); - const expr = breakpointNode.getAttribute("expression"); // Base64 encoded? + const expr = breakpointNode.getAttribute("expression")!; // Base64 encoded? if (expr.includes("|")) { this.variable = expr.slice(0, expr.indexOf("|")); this.expression = expr.slice(expr.indexOf("|") + 1); @@ -356,7 +356,7 @@ export class BreakpointSetResponse extends Response { public breakpointId: number; public constructor(document: XMLDocument, connection: Connection) { super(document, connection); - this.breakpointId = parseInt(document.documentElement.getAttribute("id"), 10); + this.breakpointId = parseInt(document.documentElement.getAttribute("id")!, 10); } } @@ -405,14 +405,14 @@ export class StackFrame { * @param {Connection} connection */ public constructor(stackFrameNode: Element, connection: Connection) { - this.method = iconv.encode(stackFrameNode.getAttribute("method"), ENCODING) + ""; - this.fileUri = iconv.encode(stackFrameNode.getAttribute("filename"), ENCODING) + ""; - this.type = stackFrameNode.getAttribute("type"); - this.line = parseInt(stackFrameNode.getAttribute("lineno"), 10); - this.methodOffset = parseInt(stackFrameNode.getAttribute("methodoffset"), 10); - this.level = parseInt(stackFrameNode.getAttribute("level"), 10); - const cmdBegin = stackFrameNode.getAttribute("cmdbegin"); - const cmdEnd = stackFrameNode.getAttribute("cmdend"); + this.method = iconv.encode(stackFrameNode.getAttribute("method")!, ENCODING) + ""; + this.fileUri = iconv.encode(stackFrameNode.getAttribute("filename")!, ENCODING) + ""; + this.type = stackFrameNode.getAttribute("type")!; + this.line = parseInt(stackFrameNode.getAttribute("lineno")!, 10); + this.methodOffset = parseInt(stackFrameNode.getAttribute("methodoffset")!, 10); + this.level = parseInt(stackFrameNode.getAttribute("level")!, 10); + const cmdBegin = stackFrameNode.getAttribute("cmdbegin")!; + const cmdEnd = stackFrameNode.getAttribute("cmdend")!; if (cmdBegin && cmdEnd) { const [cmdBeginLine, cmdBeginPos] = cmdBegin.split(":"); const [cmdEndLine, cmdEndPos] = cmdEnd.split(":"); @@ -449,7 +449,7 @@ export class SourceResponse extends Response { public source: string; public constructor(document: XMLDocument, connection: Connection) { super(document, connection); - this.source = Buffer.from(document.documentElement.textContent, "base64").toString(); + this.source = Buffer.from(document.documentElement.textContent!, "base64").toString(); } } @@ -466,8 +466,8 @@ export class Context { * @param {StackFrame} stackFrame */ public constructor(contextNode: Element, stackFrame: StackFrame) { - this.id = parseInt(contextNode.getAttribute("id"), 10); - this.name = contextNode.getAttribute("name"); + this.id = parseInt(contextNode.getAttribute("id")!, 10); + this.name = contextNode.getAttribute("name")!; this.stackFrame = stackFrame; } /** @@ -511,27 +511,27 @@ export abstract class BaseProperty { /** the number of children this property has, if any. Useful for showing array length. */ public numberOfChildren: number; /** the value of the property for primitive types */ - public value: string; + public value: string | undefined; /** children that were already included in the response */ public children: BaseProperty[]; public constructor(propertyNode: Element) { if (propertyNode.hasAttribute("name")) { - this.name = iconv.encode(propertyNode.getAttribute("name"), ENCODING) + ""; + this.name = iconv.encode(propertyNode.getAttribute("name")!, ENCODING) + ""; } - this.type = propertyNode.getAttribute("type"); + this.type = propertyNode.getAttribute("type")!; if (propertyNode.hasAttribute("classname")) { - this.class = propertyNode.getAttribute("classname"); + this.class = propertyNode.getAttribute("classname")!; } - this.hasChildren = !!parseInt(propertyNode.getAttribute("children"), 10); + this.hasChildren = !!parseInt(propertyNode.getAttribute("children")!, 10); if (this.hasChildren) { - this.numberOfChildren = parseInt(propertyNode.getAttribute("numchildren"), 10); + this.numberOfChildren = parseInt(propertyNode.getAttribute("numchildren")!, 10); } else { - const encoding = propertyNode.getAttribute("encoding"); + const encoding = propertyNode.getAttribute("encoding")!; if (encoding && encoding !== "none") { - this.value = iconv.encode(propertyNode.textContent, encoding) + ""; + this.value = iconv.encode(propertyNode.textContent!, encoding) + ""; } else { - this.value = iconv.encode(propertyNode.textContent, ENCODING) + ""; + this.value = iconv.encode(propertyNode.textContent!, ENCODING) + ""; } } if (this.value === "") { @@ -559,7 +559,7 @@ export class Property extends BaseProperty { */ public constructor(propertyNode: Element, context: Context) { super(propertyNode); - this.fullName = propertyNode.getAttribute("fullname"); + this.fullName = propertyNode.getAttribute("fullname")!; this.context = context; if (this.hasChildren) { this.children = Array.from(propertyNode.childNodes).map( @@ -605,7 +605,7 @@ export class PropertyGetResponse extends Response { */ public constructor(document: XMLDocument, property: Property) { super(document, property.context.stackFrame.connection); - this.children = Array.from(document.documentElement.firstChild.childNodes).map( + this.children = Array.from(document.documentElement.firstChild!.childNodes).map( (propertyNode: Element): Property => new Property(propertyNode, property.context) ); } @@ -662,7 +662,7 @@ export class FeatureSetResponse extends Response { public feature: string; public constructor(document: XMLDocument, connection: Connection) { super(document, connection); - this.feature = document.documentElement.getAttribute("feature"); + this.feature = document.documentElement.getAttribute("feature")!; } } @@ -673,8 +673,8 @@ export class FeatureGetResponse extends Response { public supported: boolean; public constructor(document: XMLDocument, connection: Connection) { super(document, connection); - this.feature = document.documentElement.getAttribute("feature"); - this.supported = document.documentElement.getAttribute("supported") === "1"; + this.feature = document.documentElement.getAttribute("feature")!; + this.supported = document.documentElement.getAttribute("supported")! === "1"; } } @@ -754,20 +754,20 @@ export class Connection extends DbgpConnection { if (response.documentElement.nodeName === "init") { this._initPromiseResolveFn(new InitPacket(response, this)); } else { - const transactionId = parseInt(response.documentElement.getAttribute("transaction_id"), 10); + const transactionId = parseInt(response.documentElement.getAttribute("transaction_id")!, 10); if (this._pendingCommands.has(transactionId)) { - const command = this._pendingCommands.get(transactionId); + const command = this._pendingCommands.get(transactionId)!; this._pendingCommands.delete(transactionId); this._pendingExecuteCommand = false; command.resolveFn(response); } if (this._commandQueue.length > 0) { - const command = this._commandQueue.shift(); + const command = this._commandQueue.shift()!; this._executeCommand(command).catch(command.rejectFn); } if (response.documentElement.nodeName === "stream") { - const type = response.documentElement.getAttribute("type"); - const data = Buffer.from(response.documentElement.textContent, "base64").toString(); + const type = response.documentElement.getAttribute("type")!; + const data = Buffer.from(response.documentElement.textContent!, "base64").toString(); this.sendEvent(type, data); } } @@ -976,7 +976,7 @@ export class Connection extends DbgpConnection { /** Sends a property_get command */ public async sendPropertySetCommand(property: Property): Promise { - const value = Buffer.from(property.value).toString("base64"); + const value = Buffer.from(property.value!).toString("base64"); return new PropertySetResponse( await this._enqueueCommand( "property_set", diff --git a/src/explorer/explorer.ts b/src/explorer/explorer.ts index 22efd95c..5a905f51 100644 --- a/src/explorer/explorer.ts +++ b/src/explorer/explorer.ts @@ -75,7 +75,7 @@ export function registerExplorerOpen(): vscode.Disposable { } // Remove the item from the project let prjFileName = fullName.startsWith("/") ? fullName.slice(1) : fullName; - const ext = prjFileName.split(".").pop().toLowerCase(); + const ext = prjFileName.split(".").pop()!.toLowerCase(); prjFileName = ext == "cls" ? prjFileName.slice(0, -4) : prjFileName; const prjType = prjFileName.includes("/") ? "CSP" @@ -135,13 +135,13 @@ function wasDoubleClick(uri: vscode.Uri): boolean { export class ObjectScriptExplorerProvider implements vscode.TreeDataProvider { public onDidChange?: vscode.Event; - public onDidChangeTreeData: vscode.Event; + public onDidChangeTreeData: vscode.Event; - private _onDidChangeTreeData: vscode.EventEmitter; + private _onDidChangeTreeData: vscode.EventEmitter; private _showExtraForWorkspace: { [key: string]: string[] }[] = []; public constructor() { - this._onDidChangeTreeData = new vscode.EventEmitter(); + this._onDidChangeTreeData = new vscode.EventEmitter(); this.onDidChangeTreeData = this._onDidChangeTreeData.event; } @@ -173,7 +173,7 @@ export class ObjectScriptExplorerProvider implements vscode.TreeDataProvider= 0) { diff --git a/src/explorer/nodes.ts b/src/explorer/nodes.ts index 05a77fcb..9bb64ecb 100644 --- a/src/explorer/nodes.ts +++ b/src/explorer/nodes.ts @@ -15,7 +15,7 @@ interface NodeOptions { /** Get the URI for this leaf node */ function getLeafNodeUri(node: NodeBase): vscode.Uri { - return DocumentContentProvider.getUri(node.fullName, undefined, node?.namespace, false, node.wsFolder.uri, true); + return DocumentContentProvider.getUri(node.fullName, undefined, node?.namespace, false, node.wsFolder.uri, true)!; } const inactiveMsg = "Server connection is inactive"; @@ -61,7 +61,7 @@ export class RootNode extends NodeBase { public readonly contextValue: string; private readonly _category: string; private readonly isCsp: boolean; - private readonly iconPath: vscode.ThemeIcon; + private readonly iconPath: vscode.ThemeIcon | undefined; public constructor( label: string, @@ -97,7 +97,7 @@ export class RootNode extends NodeBase { const path = this instanceof PackageNode || this.isCsp ? this.fullName + "/" : ""; return this.getList(path, this._category, false) .then((data) => - data + data! .filter((el) => { if (this._category === "OTH") { return el.Type === "100"; @@ -148,7 +148,7 @@ export class RootNode extends NodeBase { path: string, category: string, flat: boolean - ): Promise<{ Name: string; Type: string; fullName: string }[]> { + ): Promise<{ Name: string; Type: string; fullName: string }[] | undefined> { const sql = "SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?)"; let spec = ""; switch (category) { @@ -192,9 +192,9 @@ export class RootNode extends NodeBase { let nsCspApps: string[] | undefined = cspApps.get(cspAppsKey); if (nsCspApps == undefined) { nsCspApps = await api.getCSPApps().then((data) => data.result.content || []); - cspApps.set(cspAppsKey, nsCspApps); + cspApps.set(cspAppsKey, nsCspApps!); } - return nsCspApps.map((cspApp) => { + return nsCspApps!.map((cspApp) => { return { Name: cspApp.slice(1), fullName: cspApp.slice(1), Type: "10" }; }); } else { @@ -226,7 +226,7 @@ export class RootNode extends NodeBase { public getItemsForExport(): Promise { const path = this instanceof PackageNode || this.isCsp ? this.fullName + "/" : ""; const cat = this.isCsp ? "CSP" : "ALL"; - return this.getList(path, cat, true).then((data) => data.map((el) => el.Name)); + return this.getList(path, cat, true).then((data) => data!.map((el) => el.Name)); } } @@ -324,9 +324,9 @@ export class RoutineNode extends NodeBase { } export class WorkspaceNode extends NodeBase { - public eventEmitter: vscode.EventEmitter; + public eventEmitter: vscode.EventEmitter; public uniqueId: string; - public constructor(label: string, eventEmitter: vscode.EventEmitter, options: NodeOptions) { + public constructor(label: string, eventEmitter: vscode.EventEmitter, options: NodeOptions) { super(label, label, options); this.uniqueId = `serverNode:${this.namespace}:${this.namespace ? ":extra:" : ""}`; this.options.generated = workspaceState.get(`ExplorerGenerated:${this.uniqueId}`); @@ -335,7 +335,7 @@ export class WorkspaceNode extends NodeBase { } public getTreeItem(): vscode.TreeItem { - const flags = []; + const flags: string[] = []; this.options.generated && flags.push(":generated:"); this.options.system && flags.push(":system:"); const api = new AtelierAPI(this.wsFolder.uri); @@ -356,7 +356,7 @@ export class WorkspaceNode extends NodeBase { public async getChildren(_element: NodeBase): Promise { if (!new AtelierAPI(this.wsFolder.uri).active) return [new InactiveNode("", "", { wsFolder: this.wsFolder })]; - const children = []; + const children: RootNode[] = []; let node: RootNode; node = new RootNode( @@ -426,7 +426,7 @@ export class ProjectNode extends NodeBase { } public async getChildren(_element: NodeBase): Promise { - const children = []; + const children: ProjectRootNode[] = []; let node: ProjectRootNode; // Technically a project is a "document", so tell the server that we're opening it @@ -523,9 +523,9 @@ export class ProjectRootNode extends RootNode { "SELECT DISTINCT $PIECE(SUBSTR(sod.Name,?+1),'/') AS Name FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1) AS sod " + "JOIN %Studio.Project_ProjectItemsList(?,1) AS pil ON SUBSTR(sod.Name,2) %STARTSWITH ? AND (" + "(pil.Type = 'DIR' AND SUBSTR(sod.Name,2) %STARTSWITH pil.Name||'/') OR (pil.Type = 'CSP' AND SUBSTR(sod.Name,2) = pil.Name))"; - parameters = [l, this.options.project, this.fullName + "/"]; + parameters = [l, this.options.project!, this.fullName + "/"]; } else { - parameters = [l, l, l, this.options.project, this.fullName + "."]; + parameters = [l, l, l, this.options.project!, this.fullName + "."]; if (this.category == "CLS") { query = "SELECT DISTINCT CASE " + @@ -556,7 +556,7 @@ export class ProjectRootNode extends RootNode { "WHEN Type = 'CSP' OR Type = 'DIR' THEN $PIECE(Name,'/') " + "WHEN (Type != 'CSP' AND Type != 'DIR' AND $LENGTH(Name,'.') > 2) OR Type = 'CLS' OR Type = 'PKG' THEN $PIECE(Name,'.') " + "ELSE Name END Name FROM %Studio.Project_ProjectItemsList(?,1) WHERE "; - parameters = [this.options.project]; + parameters = [this.options.project!]; if (this.category == "CLS") { query += "Type = 'PKG' OR Type = 'CLS'"; } else if (this.category == "RTN") { @@ -594,7 +594,7 @@ export class ProjectRootNode extends RootNode { } } else { if (entry.includes(".")) { - if (["mac", "int", "inc"].includes(entry.split(".").pop().toLowerCase())) { + if (["mac", "int", "inc"].includes(entry.split(".").pop()!.toLowerCase())) { return new RoutineNode(entry, fullName, this.options); } else { return new ClassNode(entry, fullName, this.options); @@ -613,11 +613,11 @@ export class ProjectRootNode extends RootNode { } export class ProjectsServerNsNode extends NodeBase { - public eventEmitter: vscode.EventEmitter; + public eventEmitter: vscode.EventEmitter; public constructor( label: string, - eventEmitter: vscode.EventEmitter, + eventEmitter: vscode.EventEmitter, wsFolder: vscode.WorkspaceFolder, namespace?: string ) { diff --git a/src/explorer/projectsExplorer.ts b/src/explorer/projectsExplorer.ts index 1dcfec2e..b6c421e1 100644 --- a/src/explorer/projectsExplorer.ts +++ b/src/explorer/projectsExplorer.ts @@ -4,8 +4,8 @@ import { handleError, notIsfs, notNull } from "../utils"; import { NodeBase, ProjectsServerNsNode } from "./nodes"; export class ProjectsExplorerProvider implements vscode.TreeDataProvider { - public onDidChangeTreeData: vscode.Event; - private _onDidChangeTreeData: vscode.EventEmitter; + public onDidChangeTreeData: vscode.Event; + private _onDidChangeTreeData: vscode.EventEmitter; /** Connection info for all workspace folder roots */ private readonly _roots: string[] = []; @@ -13,7 +13,7 @@ export class ProjectsExplorerProvider implements vscode.TreeDataProvider(); + this._onDidChangeTreeData = new vscode.EventEmitter(); this.onDidChangeTreeData = this._onDidChangeTreeData.event; } @@ -60,7 +60,7 @@ export class ProjectsExplorerProvider implements vscode.TreeDataProvider(); -type ConnConfig = Pick & { +type ConnConfig = Pick & { "docker-compose"?: any; server?: any; links?: any; + username?: string; + password?: string; }; export function config(setting: "conn", workspaceFolderName?: string): ConnConfig; @@ -175,7 +178,7 @@ export function config(setting?: string, workspaceFolderName?: string): any { } let prefix: string; const workspaceFolder = vscode.workspace.workspaceFolders?.find( - (el) => el.name.toLowerCase() === workspaceFolderName.toLowerCase() + (el) => el.name.toLowerCase() === workspaceFolderName!.toLowerCase() ); if (setting && setting.startsWith("intersystems")) { return vscode.workspace.getConfiguration(setting, workspaceFolder); @@ -183,22 +186,21 @@ export function config(setting?: string, workspaceFolderName?: string): any { prefix = "objectscript"; } - if (["conn", "export"].includes(setting)) { + if (["conn", "export"].includes(setting!)) { if (workspaceFolderName && workspaceFolderName !== "") { if (workspaceFolderName.match(/.+:\d+$/)) { const { port, hostname: host, auth, query } = url.parse("http://" + workspaceFolderName, true); const { ns = "USER", https = false } = query; const [username, password] = (auth || "_SYSTEM:SYS").split(":"); - const authorization = serverManagerApi.defaultAuth(); - authorization.resolve({ username, accessToken: password }); if (setting == "conn") { return { active: true, https, ns, host, - port: +port, - auth: authorization, + port: +port!, + username, + password, } as ConnConfig; } else if (setting == "export") { return {}; @@ -210,7 +212,7 @@ export function config(setting?: string, workspaceFolderName?: string): any { return setting && setting.length ? result.get(setting) : result; } -let reporter: TelemetryReporter; +let reporter: TelemetryReporter | undefined; export let checkingConnection = false; @@ -247,8 +249,16 @@ export async function resolveConnectionSpec( return; } } - - let connSpec = await serverManagerApi.getServerSpec(serverName, scope); + const rawConnSpec = await serverManagerApi.getServerSpec(serverName, scope); + let connSpec; + if (rawConnSpec) { + connSpec = { + ...rawConnSpec, + // Some old server managers does not set name as the types suggest. + name: rawConnSpec.name ?? serverName, + auth: rawConnSpec.auth ?? new BasicAuthorization(rawConnSpec.username, rawConnSpec.password), + }; + } if (!connSpec && uri) { // Caller passed uri as a signal to process any docker-compose settings @@ -265,10 +275,10 @@ export async function resolveConnectionSpec( pathPrefix: serverForUri.pathPrefix, }, superServer: { - port: serverForUri.superserverPort, + port: serverForUri.superserverPort!, }, description: `Server for workspace folder '${serverName}'`, - auth: serverManagerApi.defaultAuth(), + auth: new BasicAuthorization(), }; } } @@ -277,7 +287,7 @@ export async function resolveConnectionSpec( if (connSpec) { const accessToken = await resolvePassword(connSpec); if (connSpec.auth.resolve({ accessToken })) { - resolvedConnSpecs.set(serverName, connSpec); + resolvedConnSpecs.set(serverName, connSpec as ResolvedConnSpec); } } } @@ -286,7 +296,7 @@ async function resolvePassword( serverSpec: serverManager.IServerSpec, ignoreUnauthenticated = false ): Promise { - if (!(serverSpec.auth.resolved() as boolean) || ignoreUnauthenticated) { + if (!serverSpec.auth?.resolved() || ignoreUnauthenticated) { const scopes = [serverSpec.name, serverSpec.auth?.username || ""]; // Handle Server Manager extension version < 3.8.0 @@ -329,14 +339,17 @@ export async function resolveUsernameAndPassword( } /** Accessor for the cache of resolved connection specs */ -export function getResolvedConnectionSpec(key: string, dflt: ResolvedConnSpec): ResolvedConnSpec { +export function getResolvedConnectionSpec( + key: string, + dflt: ResolvedConnSpec | undefined +): ResolvedConnSpec | undefined { let spec = resolvedConnSpecs.get(key); if (spec) { return spec; } // Try a case-insensitive match - key = resolvedConnSpecs.keys().find((oneKey) => oneKey.toLowerCase() === key.toLowerCase()); + key = resolvedConnSpecs.keys().find((oneKey) => oneKey.toLowerCase() === key.toLowerCase())!; if (key) { spec = resolvedConnSpecs.get(key); if (spec) { @@ -496,7 +509,7 @@ export async function checkConnection( api.config.serverName, vscode.workspace.getConfiguration("intersystems.servers", uri).get(api.config.serverName) ); - const newSpec = await resolveUsernameAndPassword(api.config.serverName, oldSpec); + const newSpec = await resolveUsernameAndPassword(api.config.serverName, oldSpec!); if (newSpec) { // We were able to resolve credentials, so try again await workspaceState.update(wsKey + ":password", newSpec.auth?.accessToken); @@ -597,15 +610,15 @@ export async function checkConnection( */ function setConnectionState(configName: string, active: boolean) { const connConfig: vscode.WorkspaceConfiguration = config("", configName); - const target: vscode.ConfigurationTarget = connConfig.inspect("conn").workspaceFolderValue + const target: vscode.ConfigurationTarget = connConfig.inspect("conn")!.workspaceFolderValue ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; const targetConfig: any = - connConfig.inspect("conn").workspaceFolderValue || connConfig.inspect("conn").workspaceValue; + connConfig.inspect("conn")!.workspaceFolderValue || connConfig.inspect("conn")!.workspaceValue; return connConfig.update("conn", { ...targetConfig, active }, target); } -function languageServer(install = true): vscode.Extension { +function languageServer(install = true): vscode.Extension | undefined { let extension = vscode.extensions.getExtension(lsExtensionId); async function languageServerInstall() { @@ -681,7 +694,7 @@ function proposedApiPrompt(active: boolean, added?: readonly vscode.WorkspaceFol const systemModes: Map = new Map(); /** Output a message notifying the user of the SystemMode of any servers they are connected to. */ -async function systemModeWarning(wsFolders: readonly vscode.WorkspaceFolder[]): Promise { +async function systemModeWarning(wsFolders: readonly vscode.WorkspaceFolder[] | undefined): Promise { if (!wsFolders || wsFolders.length == 0) return; for (const wsFolder of wsFolders) { const api = new AtelierAPI(wsFolder.uri), @@ -714,7 +727,7 @@ async function systemModeWarning(wsFolders: readonly vscode.WorkspaceFolder[]): ); outputChannel.show(true); } - systemModes.set(mapKey, systemMode); + systemModes.set(mapKey, systemMode!); } } @@ -723,7 +736,7 @@ async function systemModeWarning(wsFolders: readonly vscode.WorkspaceFolder[]): * that are showing the contents of a server-side project. * This must be done because technically a project is a "document". */ -async function fireOpenProjectUserAction(wsFolders: readonly vscode.WorkspaceFolder[]): Promise { +async function fireOpenProjectUserAction(wsFolders: readonly vscode.WorkspaceFolder[] | undefined): Promise { if (!wsFolders || wsFolders.length == 0) return; for (const wsFolder of wsFolders) { if (notIsfs(wsFolder.uri)) return; @@ -759,7 +772,7 @@ function setExplorerContextKeys(): void { } /** Cache the lists of web apps and abstract document types for all server-namespaces in `wsFolders` */ -async function updateWebAndAbstractDocsCaches(wsFolders: readonly vscode.WorkspaceFolder[]): Promise { +async function updateWebAndAbstractDocsCaches(wsFolders: readonly vscode.WorkspaceFolder[] | undefined): Promise { if (!wsFolders?.length) return; const keys: Set = new Set(); const connections: { key: string; api: AtelierAPI }[] = []; @@ -804,14 +817,14 @@ export function sendStudioAddinTelemetryEvent(addInName: string): void { } /** Send a telemetry event with details of each folder in `wsFolders` */ -function sendWsFolderTelemetryEvent(wsFolders: readonly vscode.WorkspaceFolder[], added = false): void { +function sendWsFolderTelemetryEvent(wsFolders: readonly vscode.WorkspaceFolder[] | undefined, added = false): void { if (!reporter || !wsFolders?.length) return; wsFolders.forEach((wsFolder) => { const api = new AtelierAPI(wsFolder.uri); const { csp, project, ns } = isfsConfig(wsFolder.uri); const serverSide = filesystemSchemas.includes(wsFolder.uri.scheme); const conf = vscode.workspace.getConfiguration("objectscript", wsFolder); - reporter.sendTelemetryEvent("workspaceFolder", { + reporter!.sendTelemetryEvent("workspaceFolder", { scheme: wsFolder.uri.scheme, added: String(added), isWeb: serverSide ? String(csp) : undefined, @@ -884,7 +897,7 @@ export async function activate(context: vscode.ExtensionContext): Promise - ["file", ...schemas].reduce((acc, scheme) => acc.concat(list.map((language) => ({ scheme, language }))), []); + ["file", ...schemas].reduce( + (acc, scheme) => acc.concat(list.map((language) => ({ scheme, language }))), + [] as { scheme: string; language: any }[] + ); const diagnosticProvider = new ObjectScriptDiagnosticProvider(); @@ -1070,14 +1086,14 @@ export async function activate(context: vscode.ExtensionContext): Promise { - if (vscode.workspace.workspaceFolders?.length > 1) { + if (vscode.workspace.workspaceFolders!.length > 1) { const workspaceFolder = currentWorkspaceFolder(); if (workspaceFolder && workspaceFolder != workspaceState.get("workspaceFolder")) { await workspaceState.update("workspaceFolder", workspaceFolder); @@ -1214,7 +1230,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { sendCommandTelemetryEvent("pickProcess"); const system = config.system; - let connectionUri = vscode.window.activeTextEditor?.document.uri; + let connectionUri: vscode.Uri | null | undefined = vscode.window.activeTextEditor?.document.uri; if (connectionUri) { // Ignore active editor if its document is outside the workspace (e.g. user settings.json) connectionUri = vscode.workspace.getWorkspaceFolder(connectionUri)?.uri; @@ -1279,8 +1295,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { if (value) { - const workspaceFolderIndex = vscode.workspace.workspaceFolders.findIndex( - (folder) => folder.uri.toString() === connectionUri.toString() + const workspaceFolderIndex = vscode.workspace.workspaceFolders!.findIndex( + (folder) => folder.uri.toString() === connectionUri!.toString() ); return workspaceFolderIndex < 0 ? value.label : `${value.label}@${workspaceFolderIndex}`; } @@ -1368,7 +1384,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { sendCommandTelemetryEvent("previewXml"); - previewXMLAsUDL(vscode.window.activeTextEditor); + previewXMLAsUDL(vscode.window.activeTextEditor!); }), vscode.commands.registerCommand("vscode-objectscript.addServerNamespaceToWorkspace", (resource?: vscode.Uri) => { sendCommandTelemetryEvent("addServerNamespaceToWorkspace"); @@ -1445,7 +1461,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { @@ -1926,7 +1942,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { // Log out of all CSP sessions await logoutOfSessions(); } + +// A copy of the BasicAuthorization class from ServerManager +// We use it to patch older version of getServerSpec. +export default class BasicAuthorization implements Authorization { + #username?: string; + #password?: string; + constructor(username?: string, password?: string) { + this.#username = username; + this.#password = password; + } + + public get username(): string { + return this.#username || ""; + } + + public get password(): string | undefined { + return this.#password; + } + + public get accessToken(): string | undefined { + return this.#password; + } + + public get httpAuthorizationHeader(): string { + return `Basic ${Buffer.from(`${this.#username}:${this.#password}`).toString("base64")}`; + } + + public resolved(): this is ResolvedAuthorization { + return this.username !== "" && this.#password !== undefined; + } + + public resolve(param: { accessToken: string; username?: string }): this is ResolvedAuthorization { + this.#username = param.username ?? this.#username; + this.#password = param.accessToken ?? this.#password; + return this.resolved(); + } + + public clear(): asserts this is Authorization { + this.#password = undefined; + } + + public get credentials(): { auth: { username: string; password: string }; headers?: Record } { + return { + auth: { + username: this.username, + password: this.password!, + }, + headers: {}, + }; + } + + public clone(): BasicAuthorization { + return new BasicAuthorization(this.#username, this.#password); + } +} diff --git a/src/providers/DocumentContentProvider.ts b/src/providers/DocumentContentProvider.ts index 6e314b2a..edf9d8f1 100644 --- a/src/providers/DocumentContentProvider.ts +++ b/src/providers/DocumentContentProvider.ts @@ -45,9 +45,9 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid } /** Returns the `Uri` of `name` in `workspaceFolder` if it exists */ - private static findLocalUri(name: string, workspaceFolder: string): vscode.Uri { + private static findLocalUri(name: string, workspaceFolder: string | undefined): vscode.Uri | undefined { if (!workspaceFolder) return; - const wsFolder = vscode.workspace.workspaceFolders.find((wf) => wf.name == workspaceFolder); + const wsFolder = vscode.workspace.workspaceFolders!.find((wf) => wf.name == workspaceFolder); if (!wsFolder) return; if (!notIsfs(wsFolder.uri)) return; const conf = vscode.workspace.getConfiguration("objectscript.export", wsFolder); @@ -121,7 +121,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid vfs?: boolean, wFolderUri?: vscode.Uri, forceServerCopy = false - ): vscode.Uri { + ): vscode.Uri | null { let scheme = vfs ? FILESYSTEM_SCHEMA : OBJECTSCRIPT_FILE_SCHEMA; const isCsp = name.includes("/"); @@ -131,8 +131,8 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid wFolderUri = uriOfWorkspaceFolder(workspaceFolder); } else if (!workspaceFolder) { // Make sure workspaceFolder is set correctly if only wFolderUri was passed - workspaceFolder = vscode.workspace.workspaceFolders.find( - (wf) => wf.uri.toString() == wFolderUri.toString() + workspaceFolder = vscode.workspace.workspaceFolders!.find( + (wf) => wf.uri.toString() == wFolderUri!.toString() )?.name; } let uri: vscode.Uri; @@ -144,7 +144,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid namespace = ""; } const params = new URLSearchParams(wFolderUri.query); - const cspParam = params.has(IsfsUriParam.CSP) && ["", "1"].includes(params.get(IsfsUriParam.CSP)); + const cspParam = params.has(IsfsUriParam.CSP) && ["", "1"].includes(params.get(IsfsUriParam.CSP)!); const lastDot = name.lastIndexOf("."); let uriPath = isCsp ? name : name.slice(0, lastDot).replace(/\./g, "/") + "." + name.slice(lastDot + 1); if (!isCsp && /.\.G?[1-9]\.int$/i.test(name)) { @@ -202,7 +202,7 @@ export class DocumentContentProvider implements vscode.TextDocumentContentProvid const fileName = name .split(".") .slice(0, -1) - .join(fileExt.match(/cls/i) ? "/" : "."); + .join(fileExt!.match(/cls/i) ? "/" : "."); name = fileName + "." + fileExt; uri = vscode.Uri.file(name).with({ scheme: scheme, diff --git a/src/providers/DocumentFormattingEditProvider.ts b/src/providers/DocumentFormattingEditProvider.ts index 1f4b24ef..b100a50c 100644 --- a/src/providers/DocumentFormattingEditProvider.ts +++ b/src/providers/DocumentFormattingEditProvider.ts @@ -19,7 +19,7 @@ export class DocumentFormattingEditProvider implements vscode.DocumentFormatting } private commands(document: vscode.TextDocument, options: vscode.FormattingOptions): vscode.TextEdit[] { - const edits = []; + const edits: vscode.TextEdit[] = []; let indent = 1; const isClass = document.fileName.toLowerCase().endsWith(".cls"); @@ -166,8 +166,8 @@ export class DocumentFormattingEditProvider implements vscode.DocumentFormatting } // keep strings and comments - const keepList = []; - const restorePattern = []; + const keepList: string[] = []; + const restorePattern: string[] = []; const toKeep = (str) => { keepList.push(str); restorePattern.push(String.fromCharCode(keepList.length)); @@ -225,13 +225,13 @@ export class DocumentFormattingEditProvider implements vscode.DocumentFormatting } private functions(document: vscode.TextDocument, options: vscode.FormattingOptions): vscode.TextEdit[] { - const edits = []; + const edits: vscode.TextEdit[] = []; for (let i = 0; i < document.lineCount; i++) { const line = document.lineAt(i); const pattern = /(? { + public async resolveDocumentLink( + link: StudioLink, + token: vscode.CancellationToken + ): Promise { const editor = await vscode.window .showTextDocument(link.uri) .then(undefined, (error) => handleError(error, "Failed to resolve DocumentLink to a specific location.")); @@ -63,7 +66,7 @@ export class DocumentLinkProvider implements vscode.DocumentLinkProvider { // add the offset of the method if it is a class if (link.methodname) { - const symbols = await vscode.commands.executeCommand("vscode.executeDocumentSymbolProvider", link.uri); + const symbols: any = await vscode.commands.executeCommand("vscode.executeDocumentSymbolProvider", link.uri); const method = symbols[0].children.find( (info) => (info.detail === "ClassMethod" || info.detail === "Method") && info.name === link.methodname ); diff --git a/src/providers/FileSystemProvider/FileSearchProvider.ts b/src/providers/FileSystemProvider/FileSearchProvider.ts index 5f519c00..1936ebf5 100644 --- a/src/providers/FileSystemProvider/FileSearchProvider.ts +++ b/src/providers/FileSystemProvider/FileSearchProvider.ts @@ -9,7 +9,7 @@ export class FileSearchProvider implements vscode.FileSearchProvider { query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken - ): Promise { + ): Promise { let counter = 0; // Replace all back slashes with forward slashes let pattern = query.pattern.replace(/\\/g, "/"); @@ -28,18 +28,19 @@ export class FileSearchProvider implements vscode.FileSearchProvider { for (const c of pattern) regexStr += `${[".", "/"].includes(c) ? "[./]" : c}.*`; const patternRegex = new RegExp(regexStr, "i"); if (token.isCancellationRequested) return; - return projectContentsFromUri(options.folder, true).then((docs) => - docs - .map((doc: ProjectItem) => - !token.isCancellationRequested && - // The document matches the query - (!pattern.length || patternRegex.test(doc.Name)) && - // We haven't hit the max number of results - (!options.maxResults || ++counter <= options.maxResults) - ? DocumentContentProvider.getUri(doc.Name, "", "", true, options.folder) - : null - ) - .filter(notNull) + return projectContentsFromUri(options.folder, true).then( + (docs) => + docs! + .map((doc: ProjectItem) => + !token.isCancellationRequested && + // The document matches the query + (!pattern.length || patternRegex.test(doc.Name)) && + // We haven't hit the max number of results + (!options.maxResults || ++counter <= options.maxResults) + ? DocumentContentProvider.getUri(doc.Name, "", "", true, options.folder) + : null + ) + .filter(notNull) as vscode.Uri[] ); } // When this is called without a query.pattern every file is supposed to be returned, so do not provide a filter @@ -48,16 +49,17 @@ export class FileSearchProvider implements vscode.FileSearchProvider { ? `Name LIKE '${!csp ? likePattern.replace(/\//g, ".") : likePattern}' ESCAPE '\\'` : ""; if (token.isCancellationRequested) return; - return studioOpenDialogFromURI(options.folder, { flat: true, filter }).then((data) => - data.result.content - .map((doc: { Name: string; Type: number }) => - !token.isCancellationRequested && - // We haven't hit the max number of results - (!options.maxResults || ++counter <= options.maxResults) - ? DocumentContentProvider.getUri(doc.Name, "", "", true, options.folder) - : null - ) - .filter(notNull) + return studioOpenDialogFromURI(options.folder, { flat: true, filter })!.then( + (data) => + data.result.content + .map((doc: { Name: string; Type: number }) => + !token.isCancellationRequested && + // We haven't hit the max number of results + (!options.maxResults || ++counter <= options.maxResults) + ? DocumentContentProvider.getUri(doc.Name, "", "", true, options.folder) + : null + ) + .filter(notNull) as vscode.Uri[] ); } } diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index ab296899..0e1f0611 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -88,7 +88,7 @@ export function generateFileContent( } } - const fileExt = fileName.split(".").pop().toLowerCase(); + const fileExt = fileName.split(".").pop()!.toLowerCase(); const csp = fileName.startsWith("/"); if (fileExt === "cls" && !csp) { const className = fileName.split(".").slice(0, -1).join("."); @@ -104,12 +104,12 @@ export function generateFileContent( // Replace that with one to match fileName. while (sourceLines.length > 0) { const nextLine = sourceLines.shift(); - const classNameMatch = nextLine.match(classNameRegex); + const classNameMatch = nextLine!.match(classNameRegex); if (classNameMatch) { - content.push(...preamble, nextLine.replace(classNameMatch[1], fileName.slice(0, -4)), ...sourceLines); + content.push(...preamble, nextLine!.replace(classNameMatch[1], fileName.slice(0, -4)), ...sourceLines); break; } - preamble.push(nextLine); + preamble.push(nextLine!); } if (!content.length) { // Transfer sourceLines verbatim in cases where no class header line is found @@ -192,12 +192,12 @@ export function isCSP(uri: vscode.Uri): boolean { path: path.dirname(uri.path), }) .toString(); - if (cspFilesInProjectFolder.has(parent) && cspFilesInProjectFolder.get(parent).includes(path.basename(uri.path))) { + if (cspFilesInProjectFolder.has(parent) && cspFilesInProjectFolder.get(parent)!.includes(path.basename(uri.path))) { return true; } // Read the parent directory and file is not CSP OR haven't read the parent directory yet // Use the file extension to guess if it's a web app file - const additionalExts: string[] = vscode.workspace + const additionalExts: string[] | undefined = vscode.workspace .getConfiguration("objectscript.projects", uri) .get("webAppFileExtensions"); return [ @@ -220,8 +220,8 @@ export function isCSP(uri: vscode.Uri): boolean { "ico", "xml", "txt", - ...additionalExts, - ].includes(uri.path.split(".").pop().toLowerCase()); + ...additionalExts!, + ].includes(uri.path.split(".").pop()!.toLowerCase()); } return csp; } @@ -237,7 +237,7 @@ export function isfsDocumentName(uri: vscode.Uri, csp?: boolean, pkg = false): s if (csp == undefined) csp = isCSP(uri); const doc = csp ? uri.path : uri.path.slice(1).replace(/\//g, "."); // Add the .PKG extension to non-web folders if called from StudioActions - return pkg && !csp && !doc.split("/").pop().includes(".") ? `${doc}.PKG` : doc; + return pkg && !csp && !doc.split("/").pop()!.includes(".") ? `${doc}.PKG` : doc; } /** @@ -339,7 +339,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (entryPromise instanceof File) { // previously resolved as a file result = entryPromise; - } else if (entryPromise instanceof Promise && uri.path.split("/").pop()?.split(".").length > 1) { + } else if (entryPromise instanceof Promise && uri.path.split("/").pop()!.split(".").length > 1) { // apparently a file, so resolve ahead of adding permissions result = await entryPromise; } else { @@ -351,7 +351,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const serverName = isfsDocumentName(uri); if (serverName.slice(-4).toLowerCase() == ".cls") { if (await isClassDeployed(serverName, api)) { - result.permissions |= vscode.FilePermission.Readonly; + result.permissions = result.permissions! | vscode.FilePermission.Readonly; return result; } } @@ -362,7 +362,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const statusObj = await api.actionQuery(query, [serverName]); const docStatus = statusObj.result?.content?.pop(); if (docStatus) { - result.permissions = docStatus.editable ? undefined : result.permissions | vscode.FilePermission.Readonly; + result.permissions = docStatus.editable ? undefined : result.permissions! | vscode.FilePermission.Readonly; } } } @@ -381,7 +381,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (project) { // Get all items in the project return projectContentsFromUri(uri).then((entries) => - entries.map((entry) => { + entries!.map((entry) => { const csp = ["CSP", "DIR"].includes(entry.Type); if (!entry.Name.includes(".")) { if (!parent.entries.has(entry.Name)) { @@ -403,7 +403,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const mapkey = uri.toString(); let mapvalue: string[] = []; if (cspFilesInProjectFolder.has(mapkey)) { - mapvalue = cspFilesInProjectFolder.get(mapkey); + mapvalue = cspFilesInProjectFolder.get(mapkey)!; } mapvalue.push(entry.Name); cspFilesInProjectFolder.set(mapkey, mapvalue); @@ -434,7 +434,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } } const cspSubfolders = Array.from(cspSubfolderMap.entries()); - return studioOpenDialogFromURI(uri) + return studioOpenDialogFromURI(uri)! .then((data) => data.result.content || []) .then((data) => { const results = data @@ -479,7 +479,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const identity = username.includes("*") ? `Users using ${username.slice(1, -1)}` : `User '${username}'`; const message = `${identity} cannot list ${ csp ? `web application '${uri.path}'` : "namespace" - } contents. If they do not have READ permission on the default code database of the ${api.config.ns.toUpperCase()} namespace then grant it and retry. If the problem remains then execute the following SQL in that namespace:\n\t GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO ${ + } contents. If they do not have READ permission on the default code database of the ${api.config.ns!.toUpperCase()} namespace then grant it and retry. If the problem remains then execute the following SQL in that namespace:\n\t GRANT EXECUTE ON %Library.RoutineMgr_StudioOpenDialog TO ${ username.includes("*") ? "" : username }`; handleError(message); @@ -510,7 +510,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { validateUriIsCanonical(uri); // Use _lookup() instead of _lookupAsFile() so we send // our cached mtime with the GET /doc request if we have it - return this._lookup(uri, true).then((file: File) => file.data); + return this._lookup(uri, true).then((file: File) => file.data!); } public writeFile( @@ -535,7 +535,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const api = new AtelierAPI(uri); let created = false; let update = false; - const fileExt = fileName.split(".").pop().toLowerCase(); + const fileExt = fileName.split(".").pop()!.toLowerCase(); // Use _lookup() instead of _lookupAsFile() so we send // our cached mtime with the GET /doc request if we have it return this._lookup(uri) @@ -695,11 +695,11 @@ export class FileSystemProvider implements vscode.FileSystemProvider { }) .toString(); const mapvalue = cspFilesInProjectFolder.get(parentUriStr); - const idx = mapvalue.indexOf(path.basename(uri.path)); + const idx = mapvalue!.indexOf(path.basename(uri.path)); if (idx != -1) { - mapvalue.splice(idx, 1); - if (mapvalue.length) { - cspFilesInProjectFolder.set(parentUriStr, mapvalue); + mapvalue!.splice(idx, 1); + if (mapvalue!.length) { + cspFilesInProjectFolder.set(parentUriStr, mapvalue!); } else { cspFilesInProjectFolder.delete(parentUriStr); } @@ -728,7 +728,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { // Ignore the recursive flag for project folders toDeletePromise = projectContentsFromUri(uri, true); } else { - toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined).then( + toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined)!.then( (data) => data.result.content ); } @@ -756,7 +756,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (doc.status == "") { this.processDeletedDoc( doc, - DocumentContentProvider.getUri(doc.name, undefined, undefined, true, uri), + DocumentContentProvider.getUri(doc.name, undefined, undefined, true, uri)!, doc.name.includes("/"), project.length > 0 ); @@ -801,10 +801,10 @@ export class FileSystemProvider implements vscode.FileSystemProvider { } public async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): Promise { - if (!oldUri.path.split("/").pop().includes(".")) { + if (!oldUri.path.split("/").pop()!.includes(".")) { throw new vscode.FileSystemError("Cannot rename a package/folder"); } - if (oldUri.path.split(".").pop().toLowerCase() != newUri.path.split(".").pop().toLowerCase()) { + if (oldUri.path.split(".").pop()!.toLowerCase() != newUri.path.split(".").pop()!.toLowerCase()) { throw new vscode.FileSystemError("Cannot change a file's extension during rename"); } if (vscode.workspace.getWorkspaceFolder(oldUri) != vscode.workspace.getWorkspaceFolder(newUri)) { @@ -813,13 +813,13 @@ export class FileSystemProvider implements vscode.FileSystemProvider { validateUriIsCanonical(oldUri); validateUriIsCanonical(newUri, true); // Check if the destination exists - let newFileStat: vscode.FileStat; + let newFileStat: vscode.FileStat | undefined; try { newFileStat = await vscode.workspace.fs.stat(newUri); if (!options.overwrite) { // If it does and we can't overwrite it, throw an error throw vscode.FileSystemError.FileExists(newUri); - } else if (newFileStat.permissions & vscode.FilePermission.Readonly) { + } else if (newFileStat.permissions! & vscode.FilePermission.Readonly) { // If the file is read-only, throw an error // This can happen if the target class is deployed, // or the document is marked read-only by source control @@ -901,7 +901,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { if (isfsConfig(uri).project) { compileListPromise = projectContentsFromUri(uri, true); } else { - compileListPromise = studioOpenDialogFromURI(uri, { flat: true }).then((data) => data.result.content); + compileListPromise = studioOpenDialogFromURI(uri, { flat: true })!.then((data) => data.result.content); } compileList.push(...(await compileListPromise.then((data) => data.map((e) => e.Name)))); } else { @@ -964,7 +964,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { ...filesToUpdate.map((f) => { return { type: vscode.FileChangeType.Changed, - uri: DocumentContentProvider.getUri(f, undefined, undefined, undefined, originalUri), + uri: DocumentContentProvider.getUri(f, undefined, undefined, undefined, originalUri)!, }; }) ); @@ -982,7 +982,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { ).map((f: string) => { return { type: vscode.FileChangeType.Changed, - uri: DocumentContentProvider.getUri(f, undefined, undefined, undefined, originalUri), + uri: DocumentContentProvider.getUri(f, undefined, undefined, undefined, originalUri)!, }; }) ); @@ -998,8 +998,8 @@ export class FileSystemProvider implements vscode.FileSystemProvider { private async _lookup(uri: vscode.Uri, fillInPath?: boolean): Promise { const api = new AtelierAPI(uri); const config = api.config; - const rootName = `${config.auth.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns.toUpperCase()}`; - let entry: Entry = this.superRoot.entries.get(rootName); + const rootName = `${config.auth.username}@${config.host}:${config.port}${config.pathPrefix}/${config.ns!.toUpperCase()}`; + let entry: Entry | undefined = this.superRoot.entries.get(rootName); if (!entry) { entry = new Directory(rootName, ""); this.superRoot.entries.set(rootName, entry); diff --git a/src/providers/FileSystemProvider/TextSearchProvider.ts b/src/providers/FileSystemProvider/TextSearchProvider.ts index d9e62636..0929c54d 100644 --- a/src/providers/FileSystemProvider/TextSearchProvider.ts +++ b/src/providers/FileSystemProvider/TextSearchProvider.ts @@ -34,9 +34,9 @@ function searchMatchToLine( let line = match.line ? Number(match.line) : null; if (match.member !== undefined) { // This is an attribute of a class member - if (match.member == "Storage" && match.attr.includes(",") && match.attrline == undefined) { + if (match.member == "Storage" && match.attr!.includes(",") && match.attrline == undefined) { // This is inside a Storage definition - const xmlTags = match.attr.split(","); + const xmlTags = match.attr!.split(","); const storageRegex = new RegExp(`^Storage ${xmlTags[0]}`); let inStorage = false; for (let i = 0; i < content.length; i++) { @@ -90,14 +90,14 @@ function searchMatchToLine( } else { if (match.attr === "Description") { // This is in the description - line = descLineToDocLine(content, match.attrline, i); - } else if (match.attrline || ["Code", "Data", "SqlQuery"].includes(match.attr)) { - if (["Code", "Data", "SqlQuery"].includes(match.attr)) { + line = descLineToDocLine(content, match.attrline!, i); + } else if (match.attrline || ["Code", "Data", "SqlQuery"].includes(match.attr!)) { + if (["Code", "Data", "SqlQuery"].includes(match.attr!)) { // This is in the implementation line = memend + (match.attrline ?? 1); } else { // This is a keyword with a multiline value - line = i + (match.attrline - 1 || 0); + line = i + (match.attrline! - 1 || 0); } } else { // This is in the class member definition @@ -163,7 +163,7 @@ function searchMatchToLine( if (content[i].match(classMatchPattern)) { if (match.attr == "Description") { // This is in the class description - line = descLineToDocLine(content, match.attrline, i); + line = descLineToDocLine(content, match.attrline!, i); break; } else if (match.attr == "Super" || match.attr == "Name") { // This is in the class definition line @@ -220,7 +220,7 @@ async function processSearchResults( results: number, maxResults: number, token: vscode.CancellationToken -): Promise { +): Promise { if (token.isCancellationRequested) { return; } @@ -228,7 +228,7 @@ async function processSearchResults( if (token.isCancellationRequested) { return; } - let message: vscode.TextSearchCompleteMessage; + let message: vscode.TextSearchCompleteMessage | undefined; const rejected = fileResults.filter((r) => r.status == "rejected").length; if (rejected > 0) { outputChannel.appendLine("Search errors:"); @@ -258,7 +258,7 @@ async function processSearchResults( function removeConfigExcludes(folder: vscode.Uri, excludes: string[]): string[] { // Function to get one of the two kinds of exclude settings as an array const getConfigExcludes = (key: string) => { - return Object.entries(vscode.workspace.getConfiguration(key, folder).get("exclude")) + return Object.entries(vscode.workspace.getConfiguration(key, folder).get("exclude")!) .filter((value) => value[1] === true) .map((value) => value[0]); }; @@ -289,7 +289,7 @@ export class TextSearchProvider implements vscode.TextSearchProvider { options: vscode.TextSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken - ): Promise { + ): Promise { const api = new AtelierAPI(options.folder); const rateLimiter = new RateLimiter(50); if (!api.active) { @@ -359,13 +359,13 @@ export class TextSearchProvider implements vscode.TextSearchProvider { return; } - const uri = DocumentContentProvider.getUri(file.doc, "", "", true, options.folder); + const uri = DocumentContentProvider.getUri(file.doc, "", "", true, options.folder)!; const content = decoder.decode(await vscode.workspace.fs.readFile(uri)).split(/\r?\n/); const contentLength = content.length; // Find all lines that we have matches on const multilineMethodArgs: boolean = vscode.workspace .getConfiguration("objectscript", options.folder) - .get("multilineMethodArgs"); + .get("multilineMethodArgs")!; const lines = file.matches .map((match: SearchMatch) => token.isCancellationRequested ? null : searchMatchToLine(content, match, file.doc, multilineMethodArgs) @@ -378,26 +378,26 @@ export class TextSearchProvider implements vscode.TextSearchProvider { if (token.isCancellationRequested) { return; } - const text = content[line]; + const text = content[line!]; const regex = new RegExp( query.isRegExp ? query.pattern : query.pattern.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), query.isCaseSensitive ? "g" : "gi" ); - let regexMatch: RegExpExecArray; + let regexMatch: RegExpExecArray | null; const matchRanges: vscode.Range[] = []; const previewRanges: vscode.Range[] = []; - while ((regexMatch = regex.exec(text)) !== null && counter < options.maxResults) { + while ((regexMatch = regex.exec(text)) !== null && counter < options.maxResults!) { const start = regexMatch.index; const end = start + regexMatch[0].length; - matchRanges.push(new vscode.Range(line, start, line, end)); + matchRanges.push(new vscode.Range(line!, start, line!, end)); previewRanges.push(new vscode.Range(0, start, 0, end)); counter++; } if (matchRanges.length && previewRanges.length) { if (options.beforeContext) { // Add preceding context lines that aren't themselves result lines - const previewFrom = Math.max(line - options.beforeContext, 0); - for (let i = previewFrom; i < line; i++) { + const previewFrom = Math.max(line! - options.beforeContext, 0); + for (let i = previewFrom; i < line!; i++) { if (!matchedLines.has(i)) { progress.report({ uri, @@ -417,8 +417,8 @@ export class TextSearchProvider implements vscode.TextSearchProvider { }); if (options.afterContext) { // Add following context lines that aren't themselves result lines - const previewTo = Math.min(line + options.afterContext, contentLength - 1); - for (let i = line + 1; i <= previewTo; i++) { + const previewTo = Math.min(line! + options.afterContext, contentLength - 1); + for (let i = line! + 1; i <= previewTo; i++) { if (!matchedLines.has(i)) { progress.report({ uri, @@ -444,7 +444,7 @@ export class TextSearchProvider implements vscode.TextSearchProvider { options.includes = deduplicateGlobArray(options.includes); options.excludes = deduplicateGlobArray(removeConfigExcludes(options.folder, options.excludes)); - if (api.config.apiVersion >= 6) { + if (api.config.apiVersion! >= 6) { // Build the request object const request: AsyncSearchRequest = { request: "search", @@ -559,8 +559,8 @@ export class TextSearchProvider implements vscode.TextSearchProvider { let groupLen = 0; let group: string[] = []; for (const doc of prjContents) { - group.push(doc); - groupLen += doc.length; + group.push(doc!); + groupLen += doc!.length; if (groupLen >= 1300) { // Be conservative because we really don't want ugly 414 errors requestGroups.push(group); diff --git a/src/providers/LowCodeEditorProvider.ts b/src/providers/LowCodeEditorProvider.ts index 296620ad..24215474 100644 --- a/src/providers/LowCodeEditorProvider.ts +++ b/src/providers/LowCodeEditorProvider.ts @@ -36,14 +36,14 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { return this._errorMessage(`${document.fileName} is a malformed class definition.`); } const api = new AtelierAPI(document.uri); - if (!vscode.workspace.fs.isWritableFileSystem(document.uri.scheme) && lt(api.config.serverVersion, "2025.3.0")) { + if (!vscode.workspace.fs.isWritableFileSystem(document.uri.scheme) && lt(api.config.serverVersion!, "2025.3.0")) { return this._errorMessage(`File system '${document.uri.scheme}' is read-only.`); } const className = file.name.slice(0, -4); if (!api.active) { return this._errorMessage("Server connection is not active."); } - if (lt(api.config.serverVersion, "2023.1.0")) { + if (lt(api.config.serverVersion!, "2023.1.0")) { return this._errorMessage( "Opening a low-code editor in VS Code requires InterSystems IRIS version 2023.1 or above." ); @@ -66,14 +66,14 @@ export class LowCodeEditorProvider implements vscode.CustomTextEditorProvider { } else if (queryData.result.content[0].Rule) { webApp = this._rule; } else if (queryData.result.content[0].DTL) { - if (lt(api.config.serverVersion, "2025.1.0")) { + if (lt(api.config.serverVersion!, "2025.1.0")) { return this._errorMessage( "Opening the DTL editor in VS Code requires InterSystems IRIS version 2025.1 or above." ); } webApp = this._dtl; } else if (queryData.result.content[0].BPL) { - if (lt(api.config.serverVersion, "2026.1.0")) { + if (lt(api.config.serverVersion!, "2026.1.0")) { return this._errorMessage( "Opening the BPL editor in VS Code requires InterSystems IRIS version 2026.1 or above." ); diff --git a/src/providers/ObjectScriptClassSymbolProvider.ts b/src/providers/ObjectScriptClassSymbolProvider.ts index cb2c5bf4..10db56c3 100644 --- a/src/providers/ObjectScriptClassSymbolProvider.ts +++ b/src/providers/ObjectScriptClassSymbolProvider.ts @@ -6,7 +6,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro token: vscode.CancellationToken ): Thenable { return new Promise((resolve) => { - let classItSelf = null; + let classItSelf: vscode.DocumentSymbol | null = null; let symbols: vscode.DocumentSymbol[] = []; let inComment = false; @@ -66,7 +66,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro } } symbols.push({ - children: undefined, + children: undefined!, detail: method[1], kind: vscode.SymbolKind.Method, name: method[2].replace(/"/g, ""), @@ -78,7 +78,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro const index = line.text.match(/^(Index|ForegnKey) (%?\b\w+\b)/i); if (index) { symbols.push({ - children: undefined, + children: undefined!, detail: index[1], kind: vscode.SymbolKind.Key, name: index[2], @@ -99,7 +99,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro } } symbols.push({ - children: undefined, + children: undefined!, detail: property[1], kind: vscode.SymbolKind.Property, name: property[2], @@ -111,7 +111,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro const parameter = line.text.match(/^(Parameter) (%?\b\w+\b)/i); if (parameter) { symbols.push({ - children: undefined, + children: undefined!, detail: parameter[1], kind: vscode.SymbolKind.Constant, name: parameter[2], @@ -134,7 +134,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro } } symbols.push({ - children: undefined, + children: undefined!, detail: other[1], kind: vscode.SymbolKind.Struct, name: other[2], @@ -144,7 +144,7 @@ export class ObjectScriptClassSymbolProvider implements vscode.DocumentSymbolPro } } - resolve([classItSelf]); + resolve([classItSelf!]); }); } } diff --git a/src/providers/ObjectScriptCodeLensProvider.ts b/src/providers/ObjectScriptCodeLensProvider.ts index f73a9052..a81b69dd 100644 --- a/src/providers/ObjectScriptCodeLensProvider.ts +++ b/src/providers/ObjectScriptCodeLensProvider.ts @@ -81,7 +81,7 @@ function getSqlQuery(document: vscode.TextDocument, startLine: number, startChar if ( result.length && !["SELECT", "DECLARE", "UPDATE", "DELETE", "TRUNCATE", "INSERT"].includes( - result.trimStart().split(/\s+/).shift().toUpperCase() + result.trimStart().split(/\s+/).shift()!.toUpperCase() ) ) { // Can only generate plans for certain SQL statements @@ -109,8 +109,8 @@ function scanCodeBlock( let inCStyleComment = false; for (let i = start; i < end; i++) { const line = document.lineAt(i).text; - let commentStart: number; - let commentEnd: number; + let commentStart: number | undefined; + let commentEnd: number | undefined; if (!inCStyleComment) { const commentMatch = line.match(commentRegex); if (commentMatch) { @@ -138,20 +138,20 @@ function scanCodeBlock( // Check if the match is commented out or in a string literal if ( ((commentStart == undefined && commentEnd == undefined) || - (commentStart != undefined && eSqlMatch.index < commentStart) || - (commentEnd != undefined && eSqlMatch.index > commentEnd)) && + (commentStart != undefined && eSqlMatch.index! < commentStart) || + (commentEnd != undefined && eSqlMatch.index! > commentEnd)) && // There are an even number of, or zero, quotes preceding the match - line.slice(0, eSqlMatch.index).split('"').length % 2 == 1 + line.slice(0, eSqlMatch.index!).split('"').length % 2 == 1 ) { const sqlQuery = getSqlQuery( document, i, - eSqlMatch.index + eSqlMatch[0].length, + eSqlMatch.index! + eSqlMatch[0].length, `)${(eSqlMatch[1] ?? "").split("").reverse().join("")}` ); if (sqlQuery) { result.push( - new vscode.CodeLens(new vscode.Range(i, eSqlMatch.index, i, eSqlMatch.index + eSqlMatch[0].length), { + new vscode.CodeLens(new vscode.Range(i, eSqlMatch.index!, i, eSqlMatch.index! + eSqlMatch[0].length), { title: "Show Plan", tooltip: "Show the plan for this query", command: "vscode-objectscript.showPlanWebview", @@ -187,7 +187,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { public async provideCodeLenses( document: vscode.TextDocument, token: vscode.CancellationToken - ): Promise { + ): Promise { if (![clsLangId, macLangId, intLangId].includes(document.languageId)) return; const file = currentFile(document); if (!file) return; // Document is malformed @@ -198,9 +198,9 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { if (!symbols?.length || token.isCancellationRequested) return; const api = new AtelierAPI(document.uri); const conf = vscode.workspace.getConfiguration("objectscript.debug"); - const debugThisMethod: boolean = conf.get("debugThisMethod") && api.active; - const copyToClipboard: boolean = conf.get("copyToClipboard"); - const showPlan = api.active && gte(api.config.serverVersion, "2024.1.0"); + const debugThisMethod: boolean = conf.get("debugThisMethod")! && api.active; + const copyToClipboard: boolean = conf.get("copyToClipboard")!; + const showPlan = api.active && gte(api.config.serverVersion!, "2024.1.0"); const result: vscode.CodeLens[] = []; if (document.languageId == clsLangId) { if (!symbols[0].children.length) return; @@ -245,7 +245,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { symbols[0].children.forEach((symbol, idx) => { const type = symbol.detail.toLowerCase(); if (!["xdata", "method", "classmethod", "query", "trigger"].includes(type)) return; - let symbolLine: number; + let symbolLine: number | undefined; if (languageServer) { symbolLine = symbol.selectionRange.start.line; } else { @@ -259,17 +259,17 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { switch (type) { case "xdata": { if (api.active) { - let cmd: vscode.Command; + let cmd: vscode.Command | undefined; if ( (symbol.name == "RuleDefinition" && superclasses.includes("Ens.Rule.Definition") && - gte(api.config.serverVersion, "2023.1.0")) || + gte(api.config.serverVersion!, "2023.1.0")) || (symbol.name == "DTL" && superclasses.includes("Ens.DataTransformDTL") && - gte(api.config.serverVersion, "2025.1.0")) || + gte(api.config.serverVersion!, "2025.1.0")) || (symbol.name == "BPL" && superclasses.includes("Ens.BusinessProcessBPL") && - gte(api.config.serverVersion, "2026.1.0")) + gte(api.config.serverVersion!, "2026.1.0")) ) { cmd = { title: "Reopen in Low-Code Editor", @@ -285,7 +285,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { arguments: [`/${className}.cls`, document.uri], }; } - if (cmd) result.push(new vscode.CodeLens(this.range(symbolLine), cmd)); + if (cmd) result.push(new vscode.CodeLens(this.range(symbolLine!), cmd)); } break; } @@ -294,7 +294,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { case "trigger": case "query": { // Capture the entire text of the class member definition up to the implementation - const memberInfo = parseClassMemberDefinition(document, symbol, symbolLine); + const memberInfo = parseClassMemberDefinition(document, symbol, symbolLine!); if (!memberInfo) break; const { definition, defEndLine, language, isPrivate } = memberInfo; if (showPlan) { @@ -307,7 +307,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { const sqlQuery = getSqlQuery(document, defEndLine + 1, 0, "}"); if (sqlQuery) { result.push( - new vscode.CodeLens(this.range(symbolLine), { + new vscode.CodeLens(this.range(symbolLine!), { title: "Show Plan", tooltip: "Show the plan for this query", command: "vscode-objectscript.showPlanWebview", @@ -342,7 +342,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { ) { const argsMatch = definition.match(new RegExp(`${displayName}\\(([^)]*)\\)`)); result.push( - this.addDebugThisMethod(symbolLine, [ + this.addDebugThisMethod(symbolLine!, [ `##class(${className}).${displayName}`, argsMatch && typeof argsMatch[1] == "string" && argsMatch[1].trim().length > 0, ]) @@ -354,7 +354,7 @@ export class ObjectScriptCodeLensProvider implements vscode.CodeLensProvider { (type == "classmethod" || (type == "query" && displayName[0] != '"')) ) { result.push( - this.addCopyToClipboard(symbolLine, [ + this.addCopyToClipboard(symbolLine!, [ `##class(${className}).${displayName}${type == "query" ? "Func" : ""}()`, ]) ); diff --git a/src/providers/ObjectScriptCompletionItemProvider.ts b/src/providers/ObjectScriptCompletionItemProvider.ts index 670001a7..230cfb20 100644 --- a/src/providers/ObjectScriptCompletionItemProvider.ts +++ b/src/providers/ObjectScriptCompletionItemProvider.ts @@ -40,7 +40,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem ); } } - const completions = [] + const completions: any[] = ([] as any[]) .concat( this.classes(document, position, token, context), this.macrolist(document, position, token, context), @@ -99,11 +99,11 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem const range = document.getWordRangeAtPosition(position, pattern); const text = range ? document.getText(range) : ""; if (range) { - const [, prefix, macro = ""] = text.toLowerCase().match(pattern); + const [, prefix, macro = ""] = text.toLowerCase().match(pattern)!; const file = currentFile(); const api = new AtelierAPI(); return api - .getmacrolist(file.name, []) + .getmacrolist(file!.name, []) .then((data) => data.result.content.macros) .then((list) => list.filter((el) => el.toLowerCase().startsWith(macro))) .then((list) => list.map((el) => "$$$" + el)) @@ -174,7 +174,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem return { ...el, label: this._formatter.function(el.label as string), - insertText: this._formatter.function(el.insertText), + insertText: this._formatter.function(el.insertText!), range, }; }), @@ -236,7 +236,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext - ): vscode.CompletionItem[] { + ): vscode.CompletionItem[] | null { const range = document.getWordRangeAtPosition(position, /%?\b\w+[\w\d]*\b/); const kind = vscode.CompletionItemKind.Variable; if (context.triggerKind === vscode.CompletionTriggerKind.Invoke) { @@ -273,14 +273,14 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem const searchText = document.getText(range); const method = (el) => ({ - documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null, + documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null!, insertText: new vscode.SnippetString(`${el.name}($0)`), kind: vscode.CompletionItemKind.Method, label: el.name, }); const parameter = (el) => ({ - documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null, + documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null!, insertText: new vscode.SnippetString(`${el.name}`), kind: vscode.CompletionItemKind.Constant, label: `${el.name}`, @@ -288,7 +288,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem }); const property = (el) => ({ - documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null, + documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null!, insertText: new vscode.SnippetString(`${el.name}`), kind: vscode.CompletionItemKind.Property, label: el.name, @@ -306,10 +306,10 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem return classDef.methods("class").then((data) => data.filter(search).map(method)); } - if (curFile.fileName.endsWith("cls")) { + if (curFile!.fileName.endsWith("cls")) { const selfRef = textBefore.match(/(? data.filter(search).map(parameter)); } @@ -333,7 +333,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem let pattern = /##class\(([^)]*)\)/i; let range = document.getWordRangeAtPosition(position, pattern); let text = range ? document.getText(range) : ""; - let [, className] = range ? text.match(pattern) : ""; + let [, className] = (range ? text.match(pattern)! : "") as [string, string | undefined]; if (!range) { pattern = /(\b(?:Of|As)\b (%?\b[a-zA-Z][a-zA-Z0-9]*(?:\.[a-zA-Z][a-zA-Z0-9]+)*\b\.?)?(?! of))/i; range = document.getWordRangeAtPosition(position, pattern); @@ -353,18 +353,18 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem className = text.split(/\s|\(/).pop(); } if (range) { - const percent = className.startsWith("%"); - const library = percent && className.indexOf(".") < 0; + const percent = className!.startsWith("%"); + const library = percent && className!.indexOf(".") < 0; className = className || ""; const searchName = className.replace(/(^%|")/, "").toLowerCase(); const part = className.split(".").length; - const params = []; + const params: string[] = []; let sql = ""; /// Classes from the current class's package - if (part === 1 && curFile.fileName.endsWith("cls")) { - const packageName = curFile.name.split(".").slice(0, -2).join("."); - const className2 = curFile.name.split(".").slice(0, -1).join("."); + if (part === 1 && curFile!.fileName.endsWith("cls")) { + const packageName = curFile!.name.split(".").slice(0, -2).join("."); + const className2 = curFile!.name.split(".").slice(0, -1).join("."); const part2 = packageName.split(".").length + 1; sql += ` SELECT @@ -442,7 +442,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem ): vscode.ProviderResult { const range = document.getWordRangeAtPosition(position, /\$system(\.\b\w+\b)?(\.\b\w+\b)?\./i); const text = range ? document.getText(range) : ""; - const [, className] = text.match(/\$system(\.\b\w+\b)?(\.\b\w+\b)?\./i); + const [, className] = text.match(/\$system(\.\b\w+\b)?(\.\b\w+\b)?\./i)!; const api = new AtelierAPI(); if (!className) { @@ -466,7 +466,7 @@ export class ObjectScriptCompletionItemProvider implements vscode.CompletionItem .content.methods.filter((el) => !el.private) .filter((el) => !el.internal) .map((el) => ({ - documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null, + documentation: el.desc.length ? new vscode.MarkdownString(el.desc.join("")) : null!, insertText: new vscode.SnippetString(`${el.name}($0)`), kind: vscode.CompletionItemKind.Method, label: el.name, diff --git a/src/providers/ObjectScriptDefinitionProvider.ts b/src/providers/ObjectScriptDefinitionProvider.ts index 1012e4af..feb403d3 100644 --- a/src/providers/ObjectScriptDefinitionProvider.ts +++ b/src/providers/ObjectScriptDefinitionProvider.ts @@ -24,7 +24,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider if (selfRef) { const selfEntity = document.getText(selfRef).substr(2); const range = new vscode.Range(position.line, selfRef.start.character + 2, position.line, selfRef.end.character); - const classDefinition = new ClassDefinition(file.name); + const classDefinition = new ClassDefinition(file!.name); return classDefinition.getMemberLocations(selfEntity).then((locations): vscode.DefinitionLink[] => locations.map( (location): vscode.DefinitionLink => ({ @@ -41,9 +41,9 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider const macroMatch = macroText.match(/^\${3}(\b\w+\b)$/); if (macroMatch) { const [, macro] = macroMatch; - return this.macro(workspaceFolderName, currentFile(), macro).then((data) => + return this.macro(workspaceFolderName, currentFile()!, macro).then((data) => data && data.document.length - ? new vscode.Location(DocumentContentProvider.getUri(data.document), new vscode.Position(data.line, 0)) + ? new vscode.Location(DocumentContentProvider.getUri(data.document)!, new vscode.Position(data.line, 0)) : null ); } @@ -77,7 +77,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider const [keyword, name] = part.split(" "); const start = pos + keyword.length + 1; if (this.isValid(position, start, name.length)) { - return [this.makePropertyDefinition(document, name)]; + return [this.makePropertyDefinition(document, name)!]; } } pos += part.length; @@ -87,7 +87,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider pos = 0; for (const part of parts) { if (part.match(onPropertyList)) { - const listProperties = /\(([^)]+)\)/.exec(part)[1].split(/\s*,\s*/); + const listProperties = /\(([^)]+)\)/.exec(part)![1].split(/\s*,\s*/); return listProperties .map((name) => { name = name.trim(); @@ -105,7 +105,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider pos = 0; for (const part of parts) { if (part.match(asClassList)) { - const listClasses = /\(([^)]+)\)/.exec(part)[1].split(/\s*,\s*/); + const listClasses = /\(([^)]+)\)/.exec(part)![1].split(/\s*,\s*/); return listClasses .map((name) => { name = name.trim(); @@ -149,7 +149,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider pos = 0; for (const part of parts) { if (part.match(asRoutineList)) { - const listRoutines = /\(([^)]+)\)/.exec(part)[1].split(","); + const listRoutines = /\(([^)]+)\)/.exec(part)![1].split(","); for (let name of listRoutines) { name = name.trim(); const start = pos + part.indexOf(name); @@ -174,7 +174,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider pos = 0; for (const part of parts) { if (part.match(asLabelRoutineCall)) { - const [, routine] = part.match(/\^(%?\b\w+\b)/); + const [, routine] = part.match(/\^(%?\b\w+\b)/)!; const start = pos + part.indexOf(routine) - 1; const length = routine.length + 1; return this.getFullRoutineName(routine).then((routineName) => [ @@ -195,7 +195,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider const classRef = /##class\(([^)]+)\)(?:\\$this)?\.(#?%?[a-zA-Z][a-zA-Z0-9]*)/i; const classRefRange = document.getWordRangeAtPosition(position, classRef); if (classRefRange) { - const [, className, entity] = document.getText(classRefRange).match(classRef); + const [, className, entity] = document.getText(classRefRange).match(classRef)!; const start = classRefRange.start.character + 8; if (this.isValid(position, start, className.length)) { return [ @@ -274,11 +274,11 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider new vscode.Position(position.line, start + length) ), targetRange: new vscode.Range(firstLinePos, firstLinePos), - targetUri: DocumentContentProvider.getUri(name, workspaceFolder), + targetUri: DocumentContentProvider.getUri(name, workspaceFolder)!, }; } - public makePropertyDefinition(document: vscode.TextDocument, name: string): vscode.DefinitionLink { + public makePropertyDefinition(document: vscode.TextDocument, name: string): vscode.DefinitionLink | null { const property = new RegExp(`(?<=^Property\\s)\\b${name}\\b`, "i"); let descrLine = -1; for (let i = 0; i < document.lineCount; i++) { @@ -292,12 +292,12 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider const propertyMatch = line.match(property); if (propertyMatch) { const targetSelectionRange = new vscode.Range( - new vscode.Position(i, propertyMatch.index), - new vscode.Position(i, propertyMatch.index + name.length) + new vscode.Position(i, propertyMatch.index!), + new vscode.Position(i, propertyMatch.index! + name.length) ); const targetRange = new vscode.Range( new vscode.Position(descrLine >= 0 ? descrLine : i, 0), - new vscode.Position(i, propertyMatch.index + line.length) + new vscode.Position(i, propertyMatch.index! + line.length) ); return { targetUri: document.uri, @@ -323,7 +323,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider new vscode.Position(position.line, start + length) ), targetRange: new vscode.Range(firstLinePos, firstLinePos), - targetUri: DocumentContentProvider.getUri(name, workspaceFolder), + targetUri: DocumentContentProvider.getUri(name, workspaceFolder)!, }; } @@ -334,7 +334,7 @@ export class ObjectScriptDefinitionProvider implements vscode.DefinitionProvider ): Promise<{ document: string; line: number }> { const fileName = file.name; const api = new AtelierAPI(); - let includes = []; + let includes: string[] = []; if (fileName.toLowerCase().endsWith("cls")) { const classDefinition = new ClassDefinition(fileName); includes = await classDefinition.includeCode(); diff --git a/src/providers/ObjectScriptDiagnosticProvider.ts b/src/providers/ObjectScriptDiagnosticProvider.ts index a903827e..2941ffd7 100644 --- a/src/providers/ObjectScriptDiagnosticProvider.ts +++ b/src/providers/ObjectScriptDiagnosticProvider.ts @@ -324,12 +324,12 @@ export class ObjectScriptDiagnosticProvider { } const pattern = /(? { const word = document.getWordRangeAtPosition(position); const text = document.getText( - new vscode.Range(new vscode.Position(position.line, 0), new vscode.Position(position.line, word.end.character)) + new vscode.Range(new vscode.Position(position.line, 0), new vscode.Position(position.line, word!.end.character)) ); const file = currentFile(); @@ -32,9 +32,9 @@ export class ObjectScriptHoverProvider implements vscode.HoverProvider { const range = document.getWordRangeAtPosition(position, /\^?\$+\b\w+\b$/); let search = dollarsMatch.shift(); const [dollars, value] = dollarsMatch; - search = search.toUpperCase(); + search = search!.toUpperCase(); if (dollars === "$$$") { - return this.macro(file.name, value).then((contents) => ({ + return this.macro(file!.name, value).then((contents) => ({ contents: [contents.join("")], range, })); @@ -44,7 +44,7 @@ export class ObjectScriptHoverProvider implements vscode.HoverProvider { found = found || structuredSystemVariables.find((el) => el.label === search || el.alias.includes(search)); if (found) { return { - contents: [found.documentation.join(""), this.documentationLink(found.link)], + contents: [found.documentation.join(""), this.documentationLink(found.link)!], range, }; } @@ -56,7 +56,7 @@ export class ObjectScriptHoverProvider implements vscode.HoverProvider { public async macro(fileName: string, macro: string): Promise { const api = new AtelierAPI(); - let includes = []; + let includes: string[] = []; if (fileName.toLowerCase().endsWith(".cls")) { const classDefinition = new ClassDefinition(fileName); includes = await classDefinition.includeCode(); @@ -74,7 +74,7 @@ export class ObjectScriptHoverProvider implements vscode.HoverProvider { public commands(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { const word = document.getWordRangeAtPosition(position); const text = document.getText( - new vscode.Range(new vscode.Position(position.line, 0), new vscode.Position(position.line, word.end.character)) + new vscode.Range(new vscode.Position(position.line, 0), new vscode.Position(position.line, word!.end.character)) ); const commandMatch = text.match(/^\s+\b[a-z]+\b$/i); if (commandMatch) { @@ -82,14 +82,14 @@ export class ObjectScriptHoverProvider implements vscode.HoverProvider { const command = commands.find((el) => el.label === search || el.alias.includes(search)); if (search) { return { - contents: [command.documentation.join(""), this.documentationLink(command.link)], + contents: [command!.documentation.join(""), this.documentationLink(command!.link)!], range: word, }; } } } - public documentationLink(link: string): string | null { + public documentationLink(link: string | undefined): string | null | undefined { if (link) { return `[Online documentation](${ link.startsWith("http") ? "" : "https://docs.intersystems.com/irislatest" diff --git a/src/providers/WorkspaceSymbolProvider.ts b/src/providers/WorkspaceSymbolProvider.ts index b1b991ac..28596e33 100644 --- a/src/providers/WorkspaceSymbolProvider.ts +++ b/src/providers/WorkspaceSymbolProvider.ts @@ -35,7 +35,7 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { * because we aren't including ranges. They will be resolved later. */ private _queryResultToSymbols(data: any, wsFolder: vscode.WorkspaceFolder): any[] { - const result = []; + const result: any[] = []; const uris: Map = new Map(); for (const element of data.result.content) { const kind: vscode.SymbolKind = (() => { @@ -68,7 +68,7 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { let uri: vscode.Uri; if (uris.has(element.Parent)) { - uri = uris.get(element.Parent); + uri = uris.get(element.Parent)!; } else { uri = DocumentContentProvider.getUri( `${element.Parent}.cls`, @@ -78,7 +78,7 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { wsFolder.uri, // Only "file" scheme is fully supported for client-side editing wsFolder.uri.scheme != "file" - ); + )!; uris.set(element.Parent, uri); } @@ -97,7 +97,7 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { public async provideWorkspaceSymbols( query: string, token: vscode.CancellationToken - ): Promise { + ): Promise { if (!vscode.workspace.workspaceFolders?.length) return; // Convert query to a LIKE compatible pattern const pattern = queryToFuzzyLike(query); diff --git a/src/utils/FileProviderUtil.ts b/src/utils/FileProviderUtil.ts index 451c6e2a..30d8345c 100644 --- a/src/utils/FileProviderUtil.ts +++ b/src/utils/FileProviderUtil.ts @@ -32,12 +32,12 @@ export function isfsConfig(uri: vscode.Uri): IsfsUriConfig { mapped: params.get(IsfsUriParam.Mapped) != "0", filter: params.get(IsfsUriParam.Filter) ?? "", project: params.get(IsfsUriParam.Project) ?? "", - csp: ["", "1"].includes(params.get(IsfsUriParam.CSP)), + csp: ["", "1"].includes(params.get(IsfsUriParam.CSP)!), ns: params.get(IsfsUriParam.NS) || undefined, }; } -export async function projectContentsFromUri(uri: vscode.Uri, flat = false): Promise { +export async function projectContentsFromUri(uri: vscode.Uri, flat = false): Promise { const api = new AtelierAPI(uri); if (!api.active) { return; @@ -162,7 +162,7 @@ export function fileSpecFromURI(uri: vscode.Uri): string { export function studioOpenDialogFromURI( uri: vscode.Uri, overrides: { flat?: boolean; filter?: string } = { flat: false, filter: "" } -): Promise { +): Promise | undefined { const api = new AtelierAPI(uri); if (!api.active) return; const { system, generated, mapped } = isfsConfig(uri); @@ -174,7 +174,7 @@ export function studioOpenDialogFromURI( overrides?.flat ? "1" : "0", "0", // NotStudio (0 means hide globals and OBJ files) generated ? "1" : "0", - overrides.filter, + overrides.filter!, "0", // RoundTime (0 means no rounding) mapped ? "1" : "0", ]); diff --git a/src/utils/classDefinition.ts b/src/utils/classDefinition.ts index d4e841ca..2c477d76 100644 --- a/src/utils/classDefinition.ts +++ b/src/utils/classDefinition.ts @@ -5,7 +5,7 @@ import { DocumentContentProvider } from "../providers/DocumentContentProvider"; export class ClassDefinition { public get uri(): vscode.Uri { - return DocumentContentProvider.getUri(this._classFileName, this._workspaceFolder, this._namespace); + return DocumentContentProvider.getUri(this._classFileName, this._workspaceFolder, this._namespace)!; } public static normalizeClassName(className: string, withExtension = false): string { @@ -13,8 +13,8 @@ export class ClassDefinition { } private _className: string; private _classFileName: string; - private _workspaceFolder: string; - private _namespace: string; + private _workspaceFolder: string | undefined; + private _namespace: string | undefined; public constructor(className: string, workspaceFolder?: string, namespace?: string) { this._workspaceFolder = workspaceFolder; @@ -31,11 +31,11 @@ export class ClassDefinition { } public async methods(scope: "any" | "class" | "instance" = "any"): Promise { - const methods = []; + const methods: any[] = []; const filterScope = (method) => scope === "any" || method.scope === scope; const api = new AtelierAPI(this.uri); const getMethods = (content) => { - const extend = []; + const extend: any[] = []; content.forEach((el) => { methods.push(...el.content.methods); extend.push(...el.content.super.map((extendName) => ClassDefinition.normalizeClassName(extendName, true))); @@ -49,10 +49,10 @@ export class ClassDefinition { } public async properties(): Promise { - const properties = []; + const properties: any[] = []; const api = new AtelierAPI(this.uri); const getProperties = (content) => { - const extend = []; + const extend: any[] = []; content.forEach((el) => { properties.push(...el.content.properties); extend.push(...el.content.super.map((extendName) => ClassDefinition.normalizeClassName(extendName, true))); @@ -66,10 +66,10 @@ export class ClassDefinition { } public async parameters(): Promise { - const parameters = []; + const parameters: any[] = []; const api = new AtelierAPI(this.uri); const getParameters = (content) => { - const extend = []; + const extend: any[] = []; content.forEach((el) => { parameters.push(...el.content.parameters); extend.push(...el.content.super.map((extendName) => ClassDefinition.normalizeClassName(extendName, true))); @@ -116,7 +116,7 @@ export class ClassDefinition { .then((data) => data); } - public async getMemberLocation(name: string): Promise { + public async getMemberLocation(name: string): Promise { let pattern; if (name.startsWith("#")) { pattern = `(Parameter) ${name.substr(1)}(?=[( ;])`; diff --git a/src/utils/documentIndex.ts b/src/utils/documentIndex.ts index 922f2970..f4bcc9fb 100644 --- a/src/utils/documentIndex.ts +++ b/src/utils/documentIndex.ts @@ -52,7 +52,7 @@ async function getCurrentFile( uri: vscode.Uri, forceText = false, content?: string[] | Buffer -): Promise { +): Promise { if (content) { // forceText is always true when content is passed return currentFileFromContent(uri, Buffer.isBuffer(content) ? textDecoder.decode(content) : content.join("\n")); @@ -191,10 +191,11 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr // Index classes and routines that currently exist vscode.workspace.findFiles(new vscode.RelativePattern(wsFolder, "{**/*}")).then((files) => files.forEach((file) => - fsRateLimiter.call(() => { + fsRateLimiter.call(async () => { if (isClassOrRtn(file.path) || isImportableLocalFile(file)) { return updateIndexInternal(file, documents, uris, true); } + return undefined; }) ) ); @@ -217,7 +218,7 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr if (notToSync(uri)) { return; } - if (!uri.path.split("/").pop().includes(".")) { + if (!uri.path.split("/").pop()!.includes(".")) { // Ignore creation and change events for folders return; } @@ -254,7 +255,7 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr } const api = new AtelierAPI(uri); const conf = vscode.workspace.getConfiguration("objectscript", wsFolder); - const syncLocalChanges: string = conf.get("syncLocalChanges"); + const syncLocalChanges: string = conf.get("syncLocalChanges")!; const vscodeChange = touchedByVSCode.has(uriString); const sync = api.active && (syncLocalChanges == "all" || (syncLocalChanges == "vscodeOnly" && vscodeChange)); touchedByVSCode.delete(uriString); @@ -263,8 +264,8 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr if (change.addedOrChanged) { // Create or update the document on the server try { - const willCompile = conf.get("compileOnSave") && isCompilable(change.addedOrChanged.name); - await importFile(change.addedOrChanged, willCompile); + const willCompile = conf.get("compileOnSave") && isCompilable(change.addedOrChanged.name); + await importFile(change.addedOrChanged, willCompile!); outputImport(change.addedOrChanged.name, uri); if (willCompile) { // Compile right away if this document is in the active text editor. @@ -293,7 +294,7 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr const api = new AtelierAPI(uri); const syncLocalChanges: string = vscode.workspace .getConfiguration("objectscript", wsFolder) - .get("syncLocalChanges"); + .get("syncLocalChanges")!; const sync: boolean = api.active && (syncLocalChanges == "all" || (syncLocalChanges == "vscodeOnly" && touchedByVSCode.has(uriString))); for (const subUriString of uris.keys()) { @@ -352,7 +353,7 @@ async function updateIndexInternal( if (!file) return result; result.addedOrChanged = file; if (isImportableLocalFile(uri) && sync) { - sendClientSideSyncTelemetryEvent(file.fileName.split(".").pop().toLowerCase()); + sendClientSideSyncTelemetryEvent(file.fileName.split(".").pop()!.toLowerCase()); } const documentUris = documents.get(file.name) ?? []; if (documentUris.some((u) => u.toString() == uriString)) { @@ -424,8 +425,10 @@ export function allDocumentsInWorkspace(wsFolder: vscode.WorkspaceFolder): strin } /** Get the class/routine name of the document in `uri` */ -export function getDocumentForUri(uri: vscode.Uri): string { - return wsFolderIndex.get(vscode.workspace.getWorkspaceFolder(uri)?.uri.toString())?.uris.get(uri.toString()); +export function getDocumentForUri(uri: vscode.Uri): string | undefined { + return wsFolderIndex + .get(vscode.workspace.getWorkspaceFolder(uri)?.uri.toString() as string) + ?.uris.get(uri.toString()); } /** @@ -468,7 +471,7 @@ export function inferDocName(uri: vscode.Uri): string | undefined { // is necessary for the rare situaions for documents in /foo/bar/ have a different mapping than /foo/ // and the target URI is in /foo/bar/ or a subfolder of it. const containingPathsSorted = Array.from(containingPaths).sort((a, b) => b.length - a.length); - let result: string; + let result: string | undefined; for (const prefix of containingPathsSorted) { if (uri.path.startsWith(prefix)) { // We've identified the leading path segments that don't contribute to the document diff --git a/src/utils/documentPicker.ts b/src/utils/documentPicker.ts index e9388a41..cca9b28e 100644 --- a/src/utils/documentPicker.ts +++ b/src/utils/documentPicker.ts @@ -39,7 +39,7 @@ function createMultiSelectItem( delim = "/"; } result.fullName = parent + delim + item.Name; - result.label = " ".repeat(parentPad + 2) + result.label; + result.label = " ".repeat(parentPad! + 2) + result.label; result.description = result.fullName; } if (item.Type == 9 || item.Type == 10) { @@ -193,11 +193,11 @@ export async function pickDocuments(api: AtelierAPI, prompt?: string): Promise i.fullName === event.item.fullName); - if (event.button.tooltip.charAt(0) == "E") { + if (event.button.tooltip!.charAt(0) == "E") { // Expand this item expandItem(itemIdx); } else { @@ -233,8 +233,8 @@ export async function pickDocuments(api: AtelierAPI, prompt?: string): Promise { +): Promise { let sys: "0" | "1" = "0"; let gen: "0" | "1" = "0"; let map: "0" | "1" = "1"; @@ -298,7 +298,7 @@ export async function pickDocument( // Hiding these files in other cases will improve performance. const showWeb = typeof api.wsOrFile == "object" && !notIsfs(api.wsOrFile) && isfsConfig(api.wsOrFile).csp; - return new Promise((resolve) => { + return new Promise((resolve) => { const quickPick = vscode.window.createQuickPick(); quickPick.prompt = "You may also type a full document name with extension into the filter box and press 'Enter' to select it."; @@ -356,11 +356,11 @@ export async function pickDocument( quickPick.hide(); } if (button.tooltip == "System") { - sys = button.toggle.checked ? "1" : "0"; + sys = button.toggle!.checked ? "1" : "0"; } else if (button.tooltip == "Generated") { - gen = button.toggle.checked ? "1" : "0"; + gen = button.toggle!.checked ? "1" : "0"; } else { - map = button.toggle.checked ? "1" : "0"; + map = button.toggle!.checked ? "1" : "0"; } // Refresh the items list getItems(); diff --git a/src/utils/index.ts b/src/utils/index.ts index ae29465a..fc633d8b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -171,10 +171,10 @@ function otherDocExtsForUri(uri: vscode.Uri): string[] { } /** Determine the server name of a non-`isfs` non-ObjectScript file (any file that's not CLS,MAC,INT,INC). */ -export function getServerDocName(uri: vscode.Uri): string { +export function getServerDocName(uri: vscode.Uri): string | undefined { const wsFolder = vscode.workspace.getWorkspaceFolder(uri); if (!wsFolder) return; - const cspIdx = uri.path.lastIndexOf(cspAppsForUri(uri).find((cspApp) => uri.path.includes(cspApp + "/"))); + const cspIdx = uri.path.lastIndexOf(cspAppsForUri(uri).find((cspApp) => uri.path.includes(cspApp + "/"))!); if (cspIdx != -1) { return uri.path.slice(cspIdx); } else if (uri.path.toLowerCase().endsWith(".dfi")) { @@ -184,8 +184,8 @@ export function getServerDocName(uri: vscode.Uri): string { if (relativeFilePath == "") return; // uri isn't in the workspace folder. Should never happen. // Check for matching export settings first. If no match, use base name. const config = vscode.workspace.getConfiguration("objectscript.export", uri); - const folder: string = config.get("folder"); - const addCategory: boolean = config.get("addCategory"); + const folder: string = config.get("folder")!; + const addCategory: boolean = config.get("addCategory")!; let root = [ typeof folder == "string" && folder.length ? folder : null, addCategory ? getCategory(uri.fsPath, addCategory) : null, @@ -218,7 +218,7 @@ export function isImportableLocalFile(uri: vscode.Uri): boolean { if (!vscode.workspace.getWorkspaceFolder(uri)) return false; return ( cspAppsForUri(uri).some((cspApp) => uri.path.includes(cspApp + "/")) || - otherDocExtsForUri(uri).includes(uri.path.split(".").pop().toLowerCase()) + otherDocExtsForUri(uri).includes(uri.path.split(".").pop()!.toLowerCase()) ); } @@ -228,14 +228,17 @@ export const classNameRegex = /^[ \t]*Class[ \t]+(%?[\p{L}\d\u{100}-\u{ffff}]+(? /** A regex for extracting the name and type of a routine from its content */ export const routineNameTypeRegex = /^ROUTINE ([^\s]+)(?:\s*\[\s*Type\s*=\s*\b([a-z]{3})\b)?/i; -export function currentFileFromContent(uri: vscode.Uri, content: string | Buffer): CurrentTextFile | CurrentBinaryFile { +export function currentFileFromContent( + uri: vscode.Uri, + content: string | Buffer +): CurrentTextFile | CurrentBinaryFile | null { const fileName = uri.fsPath; const workspaceFolder = workspaceFolderOfUri(uri); if (!workspaceFolder) { // No workspace folders are open return null; } - const fileExt = fileName.split(".").pop().toLowerCase(); + const fileExt = fileName.split(".").pop()!.toLowerCase(); if ( notIsfs(uri) && !isClassOrRtn(uri.path) && @@ -258,7 +261,7 @@ export function currentFileFromContent(uri: vscode.Uri, content: string | Buffer [, name, ext = "mac"] = match; } } else { - name = notIsfs(uri) ? getServerDocName(uri) : isfsDocumentName(uri); + name = notIsfs(uri) ? getServerDocName(uri)! : isfsDocumentName(uri); } if (!name) { return null; @@ -288,7 +291,7 @@ export function currentFileFromContent(uri: vscode.Uri, content: string | Buffer } } -export function currentFile(document?: vscode.TextDocument): CurrentTextFile { +export function currentFile(document?: vscode.TextDocument | null): CurrentTextFile | null { document = document || (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document @@ -298,7 +301,7 @@ export function currentFile(document?: vscode.TextDocument): CurrentTextFile { return null; } const fileName = document.fileName; - const fileExt = fileName.split(".").pop().toLowerCase(); + const fileExt = fileName.split(".").pop()!.toLowerCase(); if ( notIsfs(document.uri) && !isClassOrRtn(document.uri.path) && @@ -324,7 +327,7 @@ export function currentFile(document?: vscode.TextDocument): CurrentTextFile { [, name, ext = "mac"] = match; } } else { - name = notIsfs(uri) ? getServerDocName(uri) : isfsDocumentName(uri); + name = notIsfs(uri) ? getServerDocName(uri)! : isfsDocumentName(uri); } if (!name) { return null; @@ -385,7 +388,7 @@ export function connectionTarget(uri?: vscode.Uri): ConnectionTarget { result.configName = parts.length === 2 ? parts[0] : firstFolder.uri.authority; result.apiTarget = firstFolder.uri; } else { - result.configName = workspaceState.get("workspaceFolder") || firstFolder ? firstFolder.name : ""; + result.configName = workspaceState.get("workspaceFolder") || firstFolder ? firstFolder!.name : ""; result.apiTarget = result.configName; } } @@ -411,7 +414,7 @@ export function currentWorkspaceFolder(document?: vscode.TextDocument): string { if (firstFolder && schemas.includes(firstFolder.uri.scheme)) { return firstFolder.uri.authority; } else { - return workspaceState.get("workspaceFolder") || firstFolder ? firstFolder.name : ""; + return workspaceState.get("workspaceFolder") || firstFolder ? firstFolder!.name : ""; } } @@ -426,7 +429,7 @@ export function workspaceFolderOfUri(uri: vscode.Uri): string { return vscode.workspace.getWorkspaceFolder(uri)?.name ?? ""; } else { const rootUri = uri.with({ path: "/" }).toString(); - const foundFolder = vscode.workspace.workspaceFolders.find( + const foundFolder = vscode.workspace.workspaceFolders!.find( (workspaceFolder) => workspaceFolder.uri.toString() == rootUri ); return foundFolder ? foundFolder.name : uri.authority; @@ -491,14 +494,19 @@ export async function portFromDockerCompose( return { docker: false, port: null, superserverPort: null }; } - const result = { port: null, superserverPort: null, docker: true, service }; + const result: { port: number | null; superserverPort: number | null; docker: boolean; service?: string } = { + port: null, + superserverPort: null, + docker: true, + service, + }; const workspaceFolder = uriOfWorkspaceFolder(workspaceFolderName); if (!workspaceFolder) { // No workspace folders are open return { docker: false, port: null, superserverPort: null }; } const workspaceFolderPath = workspaceFolder.fsPath; - const workspaceRootPath = vscode.workspace.workspaceFolders[0].uri.fsPath; + const workspaceRootPath = vscode.workspace.workspaceFolders![0].uri.fsPath; const cwd: string = await fileExists(vscode.Uri.file(path.join(workspaceFolderPath, file))).then(async (exists) => { if (exists) { @@ -631,7 +639,9 @@ interface WSServerRootFolderData { const wsServerRootFolders = new Map(); /** Cache information about redirection of `.vscode` folder contents for server-side folders */ -export async function addWsServerRootFolderData(wsFolders: readonly vscode.WorkspaceFolder[]): Promise { +export async function addWsServerRootFolderData( + wsFolders: readonly vscode.WorkspaceFolder[] | undefined +): Promise { if (!wsFolders?.length) return; return Promise.allSettled( wsFolders.map(async (wsFolder) => { @@ -662,10 +672,10 @@ export async function addWsServerRootFolderData(wsFolders: readonly vscode.Works webApps = await api .getCSPApps(false, "%SYS") .then((data) => data.result.content ?? []) - .catch(() => []); - cspApps.set(key, webApps); + .catch((): string[] => []); + cspApps.set(key, webApps!); } - value.canRedirectDotvscode = webApps.includes("/_vscode"); + value.canRedirectDotvscode = webApps!.includes("/_vscode"); } wsServerRootFolders.set(wsFolder.uri.toString(), value); }) @@ -772,7 +782,7 @@ export function parseClassMemberDefinition( document: vscode.TextDocument, symbol: vscode.DocumentSymbol, symbolLine?: number -): { definition: string; defEndLine: number; language: string; isPrivate: boolean } { +): { definition: string; defEndLine: number; language: string; isPrivate: boolean } | undefined { const languageServer: boolean = vscode.extensions.getExtension(lsExtensionId)?.isActive ?? false; if (symbolLine == undefined) { if (languageServer) { @@ -786,9 +796,9 @@ export function parseClassMemberDefinition( } } } - let definition: string; - let defEndLine: number; - for (let defLine = symbolLine; defLine < document.lineCount; defLine++) { + let definition: string | undefined; + let defEndLine: number | undefined; + for (let defLine = symbolLine!; defLine < document.lineCount; defLine++) { const line = document.lineAt(defLine); if (line.text.trimEnd().endsWith("{")) { definition = document.getText( @@ -804,7 +814,7 @@ export function parseClassMemberDefinition( const privateMatch = definitionNoDelimitedValues.match(privateRegex); return { definition, - defEndLine, + defEndLine: defEndLine!, language: languageMatch && languageMatch[1] ? languageMatch[1].toLowerCase() : "objectscript", isPrivate: privateMatch != null, }; @@ -838,10 +848,10 @@ export function methodOffsetToLine( method: string, offset = 0 ): number | undefined { - let line: number; + let line: number | undefined; const languageServer: boolean = vscode.extensions.getExtension(lsExtensionId)?.isActive ?? false; // Find the DocumentSymbol for this method - let currentSymbol: vscode.DocumentSymbol; + let currentSymbol: vscode.DocumentSymbol | undefined; for (const symbol of members) { if (stripClassMemberNameQuotes(symbol.name) === method && symbol.detail.toLowerCase().includes("method")) { currentSymbol = symbol; @@ -881,7 +891,7 @@ export function base64EncodeContent(content: Buffer): string[] { // Output is 4 chars for each 3 input, so 24573/3*4 = 32764 const chunkSize = 24573; let start = 0; - const result = []; + const result: string[] = []; while (start < content.byteLength) { result.push(content.toString("base64", start, start + chunkSize)); start += chunkSize; @@ -891,12 +901,12 @@ export function base64EncodeContent(content: Buffer): string[] { /** Returns `true` if `uri` has a class file extension */ export function isClass(uriOrName: string): boolean { - return "cls" == uriOrName.split(".").pop().toLowerCase(); + return "cls" == uriOrName.split(".").pop()!.toLowerCase(); } /** Returns `true` if `uri` has a class or routine file extension */ export function isClassOrRtn(uriOrName: string): boolean { - return ["cls", "mac", "int", "inc"].includes(uriOrName.split(".").pop().toLowerCase()); + return ["cls", "mac", "int", "inc"].includes(uriOrName.split(".").pop()!.toLowerCase()); } interface ConnQPItem extends vscode.QuickPickItem { @@ -920,7 +930,7 @@ export async function getWsServerConnection(minVersion?: string): Promise(fn: () => Promise): Promise { + async call(fn: () => Promise | undefined): Promise { // Acquire a slot in the semaphore. Will not reject. await this._semaphore.acquire(); try { // Execute the provided function - return await fn(); + return (await fn())!; } finally { // Always release the slot in the semaphore after the function completes this._semaphore.release(); diff --git a/tsconfig.base.json b/tsconfig.base.json index 0a87024f..583acc95 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -5,6 +5,7 @@ "noUnusedLocals": true, "noUnusedParameters": false, "experimentalDecorators": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "strictNullChecks": true, } } From 45d065b40adc5d9be7bd252338b2369d36e0918d Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Tue, 11 Aug 2026 15:35:21 -0400 Subject: [PATCH 29/62] Prepare 3.8.3 release (#1821) --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- package-lock.json | 16 ++++++++-------- package.json | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5da05b8..781621eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [3.8.3] 11-Aug-2026 +- Enhancements + - Support OAuth2 for authorizing with InterSystems servers (#1799, #1820) + - Update `Open InterSystems Document...` command to show a flat list of documents (#1804) +- Fixes + - Always overwrite entire class for Pull Server Changes when there's a conflict between the local copy and server copy (#1797) + - Improve management of web sessions when connection changes (#1808) + - Improve performance of server-side project query (#1809) + - Only timeout server connection check during extension activation (#1814) + - Sync server-side web app files as raw bytes (#1818) + - Upgrade dependencies + ## [3.8.2] 04-Jun-2026 - Enhancements - Add `Import Files...` command that supports many file types (#1764) diff --git a/README.md b/README.md index 548d7657..e9157b30 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ To unlock these features (optional): 1. Download and install a beta version from GitHub. This is necessary because Marketplace does not allow publication of extensions that use proposed APIs. - Go to https://github.com/intersystems-community/vscode-objectscript/releases - - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.2`, look for `3.8.3-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. - - Download the VSIX file (for example `vscode-objectscript-3.8.3-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. + - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.3`, look for `3.8.4-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. + - Download the VSIX file (for example `vscode-objectscript-3.8.4-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. 2. From [Command Palette](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) choose `Preferences: Configure Runtime Arguments`. 3. In the argv.json file that opens, add this line (required for both Stable and Insiders versions of VS Code): diff --git a/package-lock.json b/package-lock.json index 5d869e4f..7230076a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.18.0", + "axios": "^1.19.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", @@ -1554,13 +1554,13 @@ } }, "node_modules/axios": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", - "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } @@ -4565,9 +4565,9 @@ } }, "node_modules/mocha": { - "version": "11.7.6", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", - "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", + "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 59abf64e..230958e3 100644 --- a/package.json +++ b/package.json @@ -1754,7 +1754,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.18.0", + "axios": "^1.19.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", From 7dbafe63931c92af2c391f0470e936db14b795c4 Mon Sep 17 00:00:00 2001 From: ProjectBot Date: Tue, 11 Aug 2026 19:42:20 +0000 Subject: [PATCH 30/62] auto bump version with release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 230958e3..c3c840df 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-objectscript", "displayName": "InterSystems ObjectScript", "description": "InterSystems ObjectScript language support for Visual Studio Code", - "version": "3.8.3-SNAPSHOT", + "version": "3.8.4-SNAPSHOT", "icon": "images/logo.png", "aiKey": "InstrumentationKey=9cd75d51-697c-406c-a929-2bcf46e97c64;IngestionEndpoint=https://eastus2-4.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=a431c56f-8ccc-4b99-b5e9-fce3763215b1", "categories": [ From 5d4083016933f48bbe5a5bc08b38e12fdc86a2cc Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 17 Aug 2026 09:28:34 -0400 Subject: [PATCH 31/62] Fix password disclosure via serverForUri and stale cookie reuse after logout/401 (#1824) --- src/api/index.ts | 5 ++++- src/commands/connectFolderToServerNamespace.ts | 2 +- src/extension.ts | 13 ++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 5fc2b801..521ce83f 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -36,6 +36,7 @@ export async function logoutOfSessions(sessions?: string[]): Promise { (sessions ?? Array.from(cookiesMap.keys())).map((session) => { const cookie = cookiesMap.get(session); if (!cookie) return; + cookiesMap.delete(session); const url = session.slice(session.indexOf("@") + 1); return axios.head(`${url}/api/atelier/?CacheLogout=end`, { headers: { @@ -79,7 +80,8 @@ export class AtelierAPI { } public get config(): ConnectionSettings { - const { serverName, active = false, https = false, pathPrefix = "", auth } = this._config; + const { serverName, active = false, https = false, pathPrefix = "" } = this._config; + const auth = this._config.auth.clone(); const ns = this.namespace || this._config.ns; const wsKey = this.configName.toLowerCase(); const host = this.externalServer ? this._config.host : workspaceState.get(wsKey + ":host", this._config.host); @@ -462,6 +464,7 @@ export class AtelierAPI { } if (response.status === 401) { authRequestMap.delete(mapKey); + cookiesMap.delete(mapKey); if (this.wsOrFile && !checkingConnection) { setTimeout(() => { checkConnection( diff --git a/src/commands/connectFolderToServerNamespace.ts b/src/commands/connectFolderToServerNamespace.ts index 2e56ddfb..0671bd2f 100644 --- a/src/commands/connectFolderToServerNamespace.ts +++ b/src/commands/connectFolderToServerNamespace.ts @@ -62,7 +62,7 @@ export async function connectFolderToServerNamespace(): Promise { // Prepare a displayable form of its connection spec as a hint to the user // This will never return the default value (second parameter) because we only just resolved the connection spec. const connSpec = getResolvedConnectionSpec(serverName, undefined); - const connDisplayString = `${connSpec!.webServer.scheme}://${connSpec!.webServer.host}:${connSpec!.webServer.port}/${connSpec!.webServer.pathPrefix}`; + const connDisplayString = `${connSpec!.webServer.scheme}://${connSpec!.webServer.host}:${connSpec!.webServer.port}${connSpec!.webServer.pathPrefix}`; // Connect and fetch namespaces const api = new AtelierAPI(vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`)); const serverConf = vscode.workspace diff --git a/src/extension.ts b/src/extension.ts index 875efb02..817e9137 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2004,8 +2004,10 @@ function serverForUri(uri: vscode.Uri): serverManager.ServerForUri { // which will require explicit user consent to divulge the password to the requesting extension. const { serverName, active, host, https, port, superserverPort, pathPrefix, auth, ns, apiVersion, serverVersion } = api.config; - if (serverName !== "") { - const password = vscode.workspace + auth.clear() as void; + const password: string | undefined = + serverName && + vscode.workspace .getConfiguration( `intersystems.servers.${serverName.toLowerCase()}`, // objectscript(xml):// URIs are not in any workspace folder, @@ -2016,11 +2018,8 @@ function serverForUri(uri: vscode.Uri): serverManager.ServerForUri { ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri : uri ) - .get("password") as string | undefined; - if (password !== undefined) { - auth.resolve({ accessToken: password }); - } - } + .get("password"); + password && auth.resolve({ accessToken: password }); return { serverName, active, From 0700423ea428a9a25541aae8748a3402d2b0cdf4 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Tue, 18 Aug 2026 09:52:20 -0400 Subject: [PATCH 32/62] Support abstract documents when importing a client-side folder (#1827) --- src/commands/compile.ts | 19 +++++-------------- src/utils/documentIndex.ts | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/commands/compile.ts b/src/commands/compile.ts index a5502833..54c405af 100644 --- a/src/commands/compile.ts +++ b/src/commands/compile.ts @@ -14,7 +14,6 @@ import { DocumentContentProvider } from "../providers/DocumentContentProvider"; import { base64EncodeContent, compileErrorMsg, - cspAppsForUri, CurrentBinaryFile, currentFile, currentFileFromContent, @@ -34,7 +33,7 @@ import { } from "../utils"; import { StudioActions } from "./studio"; import { NodeBase, PackageNode, RootNode } from "../explorer/nodes"; -import { getUrisForDocument, updateIndex } from "../utils/documentIndex"; +import { getUrisForDocument, updateIndex, urisInFolder } from "../utils/documentIndex"; import { Document } from "../api/atelier"; /** @@ -474,6 +473,8 @@ export async function namespaceCompile(): Promise { } async function importFiles(files: vscode.Uri[], noCompile = false) { + if (!files.length) return; + const textDecoder = new TextDecoder(); const toCompile: (CurrentTextFile | CurrentBinaryFile)[] = []; const rateLimiter = new RateLimiter(50); await Promise.allSettled( @@ -483,7 +484,7 @@ async function importFiles(files: vscode.Uri[], noCompile = false) { const curFile = currentFileFromContent( uri, isText(uri.path.split("/").pop(), Buffer.from(contentBytes)) - ? new TextDecoder().decode(contentBytes) + ? textDecoder.decode(contentBytes) : Buffer.from(contentBytes) ); if (curFile) { @@ -500,7 +501,6 @@ async function importFiles(files: vscode.Uri[], noCompile = false) { if (!noCompile && toCompile.length > 0) { await compile(toCompile); } - return; } export async function importFolder(uri: vscode.Uri, noCompile = false): Promise { @@ -509,16 +509,7 @@ export async function importFolder(uri: vscode.Uri, noCompile = false): Promise< if ((await vscode.workspace.fs.stat(uri)).type != vscode.FileType.Directory) { return importFiles([uri], noCompile); } - let globpattern = "*.{cls,inc,int,mac}"; - if (cspAppsForUri(uri).findIndex((cspApp) => uri.path.includes(cspApp + "/") || uri.path.endsWith(cspApp)) != -1) { - // This folder is a CSP application, so import all files - // We need to include eveything because CSP applications can - // include non-InterSystems files - globpattern = "*"; - } - vscode.workspace - .findFiles(new vscode.RelativePattern(uri, `**/${globpattern}`)) - .then((files) => importFiles(files, noCompile)); + importFiles(urisInFolder(uri), noCompile); } export async function compileExplorerItems(nodes: NodeBase[]): Promise { diff --git a/src/utils/documentIndex.ts b/src/utils/documentIndex.ts index f4bcc9fb..0bf72656 100644 --- a/src/utils/documentIndex.ts +++ b/src/utils/documentIndex.ts @@ -447,7 +447,7 @@ export function inferDocName(uri: vscode.Uri): string | undefined { const wsFolder = vscode.workspace.getWorkspaceFolder(uri); if (!wsFolder) return; const index = wsFolderIndex.get(wsFolder.uri.toString()); - if (!index || !index.uris.size) return; + if (!index?.uris.size) return; // Get a list of all unique paths containing classes or routines that // do not contribute to the name of the documents contained within const containingPaths: Set = new Set(); @@ -499,7 +499,7 @@ export function inferDocUri(docName: string, wsFolder: vscode.WorkspaceFolder): const docExt = docName.slice(-4).toLowerCase(); if (!exts.includes(docExt)) return; const index = wsFolderIndex.get(wsFolder.uri.toString()); - if (!index || !index.uris.size) return; + if (!index?.uris.size) return; const docNameNoExt = docName.slice(0, -4); // remove extension const docPkgSegments = docNameNoExt.split(".").slice(0, -1); // remove class/routine name // For each indexed document, compute its containing path and measure how @@ -535,3 +535,22 @@ export function inferDocUri(docName: string, wsFolder: vscode.WorkspaceFolder): // Convert the document name to a file path and prepend the prefix return wsFolder.uri.with({ path: `${bestPathPrefix}${docNameNoExt.replaceAll(".", "/")}${docExt}` }); } + +/** + * Returns the `Uri`s of all indexed documents in folder `uri`. + * Assumes `uri` is a folder URI. Returns documents in nested folders. + */ +export function urisInFolder(uri: vscode.Uri): vscode.Uri[] { + const wsFolder = vscode.workspace.getWorkspaceFolder(uri); + if (!wsFolder) return []; + const index = wsFolderIndex.get(wsFolder.uri.toString()); + if (!index?.uris.size) return []; + let folderPath = uri.path; + if (!folderPath.endsWith("/")) folderPath = `${folderPath}/`; + const result: vscode.Uri[] = []; + index.uris.forEach((indexDocName, indexDocUriStr) => { + const indexDocUri = vscode.Uri.parse(indexDocUriStr); + if (indexDocUri.path.startsWith(folderPath)) result.push(indexDocUri); + }); + return result; +} From d05b9113b31e425edf738558a487e3ac4e60e9c0 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 18 Aug 2026 17:22:40 -0400 Subject: [PATCH 33/62] Retry once and delete cookies on failed (#1830) --- src/api/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/api/index.ts b/src/api/index.ts index 521ce83f..7a3f91a4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -466,6 +466,12 @@ export class AtelierAPI { authRequestMap.delete(mapKey); cookiesMap.delete(mapKey); if (this.wsOrFile && !checkingConnection) { + if (!options?._retriedAfter401) { + return this.request(minVersion, method, originalPath, body, params, headers, { + ...options, + _retriedAfter401: true, + }); + } setTimeout(() => { checkConnection( this.config.auth.resolved(), @@ -583,6 +589,9 @@ export class AtelierAPI { // In some cases schedule an automatic retry. // ENOTFOUND occurs if, say, the VPN to the server's network goes down. if (["ECONNREFUSED", "ENOTFOUND", "ECONNABORTED", "ERR_CANCELED"].includes(error.code)) { + // The cached cookie is unusable once the connection itself has failed; discard it + // so the next request re-authenticates instead of resending a stale cookie. + cookiesMap.delete(mapKey); panel.text = `${this.connInfo} $(debug-disconnect)`; panel.tooltip = "Disconnected"; workspaceState.update(this.configName.toLowerCase() + ":host", undefined); From 954d25ffb07d3d76e151576e68f68402db81ea3b Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Thu, 20 Aug 2026 08:11:00 -0400 Subject: [PATCH 34/62] Prepare 3.8.4 release (#1831) --- CHANGELOG.md | 10 ++++++++++ README.md | 4 ++-- src/extension.ts | 8 ++------ src/utils/documentIndex.ts | 4 ---- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 781621eb..ee97f56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [3.8.4] 20-Aug-2026 +- Fixes + - Fix issue where exported `serverForUri()` function may expose a password (#1824) + - Sending stale cookies can cause authorization failures (#1824) + - Support abstract documents when importing a client-side folder (#1827) + - Fix issue where user is intermittently unable to connect to server (#1830) + - Telemetry changes: + - Remove `clientSideFileSynced` event + - Add `usesServerManager` key to `workspaceFolder` event for client-side folders + ## [3.8.3] 11-Aug-2026 - Enhancements - Support OAuth2 for authorizing with InterSystems servers (#1799, #1820) diff --git a/README.md b/README.md index e9157b30..054a6927 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ To unlock these features (optional): 1. Download and install a beta version from GitHub. This is necessary because Marketplace does not allow publication of extensions that use proposed APIs. - Go to https://github.com/intersystems-community/vscode-objectscript/releases - - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.3`, look for `3.8.4-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. - - Download the VSIX file (for example `vscode-objectscript-3.8.4-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. + - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.4`, look for `3.8.5-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. + - Download the VSIX file (for example `vscode-objectscript-3.8.5-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. 2. From [Command Palette](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) choose `Preferences: Configure Runtime Arguments`. 3. In the argv.json file that opens, add this line (required for both Stable and Insiders versions of VS Code): diff --git a/src/extension.ts b/src/extension.ts index 817e9137..add7da89 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -466,7 +466,7 @@ export async function checkConnection( checkingConnection = true; const username = auth.username || "UnknownUser"; - const identity = username.startsWith("*") ? `using ${username.slice(1, -1)}` : `as user '\`${username}\`'`; + const identity = username.startsWith("*") ? `using ${username.slice(1, -1)}` : `as user \`${username}\``; // What we do when api.serverInfo call succeeds const gotServerInfo = async (info: Response>) => { @@ -836,6 +836,7 @@ function sendWsFolderTelemetryEvent(wsFolders: readonly vscode.WorkspaceFolder[] "config.conn.links": String(Object.keys(conf.get("conn.links", {})).length), "config.refreshClassesOnSync": !serverSide ? conf.get("refreshClassesOnSync") : undefined, "config.insertStubContent": !serverSide ? conf.get("insertStubContent") : undefined, + usesServerManager: !serverSide ? String(api.config.serverName != "") : undefined, }); }); } @@ -845,11 +846,6 @@ export function sendUnitTestTelemetryEvent(root: vscode.Uri, debug: boolean): vo reporter?.sendTelemetryEvent("unitTestRun", { scheme: root.scheme, debug: String(debug) }); } -/** Send a telemetry event that a non-class or routine client-side file was saved */ -export function sendClientSideSyncTelemetryEvent(fileExt: string): void { - reporter?.sendTelemetryEvent("clientSideFileSynced", { fileExt }); -} - /** Send a telemetry event that a low-code editor was opened */ export function sendLowCodeTelemetryEvent(editorType: string, scheme: string): void { reporter?.sendTelemetryEvent("lowCodeEditorOpened", { editorType, scheme }); diff --git a/src/utils/documentIndex.ts b/src/utils/documentIndex.ts index 0bf72656..38e16f99 100644 --- a/src/utils/documentIndex.ts +++ b/src/utils/documentIndex.ts @@ -17,7 +17,6 @@ import { import { isText } from "istextorbinary"; import { AtelierAPI } from "../api"; import { compile, importFile } from "../commands/compile"; -import { sendClientSideSyncTelemetryEvent } from "../extension"; interface WSFolderIndex { /** The `FileSystemWatcher` for this workspace folder */ @@ -352,9 +351,6 @@ async function updateIndexInternal( const file = await getCurrentFile(uri, true, content); if (!file) return result; result.addedOrChanged = file; - if (isImportableLocalFile(uri) && sync) { - sendClientSideSyncTelemetryEvent(file.fileName.split(".").pop()!.toLowerCase()); - } const documentUris = documents.get(file.name) ?? []; if (documentUris.some((u) => u.toString() == uriString)) { // No need to update the index since this document is already present From 3e8f1de607cf26d2e989a45f9ec4f111f6312c37 Mon Sep 17 00:00:00 2001 From: ProjectBot Date: Thu, 20 Aug 2026 12:18:08 +0000 Subject: [PATCH 35/62] auto bump version with release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c3c840df..24bfbb5a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-objectscript", "displayName": "InterSystems ObjectScript", "description": "InterSystems ObjectScript language support for Visual Studio Code", - "version": "3.8.4-SNAPSHOT", + "version": "3.8.5-SNAPSHOT", "icon": "images/logo.png", "aiKey": "InstrumentationKey=9cd75d51-697c-406c-a929-2bcf46e97c64;IngestionEndpoint=https://eastus2-4.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=a431c56f-8ccc-4b99-b5e9-fce3763215b1", "categories": [ From b39a10c4554fdaa20a54b0ba77df53671c37f3bd Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 20 Aug 2026 16:34:27 -0400 Subject: [PATCH 36/62] Fix crash when opening a file with no workspace folder open (#1834) * fix #1750 * Fix crash when opening a file with no workspace folder open (#1833) vscode.workspace.workspaceFolders is undefined when no folder/workspace is open, so the non-null assertion threw on every active editor change. --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index add7da89..9b0b7445 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1130,7 +1130,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { - if (vscode.workspace.workspaceFolders!.length > 1) { + if ((vscode.workspace.workspaceFolders?.length ?? 0) > 1) { const workspaceFolder = currentWorkspaceFolder(); if (workspaceFolder && workspaceFolder != workspaceState.get("workspaceFolder")) { await workspaceState.update("workspaceFolder", workspaceFolder); From 5c72e3c670958a8867c802ef0ec5235c809cbc62 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 21 Aug 2026 09:21:34 -0400 Subject: [PATCH 37/62] Fix crash from unresolved auth on named-server connections (#1836) --- src/extension.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 9b0b7445..32406816 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -165,6 +165,7 @@ type ConnConfig = Pick(); +const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. @@ -287,7 +288,7 @@ export async function resolveConnectionSpec( if (connSpec) { const accessToken = await resolvePassword(connSpec); if (connSpec.auth.resolve({ accessToken })) { - resolvedConnSpecs.set(serverName, connSpec as ResolvedConnSpec); + resolvedConnSpecs.set(serverName, connSpec as ConnSpec); } } } @@ -322,7 +323,7 @@ async function resolvePassword( export async function resolveUsernameAndPassword( serverName: string, oldSpec: serverManager.IServerSpec -): Promise { +): Promise { const { auth: _auth, ...newSpec } = oldSpec; newSpec.name = serverName; const auth = _auth?.clone(); @@ -341,8 +342,8 @@ export async function resolveUsernameAndPassword( /** Accessor for the cache of resolved connection specs */ export function getResolvedConnectionSpec( key: string, - dflt: ResolvedConnSpec | undefined -): ResolvedConnSpec | undefined { + dflt: serverManager.IServerSpec | undefined +): ConnSpec | undefined { let spec = resolvedConnSpecs.get(key); if (spec) { return spec; @@ -358,7 +359,9 @@ export function getResolvedConnectionSpec( } // Return the default if not found - return dflt; + if (dflt) { + return { ...dflt, auth: new BasicAuthorization(dflt.username, dflt.password) }; + } } /** The `api.serverId`s of all servers that are known to be inactive */ From 96505efb551b342d9f4b0bb5f311f9a5ca8baf07 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 24 Aug 2026 16:27:29 -0400 Subject: [PATCH 38/62] Restore unauthenticated-connection support (regression from OAuth2 rewrite) (#1840) --- src/commands/addServerNamespaceToWorkspace.ts | 10 +++++-- .../connectFolderToServerNamespace.ts | 14 +++++++--- src/extension.ts | 28 ++++++++++++------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/commands/addServerNamespaceToWorkspace.ts b/src/commands/addServerNamespaceToWorkspace.ts index 078dfad6..90f4a394 100644 --- a/src/commands/addServerNamespaceToWorkspace.ts +++ b/src/commands/addServerNamespaceToWorkspace.ts @@ -47,8 +47,14 @@ async function pickNamespaceOnServer(serverName: string): Promise { } await resolveConnectionSpec(serverName, undefined, folder); // Prepare a displayable form of its connection spec as a hint to the user - // This will never return the default value (second parameter) because we only just resolved the connection spec. const connSpec = getResolvedConnectionSpec(serverName, undefined); - const connDisplayString = `${connSpec!.webServer.scheme}://${connSpec!.webServer.host}:${connSpec!.webServer.port}${connSpec!.webServer.pathPrefix}`; + if (!connSpec) { + vscode.window.showErrorMessage( + `Failed to resolve connection details for server '${serverName}'. Folder was not connected.`, + "Dismiss" + ); + return; + } + const connDisplayString = `${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}${connSpec.webServer.pathPrefix}`; // Connect and fetch namespaces const api = new AtelierAPI(vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`)); const serverConf = vscode.workspace @@ -74,7 +80,7 @@ export async function connectFolderToServerNamespace(): Promise { !(serverConf!.workspaceValue && typeof serverConf!.workspaceValue[serverName] == "object") ) { // Need to manually set connection info if the server is defined at the workspace folder level - api.setConnSpec(serverName, connSpec!); + api.setConnSpec(serverName, connSpec); } const allNamespaces: string[] | undefined = await api .serverInfo(false) @@ -82,7 +88,7 @@ export async function connectFolderToServerNamespace(): Promise { .catch(async (error) => { if (error?.statusCode == 401 && !api.config.auth.resolved()) { // Attempt to resolve username and password and try again - const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec!); + const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec); if (newSpec) { // We were able to resolve credentials, so try again api.setConnSpec(api.config.serverName, newSpec); diff --git a/src/extension.ts b/src/extension.ts index 32406816..4008df7a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -229,6 +229,9 @@ const resolvedConnSpecs = new Map(); /** * If servermanager extension is available, fetch the connection spec unless already cached. * Prompt for credentials if necessary. + * + * Best-effort only: never contacts the server, so a no-username spec is assumed unauthenticated + * and cached as-is. If wrong, `resolveUsernameAndPassword` corrects it later on an actual 401. * @param serverName authority element of an isfs uri, or `objectscript.conn.server` property, or the name of a root folder with an `objectscript.conn.docker-compose` property object * @param uri if passed, re-check the `objectscript.conn.docker-compose` case in case servermanager API couldn't do that because we're still running our own `activate` method. */ @@ -251,7 +254,7 @@ export async function resolveConnectionSpec( } } const rawConnSpec = await serverManagerApi.getServerSpec(serverName, scope); - let connSpec; + let connSpec: ConnSpec | undefined; if (rawConnSpec) { connSpec = { ...rawConnSpec, @@ -286,18 +289,23 @@ export async function resolveConnectionSpec( } if (connSpec) { - const accessToken = await resolvePassword(connSpec); - if (connSpec.auth.resolve({ accessToken })) { - resolvedConnSpecs.set(serverName, connSpec as ConnSpec); + if (connSpec.auth.resolved() || isUnknownUser(connSpec.auth.username)) { + resolvedConnSpecs.set(serverName, connSpec); + } else { + const accessToken = await resolvePassword(connSpec); + if (connSpec.auth.resolve({ accessToken })) { + resolvedConnSpecs.set(serverName, connSpec); + } } } } -async function resolvePassword( - serverSpec: serverManager.IServerSpec, - ignoreUnauthenticated = false -): Promise { - if (!serverSpec.auth?.resolved() || ignoreUnauthenticated) { +function isUnknownUser(username: string | undefined): boolean { + return (username || "unknownuser").toLowerCase() == "unknownuser"; +} + +async function resolvePassword(serverSpec: serverManager.IServerSpec): Promise { + if (!serverSpec.auth?.resolved()) { const scopes = [serverSpec.name, serverSpec.auth?.username || ""]; // Handle Server Manager extension version < 3.8.0 @@ -328,7 +336,7 @@ export async function resolveUsernameAndPassword( newSpec.name = serverName; const auth = _auth?.clone(); - const accessToken = await resolvePassword({ ...newSpec, auth }, true); + const accessToken = await resolvePassword({ ...newSpec, auth }); if (auth?.resolve({ accessToken })) { // Update the connection spec resolvedConnSpecs.set(serverName, { From 7c331209495abddbdc0307c64589dd5cd8b3c7c1 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Tue, 25 Aug 2026 06:33:06 -0400 Subject: [PATCH 39/62] Fix intermittent issues when importing a client-side folder on Windows (#1839) --- src/utils/documentIndex.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/documentIndex.ts b/src/utils/documentIndex.ts index 38e16f99..3450fbc9 100644 --- a/src/utils/documentIndex.ts +++ b/src/utils/documentIndex.ts @@ -541,7 +541,12 @@ export function urisInFolder(uri: vscode.Uri): vscode.Uri[] { if (!wsFolder) return []; const index = wsFolderIndex.get(wsFolder.uri.toString()); if (!index?.uris.size) return []; - let folderPath = uri.path; + // Need to round-trip the argument through the Uri parser + // because it can change the case of Windows drive letters + // and the check below is case-sensitive. I found this + // preferable to adding a Windows-only case-insensitive + // carve-out in the logic below. + let folderPath = vscode.Uri.parse(uri.toString()).path; if (!folderPath.endsWith("/")) folderPath = `${folderPath}/`; const result: vscode.Uri[] = []; index.uris.forEach((indexDocName, indexDocUriStr) => { From 67376fbd312e5a92d2da89c6a3e50fb83c635e2a Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Tue, 25 Aug 2026 08:50:32 -0400 Subject: [PATCH 40/62] Fix server-side projects when connected to a very old server (#1842) --- src/utils/FileProviderUtil.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/utils/FileProviderUtil.ts b/src/utils/FileProviderUtil.ts index 30d8345c..d97df511 100644 --- a/src/utils/FileProviderUtil.ts +++ b/src/utils/FileProviderUtil.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { gte } from "semver"; import { AtelierAPI } from "../api"; import { ProjectItem } from "../commands/project"; @@ -125,10 +126,19 @@ export async function projectContentsFromUri(uri: vscode.Uri, flat = false): Pro query = `SELECT DISTINCT BY (${nameCol}) ${nameCol} ` + "Name, Type FROM %Studio.Project_ProjectItemsList(?,1) AS pil WHERE " + - "(Type NOT IN ('CLS','PKG','DIR','GBL') AND EXISTS (SELECT Size FROM %Library.RoutineMgr_StudioOpenDialog(pil.Name,1,1,1,1,0,1))) OR " + - "(Type = 'CLS' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = pil.Name)) OR " + - "(Type = 'PKG' AND EXISTS (SELECT TOP 1 dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID %STARTSWITH pil.Name||'.')) OR " + - "(Type = 'DIR' AND EXISTS (SELECT TOP 1 Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,0,0,1,'Name %STARTSWITH '''||pil.Name||'/''')))"; + (gte(api.config.serverVersion!, "2021.1.0") + ? "(Type NOT IN ('CLS','PKG','DIR','GBL') AND EXISTS (SELECT Size FROM %Library.RoutineMgr_StudioOpenDialog(pil.Name,1,1,1,1,0,1))) OR " + + "(Type = 'CLS' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = pil.Name)) OR " + + "(Type = 'PKG' AND EXISTS (SELECT TOP 1 dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID %STARTSWITH pil.Name||'.')) OR " + + "(Type = 'DIR' AND EXISTS (SELECT TOP 1 Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,0,0,1,'Name %STARTSWITH '''||pil.Name||'/''')))" + : // Need to use slower version of query on very old servers + "(Type = 'MAC' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.mac,*.int,*.inc,*.bas,*.mvi',1,1,1,1,0,1) AS sod WHERE pil.Name = sod.Name)) OR " + + "(Type = 'CSP' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1) AS sod WHERE '/'||pil.Name = sod.Name)) OR " + + "(Type NOT IN ('CLS','PKG','MAC','CSP','DIR','GBL') AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.other',1,1,1,1,0,1) AS sod WHERE " + + "$PIECE(sod.Name,'.',1,$LENGTH(sod.Name,'.')-1) = $PIECE(pil.Name,'.',1,$LENGTH(pil.Name,'.')-1) AND UPPER($PIECE(sod.Name,'.',$LENGTH(sod.Name,'.'))) = $PIECE(pil.Name,'.',$LENGTH(pil.Name,'.')))) OR " + + "(Type = 'CLS' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = pil.Name)) OR " + + "(Type = 'PKG' AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID %STARTSWITH pil.Name||'.')) OR " + + "(Type = 'DIR' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,0,0,1) AS sod WHERE pil.Name %STARTSWITH sod.Name||'/' OR pil.Name = sod.Name))"); parameters = [project]; } } From efe63d3a0dc2a47fc2c440471a76e5d3108d9002 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 09:14:47 -0400 Subject: [PATCH 41/62] Skip credential resolution for inactive connections during activation (#1835) --- src/extension.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index 4008df7a..b628ddb6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -931,6 +931,7 @@ export async function activate(context: vscode.ExtensionContext): Promise(); vscode.workspace.workspaceFolders?.map((workspaceFolder) => { const uri = workspaceFolder.uri; + if (notIsfs(uri) && !vscode.workspace.getConfiguration("objectscript.conn", workspaceFolder).get("active")) return; // Don't check inactive connections const { configName } = connectionTarget(uri); const conn = config("conn", configName); @@ -1752,6 +1753,8 @@ export async function activate(context: vscode.ExtensionContext): Promise(); e.added.map((workspaceFolder) => { const uri = workspaceFolder.uri; + if (notIsfs(uri) && !vscode.workspace.getConfiguration("objectscript.conn", workspaceFolder).get("active")) + return; // Don't check inactive connections const { configName } = connectionTarget(uri); toCheck.set(configName, uri); }); From 45b23b977e5bdaa42e43b2d4b43bae9bdcc5699a Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 11:31:44 -0400 Subject: [PATCH 42/62] save locally --- package.json | 5 +++++ src/utils/index.ts | 34 +++++++++++++++------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 24bfbb5a..f78dae96 100644 --- a/package.json +++ b/package.json @@ -1259,6 +1259,11 @@ "envFile": { "description": "Name of env-file for docker-compose configuration.", "type": "string" + }, + "command": { + "description": "Compose command to invoke. If omitted, auto-detected.", + "type": "string", + "enum": ["docker compose", "docker-compose", "podman compose"] } } } diff --git a/src/utils/index.ts b/src/utils/index.ts index fc633d8b..87b20658 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -459,8 +459,11 @@ export function notNull(el: any): boolean { return el !== null; } -/** Determine the compose command to use (`docker-compose` or `docker compose`). */ -async function composeCommand(cwd?: string): Promise { +/** Determine the compose command to use (`docker-compose` or `docker compose`), honoring an explicit override. */ +async function composeCommand(cwd?: string, configuredCommand?: string): Promise { + if (configuredCommand) { + return configuredCommand; + } return new Promise((resolve) => { let cmd = "docker compose"; exec(`${cmd} version`, { cwd }, (error) => { @@ -489,6 +492,7 @@ export async function portFromDockerCompose( internalPort = 52773, internalSuperserverPort = 1972, envFile, + command, } = dockerCompose; if (!internalPort || !internalSuperserverPort || !file || !service || service === "") { return { docker: false, port: null, superserverPort: null }; @@ -527,7 +531,7 @@ export async function portFromDockerCompose( } const envFileParam = envFile ? `--env-file ${envFile}` : ""; - const cmd = `${await composeCommand(cwd)} -f ${file} ${envFileParam} `; + const cmd = `${await composeCommand(cwd, command)} -f ${file} ${envFileParam} `; return new Promise((resolve, reject) => { exec(`${cmd} ps --services --filter status=running`, { cwd }, (error, stdout) => { @@ -572,19 +576,15 @@ export async function portFromDockerCompose( export async function terminalWithDocker(): Promise { const { ns, "docker-compose": dockerCompose } = config("conn"); - const { service, file = "docker-compose.yml" } = dockerCompose; + const { service, file = "docker-compose.yml", command } = dockerCompose; const workspace = currentWorkspaceFolder(); const terminalName = `ObjectScript:${workspace}`; let terminal = terminals.find((t) => t.name == terminalName && t.exitStatus == undefined); if (!terminal) { - let exe = await composeCommand(); - const argsArr: string[] = []; - if (exe == "docker compose") { - const exeSplit = exe.split(" "); - exe = exeSplit[0]; - argsArr.push(exeSplit[1]); - } + const exeSplit = (await composeCommand(undefined, command)).split(" "); + const exe = exeSplit[0]; + const argsArr = exeSplit.slice(1); terminal = vscode.window.createTerminal( terminalName, `${exe}${process.platform == "win32" ? ".exe" : ""}`, @@ -607,19 +607,15 @@ export async function terminalWithDocker(): Promise { export async function shellWithDocker(): Promise { const { "docker-compose": dockerCompose } = config("conn"); - const { service, file = "docker-compose.yml" } = dockerCompose; + const { service, file = "docker-compose.yml", command } = dockerCompose; const workspace = currentWorkspaceFolder(); const terminalName = `Shell:${workspace}`; let terminal = terminals.find((t) => t.name == terminalName && t.exitStatus == undefined); if (!terminal) { - let exe = await composeCommand(); - const argsArr: string[] = []; - if (exe == "docker compose") { - const exeSplit = exe.split(" "); - exe = exeSplit[0]; - argsArr.push(exeSplit[1]); - } + const exeSplit = (await composeCommand(undefined, command)).split(" "); + const exe = exeSplit[0]; + const argsArr = exeSplit.slice(1); terminal = vscode.window.createTerminal( terminalName, `${exe}${process.platform == "win32" ? ".exe" : ""}`, From c48471c35e555a5b5d85f70368683d780123264b Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 13:06:42 -0400 Subject: [PATCH 43/62] save --- src/utils/index.ts | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 87b20658..50d2a2c4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -534,11 +534,17 @@ export async function portFromDockerCompose( const cmd = `${await composeCommand(cwd, command)} -f ${file} ${envFileParam} `; return new Promise((resolve, reject) => { - exec(`${cmd} ps --services --filter status=running`, { cwd }, (error, stdout) => { + // `--services --filter status=running` isn't supported by podman-compose, so use `--format json` + // instead and inspect each entry ourselves; docker compose exposes the service name directly, + // podman-compose only via a label. + exec(`${cmd} ps --format json`, { cwd }, (error, stdout) => { if (error) { reject(error.message); } - if (!stdout.replaceAll("\r", "").split("\n").includes(service)) { + const isRunning = parseComposePsJson(stdout).some( + (entry) => (entry.Service ?? entry.Labels?.["com.docker.compose.service"]) === service && entry.State === "running" + ); + if (!isRunning) { reject(`Service '${service}' not found in '${path.join(cwd, file)}', or not running.`); } @@ -546,7 +552,8 @@ export async function portFromDockerCompose( if (error) { reject(error.message); } - const [, port] = stdout.match(/:(\d+)/) || []; + // docker compose prints "host:port"; podman-compose prints a bare port number. + const [, port] = stdout.trim().match(/(\d+)$/) || []; if (!port) { reject(`Webserver port ${internalPort} not published for service '${service}' in '${path.join(cwd, file)}'.`); } @@ -560,7 +567,7 @@ export async function portFromDockerCompose( } reject(error.message); } - const [, superserverPort] = stdout.match(/:(\d+)/) || []; + const [, superserverPort] = stdout.trim().match(/(\d+)$/) || []; if (!superserverPort) { reject( `Superserver port ${internalSuperserverPort} not published for service '${service}' in '${path.join(cwd, file)}'.` @@ -574,6 +581,27 @@ export async function portFromDockerCompose( }); } +/** Parse `compose ps --format json` output, which is a JSON array for docker compose or one JSON object per line for podman-compose. */ +function parseComposePsJson(stdout: string): any[] { + try { + const parsed = JSON.parse(stdout); + return Array.isArray(parsed) ? parsed : [parsed]; + } catch { + return stdout + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => { + try { + return JSON.parse(line); + } catch { + return undefined; + } + }) + .filter((entry) => entry !== undefined); + } +} + export async function terminalWithDocker(): Promise { const { ns, "docker-compose": dockerCompose } = config("conn"); const { service, file = "docker-compose.yml", command } = dockerCompose; From 469ee89f1f18ef8d0acd8f4f8cb646ead7b36f27 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 13:18:01 -0400 Subject: [PATCH 44/62] Revert "save locally"; Add branchProtection This reverts commit 45b23b977e5bdaa42e43b2d4b43bae9bdcc5699a. --- .vscode/settings.json | 3 +++ package.json | 5 ----- src/utils/index.ts | 34 +++++++++++++++++++--------------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ebd59163..e7bd40cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,7 @@ "editor.formatOnSave": true }, "js/ts.tsdk.path": "node_modules/typescript/lib", + "git.branchProtection": [ + "master" + ], } diff --git a/package.json b/package.json index f78dae96..24bfbb5a 100644 --- a/package.json +++ b/package.json @@ -1259,11 +1259,6 @@ "envFile": { "description": "Name of env-file for docker-compose configuration.", "type": "string" - }, - "command": { - "description": "Compose command to invoke. If omitted, auto-detected.", - "type": "string", - "enum": ["docker compose", "docker-compose", "podman compose"] } } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 87b20658..fc633d8b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -459,11 +459,8 @@ export function notNull(el: any): boolean { return el !== null; } -/** Determine the compose command to use (`docker-compose` or `docker compose`), honoring an explicit override. */ -async function composeCommand(cwd?: string, configuredCommand?: string): Promise { - if (configuredCommand) { - return configuredCommand; - } +/** Determine the compose command to use (`docker-compose` or `docker compose`). */ +async function composeCommand(cwd?: string): Promise { return new Promise((resolve) => { let cmd = "docker compose"; exec(`${cmd} version`, { cwd }, (error) => { @@ -492,7 +489,6 @@ export async function portFromDockerCompose( internalPort = 52773, internalSuperserverPort = 1972, envFile, - command, } = dockerCompose; if (!internalPort || !internalSuperserverPort || !file || !service || service === "") { return { docker: false, port: null, superserverPort: null }; @@ -531,7 +527,7 @@ export async function portFromDockerCompose( } const envFileParam = envFile ? `--env-file ${envFile}` : ""; - const cmd = `${await composeCommand(cwd, command)} -f ${file} ${envFileParam} `; + const cmd = `${await composeCommand(cwd)} -f ${file} ${envFileParam} `; return new Promise((resolve, reject) => { exec(`${cmd} ps --services --filter status=running`, { cwd }, (error, stdout) => { @@ -576,15 +572,19 @@ export async function portFromDockerCompose( export async function terminalWithDocker(): Promise { const { ns, "docker-compose": dockerCompose } = config("conn"); - const { service, file = "docker-compose.yml", command } = dockerCompose; + const { service, file = "docker-compose.yml" } = dockerCompose; const workspace = currentWorkspaceFolder(); const terminalName = `ObjectScript:${workspace}`; let terminal = terminals.find((t) => t.name == terminalName && t.exitStatus == undefined); if (!terminal) { - const exeSplit = (await composeCommand(undefined, command)).split(" "); - const exe = exeSplit[0]; - const argsArr = exeSplit.slice(1); + let exe = await composeCommand(); + const argsArr: string[] = []; + if (exe == "docker compose") { + const exeSplit = exe.split(" "); + exe = exeSplit[0]; + argsArr.push(exeSplit[1]); + } terminal = vscode.window.createTerminal( terminalName, `${exe}${process.platform == "win32" ? ".exe" : ""}`, @@ -607,15 +607,19 @@ export async function terminalWithDocker(): Promise { export async function shellWithDocker(): Promise { const { "docker-compose": dockerCompose } = config("conn"); - const { service, file = "docker-compose.yml", command } = dockerCompose; + const { service, file = "docker-compose.yml" } = dockerCompose; const workspace = currentWorkspaceFolder(); const terminalName = `Shell:${workspace}`; let terminal = terminals.find((t) => t.name == terminalName && t.exitStatus == undefined); if (!terminal) { - const exeSplit = (await composeCommand(undefined, command)).split(" "); - const exe = exeSplit[0]; - const argsArr = exeSplit.slice(1); + let exe = await composeCommand(); + const argsArr: string[] = []; + if (exe == "docker compose") { + const exeSplit = exe.split(" "); + exe = exeSplit[0]; + argsArr.push(exeSplit[1]); + } terminal = vscode.window.createTerminal( terminalName, `${exe}${process.platform == "win32" ? ".exe" : ""}`, From 5ca339123fad9cacc0e2151ec914b01b8e273df8 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 13:34:36 -0400 Subject: [PATCH 45/62] Revert master to efe63d3 (before accidental personal-branch merge) Undoes 45b23b9, c48471c, 469ee89, c7afd54, which landed on master via an unintended fast-forward from a personal branch. --- src/utils/index.ts | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 7e8efea9..fc633d8b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -530,17 +530,11 @@ export async function portFromDockerCompose( const cmd = `${await composeCommand(cwd)} -f ${file} ${envFileParam} `; return new Promise((resolve, reject) => { - // `--services --filter status=running` isn't supported by podman-compose, so use `--format json` - // instead and inspect each entry ourselves; docker compose exposes the service name directly, - // podman-compose only via a label. - exec(`${cmd} ps --format json`, { cwd }, (error, stdout) => { + exec(`${cmd} ps --services --filter status=running`, { cwd }, (error, stdout) => { if (error) { reject(error.message); } - const isRunning = parseComposePsJson(stdout).some( - (entry) => (entry.Service ?? entry.Labels?.["com.docker.compose.service"]) === service && entry.State === "running" - ); - if (!isRunning) { + if (!stdout.replaceAll("\r", "").split("\n").includes(service)) { reject(`Service '${service}' not found in '${path.join(cwd, file)}', or not running.`); } @@ -548,8 +542,7 @@ export async function portFromDockerCompose( if (error) { reject(error.message); } - // docker compose prints "host:port"; podman-compose prints a bare port number. - const [, port] = stdout.trim().match(/(\d+)$/) || []; + const [, port] = stdout.match(/:(\d+)/) || []; if (!port) { reject(`Webserver port ${internalPort} not published for service '${service}' in '${path.join(cwd, file)}'.`); } @@ -563,7 +556,7 @@ export async function portFromDockerCompose( } reject(error.message); } - const [, superserverPort] = stdout.trim().match(/(\d+)$/) || []; + const [, superserverPort] = stdout.match(/:(\d+)/) || []; if (!superserverPort) { reject( `Superserver port ${internalSuperserverPort} not published for service '${service}' in '${path.join(cwd, file)}'.` @@ -577,27 +570,6 @@ export async function portFromDockerCompose( }); } -/** Parse `compose ps --format json` output, which is a JSON array for docker compose or one JSON object per line for podman-compose. */ -function parseComposePsJson(stdout: string): any[] { - try { - const parsed = JSON.parse(stdout); - return Array.isArray(parsed) ? parsed : [parsed]; - } catch { - return stdout - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0) - .map((line) => { - try { - return JSON.parse(line); - } catch { - return undefined; - } - }) - .filter((entry) => entry !== undefined); - } -} - export async function terminalWithDocker(): Promise { const { ns, "docker-compose": dockerCompose } = config("conn"); const { service, file = "docker-compose.yml" } = dockerCompose; From 0bd0a7d89735fc333a1fa4912598ce5ed472d971 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 25 Aug 2026 18:36:53 -0400 Subject: [PATCH 46/62] `serverForUri` should either set ns or be undefined (#1843) --- src/api/index.ts | 4 +-- src/extension.ts | 39 +++++++++++++----------- src/providers/DocumentContentProvider.ts | 4 +-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 7a3f91a4..53a80938 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -58,7 +58,7 @@ export interface ConnectionSettings { port: number; superserverPort?: number; pathPrefix?: string; - ns: string | undefined; + ns?: string; auth: Authorization; docker?: boolean; dockerService?: string; @@ -254,7 +254,7 @@ export class AtelierAPI { serverName = ""; } - const ns = namespace ? namespace.toUpperCase() : conn.ns ? (conn.ns as string).toUpperCase() : undefined; + const ns = namespace?.toUpperCase() || conn.ns?.toUpperCase(); if (serverName !== "") { const { webServer: { scheme, host, port, pathPrefix = "" }, diff --git a/src/extension.ts b/src/extension.ts index b628ddb6..10b31826 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2004,7 +2004,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { +async function asyncServerForUri(uri: vscode.Uri): Promise { const server = serverForUri(uri); + if (!server) { + return; + } if (!server.port) { let { apiTarget } = connectionTarget(uri); if (apiTarget instanceof vscode.Uri) { diff --git a/src/providers/DocumentContentProvider.ts b/src/providers/DocumentContentProvider.ts index edf9d8f1..099ffeac 100644 --- a/src/providers/DocumentContentProvider.ts +++ b/src/providers/DocumentContentProvider.ts @@ -9,8 +9,8 @@ import { getUrisForDocument } from "../utils/documentIndex"; import { isfsConfig, IsfsUriParam } from "../utils/FileProviderUtil"; export function compareConns( - conn1: { ns: any; server?: any; host: any; port: any; "docker-compose"?: any }, - conn2: { ns: any; server?: any; host: any; port: any; "docker-compose"?: any } + conn1: { ns?: string; server?: any; host: any; port: any; "docker-compose"?: any }, + conn2: { ns?: string; server?: any; host: any; port: any; "docker-compose"?: any } ): boolean { if (conn1.ns === conn2.ns) { // Same namespace name From 024815e73205a52d829938e84b230ac0ac4b818a Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 26 Aug 2026 09:47:49 -0400 Subject: [PATCH 47/62] Relax server-side URI validation for routines whose dotted section before the extension is only digits (#1845) --- .../FileSystemProvider/FileSystemProvider.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/providers/FileSystemProvider/FileSystemProvider.ts b/src/providers/FileSystemProvider/FileSystemProvider.ts index 0e1f0611..22a90d26 100644 --- a/src/providers/FileSystemProvider/FileSystemProvider.ts +++ b/src/providers/FileSystemProvider/FileSystemProvider.ts @@ -251,7 +251,7 @@ export function isfsDocumentName(uri: vscode.Uri, csp?: boolean, pkg = false): s * error, or a more descriptive custom error when `descriptiveError` is `true`, * when `uri`'s path is not in "canonical form". */ -function validateUriIsCanonical(uri: vscode.Uri, descriptiveError = false): void { +function validateUriIsCanonical(uri: vscode.Uri): void { const numDots = uri.path.split(".").length - 1; const lastFour = uri.path.slice(-4); if (!isfsConfig(uri).csp && [".cls", ".mac", ".int", ".inc"].includes(lastFour.toLowerCase())) { @@ -259,10 +259,12 @@ function validateUriIsCanonical(uri: vscode.Uri, descriptiveError = false): void ? "File extension must be in all lowercase" : uri.path.startsWith("/%") && lastFour == ".cls" && numDots == 1 && uri.path.split("/").length == 2 ? "Must use the full name of %Library classes" - : numDots > 1 && !(numDots == 2 && /\.G?\d\.int$/.test(uri.path)) - ? "Packages must use forward slashes as delimiters instead of dots" + : numDots > 1 && !((numDots == 2 && /\.G?\d\.int$/.test(uri.path)) || /\.\d+\.(mac|int|inc)$/.test(uri.path)) + ? // Need to allow URIs for routines with last dotted sections that are entirely digits + // due to a server-side bug. These routines are not properly split into folders. + "Packages must use forward slashes as delimiters instead of dots" : undefined; - if (msg) throw descriptiveError ? new vscode.FileSystemError(msg) : vscode.FileSystemError.FileNotFound(uri); + if (msg) throw new vscode.FileSystemError(msg); } } @@ -526,7 +528,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { const originalUri = vscode.Uri.parse(originalUriString); this._needsUpdate.delete(originalUriString); uri = redirectDotvscodeRoot(uri, new vscode.FileSystemError("Server does not have a /_vscode web application")); - validateUriIsCanonical(uri, true); + validateUriIsCanonical(uri); const csp = isCSP(uri); const fileName = isfsDocumentName(uri, csp); if (fileName.startsWith(".")) { @@ -811,7 +813,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider { throw new vscode.FileSystemError("Cannot rename a file across workspace folders"); } validateUriIsCanonical(oldUri); - validateUriIsCanonical(newUri, true); + validateUriIsCanonical(newUri); // Check if the destination exists let newFileStat: vscode.FileStat | undefined; try { From f86793f9339cba45494c83e1afe0c269ca433e65 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 26 Aug 2026 09:50:43 -0400 Subject: [PATCH 48/62] Deprecate configuring a server host/port/credentials in `objectscript.conn` (#1844) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 24bfbb5a..22a8480e 100644 --- a/package.json +++ b/package.json @@ -1196,6 +1196,7 @@ "port": { "type": "integer", "description": "TCP port number the web server listens on.", + "deprecationMessage": "It is recommended to define a server connection in `#intersystems.servers#` instead of here.", "minimum": 1, "maximum": 65535 }, From 8479a5ed5be643224d25aed12dfcb51812219bb9 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Wed, 26 Aug 2026 13:41:50 -0400 Subject: [PATCH 49/62] `serverForUri` should preserve passwords stored in `settings.json["objectscript.conn"]` (#1846) * fix #1750 * no need to clear password * save --- src/extension.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 10b31826..f1164853 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2015,8 +2015,8 @@ function serverForUri(uri: vscode.Uri): serverManager.ServerForUri | undefined { const { serverName, active, host, https, port, superserverPort, pathPrefix, auth, ns, apiVersion, serverVersion } = api.config; auth.clear() as void; - const password: string | undefined = - serverName && + const password = + config("conn", configName).password || vscode.workspace .getConfiguration( `intersystems.servers.${serverName.toLowerCase()}`, @@ -2028,7 +2028,7 @@ function serverForUri(uri: vscode.Uri): serverManager.ServerForUri | undefined { ? vscode.workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == configNameLower)?.uri : uri ) - .get("password"); + .get("password"); password && auth.resolve({ accessToken: password }); if (ns) { return { From 4cf614138589522a0aa3ca8b4f00e263ea840725 Mon Sep 17 00:00:00 2001 From: Brett Saviano Date: Wed, 2 Sep 2026 07:51:54 -0400 Subject: [PATCH 50/62] Prepare 3.8.5 release (#1850) --- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 ++-- package-lock.json | 12 ++++++------ package.json | 2 +- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee97f56d..8abbc925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## [3.8.5] 02-Sep-2026 +- Fixes + - Fix crash when opening a file with no workspace folder open (#1834) + - Skip credential resolution for inactive connections during activation (#1835) + - Fix crash from unresolved auth on named-server connections (#1836) + - Fix intermittent issues when importing a client-side folder on Windows (#1839) + - Restore unauthenticated-connection support (regression from OAuth2 rewrite) (#1840) + - Fix server-side projects when connected to a very old server (#1842) + - `serverForUri()` should either return a usable server definition or `undefined` (#1843) + - Deprecate configuring a server host/port/credentials in `objectscript.conn` in favor of using Server Manager (#1844) + - Relax server-side URI validation for routines whose dotted section before the extension is only digits (#1845) + - Passwords stored in plaintext in `objectscript.conn` should be passed to other extensions in `serverForUri()` (#1846) + - Upgrade dependencies + ## [3.8.4] 20-Aug-2026 - Fixes - Fix issue where exported `serverForUri()` function may expose a password (#1824) diff --git a/README.md b/README.md index 054a6927..9b636d4c 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ To unlock these features (optional): 1. Download and install a beta version from GitHub. This is necessary because Marketplace does not allow publication of extensions that use proposed APIs. - Go to https://github.com/intersystems-community/vscode-objectscript/releases - - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.4`, look for `3.8.5-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. - - Download the VSIX file (for example `vscode-objectscript-3.8.5-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. + - Locate the beta immediately above the release you installed from Marketplace. For instance, if you installed `3.8.5`, look for `3.8.6-beta.1`. This will be functionally identical to the Marketplace version apart from being able to use proposed APIs. + - Download the VSIX file (for example `vscode-objectscript-3.8.6-beta.1.vsix`) and install it. One way to install a VSIX is to drag it from your download folder and drop it onto the list of extensions in the Extensions view of VS Code. 2. From [Command Palette](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) choose `Preferences: Configure Runtime Arguments`. 3. In the argv.json file that opens, add this line (required for both Stable and Insiders versions of VS Code): diff --git a/package-lock.json b/package-lock.json index 7230076a..a4e12591 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-objectscript", - "version": "3.8.3-SNAPSHOT", + "version": "3.8.5-SNAPSHOT", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-objectscript", - "version": "3.8.3-SNAPSHOT", + "version": "3.8.5-SNAPSHOT", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -14,7 +14,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.19.0", + "axios": "^1.20.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", @@ -1554,9 +1554,9 @@ } }, "node_modules/axios": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", - "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", diff --git a/package.json b/package.json index 22a8480e..e04244aa 100644 --- a/package.json +++ b/package.json @@ -1755,7 +1755,7 @@ "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", "@xmldom/xmldom": "^0.9.10", - "axios": "^1.19.0", + "axios": "^1.20.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", "minimatch": "^10.2.5", From d06a5efe3d51c7de88b76bae88a38a51180448ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:56:34 -0400 Subject: [PATCH 51/62] Bump browserslist from 4.28.1 to 4.28.8 (#1851) Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.1 to 4.28.8. - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](https://github.com/browserslist/browserslist/compare/4.28.1...4.28.8) --- updated-dependencies: - dependency-name: browserslist dependency-version: 4.28.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 56 ++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4e12591..d6dd2cfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1628,13 +1628,16 @@ ] }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binaryextensions": { @@ -1704,9 +1707,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -1724,11 +1727,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -1841,9 +1844,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", - "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "dev": true, "funding": [ { @@ -2205,9 +2208,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", "dev": true, "license": "ISC" }, @@ -4732,11 +4735,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-inspect": { "version": "1.13.4", @@ -6339,9 +6345,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", "dev": true, "funding": [ { From cdb818526d48ca87a1f5cc28a92322030aa77d39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:57:47 -0400 Subject: [PATCH 52/62] Bump serialize-javascript and mocha (#1852) Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 7.1.1 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together. Updates `serialize-javascript` from 6.0.2 to 7.1.1 - [Release notes](https://github.com/yahoo/serialize-javascript/releases) - [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.2...v7.1.1) Updates `mocha` from 11.8.0 to 12.0.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v11.8.0...v12.0.0) --- updated-dependencies: - dependency-name: serialize-javascript dependency-version: 7.1.1 dependency-type: indirect - dependency-name: mocha dependency-version: 12.0.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 403 +++++++++------------------------------------- package.json | 2 +- 2 files changed, 78 insertions(+), 327 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6dd2cfa..c85d2add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "eslint-plugin-promise": "^7.2.1", "glob": "^11.1.0", "globals": "^15.12.0", - "mocha": "^11.7.5", + "mocha": "^12.0.0", "path-browserify": "^1.0.1", "prettier": "^3.3.1", "ts-loader": "^9.5.1", @@ -589,17 +589,6 @@ ], "license": "MIT" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -1830,19 +1819,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -1881,16 +1857,16 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -1932,21 +1908,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -2102,19 +2063,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2165,9 +2113,9 @@ } }, "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3235,16 +3183,6 @@ "node": ">= 0.4" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3465,16 +3403,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3972,16 +3900,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -4427,23 +4345,6 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -4559,137 +4460,98 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mocha": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", - "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-12.0.0.tgz", + "integrity": "sha512-NYNh5IFt6WYqm9bi4601m7vix8MZdXC0DwS4gY6WhXO2RgJWhivhISVmq1oklCif18OSI9l+vx5Mdm5oh1XGiQ==", "dev": true, "license": "MIT", "dependencies": { "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", + "chokidar": "^5.0.0", "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", + "diff": "^9.0.0", "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", + "glob": "^13.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", + "is-unicode-supported": "^0.1.0", + "js-yaml": "^5.0.0", + "minimatch": "^10.2.2", "ms": "^2.1.3", "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", + "serialize-javascript": "^7.0.2", + "strip-json-comments": "^5.0.3", "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" + "workerpool": "^10.0.0" }, "bin": { - "_mocha": "bin/_mocha", "mocha": "bin/mocha.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/mocha/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/mocha/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/mocha/node_modules/js-yaml": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz", + "integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==", "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bin": { + "js-yaml": "bin/js-yaml.mjs" } }, - "node_modules/mocha/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/mocha/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/supports-color": { @@ -5089,16 +4951,17 @@ "dev": true }, "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5209,15 +5072,6 @@ "node": ">=6" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -5246,13 +5100,13 @@ "dev": true }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -5328,16 +5182,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5432,26 +5276,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -5563,13 +5387,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz", + "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/set-function-length": { @@ -6649,30 +6473,12 @@ } }, "node_modules/workerpool": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", - "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.3.tgz", + "integrity": "sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==", "dev": true, "license": "Apache-2.0" }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -6712,61 +6518,6 @@ } } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e04244aa..e398e965 100644 --- a/package.json +++ b/package.json @@ -1742,7 +1742,7 @@ "eslint-plugin-promise": "^7.2.1", "glob": "^11.1.0", "globals": "^15.12.0", - "mocha": "^11.7.5", + "mocha": "^12.0.0", "path-browserify": "^1.0.1", "prettier": "^3.3.1", "ts-loader": "^9.5.1", From c9c0a5173b16fdbe5e33cac0d0be0cf5fe2bf695 Mon Sep 17 00:00:00 2001 From: ProjectBot Date: Wed, 2 Sep 2026 12:03:39 +0000 Subject: [PATCH 53/62] auto bump version with release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e398e965..73d823b7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-objectscript", "displayName": "InterSystems ObjectScript", "description": "InterSystems ObjectScript language support for Visual Studio Code", - "version": "3.8.5-SNAPSHOT", + "version": "3.8.6-SNAPSHOT", "icon": "images/logo.png", "aiKey": "InstrumentationKey=9cd75d51-697c-406c-a929-2bcf46e97c64;IngestionEndpoint=https://eastus2-4.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=a431c56f-8ccc-4b99-b5e9-fce3763215b1", "categories": [ From 635033a67683430e91c85988bde12cd35bd97659 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:24:33 -0400 Subject: [PATCH 54/62] Bump @xmldom/xmldom from 0.9.10 to 0.9.12 (#1854) --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index c85d2add..7e1de87d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "vscode-objectscript", - "version": "3.8.5-SNAPSHOT", + "version": "3.8.6-SNAPSHOT", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-objectscript", - "version": "3.8.5-SNAPSHOT", + "version": "3.8.6-SNAPSHOT", "hasInstallScript": true, "license": "MIT", "dependencies": { "@vscode/debugadapter": "^1.68.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", - "@xmldom/xmldom": "^0.9.10", + "@xmldom/xmldom": "^0.9.12", "axios": "^1.20.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", @@ -1204,9 +1204,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", - "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", "license": "MIT", "engines": { "node": ">=14.6" diff --git a/package.json b/package.json index 73d823b7..3d0b5de0 100644 --- a/package.json +++ b/package.json @@ -1754,7 +1754,7 @@ "@vscode/debugadapter": "^1.68.0", "@vscode/debugprotocol": "^1.68.0", "@vscode/extension-telemetry": "^1.5.2", - "@xmldom/xmldom": "^0.9.10", + "@xmldom/xmldom": "^0.9.12", "axios": "^1.20.0", "iconv-lite": "^0.7.2", "istextorbinary": "^7.0.0", From b62145644e60347ba51a9c981d4c3e5126c30045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:25:11 -0400 Subject: [PATCH 55/62] Bump @humanfs/node from 0.16.6 to 0.16.8 (#1855) --- package-lock.json | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e1de87d..4282e84a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -239,38 +239,41 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { From 6b74b6ff9d987501f84a20788bf37c509ccca9f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:45:46 -0400 Subject: [PATCH 56/62] Bump fast-uri from 3.1.5 to 3.1.7 (#1856) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4282e84a..e7230739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2964,9 +2964,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "dev": true, "funding": [ { From da96968516ddb5622981a19d47167a7def002475 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 7 Sep 2026 13:02:40 -0400 Subject: [PATCH 57/62] Fix docker-compose connections always prompting for password (#1860) * fix #1750 * Fix docker-compose connections always prompting for password resolveConnectionSpec's docker-compose branch built connSpec.auth as an empty BasicAuthorization(), discarding the plaintext password already resolved by serverForUri from objectscript.conn.password. --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index f1164853..f3496d8b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -282,7 +282,7 @@ export async function resolveConnectionSpec( port: serverForUri.superserverPort!, }, description: `Server for workspace folder '${serverName}'`, - auth: new BasicAuthorization(), + auth: serverForUri.auth ?? new BasicAuthorization(), }; } } From 3faf484f86647a5c771853953e6b5bc9e0a8ed02 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 7 Sep 2026 13:03:03 -0400 Subject: [PATCH 58/62] Fix broken Marketplace badges, drop superfluous ones in README (#1857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix #1750 * Fix broken Marketplace badges, drop superfluous ones in README shields.io retired its visual-studio-marketplace badge category (both now render "retired badge"); switch to vsmarketplacebadges.dev, which queries the Marketplace API directly. Also drop the Snyk badge and the redundant Caché/Ensemble badges, which now all link to the same InterSystems IRIS product page. * Switch packaging/publishing from deprecated vsce to @vscode/vsce The legacy `vsce` package's last published version (2.15.0, which npx resolves to since the package is no longer updated) hardcodes an SVG badge-host allowlist that only trusts the now-dead vsmarketplacebadge.apphb.com, not its replacement vsmarketplacebadges.dev. @vscode/vsce is the actively maintained successor and already trusts vsmarketplacebadges.dev. --- .github/workflows/main.yml | 6 +++--- .github/workflows/prerelease.yml | 6 +++--- README.md | 8 ++------ package.json | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2131603..e7a738b9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: run: xvfb-run npm test - name: Build package run: | - npx vsce package -o ${{ steps.set-version.outputs.name }}.vsix + npx @vscode/vsce package -o ${{ steps.set-version.outputs.name }}.vsix - uses: actions/upload-artifact@v6 if: github.event_name != 'release' with: @@ -142,7 +142,7 @@ jobs: git push - name: Build package run: | - npx vsce package -o ${{ steps.set-version.outputs.name }}.vsix + npx @vscode/vsce package -o ${{ steps.set-version.outputs.name }}.vsix - name: Upload Release Asset id: upload-release-asset uses: softprops/action-gh-release@v3 @@ -153,7 +153,7 @@ jobs: - name: Publish to VSCode Marketplace run: | [ -n "${{ secrets.VSCE_TOKEN }}" ] && \ - npx vsce publish --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true + npx @vscode/vsce publish --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true - name: Publish to Open VSX Registry timeout-minutes: 5 run: | diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index c78d801f..4c51a316 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -64,7 +64,7 @@ jobs: run: xvfb-run npm test - name: Build pre-release package run: | - npx vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix + npx @vscode/vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix - uses: actions/upload-artifact@v6 if: github.event_name != 'release' with: @@ -137,7 +137,7 @@ jobs: git push - name: Build pre-release package run: | - npx vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix + npx @vscode/vsce package --pre-release -o ${{ steps.set-version.outputs.name }}.vsix - name: Upload Release Asset id: upload-release-asset uses: softprops/action-gh-release@v3 @@ -148,4 +148,4 @@ jobs: - name: Publish to VSCode Marketplace run: | [ -n "${{ secrets.VSCE_TOKEN }}" ] && \ - npx vsce publish --pre-release --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true + npx @vscode/vsce publish --pre-release --packagePath ${{ steps.set-version.outputs.name }}.vsix -p ${{ secrets.VSCE_TOKEN }} || true diff --git a/README.md b/README.md index 9b636d4c..185e731c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ -[![Known Vulnerabilities](https://snyk.io/test/github/intersystems-community/vscode-objectscript/badge.svg)](https://snyk.io/test/github/intersystems-community/vscode-objectscript) -![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/intersystems-community.vscode-objectscript.svg) -[![](https://img.shields.io/visual-studio-marketplace/i/intersystems-community.vscode-objectscript.svg)](https://marketplace.visualstudio.com/items?itemName=intersystems-community.vscode-objectscript) - +![Visual Studio Marketplace Version](https://vsmarketplacebadges.dev/version/intersystems-community.vscode-objectscript.svg) +[![](https://vsmarketplacebadges.dev/installs/intersystems-community.vscode-objectscript.svg)](https://marketplace.visualstudio.com/items?itemName=intersystems-community.vscode-objectscript) [![](https://img.shields.io/badge/InterSystems-IRIS-blue.svg)](https://www.intersystems.com/products/intersystems-iris/) -[![](https://img.shields.io/badge/InterSystems-Caché-blue.svg)](https://www.intersystems.com/products/cache/) -[![](https://img.shields.io/badge/InterSystems-Ensemble-blue.svg)](https://www.intersystems.com/products/ensemble/) # InterSystems ObjectScript extension for VS Code diff --git a/package.json b/package.json index 3d0b5de0..ea89ee15 100644 --- a/package.json +++ b/package.json @@ -1706,7 +1706,7 @@ "vscode:prepublish": "webpack --mode production", "webpack": "webpack --mode development", "webpack-dev": "webpack --mode development --watch", - "package": "vsce package", + "package": "@vscode/vsce package", "compile": "webpack --mode production && tsc -p ./", "test-compile": "webpack --mode development && tsc -p ./", "watch": "tsc -w -p ./tsconfig.json", From f6b82c5bc225c6a88a590185dcde87a56bf9c682 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Wed, 9 Sep 2026 19:04:27 -0400 Subject: [PATCH 59/62] Lite Terminal: syntax color pasted input that ends with a newline (#1866) --- src/commands/webSocketTerminal.ts | 52 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/commands/webSocketTerminal.ts b/src/commands/webSocketTerminal.ts index 818313d8..c5b138fe 100644 --- a/src/commands/webSocketTerminal.ts +++ b/src/commands/webSocketTerminal.ts @@ -36,18 +36,12 @@ const actions = { }; /** Data received from the WebSocket */ -interface WebSocketMessage { - /** The type of the message */ - type: "prompt" | "read" | "error" | "output" | "init" | "color"; - /** The text of the message. Present for all types but "read" and "init". */ - text?: string; - /** The WebSocket protocol version. Only present for "init". */ - protocol?: number; - /** The InterSystems IRIS `$ZVERSION`. Only present for "init". */ - version?: string; - /** The current namespace. Only present for "prompt" on IRIS 2025.3+. */ - ns?: string; -} +type WebSocketMessage = + /** `ns` is only sent by IRIS 2025.3+ */ + | { type: "prompt"; text: string; ns?: string } + | { type: "error" | "output" | "color"; text: string } + | { type: "read" } + | { type: "init"; protocol: number; version: string }; class WebSocketTerminal implements vscode.Pseudoterminal { private _writeEmitter = new vscode.EventEmitter(); @@ -99,7 +93,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal { private _colorsRegex = /\x1b[^m]*?m/g; /** The terminal's current namespace */ - public currentNs: string; + public currentNs?: string; constructor( public readonly targetUri: vscode.Uri, @@ -259,19 +253,19 @@ class WebSocketTerminal implements vscode.Pseudoterminal { // Write the output to the terminal if (this._firstOutputLineSincePrompt) { // Strip leading \r\n since we printed it already - message.text = message.text!.startsWith("\r\n") ? message.text!.slice(2) : message.text; + message.text = message.text.startsWith("\r\n") ? message.text.slice(2) : message.text; this._firstOutputLineSincePrompt = false; } - if (message.text!.includes("\x1b[31;1m")) { - if (message.text!.includes("\x1b[31;1m")) { + if (message.text.includes("\x1b[31;1m")) { + if (message.text.includes("\x1b[31;1m")) { // Report no exit code for interrupts this._promptExitCode = ""; } else { this._promptExitCode = ";1"; } } - this._margin = this._cursorCol = message.text!.split("\r\n").pop()!.length; - this._hideCursorWrite(message.text!); + this._margin = this._cursorCol = message.text.split("\r\n").pop()!.length; + this._hideCursorWrite(message.text); break; case "prompt": case "read": @@ -280,11 +274,11 @@ class WebSocketTerminal implements vscode.Pseudoterminal { this._hideCursorWrite( `\x1b]633;D${this._promptExitCode}\x07\r\n\x1b]633;A\x07${message.text}\x1b]633;B\x07` ); - this._margin = this._cursorCol = message.text!.replace(this._colorsRegex, "").length; - this._prompt = message.text!; + this._margin = this._cursorCol = message.text.replace(this._colorsRegex, "").length; + this._prompt = message.text; this._promptExitCode = ";0"; // Store the current namespace - this.currentNs = message.ns!; + this.currentNs = message.ns; } // Enable input this._state = message.type; @@ -301,15 +295,17 @@ class WebSocketTerminal implements vscode.Pseudoterminal { ); break; case "color": { + // Outdated: the input is no longer on screen + if (this._state != "prompt") break; // Replace the input with the syntax colored text, keeping the cursor at the same spot let cursorLine = Math.ceil((this._cursorCol + 1) / this._cols) - 1; - if (message.text!.includes("\r\n")) { - const lines = message.text!.replace(this._colorsRegex, "").split("\r\n"); + if (message.text.includes("\r\n")) { + const lines = message.text.replace(this._colorsRegex, "").split("\r\n"); lines.pop(); cursorLine += lines.reduce((sum, line) => sum + Math.ceil((line.length + 1) / this._cols), 0); } this._hideCursorWrite( - `\x1b7${cursorLine > 0 ? `\x1b[${cursorLine}A` : ""}\r\x1b[0J${this._prompt}${message.text!.replace( + `\x1b7${cursorLine > 0 ? `\x1b[${cursorLine}A` : ""}\r\x1b[0J${this._prompt}${message.text.replace( /\r\n/g, `\r\n${this.multiLinePrompt}` )}\x1b8` @@ -670,7 +666,12 @@ class WebSocketTerminal implements vscode.Pseudoterminal { } // Save the cursor position, write the text, restore the cursor position, then move the cursor manually this._hideCursorWrite(`\x1b7${eraseAfterCursor}${char}\x1b8${rowStr}${colStr}`); + if (this._input != "" && this._state == "prompt") { + this._socket.send(JSON.stringify({ type: "color", input: this._input })); + } if (submit) { + // Let the coloring arrive before submitting moves the input off its line + await new Promise((resolve) => setTimeout(resolve, 100)); if (this._state == "prompt") { // Reset historyIdx this._historyIdx = -1; @@ -703,9 +704,6 @@ class WebSocketTerminal implements vscode.Pseudoterminal { this._input = ""; this._state = "eval"; this._margin = this._cursorCol = 0; - } else if (this._input != "" && this._state == "prompt") { - // Syntax color input - this._socket.send(JSON.stringify({ type: "color", input: this._input })); } } } From 8ee2ae0197f47abdcd21eeb3bfa46152b1cf5438 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Wed, 9 Sep 2026 22:01:55 -0400 Subject: [PATCH 60/62] Podman support (#1859) --- src/utils/index.ts | 90 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index fc633d8b..ad944d9e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -459,7 +459,7 @@ export function notNull(el: any): boolean { return el !== null; } -/** Determine the compose command to use (`docker-compose` or `docker compose`). */ +/** Determine the compose command to use (`docker-compose`, `docker compose`, `podman compose`, or `podman-compose`). */ async function composeCommand(cwd?: string): Promise { return new Promise((resolve) => { let cmd = "docker compose"; @@ -468,11 +468,89 @@ async function composeCommand(cwd?: string): Promise { // 'docker compose' is not present, so default to 'docker-compose' cmd = "docker-compose"; } - resolve(cmd); + exec(`${cmd} version`, { cwd }, (error) => { + if (error) { + // Neither Docker Compose variant is present; try Podman Compose. 'podman compose' can locate a + // provider that a plain PATH lookup for 'podman-compose' wouldn't, so try it first. + cmd = "podman compose"; + } + exec(`${cmd} version`, { cwd }, (error) => { + if (error) { + cmd = "podman-compose"; + } + resolve(cmd); + }); + }); }); }); } +/** + * Podman's compose tooling doesn't support the same `ps --services --filter status=running` and + * `port` output format as Docker Compose (bare port number instead of a host:port pair, and no + * `--services`/`--filter` flags), so it needs its own port/running-state resolution. + */ +async function portFromPodmanCompose( + cmd: string, + cwd: string, + file: string, + service: string, + internalPort: number, + internalSuperserverPort: number, + dockerCompose: { internalSuperserverPort?: number }, + result: { port: number | null; superserverPort: number | null; docker: boolean; service?: string } +): Promise { + const ps = await run("ps --format json"); + let containers: { State?: string; Labels?: Record }[] = []; + try { + containers = JSON.parse(ps); + } catch { + // Leave containers empty; treated as not running below + } + if (!containers.some((c) => c.State === "running" && c.Labels?.["com.docker.compose.service"] === service)) { + throw `Service '${service}' not found in '${path.join(cwd, file)}', or not running.`; + } + const port = parsePort(await run(`port --protocol=tcp ${service} ${internalPort}`)); + if (!port) { + throw `Webserver port ${internalPort} not published for service '${service}' in '${path.join(cwd, file)}'.`; + } + result.port = parseInt(port, 10); + let superserverPort: string | undefined; + try { + superserverPort = parsePort(await run(`port --protocol=tcp ${service} ${internalSuperserverPort}`)); + } catch (error) { + // Not an error if we were merely looking for the default port and the container doesn't publish it + if (!dockerCompose.internalSuperserverPort) return result; + throw error; + } + if (!superserverPort) { + throw `Superserver port ${internalSuperserverPort} not published for service '${service}' in '${path.join(cwd, file)}'.`; + } + result.superserverPort = parseInt(superserverPort, 10); + return result; + + // Runs a compose subcommand and returns its stdout, rejecting with a plain string on failure. + function run(args: string): Promise { + return new Promise((resolve, reject) => { + exec(`${cmd} ${args}`, { cwd }, (error, stdout) => { + if (error) { + reject(error.message); + return; + } + resolve(stdout); + }); + }); + } + + function parsePort(stdout: string) { + return stdout + .trim() + .split("\n") + .pop() + ?.match(/(\d+)$/)?.[1]; + } +} + export async function portFromDockerCompose( workspaceFolderName?: string ): Promise<{ port: number | null; superserverPort: number | null; docker: boolean; service?: string }> { @@ -529,6 +607,10 @@ export async function portFromDockerCompose( const envFileParam = envFile ? `--env-file ${envFile}` : ""; const cmd = `${await composeCommand(cwd)} -f ${file} ${envFileParam} `; + if (cmd.startsWith("podman compose ") || cmd.startsWith("podman-compose ")) { + return portFromPodmanCompose(cmd, cwd, file, service, internalPort, internalSuperserverPort, dockerCompose, result); + } + return new Promise((resolve, reject) => { exec(`${cmd} ps --services --filter status=running`, { cwd }, (error, stdout) => { if (error) { @@ -580,7 +662,7 @@ export async function terminalWithDocker(): Promise { if (!terminal) { let exe = await composeCommand(); const argsArr: string[] = []; - if (exe == "docker compose") { + if (exe.includes(" ")) { const exeSplit = exe.split(" "); exe = exeSplit[0]; argsArr.push(exeSplit[1]); @@ -615,7 +697,7 @@ export async function shellWithDocker(): Promise { if (!terminal) { let exe = await composeCommand(); const argsArr: string[] = []; - if (exe == "docker compose") { + if (exe.includes(" ")) { const exeSplit = exe.split(" "); exe = exeSplit[0]; argsArr.push(exeSplit[1]); From 14c0cf0df6c02246903f1a16b78b94de3432ee7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:14:29 -0400 Subject: [PATCH 61/62] Bump js-yaml from 4.3.1 to 4.3.2 (#1868) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...4.3.2) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7230739..e2aa043a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4158,9 +4158,9 @@ } }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { From 5b1d295bf4fbaff0056c11cc9bba455a1a6c76ac Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 11 Sep 2026 08:22:31 -0400 Subject: [PATCH 62/62] Fix connection check prompting/disabling on a merely-stale session (#1864) --- src/api/index.ts | 29 ++++++++++---------- src/commands/serverActions.ts | 4 +-- src/extension.ts | 51 ++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 53a80938..2687537e 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -8,9 +8,8 @@ import BasicAuthorization, { extensionContext, workspaceState, panel, - checkConnection, + ensureConnection, schemas, - checkingConnection, inactiveServerIds, } from "../extension"; import { currentWorkspaceFolder, outputChannel, outputConsole } from "../utils"; @@ -121,10 +120,8 @@ export class AtelierAPI { return filename; } - public constructor(wsOrFile?: string | vscode.Uri, retryAfter401 = true) { - if (retryAfter401) { - this.wsOrFile = wsOrFile; - } + public constructor(wsOrFile?: string | vscode.Uri) { + this.wsOrFile = wsOrFile; let workspaceFolderName = ""; let namespace = ""; if (wsOrFile) { @@ -328,12 +325,19 @@ export class AtelierAPI { private async request( minVersion: number, - method: string, + method: "GET" | "HEAD" | "PUT" | "POST" | "DELETE", path?: string, body?: any, params?: any, headers?: any, - options?: any + options?: { + /** Abort the request if it hasn't completed within this many milliseconds. */ + timeout?: number; + /** Suppress writing this request/response to the ObjectScript output channel, even when `objectscript.outputRESTTraffic` is on. */ + noOutput?: boolean; + /** On a 401 response, suppress the automatic single retry with fresh credentials. */ + _retriedAfter401?: boolean; + } ): Promise { const { active, apiVersion, host, port, https } = this.config; if (!active || !port || !host) { @@ -365,7 +369,6 @@ export class AtelierAPI { }); return result.length ? "?" + result.join("&") : ""; }; - method = method.toUpperCase(); if (body && !headers["Content-Type"]) { headers["Content-Type"] = "application/json"; } @@ -465,7 +468,7 @@ export class AtelierAPI { if (response.status === 401) { authRequestMap.delete(mapKey); cookiesMap.delete(mapKey); - if (this.wsOrFile && !checkingConnection) { + if (this.wsOrFile) { if (!options?._retriedAfter401) { return this.request(minVersion, method, originalPath, body, params, headers, { ...options, @@ -473,7 +476,7 @@ export class AtelierAPI { }); } setTimeout(() => { - checkConnection( + ensureConnection( this.config.auth.resolved(), typeof this.wsOrFile === "object" ? this.wsOrFile : undefined, true @@ -596,9 +599,7 @@ export class AtelierAPI { panel.tooltip = "Disconnected"; workspaceState.update(this.configName.toLowerCase() + ":host", undefined); workspaceState.update(this.configName.toLowerCase() + ":port", undefined); - if (!checkingConnection) { - setTimeout(() => checkConnection(false, undefined, true), 30000); - } + setTimeout(() => ensureConnection(false, undefined, true), 30000); } throw error; } diff --git a/src/commands/serverActions.ts b/src/commands/serverActions.ts index 23176c50..aeec30b1 100644 --- a/src/commands/serverActions.ts +++ b/src/commands/serverActions.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import { config, workspaceState, - checkConnection, + ensureConnection, explorerProvider, filesystemSchemas, FILESYSTEM_SCHEMA, @@ -72,7 +72,7 @@ export async function serverActions(): Promise { return connConfig.update("conn", { ...targetConfig, active: !active }, target); } case "refreshConnection": { - await checkConnection(true, undefined, true); + await ensureConnection(true, undefined, true); break; } case "switchNamespace": { diff --git a/src/extension.ts b/src/extension.ts index f3496d8b..fc4ad561 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -215,8 +215,6 @@ export function config(setting?: string, workspaceFolderName?: string): any { let reporter: TelemetryReporter | undefined; -export let checkingConnection = false; - export let serverManagerApi: serverManager.ServerManagerAPI; type ConnSpec = serverManager.IServerSpec & { @@ -375,18 +373,29 @@ export function getResolvedConnectionSpec( /** The `api.serverId`s of all servers that are known to be inactive */ export const inactiveServerIds: Set = new Set(); -export async function checkConnection( +/** `configName`s for which an `ensureConnection` call is currently in progress */ +const ensuringConnection: Set = new Set(); + +/** + * Verify `uri`'s connection works, repairing it if not (may show a modal credential prompt). + * Reflects the outcome in the status bar, `objectscript.conn.active`, and `workspaceState`. + * No-ops if a call is already in progress. + * @param clearState Discard cached connection details first, forcing fresh resolution. + * @param triggerRefreshes Refresh the explorer/projects views once settled. + * @param withTimeout Time out the check so it can't hang extension activation. + */ +export async function ensureConnection( clearState = false, uri?: vscode.Uri, triggerRefreshes?: boolean, - inActivate = false + withTimeout = false ): Promise { - // Do nothing if already checking the connection - if (checkingConnection) { + const { apiTarget, configName } = connectionTarget(uri); + // Do nothing if already checking this connection + if (ensuringConnection.has(configName)) { return; } - const { apiTarget, configName } = connectionTarget(uri); const wsKey = configName.toLowerCase(); if (clearState) { // clean-up cached values @@ -399,7 +408,7 @@ export async function checkConnection( await workspaceState.update(wsKey + ":docker", undefined); _onDidChangeConnection.fire(); } - let api = new AtelierAPI(apiTarget, false); + let api = new AtelierAPI(apiTarget); const { active, host = "", port = 0, superserverPort = 0, ns = "", auth } = api.config; vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active); if (!panel.text) { @@ -463,7 +472,7 @@ export async function checkConnection( if (api.externalServer) { inactiveServerIds.delete(api.serverId); } - api = new AtelierAPI(apiTarget, false); + api = new AtelierAPI(apiTarget); if (!api.config.host || !api.config.port || !api.config.ns) { const message = "'host', 'port' and 'ns' must be specified."; @@ -474,7 +483,7 @@ export async function checkConnection( if (!api.externalServer) await setConnectionState(configName, false); return; } - checkingConnection = true; + ensuringConnection.add(configName); const username = auth.username || "UnknownUser"; const identity = username.startsWith("*") ? `using ${username.slice(1, -1)}` : `as user \`${username}\``; @@ -496,7 +505,7 @@ export async function checkConnection( // Do the check // Only time out requests when called from activate() // Timeout is needed in that case to prevent extension activation from hanging - const serverInfoTimeout = inActivate ? 5000 : undefined; + const serverInfoTimeout = withTimeout ? 5000 : undefined; return api .serverInfo(true, serverInfoTimeout) .then(gotServerInfo) @@ -524,7 +533,7 @@ export async function checkConnection( if (newSpec) { // We were able to resolve credentials, so try again await workspaceState.update(wsKey + ":password", newSpec.auth?.accessToken); - api = new AtelierAPI(apiTarget, false); + api = new AtelierAPI(apiTarget); await api .serverInfo(true, serverInfoTimeout) .then(async (info) => { @@ -537,9 +546,6 @@ export async function checkConnection( if (error?.statusCode != 401) errorMessage = undefined; await workspaceState.update(wsKey + ":password", undefined); success = false; - }) - .finally(() => { - checkingConnection = false; }); } } else { @@ -572,9 +578,6 @@ export async function checkConnection( await workspaceState.update(wsKey + ":password", undefined); return false; }) - .finally(() => { - checkingConnection = false; - }) ); } else { inactiveServerIds.add(api.serverId); @@ -600,7 +603,7 @@ export async function checkConnection( if (!api.externalServer) await setConnectionState(configName, false); }) .finally(() => { - checkingConnection = false; + ensuringConnection.delete(configName); if (triggerRefreshes) { setTimeout(() => { explorerProvider.refresh(); @@ -948,7 +951,7 @@ export async function activate(context: vscode.ExtensionContext): Promise