v1.0.5 — the GitLab recipe runs on a CI runner, and the merge request carries only translations
LatestThe GitLab recipe we print could not translate anything on an actual GitLab runner. Everything here was found by running the recipe exactly as printed, on a detached HEAD, rather than by reading it.
The recipe never reached the API
A CI runner checks out a detached HEAD. There, git branch --show-current succeeds and prints an empty string — so the || fallbacks in get_current_branch were never reached, and validate_args rejected the empty file tag before a single request went out.
recipe as printed: EXIT=1 on disk: en.json <- nothing translated
same, with --file-tag-name: EXIT=0 on disk: de.json en.json fr.json
An empty answer is now treated as no answer: the runner's own branch variable is consulted (CI_COMMIT_REF_NAME, GITHUB_REF_NAME, BITBUCKET_BRANCH, BRANCH_NAME, CIRCLE_BRANCH), then main. That fixes the printed recipe, the standalone snippet, and every other CI whose checkout is detached — Bitbucket Pipelines and the Jenkins git plugin included.
The merge request carried the CLI itself
The recipe curled ptc-cli.sh into the project root and ran git add -A, so every merge request contained the script plus whatever an earlier job step had dirtied:
what it committed: .ptc-config.yml dist/bundle.js locales/de.json package-lock.json ptc-cli.sh
what it should: locales/de.json
And because that download was always a new file, git diff --cached --quiet never short-circuited — each run force-updated the merge request even when no translation had changed.
The download now goes to /tmp, and a new flag records what the run actually wrote:
--written-manifest FILE every written path, NUL-separated, repository-root-relative
which the recipe hands to git add --pathspec-from-file=FILE --pathspec-file-nul. git reads the file itself, so the recipe needs no arrays and no word splitting — its shell is busybox sh, since alpine has no bash until before_script installs it.
Two behaviours shaped that design, both verified in alpine:3.22 (git 2.49.1): an ignored path in the manifest makes git add exit 1 while still staging the rest, so the recipe needs || true or GitLab aborts the job; and a path holding a glob metacharacter stages the wrong file — messages[1].json also staged the caller's messages1.json — unless written as :(literal).
The unpack filter dropped documented file types
find "$dir" -type f -name "*.json" -o -name "*.po" -o ...
-o binds looser than the implicit -a, so -type f applied only to the first -name: a directory named *.po matched and was moved wholesale. The alternatives are now grouped, and the list covers what the docs promise — .php and .properties were both documented and both silently never reached disk. .xml, .strings and .resx are covered too.
[skip ci] was the wrong loop guard
GitLab honours it by creating no pipeline at all. That silenced the translation merge request's own pipeline — leaving the translations untested and, with "Pipelines must succeed" enabled, unmergeable — and a squash carried the marker into the default branch, silencing the whole project. The guard moved to rules:, which GitLab evaluates against the commit message without suppressing anything.
How this was checked
tests/test-ci-recipe.sh runs the printed recipe end to end against a mock PTC API, with only curl and git push shimmed, and asserts what the merge request would contain: translation on a detached HEAD; only translations committed, with the fixture dirtying dist/bundle.js and package-lock.json first; the CLI neither committed nor left behind; no push when a second identical run writes nothing; and the shape of the push itself.
The detached-HEAD behaviour is now pinned by a test that previously documented it as a known defect.
Verifying the download
sha256 d5b1b2c62c530f43d76aa924e399548379369e458955a31bd11539580d71428d ptc-cli.sh
curl -fsSL https://raw.githubusercontent.com/OnTheGoSystems/ptc-cli/v1.0.5/ptc-cli.sh -o ptc-cli.sh
shasum -a 256 ptc-cli.sh # sha256sum on Linux9 suites, 0 failures, on bash 3.2 (macOS) and on current bash (Linux).
v1 points here. v1.0.4 and earlier are unchanged if you pin them.