Skip to content

Commit 8aa076e

Browse files
authored
feat(vs-extension): API Browser shopper-schema → sfnext scapi add (#457)
* feat(vs-extension): API Browser shopper-schema → sfnext scapi add Right-click a Shopper schema in the API Browser to run `pnpm sfnext scapi add <family> <name> <version>` in an integrated terminal. Menu is gated on Storefront Next workspace detection (via @salesforce/b2c-tooling-sdk/discovery) so it only appears in sfnext projects. Logs detection + invocation to the B2C DX output channel for diagnosis. * refactor(vs-extension): trim verbose API Browser diagnostic logging
1 parent 9e44bee commit 8aa076e

5 files changed

Lines changed: 96 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'b2c-vs-extension': minor
3+
---
4+
5+
API Browser: right-click a Shopper schema to run `pnpm sfnext scapi add` in an integrated terminal. The action is only shown when the workspace is detected as a Storefront Next project.

packages/b2c-vs-extension/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@
261261
"title": "Open API Documentation",
262262
"category": "B2C DX - API Browser"
263263
},
264+
{
265+
"command": "b2c-dx.apiBrowser.scapiAdd",
266+
"title": "Add to Storefront (sfnext scapi add)",
267+
"icon": "$(add)",
268+
"category": "B2C DX - API Browser"
269+
},
264270
{
265271
"command": "b2c-dx.sandbox.refresh",
266272
"title": "Refresh",
@@ -680,6 +686,11 @@
680686
}
681687
],
682688
"view/item/context": [
689+
{
690+
"command": "b2c-dx.apiBrowser.scapiAdd",
691+
"when": "view == b2cApiBrowser && viewItem == apiSchema-shopper && b2c-dx.isStorefrontNext",
692+
"group": "1_storefront@1"
693+
},
683694
{
684695
"command": "b2c-dx.webdav.addCatalog",
685696
"when": "view == b2cWebdavExplorer && viewItem == virtual-root-catalogs",
@@ -902,6 +913,10 @@
902913
"command": "b2c-dx.apiBrowser.openSwagger",
903914
"when": "false"
904915
},
916+
{
917+
"command": "b2c-dx.apiBrowser.scapiAdd",
918+
"when": "false"
919+
},
905920
{
906921
"command": "b2c-dx.sandbox.removeRealm",
907922
"when": "false"

packages/b2c-vs-extension/src/api-browser/api-browser-tree-provider.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export interface SchemaEntry {
2727
status?: 'current' | 'deprecated';
2828
}
2929

30+
export function isShopperSchema(schema: Pick<SchemaEntry, 'apiFamily' | 'apiName'>): boolean {
31+
const family = (schema.apiFamily ?? '').toLowerCase();
32+
const name = (schema.apiName ?? '').toLowerCase();
33+
return family.startsWith('shopper') || name.startsWith('shopper');
34+
}
35+
3036
export class ApiSchemaTreeItem extends vscode.TreeItem {
3137
readonly nodeType = 'apiSchema' as const;
3238
readonly schema: SchemaEntry;
@@ -36,7 +42,11 @@ export class ApiSchemaTreeItem extends vscode.TreeItem {
3642
this.schema = schema;
3743
this.id = `api:schema:${schema.apiFamily}:${schema.apiName}:${schema.apiVersion}`;
3844
this.description = schema.apiVersion;
39-
this.contextValue = 'apiSchema';
45+
46+
// The contextValue drives the Storefront Next "scapi add" right-click menu,
47+
// which is only offered for Shopper schemas (see isShopperSchema + package.json).
48+
const isShopper = isShopperSchema(schema);
49+
this.contextValue = isShopper ? 'apiSchema-shopper' : 'apiSchema-admin';
4050

4151
// Tooltip type is best-effort — the authoritative classification happens
4252
// when the spec is loaded (see detectApiType in swagger-webview.ts) since
@@ -45,12 +55,12 @@ export class ApiSchemaTreeItem extends vscode.TreeItem {
4555
let apiType: string;
4656
if (schema.apiFamily === 'custom') {
4757
apiType = 'Custom';
48-
} else if (schema.apiName.startsWith('shopper-') || schema.apiFamily === 'shopper') {
58+
} else if (isShopper) {
4959
apiType = 'Shopper';
5060
} else {
5161
apiType = 'Admin';
5262
}
53-
this.tooltip = `${schema.apiName} ${schema.apiVersion} (${apiType})`;
63+
this.tooltip = `${schema.apiName} ${schema.apiVersion} (${apiType}) — apiFamily="${schema.apiFamily}"`;
5464

5565
if (schema.status === 'deprecated') {
5666
this.iconPath = new vscode.ThemeIcon('warning', new vscode.ThemeColor('list.warningForeground'));

packages/b2c-vs-extension/src/api-browser/index.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,26 @@
66
import * as vscode from 'vscode';
77
import type {B2CExtensionConfig} from '../config-provider.js';
88
import {registerSafeCommand} from '../safety.js';
9-
import {ApiBrowserTreeDataProvider, type SchemaEntry} from './api-browser-tree-provider.js';
9+
import {
10+
ApiBrowserTreeDataProvider,
11+
ApiSchemaTreeItem,
12+
isShopperSchema,
13+
type SchemaEntry,
14+
} from './api-browser-tree-provider.js';
1015
import {SwaggerWebviewManager} from './swagger-webview.js';
1116

17+
const SFNEXT_TERMINAL_NAME = 'sfnext scapi add';
18+
19+
function runSfnextScapiAdd(schema: SchemaEntry, cwd: string, log: vscode.OutputChannel): void {
20+
const existing = vscode.window.terminals.find((t) => t.name === SFNEXT_TERMINAL_NAME);
21+
const terminal = existing ?? vscode.window.createTerminal({name: SFNEXT_TERMINAL_NAME, cwd});
22+
const command = `pnpm sfnext scapi add ${schema.apiFamily} ${schema.apiName} ${schema.apiVersion}`;
23+
log.appendLine(`[API Browser] Spawning terminal "${SFNEXT_TERMINAL_NAME}" (cwd=${cwd})`);
24+
log.appendLine(`[API Browser] $ ${command}`);
25+
terminal.show();
26+
terminal.sendText(command);
27+
}
28+
1229
export function registerApiBrowser(
1330
context: vscode.ExtensionContext,
1431
configProvider: B2CExtensionConfig,
@@ -30,9 +47,24 @@ export function registerApiBrowser(
3047
swaggerManager.openSwaggerPanel(schema);
3148
});
3249

50+
const scapiAddDisposable = registerSafeCommand(
51+
'b2c-dx.apiBrowser.scapiAdd',
52+
(item: ApiSchemaTreeItem | SchemaEntry) => {
53+
const schema = item instanceof ApiSchemaTreeItem ? item.schema : item;
54+
if (!schema || !isShopperSchema(schema)) {
55+
const msg = `Storefront Next scapi add is only available for Shopper schemas (got apiFamily="${schema?.apiFamily ?? '<missing>'}", apiName="${schema?.apiName ?? '<missing>'}").`;
56+
log.appendLine(`[API Browser] ${msg}`);
57+
vscode.window.showErrorMessage(msg);
58+
return;
59+
}
60+
const cwd = configProvider.getWorkingDirectory();
61+
runSfnextScapiAdd(schema, cwd, log);
62+
},
63+
);
64+
3365
configProvider.onDidReset(() => {
3466
treeProvider.refresh();
3567
});
3668

37-
context.subscriptions.push(treeView, refreshDisposable, openSwaggerDisposable, swaggerManager);
69+
context.subscriptions.push(treeView, refreshDisposable, openSwaggerDisposable, scapiAddDisposable, swaggerManager);
3870
}

packages/b2c-vs-extension/src/extension.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
55
*/
66
import {DwJsonSource} from '@salesforce/b2c-tooling-sdk/config';
7+
import {detectWorkspaceType} from '@salesforce/b2c-tooling-sdk/discovery';
78
import {configureLogger} from '@salesforce/b2c-tooling-sdk/logging';
89

910
import * as path from 'path';
@@ -48,6 +49,27 @@ function applyLogLevel(log: vscode.OutputChannel): void {
4849
}
4950
}
5051

52+
async function updateStorefrontNextContext(
53+
configProvider: B2CExtensionConfig,
54+
log: vscode.OutputChannel,
55+
): Promise<void> {
56+
const workingDir = configProvider.getWorkingDirectory();
57+
log.appendLine(`[Workspace] Running detectWorkspaceType for cwd=${workingDir}`);
58+
let isStorefrontNext = false;
59+
try {
60+
const result = await detectWorkspaceType(workingDir);
61+
log.appendLine(
62+
`[Workspace] Detection result: projectTypes=[${result.projectTypes.join(', ')}] matchedPatterns=[${result.matchedPatterns.join(', ')}]`,
63+
);
64+
isStorefrontNext = result.projectTypes.includes('storefront-next');
65+
} catch (err) {
66+
const message = err instanceof Error ? err.message : String(err);
67+
log.appendLine(`[Workspace] storefront-next detection failed: ${message}`);
68+
}
69+
await vscode.commands.executeCommand('setContext', 'b2c-dx.isStorefrontNext', isStorefrontNext);
70+
log.appendLine(`[Workspace] setContext b2c-dx.isStorefrontNext=${isStorefrontNext} (cwd=${workingDir})`);
71+
}
72+
5173
export async function activate(context: vscode.ExtensionContext) {
5274
const log = vscode.window.createOutputChannel('B2C DX');
5375

@@ -101,6 +123,13 @@ async function activateInner(context: vscode.ExtensionContext, log: vscode.Outpu
101123

102124
registerSafety(context, configProvider);
103125

126+
void updateStorefrontNextContext(configProvider, log);
127+
context.subscriptions.push(
128+
configProvider.onDidReset(() => {
129+
void updateStorefrontNextContext(configProvider, log);
130+
}),
131+
);
132+
104133
const cartridgeService = new CartridgeService(configProvider);
105134
context.subscriptions.push(cartridgeService);
106135

0 commit comments

Comments
 (0)