Skip to content

Commit 8302ce2

Browse files
authored
ci: add scheduled git backup of Tokens Studio tokens (#5175)
* ci: add scheduled git backup of Tokens Studio tokens * ci: clarify that manual dispatch must run from main for OIDC auth
1 parent 5c338ea commit 8302ce2

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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()

documentation/agent-instructions/TOKENS_STUDIO.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,21 @@ Classify every command before running it:
9191
`.github/workflows/tokens_studio_release.yaml` runs on every Tokens Studio release and opens/updates an automated PR (branch `tokens-studio-release`) with the full token state:
9292

9393
1. **Trigger:** the outbound **CI Trigger** in Studio (Integrations → CI Triggers, `eds-tokens-release`) sends a `repository_dispatch` event (type `tokens-release`) to this repo on `release.created`. It can be fired manually with its **Test** button.
94-
2. **Auth:** the workflow authenticates back to Studio via GitHub OIDC against the inbound **CI Integration** (Integrations → CI Integration (Inbound); subject pattern `repo:equinor/design-system:ref:refs/heads/main`, read-only). The outbound trigger and the inbound integration are **separate configs** — a 403 `No matching CI integration found` from `studio ... --ci` means the *inbound* integration is missing or misconfigured, no matter how healthy the trigger looks.
94+
2. **Auth:** the workflow authenticates back to Studio via GitHub OIDC against the inbound **CI Integration** (Integrations → CI Integration (Inbound); subject pattern `repo:equinor/design-system:ref:refs/heads/main`, read-only). The outbound trigger and the inbound integration are **separate configs** — a 403 `No matching CI integration found` from `studio ... --ci` means the _inbound_ integration is missing or misconfigured, no matter how healthy the trigger looks.
9595
3. **Steps:** `studio tokens pull` (raw token sets → `src/tokens/<alias>/`), `studio exports run` for the two saved configurations — `EDS-CSS` (css) and `EDS-DTCG` (dtcg), referenced by ID — into `src/tokens/css/` and `src/tokens/dtcg/`, then `pnpm run generate:ts-tokens` which combines DTCG structure with evaluated CSS values into TS modules in `src/tokens/ts/` (see the script header in `packages/eds-tokens/scripts/generate-ts-tokens.mjs` for the mechanics, including CSS Color 4 gamut mapping).
9696
4. **Output:** all three directories are committed to the release PR. They are **generated — never edit them by hand**, and they are not yet wired into the package `exports` map.
9797

98+
## Backup & recovery
99+
100+
The platform has **no undo, rollback or restore** — only a read-only version history of releases — and plugin changes push to Studio in real time. `.github/workflows/tokens_studio_backup.yaml` is the safety net for everything between releases: every hour (cron `23 * * * *`, plus manual `workflow_dispatch`) it runs `studio tokens pull` for all sources in `.studio.json` and commits changes to the orphan branch **`tokens-studio-backup`** (one directory per source alias — never merge this branch). Runs that find no changes make no commit. Auth is the same inbound CI Integration as the release pipeline — no extra setup. Failures alert via the Slack step; an hourly backup that fails silently is no safety net.
101+
102+
**Recovery is manual — the CLI has no push command (only `pull`/`watch`):**
103+
104+
1. On the `tokens-studio-backup` branch, find the last good state: `git log --stat -- <alias>/`, then `git diff` between commits to locate when the bad change landed (hourly granularity).
105+
2. Extract the affected token-set JSON from that commit: `git show <commit>:<alias>/<set>.json`.
106+
3. Re-import the JSON into Tokens Studio (plugin / file upload), coordinated with the designers — never restore over in-progress work without agreeing on the target state first.
107+
4. If the mistake also made it into a Studio release, the `tokens-studio-release` PR history holds the same data at release granularity.
108+
98109
## Staying current
99110

100111
The CLI and platform are young and move fast (v0.1.x as of the snapshot below). **Never answer Tokens Studio questions from memory alone.** In order of authority:
@@ -121,4 +132,4 @@ studio
121132
└── flags --host --json --no-color --verbose
122133
```
123134

124-
Known state in this repo at snapshot time: CLI installed as a devDependency of `packages/eds-tokens` and approved in `onlyBuiltDependencies`. `.studio.json` lives in `packages/eds-tokens` (single source alias `eds-test-3`, project `4952a007-699a-4124-a043-124e80cc28d6`, branch `main`, format raw — the test-phase alias/output is due a production-shaped rename). Two saved export configurations on the project: `EDS-CSS` (css, id `39d37416-632b-4f04-b49b-cfaec63e3baa`, prefix `eds`, split layout, density base comfortable) and `EDS-DTCG` (dtcg, id `019463d4-9bef-4e37-9425-6de80eec87c8`). CI auth is OIDC (`id-token: write` + `--ci`) — no service token stored. See § The release pipeline for the workflow chain.
135+
Known state in this repo at snapshot time: CLI installed as a devDependency of `packages/eds-tokens` and approved in `onlyBuiltDependencies`. `.studio.json` lives in `packages/eds-tokens` (single source alias `eds-test-3`, project `4952a007-699a-4124-a043-124e80cc28d6`, branch `main`, format raw — the test-phase alias/output is due a production-shaped rename). Two saved export configurations on the project: `EDS-CSS` (css, id `39d37416-632b-4f04-b49b-cfaec63e3baa`, prefix `eds`, split layout, density base comfortable) and `EDS-DTCG` (dtcg, id `019463d4-9bef-4e37-9425-6de80eec87c8`). CI auth is OIDC (`id-token: write` + `--ci`) — no service token stored. Two workflows use it: `tokens_studio_release.yaml` (release-triggered, § The release pipeline) and `tokens_studio_backup.yaml` (hourly cron, § Backup & recovery).

0 commit comments

Comments
 (0)