Add GitHub Actions for CI/CD automation#163
Conversation
Composite YAML actions for B2C CLI operations: setup, run, code-deploy, mrt-deploy, job-run, and webdav-upload. Includes starter workflow templates, CI test workflow, and user guide documentation.
The setup action already sets SFCC_DISABLE_TELEMETRY and NO_COLOR via $GITHUB_ENV — no need to duplicate at the workflow level.
Only disable telemetry in our own test workflow, not for customers.
Most use cases will derive the code version dynamically rather than storing it as a static repository variable.
--json now only controls structured result output to stdout. Logs always use pino-pretty on stderr. A new --jsonl flag (with --json-logs alias and SFCC_JSON_LOGS env var) is available for users who explicitly want JSONL log output. In the run action, stderr is no longer merged into stdout so human-readable logs stream directly to the GitHub Actions step log.
The setup action now accepts a log-level input that sets SFCC_LOG_LEVEL for all subsequent steps. Test actions workflow uses trace level for maximum visibility. Document logging configuration in CI/CD guide and actions README.
Add practical examples showing how to use fromJSON() to extract fields from code-deploy, job-run, and code-list results. Remove pino-pretty implementation detail from logging section.
Lead with direct output reference, only introduce fromJSON() where it's actually needed (conditional expressions, extracting fields).
Restore echo of captured stdout in the run action so command output (help text, JSON results) appears in the GitHub Actions step log. Stderr already streams directly. Update the Using Outputs docs to show continue-on-error pattern for inspecting failed job results.
Replace buffered capture with tee + temp file so stdout streams to the step log as the command runs. Long-running commands like code deploy and job run --wait now show progress immediately instead of buffering until completion.
New high-level action that handles upload, job execution, waiting, and cleanup in a single step. Update the workflow template to use it.
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive GitHub Actions for CI/CD automation of B2C Commerce operations. The changes introduce composite YAML actions for common deployment tasks, along with documentation and workflow templates to help users get started quickly.
Changes:
- Separate
--json(structured output) and--jsonl/SFCC_JSON_LOGS(JSON log messages) flags in the CLI to distinguish between command output and logging - Foundation actions:
setup(CLI installation + environment configuration),run(command execution), and root action (combined setup + run) - High-level actions:
code-deploy,mrt-deploy,job-run,data-import, andwebdav-uploadwith typed inputs - Comprehensive CI/CD guide documentation with examples and patterns
- Workflow templates for common deployment scenarios
- Test workflow with smoke tests and E2E tests
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
packages/b2c-tooling-sdk/src/logging/index.ts |
Updated example to use SFCC_JSON_LOGS instead of CI env var |
packages/b2c-tooling-sdk/src/cli/base-command.ts |
Added separate --jsonl flag for JSON log output, distinct from --json for structured results |
docs/guide/ci-cd.md |
Comprehensive guide for using GitHub Actions with examples, patterns, and configuration details |
docs/.vitepress/config.mts |
Added CI/CD guide to documentation sidebar |
actions/workflow-templates/*.yml |
Starter workflow templates for code deploy, MRT deploy, and data import |
actions/setup/action.yml |
Foundation action to install CLI and configure environment variables |
actions/run/action.yml |
Foundation action to execute CLI commands with JSON output capture |
action.yml |
Root action combining setup and run in one step |
actions/code-deploy/action.yml |
High-level action for deploying cartridges with typed inputs |
actions/mrt-deploy/action.yml |
High-level action for MRT bundle deployment |
actions/job-run/action.yml |
High-level action for executing B2C jobs |
actions/data-import/action.yml |
High-level action for site archive import |
actions/webdav-upload/action.yml |
High-level action for WebDAV file uploads |
actions/README.md |
Quick reference guide for all actions |
.github/workflows/test-actions.yml |
CI workflow with smoke tests and E2E tests |
.changeset/github-actions.md |
Changeset for minor version bump |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CMD="job run ${{ inputs.job-id }}" | ||
|
|
||
| if [ "${{ inputs.wait }}" = "true" ]; then | ||
| CMD="$CMD --wait --timeout ${{ inputs.timeout }}" |
There was a problem hiding this comment.
The timeout input should be properly quoted to prevent command injection. Consider: CMD="$CMD --wait --timeout \"${{ inputs.timeout }}\""
| CMD="$CMD --wait --timeout ${{ inputs.timeout }}" | |
| CMD="$CMD --wait --timeout \"${{ inputs.timeout }}\"" |
| with: | ||
| command: 'webdav put --help' | ||
| json: 'false' | ||
|
|
There was a problem hiding this comment.
The test suite only runs help commands for the high-level composite actions (code-deploy, mrt-deploy, job-run, data-import, webdav-upload) but doesn't actually test using these actions themselves. Consider adding test cases that use these composite actions with mock credentials to verify they correctly call the setup and run actions with properly constructed commands.
| code-deploy-composite: | |
| name: 'Code Deploy (composite)' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use code-deploy composite with mock credentials | |
| uses: ./actions/code-deploy | |
| with: | |
| client-id: 'mock-client-id' | |
| client-secret: 'mock-client-secret' | |
| server: 'https://mock.example.com' | |
| username: 'mock-username' | |
| password: 'mock-password' | |
| mrt-deploy-composite: | |
| name: 'MRT Deploy (composite)' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use mrt-deploy composite with mock credentials | |
| uses: ./actions/mrt-deploy | |
| with: | |
| client-id: 'mock-client-id' | |
| client-secret: 'mock-client-secret' | |
| server: 'https://mock.example.com' | |
| username: 'mock-username' | |
| password: 'mock-password' | |
| job-run-composite: | |
| name: 'Job Run (composite)' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use job-run composite with mock credentials | |
| uses: ./actions/job-run | |
| with: | |
| client-id: 'mock-client-id' | |
| client-secret: 'mock-client-secret' | |
| server: 'https://mock.example.com' | |
| username: 'mock-username' | |
| password: 'mock-password' | |
| data-import-composite: | |
| name: 'Data Import (composite)' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use data-import composite with mock credentials | |
| uses: ./actions/data-import | |
| with: | |
| client-id: 'mock-client-id' | |
| client-secret: 'mock-client-secret' | |
| server: 'https://mock.example.com' | |
| username: 'mock-username' | |
| password: 'mock-password' | |
| webdav-upload-composite: | |
| name: 'WebDAV Upload (composite)' | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use webdav-upload composite with mock credentials | |
| uses: ./actions/webdav-upload | |
| with: | |
| client-id: 'mock-client-id' | |
| client-secret: 'mock-client-secret' | |
| server: 'https://mock.example.com' | |
| username: 'mock-username' | |
| password: 'mock-password' |
|
|
||
| - name: Install B2C CLI | ||
| shell: bash | ||
| run: npm install -g @salesforce/b2c-cli@${{ inputs.version }} |
There was a problem hiding this comment.
The version input should be properly quoted to prevent command injection. Consider: run: npm install -g "@salesforce/b2c-cli@${{ inputs.version }}"
| run: npm install -g @salesforce/b2c-cli@${{ inputs.version }} | |
| run: npm install -g "@salesforce/b2c-cli@${{ inputs.version }}" |
| IFS=',' read -ra XCARTS <<< "${{ inputs.exclude-cartridges }}" | ||
| for cart in "${XCARTS[@]}"; do | ||
| cart=$(echo "$cart" | xargs) | ||
| CMD="$CMD -x $cart" |
There was a problem hiding this comment.
The cartridge names in the exclude loop should be properly quoted to prevent issues with cartridge names containing spaces or special characters. Consider: CMD="$CMD -x \"$cart\""
| CMD="$CMD -x $cart" | |
| CMD="$CMD -x \"$cart\"" |
| fi | ||
|
|
||
| if [ -n "${{ inputs.code-version }}" ]; then | ||
| CMD="$CMD -v ${{ inputs.code-version }}" |
There was a problem hiding this comment.
The code-version input should be properly quoted to prevent command injection and handle versions with spaces or special characters. Consider: CMD="$CMD -v \"${{ inputs.code-version }}\""
| CMD="$CMD -v ${{ inputs.code-version }}" | |
| CMD="$CMD -v \"${{ inputs.code-version }}\"" |
| id: upload | ||
| uses: ./actions/run | ||
| with: | ||
| command: "webdav put ${{ inputs.local-path }} ${{ inputs.remote-path }} --root ${{ inputs.root }}" |
There was a problem hiding this comment.
All path inputs (local-path, remote-path, root) should be properly quoted to prevent command injection and handle paths with spaces or special characters. Consider: command: "webdav put \"${{ inputs.local-path }}\" \"${{ inputs.remote-path }}\" --root \"${{ inputs.root }}\""
| command: "webdav put ${{ inputs.local-path }} ${{ inputs.remote-path }} --root ${{ inputs.root }}" | |
| command: "webdav put \"${{ inputs.local-path }}\" \"${{ inputs.remote-path }}\" --root \"${{ inputs.root }}\"" |
| id: build-cmd | ||
| shell: bash | ||
| run: | | ||
| CMD="code deploy ${{ inputs.cartridge-path }}" |
There was a problem hiding this comment.
The cartridge-path input should be properly quoted to prevent command injection and handle paths with spaces or special characters. Consider using double quotes around the variable interpolation: CMD="code deploy \"${{ inputs.cartridge-path }}\""
| CMD="code deploy ${{ inputs.cartridge-path }}" | |
| CMD="code deploy \"${{ inputs.cartridge-path }}\"" |
| IFS=',' read -ra CARTS <<< "${{ inputs.cartridges }}" | ||
| for cart in "${CARTS[@]}"; do | ||
| cart=$(echo "$cart" | xargs) | ||
| CMD="$CMD -c $cart" |
There was a problem hiding this comment.
The cartridge names in the loop should be properly quoted to prevent issues with cartridge names containing spaces or special characters. Consider: CMD="$CMD -c \"$cart\""
| fi | ||
|
|
||
| if [ -n "${{ inputs.timeout }}" ]; then | ||
| CMD="$CMD --timeout ${{ inputs.timeout }}" |
There was a problem hiding this comment.
The timeout input should be properly quoted to prevent command injection. Consider: CMD="$CMD --timeout \"${{ inputs.timeout }}\""
| CMD="$CMD --timeout ${{ inputs.timeout }}" | |
| CMD="$CMD --timeout \"${{ inputs.timeout }}\"" |
| server: ${{ vars.SFCC_SERVER }} | ||
| username: ${{ secrets.SFCC_USERNAME }} | ||
| password: ${{ secrets.SFCC_PASSWORD }} | ||
| target: ${{ github.event.inputs.import-file }} |
There was a problem hiding this comment.
The target: ${{ github.event.inputs.import-file }} workflow example wires a manual workflow_dispatch input directly into the data-import action, which ultimately builds a shell command string and executes it via the run action. Because import-file is provided at dispatch time, a less-trusted user who can trigger this workflow could inject shell metacharacters (e.g., ;, &&, $()) into the path and cause arbitrary commands to run on the GitHub runner with access to repository secrets. To prevent command injection, ensure that import-file is restricted/validated to safe path patterns (or passed in a way that does not get interpolated into a shell command string), or update the underlying action to avoid constructing and executing shell strings from untrusted input.
Accept a list of plugins (one per line) to install after the CLI. Cache the oclif data directory (~/.local/share/b2c) alongside npm so plugin installs are reused across runs.
patricksullivansf
left a comment
There was a problem hiding this comment.
interesting approach. i still have much to learn.
Summary
setup(install CLI + set env vars),run(execute any command), root action (setup + run in one step)code-deploy,mrt-deploy,job-run,webdav-uploadwith typed inputstest-actions.yml) with smoke tests and E2E tests gated one2e-devenvironmentdocs/guide/ci-cd.mdadded to VitePress sidebarTest plan
test-actions.ymlworkflow passes on this PR (setup-latest, setup-nightly, root-action, setup-then-run, help smoke tests)pnpm run docs:devand check CI/CD guide pagee2e-devenvironment