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
7 changes: 7 additions & 0 deletions .changeset/site-library-config-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@salesforce/b2c-cli': minor
'@salesforce/b2c-tooling-sdk': minor
'b2c-vs-extension': minor
---

The `libraries` config field now accepts `{id, siteLibrary?}` objects in addition to bare strings (mixed forms allowed in the same array). This lets you mark site-private libraries in `dw.json` or `package.json` so `b2c content list` / `content export` can default `--site-library` based on which library you target, and the VS Code Content Libraries tree auto-loads every configured library on activation. To upgrade, optionally replace `"libraries": ["RefArchSharedLibrary"]` with `"libraries": ["RefArchSharedLibrary", {"id": "SiteGenesis", "siteLibrary": true}]`. The existing string-only form continues to work unchanged. Also adds `libraries`, `assetQuery`, and `realm` to the documented `package.json` allowed fields list (already supported in code).
43 changes: 40 additions & 3 deletions docs/cli/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ In addition to [global flags](./index#global-flags):
|------|-------------|---------|
| `--library` | Library ID or site ID. Also configurable via `content-library` in dw.json. | |
| `--output`, `-o` | Output directory | `content-<timestamp>` |
| `--site-library` | Treat the library as a site-private library | `false` |
| `--site-library` / `--no-site-library` | Treat the library as a site-private library. Defaults from a matching `libraries` config entry, otherwise `false` | from config |
| `--asset-query`, `-q` | JSON dot-paths for static asset extraction (can be repeated) | `image.path` |
| `--regex`, `-r` | Treat page IDs as regular expressions | `false` |
| `--folder` | Filter by folder classification (can be repeated) | |
Expand Down Expand Up @@ -102,6 +102,38 @@ export SFCC_SERVER=my-sandbox.demandware.net
export SFCC_CLIENT_ID=your-client-id
export SFCC_CLIENT_SECRET=your-client-secret
b2c content export homepage

# With a libraries config entry (see below) marking the site library as
# site-private, --site-library is inferred automatically:
b2c content export homepage --library SiteGenesis
```

### Configuring multiple libraries

Listing libraries under `b2c.libraries` in `package.json` (or `libraries` in `dw.json`) lets the CLI pick a default library and infer `--site-library` per entry. Bare strings are shared libraries; `{id, siteLibrary: true}` marks a site-private library. Both forms can be mixed:

```json
{
"b2c": {
"libraries": [
"RefArchSharedLibrary",
{ "id": "SiteGenesis", "siteLibrary": true }
]
}
}
```

With this config:

```bash
# Uses RefArchSharedLibrary (first entry) as the default library
b2c content export hero-banner

# --site-library is inferred from the matching entry
b2c content export homepage --library SiteGenesis

# Explicit flag still overrides the config
b2c content export homepage --library SiteGenesis --no-site-library
```

### Output
Expand All @@ -116,7 +148,7 @@ With `--json`, returns a structured result including the library tree, output pa

### Notes

- The `--library` flag can be set in `dw.json` as `content-library` or in `package.json` under `b2c.contentLibrary` to avoid passing it every time
- The `--library` flag can be set in `dw.json` as `content-library` or in `package.json` under `b2c.contentLibrary` to avoid passing it every time. You can also list libraries under `b2c.libraries` (mixed strings or `{id, siteLibrary?}` objects); when the resolved library matches an entry marked `siteLibrary: true`, `--site-library` defaults to true automatically. The CLI flag still wins when passed explicitly
- Use `b2c content list` to discover available page IDs before exporting
- You can export pages, content assets, or individual components by their content ID. When a component ID is specified, it is promoted to the root of the export with its full child tree
- The `--asset-query` flag specifies JSON dot-notation paths within component data to extract static asset references. The default `image.path` covers the common Page Designer image component pattern
Expand All @@ -141,7 +173,7 @@ In addition to [global flags](./index#global-flags):
| Flag | Description | Default |
|------|-------------|---------|
| `--library` | Library ID or site ID. Also configurable via `content-library` in dw.json. | |
| `--site-library` | Treat the library as a site-private library | `false` |
| `--site-library` / `--no-site-library` | Treat the library as a site-private library. Defaults from a matching `libraries` config entry, otherwise `false` | from config |
| `--library-file` | Use a local library XML file instead of fetching from instance | |
| `--type` | Filter by node type: `page`, `content`, or `component` | |
| `--components` | Include components in table output | `false` |
Expand All @@ -166,6 +198,11 @@ b2c content list --library SharedLibrary --tree
# List from a site-private library
b2c content list --library RefArch --site-library

# With a libraries config entry marking the site library as site-private,
# --site-library is inferred automatically (see "b2c content export"
# above for an example b2c.libraries config):
b2c content list --library SiteGenesis

# List from a local XML file
b2c content list --library SharedLibrary --library-file ./library.xml

Expand Down
48 changes: 39 additions & 9 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,11 @@ For the full command reference with all flags, see [Setup Commands](/cli/setup).
| `account-manager-host` | Account Manager hostname for OAuth |
| `shortCode` | SCAPI short code. Also accepts `short-code` or `scapi-shortcode`. |
| `content-library` | Default content library ID for `content export` and `content list` commands |
| `libraries` | Library IDs for the WebDAV browser and Content Libraries tree. Accepts `string[]` or `[{id, siteLibrary?}]`; elements may be mixed |
| `asset-query` | JSON dot-paths used to extract static asset URLs during content library parsing (default `["image.path"]`). Also accepts `assetQuery` |
| `tenant-id` | Organization/tenant ID for SCAPI |
| `sandbox-api-host` | ODS (sandbox) API hostname |
| `realm` | Default ODS realm for sandbox operations |
| `cip-host` | CIP analytics host override |
| `mrtApiKey` | MRT API key |
| `mrtProject` | MRT project slug |
Expand Down Expand Up @@ -319,15 +322,18 @@ You can store project-level defaults in your `package.json` file under the `b2c`

Only non-sensitive, project-level fields can be configured in `package.json`. Both camelCase and kebab-case are accepted (e.g., `shortCode` or `short-code`):

| Field | Description |
| -------------------- | --------------------------------------------------------------------------- |
| `shortCode` | SCAPI short code |
| `clientId` | OAuth client ID (for implicit login discovery) |
| `contentLibrary` | Default content library ID for `content export` and `content list` commands |
| `mrtProject` | MRT project slug |
| `mrtOrigin` | MRT API origin URL override |
| `accountManagerHost` | Account Manager hostname for OAuth |
| `sandboxApiHost` | ODS (sandbox) API hostname |
| Field | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `shortCode` | SCAPI short code |
| `clientId` | OAuth client ID (for implicit login discovery) |
| `contentLibrary` | Default content library ID for `content export` and `content list` commands |
| `libraries` | Library IDs for the WebDAV browser and Content Libraries tree. Accepts `string[]` or `[{id, siteLibrary?}]`; elements may be mixed |
| `assetQuery` | JSON dot-paths used to extract static asset URLs during content library parsing (default `["image.path"]`) |
| `mrtProject` | MRT project slug |
| `mrtOrigin` | MRT API origin URL override |
| `accountManagerHost` | Account Manager hostname for OAuth |
| `sandboxApiHost` | ODS (sandbox) API hostname |
| `realm` | Default ODS realm for sandbox operations |

::: warning Security Note
Sensitive fields like `hostname`, `password`, `clientSecret`, `username`, and `mrtApiKey` are intentionally **not** supported in `package.json`. These should be configured via `dw.json` (which should be in `.gitignore`), environment variables, or secure credential stores.
Expand All @@ -337,6 +343,30 @@ Sensitive fields like `hostname`, `password`, `clientSecret`, `username`, and `m
`package.json` has the lowest priority of all configuration sources. Values from `dw.json`, environment variables, or CLI flags will always override `package.json` settings. This makes it ideal for project defaults that can be overridden per-environment.
:::

### Content Libraries Example

The `libraries` field can list the content libraries your project works with so that the VS Code Content Libraries tree auto-loads them and `b2c content list/export` can default `--site-library` based on the entry.

A bare string is treated as a shared library; an object can mark a library as site-private. Both forms can appear in the same array:

```json
{
"b2c": {
"libraries": [
"RefArchSharedLibrary",
{ "id": "SiteGenesis", "siteLibrary": true }
]
}
}
```

With this config:

- `b2c content list --library SiteGenesis` calls the site-library API automatically (no need to pass `--site-library`); the library ID is the site ID.
- `b2c content list --library RefArchSharedLibrary` treats `RefArchSharedLibrary` as a shared library.
- `--site-library` / `--no-site-library` on the command line still wins over the config default.
- The VS Code Content Libraries tree shows both entries on activation, with `SiteGenesis` marked `[site]`.

### Resolution Priority

Configuration is resolved with the following precedence (highest to lowest):
Expand Down
19 changes: 13 additions & 6 deletions packages/b2c-cli/src/commands/content/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import {Args, Flags, ux} from '@oclif/core';
import {JobCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {resolveLibraryEntries} from '@salesforce/b2c-tooling-sdk/config';
import {
LibraryNode,
exportContent,
Expand Down Expand Up @@ -36,15 +37,15 @@
static flags = {
...JobCommand.baseFlags,
library: Flags.string({
description: 'Library ID or site ID (also configurable via dw.json "content-library")',
description: 'Library ID or site ID (also configurable via dw.json "content-library" or "libraries")',
}),
output: Flags.string({
char: 'o',
description: 'Output directory',
}),
'site-library': Flags.boolean({
description: 'Library is a site-private library',
default: false,
description: 'Library is a site-private library (defaults from a matching "libraries" config entry)',
allowNo: true,
}),
'asset-query': Flags.string({
char: 'q',
Expand Down Expand Up @@ -92,7 +93,7 @@
fetchContentLibrary,
};

async run(): Promise<ContentExportResult> {

Check warning on line 96 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test-windows (24.x)

Async method 'run' has a complexity of 21. Maximum allowed is 20

Check warning on line 96 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test-windows (22.x)

Async method 'run' has a complexity of 21. Maximum allowed is 20

Check warning on line 96 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Async method 'run' has a complexity of 21. Maximum allowed is 20

Check warning on line 96 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Async method 'run' has a complexity of 21. Maximum allowed is 20
const {argv, flags} = await this.parse(ContentExport);
const pageIds = argv as string[];
const outputPath =
Expand All @@ -106,11 +107,17 @@
this.error('At least one content ID is required.');
}

const libraryId = flags.library ?? this.resolvedConfig.values.contentLibrary;
const libraryEntries = resolveLibraryEntries(this.resolvedConfig.values.libraries);
const libraryId = flags.library ?? this.resolvedConfig.values.contentLibrary ?? libraryEntries[0]?.id;
if (!libraryId) {
this.error('Library is required. Set via --library flag or "content-library" in dw.json.');
}

const isSiteLibrary =
flags['site-library'] === undefined
? (libraryEntries.find((e) => e.id === libraryId)?.siteLibrary ?? false)
: flags['site-library'];

if (!flags['library-file']) {
this.requireOAuthCredentials();
}
Expand All @@ -121,7 +128,7 @@
if (flags['dry-run']) {
const {library} = await this.operations.fetchContentLibrary(this.instance, libraryId, {
libraryFile: flags['library-file'],
isSiteLibrary: flags['site-library'],
isSiteLibrary,
assetQuery,
keepOrphans: flags['keep-orphans'],
waitOptions,
Expand Down Expand Up @@ -221,7 +228,7 @@
}

const result = await this.operations.exportContent(this.instance, pageIds, libraryId, outputPath, {
isSiteLibrary: flags['site-library'],
isSiteLibrary,
assetQuery,
libraryFile: flags['library-file'],
offline: flags.offline,
Expand Down Expand Up @@ -255,7 +262,7 @@

const pluralS = (n: number) => (n === 1 ? '' : 's');

function formatSummary(

Check warning on line 265 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test-windows (24.x)

Function 'formatSummary' has too many parameters (6). Maximum allowed is 4

Check warning on line 265 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test-windows (22.x)

Function 'formatSummary' has too many parameters (6). Maximum allowed is 4

Check warning on line 265 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Function 'formatSummary' has too many parameters (6). Maximum allowed is 4

Check warning on line 265 in packages/b2c-cli/src/commands/content/export.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Function 'formatSummary' has too many parameters (6). Maximum allowed is 4
prefix: string,
pages: number,
content: number,
Expand Down
17 changes: 12 additions & 5 deletions packages/b2c-cli/src/commands/content/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* 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
*/
import {Flags} from '@oclif/core';

Check warning on line 6 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test-windows (24.x)

'D:\a\b2c-developer-tooling\b2c-developer-tooling\node_modules\@oclif\core\lib\index.d.ts' imported multiple times

Check warning on line 6 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test-windows (22.x)

'D:\a\b2c-developer-tooling\b2c-developer-tooling\node_modules\@oclif\core\lib\index.d.ts' imported multiple times

Check warning on line 6 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

'/home/runner/work/b2c-developer-tooling/b2c-developer-tooling/node_modules/@oclif/core/lib/index.d.ts' imported multiple times

Check warning on line 6 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

'/home/runner/work/b2c-developer-tooling/b2c-developer-tooling/node_modules/@oclif/core/lib/index.d.ts' imported multiple times
import {ux} from '@oclif/core';

Check warning on line 7 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test-windows (24.x)

'D:\a\b2c-developer-tooling\b2c-developer-tooling\node_modules\@oclif\core\lib\index.d.ts' imported multiple times

Check warning on line 7 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test-windows (22.x)

'D:\a\b2c-developer-tooling\b2c-developer-tooling\node_modules\@oclif\core\lib\index.d.ts' imported multiple times

Check warning on line 7 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

'/home/runner/work/b2c-developer-tooling/b2c-developer-tooling/node_modules/@oclif/core/lib/index.d.ts' imported multiple times

Check warning on line 7 in packages/b2c-cli/src/commands/content/list.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

'/home/runner/work/b2c-developer-tooling/b2c-developer-tooling/node_modules/@oclif/core/lib/index.d.ts' imported multiple times
import {
JobCommand,
TableRenderer,
Expand All @@ -12,6 +12,7 @@
selectColumns,
type ColumnDef,
} from '@salesforce/b2c-tooling-sdk/cli';
import {resolveLibraryEntries} from '@salesforce/b2c-tooling-sdk/config';
import {fetchContentLibrary} from '@salesforce/b2c-tooling-sdk/operations/content';

interface ContentListItem {
Expand Down Expand Up @@ -64,11 +65,11 @@
static flags = {
...JobCommand.baseFlags,
library: Flags.string({
description: 'Library ID or site ID (also configurable via dw.json "content-library")',
description: 'Library ID or site ID (also configurable via dw.json "content-library" or "libraries")',
}),
'site-library': Flags.boolean({
description: 'Site-private library',
default: false,
description: 'Site-private library (defaults from a matching "libraries" config entry)',
allowNo: true,
}),
'library-file': Flags.string({
description: 'Local XML file',
Expand Down Expand Up @@ -98,11 +99,17 @@
async run(): Promise<{data: ContentListItem[]}> {
const {flags} = await this.parse(ContentList);

const libraryId = flags.library ?? this.resolvedConfig.values.contentLibrary;
const libraryEntries = resolveLibraryEntries(this.resolvedConfig.values.libraries);
const libraryId = flags.library ?? this.resolvedConfig.values.contentLibrary ?? libraryEntries[0]?.id;
if (!libraryId) {
this.error('Library is required. Set via --library flag or "content-library" in dw.json.');
}

const isSiteLibrary =
flags['site-library'] === undefined
? (libraryEntries.find((e) => e.id === libraryId)?.siteLibrary ?? false)
: flags['site-library'];

if (!flags['library-file']) {
this.requireOAuthCredentials();
}
Expand All @@ -113,7 +120,7 @@

const {library} = await this.operations.fetchContentLibrary(instance, libraryId, {
libraryFile: flags['library-file'],
isSiteLibrary: flags['site-library'],
isSiteLibrary,
waitOptions,
});

Expand Down
68 changes: 68 additions & 0 deletions packages/b2c-cli/test/commands/content/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,72 @@ describe('content list', () => {
expect(libraryId).to.equal('TestLib');
expect(options.isSiteLibrary).to.equal(true);
});

it('defaults --site-library from a matching libraries config entry', async () => {
const command: any = await createCommand({library: 'homepage'});
stubCommon(command);
sinon.stub(command, 'jsonEnabled').returns(true);
Object.defineProperty(command, 'resolvedConfig', {
value: {
values: {
libraries: ['RefArch', {id: 'homepage', siteLibrary: true}],
},
},
configurable: true,
});

const mockLibrary = createMockLibrary();
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});

await command.run();

const [, libraryId, options] = fetchStub.firstCall.args;
expect(libraryId).to.equal('homepage');
expect(options.isSiteLibrary).to.equal(true);
});

it('explicit --no-site-library overrides libraries config default', async () => {
const command: any = await createCommand({library: 'homepage', 'site-library': false});
stubCommon(command);
sinon.stub(command, 'jsonEnabled').returns(true);
Object.defineProperty(command, 'resolvedConfig', {
value: {
values: {
libraries: [{id: 'homepage', siteLibrary: true}],
},
},
configurable: true,
});

const mockLibrary = createMockLibrary();
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});

await command.run();

const options = fetchStub.firstCall.args[2];
expect(options.isSiteLibrary).to.equal(false);
});

it('falls back to first libraries entry when --library and contentLibrary unset', async () => {
const command: any = await createCommand({});
stubCommon(command);
sinon.stub(command, 'jsonEnabled').returns(true);
Object.defineProperty(command, 'resolvedConfig', {
value: {
values: {
libraries: [{id: 'RefArch'}, {id: 'homepage', siteLibrary: true}],
},
},
configurable: true,
});

const mockLibrary = createMockLibrary();
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});

await command.run();

const [, libraryId, options] = fetchStub.firstCall.args;
expect(libraryId).to.equal('RefArch');
expect(options.isSiteLibrary).to.equal(false);
});
});
Loading
Loading