feat: Script API IntelliSense via TypeScript definitions#451
Merged
Conversation
Adds @salesforce/b2c-script-types workspace package containing modular TypeScript definitions for the B2C Commerce Script API (dw/*, version 26.7) plus a TypeScript Language Service plugin. The B2C DX VS Code extension registers the plugin via contributes.typescriptServerPlugins; it scopes resolution to detected cartridge directories (using the existing cartridge enumeration, hoisted into a shared CartridgeService) so non-cartridge JavaScript and TypeScript files in the same workspace are unaffected. No files are written into the user's repo. For other IDEs (plain VS Code, WebStorm, Neovim), adds `b2c setup ide vscode-types` which vendors the same bundle into .b2c-script-types/ and writes a jsconfig.json. VSIX packaging: vsce --no-dependencies hard-codes a node_modules ignore that .vscodeignore can't override, so the extension's package script now runs an inject-script-types.mjs pass that adds the staged plugin tree into the produced zip.
tsserver canonicalizes containingFile paths to forward slashes on every platform, but findCartridges() returns backslash-separated paths on Windows. The plugin's startsWith check was therefore always false on Windows, so dw/* IntelliSense silently did nothing. Normalize both the cartridgeRoots and the incoming containingFile to forward slashes, and fold case when ts.sys.useCaseSensitiveFileNames is false (Windows + default macOS) so different drive-letter casing or upper/lowercase paths still match.
…n Windows path.join produces backslashes on Windows, but tsserver keys its internal file map on forward-slash paths. Returning a backslash resolvedFileName from the plugin can cause subtle duplicate-file or cannot-find-module issues in cartridge files until the TS server restarts.
The TS Server plugin now resolves SFCC cartridge-style imports across the workspace's cartridge path, not just dw/* modules: ~/cartridge/scripts/foo -> any cartridge, owner-first override semantics */cartridge/scripts/foo -> equivalent to ~/ bar/cartridge/scripts/foo -> only the cartridge named "bar" Cartridge order is sourced from the resolved configuration's `cartridges` field (dw.json / SFCC_CARTRIDGES / .env), matching the runtime cartridge path. When that's not set, cartridges fall back to discovery order with known base cartridges (app_storefront_base, modules) sorted last. The same ordering now drives the B2C-DX cartridges tree view, so the sidebar and IntelliSense agree on priority.
Adds a CLI-reference entry for `b2c setup ide vscode-types` covering flags, examples, and outputs, and expands the IDE integration guide's LSP section with concrete recommendations for Neovim (ts_ls via nvim-lspconfig), Helix, Zed, and Sublime LSP. Also clarifies that the generated jsconfig.json belongs at the repo root because its `paths` mappings are repo-root-relative.
…tics Address review feedback and extend the Script API IntelliSense to LSP-based editors (Neovim, Helix, Zed, Sublime, etc.) without the VS Code extension: - Add `b2c setup ide tsserver-plugin` (also `--json`) which prints the absolute paths to the bundled TS Server plugin and types directory. Editors wire `pluginPath` into `init_options.plugins[].location` and TypeScript probe-loads `@salesforce/b2c-script-types` from `<location>/node_modules/...`. - Bundle the plugin under the probe-shaped layout `dist/script-types/node_modules/@salesforce/b2c-script-types/` so TS's plugin loader finds it. Update vscode-types' types-path resolution to match. - Auto-discovery: when no host pushes a cartridge list (i.e. plain LSP usage), the plugin walks the project root for `.project` markers and honors `dw.json`'s `cartridges` field for ordering. `applyConfig` no longer wipes the discovered list when subsequent updates omit the `cartridges` field. Adds an `autoDiscover` config flag (default on) for hosts that want to opt out. - Restrict `~/cartridge/...` resolution to the cartridge that owns the calling file (matches SFCC runtime). `*/cartridge/...` keeps the cartridge-path walk with owner-first override priority. - Drop the multi-wildcard cartridge entries from the generated jsconfig.json and the `jsconfig.template.json` — TypeScript only allows one `*` per pattern, so `~/cartridge/*` and `*/cartridge/scripts/*` produced TS5061/TS5062 and never functioned. The vendored `jsconfig.json` now configures `dw/*` only; cartridge-relative IntelliSense for plain IDEs requires the LSP plugin path. - Update IDE Integration guide with the LSP plugin setup, document the new CLI command in cli/setup.md, refresh the changeset summary.
…rdering Both the VS Code cartridge tree and the LSP plugin's auto-discovery used a Set of "known base cartridges" with a single rank, which let modules sort before app_storefront_base depending on filesystem discovery order. SFRA's runtime cartridge path ends in `...:app_storefront_base:modules` so modules should be strictly last. Replace the set with a priority map (app_storefront_base=1, modules=2); non-base cartridges keep their discovery-order rank (0).
Set CartridgeItem.command to 'revealInExplorer'. Activating a cartridge in the B2C-DX cartridges tree (single or double click depending on the user's workbench.list.openMode) now expands the file tree to the cartridge directory in the sidebar Explorer view.
…rtridge
The SFRA `modules` cartridge exposes its entire tree to script-side requires
without the `cartridge/scripts/` prefix used by other cartridges, so calls
like `require('server')` -> `<modules>/server[.js|/index.js]` and
`require('server/middleware')` -> `<modules>/server/middleware.js`. The
plugin previously didn't recognize these and fell through to the original
resolver, leaving them unresolved.
Add a fallback resolver that fires after `dw/*` and cartridge-style
resolution when the require is a bare name (no relative or absolute path,
no cartridge prefix) and a cartridge literally named `modules` is in the
list. Tries the standard candidate extensions plus a package.json `main`
fallback for directories without an index.js.
The SFRA modules cartridge does not ship its own .d.ts files; properly typed
declarations would replace this discovery once available.
…handler Tree-view activation gestures are user-global (workbench.list.openMode), so making cartridge clicks reveal-only-on-double-click isn't possible per item. Switch to a context menu item instead: right-click a cartridge in the B2C-DX → Cartridges view → Reveal in Explorer. Single-clicking a cartridge now just selects it; double-clicking does nothing (the previous behavior).
…out jsconfig
types/global.d.ts declares SFCC's ambient identifiers (session, request,
response, customer, empty(...), and the `dw` namespace alias) inside a
`declare global { ... }` block. That only takes effect when the file is part
of the TS program. The vendored `jsconfig.json` includes it explicitly, but
the VS Code extension (and any other plugin host without a jsconfig) didn't
load it — so cartridge JS opened in those modes had `dw/*` IntelliSense via
the plugin's module resolver but no globals.
Wrap `host.getScriptFileNames` to append the bundled global.d.ts when the
project contains at least one cartridge file, deduping by normalized path
so projects that already include it via jsconfig are unaffected.
Add ambient declarations for the SFRA `modules` cartridge (server, route,
request, response, middleware, render, querystring, forms) so cartridge code
that does `require('server').middleware.https` etc. type-checks under
`checkJs: true`. TypeScript can't infer the dynamic property assignments in
modules/server.js (`var x = require(...); x.foo = ...; module.exports = x`),
so without ambient declarations the augmented Server type is invisible.
The plugin now injects types/sfra/server.d.ts alongside the existing
global.d.ts injection when a cartridge literally named `modules` is in the
project. resolveModulesCartridge skips bare names covered by the ambient
(`server`, `server/middleware`, etc.) so TS uses the typed declarations
instead of the inferred .js types. Other bare names (e.g.
`server/EventEmitter`) still fall through to filesystem resolution.
Targets vanilla SFRA — projects that have customized the `modules` cartridge
may see drift between the declarations and the actual implementation.
…ules cartridge source
The bundled types/sfra/server.d.ts gives cartridge JS the typed shape it needs
under checkJs:true, but go-to-definition on `require('server')`,
`server.middleware`, etc. landed inside the .d.ts instead of the actual SFRA
implementation in dependencies/modules/server*.js.
Wrap the language service so getDefinitionAtPosition,
getDefinitionAndBoundSpan, getTypeDefinitionAtPosition, and
getImplementationAtPosition rewrite results that fall inside server.d.ts.
Each `declare module 'X' { ... }` block in the d.ts is mapped to a byte
range; a result whose textSpan starts inside one of those ranges is
redirected to <modulesCart>/<X>.js (or .../X/index.js).
Member-level go-to-def (e.g. clicking on a method within a Server interface
member declaration) lands at the start of the matching JS file rather than
the specific function declaration — better than the d.ts; refining this
further would require scanning the JS for the matching name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Script API IntelliSense for cartridge JavaScript across both VS Code (via the B2C DX extension) and any LSP-based editor (Neovim, Helix, Zed, Sublime, plain VS Code, WebStorm/IntelliJ Ultimate).
@salesforce/b2c-script-typescontaining modular TypeScript definitions for the B2C Commerce Script API (dw/*, version 26.7) plus a TypeScript Server plugin.contributes.typescriptServerPluginsand pushes the resolved cartridge list in (powered by a new sharedCartridgeServicehoisted from the cartridge tree provider). Resolution is scoped to detected cartridges so non-cartridge JS/TS files in the same workspace are unaffected. No files are written into the user's repo.b2c setup ide tsserver-plugin— prints the absolute path to the bundled TS Server plugin and types directory (also--json). LSP clients wire the printed path intoinit_options.plugins[].location. Full feature parity with the VS Code extension, including cartridge-relative require resolution.b2c setup ide vscode-types— vendors./.b2c-script-types/types/and writes adw/*-onlyjsconfig.jsonfor plaintsconfig-based setups.~/cartridge/...resolves only within the cartridge that owns the calling file.*/cartridge/...walks the cartridge path with owner-first override priority.<cartridgeName>/cartridge/...resolves against the named cartridge.cartridgesfromdw.json/SFCC_CARTRIDGES; the cartridge tree view mirrors it..projectmarkers and honorsdw.json'scartridgesfield for ordering — no separate vendoring step needed.Bundle layout note
The plugin ships at
dist/script-types/node_modules/@salesforce/b2c-script-types/so TypeScript's plugin probe (<location>/node_modules/<pluginName>) can load it.b2c-script-typesremainsprivate: trueand is not added as a dep of any published package — production npm distribution is unaffected.VSIX packaging note
vsce --no-dependencieshard-codes anode_modules/**ignore that.vscodeignorenegations can't override. The extension'spackagescript runsscripts/inject-script-types.mjsafter vsce, which appends the staged plugin tree to the produced zip. Verified the final VSIX contains 540 script-types files includingplugin/index.js.Test plan
pnpm run lint:agent,pnpm run typecheck:agent,pnpm run -r format:check,pnpm run buildall passpnpm --filter @salesforce/b2c-cli run test:agent(1232 passing)b2c setup ide vscode-types --project-directory /tmp/test --forceproduces a workingjsconfig.jsonand.b2c-script-types/types/treeb2c setup ide tsserver-plugin --jsonreturns validpluginPath/typesPath; verified thatts.resolveJSModule('@salesforce/b2c-script-types', '<pluginPath>/node_modules', ts.sys)resolves andrequire()returns the plugin's init functionpnpm --filter b2c-vs-extension run packageproduces a VSIX containingextension/node_modules/@salesforce/b2c-script-types/plugin/index.jsand the fulltypes/treerequire('dw/catalog/shows module completions,~/cartridge/...and*/cartridge/...` resolve, and no files appear in the workspace.js/.tsfile in the same workspace does NOT seedw/*completions (plugin scoping works)b2c-dx.features.scriptTypestofalseand reload; confirm IntelliSense fordw/*drops while the rest of the language service continues workingts_lsinit_options.plugins[]and confirm cartridge JS getsdw/*IntelliSense plus~//*/resolution against an SFRA-style project