@chesterbrains/translify-cli 0.7.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 +108 -13
- package/dist/commands/push.js +10 -10
- package/dist/http.js +1 -1
- package/dist/index.js +1 -1
- package/dist/render.js +22 -22
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,34 +257,38 @@ Options:
|
|
|
257
257
|
- **Source locale: your files win.** Existing source translations are overwritten with what is in the file.
|
|
258
258
|
- **Target locales: Translify fills only the gaps.** Keys that already have a translation are left alone and counted as skipped.
|
|
259
259
|
- `--overwrite-targets` also overwrites existing target translations.
|
|
260
|
-
- `--prune`
|
|
260
|
+
- `--prune` moves keys that no longer exist in your source files to the Translify trash, together with their translations. They can be restored for 30 days. See [Prune safety](#prune-safety).
|
|
261
261
|
- `--yes` skips the confirmation prompt for the two destructive flags. Without a terminal and without `--yes`, a destructive push is refused and nothing changes.
|
|
262
262
|
- `--dry-run` shows the effect of any combination and writes nothing.
|
|
263
263
|
|
|
264
264
|
## Prune safety
|
|
265
265
|
|
|
266
|
-
`translify push --prune` is the only command that
|
|
266
|
+
`translify push --prune` is the only command that removes keys. It never deletes on the spot: it archives them. What
|
|
267
|
+
it archives, and what stops it:
|
|
267
268
|
|
|
268
269
|
- **Only the namespaces in this push.** A key is an orphan when its namespace is in the push and its source file no
|
|
269
270
|
longer has it. Namespaces you did not push (another rule, another repository, `--namespace` left them out) are never
|
|
270
271
|
touched.
|
|
271
|
-
- **
|
|
272
|
-
|
|
272
|
+
- **Archived, not deleted.** A pruned key goes to the trash in Translify, with its translations and their history.
|
|
273
|
+
It is deleted for good 30 days later. Until then, restore it from the trash on the project's Translations tab, or
|
|
274
|
+
push a source file that names it again: the push restores it in place and reports it as restored.
|
|
275
|
+
- **Live keys too.** A key that is live in an environment is archived like any other, but the
|
|
276
|
+
environment keeps serving it until its next publish, which drops it. Restore it before then if it should stay.
|
|
273
277
|
- **Never a whole namespace by accident.** If a source file in the push has no entries, the push is refused
|
|
274
|
-
(`VALIDATION_FAILED` with `emptySourcePrune`) instead of
|
|
278
|
+
(`VALIDATION_FAILED` with `emptySourcePrune`) instead of archiving every key in that namespace. Emptying a namespace
|
|
275
279
|
is done in the Translify web app.
|
|
276
|
-
- **Exactly the list you confirmed.** The CLI always runs a dry run first, shows the keys it would
|
|
280
|
+
- **Exactly the list you confirmed.** The CLI always runs a dry run first, shows the keys it would archive and asks
|
|
277
281
|
for confirmation. The real push sends a digest of that list; if the server's list differs by then (keys added,
|
|
278
|
-
removed or
|
|
282
|
+
removed, archived or restored in between), it refuses with `ORPHANS_CHANGED` and writes nothing. A server too old to return
|
|
279
283
|
the digest makes `--prune` refuse.
|
|
280
284
|
- **No silent prune in CI.** Without a terminal, `--prune` is refused unless you pass `--yes`.
|
|
281
|
-
- **Atomic.** The whole push,
|
|
285
|
+
- **Atomic.** The whole push, archives and restores included, is one transaction: if anything fails, nothing is written.
|
|
282
286
|
|
|
283
287
|
`--yes` confirms whatever that run's dry run lists, so in CI review the list before it runs unattended. One way:
|
|
284
288
|
show the orphans on pull requests and prune on `main`.
|
|
285
289
|
|
|
286
290
|
```yaml
|
|
287
|
-
# on pull_request: list what a prune would
|
|
291
|
+
# on pull_request: list what a prune would archive, change nothing
|
|
288
292
|
- run: npx translify push --prune --dry-run
|
|
289
293
|
env:
|
|
290
294
|
TRANSLIFY_SECRET_KEY: ${{ secrets.TRANSLIFY_SECRET_KEY }}
|
|
@@ -305,9 +309,97 @@ When the project has **Require review** turned on in Translify:
|
|
|
305
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.
|
|
306
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.
|
|
307
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
|
+
|
|
308
400
|
## CI (GitHub Actions)
|
|
309
401
|
|
|
310
|
-
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
|
|
311
403
|
[`recipes/github-actions/`](https://github.com/chesterbrains/translify-cli/tree/main/recipes/github-actions); copy the
|
|
312
404
|
one you need to `.github/workflows/` as it is.
|
|
313
405
|
|
|
@@ -319,8 +411,11 @@ one you need to `.github/workflows/` as it is.
|
|
|
319
411
|
- [`translify-release.yml`](https://github.com/chesterbrains/translify-cli/blob/main/recipes/github-actions/translify-release.yml)
|
|
320
412
|
is for apps that bundle translations at build time: `lint`, then `pull`, your build, then `publish` to the environment
|
|
321
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).
|
|
322
417
|
|
|
323
|
-
|
|
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
|
|
324
419
|
release must not turn the workflow red on its own.
|
|
325
420
|
|
|
326
421
|
Fail a pull request when local files drift from Translify:
|
|
@@ -364,7 +459,7 @@ The server's error `code` decides the exit code first; the HTTP status is only u
|
|
|
364
459
|
| `VALIDATION_FAILED` | 1 | Nothing was written. The output lists `file:line key: reason`; fix those entries and push again. |
|
|
365
460
|
| `LOCALE_NOT_FOUND` | 1 | A locale in your files is not enabled in the project. Enable it in Translify, or map it with `locales` in `translify.json`. |
|
|
366
461
|
| `NAMESPACE_NOT_FOUND` | 1 | The namespace does not exist in the project. Create it in Translify or fix the `--namespace` / pattern. |
|
|
367
|
-
| `VALIDATION_FAILED` with `emptySourcePrune` | 1 | `--prune` was refused because a source file in the push has no entries (it would
|
|
462
|
+
| `VALIDATION_FAILED` with `emptySourcePrune` | 1 | `--prune` was refused because a source file in the push has no entries (it would archive the whole namespace). Push the real source file, or drop `--prune`; emptying a namespace is done in the Translify web app. |
|
|
368
463
|
| `JSON_STYLE_REQUIRES_JSON` | 1 | `jsonStyle` only applies to rules with `"format": "json"`. Remove it from the other rules. |
|
|
369
464
|
| `BAD_REQUEST` | 1 | The request was malformed. Re-run with `--debug` for the server's message. |
|
|
370
465
|
| `ENVIRONMENT_NOT_FOUND` / `ENVIRONMENT_ARCHIVED` | 1 | Check the environment slug passed to `publish` or `--from env:<slug>`, and that it is not archived. |
|
|
@@ -377,7 +472,7 @@ The server's error `code` decides the exit code first; the HTTP status is only u
|
|
|
377
472
|
| `QUOTA_EXCEEDED` | 3 | Your plan limit was reached (usually translation keys). Remove keys, or push a smaller set. |
|
|
378
473
|
| `PUSH_TOO_LARGE` | 4 | Split the push by namespace: `translify push --namespace <ns>`. |
|
|
379
474
|
| `CLI_TOO_OLD` | 4 | Upgrade: `npm i -g @chesterbrains/translify-cli@latest` (or use `npx translify@latest`). |
|
|
380
|
-
| `ORPHANS_CHANGED` | 1 | The orphan list changed between the dry run and the real push (keys were added, removed or
|
|
475
|
+
| `ORPHANS_CHANGED` | 1 | The orphan list changed between the dry run and the real push (keys were added, removed, archived or restored). Nothing was written; run `translify push --prune` again to review the new list. |
|
|
381
476
|
| `REVIEW_DISABLED` | 1 | `push --status draft` or `--status needs-review` on a project without review. Turn on **Require review** in the project settings, or drop `--status`. Nothing was written. |
|
|
382
477
|
| `APPROVED_ONLY_REQUIRES_WORKING` | 1 | `--only-approved` was combined with `--from env:<slug>`. Pull an environment without it; it already holds only what its gate allowed. |
|
|
383
478
|
| `INVALID_CELLS` | 6 | `publish` refused: translations that would go live have placeholder errors. Nothing was published; the output lists them like `lint` does. Fix them in Translify, or re-run with `--allow-errors` to publish anyway (recorded in the activity log). |
|
package/dist/commands/push.js
CHANGED
|
@@ -42,10 +42,11 @@ const buildForm = (uploads, opts, dryRun, prune, pruneDigest) => {
|
|
|
42
42
|
*/
|
|
43
43
|
const describeDamage = async (preview, opts, api) => {
|
|
44
44
|
const damage = [];
|
|
45
|
-
//
|
|
46
|
-
const
|
|
47
|
-
if (opts.prune === true &&
|
|
48
|
-
damage.push(`
|
|
45
|
+
// Every orphan is archived, live ones included: they keep serving until the next publish.
|
|
46
|
+
const archivable = preview.orphans.length;
|
|
47
|
+
if (opts.prune === true && archivable > 0) {
|
|
48
|
+
damage.push(`move ${archivable} key(s) and their translations to the trash (restorable for 30 days in Translify)`);
|
|
49
|
+
}
|
|
49
50
|
if (opts.overwriteTargets === true) {
|
|
50
51
|
const { project } = await api.get('/cli/v1/whoami');
|
|
51
52
|
const overwritten = Object.entries(preview.locales)
|
|
@@ -90,12 +91,12 @@ export async function runPush(opts, deps) {
|
|
|
90
91
|
}
|
|
91
92
|
const preview = await deps.api.post(PUSH, buildForm(uploads, opts, true, opts.prune === true));
|
|
92
93
|
if (opts.prune === true && typeof preview.pruneDigest !== 'string') {
|
|
93
|
-
deps.err('This server cannot confirm a prune (its dry run returned no pruneDigest), so --prune would
|
|
94
|
+
deps.err('This server cannot confirm a prune (its dry run returned no pruneDigest), so --prune would archive without a bound. ' +
|
|
94
95
|
'Nothing was changed. Upgrade the Translify server, or push without --prune.');
|
|
95
96
|
return EXIT.failed;
|
|
96
97
|
}
|
|
97
98
|
const damage = await describeDamage(preview, opts, deps.api);
|
|
98
|
-
// Nothing would be
|
|
99
|
+
// Nothing would be archived or overwritten: no question to ask (F19).
|
|
99
100
|
if (damage.length > 0) {
|
|
100
101
|
// With --json, stdout is reserved for the final document.
|
|
101
102
|
(opts.json === true ? deps.err : deps.out)(renderPush(preview));
|
|
@@ -111,10 +112,9 @@ export async function runPush(opts, deps) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
|
-
// With
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
const prune = opts.prune === true && deletable > 0;
|
|
115
|
+
// With orphans the real push sends the digest of the list the user confirmed: if the server's orphan
|
|
116
|
+
// set differs it answers 409 ORPHANS_CHANGED and writes nothing. With none, there is nothing to prune.
|
|
117
|
+
const prune = opts.prune === true && preview.orphans.length > 0;
|
|
118
118
|
const res = await deps.api.post(PUSH, buildForm(uploads, opts, false, prune, prune ? preview.pruneDigest : undefined));
|
|
119
119
|
show(res, preview);
|
|
120
120
|
return EXIT.ok;
|
package/dist/http.js
CHANGED
|
@@ -85,7 +85,7 @@ const describeBody = (status, body) => {
|
|
|
85
85
|
case 'NAMESPACE_NOT_FOUND':
|
|
86
86
|
return `Unknown namespace(s): ${list(body.namespaces)}.`;
|
|
87
87
|
case 'ORPHANS_CHANGED':
|
|
88
|
-
return 'The orphans changed since the dry run (keys were added, removed or
|
|
88
|
+
return 'The orphans changed since the dry run (keys were added, removed, archived or restored meanwhile). Nothing was written. Run translify push --prune again to review the new list.';
|
|
89
89
|
case 'REVIEW_DISABLED':
|
|
90
90
|
return 'Review is off for this project, so --status draft or needs-review cannot be used. Turn on "Require review" in the project settings, or drop --status.';
|
|
91
91
|
case 'APPROVED_ONLY_REQUIRES_WORKING':
|
package/dist/index.js
CHANGED
|
@@ -89,7 +89,7 @@ program
|
|
|
89
89
|
.description('Upload source and target files')
|
|
90
90
|
.option('--dry-run', 'show what would change, write nothing')
|
|
91
91
|
.option('--overwrite-targets', 'overwrite existing target-locale translations')
|
|
92
|
-
.option('--prune', '
|
|
92
|
+
.option('--prune', 'move keys missing from your source files to the Translify trash (restorable for 30 days); archives exactly the keys you confirmed, and refuses if anything changed meanwhile (re-run to review)')
|
|
93
93
|
.option('-y, --yes', 'confirm destructive flags without prompting')
|
|
94
94
|
.option('--namespace <ns>', 'only this namespace')
|
|
95
95
|
.addOption(new Option('--status <status>', 'status target-locale cells land in (default: needs-review when the project requires review, approved otherwise)').choices(PUSH_STATUSES))
|
package/dist/render.js
CHANGED
|
@@ -14,8 +14,8 @@ export function summarizePush(res) {
|
|
|
14
14
|
skippedFilled: 0,
|
|
15
15
|
unknownKeys: 0,
|
|
16
16
|
orphans: res.orphans.length,
|
|
17
|
-
publishedOrphans: res.orphans.filter((orphan) => orphan.published).length,
|
|
18
17
|
pruned: res.pruned,
|
|
18
|
+
keysRestored: res.keysRestored ?? 0,
|
|
19
19
|
staleReset: res.staleReset ?? 0,
|
|
20
20
|
};
|
|
21
21
|
for (const counts of Object.values(res.locales)) {
|
|
@@ -47,36 +47,36 @@ export function renderPush(res, view = {}) {
|
|
|
47
47
|
if (stale > 0) {
|
|
48
48
|
lines.push(pc.yellow(`${stale} approved translation(s) ${res.dryRun ? 'would go' : 'went'} back to review because their source text changed.`));
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
lines.push(` …and ${list.length - MAX_ORPHANS} more`);
|
|
59
|
-
};
|
|
50
|
+
const restored = res.keysRestored ?? 0;
|
|
51
|
+
if (restored > 0) {
|
|
52
|
+
lines.push(pc.green(`${res.dryRun ? 'Would restore' : 'Restored'} ${restored} key(s) from the trash.`));
|
|
53
|
+
}
|
|
54
|
+
// A prune archives every orphan (it goes to the trash, purged 30 days later); `pruned` is what a dry
|
|
55
|
+
// run would archive or a real push did. A real prune can archive fewer than it listed when some
|
|
56
|
+
// orphans were archived from the web app meanwhile.
|
|
57
|
+
const listed = res.orphans;
|
|
60
58
|
if (listed.length > 0) {
|
|
59
|
+
const meanwhile = listed.length - res.pruned;
|
|
61
60
|
const header = res.pruned === 0
|
|
62
61
|
? `${listed.length} key(s) in Translify are not in your source files:`
|
|
63
62
|
: res.dryRun
|
|
64
|
-
? `${listed.length} key(s) not in your source files would be
|
|
65
|
-
: `
|
|
63
|
+
? `${listed.length} key(s) not in your source files would be moved to the trash:`
|
|
64
|
+
: `Moved ${res.pruned} key(s) that were not in your source files to the trash (restorable for 30 days${meanwhile > 0 ? `; ${meanwhile} already archived meanwhile` : ''}):`;
|
|
66
65
|
lines.push(pc.yellow(` ${header}`));
|
|
67
|
-
|
|
66
|
+
for (const orphan of listed.slice(0, MAX_ORPHANS))
|
|
67
|
+
lines.push(` ${orphan.namespace}:${orphan.key}`);
|
|
68
|
+
if (listed.length > MAX_ORPHANS)
|
|
69
|
+
lines.push(` …and ${listed.length - MAX_ORPHANS} more`);
|
|
68
70
|
if (res.pruned === 0 && view.prune !== true)
|
|
69
|
-
lines.push(pc.dim(' Use --prune to
|
|
71
|
+
lines.push(pc.dim(' Use --prune to move them to the trash.'));
|
|
70
72
|
}
|
|
71
|
-
if (
|
|
72
|
-
lines.push(pc.yellow(` ${
|
|
73
|
-
names(published);
|
|
73
|
+
if (res.pruned > 0 && res.dryRun) {
|
|
74
|
+
lines.push(pc.yellow(` Would move ${res.pruned} key(s) to the trash (restorable for 30 days).`));
|
|
74
75
|
}
|
|
75
|
-
if (res.pruned > 0 && res.dryRun)
|
|
76
|
-
lines.push(pc.yellow(` Would delete ${res.pruned} key(s).`));
|
|
77
76
|
// A real prune's orphan header already says it; repeat it only when there was no list.
|
|
78
|
-
if (res.pruned > 0 && !res.dryRun && listed.length === 0)
|
|
79
|
-
lines.push(pc.
|
|
77
|
+
if (res.pruned > 0 && !res.dryRun && listed.length === 0) {
|
|
78
|
+
lines.push(pc.yellow(` Moved ${res.pruned} key(s) to the Translify trash (restorable for 30 days).`));
|
|
79
|
+
}
|
|
80
80
|
return lines.join('\n');
|
|
81
81
|
}
|
|
82
82
|
const MAX_VALUE = 80;
|
package/dist/version.js
CHANGED