Skip to content

@W-22451824 feat: complete SCAPI migration with OCAPI fallback - #413

Open
clavery wants to merge 29 commits into
mainfrom
feature/scapi-migration
Open

@W-22451824 feat: complete SCAPI migration with OCAPI fallback#413
clavery wants to merge 29 commits into
mainfrom
feature/scapi-migration

Conversation

@clavery

@clavery clavery commented May 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the SCAPI-first migration for jobs, code versions, Business Manager users and roles, sites, site archive operations, CAP operations, catalog discovery, and the related VS Code surfaces. OCAPI remains a temporary compatibility path for release 26.8.

This is intentionally a major SDK and CLI changeset: SCAPI and OCAPI responses retain their native backend-specific shapes. Consumers that need the legacy response shape can explicitly select OCAPI.

Backend behavior

  • auto prefers SCAPI when coordinates and stateless authentication are available, then silently falls back to OCAPI only for safe capability, authentication, or request failures.
  • scapi never crosses into OCAPI. Unsupported capabilities fail before any OCAPI request with an actionable release 26.8 error.
  • ocapi selects the legacy implementation explicitly.
  • Multi-request operations pin their selected backend to avoid mixing response shapes.
  • Client credentials and JWT Bearer are both treated as system authentication.
  • Browser, PKCE, and implicit user authentication use OCAPI in auto; explicit SCAPI reports that SCAPI Admin user authentication is not yet supported.

SCAPI coverage

  • Job run, execution search, wait, logs, history deletion, and system-job triggers.
  • Code-version list, activate, delete, reload, deploy support, and code discovery.
  • Business Manager user and role CRUD plus portable user search.
  • Site enumeration and cartridge-path reads.
  • Site archive import/export and CAP install/uninstall, with WebDAV retained for archive transfer.
  • Catalog discovery and VS Code jobs, code, export, onboarding, and API Browser surfaces.

Mutating system jobs fall back only when SCAPI proves that no job was created. Ambiguous network or server failures propagate instead of risking duplicate execution.

Temporary OCAPI compatibility gaps

The live release 26.8 SCAPI schemas do not currently expose:

  • Inventory-list enumeration.
  • Business Manager current-user lookup (whoami).
  • Business Manager access-key administration.
  • Raw OCAPI user-search JSON.

In auto, these use the temporary OCAPI compatibility path. In explicit SCAPI mode, they fail visibly before OCAPI is contacted and direct the caller to select OCAPI for now.

VS Code compatibility

  • Successful automatic fallback is notification-free and recorded only in logs.
  • Partial export discovery logs unsupported inventory enumeration while continuing to display SCAPI-supported categories.
  • API Browser consistently normalizes configured tenant IDs to organization IDs.
  • JWT-authenticated fallback is supported equivalently to client credentials.
  • The preview Stop Execution command was removed because neither the canonical CLI nor OCAPI supports running-job cancellation.

Additional correctness work

  • Merged current main and aligned SCAPI selection with the recently merged PKCE authentication work.
  • Invalidated rejected cascade tokens before retrying Account Manager.
  • Added full sites pagination with bounded-concurrency enrichment.
  • Wired SFCC_API_BACKEND consistently for SDK consumers.
  • Updated documentation and agent skills to describe SCAPI-first behavior and the release 26.8 limitations.

Validation

  • Workspace lint
  • Workspace typecheck
  • SDK build
  • SDK tests: 2,220 passing, 7 pending
  • CLI tests: 1,454 passing
  • Focused fallback, explicit-SCAPI guard, API Browser, and VS Code command tests
  • Changed-file formatting and git diff --check
  • Manual end-to-end verification against instances with and without provisioned SCAPI scopes

clavery added 22 commits May 8, 2026 12:22
Introduces a JobsBackend interface so job commands can transparently
use either OCAPI or SCAPI. Auto mode prefers SCAPI when shortCode and
tenantId are configured, falling back to OCAPI on invalid_scope errors.

- New SCAPI Jobs client (operation/jobs/v1) with optimistic sfcc.jobs.rw
  scope and read-only downgrade for read operations
- Canonical JobExecutionResult type bridges OCAPI snake_case and SCAPI
  camelCase response shapes
- --api-backend flag and apiBackend dw.json field for explicit control
- New job execution delete command (SCAPI only)
- job:run, job:search, job:wait, job:log migrated to backend abstraction
- job:import and job:export remain OCAPI-only for now
# Conflicts:
#	packages/b2c-cli/src/commands/job/run.ts
#	packages/b2c-cli/src/commands/job/search.ts
#	packages/b2c-tooling-sdk/src/config/dw-json.ts
#	packages/b2c-tooling-sdk/src/config/mapping.ts
Pulls three domain-agnostic utilities out of jobs into shared modules
ahead of applying the same pattern to scripts, users, and roles:

- isInvalidScopeError, ApiBackendPreference, resolveScapiOrOcapi —
  scope-error detection and preference resolution
- ScapiFallbackBackend<T> — generic fallback wrapper that tries SCAPI
  first and falls back to OCAPI on invalid_scope
- ScopeTierManager<C> — manages dual rw/read-only client tiers with
  optimistic rw + downgrade on scope error

Refactors ScapiJobsBackend and FallbackJobsBackend to use the new
utilities without behavior change. Removes the unused
downgradeToReadOnly() method and confusing tier-resolution logic.
Migrates code list/activate/delete commands to use the dual-backend
pattern. New CodeCommand base class exposes createScriptsBackend(),
which selects between OCAPI and SCAPI based on --api-backend.

- New SCAPI Scripts client (dx/scripts/v1) reusing the shared
  ScopeTierManager and ScapiFallbackBackend utilities
- ScriptsBackend interface with canonical CodeVersionInfo shape
  (camelCase, _raw escape hatch)
- reloadCodeVersion remains OCAPI-only — SCAPI backend throws,
  auto mode falls back to OCAPI on the first reload call
Migrates bm users list/get/update/delete commands to the dual-backend
pattern. New BmCommand base class exposes createUsersBackend(),
which selects between OCAPI and SCAPI based on --api-backend.

- New SCAPI Merchant Users client (merchant/users/v1)
- UsersBackend interface with canonical UserInfo (camelCase)
- bm users search, bm whoami, bm access-key * stay OCAPI-only —
  no SCAPI equivalents
- SCAPI updateUser does not support `disabled`; the SCAPI backend
  throws when --disabled is passed, prompting the user to use OCAPI
Migrates bm roles list/get/create/delete/grant/revoke and
bm roles permissions get/set commands to the dual-backend pattern.
BmCommand now exposes createRolesBackend() alongside createUsersBackend().

- New SCAPI Merchant Roles client (merchant/roles/v1)
- RolesBackend with canonical RoleInfo and RolePermissionsInfo
  (camelCase; OCAPI mapping converts snake_case fields like
  locale_id → localeId)
- Permissions display updated to use canonical camelCase fields
- Bm roles get --expand users continues to work via the _raw
  escape hatch (handles both OCAPI snake_case and SCAPI camelCase
  user fields)
- Configuration guide: clarify api-backend applies to job, code,
  bm users, and bm roles commands
- code.md: new "API Backend" section with SCAPI scopes, fallback
  behavior, and notes on reload/deploy/download/watch staying OCAPI/WebDAV
- bm.md: new "API Backend" section with per-command compatibility
  table (users search, whoami, access-key remain OCAPI-only)
- b2c-code skill: backend selection examples
- b2c-bm-users-roles skill: backend selection notes including the
  --disabled fallback caveat
- Single changeset replaces the jobs-only one
Three correctness fixes plus four DRY extractions across the SCAPI
migration. Tests stay green (1722 SDK + 1219 CLI) and the API surface
is unchanged for consumers.

Bugs fixed:

- code activate --reload now works in auto mode. The reload toggle
  (list + activate(alt) + activate(target)) is implementable on any
  backend, so reloadCodeVersion is a backend-agnostic free function
  that takes a ScriptsBackend. The OCAPI-only stub in
  ScapiScriptsBackend that previously broke fallback is gone.

- job run --body is no longer subject to a special-case backend switch.
  SCAPI accepts raw bodies for system jobs (just with a slightly
  different payload shape) so we pass --body through to whichever
  backend the user picked. Removes a no-op resolveBackend helper that
  called createJobsBackend twice.

- Scope merging now works for any AuthStrategy. The instanceof
  OAuthStrategy check silently dropped scopes for ImplicitOAuthStrategy
  and StatefulOAuthStrategy. AuthStrategy gains an optional
  withAdditionalScopes; a new withScopes() helper centralizes the
  method-presence check across all SCAPI client factories.

DRY extractions:

- Fallback*Backend subclasses (jobs, scripts, users, roles) replaced
  with a single Proxy-based createFallbackBackend<T>(). Each domain
  shed ~25 lines of mechanical method delegation. The proxy traps
  reads of `name` and routes method calls through the same
  withFallback logic.

- create*Backend factory functions (jobs, scripts, users, roles)
  collapsed into createDualBackend<T>() that takes constructors. Each
  domain backend.ts shrunk from ~50 lines to ~10.

- createScapi*Client factories collapsed into buildScapiClient<P>()
  that takes a path segment, scope set, and middleware key. Each
  domain client.ts shrunk from ~60 lines to ~30.

- InstanceCommand gained createBackend<T>() so per-domain command
  base classes (JobCommand/CodeCommand/BmCommand) shrink to one-line
  wrappers.

Other:

- Renamed JobExecutionResult to JobExecutionInfo for consistency with
  CodeVersionInfo, UserInfo, RoleInfo across the canonical types.
Adds 8 unit tests for createFallbackBackend covering:
- happy path (SCAPI works, choice is cached)
- fallback path (invalid_scope triggers OCAPI, choice is cached)
- name reflects the resolved backend
- non-fallback errors are rethrown without falling back
- multi-arg method dispatch
- non-method property access (documented as SCAPI-target-only)

Tightens the JSDoc on createFallbackBackend to spell out the contract
explicitly: both backends must implement T (TypeScript enforces this
at the call site), only methods are routed through fallback, non-method
properties stay on the SCAPI target, and concurrent first-calls are
benign since they only retry SCAPI redundantly.
The hostile review surfaced two real type-safety gaps in the dual-backend
pattern. Both are fixed by making interface-level capability explicit
rather than relying on runtime throws.

1. RoleInfo.permissions silently dropped after fallback. The OCAPI role
   mapper omitted permissions; SCAPI included them. Both satisfied the
   optional `permissions?` field, so TypeScript and the Proxy were happy
   while data quietly disappeared on the fallback path. Fix: map OCAPI's
   permissions through the existing snake_case → camelCase converter so
   getRole returns the same shape from both backends.

2. JobsBackend.deleteJobExecution was a runtime-throwing stub on OCAPI.
   Auto-mode behavior was unstable: it worked on a SCAPI-resolved
   backend, threw on an OCAPI-resolved one. Fix: split capability into
   DeletableJobsBackend (extends JobsBackend), which only ScapiJobsBackend
   implements. A supportsDeleteJobExecution() type guard lets callers
   narrow before calling. The Fallback Proxy detects SCAPI-only methods
   (those missing on OCAPI) and routes them directly to SCAPI without
   attempting fallback — invalid_scope errors propagate to the caller
   instead of trying an OCAPI that can't handle the operation.

The job execution delete command now uses the type guard and gives a
clear error message when the active backend can't delete.

Adds 2 fallback-backend tests covering SCAPI-only method dispatch.
1732 SDK + 1219 CLI tests passing.
Replaces the layered backend abstraction (interface + adapter classes +
Proxy fallback wrapper + ScopeTierManager) for the jobs domain with a
single CLI-side dispatcher and free-function SCAPI ops. The dispatcher's
sole purpose is caching the resolved backend across multi-call
operations in apiBackend=auto mode, so a polling command (job run --wait)
doesn't re-probe SCAPI on every iteration when the user has no SCAPI
scopes provisioned.

Auth changes:
- AuthStrategy gains optional getAccessTokenForCascade(candidates).
  OAuthStrategy and JwtOAuthStrategy walk candidates in order; first
  that AM accepts wins, cached per requested scope set.
- findCachedTokenSatisfying scans the cache for a non-expired token
  whose scopes are a superset of a required set, so a previously
  granted broader-scope token can serve a narrower request without
  another AM round trip.
- 5 new unit tests exercise cascade resolution, cache reuse, and error
  paths.

Client/middleware changes:
- buildScapiClient gains scopeCascade option and a new
  createScapiAuthMiddleware that reads the per-request
  x-b2c-scope-mode header and asks the strategy to resolve the chosen
  cascade tier (read or write). Legacy defaultScopes still works for
  scripts/users/roles until they migrate.
- SCAPI Jobs declares its cascade once at client construction:
  read = [['sfcc.jobs.rw'], ['sfcc.jobs']], write = [['sfcc.jobs.rw']].

Jobs domain:
- ScapiJobsBackend / OcapiJobsBackend / FallbackJobsBackend / DeletableJobsBackend
  all deleted. ScopeTierManager dependency removed from jobs.
- New scapi-ops.ts exports free functions (scapiExecuteJob, scapiGetJobExecution,
  scapiSearchJobExecutions, scapiDeleteJobExecution, scapiGetJobLog) that
  take a ScapiJobsClient and declare scope mode via headers.
- mapOcapiExecution / mapOcapiSearchResult exposed as transitional helpers
  for the OCAPI dispatcher branches.
- waitForJobExecution rewritten to take a getter callback over the
  canonical JobExecutionInfo shape.
- New CanonicalJobExecutionError carries JobExecutionInfo (was raw OCAPI),
  fixing a regression where SCAPI --wait failures reported 'ERROR'
  instead of the real exit code.

CLI:
- BackendDispatcher moved to src/compat/dispatcher.ts behind a new
  ./compat package export. Re-exported from ./cli for ergonomics.
  runScapiOnly removed; SCAPI-only commands branch on apiBackendPreference
  + buildScapiJobsClient directly.
- JobCommand exposes createJobsDispatcher, buildScapiJobsClient, and
  showJobLog (canonical-first, raw-OCAPI accepted for legacy callers).
- All five job commands rewritten to dispatcher.run({scapi, ocapi})
  with the scapi branch receiving a typed ScapiJobsClient.

Behavioral guarantee: no CLI-visible changes. OCAPI-only setups, explicit
--api-backend ocapi/scapi, and apiBackend=auto with full SCAPI scopes
all behave identically to the prior implementation. The auto + missing
SCAPI scopes path now caches the OCAPI choice across polls instead of
re-probing AM on every call.

13 dispatcher unit tests + 5 cascade unit tests + updated CLI command
tests. 1746 SDK + 1220 CLI tests passing.
# Conflicts:
#	packages/b2c-cli/test/commands/job/run.test.ts
#	packages/b2c-tooling-sdk/package.json
reloadCodeVersion now takes a ScriptsBackend (was B2CInstance). Wrap the
extension's instance in OcapiScriptsBackend at the two call sites so the
extension keeps its OCAPI-only behavior while satisfying the new type.

Mirrors how b2c-cli/src/commands/code/deploy.ts:207 already handles this.
# Conflicts:
#	packages/b2c-tooling-sdk/src/cli/job-command.ts
#	packages/b2c-tooling-sdk/src/operations/jobs/index.ts
#	packages/b2c-vs-extension/src/code-sync/deploy-command.ts
#	packages/b2c-vs-extension/src/code-sync/index.ts
OCAPI is deprecated and disabled on newer instances; the CLI previously
surfaced its 403 OcapiDeprecatedException as an opaque 'Failed to ...'
error with no guidance. Add a central detector + actionable error in
error-utils (isOcapiDeprecatedFault, OcapiDeprecatedError,
throwOcapiError) and route every OCAPI-terminal site through it (code
versions, jobs run/site-archive, cap install/uninstall/list, scaffold,
sites list).

Also fixes a credential leak: B2C embeds the bearer JWT in some auth
faults (InvalidAccessTokenException); getApiErrorMessage now redacts
tokens from every user-facing message.

Rewrite code/job/bm docs and agent skills SCAPI-first, presenting OCAPI
as the deprecated fallback rather than the assumed baseline.

Also fixes a pre-existing prettier error in b2c-dx-mcp diagnostics.
… redaction

Remove redactTokens: the leaked token is our own, echoed by the remote
OCAPI server only on the deprecated path, so scrubbing it isn't worth
the machinery. getApiErrorMessage is back to a pure extractor.

The OCAPI-deprecation message now names the exact SCAPI scope the failed
operation needs (e.g. 'sfcc.scripts' or 'sfcc.scripts.rw'), via an
optional requiredScopes arg threaded through throwOcapiError /
OcapiDeprecatedError. Scope constants are sourced from each domain's
canonical definitions (derived from the jobs cascade where applicable)
so they can't drift. Operations with no SCAPI equivalent (sites,
bm whoami/search/access-key, cap) keep the generic 'sfcc.* scopes'
wording. Routed bm users/roles and the cartridge-path read through the
shared helper so the dual-backend fallbacks surface the guidance too.
Migrate `sites list` and `sites cartridges list` (reads) to a dual backend
that prefers the SCAPI site/sites API when shortCode/tenantId/sfcc.sites scopes
are configured, falling back to the deprecated OCAPI Data API otherwise.

- New SCAPI sites client (site/sites/v1) with read/write scope cascade
- ScapiSitesBackend enriches the id-only list response via per-site getSite
- OcapiSitesBackend preserves the legacy /sites path and surfaces an
  OcapiDeprecatedError naming the sfcc.sites scopes on deprecated instances
- Cartridge-path writes have no SCAPI equivalent and stay on OCAPI / archive
- Docs and the b2c-sites skill are now SCAPI-first
B2CInstance now encodes the SCAPI connection coordinates and backend
preference, so SCAPI operations need nothing beyond a configured instance.
This is the forward-looking seam for the OCAPI -> SCAPI transition: when
OCAPI is eventually removed, the OCAPI accessors disappear and the SCAPI
plumbing stands on its own.

- InstanceConfig gains shortCode/tenantId/apiBackend; populated in
  createInstanceFromConfig from resolved config.
- AuthConfig.oauth gains jwtCertPath/jwtKeyPath/jwtPassphrase so the instance
  can build a JWT Bearer strategy for SCAPI, not just client-credentials.
- New B2CInstance.scapiClientConfig getter returns {shortCode, tenantId, auth}
  or undefined, encapsulating the "only stateless scope-flexible OAuth
  qualifies for auto-SCAPI" eligibility rule in one place. New apiBackend
  getter exposes the preference.
- createDualBackend / DualBackendConfig drop the threaded
  shortCode/tenantId/auth fields and source them from instance.scapiClientConfig;
  preference defaults to instance.apiBackend.
- InstanceCommand.createBackend + hasScapiConfig, JobCommand.buildScapiJobsClient,
  the sites CLI commands, and the VS Code scripts-backend builder all simplify
  to pass just the instance. This also fixes a latent issue where the CLI auth
  path could hand a fixed-scope stateful-session token to the SCAPI leg.
- Adds B2CInstance.scapiClientConfig unit coverage.
…fallback

Site archive import/export and CAP install/uninstall were OCAPI-only because
they POSTed directly to /jobs/{id}/executions. With B2CInstance now carrying
SCAPI config, route them through a shared dual-backend runner.

- New operations/jobs/run-system-job.ts: runSystemJob(instance, spec) starts a
  named system job, waits, and returns the raw OCAPI JobExecution. In auto mode
  it tries SCAPI (instance.scapiClientConfig) and falls back to OCAPI only if
  the *start* is rejected — once a job has started it never falls back, so a
  write is never re-run. Honors apiBackend ocapi/scapi/auto.
- The SCAPI JobExecutionRequest accepts the same {parameters:[{name,value}]}
  shape the OCAPI internal-user retry already used, so each operation declares
  one parameters array reused by both backends.
- New mapCanonicalToOcapiExecution reverse mapper keeps the public result/error
  contract identical across backends: every consumer reads raw snake_case off
  result.execution, and a SCAPI job failure is re-thrown as the raw
  JobExecutionError so existing log-fetch handling works unchanged.
- site-archive import/export and cap install/uninstall rewired to runSystemJob;
  log-fetch-on-failure factored into a shared helper. OCAPI behavior (shorthand
  body + UnknownPropertyException params retry) preserved exactly.
- Adds runSystemJob tests (SCAPI path, raw-shape contract, no-fallback-after-
  start, auto fallback, OCAPI default, params retry, deprecation, explicit
  preference). Existing site-archive + CAP tests pass unchanged.

Not yet live-verified end-to-end (deferred): auto mode keeps working via OCAPI
fallback if SCAPI rejects a system-job start.
Five confirmed findings from an adversarial review of the migration work,
verified against the code before fixing.

F1 (data integrity): runSystemJob no longer re-runs a mutating system job over
OCAPI after an ambiguous SCAPI failure. executeJob now throws a typed
ScapiJobStartError carrying the HTTP status; auto-mode fallback is gated on
isSafeStartFallback — invalid_scope, ScapiCapabilityUnsupportedError, or a
client-side rejection status (400/401/403/404/405/406/415). Network/timeout and
5xx/429 propagate without a re-run (job may have started).

F3 (auth): OAuthStrategy/JwtOAuthStrategy invalidateToken() now clears ALL
cached tokens for the client/method/AM-host identity, not just the base-scope
key. A cascade 401 retry previously reused the rejected token cached under the
merged-scope key; it now re-requests from AM.

F2 (correctness): ScapiSitesBackend.listSites paginates server-side (limit 50,
offset) honoring start/count, instead of reading only the first 25-item page;
per-site enrichment now uses bounded concurrency (5) and skips already-rich
items rather than an unbounded Promise.all.

F4 (clarity): explicit --api-backend scapi with implicit/stateful auth now
fails with a message naming the real requirement (a stateless client-credentials
or JWT flow), via shared scapiUnavailableMessage; scapiClientConfig JSDoc and
the changeset no longer imply explicit opt-in works for those flows.

F5 (docs/config): SFCC_API_BACKEND now maps in EnvSource (with value
validation) so the VS Code extension honors it; corrected stale OCAPI-only
claims in jobs.md, code.md (--reload is backend-agnostic), auth.md (Sites reads
use SCAPI), the b2c-job/b2c-code skills, and the authentication guide.

Removes now-dead invalidateCachedOAuthToken. Adds tests for all fixes.
@clavery clavery changed the title feat: SCAPI migration for jobs, code, and bm users/roles commands feat: SCAPI migration for jobs, code, bm, sites, site-archive, and CAP Jul 15, 2026
clavery added 5 commits July 28, 2026 10:42
# Conflicts:
#	packages/b2c-cli/src/commands/code/activate.ts
#	packages/b2c-cli/src/commands/code/deploy.ts
#	packages/b2c-tooling-sdk/package.json
#	packages/b2c-tooling-sdk/src/operations/code/deploy.ts
#	packages/b2c-tooling-sdk/src/operations/code/versions.ts
#	packages/b2c-tooling-sdk/src/operations/jobs/run.ts
#	packages/b2c-vs-extension/src/code-sync/cartridge-commands.ts
#	packages/b2c-vs-extension/src/code-sync/deploy-command.ts
…backends

An OCAPI-coverage audit found several discovery/auto-detect call sites still
hitting raw OCAPI even though a SCAPI backend already existed. Wire them
through the dual (SCAPI-with-OCAPI-fallback) backends so 'cover all use cases'
holds beyond the original command scope:

- operations/cap/list.ts, scaffold/sources.ts, operations/jobs/discover.ts:
  site discovery now uses createSitesBackend({instance}).listSites() instead of
  a raw instance.ocapi.GET('/sites'). In jobs/discover, only the sites category
  moves to SCAPI — catalogs and inventory-lists have no SCAPI list endpoint and
  stay on OCAPI.
- VS Code cartridge-path site picker (pickSite) uses createSitesBackend too.
- code download active-version auto-discovery goes through createScriptsBackend
  so SCAPI-only instances can discover the active version without OCAPI.

Also fixes a pre-existing gap surfaced by this work: OcapiSitesBackend.listSites
did not paginate, so an unbounded listing silently truncated at the OCAPI page
size. It now pages through the full collection (honoring an explicit
start/count as a single page), matching the SCAPI backend.

Genuinely OCAPI-only operations are left as-is (no SCAPI equivalent): BM user
search / whoami / access-key, cartridge-path writes, catalog/inventory-list
discovery, and aborting a running job execution.
Expose BM user creation as a full CLI command. The SDK backends already
implemented createOrReplaceUser (PUT /users/{login}) on both SCAPI and OCAPI,
but nothing surfaced it — bm users only had list/get/search/update/delete.

- New bm users create command (create-or-replace) wrapping
  backend.createOrReplaceUser via the dual SCAPI/OCAPI backend, consistent with
  the other bm users commands. Flags: login arg + --email (required),
  --first-name/--last-name/--external-id/--password/--role (repeatable)/
  --disabled/--preferred-ui-locale/--preferred-data-locale. JSON output.
- Docs + skill reframed: the LocalUserCreationException note previously implied
  "these commands do not create users"; it now documents that create IS
  available but is rejected with LocalUserCreationException on SSO/AM-only
  instances (local user creation must be enabled on the instance). Updated
  docs/cli/bm.md (command table, Users section, per-command reference),
  the b2c-bm-users-roles skill, and the SDK module JSDoc (bm-users index +
  users.ts).
- Adds create.test.ts (field passthrough, optional-field omission, --disabled,
  LocalUserCreationException surfacing).
# Conflicts:
#	packages/b2c-tooling-sdk/src/auth/types.ts
#	packages/b2c-vs-extension/src/code-sync/code-sync-manager.ts
#	packages/b2c-vs-extension/src/code-sync/deploy-command.ts
@clavery
clavery marked this pull request as ready for review August 7, 2026 14:26
@clavery
clavery requested a review from wei-liu-sf as a code owner August 7, 2026 14:26
@clavery
clavery temporarily deployed to b2c-docs-preview August 7, 2026 14:26 — with GitHub Actions Inactive
@clavery clavery changed the title feat: SCAPI migration for jobs, code, bm, sites, site-archive, and CAP @W-22451824 feat: SCAPI migration for jobs, code, bm, sites, site-archive, and CAP Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📘 Docs preview

Your documentation changes are published at:

https://d3uhw92m2zac57.cloudfront.net/pr-413/

Commit 2feab01 · ✅ Published

This preview updates on every push and is removed when the PR is closed.

@clavery
clavery deployed to b2c-docs-preview August 11, 2026 15:08 — with GitHub Actions Active
@clavery
clavery deployed to b2c-docs-preview August 11, 2026 19:21 — with GitHub Actions Active
@clavery clavery changed the title @W-22451824 feat: SCAPI migration for jobs, code, bm, sites, site-archive, and CAP @W-22451824 feat: complete SCAPI migration with OCAPI fallback Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant