Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .changeset/script-api-intellisense.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@salesforce/b2c-cli': minor
'b2c-vs-extension': minor
---

Add Script API IntelliSense for cartridge JavaScript: `dw/*`, cartridge-style requires, and SFCC globals (`session`, `request`, `response`, `customer`, etc.) are typed automatically. The VS Code extension wires this up out of the box; for other editors, run `b2c setup ide tsserver-plugin` (LSP plugin) or `b2c setup ide vscode-types` (jsconfig). See the IDE Integration guide for details.
92 changes: 92 additions & 0 deletions docs/cli/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,102 @@ b2c setup ide
# Show setup ide subcommands
b2c setup ide --help

# Vendor Script API TypeScript definitions for IDE IntelliSense
b2c setup ide vscode-types

# Print TS Server plugin path for LSP-based editors (Neovim, Helix, Zed, etc.)
b2c setup ide tsserver-plugin --json

# Generate Prophet integration script
b2c setup ide prophet
```

## b2c setup ide vscode-types

Vendor B2C Commerce Script API TypeScript definitions and write a `jsconfig.json` into the workspace so any IDE that drives `tsserver` (plain VS Code, WebStorm, IntelliJ Ultimate, Neovim, Helix, Zed, Sublime Text) gets `dw/*` IntelliSense, hover docs, and signature help in cartridge JavaScript files.

The B2C DX VS Code extension does not need this command — it injects the same TypeScript Server plugin at runtime without writing files into your repo. Use this command only when you're not running the extension.

### Usage

```bash
b2c setup ide vscode-types [FLAGS]
```

### Flags

| Flag | Description | Default |
| ---------------- | ----------------------------------------------------------------- | ---------------- |
| `--output`, `-o` | Path for the generated `jsconfig.json` (relative to project root) | `jsconfig.json` |
| `--force`, `-f` | Overwrite output files if they already exist | `false` |
| `--[no-]copy` | Copy bundled types into `./.b2c-script-types/` | `true` |
| `--json` | Output results as JSON | `false` |

The generated `paths` mappings are written relative to the repo root, so `--output` is only intended for renaming the file itself (e.g., `--output jsconfig.cartridges.json`), not for relocating it into a subdirectory.

### Examples

```bash
# Default: write ./jsconfig.json and ./.b2c-script-types/types/ at the repo root
b2c setup ide vscode-types

# Re-vendor after upgrading the CLI
b2c setup ide vscode-types --force

# Regenerate jsconfig only (skip the type bundle copy; types must already be vendored)
b2c setup ide vscode-types --no-copy --force
```

### Output

The command produces:

- `./.b2c-script-types/types/` — vendored copy of the Script API definitions, version-pinned to the CLI release. Safe to commit.
- `./jsconfig.json` (or the path passed to `--output`) — TypeScript Language Service configuration mapping `dw/*` to the vendored types. Cartridge-relative requires (`~/cartridge/...`, `*/cartridge/...`) can't be expressed in standalone TypeScript `paths` mappings and will appear unresolved without the B2C DX VS Code extension.

See the [IDE Integration guide](/guide/ide-integration#script-api-intellisense) for editor-specific setup notes (Neovim, Helix, Zed, etc.).

## b2c setup ide tsserver-plugin

Print absolute paths to the bundled `@salesforce/b2c-script-types` TypeScript Server plugin and types directory. Use this when configuring an LSP-based editor (Neovim, Helix, Zed, Sublime, etc.) to load the plugin via `init_options.plugins[]` — full feature parity with the B2C DX VS Code extension, including cartridge-relative require resolution.

The command performs no filesystem writes; it just resolves and prints paths.

### Usage

```bash
b2c setup ide tsserver-plugin [FLAGS]
```

### Flags

| Flag | Description | Default |
| -------- | ---------------------- | ------- |
| `--json` | Output results as JSON | `false` |

### Examples

```bash
# Human-readable
b2c setup ide tsserver-plugin

# JSON for tooling (e.g. nvim-sfcc)
b2c setup ide tsserver-plugin --json
```

### Output

```json
{
"pluginName": "@salesforce/b2c-script-types",
"pluginPath": "/usr/local/lib/node_modules/@salesforce/b2c-cli/dist/script-types",
"typesPath": "/usr/local/lib/node_modules/@salesforce/b2c-cli/dist/script-types/types",
"version": "26.7.0"
}
```

Pass `pluginName` as `name` and `pluginPath` as `location` in your editor's `tsserver` `init_options.plugins[]` entry. The plugin auto-discovers cartridges in the project root and honors `dw.json`'s `cartridges` field for ordering — no host-side wiring needed.

## b2c setup ide prophet

Generate a `dw.js` script for the [Prophet VS Code extension](https://marketplace.visualstudio.com/items?itemName=SqrTT.prophet).
Expand Down
114 changes: 112 additions & 2 deletions docs/guide/ide-integration.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,123 @@
---
description: Configure IDE tooling like Prophet VS Code extension and IntelliJ SFCC plugin to consume resolved B2C CLI configuration.
description: Configure IDE tooling like Prophet VS Code extension and IntelliJ SFCC plugin to consume resolved B2C CLI configuration, plus enable Script API IntelliSense via TypeScript definitions.
---

# IDE Integration

This guide explains how to connect third-party IDE extensions (Prophet, IntelliJ SFCC) to your B2C CLI configuration.
This guide explains how to connect third-party IDE extensions (Prophet, IntelliJ SFCC) to your B2C CLI configuration, and how to enable Script API IntelliSense in any IDE.

> Looking for the **Salesforce-published B2C DX VS Code Extension**? See the dedicated [VS Code Extension](../vscode-extension/) section — it consumes `dw.json` and the active instance directly, no bridge script required.

## Script API IntelliSense

Get autocomplete and inline documentation on `require('dw/catalog/ProductMgr')`, hover docs from JSDoc, signature help, and member completion in cartridge JavaScript files. The B2C tooling ships TypeScript definitions for the Script API (currently version 26.7) covering all `dw/*` modules, top-level globals (`request`, `customer`, `session`), and the `ICustomAttributes` extension hook.

There are three setup paths depending on your IDE:

### B2C DX VS Code Extension (recommended)

If you have the B2C DX VS Code extension installed, IntelliSense is automatic:

- No configuration files are written into your repository.
- The extension registers a TypeScript Server plugin that resolves `dw/*` modules transparently for any file inside a detected cartridge (folders containing a `.project` file alongside a `cartridge/` directory).
- Files outside your cartridges are unaffected.

You can disable the feature with the `b2c-dx.features.scriptTypes` setting (default: `true`).

The plugin also resolves SFCC cartridge-style requires, matching runtime semantics:

- `require('~/cartridge/scripts/foo')` — resolves only within the cartridge that contains the current file (the SFCC `~` shortcut for "current cartridge"). If `foo` doesn't exist there, IntelliSense reports it unresolved — same as runtime.
- `require('*/cartridge/scripts/foo')` — walks the cartridge path with owner-first override priority (SFRA-style override).
- `require('app_storefront_base/cartridge/scripts/foo')` — resolves only within the named cartridge.
- `require('server')`, `require('server/middleware')`, etc. — bare requires resolve against the SFRA `modules` cartridge if present (its tree is exposed at the root, not under `cartridge/scripts/`). When a `modules` cartridge is detected, the plugin also injects ambient type declarations for the SFRA `server` API (Server, Route, Request, Response, middleware, forms, querystring) so cartridge code type-checks under `checkJs: true` despite the dynamic property assignments in `modules/server.js` that TypeScript can't infer on its own.

Cartridge resolution order matches your runtime cartridge path: the `cartridges` field from your resolved configuration (`dw.json`, `SFCC_CARTRIDGES`, `.env`, etc.) wins. When that's not set, cartridges fall back to discovery order with known base cartridges (`app_storefront_base`, `modules`) sorted last. The same ordering also drives the **B2C-DX → Cartridges** tree view.

### Standalone VS Code, WebStorm, or IntelliJ Ultimate

For IDEs without the extension, run the following from your project root to vendor the type bundle and a `jsconfig.json`:

```bash
b2c setup ide vscode-types
```

This creates two artifacts at the repo root:

- `./.b2c-script-types/types/` — vendored copy of the Script API definitions.
- `./jsconfig.json` — TypeScript Language Service configuration mapping `dw/*` to the vendored types.

You can commit both into your repository if you want everyone on the team to share the same setup. To re-vendor after upgrading the CLI, re-run with `--force`. The `jsconfig.json` lives at the repo root by design — the `paths` mappings inside it are repo-root-relative and will not resolve correctly from a subdirectory.

The generated `jsconfig.json` looks like this — feel free to author it yourself if you prefer:

```json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"allowJs": true,
"checkJs": false,
"noEmit": true,
"baseUrl": ".",
"paths": {
"dw/*": ["./.b2c-script-types/types/dw/*"]
},
"types": []
},
"include": [".b2c-script-types/types/global.d.ts", "**/cartridge/**/*.js"],
"exclude": ["**/cartridge/static/**", "**/node_modules/**"]
}
```

### Neovim, Helix, Zed, Sublime, or other LSP-based editors

Modern editors that drive `tsserver` through the Language Server Protocol have two ways to wire up Script API IntelliSense:

**Option A — vendored `jsconfig.json` (dw/* only).** Run `b2c setup ide vscode-types` at the repo root and your LSP picks it up on next start. Provides only `dw/*` resolution; cartridge-relative requires (`~/cartridge/...`, `*/cartridge/...`) are not handled because TypeScript `paths` mappings can't express multi-cartridge lookups.

**Option B — load the bundled TS Server plugin (full feature parity with the VS Code extension).** Configure your LSP client to load `@salesforce/b2c-script-types` as a TypeScript Server plugin via `init_options`. The plugin auto-discovers cartridges by walking the project for `.project` files, and honors `dw.json`'s `cartridges` field for ordering — no separate vendoring step.

Resolve the plugin location via the CLI:

```bash
b2c setup ide tsserver-plugin --json
# {"pluginName":"@salesforce/b2c-script-types","pluginPath":"/usr/lib/.../dist/script-types","typesPath":"...","version":"26.7.0"}
```

The recommended language servers and what to install:

- **Neovim** with [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) — use the `ts_ls` server (formerly `tsserver`), backed by the `typescript-language-server` npm package. Older `coc-tsserver` setups also work. The [nvim-sfcc](https://github.com/clavery/nvim-sfcc) plugin wraps the wiring below.
- **Helix** — bundles `typescript-language-server`; nothing to wire up beyond installing the package globally (`npm i -g typescript-language-server typescript`).
- **Zed** — ships TypeScript support out of the box; no extra configuration.
- **Sublime Text** — install `LSP` and `LSP-typescript` from Package Control.

A minimal Neovim 0.10+ snippet using `nvim-lspconfig` and the TS Server plugin:

```lua
local function b2c_plugin_path()
local out = vim.fn.system({ 'b2c', 'setup', 'ide', 'tsserver-plugin', '--json' })
return (vim.fn.json_decode(out) or {}).pluginPath
end

require('lspconfig').ts_ls.setup({
root_dir = require('lspconfig.util').root_pattern('jsconfig.json', 'tsconfig.json', '.project', '.git'),
filetypes = { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact' },
init_options = {
plugins = {
{ name = '@salesforce/b2c-script-types', location = b2c_plugin_path() },
},
},
})
```

If your editor's LSP client is launched outside the repo root (for example, opening a single cartridge subdirectory), point it at the project root so the plugin's auto-discovery walks the right tree.

### Notes

- The bundle is version-locked to a Script API release (currently 26.7). Re-run `b2c setup ide vscode-types --force` after upgrading the CLI to refresh the vendored copy. The plugin path returned by `b2c setup ide tsserver-plugin` always points at the bundle shipped with your installed CLI.
- The vendored `jsconfig.json` only configures `dw/*` IntelliSense. Cartridge-relative requires (`~/cartridge/...`, `*/cartridge/...`, `cartridgeName/cartridge/...`) cannot be expressed in standalone TypeScript `paths` mappings (TypeScript allows at most one `*` per pattern), so they will appear unresolved without the B2C DX VS Code extension or another host that loads `@salesforce/b2c-script-types/plugin` via LSP.

## Prophet VS Code Extension

[Prophet](https://marketplace.visualstudio.com/items?itemName=SqrTT.prophet) can load `dw.json`-compatible configuration by executing a local `dw.js` script in your project directory.
Expand Down
4 changes: 4 additions & 0 deletions docs/vscode-extension/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Stream live `error-*.log`, `warn-*.log`, and `info-*.log` files from your sandbo

The bottom-left of the window shows your active instance — the name, the hostname, and a pin icon if you've locked a particular folder as the project root. Click it to switch instances; every view updates instantly.

### Script API IntelliSense

Cartridge JavaScript files automatically light up with autocomplete and hover docs for `dw/*` modules — no `jsconfig.json` written into your repo. See the [Script API IntelliSense guide](../guide/ide-integration#script-api-intellisense) for how it works and how to enable the same support in other IDEs.

### B2C CLI Plugin Support

The extension runs the [B2C CLI](../guide/) under the hood, so any plugin you've installed via `b2c plugins install` automatically applies here too. Add a plugin that introduces a new config source, a custom sandbox command, or middleware, and the extension picks it up the next time the workspace loads — no separate plugin registry. (The MCP server works the same way; see the [MCP plugins note](../mcp/#plugins).)
Expand Down
2 changes: 1 addition & 1 deletion packages/b2c-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
},
"repository": "SalesforceCommerceCloud/b2c-developer-tooling",
"scripts": {
"build": "shx rm -rf dist && tsc -p tsconfig.build.json",
"build": "shx rm -rf dist && tsc -p tsconfig.build.json && shx mkdir -p dist/script-types/node_modules/@salesforce/b2c-script-types && shx cp -r ../b2c-script-types/types dist/script-types/node_modules/@salesforce/b2c-script-types/types && shx cp -r ../b2c-script-types/plugin dist/script-types/node_modules/@salesforce/b2c-script-types/plugin && shx cp ../b2c-script-types/package.json dist/script-types/node_modules/@salesforce/b2c-script-types/package.json",
"lint": "eslint",
"lint:agent": "eslint --quiet",
"typecheck:agent": "tsc --noEmit -p test --pretty false",
Expand Down
125 changes: 125 additions & 0 deletions packages/b2c-cli/src/commands/setup/ide/tsserver-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {existsSync} from 'node:fs';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import {fileURLToPath} from 'node:url';

import {ux} from '@oclif/core';
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';

import {withDocs} from '../../../i18n/index.js';

interface TsserverPluginInfo {
/** TS server plugin package name (use as `name` in tsserver init_options.plugins). */
pluginName: string;
/**
* Probe location (use as `location` in tsserver init_options.plugins). TypeScript
* resolves the plugin via `<location>/node_modules/<pluginName>`, so this is the
* parent of `node_modules` rather than the package directory itself.
*/
pluginPath: string;
/** Absolute directory containing the bundled dw/* TypeScript definitions. */
typesPath: string;
/** Plugin/types bundle version, when available. */
version?: string;
}

const SCRIPT_TYPES_PACKAGE = '@salesforce/b2c-script-types';

/**
* Print absolute paths to the bundled @salesforce/b2c-script-types TypeScript
* Server plugin and types directory. Useful for editor integrations (Neovim,
* Helix, Zed, etc.) that need to wire up the plugin via LSP init_options.
*
* Performs no filesystem writes — purely a metadata lookup.
*/
export default class SetupIdeTsserverPlugin extends BaseCommand<typeof SetupIdeTsserverPlugin> {
static description = withDocs(
'Print paths to the bundled Script API TypeScript Server plugin (for LSP-based editors)',
'/cli/setup.html#b2c-setup-ide-tsserver-plugin',
);

static enableJsonFlag = true;

static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json'];

static flags = {
...BaseCommand.baseFlags,
};

async run(): Promise<TsserverPluginInfo> {
const {pluginPath, packageDir} = this.resolvePaths();
const typesPath = path.join(packageDir, 'types');
let version: string | undefined;
try {
const pkg = JSON.parse(await fs.readFile(path.join(packageDir, 'package.json'), 'utf8')) as {
version?: string;
};
version = pkg.version;
} catch {
// Best-effort; missing version metadata is non-fatal.
}

const result: TsserverPluginInfo = {
pluginName: SCRIPT_TYPES_PACKAGE,
pluginPath,
typesPath,
version,
};

if (!this.jsonEnabled()) {
ux.stdout(`Plugin name: ${result.pluginName}`);
ux.stdout(`Plugin path: ${result.pluginPath}`);
ux.stdout(`Types path: ${result.typesPath}`);
if (version) ux.stdout(`Version: ${version}`);
}

return result;
}

private isPackageDir(dir: string): boolean {
return (
existsSync(path.join(dir, 'package.json')) &&
existsSync(path.join(dir, 'plugin')) &&
existsSync(path.join(dir, 'types'))
);
}

/**
* Resolve the bundled b2c-script-types package. tsserver looks up plugins via
* `<pluginPath>/node_modules/<pluginName>`, so the returned `pluginPath` is the
* directory whose `node_modules/` contains the package — NOT the package dir
* itself. `packageDir` is the actual package root (where package.json/types/
* live), used for metadata reads and the typesPath.
*
* The bundle ships at `dist/script-types/node_modules/@salesforce/b2c-script-types/`
* for both production (npm-installed CLI) and source-tree dev runs after
* `pnpm --filter @salesforce/b2c-cli run build`. Running `./cli` against
* uncompiled source without a prior build will not find the bundle.
*/
private resolvePaths(): {pluginPath: string; packageDir: string} {
const here = path.dirname(fileURLToPath(import.meta.url));

// Compiled: dist/commands/setup/ide/tsserver-plugin.js -> dist/script-types/...
const builtRoot = path.resolve(here, '..', '..', '..', 'script-types');
// tsx (source): src/commands/setup/ide/tsserver-plugin.ts -> dist/script-types/...
// (resolved relative to the package root, requires a prior build)
const srcRoot = path.resolve(here, '..', '..', '..', '..', 'dist', 'script-types');

for (const root of [builtRoot, srcRoot]) {
const packageDir = path.join(root, 'node_modules', SCRIPT_TYPES_PACKAGE);
if (this.isPackageDir(packageDir)) {
return {pluginPath: root, packageDir};
}
}

this.error(
'Could not locate the bundled Script API plugin. ' +
'For an installed CLI, reinstall it. For a source checkout, run `pnpm --filter @salesforce/b2c-cli run build` first.',
);
}
}
Loading
Loading