Skip to content

Commit 3758114

Browse files
authored
Add EnvSource config source and dedicated VS Code extension release (#241)
* Add EnvSource config source and improve VS Code extension config loading - Add EnvSource ConfigSource in SDK that maps SFCC_* env vars to NormalizedConfig fields (priority -10, opt-in via sourcesBefore) - Extension now loads .env files and uses EnvSource for env var support - Smart workspace folder detection scans for dw.json, .env with SFCC_* vars, and package.json b2c key in multi-root workspaces - Add "Use as B2C Commerce Root" context menu on workspace folders to pin a specific folder, with "Reset to Auto-Detect" command to clear - Pinned root persisted in workspace state, shown in status bar - Add .env file watchers alongside existing dw.json watchers * Add dedicated VS Code extension GitHub Release and changeset Add a new publish.yml step that creates a separate `b2c-vs-extension@X.Y.Z` GitHub Release with the VSIX attached, so users can easily find and download the latest extension. The release is marked `--latest=false` to avoid overtaking the primary npm publish releases. The step only runs when the extension publishes alongside other packages (to avoid duplicating the combined release when extension-only). * Add changeset for VS Code extension config improvements * Add b2c-vs-extension to AGENTS.md package list and valid changesets
1 parent 732d4ad commit 3758114

11 files changed

Lines changed: 649 additions & 30 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+
Add `.env` file loading, `SFCC_*` env var support, and smart workspace folder detection for multi-root workspaces
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@salesforce/b2c-tooling-sdk': minor
3+
---
4+
5+
Add `EnvSource` config source that maps `SFCC_*` environment variables to config fields

.github/workflows/publish.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,35 @@ jobs:
400400
env:
401401
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
402402

403+
- name: Create VS Code extension release
404+
if: >-
405+
steps.release-type.outputs.type == 'stable'
406+
&& steps.packages.outputs.publish_vsx == 'true'
407+
&& (steps.packages.outputs.publish_cli == 'true' || steps.packages.outputs.publish_sdk == 'true' || steps.packages.outputs.publish_mcp == 'true')
408+
run: |
409+
VSX_TAG="b2c-vs-extension@${{ steps.packages.outputs.version_vsx }}"
410+
411+
# Create a dedicated release for the extension (not latest — main releases own that)
412+
gh release create "$VSX_TAG" \
413+
--title "VS Code Extension ${{ steps.packages.outputs.version_vsx }}" \
414+
--latest=false \
415+
--notes "$(cat <<'NOTES'
416+
## B2C DX VS Code Extension v${{ steps.packages.outputs.version_vsx }}
417+
418+
Download the \`.vsix\` file below and install via:
419+
\`\`\`
420+
code --install-extension b2c-vs-extension-${{ steps.packages.outputs.version_vsx }}.vsix
421+
\`\`\`
422+
423+
Or in VS Code: Extensions → ⋯ → Install from VSIX...
424+
NOTES
425+
)"
426+
427+
# Upload the VSIX to the dedicated release
428+
gh release upload "$VSX_TAG" packages/b2c-vs-extension/*.vsix
429+
env:
430+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
431+
403432
- name: Trigger documentation deployment
404433
if: >-
405434
steps.release-type.outputs.type == 'stable' && steps.changesets.outputs.skip != 'true' && steps.quick-check.outputs.skip != 'true'

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This is a monorepo project with the following packages:
44
- `./packages/b2c-cli` - the command line interface built with oclif
55
- `./packages/b2c-tooling-sdk` - the SDK/library for B2C Commerce operations; supports the CLI and can be used standalone
66
- `./packages/b2c-dx-mcp` - Model Context Protocol server; also built with oclif
7+
- `./packages/b2c-vs-extension` - VS Code extension (not published to npm; packaged as VSIX and versioned via git tags)
78
- `./docs` - documentation site (private `@salesforce/b2c-dx-docs` workspace package; not published to npm)
89

910
## Common Commands
@@ -182,7 +183,7 @@ Changeset guidelines:
182183
- HOW a consumer should update their code
183184
- Good changesets are brief and user-focused (not contributor); they are generally 1 line or two; The content of the changeset is used in CHANGELOG and release notes. You do not need to list internal implementation details or all details of commands; just the high level summary for users.
184185

185-
Valid changeset packages: `@salesforce/b2c-cli`, `@salesforce/b2c-tooling-sdk`, `@salesforce/b2c-dx-mcp`, `@salesforce/b2c-dx-docs`
186+
Valid changeset packages: `@salesforce/b2c-cli`, `@salesforce/b2c-tooling-sdk`, `@salesforce/b2c-dx-mcp`, `b2c-vs-extension`, `@salesforce/b2c-dx-docs`
186187

187188
Create a changeset file directly in `.changeset/` with a unique filename (e.g., `descriptive-change-name.md`):
188189

packages/b2c-tooling-sdk/src/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,4 @@ export {ConfigSourceRegistry, globalConfigSourceRegistry} from './config-source-
145145

146146
// Config sources (for direct use)
147147
export {DwJsonSource} from './sources/dw-json-source.js';
148+
export {EnvSource} from './sources/env-source.js';
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
/**
7+
* Environment variable configuration source.
8+
*
9+
* Maps SFCC_* environment variables to NormalizedConfig fields.
10+
* Not included in default sources — opt-in only via `sourcesBefore`.
11+
*
12+
* @internal This module is internal to the SDK. Use ConfigResolver instead.
13+
*/
14+
import type {AuthMethod} from '../../auth/types.js';
15+
import {getPopulatedFields} from '../mapping.js';
16+
import type {ConfigSource, ConfigLoadResult, NormalizedConfig, ResolveConfigOptions} from '../types.js';
17+
import {getLogger} from '../../logging/logger.js';
18+
19+
/**
20+
* Mapping of SFCC_* environment variable names to NormalizedConfig field names.
21+
*/
22+
const ENV_VAR_MAP: Record<string, keyof NormalizedConfig> = {
23+
SFCC_SERVER: 'hostname',
24+
SFCC_WEBDAV_SERVER: 'webdavHostname',
25+
SFCC_CODE_VERSION: 'codeVersion',
26+
SFCC_USERNAME: 'username',
27+
SFCC_PASSWORD: 'password',
28+
SFCC_CERTIFICATE: 'certificate',
29+
SFCC_CERTIFICATE_PASSPHRASE: 'certificatePassphrase',
30+
SFCC_SELFSIGNED: 'selfSigned',
31+
SFCC_CLIENT_ID: 'clientId',
32+
SFCC_CLIENT_SECRET: 'clientSecret',
33+
SFCC_OAUTH_SCOPES: 'scopes',
34+
SFCC_SHORTCODE: 'shortCode',
35+
SFCC_TENANT_ID: 'tenantId',
36+
SFCC_AUTH_METHODS: 'authMethods',
37+
SFCC_ACCOUNT_MANAGER_HOST: 'accountManagerHost',
38+
SFCC_SANDBOX_API_HOST: 'sandboxApiHost',
39+
};
40+
41+
/** Fields that should be parsed as comma-separated arrays. */
42+
const ARRAY_FIELDS = new Set<keyof NormalizedConfig>(['scopes', 'authMethods']);
43+
44+
/** Fields that should be parsed as booleans. */
45+
const BOOLEAN_FIELDS = new Set<keyof NormalizedConfig>(['selfSigned']);
46+
47+
/**
48+
* Configuration source that reads SFCC_* environment variables.
49+
*
50+
* Priority -10 (higher than dw.json at 0), matching CLI behavior where
51+
* env vars override file-based config.
52+
*
53+
* Not added to default sources — opt-in only. The CLI handles env vars
54+
* via oclif flag `env:` mappings; this source is for consumers like
55+
* the VS Code extension that call `resolveConfig()` directly.
56+
*
57+
* @example
58+
* ```typescript
59+
* import { resolveConfig, EnvSource } from '@salesforce/b2c-tooling-sdk/config';
60+
*
61+
* const config = resolveConfig({}, {
62+
* sourcesBefore: [new EnvSource()],
63+
* });
64+
* ```
65+
*
66+
* @internal
67+
*/
68+
export class EnvSource implements ConfigSource {
69+
readonly name = 'EnvSource';
70+
readonly priority = -10;
71+
72+
private readonly env: Record<string, string | undefined>;
73+
74+
/**
75+
* @param env - Environment object to read from. Defaults to `process.env`.
76+
*/
77+
constructor(env?: Record<string, string | undefined>) {
78+
this.env = env ?? process.env;
79+
}
80+
81+
load(_options: ResolveConfigOptions): ConfigLoadResult | undefined {
82+
const logger = getLogger();
83+
const config: NormalizedConfig = {};
84+
85+
for (const [envVar, configField] of Object.entries(ENV_VAR_MAP)) {
86+
const value = this.env[envVar];
87+
if (value === undefined || value === '') continue;
88+
89+
if (BOOLEAN_FIELDS.has(configField)) {
90+
(config as Record<string, unknown>)[configField] = value === 'true' || value === '1';
91+
} else if (ARRAY_FIELDS.has(configField)) {
92+
(config as Record<string, unknown>)[configField] = value
93+
.split(',')
94+
.map((s) => s.trim())
95+
.filter(Boolean) as string[] | AuthMethod[];
96+
} else {
97+
(config as Record<string, unknown>)[configField] = value;
98+
}
99+
}
100+
101+
const fields = getPopulatedFields(config);
102+
if (fields.length === 0) {
103+
logger.trace('[EnvSource] No SFCC_* environment variables found');
104+
return undefined;
105+
}
106+
107+
logger.trace({fields}, '[EnvSource] Loaded config from environment variables');
108+
109+
return {config, location: 'environment variables'};
110+
}
111+
}

packages/b2c-tooling-sdk/src/config/sources/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
* @internal This module is internal to the SDK. Use ConfigResolver instead.
1010
*/
1111
export {DwJsonSource} from './dw-json-source.js';
12+
export {EnvSource} from './env-source.js';
1213
export {MobifySource} from './mobify-source.js';
1314
export {PackageJsonSource} from './package-json-source.js';

0 commit comments

Comments
 (0)