scratch-l10n 6.1.102 → 6.1.103
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scratch-l10n",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.103",
|
|
4
4
|
"description": "Localization for the Scratch 3.0 components",
|
|
5
5
|
"main": "./dist/l10n.js",
|
|
6
6
|
"browser": "./src/index.mjs",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"lodash.defaultsdeep": "4.6.1",
|
|
56
56
|
"mkdirp": "3.0.1",
|
|
57
57
|
"transifex": "1.6.6",
|
|
58
|
-
"tsx": "4.23.
|
|
58
|
+
"tsx": "4.23.4"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@babel/cli": "7.29.7",
|
|
@@ -61,16 +61,22 @@ const DRY_RUN = !!process.env.DRY_RUN && process.env.DRY_RUN !== '0' && process.
|
|
|
61
61
|
/**
|
|
62
62
|
* Wall-clock budget for a single run, in minutes (override with `HELP_SYNC_MAX_MINUTES`). When it is
|
|
63
63
|
* exceeded the sync stops starting new writes, persists the baseline for everything that finished, and
|
|
64
|
-
* exits cleanly.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
64
|
+
* exits cleanly. Durability across a cancellation is already handled by the incremental baseline flush
|
|
65
|
+
* plus the workflow's save-on-cancel step, so this budget is the graceful path, not the safety net:
|
|
66
|
+
* exiting cleanly under the step's hard `timeout-minutes` lets the run print its failure summary, exit
|
|
67
|
+
* non-zero on genuine errors, and finish as a success rather than a cancellation -- so real problems
|
|
68
|
+
* surface (and alert) while the baseline is still converging instead of being masked by a timeout.
|
|
68
69
|
*/
|
|
69
70
|
const RUN_TIME_BUDGET_MS = (() => {
|
|
70
71
|
const minutes = Number(process.env.HELP_SYNC_MAX_MINUTES)
|
|
71
72
|
return (Number.isFinite(minutes) && minutes > 0 ? minutes : 24) * 60_000
|
|
72
73
|
})()
|
|
73
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* When this run began, used to enforce {@link RUN_TIME_BUDGET_MS}. Each sync phase (`pull:help:names`,
|
|
76
|
+
* `pull:help:articles`) runs as its own job step with its own step-level `timeout-minutes`, so this
|
|
77
|
+
* per-process clock starts alongside that step's timeout: the soft budget sits just under it, stopping
|
|
78
|
+
* new writes and exiting cleanly (reporting failures) before the step is hard-killed.
|
|
79
|
+
*/
|
|
74
80
|
const runStartedAt = Date.now()
|
|
75
81
|
/** Count of changed, supported pairs left unattempted because the run hit its time budget. */
|
|
76
82
|
let deferredForBudget = 0
|
|
@@ -192,6 +198,49 @@ const markSynced = (resource: string, locale: string): void => {
|
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
200
|
|
|
201
|
+
/** Minimum interval between incremental baseline flushes, to bound both progress loss and write churn. */
|
|
202
|
+
const BASELINE_FLUSH_INTERVAL_MS = 15_000
|
|
203
|
+
/** When the baseline was last flushed to disk, used to debounce {@link maybeFlushBaseline}. */
|
|
204
|
+
let lastBaselineFlushAt = 0
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Merge the pairs synced so far into the baseline and persist it to disk. A no-op in a dry run (which
|
|
208
|
+
* must never mutate the baseline) and before change detection is initialized: a script that calls
|
|
209
|
+
* saveItem without initChangeDetection (the locale-debug pull) leaves baseline/currentStats empty, and
|
|
210
|
+
* persisting then would clobber the real baseline with an empty file, forcing the next sync to re-sync
|
|
211
|
+
* everything. Safe to call from concurrently-running saveItem tasks: JS is single-threaded and
|
|
212
|
+
* {@link saveBaseline} writes atomically, so calls cannot interleave a partial file.
|
|
213
|
+
*/
|
|
214
|
+
const flushBaseline = (): void => {
|
|
215
|
+
if (DRY_RUN || !changeDetectionReady) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
for (const [key, timestamp] of syncedPairs) {
|
|
219
|
+
baseline.set(key, timestamp)
|
|
220
|
+
}
|
|
221
|
+
saveBaseline(baseline)
|
|
222
|
+
lastBaselineFlushAt = Date.now()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Persist the baseline if enough time has passed since the last flush. Called as the sync makes
|
|
227
|
+
* progress so a run cancelled mid-sync (for example by its phase step's timeout) still keeps what it
|
|
228
|
+
* finished on disk, letting the workflow's save-on-cancel step cache it and the next run continue
|
|
229
|
+
* instead of restarting a full sync. The graceful path still calls {@link flushBaseline} once more at
|
|
230
|
+
* the end via {@link finalizeSync}.
|
|
231
|
+
*/
|
|
232
|
+
const maybeFlushBaseline = (): void => {
|
|
233
|
+
// Nothing to persist in a dry run or before change detection is initialized, so skip the debounce
|
|
234
|
+
// entirely rather than calling flushBaseline (a no-op there) on every batch. flushBaseline keeps the
|
|
235
|
+
// same guards for the finalizeSync path.
|
|
236
|
+
if (DRY_RUN || !changeDetectionReady) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
if (Date.now() - lastBaselineFlushAt >= BASELINE_FLUSH_INTERVAL_MS) {
|
|
240
|
+
flushBaseline()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
195
244
|
/**
|
|
196
245
|
* Finish the run: persist the advanced baseline (unless this is a dry run) and print the failure
|
|
197
246
|
* summary. Call once, after all saves have completed.
|
|
@@ -203,10 +252,7 @@ export const finalizeSync = (): void => {
|
|
|
203
252
|
`${plannedWrites} Freshdesk write(s). No writes were issued and the baseline was left unchanged.`,
|
|
204
253
|
)
|
|
205
254
|
} else {
|
|
206
|
-
|
|
207
|
-
baseline.set(key, timestamp)
|
|
208
|
-
}
|
|
209
|
-
saveBaseline(baseline)
|
|
255
|
+
flushBaseline()
|
|
210
256
|
console.log(`Synced ${syncedPairs.size} (resource, locale) pair(s); baseline now holds ${baseline.size} entries.`)
|
|
211
257
|
}
|
|
212
258
|
if (deferredForBudget > 0) {
|
|
@@ -537,5 +583,8 @@ export const saveItem = async (item: TransifexResourceObject, languages: string[
|
|
|
537
583
|
}
|
|
538
584
|
}),
|
|
539
585
|
)
|
|
586
|
+
// Persist progress periodically so a run cancelled mid-sync keeps what it finished (see
|
|
587
|
+
// maybeFlushBaseline); the interval debounce keeps this from writing on every batch.
|
|
588
|
+
maybeFlushBaseline()
|
|
540
589
|
}
|
|
541
590
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* The file location is overridable via `HELP_SYNC_BASELINE_FILE` so the persistence mechanism (a
|
|
9
9
|
* committed repo file vs. a restored CI cache) can change without touching this code.
|
|
10
10
|
*/
|
|
11
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs'
|
|
12
12
|
import { dirname } from 'path'
|
|
13
13
|
import { messageOf } from './errors.mts'
|
|
14
14
|
|
|
@@ -42,7 +42,11 @@ export const loadBaseline = (path?: string): SyncBaseline => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Persist the baseline to disk, sorted by key for a stable, diff-friendly file.
|
|
45
|
+
* Persist the baseline to disk, sorted by key for a stable, diff-friendly file. The write is atomic
|
|
46
|
+
* (write a temp file, then rename over the target) because the sync flushes the baseline repeatedly
|
|
47
|
+
* as it makes progress and relies on the file surviving an abrupt kill (a job timeout mid-write). A
|
|
48
|
+
* partial `writeFileSync` would otherwise leave a truncated file that {@link loadBaseline} discards as
|
|
49
|
+
* corrupt, throwing away every pair synced so far. `renameSync` within the same directory is atomic.
|
|
46
50
|
* @param baseline - the baseline to write
|
|
47
51
|
* @param path - override for the baseline file location; defaults to `HELP_SYNC_BASELINE_FILE` or {@link DEFAULT_BASELINE_PATH}
|
|
48
52
|
*/
|
|
@@ -53,5 +57,7 @@ export const saveBaseline = (baseline: SyncBaseline, path?: string): void => {
|
|
|
53
57
|
for (const key of [...baseline.keys()].sort()) {
|
|
54
58
|
sorted[key] = baseline.get(key) ?? null
|
|
55
59
|
}
|
|
56
|
-
|
|
60
|
+
const tempFile = `${file}.tmp`
|
|
61
|
+
writeFileSync(tempFile, JSON.stringify(sorted, null, 2) + '\n')
|
|
62
|
+
renameSync(tempFile, file)
|
|
57
63
|
}
|