@chesterbrains/translify-cli 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,9 +309,97 @@ When the project has **Require review** turned on in Translify:
|
|
|
309
309
|
- `publish` refuses when a translation that would go live has a placeholder error (a missing or extra placeholder, invalid ICU, a printf type mismatch). Nothing is published, the refused translations are listed like `lint` lists them, and the command exits 6. Warnings never block. On an environment with **Publish approved text only**, only approved translations count: one carried over from its last approved text never blocks. `--allow-errors` publishes anyway and reports how many went out with errors; the publish is recorded in the activity log with the override.
|
|
310
310
|
- `pull --only-approved` writes the approved text from the working copy and leaves out translations that were never approved, for building a release bundle without publishing. `status --only-approved` compares against the same. It works with `--from working` only: environments already apply the gate.
|
|
311
311
|
|
|
312
|
+
## Webhooks
|
|
313
|
+
|
|
314
|
+
A project's admins can add webhooks under **Integrations**. Translify calls them with `publication.created` every time
|
|
315
|
+
an environment's live batch changes: a publish, a rollback or an unpublish (`batch.kind` says which). A publish whose
|
|
316
|
+
result is identical to what is live emits nothing. `ping` is the test event, sent by "Send test" and by the
|
|
317
|
+
verification dispatch that saves a GitHub webhook.
|
|
318
|
+
|
|
319
|
+
Deliveries are made by a worker, not by the request that published: five attempts, waiting 1 minute, 5 minutes,
|
|
320
|
+
30 minutes and 2 hours between them, then the delivery is `DEAD` and can be resent from the app. The log keeps 30
|
|
321
|
+
days. The delivery id is the same on every attempt (`X-Translify-Delivery` and the payload's `id`), so a receiver that
|
|
322
|
+
answers slowly and gets the retry can dedupe on it. Answer 2xx as soon as the request is accepted and do the work
|
|
323
|
+
afterwards; the sender waits 10 seconds.
|
|
324
|
+
|
|
325
|
+
### Payload
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"id": "<delivery id>",
|
|
330
|
+
"event": "publication.created",
|
|
331
|
+
"createdAt": "2026-10-25T14:02:11.000Z",
|
|
332
|
+
"project": { "id": "…", "slug": "shop", "name": "Shop" },
|
|
333
|
+
"environment": { "id": "…", "slug": "production", "name": "Production", "requireApproved": true },
|
|
334
|
+
"batch": {
|
|
335
|
+
"id": "…", "seq": 41, "kind": "PUBLISH",
|
|
336
|
+
"publishedCount": 1280, "addedCount": 3, "changedCount": 14, "removedCount": 0,
|
|
337
|
+
"withheldCount": 0, "errorCount": 0
|
|
338
|
+
},
|
|
339
|
+
"actor": { "type": "user", "id": "…", "name": "Anna Rossi" }
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`addedCount`, `changedCount` and `removedCount` are the batch's diff against the previous live batch: a first publish
|
|
344
|
+
has everything in `addedCount`; an `UNPUBLISH` has `publishedCount: 0` and everything that went offline in
|
|
345
|
+
`removedCount`. "Did anything change" is the sum of the three. `withheldCount` is what a `requireApproved` gate kept
|
|
346
|
+
back, `errorCount` the cells published with placeholder errors (`--allow-errors`). `actor.type` is `user`, `apiKey`
|
|
347
|
+
(a CLI publish; `name` is the key's name) or `system`. A `ping` carries `id`, `event`, `createdAt`, `project` and
|
|
348
|
+
`webhook { id, target, destination }` only.
|
|
349
|
+
|
|
350
|
+
### Verify a delivery
|
|
351
|
+
|
|
352
|
+
Generic webhooks `POST` the payload as `application/json` to an `https://` URL on a public host, with:
|
|
353
|
+
|
|
354
|
+
| Header | Value |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| `X-Translify-Event` | `publication.created` or `ping` |
|
|
357
|
+
| `X-Translify-Delivery` | the delivery id, identical on every attempt |
|
|
358
|
+
| `X-Translify-Timestamp` | unix seconds when the attempt was sent |
|
|
359
|
+
| `X-Translify-Signature` | `v1=<hex hmac-sha256(secret, "<timestamp>.<body>")>` |
|
|
360
|
+
| `User-Agent` | `translify-webhooks/1` |
|
|
361
|
+
|
|
362
|
+
The signing secret (`whsec_…`) is shown once when the webhook is created. Verify over the raw body, compare in constant
|
|
363
|
+
time and refuse a timestamp more than 300 s (5 minutes) away from now:
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
367
|
+
|
|
368
|
+
export function verify(rawBody, headers, secret) {
|
|
369
|
+
const timestamp = Number(headers['x-translify-timestamp']);
|
|
370
|
+
if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
|
|
371
|
+
const expected = `v1=${createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')}`;
|
|
372
|
+
const given = Buffer.from(String(headers['x-translify-signature'] ?? ''));
|
|
373
|
+
const wanted = Buffer.from(expected);
|
|
374
|
+
return given.length === wanted.length && timingSafeEqual(given, wanted);
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### GitHub
|
|
379
|
+
|
|
380
|
+
A webhook with the **GitHub** target sends a
|
|
381
|
+
[`repository_dispatch`](https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event) instead:
|
|
382
|
+
`POST https://api.github.com/repos/<owner>/<repo>/dispatches` with `event_type` `translify.publication` (or
|
|
383
|
+
`translify.ping`) and the payload above as `client_payload`. No signature: GitHub authenticates the token.
|
|
384
|
+
|
|
385
|
+
The token is a fine-grained personal access token with **Contents: Read and write** on that one repository (that is
|
|
386
|
+
the permission `repository_dispatch` needs). Translify verifies it with a `translify.ping` dispatch before saving and
|
|
387
|
+
refuses a token or repository GitHub rejects; the expiry GitHub reports is shown in the list from 14 days out.
|
|
388
|
+
|
|
389
|
+
[`recipes/github-actions/translify-pull-on-publish.yml`](https://github.com/chesterbrains/translify-cli/blob/main/recipes/github-actions/translify-pull-on-publish.yml)
|
|
390
|
+
is the workflow that closes the loop: on `translify.publication` it runs `translify pull --from env:<slug>` for the
|
|
391
|
+
environment in the payload, commits, opens a pull request whose body carries the publish summary (batch, counts,
|
|
392
|
+
actor), runs your build and merges. Two things to set after copying it: the repository setting **Allow GitHub Actions
|
|
393
|
+
to create and approve pull requests** (Settings → Actions → General; without it the job token cannot open the PR),
|
|
394
|
+
and the environment the repository follows: a literal in the job's `if:`, and the webhook limited to that environment
|
|
395
|
+
in Translify, since every environment pulls into the same files. The workflow builds and merges inside the
|
|
396
|
+
same run because a pull request opened with `GITHUB_TOKEN` never triggers other workflows. The
|
|
397
|
+
[translify-examples](https://github.com/chesterbrains/translify-examples/blob/main/.github/workflows/translify-pull-on-publish.yml)
|
|
398
|
+
repository runs this workflow against the demo project.
|
|
399
|
+
|
|
312
400
|
## CI (GitHub Actions)
|
|
313
401
|
|
|
314
|
-
Store the key in the repository secrets as `TRANSLIFY_SECRET_KEY`.
|
|
402
|
+
Store the key in the repository secrets as `TRANSLIFY_SECRET_KEY`. Three ready-made workflows live in
|
|
315
403
|
[`recipes/github-actions/`](https://github.com/chesterbrains/translify-cli/tree/main/recipes/github-actions); copy the
|
|
316
404
|
one you need to `.github/workflows/` as it is.
|
|
317
405
|
|
|
@@ -323,8 +411,11 @@ one you need to `.github/workflows/` as it is.
|
|
|
323
411
|
- [`translify-release.yml`](https://github.com/chesterbrains/translify-cli/blob/main/recipes/github-actions/translify-release.yml)
|
|
324
412
|
is for apps that bundle translations at build time: `lint`, then `pull`, your build, then `publish` to the environment
|
|
325
413
|
chosen in the Actions tab (or on a tag). Key scopes: `PULL` and `PUBLISH`.
|
|
414
|
+
- [`translify-pull-on-publish.yml`](https://github.com/chesterbrains/translify-cli/blob/main/recipes/github-actions/translify-pull-on-publish.yml)
|
|
415
|
+
runs when Translify's GitHub webhook announces a publish: `pull --from env:<slug>` for the published environment,
|
|
416
|
+
commit, a pull request with the publish summary, your build, merge. Key scope: `PULL`. Setup in [Webhooks](#webhooks).
|
|
326
417
|
|
|
327
|
-
|
|
418
|
+
All three use `npx -y translify`, so they follow every 0.x release; pin a version (`npx -y @chesterbrains/translify-cli@0.6`) if a breaking
|
|
328
419
|
release must not turn the workflow red on its own.
|
|
329
420
|
|
|
330
421
|
Fail a pull request when local files drift from Translify:
|
package/dist/version.js
CHANGED