Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
67 changes: 67 additions & 0 deletions app/vue/src/composables/__tests__/useSuggestions.test.ts
Original file line number Diff line number Diff line change
@@ -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:<port>. 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<typeof vi.fn>

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'])
})
})
21 changes: 19 additions & 2 deletions app/vue/src/composables/useSuggestions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ref, type Ref } from 'vue'
import { getAdapter } from '../adapters/environment'

interface SuggestionCache {
key: string
Expand All @@ -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:<port> 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<string[]> = ref([])
let loaded = false
Expand All @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions mcp-server/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
2 changes: 1 addition & 1 deletion vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 15 additions & 18 deletions vscode-extension/src/editors/artifactInspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
]

/**
Expand Down Expand Up @@ -79,25 +81,20 @@ 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,
})

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) {
Expand Down
Loading