Skip to content

Commit 1e20f15

Browse files
committed
fix(ci): batch i18n locale refresh commits
1 parent 8f9e163 commit 1e20f15

3 files changed

Lines changed: 176 additions & 23 deletions

File tree

.github/workflows/control-ui-locale-refresh.yml

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,31 +200,86 @@ jobs:
200200
201201
run_openai_refresh
202202
203-
- name: Commit and push locale updates
203+
- name: Prepare locale artifact
204204
env:
205205
LOCALE: ${{ matrix.locale }}
206+
run: |
207+
set -euo pipefail
208+
artifact_dir="${RUNNER_TEMP}/control-ui-locale-${LOCALE}"
209+
mkdir -p "${artifact_dir}"
210+
git add -A ui/src/i18n
211+
git diff --cached --binary --full-index -- ui/src/i18n > "${artifact_dir}/${LOCALE}.patch"
212+
213+
- name: Upload locale artifact
214+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
215+
with:
216+
name: control-ui-locale-${{ matrix.locale }}
217+
path: ${{ runner.temp }}/control-ui-locale-${{ matrix.locale }}/${{ matrix.locale }}.patch
218+
if-no-files-found: error
219+
retention-days: 1
220+
221+
finalize:
222+
name: Commit control UI locale refresh
223+
needs: refresh
224+
if: needs.refresh.result == 'success'
225+
runs-on: ubuntu-latest
226+
steps:
227+
- name: Checkout
228+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
229+
with:
230+
fetch-depth: 0
231+
persist-credentials: true
232+
submodules: false
233+
234+
- name: Download locale artifacts
235+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
236+
with:
237+
pattern: control-ui-locale-*
238+
path: ${{ runner.temp }}/control-ui-locale-artifacts
239+
merge-multiple: true
240+
241+
- name: Apply locale artifacts
242+
run: |
243+
set -euo pipefail
244+
while IFS= read -r patch; do
245+
if [ -s "${patch}" ]; then
246+
git apply "${patch}"
247+
fi
248+
done < <(find "${RUNNER_TEMP}/control-ui-locale-artifacts" -type f -name '*.patch' | sort)
249+
250+
- name: Setup Node environment
251+
uses: ./.github/actions/setup-node-env
252+
with:
253+
install-bun: "false"
254+
255+
- name: Validate control UI locale refresh
256+
run: node --import tsx scripts/control-ui-i18n.ts check
257+
258+
- name: Commit and push aggregate locale refresh
259+
env:
206260
TARGET_BRANCH: ${{ github.event.repository.default_branch }}
207261
run: |
208262
set -euo pipefail
209-
if git diff --quiet -- ui/src/i18n; then
210-
echo "No control UI locale changes for ${LOCALE}."
263+
if ! git status --porcelain -- ui/src/i18n | grep -q .; then
264+
echo "No control UI locale changes."
211265
exit 0
212266
fi
213267
214268
git config user.name "github-actions[bot]"
215269
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
216270
git add -A ui/src/i18n
217-
git commit --no-verify -m "chore(ui): refresh ${LOCALE} control ui locale"
271+
git commit --no-verify -m "chore(ui): refresh control ui locales"
218272
219273
for attempt in 1 2 3 4 5; do
220274
git fetch origin "${TARGET_BRANCH}"
221-
git rebase --autostash "origin/${TARGET_BRANCH}"
275+
git rebase "origin/${TARGET_BRANCH}"
222276
if git push origin HEAD:"${TARGET_BRANCH}"; then
223277
exit 0
224278
fi
225-
echo "Push attempt ${attempt} for ${LOCALE} failed; retrying."
279+
git rebase --abort >/dev/null 2>&1 || true
280+
echo "Aggregate push attempt ${attempt} failed; retrying."
226281
sleep $((attempt * 2))
227282
done
228283
229-
echo "Failed to push ${LOCALE} locale update after retries."
284+
echo "Failed to push aggregate control UI locale update after retries."
230285
exit 1

.github/workflows/native-app-locale-refresh.yml

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,91 @@ jobs:
147147
148148
run_openai_refresh
149149
150-
- name: Commit and push locale artifact
150+
- name: Prepare locale artifact
151151
env:
152152
LOCALE: ${{ matrix.locale }}
153+
run: |
154+
set -euo pipefail
155+
artifact_dir="${RUNNER_TEMP}/native-locale-${LOCALE}"
156+
mkdir -p "${artifact_dir}"
157+
git add -A apps/.i18n/native
158+
git diff --cached --binary --full-index -- apps/.i18n/native > "${artifact_dir}/${LOCALE}.patch"
159+
160+
- name: Upload locale artifact
161+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
162+
with:
163+
name: native-locale-${{ matrix.locale }}
164+
path: ${{ runner.temp }}/native-locale-${{ matrix.locale }}/${{ matrix.locale }}.patch
165+
if-no-files-found: error
166+
retention-days: 1
167+
168+
finalize:
169+
name: Commit native locale refresh
170+
needs: refresh
171+
if: needs.refresh.result == 'success'
172+
runs-on: ubuntu-latest
173+
steps:
174+
- name: Checkout
175+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
176+
with:
177+
fetch-depth: 0
178+
persist-credentials: true
179+
submodules: false
180+
181+
- name: Download locale artifacts
182+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
183+
with:
184+
pattern: native-locale-*
185+
path: ${{ runner.temp }}/native-locale-artifacts
186+
merge-multiple: true
187+
188+
- name: Apply locale artifacts
189+
run: |
190+
set -euo pipefail
191+
while IFS= read -r patch; do
192+
if [ -s "${patch}" ]; then
193+
git apply "${patch}"
194+
fi
195+
done < <(find "${RUNNER_TEMP}/native-locale-artifacts" -type f -name '*.patch' | sort)
196+
197+
- name: Setup Node environment
198+
uses: ./.github/actions/setup-node-env
199+
with:
200+
install-bun: "false"
201+
202+
# Every worker observes the same source inventory. Generate it once here
203+
# so locale patches remain independent and can be applied sequentially.
204+
- name: Refresh shared native inventory
205+
run: node --import tsx scripts/native-app-i18n.ts sync --write
206+
207+
- name: Validate native locale refresh
208+
run: node --import tsx scripts/native-app-i18n.ts check
209+
210+
- name: Commit and push aggregate locale refresh
211+
env:
153212
TARGET_BRANCH: ${{ github.event.repository.default_branch }}
154213
run: |
155214
set -euo pipefail
156215
if ! git status --porcelain -- apps/.i18n/native apps/.i18n/native-source.json | grep -q .; then
157-
echo "No native locale changes for ${LOCALE}."
216+
echo "No native locale changes."
158217
exit 0
159218
fi
160219
161220
git config user.name "github-actions[bot]"
162221
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
163222
git add -A apps/.i18n/native apps/.i18n/native-source.json
164-
git commit --no-verify -m "chore(i18n): refresh native ${LOCALE} locale"
223+
git commit --no-verify -m "chore(i18n): refresh native locales"
165224
166225
for attempt in 1 2 3 4 5; do
167226
git fetch origin "${TARGET_BRANCH}"
168-
git rebase --autostash "origin/${TARGET_BRANCH}"
227+
git rebase "origin/${TARGET_BRANCH}"
169228
if git push origin HEAD:"${TARGET_BRANCH}"; then
170229
exit 0
171230
fi
172-
echo "Push attempt ${attempt} for ${LOCALE} failed; retrying."
231+
git rebase --abort >/dev/null 2>&1 || true
232+
echo "Aggregate push attempt ${attempt} failed; retrying."
173233
sleep $((attempt * 2))
174234
done
175235
176-
echo "Failed to push ${LOCALE} native locale update after retries."
236+
echo "Failed to push aggregate native locale update after retries."
177237
exit 1

test/scripts/ci-workflow-guards.test.ts

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { parse } from "yaml";
77
const CHECKOUT_V6 = "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10";
88
const CACHE_V5 = "actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae";
99
const UPLOAD_ARTIFACT_V7 = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a";
10+
const DOWNLOAD_ARTIFACT_V8 = "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c";
1011
const OPENGREP_PR_DIFF_WORKFLOW = ".github/workflows/opengrep-precise.yml";
1112
const OPENGREP_FULL_WORKFLOW = ".github/workflows/opengrep-precise-full.yml";
1213
const CONTROL_UI_LOCALE_REFRESH_WORKFLOW = ".github/workflows/control-ui-locale-refresh.yml";
@@ -124,17 +125,21 @@ describe("ci workflow guards", () => {
124125
expect(findUnpinnedExternalActions()).toEqual([]);
125126
});
126127

127-
it("keeps locale refresh bots from cancelling active refresh matrices", () => {
128+
it("keeps locale refresh matrices alive and commits each aggregate once", () => {
128129
const controlUiWorkflow = parse(readFileSync(CONTROL_UI_LOCALE_REFRESH_WORKFLOW, "utf8"));
129-
const source = readFileSync(NATIVE_APP_LOCALE_REFRESH_WORKFLOW, "utf8");
130-
const workflow = parse(source);
130+
const workflow = parse(readFileSync(NATIVE_APP_LOCALE_REFRESH_WORKFLOW, "utf8"));
131131
const refresh = workflow.jobs.refresh;
132-
const commitStep = refresh.steps.find(
133-
(step: { name?: string }) => step.name === "Commit and push locale artifact",
134-
);
132+
const nativeFinalize = workflow.jobs.finalize;
133+
const controlUiFinalize = controlUiWorkflow.jobs.finalize;
135134
const refreshStep = refresh.steps.find(
136135
(step: { name?: string }) => step.name === "Refresh native locale artifact",
137136
);
137+
const nativeArtifactStep = refresh.steps.find(
138+
(step: { name?: string }) => step.name === "Prepare locale artifact",
139+
);
140+
const nativeInventoryStep = nativeFinalize.steps.find(
141+
(step: { name?: string }) => step.name === "Refresh shared native inventory",
142+
);
138143
const controlUiRefreshStep = controlUiWorkflow.jobs.refresh.steps.find(
139144
(step: { name?: string }) => step.name === "Refresh control UI locale files",
140145
);
@@ -156,6 +161,11 @@ describe("ci workflow guards", () => {
156161
"${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY }}",
157162
);
158163
expect(refreshStep.env.OPENAI_API_KEY).toBe("${{ secrets.OPENAI_API_KEY }}");
164+
expect(nativeArtifactStep.run).toContain("git add -A apps/.i18n/native");
165+
expect(nativeArtifactStep.run).not.toContain("native-source.json");
166+
expect(nativeInventoryStep.run).toBe(
167+
"node --import tsx scripts/native-app-i18n.ts sync --write",
168+
);
159169
expect(controlUiRefreshStep.run).toContain("run_refresh anthropic");
160170
expect(controlUiRefreshStep.run).toContain("retrying with OpenAI");
161171
expect(controlUiRefreshStep.run).toContain("run_openai_refresh");
@@ -165,10 +175,38 @@ describe("ci workflow guards", () => {
165175
);
166176
expect(controlUiRefreshStep.env.OPENAI_API_KEY).toBe("${{ secrets.OPENAI_API_KEY }}");
167177
expect(controlUiRefreshStep.env.OPENCLAW_CONTROL_UI_I18N_AUTH_OPTIONAL).toBe("0");
168-
expect(commitStep.run).toContain("for attempt in 1 2 3 4 5");
169-
expect(commitStep.run).toContain('git fetch origin "${TARGET_BRANCH}"');
170-
expect(commitStep.run).toContain('git rebase --autostash "origin/${TARGET_BRANCH}"');
171-
expect(commitStep.run).toContain('git push origin HEAD:"${TARGET_BRANCH}"');
178+
179+
for (const [refreshJob, finalizeJob, artifactPattern, commitMessage] of [
180+
[refresh, nativeFinalize, "native-locale-*", "chore(i18n): refresh native locales"],
181+
[
182+
controlUiWorkflow.jobs.refresh,
183+
controlUiFinalize,
184+
"control-ui-locale-*",
185+
"chore(ui): refresh control ui locales",
186+
],
187+
] as const) {
188+
const uploadStep = refreshJob.steps.find(
189+
(step: { name?: string }) => step.name === "Upload locale artifact",
190+
);
191+
const downloadStep = finalizeJob.steps.find(
192+
(step: { name?: string }) => step.name === "Download locale artifacts",
193+
);
194+
const commitStep = finalizeJob.steps.find(
195+
(step: { name?: string }) => step.name === "Commit and push aggregate locale refresh",
196+
);
197+
198+
expect(finalizeJob.needs).toBe("refresh");
199+
expect(finalizeJob.if).toBe("needs.refresh.result == 'success'");
200+
expect(uploadStep.uses).toBe(UPLOAD_ARTIFACT_V7);
201+
expect(downloadStep.uses).toBe(DOWNLOAD_ARTIFACT_V8);
202+
expect(downloadStep.with.pattern).toBe(artifactPattern);
203+
expect(downloadStep.with["merge-multiple"]).toBe(true);
204+
expect(commitStep.run).toContain(`git commit --no-verify -m "${commitMessage}"`);
205+
expect(commitStep.run).toContain("for attempt in 1 2 3 4 5");
206+
expect(commitStep.run).toContain('git fetch origin "${TARGET_BRANCH}"');
207+
expect(commitStep.run).toContain('git rebase "origin/${TARGET_BRANCH}"');
208+
expect(commitStep.run).toContain('git push origin HEAD:"${TARGET_BRANCH}"');
209+
}
172210
});
173211

174212
it("fails OpenGrep SARIF artifact uploads when reports are missing", () => {

0 commit comments

Comments
 (0)