champollion 0.3.3
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/LICENSE +133 -0
- package/README.md +387 -0
- package/bin/cli.js +278 -0
- package/index.js +135 -0
- package/lib/api-key.js +127 -0
- package/lib/autofix.js +432 -0
- package/lib/bridge/method_bridge.py +430 -0
- package/lib/card-source-resolution.mjs +284 -0
- package/lib/cards/cache.js +169 -0
- package/lib/cards/env.js +82 -0
- package/lib/cards/fetch-card-child.js +38 -0
- package/lib/cards/reader.js +435 -0
- package/lib/cards/refresh.js +111 -0
- package/lib/cards/remote.js +387 -0
- package/lib/cldf-export.mjs +540 -0
- package/lib/cldf-terms.mjs +62 -0
- package/lib/command-help.js +790 -0
- package/lib/commands/audit.js +49 -0
- package/lib/commands/card.js +454 -0
- package/lib/commands/doctor.js +559 -0
- package/lib/commands/fonts.js +489 -0
- package/lib/commands/help.js +91 -0
- package/lib/commands/init.js +1259 -0
- package/lib/commands/integrity.js +148 -0
- package/lib/commands/leaderboard.js +478 -0
- package/lib/commands/lint.js +30 -0
- package/lib/commands/models.js +177 -0
- package/lib/commands/plugin.js +103 -0
- package/lib/commands/provenance.js +45 -0
- package/lib/commands/recommend.js +75 -0
- package/lib/commands/register-corpus.js +678 -0
- package/lib/commands/repair-script.js +42 -0
- package/lib/commands/seal-corpus.js +355 -0
- package/lib/commands/seo.js +72 -0
- package/lib/commands/serve.js +147 -0
- package/lib/commands/status.js +265 -0
- package/lib/commands/submit.js +332 -0
- package/lib/commands/sync.js +89 -0
- package/lib/commands/tm.js +573 -0
- package/lib/commands/verify.js +39 -0
- package/lib/commands/watch.js +20 -0
- package/lib/commands/wrap.js +138 -0
- package/lib/commands/xliff.js +327 -0
- package/lib/commercial-eligibility.js +235 -0
- package/lib/concurrent.js +87 -0
- package/lib/config.js +523 -0
- package/lib/contamination-lane.js +76 -0
- package/lib/content-sync.js +731 -0
- package/lib/content.js +733 -0
- package/lib/corpus-registration.mjs +608 -0
- package/lib/cost-report.js +346 -0
- package/lib/diff.js +155 -0
- package/lib/docusaurus-sync.js +1256 -0
- package/lib/flatten.js +55 -0
- package/lib/format.js +954 -0
- package/lib/hash.js +159 -0
- package/lib/icu.js +473 -0
- package/lib/integrity.js +689 -0
- package/lib/license-gate.mjs +478 -0
- package/lib/license-identify.mjs +229 -0
- package/lib/lint.js +629 -0
- package/lib/method-manifest.js +60 -0
- package/lib/methods/anthropic.js +140 -0
- package/lib/methods/apertium.js +163 -0
- package/lib/methods/api.js +316 -0
- package/lib/methods/base.js +184 -0
- package/lib/methods/content-separator.js +45 -0
- package/lib/methods/deepl.js +426 -0
- package/lib/methods/direct-llm.js +586 -0
- package/lib/methods/external.js +332 -0
- package/lib/methods/fetch-with-retry.js +124 -0
- package/lib/methods/gemini.js +147 -0
- package/lib/methods/google-translate.js +402 -0
- package/lib/methods/http-utils.js +122 -0
- package/lib/methods/libretranslate.js +314 -0
- package/lib/methods/llm-coached.js +670 -0
- package/lib/methods/llm.js +592 -0
- package/lib/methods/local.js +76 -0
- package/lib/methods/microsoft-translator.js +331 -0
- package/lib/methods/openai.js +131 -0
- package/lib/methods/openrouter-client.js +327 -0
- package/lib/methods/openrouter-pricing.js +156 -0
- package/lib/methods/provider-env.js +115 -0
- package/lib/methods/provider-pricing.js +310 -0
- package/lib/methods/tilde.js +150 -0
- package/lib/methods/translated.js +229 -0
- package/lib/methods/translation-error.js +80 -0
- package/lib/models.js +258 -0
- package/lib/no-translate.js +233 -0
- package/lib/output.js +238 -0
- package/lib/pairs.js +547 -0
- package/lib/plugins.js +447 -0
- package/lib/provenance.js +323 -0
- package/lib/recommend.js +648 -0
- package/lib/registers.js +1185 -0
- package/lib/repair-script.js +266 -0
- package/lib/scripts.js +994 -0
- package/lib/seal.mjs +464 -0
- package/lib/sealed-qualifier.mjs +211 -0
- package/lib/security.js +59 -0
- package/lib/segment.js +369 -0
- package/lib/seo.js +275 -0
- package/lib/serve.js +854 -0
- package/lib/string-classify.js +85 -0
- package/lib/submit.mjs +344 -0
- package/lib/sync.js +969 -0
- package/lib/tags/bcp47.js +202 -0
- package/lib/tags/resolve.js +314 -0
- package/lib/terminology.js +111 -0
- package/lib/tm-seed.js +294 -0
- package/lib/tm.js +515 -0
- package/lib/translate-pair.js +197 -0
- package/lib/translate.js +203 -0
- package/lib/types.js +230 -0
- package/lib/validate.js +510 -0
- package/lib/verify.js +451 -0
- package/lib/watch.js +145 -0
- package/lib/xliff.js +184 -0
- package/package.json +93 -0
- package/shared/ATTRIBUTION.md +145 -0
- package/shared/CORPORA-CARDS.md +288 -0
- package/shared/DATA-SOVEREIGNTY.md +500 -0
- package/shared/LANGUAGE-CARD-FIELDS.md +532 -0
- package/shared/card-lint-baseline.json +3189 -0
- package/shared/cards-fallback.json +1 -0
- package/shared/catalogue/card-config.json +6091 -0
- package/shared/catalogue/external-results.json +3888 -0
- package/shared/catalogue/gender-guidance.json +1038 -0
- package/shared/catalogue/method-coverage.json +1751 -0
- package/shared/catalogue/metric-coverage.json +170 -0
- package/shared/catalogue/metric-reliability.json +1 -0
- package/shared/catalogue/register-presets.json +3180 -0
- package/shared/catalogue/vitality-scales.json +55 -0
- package/shared/cldr-index.json +1115 -0
- package/shared/code-bridge.json +253 -0
- package/shared/corpora-cards-v1-reference.md +281 -0
- package/shared/curated-dictionary-flags.json +35 -0
- package/shared/curated-endonyms.json +35 -0
- package/shared/curated-fsts.json +51 -0
- package/shared/curated-orthography-conventions.json +26 -0
- package/shared/curated-sil-resources.json +374 -0
- package/shared/curated-tools.json +41 -0
- package/shared/docent/corpus.json +11333 -0
- package/shared/docent/faq.en.json +564 -0
- package/shared/docent/register-blocks.json +60 -0
- package/shared/docent/system-prompt.md +144 -0
- package/shared/domain-taxonomy.json +35 -0
- package/shared/explainers/glossary.json +2975 -0
- package/shared/explainers/tc-features.json +20112 -0
- package/shared/explainers/term-watchlist.json +147 -0
- package/shared/human-services.json +59 -0
- package/shared/license-corrections.json +261 -0
- package/shared/license-evidence.json +13452 -0
- package/shared/licenses.json +6781 -0
- package/shared/method-registry.json +236 -0
- package/shared/metric-registry.json +620 -0
- package/shared/model-aliases.json +7 -0
- package/shared/schemas/champollion-plugin.schema.json +206 -0
- package/shared/schemas/corpora-card.schema.json +957 -0
- package/shared/schemas/domain-taxonomy.schema.json +64 -0
- package/shared/schemas/external-results.schema.json +314 -0
- package/shared/schemas/human-services.schema.json +90 -0
- package/shared/schemas/language-card.schema.json +1308 -0
- package/shared/schemas/licenses.schema.json +155 -0
- package/shared/schemas/method-card.schema.json +412 -0
- package/shared/schemas/method-registry.schema.json +85 -0
- package/shared/schemas/metric-registry.schema.json +96 -0
- package/shared/schemas/metric-reliability.schema.json +178 -0
- package/shared/schemas/model-aliases.schema.json +27 -0
- package/shared/schemas/source-snapshot.schema.json +96 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repair-script.js — undo script conversions that should never have happened.
|
|
3
|
+
*
|
|
4
|
+
* THE DAMAGE THIS REPAIRS: before 0.3.0, script conversion was unconditional,
|
|
5
|
+
* so every tlh / x-elvish-s / x-kryptonian project had its translations
|
|
6
|
+
* rewritten into Private Use Area codepoints — scripts that are NOT in
|
|
7
|
+
* Unicode and render as nothing without a purpose-built font. A project
|
|
8
|
+
* shipping Latin-transliteration fonts (the common case) displayed blank
|
|
9
|
+
* strings. Worse, the converters silently passed through letters they could
|
|
10
|
+
* not map, leaving values that are half PUA and half Latin.
|
|
11
|
+
*
|
|
12
|
+
* WHAT IT DOES: for every configured target locale whose converter emits PUA
|
|
13
|
+
* and whose script resolution says conversion is OFF, scan the locale files
|
|
14
|
+
* for PUA codepoints and reverse them back to the working script
|
|
15
|
+
* (romanization) via the converter's own table. Values without PUA are never
|
|
16
|
+
* touched.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IT DOES NOT TOUCH:
|
|
19
|
+
* - The Translation Memory — it stores PRE-conversion values, so it was
|
|
20
|
+
* never damaged (verified: translate-pair.js stores before sync converts).
|
|
21
|
+
* - The hash manifest — it hashes SOURCE values, so a repair never
|
|
22
|
+
* triggers re-translation.
|
|
23
|
+
* - Locales whose conversion is opted ON (`script: "Piqd"` etc.) — there
|
|
24
|
+
* the PUA is the deliverable; the scan reports them as skipped.
|
|
25
|
+
* - Real-Unicode conversions (crk Syllabics, sr Cyrillic) — those render
|
|
26
|
+
* everywhere, and "reversing" them would destroy legitimate content.
|
|
27
|
+
*
|
|
28
|
+
* REVERSAL FIDELITY: pIqaD reverses exactly (the map is injective up to the
|
|
29
|
+
* straight/curly apostrophe, both of which restore as '). Tengwar, Cree
|
|
30
|
+
* syllabics and Kryptonian normalise case on the way in, so their reversals
|
|
31
|
+
* are case-lossy — reported per file, never silent. Codepoints from a PUA
|
|
32
|
+
* block the converter does not own are left in place and reported as
|
|
33
|
+
* unreversible (they are damage from something else, and inventing a reading
|
|
34
|
+
* for them would be fabrication).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import fs from 'node:fs';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
import { resolveConfig } from './config.js';
|
|
40
|
+
import { resolvePairs } from './pairs.js';
|
|
41
|
+
import { getLanguageCard } from './registers.js';
|
|
42
|
+
import {
|
|
43
|
+
SCRIPT_CONVERTERS, reverseScript, isPrivateUse, converterKeyForLocale, getConverterInfo,
|
|
44
|
+
} from './scripts.js';
|
|
45
|
+
import {
|
|
46
|
+
readLocaleFile, writeLocaleFile, detectFormatFromDir, detectYAMLStyle, getExtension,
|
|
47
|
+
} from './format.js';
|
|
48
|
+
import { discoverDocusaurusJSONFiles } from './docusaurus-sync.js';
|
|
49
|
+
import { visualize } from './integrity.js';
|
|
50
|
+
import { output } from './output.js';
|
|
51
|
+
|
|
52
|
+
/** Does this string contain any Private Use Area codepoint? */
|
|
53
|
+
function hasPua(value) {
|
|
54
|
+
if (typeof value !== 'string') return false;
|
|
55
|
+
for (const ch of value) {
|
|
56
|
+
if (isPrivateUse(ch.codePointAt(0))) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Repair every PUA-bearing string in a nested JSON structure, in place.
|
|
63
|
+
*
|
|
64
|
+
* Generic deep walk rather than Docusaurus-message surgery: only strings that
|
|
65
|
+
* actually contain PUA are touched, so descriptions, URLs, and every other
|
|
66
|
+
* field pass through byte-identical whatever the file's shape.
|
|
67
|
+
*
|
|
68
|
+
* @param {*} node - Parsed JSON value (object/array/primitive)
|
|
69
|
+
* @param {string} converterKey - Converter whose table reverses the PUA
|
|
70
|
+
* @param {object} stats - Mutated: { repaired, caseLossy, unreversed:Set, samples:[] }
|
|
71
|
+
* @returns {*} The repaired node
|
|
72
|
+
*/
|
|
73
|
+
function repairNode(node, converterKey, stats) {
|
|
74
|
+
if (typeof node === 'string') {
|
|
75
|
+
if (!hasPua(node)) return node;
|
|
76
|
+
const { reversed, caseLossy, unreversed } = reverseScript(node, converterKey);
|
|
77
|
+
stats.repaired++;
|
|
78
|
+
if (caseLossy) stats.caseLossy++;
|
|
79
|
+
for (const ch of unreversed) stats.unreversed.add(ch);
|
|
80
|
+
if (stats.samples.length < 3) {
|
|
81
|
+
stats.samples.push({ before: node, after: reversed });
|
|
82
|
+
}
|
|
83
|
+
return reversed;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(node)) {
|
|
86
|
+
return node.map(v => repairNode(v, converterKey, stats));
|
|
87
|
+
}
|
|
88
|
+
if (node && typeof node === 'object') {
|
|
89
|
+
for (const k of Object.keys(node)) {
|
|
90
|
+
node[k] = repairNode(node[k], converterKey, stats);
|
|
91
|
+
}
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
return node;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Run the repair across a project's configured locales.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} options
|
|
101
|
+
* @param {string} options.cwd - Project root
|
|
102
|
+
* @param {object} options.cliArgs - Parsed CLI args (dry, locale, json)
|
|
103
|
+
* @returns {{ locales: Array, totals: object, exitCode: number }}
|
|
104
|
+
*/
|
|
105
|
+
function runRepairScript({ cwd = process.cwd(), cliArgs = {} } = {}) {
|
|
106
|
+
const config = resolveConfig(cliArgs, cwd);
|
|
107
|
+
const dry = !!cliArgs.dry;
|
|
108
|
+
const localeFilter = cliArgs.locale || null;
|
|
109
|
+
|
|
110
|
+
const pairs = resolvePairs(config);
|
|
111
|
+
const report = [];
|
|
112
|
+
const totals = { filesScanned: 0, valuesRepaired: 0, caseLossy: 0, unreversedCodepoints: 0 };
|
|
113
|
+
let hadWriteFailure = false;
|
|
114
|
+
|
|
115
|
+
for (const [, pairConfig] of pairs) {
|
|
116
|
+
const locale = pairConfig.target;
|
|
117
|
+
if (localeFilter && locale !== localeFilter) continue;
|
|
118
|
+
|
|
119
|
+
const card = getLanguageCard(locale);
|
|
120
|
+
const converterKey = converterKeyForLocale(locale, card);
|
|
121
|
+
if (!converterKey) continue;
|
|
122
|
+
const conv = SCRIPT_CONVERTERS[converterKey];
|
|
123
|
+
// Real-Unicode converters (crk, sr) never emit PUA — nothing to repair.
|
|
124
|
+
if (!conv.puaRange) continue;
|
|
125
|
+
|
|
126
|
+
const resolution = pairConfig.scriptResolution || {};
|
|
127
|
+
if (resolution.converterKey) {
|
|
128
|
+
report.push({
|
|
129
|
+
locale,
|
|
130
|
+
converter: getConverterInfo(converterKey).to,
|
|
131
|
+
skipped: 'conversion enabled — PUA is the configured output for this locale',
|
|
132
|
+
files: [],
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Discover this locale's files. Docusaurus keeps a directory per locale;
|
|
138
|
+
// everything else is one flat file per locale.
|
|
139
|
+
const files = [];
|
|
140
|
+
let flatFormat = null;
|
|
141
|
+
if (config.format === 'docusaurus') {
|
|
142
|
+
files.push(...discoverDocusaurusJSONFiles(path.join(config.localesDir, locale)));
|
|
143
|
+
} else {
|
|
144
|
+
flatFormat = config.format !== 'auto' ? config.format : detectFormatFromDir(config.localesDir);
|
|
145
|
+
const filePath = path.join(config.localesDir, `${locale}${getExtension(flatFormat)}`);
|
|
146
|
+
if (fs.existsSync(filePath)) files.push(filePath);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const localeReport = {
|
|
150
|
+
locale,
|
|
151
|
+
converter: getConverterInfo(converterKey).to,
|
|
152
|
+
files: [],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
for (const filePath of files) {
|
|
156
|
+
totals.filesScanned++;
|
|
157
|
+
const stats = { repaired: 0, caseLossy: 0, unreversed: new Set(), samples: [] };
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
if (config.format === 'docusaurus' || flatFormat === 'json') {
|
|
161
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
162
|
+
const repaired = repairNode(parsed, converterKey, stats);
|
|
163
|
+
if (stats.repaired > 0 && !dry) {
|
|
164
|
+
fs.writeFileSync(filePath, JSON.stringify(repaired, null, 2) + '\n', 'utf-8');
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// TOML/YAML: readLocaleFile returns a flat map; repair values and
|
|
168
|
+
// write back through the format-preserving serializer.
|
|
169
|
+
const flat = readLocaleFile(filePath, flatFormat);
|
|
170
|
+
const yamlStyle = flatFormat === 'yaml'
|
|
171
|
+
? detectYAMLStyle(fs.readFileSync(filePath, 'utf-8'))
|
|
172
|
+
: null;
|
|
173
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
174
|
+
flat[k] = repairNode(v, converterKey, stats);
|
|
175
|
+
}
|
|
176
|
+
if (stats.repaired > 0 && !dry) {
|
|
177
|
+
writeLocaleFile(filePath, flat, flatFormat, flat, yamlStyle);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
hadWriteFailure = true;
|
|
182
|
+
localeReport.files.push({ file: filePath, error: err.message });
|
|
183
|
+
output.error(`[repair-script] ${filePath}: ${err.message}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (stats.repaired > 0 || stats.unreversed.size > 0) {
|
|
188
|
+
totals.valuesRepaired += stats.repaired;
|
|
189
|
+
totals.caseLossy += stats.caseLossy;
|
|
190
|
+
totals.unreversedCodepoints += stats.unreversed.size;
|
|
191
|
+
localeReport.files.push({
|
|
192
|
+
file: path.relative(cwd, filePath),
|
|
193
|
+
repaired: stats.repaired,
|
|
194
|
+
caseLossy: stats.caseLossy,
|
|
195
|
+
unreversedCodepoints: [...stats.unreversed].map(
|
|
196
|
+
ch => 'U+' + ch.codePointAt(0).toString(16).toUpperCase().padStart(4, '0'),
|
|
197
|
+
),
|
|
198
|
+
samples: stats.samples,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (localeReport.files.length > 0 || files.length > 0) {
|
|
204
|
+
report.push(localeReport);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Unreversible PUA means the file STILL cannot render — the repair is
|
|
209
|
+
// incomplete and the caller must know. Write failures likewise.
|
|
210
|
+
const exitCode = (hadWriteFailure || totals.unreversedCodepoints > 0) ? 1 : 0;
|
|
211
|
+
return { locales: report, totals, exitCode, dry };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Print the human-readable repair report.
|
|
216
|
+
*
|
|
217
|
+
* @param {{locales: Array, totals: object, dry: boolean}} result
|
|
218
|
+
*/
|
|
219
|
+
function printRepairReport(result) {
|
|
220
|
+
const { locales, totals, dry } = result;
|
|
221
|
+
const verb = dry ? 'would repair' : 'repaired';
|
|
222
|
+
|
|
223
|
+
output.raw('\n champollion repair-script — reverse unintended script conversion\n');
|
|
224
|
+
|
|
225
|
+
if (locales.length === 0) {
|
|
226
|
+
output.raw(' No configured locale uses a PUA script converter — nothing to scan.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const loc of locales) {
|
|
230
|
+
if (loc.skipped) {
|
|
231
|
+
output.raw(` ── ${loc.locale} (${loc.converter}) — skipped: ${loc.skipped}`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (loc.files.length === 0) {
|
|
235
|
+
output.raw(` ── ${loc.locale} (${loc.converter}) — clean, no PUA found`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
output.raw(` ── ${loc.locale} (${loc.converter}) ─────────────────────`);
|
|
239
|
+
for (const f of loc.files) {
|
|
240
|
+
if (f.error) {
|
|
241
|
+
output.error(` ${f.file}: ${f.error}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const lossy = f.caseLossy > 0 ? ` (${f.caseLossy} case-lossy — reversal cannot recover capitalisation; review)` : '';
|
|
245
|
+
output.raw(` ${f.file}: ${verb} ${f.repaired} value(s)${lossy}`);
|
|
246
|
+
for (const s of f.samples) {
|
|
247
|
+
output.raw(` ${visualize(s.before)}`);
|
|
248
|
+
output.raw(` → ${visualize(s.after)}`);
|
|
249
|
+
}
|
|
250
|
+
if (f.unreversedCodepoints.length > 0) {
|
|
251
|
+
output.warn(` ${f.file}: ${f.unreversedCodepoints.length} PUA codepoint(s) not in this converter's table, left in place: ${f.unreversedCodepoints.join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
output.raw('');
|
|
257
|
+
const summary = `${totals.valuesRepaired} value(s) ${verb} across ${totals.filesScanned} file(s); ${totals.caseLossy} case-lossy; ${totals.unreversedCodepoints} unreversible codepoint(s).`;
|
|
258
|
+
if (totals.unreversedCodepoints > 0) output.error(` ${summary}`);
|
|
259
|
+
else output.raw(` ${summary}`);
|
|
260
|
+
if (dry && totals.valuesRepaired > 0) {
|
|
261
|
+
output.raw(' Dry run — nothing was written. Re-run without --dry to apply.');
|
|
262
|
+
}
|
|
263
|
+
output.raw('');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { runRepairScript, printRepairReport, hasPua };
|