@@ -7,6 +7,7 @@ import { parse } from "yaml";
77const CHECKOUT_V6 = "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" ;
88const CACHE_V5 = "actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae" ;
99const UPLOAD_ARTIFACT_V7 = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" ;
10+ const DOWNLOAD_ARTIFACT_V8 = "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" ;
1011const OPENGREP_PR_DIFF_WORKFLOW = ".github/workflows/opengrep-precise.yml" ;
1112const OPENGREP_FULL_WORKFLOW = ".github/workflows/opengrep-precise-full.yml" ;
1213const 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