# CI/CD with GitHub Actions

The B2C Developer Tooling project provides official GitHub Actions for automating B2C Commerce operations in your CI/CD pipelines.

## Overview

The official actions handle CLI installation, credential configuration, and Node.js setup automatically — so your workflow files stay focused on *what* you want to deploy rather than *how* to configure the tooling. High-level actions provide typed inputs for common operations like code deployment and data import, while a raw command passthrough covers everything else.

The actions are available from the `SalesforceCommerceCloud/b2c-developer-tooling` repository and support:

- **Code deployment** — deploy and activate cartridges
- **Data import** — import site archives in a single step
- **MRT deployment** — push and deploy MRT storefront bundles
- **Job execution** — run B2C jobs with wait and timeout
- **WebDAV uploads** — upload files for data import, content, etc.
- **Any CLI command** — raw passthrough for operations not covered by high-level actions

All actions are composite YAML — no compiled JavaScript, fully transparent and auditable. They cache the npm install across runs and expose structured JSON outputs for downstream workflow steps.

## Authentication

Store credentials as GitHub [repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) and non-sensitive configuration as [repository variables](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables).

**Recommended secrets:**

| Secret | Description |
|--------|-------------|
| `SFCC_CLIENT_ID` | OAuth Client ID |
| `SFCC_CLIENT_SECRET` | OAuth Client Secret |
| `SFCC_USERNAME` | WebDAV username |
| `SFCC_PASSWORD` | WebDAV password/access key |
| `MRT_API_KEY` | MRT API key |

**Recommended variables:**

| Variable | Description |
|----------|-------------|
| `SFCC_SERVER` | B2C instance hostname |
| `MRT_PROJECT` | MRT project slug |
| `MRT_ENVIRONMENT` | MRT environment |

Credentials can be passed per-action or set once with the **setup** action so they're available to all subsequent steps.

## Quick Start: Deploy Cartridges

```yaml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Install the B2C CLI and configure credentials for subsequent steps
      - uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
        with:
          client-id: ${{ secrets.SFCC_CLIENT_ID }}
          client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
          server: ${{ vars.SFCC_SERVER }}
          username: ${{ secrets.SFCC_USERNAME }}
          password: ${{ secrets.SFCC_PASSWORD }}

      # Run your build steps as usual
      - run: npm ci
      - run: npm run build

      # Generate a code version from the branch name and date
      - name: Set code version
        id: version
        run: |
          BRANCH=$(echo "$GITHUB_REF_NAME" | tr '/' '-')
          echo "code-version=${BRANCH}-$(date +%Y%m%d-%H%M%S)" >> "$GITHUB_OUTPUT"

      # Deploy cartridges — only operation-specific inputs needed
      - uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/code-deploy@v1
        with:
          code-version: ${{ steps.version.outputs.code-version }}
          activate: true
```

The **setup** step installs the CLI and configures credentials for all subsequent steps. Everything after that — your build, version calculation, and deploy — can focus on your project's needs.

## Actions Reference

### Root Action

```
uses: SalesforceCommerceCloud/b2c-developer-tooling@v1
```

Combines setup and command execution. Pass a `command` to run a CLI command, or omit it for setup-only.

| Input | Default | Description |
|-------|---------|-------------|
| `command` | — | CLI command to run |
| `version` | `latest` | CLI version to install |
| `node-version` | `22` | Node.js version |
| `json` | `true` | Append `--json` flag and parse output |
| `working-directory` | `.` | Working directory |
| Auth inputs | — | See [Authentication](#authentication) |

### Setup

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
```

Installs the CLI and writes credentials to environment variables. Use this when you need multiple steps after setup.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
  with:
    client-id: ${{ secrets.SFCC_CLIENT_ID }}
    client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
    server: ${{ vars.SFCC_SERVER }}
    plugins: |
      @myorg/b2c-plugin-custom
      sfcc-solutions-share/b2c-plugin-intellij-sfcc-config
```

Plugins are installed after the CLI and cached across runs. Each line is an npm package name or GitHub `owner/repo`.

### Run

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/run@v1
```

Executes any CLI command. Pairs with the setup action.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/run@v1
  with:
    command: 'sandbox list --realm abcd'
```

| Input | Default | Description |
|-------|---------|-------------|
| `command` | *(required)* | CLI command to run |
| `json` | `true` | Append `--json` and parse output |
| `working-directory` | `.` | Working directory |

### Code Deploy

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/code-deploy@v1
```

Deploy cartridges with typed inputs.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/code-deploy@v1
  with:
    client-id: ${{ secrets.SFCC_CLIENT_ID }}
    client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
    server: ${{ vars.SFCC_SERVER }}
    username: ${{ secrets.SFCC_USERNAME }}
    password: ${{ secrets.SFCC_PASSWORD }}
    code-version: ${{ vars.SFCC_CODE_VERSION }}
    reload: true
    cartridges: 'app_storefront_base,app_custom'
```

| Input | Default | Description |
|-------|---------|-------------|
| `cartridge-path` | `.` | Path to cartridge source directory |
| `activate` | `false` | Activate code version after deploy |
| `code-version` | — | Code version (overrides env) |
| `cartridges` | — | Comma-separated cartridges to include |
| `exclude-cartridges` | — | Comma-separated cartridges to exclude |
| `delete` | `false` | Delete existing cartridges first |

### Data Import

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/data-import@v1
```

Import a site archive. Handles upload, job execution, waiting, and cleanup in one step.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/data-import@v1
  with:
    client-id: ${{ secrets.SFCC_CLIENT_ID }}
    client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
    server: ${{ vars.SFCC_SERVER }}
    username: ${{ secrets.SFCC_USERNAME }}
    password: ${{ secrets.SFCC_PASSWORD }}
    target: './export/site-import.zip'
    timeout: 600
```

| Input | Default | Description |
|-------|---------|-------------|
| `target` | *(required)* | Local file, directory, or zip to import |
| `timeout` | — | Timeout in seconds |
| `keep-archive` | `false` | Keep archive on instance after import |
| `show-log` | `true` | Show job log on failure |

### MRT Deploy

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/mrt-deploy@v1
```

Push and deploy an MRT bundle.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/mrt-deploy@v1
  with:
    mrt-api-key: ${{ secrets.MRT_API_KEY }}
    project: ${{ vars.MRT_PROJECT }}
    environment: ${{ vars.MRT_ENVIRONMENT }}
    build-directory: build
    message: 'Deploy from CI'
```

| Input | Default | Description |
|-------|---------|-------------|
| `project` | — | MRT project slug |
| `environment` | — | Target environment |
| `build-directory` | `build` | Local build directory |
| `message` | — | Bundle message |
| `bundle-id` | — | Deploy existing bundle by ID |

### Job Run

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/job-run@v1
```

Execute a B2C job and optionally wait for completion.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/job-run@v1
  with:
    job-id: 'sfcc-site-archive-import'
    wait: true
    timeout: 600
    parameters: |
      ImportFile=site-import.zip
      ImportMode=merge
```

| Input | Default | Description |
|-------|---------|-------------|
| `job-id` | *(required)* | Job ID to execute |
| `wait` | `true` | Wait for completion |
| `timeout` | `900` | Timeout in seconds (when wait=true) |
| `parameters` | — | `KEY=VALUE` pairs, one per line |
| `show-log` | `true` | Show job log on failure |

### WebDAV Upload

```
uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/webdav-upload@v1
```

Upload files via WebDAV.

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/webdav-upload@v1
  with:
    local-path: './export/site-import.zip'
    remote-path: 'src/instance/'
    root: IMPEX
```

| Input | Default | Description |
|-------|---------|-------------|
| `local-path` | *(required)* | Local file or directory |
| `remote-path` | *(required)* | Remote destination path |
| `root` | `IMPEX` | WebDAV root (IMPEX, TEMP, CARTRIDGES, etc.) |

## Patterns

### Data Import Pipeline

Import a site archive:

```yaml
name: Data Import

on:
  workflow_dispatch:
    inputs:
      import-file:
        description: 'Path to the site import archive'
        required: true
        default: 'export/site-import.zip'

jobs:
  import:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/data-import@v1
        with:
          client-id: ${{ secrets.SFCC_CLIENT_ID }}
          client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
          server: ${{ vars.SFCC_SERVER }}
          username: ${{ secrets.SFCC_USERNAME }}
          password: ${{ secrets.SFCC_PASSWORD }}
          target: ${{ github.event.inputs.import-file }}
          timeout: 600
```

### MRT Release Deploy

Build and deploy an MRT storefront on release:

```yaml
name: MRT Deploy

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build storefront
        run: npm run build

      - uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/mrt-deploy@v1
        with:
          mrt-api-key: ${{ secrets.MRT_API_KEY }}
          project: ${{ vars.MRT_PROJECT }}
          environment: ${{ vars.MRT_ENVIRONMENT }}
          build-directory: build
          message: 'Release ${{ github.event.release.tag_name }}'
```

### Using Outputs

When `json` is enabled (the default), the `result` output contains the command's structured JSON. Reference it directly in downstream steps:

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/code-deploy@v1
  id: deploy
  with:
    code-version: v25_03_1
    reload: true

- name: Show deploy result
  run: echo '${{ steps.deploy.outputs.result }}'
```

```json
{
  "cartridges": [
    { "name": "app_storefront_base", "dest": "app_storefront_base", "src": "..." },
    { "name": "app_custom", "dest": "app_custom", "src": "..." }
  ],
  "codeVersion": "v25_03_1",
  "reloaded": true
}
```

Actions exit with the CLI's exit code, so a failed job will fail the step. Use `continue-on-error` and `fromJSON()` when you need to inspect the result after a failure:

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/job-run@v1
  id: job
  continue-on-error: true
  with:
    job-id: 'sfcc-site-archive-import'
    wait: true

- name: Handle job failure
  if: steps.job.outputs.exit-code != '0'
  run: echo "Job failed with status ${{ fromJSON(steps.job.outputs.result).exit_status.code }}"
```

## Version Pinning

Use the `version` input to pin the CLI version:

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling@v1
  with:
    version: '0.4.1'
```

Use `@v1` for the latest stable action version (recommended). The floating `v1` tag is updated on each backward-compatible release.

> **Note:** High-level actions (`code-deploy`, `data-import`, `job-run`, `mrt-deploy`, `webdav-upload`) and the root action internally reference `actions/setup@v1` and `actions/run@v1`. This means even if you pin the outer action to a specific SHA or tag, the setup and run steps resolve to the latest `v1` release. For full SHA-level reproducibility, use `actions/setup` + `actions/run` directly — each can be pinned independently to an exact SHA. For most users, `@v1` on the high-level actions is the recommended approach.

## Plugins

The CLI supports [plugins](/guide/extending) for custom configuration sources, HTTP middleware, and more. Install plugins in CI with the `plugins` input on the `setup` action:

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
  with:
    client-id: ${{ secrets.SFCC_CLIENT_ID }}
    client-secret: ${{ secrets.SFCC_CLIENT_SECRET }}
    server: ${{ vars.SFCC_SERVER }}
    plugins: |
      @myorg/b2c-plugin-custom
      sfcc-solutions-share/b2c-plugin-intellij-sfcc-config
```

Each line is an npm package name or GitHub `owner/repo`. Plugins are installed after the CLI and cached across workflow runs.

## Logging

The `setup` action accepts a `log-level` input that sets `SFCC_LOG_LEVEL` for all subsequent steps:

```yaml
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
  with:
    log-level: debug
```

Available levels (most to least verbose): `trace`, `debug`, `info` (default), `warn`, `error`, `silent`.

You can also set the environment variable directly in your workflow:

```yaml
env:
  SFCC_LOG_LEVEL: debug
```

Logs are always human-readable on stderr. The `--json` flag only controls the structured result on stdout. If you need machine-readable log lines (e.g., for log aggregation), set `SFCC_JSON_LOGS=true`.

## CI Defaults

All actions automatically configure:

- **`NO_COLOR=1`** — clean log output without ANSI colors
