Skip to content

Commit e65bd15

Browse files
authored
feat: B2C Commerce script debugger (SDAPI 2.0 DAP adapter) (#330)
* feat: add B2C Commerce script debugger (SDAPI 2.0 DAP adapter) SDK layer (operations/debug/): - SDAPI 2.0 REST client with Basic auth - Debug session manager with thread polling and keepalive - DAP adapter extending @vscode/debugadapter for stdio use - Source mapping between local cartridge paths and server script paths - Variable store bridging DAP references to SDAPI object paths CLI command (b2c debug): - Runs DAP adapter over stdio for use with nvim-dap and other DAP clients - Resolves credentials from dw.json, auto-discovers cartridges Key behaviors: - InitializedEvent deferred until after SDAPI client creation - Thread re-halt detection when poll catches same thread halted at new location - Halt OutputEvent includes local path for click-through in editors - DAP OutputEvent is primary debug output; SDK logger demoted to debug/trace * feat: VS Code inline debug adapter, terminate request, and logpoints VS Code extension: - Register b2c-script debug type with DebugAdapterInlineImplementation - Auto-discover cartridges and resolve credentials from dw.json - launch.json snippet for quick configuration DAP adapter enhancements: - supportsTerminateRequest: clean session shutdown via terminate button - supportsLogPoints: breakpoints with log messages eval {expressions} server-side and auto-resume (note: briefly halts thread per hit) - Richer OutputEvent messages: halt locations use local paths, breakpoint set counts, continue confirmation - Evaluate results are expandable when the expression has members CLI: demote debug command callbacks to logger.debug (DAP OutputEvent is the primary output channel) * fix: improve debugger error messages for missing credentials and auth failures - VS Code extension shows actionable error messages referencing configuration (dw.json) when credentials are missing - DAP adapter outputs friendly messages for 401 (auth failed) and 412 (debugger not enabled) to both the debug console and notification * fix: widen sendDAP type to accept DebugProtocol.Request in test CI's pretest runs tsc -p test which typechecks test files. The sendDAP helper accepted only ProtocolMessage but callers passed Request objects with command/arguments properties.
1 parent fd1b1d0 commit e65bd15

19 files changed

Lines changed: 2810 additions & 4 deletions

File tree

packages/b2c-cli/eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default [
7777
},
7878
},
7979
{
80-
files: ['src/commands/setup/**/*.ts', 'src/commands/slas/**/*.ts'],
80+
files: ['src/commands/setup/**/*.ts', 'src/commands/slas/**/*.ts', 'src/commands/debug/**/*.ts'],
8181
rules: {
8282
// ESLint import resolver doesn't understand conditional exports (development condition)
8383
// but Node.js resolves them correctly at runtime
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
import {Flags} from '@oclif/core';
7+
import {InstanceCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {findCartridges} from '@salesforce/b2c-tooling-sdk/operations/code';
9+
import {B2CScriptDebugAdapter, type DebugSessionCallbacks} from '@salesforce/b2c-tooling-sdk/operations/debug';
10+
11+
export default class Debug extends InstanceCommand<typeof Debug> {
12+
static description = 'Start a DAP debug adapter for B2C Commerce script debugging';
13+
14+
static examples = [
15+
'<%= config.bin %> <%= command.id %>',
16+
'<%= config.bin %> <%= command.id %> --cartridge-path ./cartridges',
17+
'<%= config.bin %> <%= command.id %> --client-id my-debugger',
18+
];
19+
20+
static flags = {
21+
...InstanceCommand.baseFlags,
22+
'cartridge-path': Flags.string({
23+
description: 'Path to cartridges directory',
24+
default: '.',
25+
}),
26+
'client-id': Flags.string({
27+
description: 'Client ID for the debugger API',
28+
default: 'b2c-cli',
29+
}),
30+
};
31+
32+
async run(): Promise<void> {
33+
this.requireServer();
34+
35+
const hostname = this.resolvedConfig.values.hostname!;
36+
const username = this.resolvedConfig.values.username;
37+
const password = this.resolvedConfig.values.password;
38+
39+
if (!username || !password) {
40+
this.error(
41+
'Basic auth credentials (username/password) are required for the script debugger. ' +
42+
'Set via --username/--password flags, SFCC_USERNAME/SFCC_PASSWORD env vars, or dw.json.',
43+
);
44+
}
45+
46+
const cartridgePath = this.flags['cartridge-path'] ?? '.';
47+
const cartridges = findCartridges(cartridgePath);
48+
if (cartridges.length === 0) {
49+
this.warn(`No cartridges found in ${cartridgePath}`);
50+
}
51+
52+
this.logger.info(
53+
`Mapped ${cartridges.length} cartridge(s): ${cartridges.map((c) => c.name).join(', ') || '(none)'}`,
54+
);
55+
56+
const callbacks: DebugSessionCallbacks = {
57+
onConnected: (host) => this.logger.debug(`Connected to script debugger on ${host}`),
58+
onDisconnected: () => this.logger.debug('Script debugger disconnected'),
59+
onDebuggerDisabled: () => this.logger.debug('Script debugger was disabled externally'),
60+
};
61+
62+
const adapter = new B2CScriptDebugAdapter(
63+
{
64+
hostname,
65+
username,
66+
password,
67+
clientId: this.flags['client-id'],
68+
cartridgeRoots: cartridges,
69+
},
70+
callbacks,
71+
);
72+
73+
// Run the DAP adapter over stdio
74+
adapter.start(process.stdin, process.stdout);
75+
76+
// Wait for the adapter to finish (stdin closes)
77+
await new Promise<void>((resolve) => {
78+
process.stdin.on('end', resolve);
79+
process.stdin.on('close', resolve);
80+
});
81+
}
82+
}

packages/b2c-tooling-sdk/package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,17 @@
364364
"types": "./dist/cjs/i18n/index.d.ts",
365365
"default": "./dist/cjs/i18n/index.js"
366366
}
367+
},
368+
"./operations/debug": {
369+
"development": "./src/operations/debug/index.ts",
370+
"import": {
371+
"types": "./dist/esm/operations/debug/index.d.ts",
372+
"default": "./dist/esm/operations/debug/index.js"
373+
},
374+
"require": {
375+
"types": "./dist/cjs/operations/debug/index.d.ts",
376+
"default": "./dist/cjs/operations/debug/index.js"
377+
}
367378
}
368379
},
369380
"main": "./dist/cjs/index.js",
@@ -403,12 +414,12 @@
403414
"@oclif/prettier-config": "catalog:",
404415
"@oclif/test": "catalog:",
405416
"@salesforce/dev-config": "catalog:",
406-
"@types/tar-fs": "^2.0.4",
407417
"@types/chai": "catalog:",
408418
"@types/ejs": "^3.1.5",
409419
"@types/mocha": "catalog:",
410420
"@types/node": "catalog:",
411421
"@types/sinon": "catalog:",
422+
"@types/tar-fs": "^2.0.4",
412423
"@types/xml2js": "^0.4.14",
413424
"c8": "catalog:",
414425
"chai": "catalog:",
@@ -438,8 +449,8 @@
438449
"node": ">=22.16.0"
439450
},
440451
"dependencies": {
452+
"@vscode/debugadapter": "1.68.0",
441453
"applicationinsights": "2.9.8",
442-
"tar-fs": "3.1.2",
443454
"chokidar": "5.0.0",
444455
"cliui": "catalog:",
445456
"ejs": "3.1.10",
@@ -454,6 +465,7 @@
454465
"pino": "10.1.0",
455466
"pino-pretty": "13.1.2",
456467
"protobufjs": "7.5.4",
468+
"tar-fs": "3.1.2",
457469
"undici": "7.19.2",
458470
"xml2js": "0.6.2"
459471
}

0 commit comments

Comments
 (0)