Skip to content

ci: build and test the localization store on every push - #18

Merged
Ryanmello07 merged 1 commit into
mainfrom
ci/build-and-test
Aug 22, 2026
Merged

ci: build and test the localization store on every push#18
Ryanmello07 merged 1 commit into
mainfrom
ci/build-and-test

Conversation

@Ryanmello07

Copy link
Copy Markdown
Contributor

What this adds

.github/workflows/build-and-test.yml — this repo's first CI. One job on ubuntu-latest, ~1-2 minutes, no secrets.

Nothing validates keys/*.yaml today. The first thing that runs the store is the release build (build/all/run.shnpm run gen), which regenerates and writes into ../android, ../apple, ../windows, ../linux — so a malformed key surfaces long after, and somewhere else. This runs the same code on every push and PR.

step what it actually proves
npm ci package-lock.json installs exactly, and has not drifted from package.json
npm run gen gen/store.mjs validate() over all 1,224 keys — snake_case ids, required source/description/en, known locales, one xcstrings key per source on apple, every declared placeholder present in every localization, exactly the CLDR plural categories a locale has, product names never translated — then all four emitters run (android strings.xml, apple Localizable.xcstrings, windows Resources.resw, linux .po/.pot/LINGUAS), and the step asserts a representative output of each was written non-empty
npm test node gen/generate.mjs --check — regenerate over the tree the previous step wrote and require byte equality, i.e. the codegen is deterministic (sort order, object key order, Intl.Collator)
node … index.js the package's public API, which nothing else here touches: the platform filter (loadAllKeys() vs { platform: null }), alias resolution (continuecontinue_txt), and the promise that every value is a string (a plural key collapses to its other form)

Triggers: push and pull_request on main, plus workflow_dispatch. permissions: contents: read, timeout-minutes: 10, and a cancel-in-progress concurrency group.

What it deliberately does not do

  • No publishing, no signing, no secrets. release:patch / release:minor / release:major run npm publish and need an npm token; nothing here reaches them. npm pack is not run either, so prepublishOnly never fires.
  • No gen/verify-lossless.mjs. It requires --baseline <dir>, a snapshot of the pre-migration app tree that is not in this repo; run bare — which is all the verify-lossless script does — it exits 2. Adding it would mean committing or fetching that snapshot.
  • No diff against the sibling app checkouts — see below.

The one thing to decide

npm test is node gen/generate.mjs --check, and by default it compares the generated files against $URNETWORK_ROOT/{android,apple,windows,linux}/…, i.e. four other repositories' working trees. That is the README's "CI drift gate", and a runner has no siblings, so a bare npm test on a fresh checkout fails 100% of the time (every file reads as drift).

This workflow points URNETWORK_ROOT at a scratch directory, so npm test gates on the codegen being complete and deterministic and the check stays inside this repo. The reason is the trade-off server/test.yml already writes down where it has no choice: "Tracking each sibling's default branch would make this repo's CI a function of six other repos' latest commits — one unrelated push there turns every open PR here red at once." Here we do have a choice, because the store validates standalone.

If you want the cross-repo gate too, it is a second job — four blobless sparse checkouts (only android/app/app/src/main/res/values*/strings.xml, apple/app/network/Shared/Resources/Localizable.xcstrings, windows/app/src/App/Strings/, linux/app/po/) beside the checkout, then plain npm test with the default URNETWORK_ROOT. Worth knowing before you say yes: it goes red whenever an app repo lags a store change, which is a normal state between a translation landing here and the next release build regenerating the app trees — so it wants to be non-blocking, or the app repos want a bot that regenerates on push. Say the word and I will add it in the shape you prefer.

Two smaller calls, both one-liners:

  • Node is pinned to 24, matching the release pipeline's node v24.14.1 gate and urnetwork/extension's CI. package.json declares no engines, and js-yaml is the only dependency, so nothing narrower is implied. web/build.yml uses node-version: latest; if you would rather this repo float with it, that is one line.
  • site keys are generated by nothing. 213 keys declare platforms: [site], but gen/generate.mjs only has android, apple, windows, linux targets, so those keys are validated here but never emitted. Not a CI question — flagging it because the audit fell out of this.

Verification

python3 -c "import yaml; yaml.safe_load(...)" and actionlint (with shellcheck) both clean; every npm script, export and output path referenced was checked against this checkout. There is no node on the machine this was written on, so the run on this PR is the first real execution — I will report the result here.

This repo had no CI. Nothing validated keys/*.yaml until the release build
ran `npm run gen` and wrote into the app repos, so a malformed key -- the
common failure when a translation lands -- surfaced far from where it was
introduced.

The workflow does what the repo already does, on a runner:

  npm ci                 install package-lock.json exactly
  npm run gen            load + validate all 1,224 keys/*.yaml through
                         gen/store.mjs validate(), then run all four
                         emitters (android strings.xml, apple
                         Localizable.xcstrings, windows Resources.resw,
                         linux .po/.pot/LINGUAS) and assert each wrote
  npm test               `node gen/generate.mjs --check` against that same
                         tree: regenerate and require byte equality, which
                         gates the codegen being deterministic
  node -e ...index.js    the package's public API -- the platform filter,
                         alias resolution, every value a string

URNETWORK_ROOT points at a scratch directory rather than its default (`..`,
the sibling app checkouts, which a runner does not have). That keeps the
check inside this repo: `npm test` against the real siblings would make
every PR here a function of four other repos' latest commits, the trade-off
server/test.yml documents where it has no choice. Opting in is four sparse
clones and one env var; left to the maintainers.

No release, no signing, no secrets: package.json's release:* scripts run
`npm publish` and are not reachable from here. gen/verify-lossless.mjs is
also left out -- it requires a --baseline snapshot of the pre-migration app
tree that is not in this repo, and exits 2 without one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAXFxG1EK4jTxQ1iW73BUr
@Ryanmello07

Copy link
Copy Markdown
Contributor Author

Green on the first run: 32549996274, 8 seconds on ubuntu-latest, every step executed.

Install     added 2 packages, and audited 3 packages in 2s
Build       localizations: wrote 78 of 78 files (android, apple, windows, linux)
Test        localizations: 78 generated files are up to date
API         web 57 keys, store 1234 keys, 28 locales

78 = 19 android values* + 1 apple catalog + 28 windows .resw + 28 .po + .pot + LINGUAS. 1234 = 1,224 keys plus the 10 aliases; 57 is the web set (47 keys + their 10 aliases), which is the slice the extension consumes.

One annotation, fleet-wide rather than specific to this file: actions/checkout@v4 and actions/setup-node@v4 still declare Node 20 and are being forced onto Node 24 by the runner. Every urnetwork workflow pins @v4, so this one matches; bumping to @v5 is a change worth making everywhere at once rather than here alone.

@Ryanmello07
Ryanmello07 marked this pull request as ready for review August 22, 2026 05:32
@Ryanmello07
Ryanmello07 merged commit d64c666 into main Aug 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant