Skip to content

Commit e8d7fb9

Browse files
committed
site library support in libraries config
Extends libraries to accept {id, siteLibrary?} entries (mixed with strings). b2c content list/export default --site-library from a matching entry; the flag is now allowNo with no default so explicit values still win. The VS Code Content Libraries tree auto-seeds every entry on activation. Adds libraries, assetQuery, and realm rows to the documented package.json / dw.json field tables.
1 parent 7b3524f commit e8d7fb9

16 files changed

Lines changed: 283 additions & 37 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@salesforce/b2c-cli': minor
3+
'@salesforce/b2c-tooling-sdk': minor
4+
'b2c-vs-extension': minor
5+
---
6+
7+
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": ["RefArch"]` with `"libraries": ["RefArch", {"id": "homepage", "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).

docs/cli/content.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ In addition to [global flags](./index#global-flags):
5353
|------|-------------|---------|
5454
| `--library` | Library ID or site ID. Also configurable via `content-library` in dw.json. | |
5555
| `--output`, `-o` | Output directory | `content-<timestamp>` |
56-
| `--site-library` | Treat the library as a site-private library | `false` |
56+
| `--site-library` / `--no-site-library` | Treat the library as a site-private library. Defaults from a matching `libraries` config entry, otherwise `false` | from config |
5757
| `--asset-query`, `-q` | JSON dot-paths for static asset extraction (can be repeated) | `image.path` |
5858
| `--regex`, `-r` | Treat page IDs as regular expressions | `false` |
5959
| `--folder` | Filter by folder classification (can be repeated) | |
@@ -116,7 +116,7 @@ With `--json`, returns a structured result including the library tree, output pa
116116

117117
### Notes
118118

119-
- 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
119+
- 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
120120
- Use `b2c content list` to discover available page IDs before exporting
121121
- 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
122122
- 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
@@ -141,7 +141,7 @@ In addition to [global flags](./index#global-flags):
141141
| Flag | Description | Default |
142142
|------|-------------|---------|
143143
| `--library` | Library ID or site ID. Also configurable via `content-library` in dw.json. | |
144-
| `--site-library` | Treat the library as a site-private library | `false` |
144+
| `--site-library` / `--no-site-library` | Treat the library as a site-private library. Defaults from a matching `libraries` config entry, otherwise `false` | from config |
145145
| `--library-file` | Use a local library XML file instead of fetching from instance | |
146146
| `--type` | Filter by node type: `page`, `content`, or `component` | |
147147
| `--components` | Include components in table output | `false` |

docs/guide/configuration.md

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

320323
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`):
321324

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

332338
::: warning Security Note
333339
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.
@@ -337,6 +343,30 @@ Sensitive fields like `hostname`, `password`, `clientSecret`, `username`, and `m
337343
`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.
338344
:::
339345

346+
### Content Libraries Example
347+
348+
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.
349+
350+
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:
351+
352+
```json
353+
{
354+
"b2c": {
355+
"libraries": [
356+
"RefArch",
357+
{ "id": "homepage", "siteLibrary": true }
358+
]
359+
}
360+
}
361+
```
362+
363+
With this config:
364+
365+
- `b2c content list --library homepage` calls the site-library API automatically (no need to pass `--site-library`).
366+
- `b2c content list --library RefArch` treats `RefArch` as a shared library.
367+
- `--site-library` / `--no-site-library` on the command line still wins over the config default.
368+
- The VS Code Content Libraries tree shows both entries on activation, with `homepage` marked `[site]`.
369+
340370
### Resolution Priority
341371

342372
Configuration is resolved with the following precedence (highest to lowest):

packages/b2c-cli/src/commands/content/export.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import {Args, Flags, ux} from '@oclif/core';
77
import {JobCommand} from '@salesforce/b2c-tooling-sdk/cli';
8+
import {resolveLibraryEntries} from '@salesforce/b2c-tooling-sdk/config';
89
import {
910
LibraryNode,
1011
exportContent,
@@ -36,15 +37,15 @@ export default class ContentExport extends JobCommand<typeof ContentExport> {
3637
static flags = {
3738
...JobCommand.baseFlags,
3839
library: Flags.string({
39-
description: 'Library ID or site ID (also configurable via dw.json "content-library")',
40+
description: 'Library ID or site ID (also configurable via dw.json "content-library" or "libraries")',
4041
}),
4142
output: Flags.string({
4243
char: 'o',
4344
description: 'Output directory',
4445
}),
4546
'site-library': Flags.boolean({
46-
description: 'Library is a site-private library',
47-
default: false,
47+
description: 'Library is a site-private library (defaults from a matching "libraries" config entry)',
48+
allowNo: true,
4849
}),
4950
'asset-query': Flags.string({
5051
char: 'q',
@@ -106,11 +107,17 @@ export default class ContentExport extends JobCommand<typeof ContentExport> {
106107
this.error('At least one content ID is required.');
107108
}
108109

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

116+
const isSiteLibrary =
117+
flags['site-library'] === undefined
118+
? (libraryEntries.find((e) => e.id === libraryId)?.siteLibrary ?? false)
119+
: flags['site-library'];
120+
114121
if (!flags['library-file']) {
115122
this.requireOAuthCredentials();
116123
}
@@ -121,7 +128,7 @@ export default class ContentExport extends JobCommand<typeof ContentExport> {
121128
if (flags['dry-run']) {
122129
const {library} = await this.operations.fetchContentLibrary(this.instance, libraryId, {
123130
libraryFile: flags['library-file'],
124-
isSiteLibrary: flags['site-library'],
131+
isSiteLibrary,
125132
assetQuery,
126133
keepOrphans: flags['keep-orphans'],
127134
waitOptions,
@@ -221,7 +228,7 @@ export default class ContentExport extends JobCommand<typeof ContentExport> {
221228
}
222229

223230
const result = await this.operations.exportContent(this.instance, pageIds, libraryId, outputPath, {
224-
isSiteLibrary: flags['site-library'],
231+
isSiteLibrary,
225232
assetQuery,
226233
libraryFile: flags['library-file'],
227234
offline: flags.offline,

packages/b2c-cli/src/commands/content/list.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
selectColumns,
1313
type ColumnDef,
1414
} from '@salesforce/b2c-tooling-sdk/cli';
15+
import {resolveLibraryEntries} from '@salesforce/b2c-tooling-sdk/config';
1516
import {fetchContentLibrary} from '@salesforce/b2c-tooling-sdk/operations/content';
1617

1718
interface ContentListItem {
@@ -64,11 +65,11 @@ export default class ContentList extends JobCommand<typeof ContentList> {
6465
static flags = {
6566
...JobCommand.baseFlags,
6667
library: Flags.string({
67-
description: 'Library ID or site ID (also configurable via dw.json "content-library")',
68+
description: 'Library ID or site ID (also configurable via dw.json "content-library" or "libraries")',
6869
}),
6970
'site-library': Flags.boolean({
70-
description: 'Site-private library',
71-
default: false,
71+
description: 'Site-private library (defaults from a matching "libraries" config entry)',
72+
allowNo: true,
7273
}),
7374
'library-file': Flags.string({
7475
description: 'Local XML file',
@@ -98,11 +99,17 @@ export default class ContentList extends JobCommand<typeof ContentList> {
9899
async run(): Promise<{data: ContentListItem[]}> {
99100
const {flags} = await this.parse(ContentList);
100101

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

108+
const isSiteLibrary =
109+
flags['site-library'] === undefined
110+
? (libraryEntries.find((e) => e.id === libraryId)?.siteLibrary ?? false)
111+
: flags['site-library'];
112+
106113
if (!flags['library-file']) {
107114
this.requireOAuthCredentials();
108115
}
@@ -113,7 +120,7 @@ export default class ContentList extends JobCommand<typeof ContentList> {
113120

114121
const {library} = await this.operations.fetchContentLibrary(instance, libraryId, {
115122
libraryFile: flags['library-file'],
116-
isSiteLibrary: flags['site-library'],
123+
isSiteLibrary,
117124
waitOptions,
118125
});
119126

packages/b2c-cli/test/commands/content/list.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,72 @@ describe('content list', () => {
154154
expect(libraryId).to.equal('TestLib');
155155
expect(options.isSiteLibrary).to.equal(true);
156156
});
157+
158+
it('defaults --site-library from a matching libraries config entry', async () => {
159+
const command: any = await createCommand({library: 'homepage'});
160+
stubCommon(command);
161+
sinon.stub(command, 'jsonEnabled').returns(true);
162+
Object.defineProperty(command, 'resolvedConfig', {
163+
value: {
164+
values: {
165+
libraries: ['RefArch', {id: 'homepage', siteLibrary: true}],
166+
},
167+
},
168+
configurable: true,
169+
});
170+
171+
const mockLibrary = createMockLibrary();
172+
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});
173+
174+
await command.run();
175+
176+
const [, libraryId, options] = fetchStub.firstCall.args;
177+
expect(libraryId).to.equal('homepage');
178+
expect(options.isSiteLibrary).to.equal(true);
179+
});
180+
181+
it('explicit --no-site-library overrides libraries config default', async () => {
182+
const command: any = await createCommand({library: 'homepage', 'site-library': false});
183+
stubCommon(command);
184+
sinon.stub(command, 'jsonEnabled').returns(true);
185+
Object.defineProperty(command, 'resolvedConfig', {
186+
value: {
187+
values: {
188+
libraries: [{id: 'homepage', siteLibrary: true}],
189+
},
190+
},
191+
configurable: true,
192+
});
193+
194+
const mockLibrary = createMockLibrary();
195+
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});
196+
197+
await command.run();
198+
199+
const options = fetchStub.firstCall.args[2];
200+
expect(options.isSiteLibrary).to.equal(false);
201+
});
202+
203+
it('falls back to first libraries entry when --library and contentLibrary unset', async () => {
204+
const command: any = await createCommand({});
205+
stubCommon(command);
206+
sinon.stub(command, 'jsonEnabled').returns(true);
207+
Object.defineProperty(command, 'resolvedConfig', {
208+
value: {
209+
values: {
210+
libraries: [{id: 'RefArch'}, {id: 'homepage', siteLibrary: true}],
211+
},
212+
},
213+
configurable: true,
214+
});
215+
216+
const mockLibrary = createMockLibrary();
217+
const fetchStub = sinon.stub(command.operations, 'fetchContentLibrary').resolves({library: mockLibrary});
218+
219+
await command.run();
220+
221+
const [, libraryId, options] = fetchStub.firstCall.args;
222+
expect(libraryId).to.equal('RefArch');
223+
expect(options.isSiteLibrary).to.equal(false);
224+
});
157225
});

packages/b2c-tooling-sdk/src/config/dw-json.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as fsp from 'node:fs/promises';
1515
import * as path from 'node:path';
1616
import type {AuthMethod} from '../auth/types.js';
1717
import {getLogger} from '../logging/logger.js';
18+
import type {LibraryEntry} from './types.js';
1819
import {normalizeConfigKeys} from './mapping.js';
1920

2021
/**
@@ -83,8 +84,14 @@ export interface DwJsonConfig {
8384
contentLibrary?: string;
8485
/** Catalog IDs for WebDAV browsing */
8586
catalogs?: string[];
86-
/** Library IDs for WebDAV browsing */
87-
libraries?: string[];
87+
/**
88+
* Library IDs for WebDAV browsing and the Content Libraries tree.
89+
*
90+
* Accepts either a string array or a mixed array of strings and
91+
* `{id, siteLibrary?}` objects. Object entries can mark individual
92+
* libraries as site-private.
93+
*/
94+
libraries?: (string | LibraryEntry)[];
8895
/** JSON dot-paths for asset extraction during content library parsing (defaults to ['image.path']) */
8996
assetQuery?: string[];
9097
/** Optional CIP analytics host override */

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export {resolveConfig, ConfigResolver, createConfigResolver} from './resolver.js
102102
export type {
103103
MaybePromise,
104104
NormalizedConfig,
105+
LibraryEntry,
105106
ConfigSource,
106107
ConfigLoadResult,
107108
ConfigSourceInfo,
@@ -116,7 +117,7 @@ export type {
116117
} from './types.js';
117118

118119
// Instance creation utility (public API for CLI commands)
119-
export {createInstanceFromConfig, normalizeConfigKeys} from './mapping.js';
120+
export {createInstanceFromConfig, normalizeConfigKeys, resolveLibraryEntries} from './mapping.js';
120121

121122
// Low-level dw.json API (still available for advanced use)
122123
export {

0 commit comments

Comments
 (0)