Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ext-config-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'b2c-vs-extension': minor
---

Add `.env` file loading, `SFCC_*` env var support, and smart workspace folder detection for multi-root workspaces
5 changes: 5 additions & 0 deletions .changeset/ext-config-source-and-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@salesforce/b2c-tooling-sdk': minor
---

Add `EnvSource` config source that maps `SFCC_*` environment variables to config fields
29 changes: 29 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,35 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create VS Code extension release
if: >-
steps.release-type.outputs.type == 'stable'
&& steps.packages.outputs.publish_vsx == 'true'
&& (steps.packages.outputs.publish_cli == 'true' || steps.packages.outputs.publish_sdk == 'true' || steps.packages.outputs.publish_mcp == 'true')
run: |
VSX_TAG="b2c-vs-extension@${{ steps.packages.outputs.version_vsx }}"

# Create a dedicated release for the extension (not latest — main releases own that)
gh release create "$VSX_TAG" \
--title "VS Code Extension ${{ steps.packages.outputs.version_vsx }}" \
--latest=false \
--notes "$(cat <<'NOTES'
## B2C DX VS Code Extension v${{ steps.packages.outputs.version_vsx }}

Download the \`.vsix\` file below and install via:
\`\`\`
code --install-extension b2c-vs-extension-${{ steps.packages.outputs.version_vsx }}.vsix
\`\`\`

Or in VS Code: Extensions → ⋯ → Install from VSIX...
NOTES
)"

# Upload the VSIX to the dedicated release
gh release upload "$VSX_TAG" packages/b2c-vs-extension/*.vsix
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Trigger documentation deployment
if: >-
steps.release-type.outputs.type == 'stable' && steps.changesets.outputs.skip != 'true' && steps.quick-check.outputs.skip != 'true'
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This is a monorepo project with the following packages:
- `./packages/b2c-cli` - the command line interface built with oclif
- `./packages/b2c-tooling-sdk` - the SDK/library for B2C Commerce operations; supports the CLI and can be used standalone
- `./packages/b2c-dx-mcp` - Model Context Protocol server; also built with oclif
- `./packages/b2c-vs-extension` - VS Code extension (not published to npm; packaged as VSIX and versioned via git tags)
- `./docs` - documentation site (private `@salesforce/b2c-dx-docs` workspace package; not published to npm)

## Common Commands
Expand Down Expand Up @@ -182,7 +183,7 @@ Changeset guidelines:
- HOW a consumer should update their code
- 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.

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

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

Expand Down
1 change: 1 addition & 0 deletions packages/b2c-tooling-sdk/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ export {ConfigSourceRegistry, globalConfigSourceRegistry} from './config-source-

// Config sources (for direct use)
export {DwJsonSource} from './sources/dw-json-source.js';
export {EnvSource} from './sources/env-source.js';
111 changes: 111 additions & 0 deletions packages/b2c-tooling-sdk/src/config/sources/env-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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
*/
/**
* Environment variable configuration source.
*
* Maps SFCC_* environment variables to NormalizedConfig fields.
* Not included in default sources — opt-in only via `sourcesBefore`.
*
* @internal This module is internal to the SDK. Use ConfigResolver instead.
*/
import type {AuthMethod} from '../../auth/types.js';
import {getPopulatedFields} from '../mapping.js';
import type {ConfigSource, ConfigLoadResult, NormalizedConfig, ResolveConfigOptions} from '../types.js';
import {getLogger} from '../../logging/logger.js';

/**
* Mapping of SFCC_* environment variable names to NormalizedConfig field names.
*/
const ENV_VAR_MAP: Record<string, keyof NormalizedConfig> = {
SFCC_SERVER: 'hostname',
SFCC_WEBDAV_SERVER: 'webdavHostname',
SFCC_CODE_VERSION: 'codeVersion',
SFCC_USERNAME: 'username',
SFCC_PASSWORD: 'password',
SFCC_CERTIFICATE: 'certificate',
SFCC_CERTIFICATE_PASSPHRASE: 'certificatePassphrase',
SFCC_SELFSIGNED: 'selfSigned',
SFCC_CLIENT_ID: 'clientId',
SFCC_CLIENT_SECRET: 'clientSecret',
SFCC_OAUTH_SCOPES: 'scopes',
SFCC_SHORTCODE: 'shortCode',
SFCC_TENANT_ID: 'tenantId',
SFCC_AUTH_METHODS: 'authMethods',
SFCC_ACCOUNT_MANAGER_HOST: 'accountManagerHost',
SFCC_SANDBOX_API_HOST: 'sandboxApiHost',
};

/** Fields that should be parsed as comma-separated arrays. */
const ARRAY_FIELDS = new Set<keyof NormalizedConfig>(['scopes', 'authMethods']);

/** Fields that should be parsed as booleans. */
const BOOLEAN_FIELDS = new Set<keyof NormalizedConfig>(['selfSigned']);

/**
* Configuration source that reads SFCC_* environment variables.
*
* Priority -10 (higher than dw.json at 0), matching CLI behavior where
* env vars override file-based config.
*
* Not added to default sources — opt-in only. The CLI handles env vars
* via oclif flag `env:` mappings; this source is for consumers like
* the VS Code extension that call `resolveConfig()` directly.
*
* @example
* ```typescript
* import { resolveConfig, EnvSource } from '@salesforce/b2c-tooling-sdk/config';
*
* const config = resolveConfig({}, {
* sourcesBefore: [new EnvSource()],
* });
* ```
*
* @internal
*/
export class EnvSource implements ConfigSource {
readonly name = 'EnvSource';
readonly priority = -10;

private readonly env: Record<string, string | undefined>;

/**
* @param env - Environment object to read from. Defaults to `process.env`.
*/
constructor(env?: Record<string, string | undefined>) {
this.env = env ?? process.env;
}

load(_options: ResolveConfigOptions): ConfigLoadResult | undefined {
const logger = getLogger();
const config: NormalizedConfig = {};

for (const [envVar, configField] of Object.entries(ENV_VAR_MAP)) {
const value = this.env[envVar];
if (value === undefined || value === '') continue;

if (BOOLEAN_FIELDS.has(configField)) {
(config as Record<string, unknown>)[configField] = value === 'true' || value === '1';
} else if (ARRAY_FIELDS.has(configField)) {
(config as Record<string, unknown>)[configField] = value
.split(',')
.map((s) => s.trim())
.filter(Boolean) as string[] | AuthMethod[];
} else {
(config as Record<string, unknown>)[configField] = value;
}
}

const fields = getPopulatedFields(config);
if (fields.length === 0) {
logger.trace('[EnvSource] No SFCC_* environment variables found');
return undefined;
}

logger.trace({fields}, '[EnvSource] Loaded config from environment variables');

return {config, location: 'environment variables'};
}
}
1 change: 1 addition & 0 deletions packages/b2c-tooling-sdk/src/config/sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
* @internal This module is internal to the SDK. Use ConfigResolver instead.
*/
export {DwJsonSource} from './dw-json-source.js';
export {EnvSource} from './env-source.js';
export {MobifySource} from './mobify-source.js';
export {PackageJsonSource} from './package-json-source.js';
Loading
Loading