diff --git a/CHANGELOG.json b/CHANGELOG.json index d89a94a7..ae397c8b 100644 --- a/CHANGELOG.json +++ b/CHANGELOG.json @@ -1,4 +1,19 @@ [ + { + "date": "2026-07-14", + "version": "4.202607.1", + "Fixed": [ + "VS Code extension: resolve HANA connection when the CAP project (with .cdsrc-private.json / default-env.json) lives in a subfolder rather than the workspace root; scans subfolders and prompts when multiple projects are found (#182)", + "VS Code extension: artifact inspectors (hdbtable, hdbview, hdbfunction, hdbprocedure, hdbsynonym, hdbrole, hdbsequence) now pre-fill the object name from the opened file", + "VS Code extension: type-ahead suggestions now work in webview inputs (schema/table/etc.) by using the local server base URL instead of relative fetch paths" + ], + "Added": [ + "VS Code extension: hana-cli.projectPath setting to explicitly point at the connection-config folder and skip auto-detection (#182)" + ], + "Changed": [ + "hana-cli vscode status now compares the installed extension against the packaged .vsix and suggests updating; vscode install upgrades in place via --force (#182)" + ] + }, { "date": "2026-07-11", "version": "4.202607.0", diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2b3053..e795c7a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). +## [4.202607.1] - 2026-07-14 + +**Fixed** + +- VS Code extension: resolve HANA connection when the CAP project (with .cdsrc-private.json / default-env.json) lives in a subfolder rather than the workspace root; scans subfolders and prompts when multiple projects are found (#182) +- VS Code extension: artifact inspectors (hdbtable, hdbview, hdbfunction, hdbprocedure, hdbsynonym, hdbrole, hdbsequence) now pre-fill the object name from the opened file +- VS Code extension: type-ahead suggestions now work in webview inputs (schema/table/etc.) by using the local server base URL instead of relative fetch paths + +**Added** + +- VS Code extension: hana-cli.projectPath setting to explicitly point at the connection-config folder and skip auto-detection (#182) + +**Changed** + +- hana-cli vscode status now compares the installed extension against the packaged .vsix and suggests updating; vscode install upgrades in place via --force (#182) + ## [4.202607.0] - 2026-07-11 **Added** diff --git a/app/vue/src/composables/__tests__/useSuggestions.test.ts b/app/vue/src/composables/__tests__/useSuggestions.test.ts new file mode 100644 index 00000000..3518f750 --- /dev/null +++ b/app/vue/src/composables/__tests__/useSuggestions.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { setAdapter } from '../../adapters/environment' +import { useSuggestions } from '../useSuggestions' + +/** + * Regression tests for useSuggestions in the VS Code webview context. + * + * In the extension the page origin is vscode-webview://…, while the hana-cli + * server runs at http://localhost:. Relative fetches never reach the + * server, so type-ahead suggestions silently return nothing. These tests pin + * that every request is prefixed with the adapter's API base URL. + */ + +const BASE = 'http://localhost:4321' + +function makeAdapter(baseUrl: string) { + return { + getApiBaseUrl: () => baseUrl, + getWebSocketUrl: () => `${baseUrl}/websockets`, + notifyDirtyState: () => {}, + requestSave: () => {}, + getTheme: () => 'light' as const, + onMessage: () => {}, + postMessage: () => {}, + isVSCode: () => true + } +} + +describe('useSuggestions API base URL', () => { + let fetchMock: ReturnType + + beforeEach(() => { + setAdapter(makeAdapter(BASE)) + fetchMock = vi.fn(async (url: string) => { + // PUT to set params returns ok; GET returns rows + if (url.endsWith('/')) { + return { ok: true, json: async () => ({}) } as any + } + return { + ok: true, + json: async () => [{ TABLE_NAME: 'FOO' }, { TABLE_NAME: 'BAR' }] + } as any + }) + vi.stubGlobal('fetch', fetchMock) + }) + + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('prefixes the params PUT and the GET with the adapter base URL', async () => { + const s = useSuggestions('tables-ui', 'TABLE_NAME') + await s.load({ schema: 'S', table: '*', limit: 1000 }) + + const urls = fetchMock.mock.calls.map(c => c[0] as string) + expect(urls.some(u => u === `${BASE}/`)).toBe(true) + expect(urls).toContain(`${BASE}/hana/tables-ui`) + // No bare relative URLs that would miss the server in the webview + expect(urls.every(u => u.startsWith(BASE))).toBe(true) + }) + + it('maps the configured name field into suggestion items', async () => { + const s = useSuggestions('tables-ui', 'TABLE_NAME') + await s.load({ schema: 'S2', table: '*', limit: 1000 }) + expect(s.items.value).toEqual(['FOO', 'BAR']) + }) +}) diff --git a/app/vue/src/composables/useSuggestions.ts b/app/vue/src/composables/useSuggestions.ts index bade2c14..babc4e97 100644 --- a/app/vue/src/composables/useSuggestions.ts +++ b/app/vue/src/composables/useSuggestions.ts @@ -1,4 +1,5 @@ import { ref, type Ref } from 'vue' +import { getAdapter } from '../adapters/environment' interface SuggestionCache { key: string @@ -7,6 +8,21 @@ interface SuggestionCache { const cache: SuggestionCache[] = [] +/** + * Resolve the API base URL from the active environment adapter. + * In the browser this is window.location.origin; in the VS Code webview it is + * the http://localhost: the extension server listens on. Relative URLs + * would resolve against the vscode-webview:// origin and never reach the + * server, so all fetches must be absolute. + */ +function baseUrl(): string { + try { + return getAdapter().getApiBaseUrl() + } catch { + return '' + } +} + export function useSuggestions(endpoint: string, nameField: string) { const items: Ref = ref([]) let loaded = false @@ -21,14 +37,15 @@ export function useSuggestions(endpoint: string, nameField: string) { } try { + const api = baseUrl() if (Object.keys(params).length > 0) { - await fetch('/', { + await fetch(`${api}/`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }) } - const res = await fetch(`/hana/${endpoint}`) + const res = await fetch(`${api}/hana/${endpoint}`) if (!res.ok) return const data = await res.json() diff --git a/mcp-server/npm-shrinkwrap.json b/mcp-server/npm-shrinkwrap.json index 46c62826..6c068686 100644 --- a/mcp-server/npm-shrinkwrap.json +++ b/mcp-server/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "hana-cli-mcp-server", - "version": "1.202607.0", + "version": "1.202607.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hana-cli-mcp-server", - "version": "1.202607.0", + "version": "1.202607.1", "license": "SEE LICENSE IN ../LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0" diff --git a/mcp-server/package.json b/mcp-server/package.json index 4046edc4..d719e979 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "hana-cli-mcp-server", - "version": "1.202607.0", + "version": "1.202607.1", "description": "MCP Server for HANA CLI Tool", "type": "module", "main": "build/index.js", diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 960159c0..42d61388 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "hana-cli", - "version": "4.202607.0", + "version": "4.202607.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hana-cli", - "version": "4.202607.0", + "version": "4.202607.1", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { diff --git a/package.json b/package.json index a1ed68b1..62401408 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hana-cli", - "version": "4.202607.0", + "version": "4.202607.1", "description": "HANA Developer Command Line Interface", "main": "index.js", "bin": { diff --git a/server.json b/server.json index f821842f..3ff58eb9 100644 --- a/server.json +++ b/server.json @@ -7,12 +7,12 @@ "url": "https://github.com/SAP-samples/hana-developer-cli-tool-example", "source": "github" }, - "version": "4.202607.0", + "version": "4.202607.1", "packages": [ { "registryType": "npm", "identifier": "hana-cli", - "version": "4.202607.0", + "version": "4.202607.1", "runtimeHint": "npx", "runtimeArguments": [ { diff --git a/vscode-extension/package.json b/vscode-extension/package.json index a241e2c0..d8c00112 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -2,7 +2,7 @@ "name": "hana-cli", "displayName": "hana-cli Tools for SAP HANA", "description": "Visual editors and database tools for SAP HANA powered by hana-cli", - "version": "0.1.7", + "version": "0.1.8", "publisher": "SAP-samples", "icon": "icon.png", "license": "Apache-2.0", diff --git a/vscode-extension/src/editors/artifactInspector.ts b/vscode-extension/src/editors/artifactInspector.ts index 5631b244..3147e0ee 100644 --- a/vscode-extension/src/editors/artifactInspector.ts +++ b/vscode-extension/src/editors/artifactInspector.ts @@ -7,16 +7,18 @@ interface ArtifactConfig { viewType: string route: string kind: string + /** Query-param key the target Vue view reads the artifact name from. */ + queryKey: string } const ARTIFACT_CONFIGS: ArtifactConfig[] = [ - { viewType: 'hana-cli.tableInspector', route: '/inspect-table', kind: 'table' }, - { viewType: 'hana-cli.viewInspector', route: '/inspect-view', kind: 'view' }, - { viewType: 'hana-cli.procedureInspector', route: '/call-procedure', kind: 'procedure' }, - { viewType: 'hana-cli.functionInspector', route: '/inspect-function', kind: 'function' }, - { viewType: 'hana-cli.synonymInspector', route: '/inspect-table', kind: 'synonym' }, - { viewType: 'hana-cli.roleInspector', route: '/inspect-table', kind: 'role' }, - { viewType: 'hana-cli.sequenceInspector', route: '/inspect-table', kind: 'sequence' }, + { viewType: 'hana-cli.tableInspector', route: '/inspect-table', kind: 'table', queryKey: 'table' }, + { viewType: 'hana-cli.viewInspector', route: '/inspect-view', kind: 'view', queryKey: 'view' }, + { viewType: 'hana-cli.procedureInspector', route: '/call-procedure', kind: 'procedure', queryKey: 'procedure' }, + { viewType: 'hana-cli.functionInspector', route: '/inspect-function', kind: 'function', queryKey: 'function' }, + { viewType: 'hana-cli.synonymInspector', route: '/inspect-table', kind: 'synonym', queryKey: 'table' }, + { viewType: 'hana-cli.roleInspector', route: '/inspect-table', kind: 'role', queryKey: 'table' }, + { viewType: 'hana-cli.sequenceInspector', route: '/inspect-table', kind: 'sequence', queryKey: 'table' }, ] /** @@ -79,8 +81,13 @@ class ArtifactInspectorProvider implements vscode.CustomReadonlyEditorProvider { const port = await ensureServer(this._context) + // Pass the artifact name as a route query param the target Vue view reads + // directly (e.g. /inspect-table?table=NAME). This mirrors how the browser + // UI navigates to these views and pre-populates the input on load. + const routeWithName = `${this._config.route}?${this._config.queryKey}=${encodeURIComponent(name)}` + webviewPanel.webview.html = getWebviewContent(webviewPanel.webview, this._context.extensionUri, { - route: this._config.route, + route: routeWithName, port, chromeless: true, }) @@ -88,16 +95,6 @@ class ArtifactInspectorProvider implements vscode.CustomReadonlyEditorProvider { webviewPanel.webview.onDidReceiveMessage( async (message: { type: string; level?: string; text?: string; path?: string }) => { switch (message.type) { - case 'ready': { - webviewPanel.webview.postMessage({ - type: 'openArtifact', - kind: this._config.kind, - name, - schema: '', - }) - break - } - case 'showMessage': { const text = message.text || '' switch (message.level) {