|
| 1 | +name: Tokens Studio backup |
| 2 | +# Tokens Studio has no undo/rollback — only a read-only version history |
| 3 | +# of releases. Plugin changes push to the platform in real time, so a |
| 4 | +# designer mistake propagates immediately and the release workflow only |
| 5 | +# snapshots at release moments. This job is the safety net in between: |
| 6 | +# it pulls the current state of every token source on a schedule and |
| 7 | +# commits changes to the orphan branch `tokens-studio-backup`, giving us |
| 8 | +# diffs, history and a recovery point independent of the platform. |
| 9 | +# Recovery instructions: documentation/agent-instructions/TOKENS_STUDIO.md |
| 10 | +on: |
| 11 | + schedule: |
| 12 | + # Hourly, off the whole hour to avoid the GitHub cron rush |
| 13 | + - cron: '23 * * * *' |
| 14 | + # Dispatch from main only — the OIDC subject must match the inbound |
| 15 | + # CI integration's refs/heads/main pattern; any other ref gets a 403 |
| 16 | + workflow_dispatch: |
| 17 | +# A slow run must not race the next hourly tick on the shared branch |
| 18 | +concurrency: |
| 19 | + group: tokens-studio-backup |
| 20 | + cancel-in-progress: false |
| 21 | +jobs: |
| 22 | + backup-tokens: |
| 23 | + name: Back up tokens from Tokens Studio |
| 24 | + runs-on: ubuntu-latest |
| 25 | + timeout-minutes: 15 |
| 26 | + permissions: |
| 27 | + # OIDC token so the studio CLI can authenticate against the |
| 28 | + # Tokens Studio CI integration (no service token needed). |
| 29 | + # Scheduled runs always execute on main, so the token subject |
| 30 | + # matches the integration's subject pattern |
| 31 | + # (repo:equinor/design-system:ref:refs/heads/main). Manual |
| 32 | + # dispatches must also pick main — see the trigger comment. |
| 33 | + id-token: write |
| 34 | + # Push backup commits to the tokens-studio-backup branch |
| 35 | + contents: write |
| 36 | + steps: |
| 37 | + - name: Checkout |
| 38 | + uses: actions/checkout@v7 |
| 39 | + # The branch was seeded manually once (orphan, README only) so |
| 40 | + # this checkout can always assume it exists |
| 41 | + - name: Checkout backup branch |
| 42 | + uses: actions/checkout@v7 |
| 43 | + with: |
| 44 | + ref: tokens-studio-backup |
| 45 | + path: backup |
| 46 | + - name: Install Node.js |
| 47 | + uses: actions/setup-node@v6 |
| 48 | + with: |
| 49 | + node-version: '24.16.0' |
| 50 | + # Same key as _setup.yml so the store cache is shared with the |
| 51 | + # other workflows |
| 52 | + - name: Cache pnpm-store |
| 53 | + uses: actions/cache@v6 |
| 54 | + with: |
| 55 | + path: ~/.pnpm-store |
| 56 | + key: ${{ runner.os }}-pnpm-and-store-force-${{ hashFiles('pnpm-lock.yaml') }} |
| 57 | + - name: Setup pnpm |
| 58 | + uses: pnpm/action-setup@v6 |
| 59 | + with: |
| 60 | + run_install: false |
| 61 | + - name: Install dependencies |
| 62 | + run: pnpm install --force |
| 63 | + # No alias argument = pull every source configured in |
| 64 | + # packages/eds-tokens/.studio.json (token sets + $themes.json + |
| 65 | + # $metadata.json). --verbose because the run is unattended — the |
| 66 | + # Actions log is the only place to diagnose a bad pull |
| 67 | + - name: Pull tokens from Tokens Studio |
| 68 | + run: pnpm --filter @equinor/eds-tokens exec studio tokens pull --ci --verbose |
| 69 | + # Aliases and output dirs are read from .studio.json so a config |
| 70 | + # rename (e.g. the planned eds-test-3 → eds) never requires a |
| 71 | + # workflow change. Each source lands at backup/<alias>/; |
| 72 | + # --delete keeps removals visible in the diff |
| 73 | + - name: Sync pulled sources into the backup branch |
| 74 | + run: | |
| 75 | + jq -r '.configurations | to_entries[] | "\(.key)\t\(.value.output)"' packages/eds-tokens/.studio.json | |
| 76 | + while IFS=$'\t' read -r alias output; do |
| 77 | + echo "Syncing $alias (packages/eds-tokens/$output → backup/$alias)" |
| 78 | + rsync -a --delete "packages/eds-tokens/$output/" "backup/$alias/" |
| 79 | + done |
| 80 | + - name: Commit and push backup |
| 81 | + working-directory: backup |
| 82 | + run: | |
| 83 | + git config user.name 'github-actions[bot]' |
| 84 | + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' |
| 85 | + git add -A |
| 86 | + if git diff --cached --quiet; then |
| 87 | + echo 'No token changes since last backup' |
| 88 | + else |
| 89 | + git commit -m "chore: tokens backup $(date -u +%Y-%m-%dT%H:%M:%SZ) (run ${GITHUB_RUN_ID})" |
| 90 | + git push origin tokens-studio-backup |
| 91 | + fi |
| 92 | + # The run is unattended and hourly — a broken backup must not be |
| 93 | + # silent, or the safety net quietly stops existing |
| 94 | + - name: log-errors-to-slack |
| 95 | + uses: act10ns/slack@v2 |
| 96 | + env: |
| 97 | + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} |
| 98 | + with: |
| 99 | + status: ${{ job.status }} |
| 100 | + steps: ${{ toJson(steps) }} |
| 101 | + if: failure() |
0 commit comments