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
12 changes: 12 additions & 0 deletions .github/workflows/release-hana-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Build VS Code extension .vsix
run: |
# Install sub-project deps (root npm ci does not cover these) and
# build a fresh, self-contained .vsix that ships inside the npm
# package for `hana-cli vscode install`. Installing app/vue deps here
# also satisfies the root prepack step (build:vue) run by npm publish.
rm -f vscode-extension/*.vsix
npm ci --prefix app/vue
npm ci --prefix vscode-extension
npm run build:vscode
cd vscode-extension && npx vsce package --no-dependencies

- name: Verify package version matches tag
run: |
PKG_VERSION=$(node -e "import fs from 'fs'; console.log(JSON.parse(fs.readFileSync('package.json','utf8')).version)")
Expand Down
28 changes: 20 additions & 8 deletions docs/03-features/vscode-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ The extension uses a **Hybrid Direct Webview + Embedded Server** architecture:

### From the CLI

Once `hana-cli` is installed (globally via `npm install -g hana-cli`, or in a project), a prebuilt `.vsix` ships inside the npm package, so `install` works offline with no build step:

```bash
# Check if the extension is installed
hana-cli vscode status

# Install from a local .vsix package
# Install from the bundled .vsix package
hana-cli vscode install

# Install for VS Code Insiders
Expand All @@ -121,6 +123,8 @@ hana-cli vscode install --insiders
hana-cli vscode uninstall
```

The bundled `.vsix` is self-contained and OS-portable — it includes the web UI assets and uses Node's built-in `node:sqlite` driver, so there is no platform-specific native binary and no per-OS rebuild.

### From VS Code

1. Open the Extensions panel (Ctrl+Shift+X)
Expand All @@ -129,16 +133,24 @@ hana-cli vscode uninstall

### From Source (Development)

::: warning Install the parent project first
The extension's bundle step reaches into the **root** hana-cli project's `node_modules`, `routes/`, and `utils/` (they are inlined into the extension). You must install the root project's dependencies **before** building the extension, or esbuild fails with dozens of `Could not resolve "express" / "exceljs" / "@sap/cds" …` errors.
:::

```bash
# Build the extension
cd vscode-extension
npm install
npm run bundle
# 1. From the repo ROOT — install parent deps the bundle inlines
npm ci

# 2. Build the Vue web UI for the webview + bundle the extension.
# This copies the UI assets into vscode-extension/webview-dist so the
# packaged .vsix is self-contained.
npm run build:vscode

# Package as .vsix
npx vsce package
# 3. Package as .vsix
cd vscode-extension
npx vsce package --no-dependencies

# Install the generated .vsix
# 4. Install the generated .vsix
code --install-extension hana-cli-0.1.0.vsix
```

Expand Down
9 changes: 9 additions & 0 deletions utils/database/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ export default class dbClientClass {
throw new Error(base.bundle.getText("error.cdsProjectMissing"))
}
if (optionsCDS.kind === 'sqlite') { //SQLite CDS
// Prefer Node's built-in node:sqlite driver over the native
// better-sqlite3 binding. This avoids a platform-specific
// native .node binary (portable VS Code .vsix, no per-OS
// rebuild) and matches the CAP 10 default. Node >=22 (our
// engines baseline) always ships node:sqlite. An explicit
// driver in the project/credentials config still wins.
if (!optionsCDS.driver && !optionsCDS.credentials?.driver) {
optionsCDS.driver = 'node'
}
// Load actual SQLite credentials and merge them into optionsCDS if needed
const conn = await import("../connections.js")
const credentials = await conn.getConnOptions(prompts)
Expand Down
1 change: 1 addition & 0 deletions vscode-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
out/
dist/
webview-dist/
_i18n/
*.vsix
22 changes: 22 additions & 0 deletions vscode-extension/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# npm packaging rules for the vscode-extension folder.
#
# npm uses this file INSTEAD of .gitignore for this directory when deciding
# what to include in the published hana-cli npm tarball. The prebuilt *.vsix
# is git-ignored (build artifact) but MUST ship in the npm package so
# `hana-cli vscode install` can find and install it offline.
#
# Ship: *.vsix and package.json. Exclude everything else in this folder —
# consumers of the npm package never build the extension from source.
src/
out/
test/
dist/
webview-dist/
node_modules/
_i18n/
.vscode/
esbuild.config.mjs
tsconfig.json
tsconfig.test.json
package-lock.json
.gitignore
34 changes: 34 additions & 0 deletions vscode-extension/esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
const production = process.argv.includes('--production')
const projectRoot = path.resolve(__dirname, '..')

// Copy the built Vue webview assets into the extension so a packaged .vsix is
// self-contained. At runtime htmlProvider prefers vscode-extension/webview-dist
// and only falls back to ../app/vue/dist-vscode when running from source (F5).
// Without this copy, an installed .vsix would resolve to VS Code's extensions
// dir where the sibling app/ tree does not exist → blank webviews.
function copyWebviewAssets() {
const src = path.resolve(projectRoot, 'app', 'vue', 'dist-vscode')
const dest = path.resolve(__dirname, 'webview-dist')
if (!fs.existsSync(path.join(src, 'assets', 'index.js'))) {
console.warn(
`[esbuild] Vue webview assets not found at ${src}.\n` +
` Run "npm run build:vscode" from the project root first, ` +
`or the packaged .vsix will have no UI.`
)
return
}
fs.rmSync(dest, { recursive: true, force: true })
fs.cpSync(src, dest, { recursive: true })
console.log(`[esbuild] Copied Vue webview assets → ${dest}`)
}

copyWebviewAssets()

// The route modules in src/server/routes.ts import from '../../routes/*.js'
// which TypeScript preserves as-is (@ts-ignore). From the compiled location
// (out/server/), these paths don't resolve correctly. This plugin redirects
Expand Down Expand Up @@ -142,7 +165,18 @@ await esbuild.build({
'vscode',
// Optional/dynamic dependencies from the parent project's node_modules
// that are not needed at runtime in the VS Code extension context
//
// sqlite3 is optionally require()d by @sap/cds (lib/srv/middlewares/trace.js)
// but never installed here; keep it external so esbuild leaves the guarded
// require in place instead of failing the build.
'sqlite3',
// better-sqlite3 is a native (.node) module. We force @cap-js/sqlite to use
// Node's built-in node:sqlite driver (see utils/database/index.js), so the
// better-sqlite3 require path is never executed at runtime. Marking it
// external keeps its platform-specific binary out of the bundle, making the
// .vsix OS-portable (no per-OS rebuild). node:sqlite is a Node builtin and
// is external automatically.
'better-sqlite3',
'@cap-js/cds-test',
'@sap-cloud-sdk/connectivity',
'@sap-cloud-sdk/http-client',
Expand Down
24 changes: 22 additions & 2 deletions vscode-extension/src/webview/htmlProvider.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import * as vscode from 'vscode'
import * as crypto from 'crypto'
import * as fs from 'fs'

interface WebviewContentOptions {
route?: string
port?: number
chromeless?: boolean
}

/**
* Resolve the directory containing the built Vue webview assets.
*
* A packaged .vsix ships the assets inside the extension itself at
* `webview-dist/` (copied there by the bundle step). When running from source
* via F5 (--extensionDevelopmentPath), that folder may not exist yet, so we
* fall back to the sibling `../app/vue/dist-vscode` build output in the repo.
*
* @param extensionUri - The extension's root URI
* @returns URI of the directory holding `assets/index.js` and `assets/index.css`
*/
function resolveWebviewDist(extensionUri: vscode.Uri): vscode.Uri {
const bundled = vscode.Uri.joinPath(extensionUri, 'webview-dist')
if (fs.existsSync(vscode.Uri.joinPath(bundled, 'assets', 'index.js').fsPath)) {
return bundled
}
return vscode.Uri.joinPath(extensionUri, '..', 'app', 'vue', 'dist-vscode')
}

export function getWebviewContent(
webview: vscode.Webview,
extensionUri: vscode.Uri,
options: WebviewContentOptions = {}
): string {
const nonce = crypto.randomBytes(16).toString('hex')

const distPath = vscode.Uri.joinPath(extensionUri, '..', 'app', 'vue', 'dist-vscode')
const distPath = resolveWebviewDist(extensionUri)
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(distPath, 'assets', 'index.js'))
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(distPath, 'assets', 'index.css'))

Expand Down Expand Up @@ -72,7 +92,7 @@ export function getWebviewContent(
}

export function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
const distPath = vscode.Uri.joinPath(extensionUri, '..', 'app', 'vue', 'dist-vscode')
const distPath = resolveWebviewDist(extensionUri)
return {
enableScripts: true,
localResourceRoots: [distPath]
Expand Down
Loading