@inline-i18n-multi/cli 0.6.0 → 0.8.0

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/dist/index.js CHANGED
@@ -31,8 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  coverage: () => coverage,
34
+ extractProjectDictionaryKeys: () => extractProjectDictionaryKeys,
35
+ extractProjectTCalls: () => extractProjectTCalls,
34
36
  find: () => find,
37
+ parseDictionaryKeys: () => parseDictionaryKeys,
35
38
  parseProject: () => parseProject,
39
+ parseTCalls: () => parseTCalls,
40
+ typegen: () => typegen,
41
+ unused: () => unused,
36
42
  validate: () => validate
37
43
  });
38
44
  module.exports = __toCommonJS(index_exports);
@@ -79,6 +85,16 @@ function extractVariables(text) {
79
85
  if (!matches) return [];
80
86
  return matches.map((m) => m.slice(1, -1));
81
87
  }
88
+ var ICU_TYPE_PATTERN = /\{(\w+),\s*(\w+)/g;
89
+ function extractICUTypes(text) {
90
+ const types = [];
91
+ let match;
92
+ ICU_TYPE_PATTERN.lastIndex = 0;
93
+ while ((match = ICU_TYPE_PATTERN.exec(text)) !== null) {
94
+ types.push({ variable: match[1], type: match[2] });
95
+ }
96
+ return types;
97
+ }
82
98
  function parseFile(filePath) {
83
99
  const entries = [];
84
100
  const code = fs.readFileSync(filePath, "utf-8");
@@ -114,6 +130,11 @@ function parseFile(filePath) {
114
130
  if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.value.type === "StringLiteral") {
115
131
  entry.translations[prop.key.name] = prop.value.value;
116
132
  entry.variables.push(...extractVariables(prop.value.value));
133
+ const types = extractICUTypes(prop.value.value);
134
+ if (types.length > 0) {
135
+ if (!entry.icuTypes) entry.icuTypes = {};
136
+ entry.icuTypes[prop.key.name] = types;
137
+ }
117
138
  }
118
139
  }
119
140
  } else if (args[0]?.type === "StringLiteral" && args[1]?.type === "StringLiteral") {
@@ -122,6 +143,16 @@ function parseFile(filePath) {
122
143
  entry.translations[lang2] = args[1].value;
123
144
  entry.variables.push(...extractVariables(args[0].value));
124
145
  entry.variables.push(...extractVariables(args[1].value));
146
+ const types1 = extractICUTypes(args[0].value);
147
+ if (types1.length > 0) {
148
+ if (!entry.icuTypes) entry.icuTypes = {};
149
+ entry.icuTypes[lang1] = types1;
150
+ }
151
+ const types2 = extractICUTypes(args[1].value);
152
+ if (types2.length > 0) {
153
+ if (!entry.icuTypes) entry.icuTypes = {};
154
+ entry.icuTypes[lang2] = types2;
155
+ }
125
156
  }
126
157
  entry.variables = [...new Set(entry.variables)];
127
158
  if (Object.keys(entry.translations).length > 0) {
@@ -131,6 +162,125 @@ function parseFile(filePath) {
131
162
  });
132
163
  return entries;
133
164
  }
165
+ function flattenObjectKeys(node, prefix = "") {
166
+ const keys = [];
167
+ if (node.type !== "ObjectExpression" || !node.properties) return keys;
168
+ for (const prop of node.properties) {
169
+ if (prop.type !== "ObjectProperty") continue;
170
+ let propName;
171
+ if (prop.key.type === "Identifier") propName = prop.key.name;
172
+ else if (prop.key.type === "StringLiteral") propName = prop.key.value;
173
+ if (!propName) continue;
174
+ const fullKey = prefix ? `${prefix}.${propName}` : propName;
175
+ if (prop.value.type === "StringLiteral") {
176
+ keys.push(fullKey);
177
+ } else if (prop.value.type === "ObjectExpression") {
178
+ keys.push(...flattenObjectKeys(prop.value, fullKey));
179
+ }
180
+ }
181
+ return keys;
182
+ }
183
+ function parseDictionaryKeys(filePath) {
184
+ const entries = [];
185
+ const code = fs.readFileSync(filePath, "utf-8");
186
+ let ast;
187
+ try {
188
+ ast = (0, import_parser.parse)(code, {
189
+ sourceType: "module",
190
+ plugins: ["typescript", "jsx"]
191
+ });
192
+ } catch {
193
+ return [];
194
+ }
195
+ (0, import_traverse.default)(ast, {
196
+ CallExpression(nodePath) {
197
+ const { node } = nodePath;
198
+ const { callee } = node;
199
+ if (callee.type !== "Identifier" || callee.name !== "loadDictionaries") return;
200
+ const args = node.arguments;
201
+ const loc = node.loc;
202
+ if (!loc || !args[0] || args[0].type !== "ObjectExpression") return;
203
+ let namespace = "default";
204
+ if (args[1]?.type === "StringLiteral") {
205
+ namespace = args[1].value;
206
+ }
207
+ const dictObj = args[0];
208
+ const allKeys = /* @__PURE__ */ new Set();
209
+ for (const localeProp of dictObj.properties) {
210
+ if (localeProp.type !== "ObjectProperty") continue;
211
+ if (localeProp.value.type !== "ObjectExpression") continue;
212
+ const localeKeys = flattenObjectKeys(localeProp.value);
213
+ for (const key of localeKeys) {
214
+ allKeys.add(key);
215
+ }
216
+ }
217
+ if (allKeys.size > 0) {
218
+ entries.push({
219
+ file: filePath,
220
+ line: loc.start.line,
221
+ namespace,
222
+ keys: [...allKeys]
223
+ });
224
+ }
225
+ }
226
+ });
227
+ return entries;
228
+ }
229
+ function parseTCalls(filePath) {
230
+ const entries = [];
231
+ const code = fs.readFileSync(filePath, "utf-8");
232
+ let ast;
233
+ try {
234
+ ast = (0, import_parser.parse)(code, {
235
+ sourceType: "module",
236
+ plugins: ["typescript", "jsx"]
237
+ });
238
+ } catch {
239
+ return [];
240
+ }
241
+ (0, import_traverse.default)(ast, {
242
+ CallExpression(nodePath) {
243
+ const { node } = nodePath;
244
+ const { callee } = node;
245
+ if (callee.type !== "Identifier" || callee.name !== "t") return;
246
+ const args = node.arguments;
247
+ const loc = node.loc;
248
+ if (!loc || !args[0] || args[0].type !== "StringLiteral") return;
249
+ entries.push({
250
+ file: filePath,
251
+ line: loc.start.line,
252
+ key: args[0].value
253
+ });
254
+ }
255
+ });
256
+ return entries;
257
+ }
258
+ async function extractProjectDictionaryKeys(options = {}) {
259
+ const {
260
+ cwd = process.cwd(),
261
+ include = ["**/*.{ts,tsx,js,jsx}"],
262
+ exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
263
+ } = options;
264
+ const files = await (0, import_fast_glob.default)(include, { cwd, ignore: exclude, absolute: true });
265
+ const allEntries = [];
266
+ for (const file of files) {
267
+ allEntries.push(...parseDictionaryKeys(file));
268
+ }
269
+ return allEntries;
270
+ }
271
+ async function extractProjectTCalls(options = {}) {
272
+ const {
273
+ cwd = process.cwd(),
274
+ include = ["**/*.{ts,tsx,js,jsx}"],
275
+ exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
276
+ } = options;
277
+ const files = await (0, import_fast_glob.default)(include, { cwd, ignore: exclude, absolute: true });
278
+ const allEntries = [];
279
+ for (const file of files) {
280
+ allEntries.push(...parseTCalls(file));
281
+ }
282
+ return allEntries;
283
+ }
134
284
  async function parseProject(options = {}) {
135
285
  const {
136
286
  cwd = process.cwd(),
@@ -188,10 +338,131 @@ Searching for: "${query}"
188
338
  }
189
339
 
190
340
  // src/commands/validate.ts
341
+ var import_chalk3 = __toESM(require("chalk"));
342
+
343
+ // src/commands/unused.ts
191
344
  var import_chalk2 = __toESM(require("chalk"));
345
+ var PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
346
+ async function unused(options = {}) {
347
+ const { cwd } = options;
348
+ console.log(import_chalk2.default.blue("\nDetecting unused translations...\n"));
349
+ const dictEntries = await extractProjectDictionaryKeys({ cwd });
350
+ const tCalls = await extractProjectTCalls({ cwd });
351
+ const usedKeys = /* @__PURE__ */ new Set();
352
+ for (const call of tCalls) {
353
+ usedKeys.add(call.key);
354
+ if (!call.key.includes(":")) {
355
+ usedKeys.add(call.key);
356
+ }
357
+ }
358
+ const unusedKeys = [];
359
+ for (const entry of dictEntries) {
360
+ for (const key of entry.keys) {
361
+ const fullKey = entry.namespace === "default" ? key : `${entry.namespace}:${key}`;
362
+ if (usedKeys.has(fullKey)) continue;
363
+ let isPluralVariant = false;
364
+ for (const suffix of PLURAL_SUFFIXES) {
365
+ if (key.endsWith(suffix)) {
366
+ const baseKey = key.slice(0, -suffix.length);
367
+ const fullBaseKey = entry.namespace === "default" ? baseKey : `${entry.namespace}:${baseKey}`;
368
+ if (usedKeys.has(fullBaseKey)) {
369
+ isPluralVariant = true;
370
+ break;
371
+ }
372
+ }
373
+ }
374
+ if (isPluralVariant) continue;
375
+ unusedKeys.push({
376
+ namespace: entry.namespace,
377
+ key: fullKey,
378
+ definedIn: entry.file,
379
+ line: entry.line
380
+ });
381
+ }
382
+ }
383
+ if (unusedKeys.length === 0) {
384
+ const totalKeys = dictEntries.reduce((sum, e) => sum + e.keys.length, 0);
385
+ console.log(import_chalk2.default.green("No unused translations found!\n"));
386
+ console.log(import_chalk2.default.gray(`Checked ${totalKeys} dictionary key(s) against ${tCalls.length} t() call(s)`));
387
+ return { unusedKeys };
388
+ }
389
+ console.log(import_chalk2.default.yellow(`Found ${unusedKeys.length} unused translation key(s):
390
+ `));
391
+ for (const item of unusedKeys) {
392
+ const relativePath = item.definedIn.replace(process.cwd() + "/", "");
393
+ console.log(` ${import_chalk2.default.red("-")} ${import_chalk2.default.cyan(item.key)}`);
394
+ console.log(import_chalk2.default.gray(` defined in ${relativePath}:${item.line}`));
395
+ }
396
+ console.log();
397
+ return { unusedKeys };
398
+ }
399
+
400
+ // src/commands/validate.ts
401
+ function checkVariableConsistency(entry) {
402
+ const varsByLocale = [];
403
+ for (const [locale, text] of Object.entries(entry.translations)) {
404
+ const matches = text.match(/\{(\w+)\}/g) || [];
405
+ const varNames = [...new Set(matches.map((v) => v.slice(1, -1)))].sort();
406
+ varsByLocale.push({ locale, vars: varNames });
407
+ }
408
+ if (varsByLocale.length < 2) return null;
409
+ const reference = varsByLocale[0];
410
+ const details = [];
411
+ for (let i = 1; i < varsByLocale.length; i++) {
412
+ const current = varsByLocale[i];
413
+ const refSet = new Set(reference.vars);
414
+ const curSet = new Set(current.vars);
415
+ const onlyInRef = reference.vars.filter((v) => !curSet.has(v));
416
+ const onlyInCur = current.vars.filter((v) => !refSet.has(v));
417
+ if (onlyInRef.length > 0) {
418
+ details.push(
419
+ `${reference.locale} has {${onlyInRef.join("}, {")}} missing in ${current.locale}`
420
+ );
421
+ }
422
+ if (onlyInCur.length > 0) {
423
+ details.push(
424
+ `${current.locale} has {${onlyInCur.join("}, {")}} missing in ${reference.locale}`
425
+ );
426
+ }
427
+ }
428
+ if (details.length === 0) return null;
429
+ return {
430
+ type: "variable_mismatch",
431
+ message: "Variable mismatch between translations",
432
+ entries: [entry],
433
+ details
434
+ };
435
+ }
436
+ function checkICUTypeConsistency(entry) {
437
+ if (!entry.icuTypes) return null;
438
+ const locales = Object.keys(entry.icuTypes);
439
+ if (locales.length < 2) return null;
440
+ const details = [];
441
+ const reference = locales[0];
442
+ const refTypes = entry.icuTypes[reference];
443
+ for (let i = 1; i < locales.length; i++) {
444
+ const locale = locales[i];
445
+ const curTypes = entry.icuTypes[locale];
446
+ for (const refType of refTypes) {
447
+ const match = curTypes.find((t) => t.variable === refType.variable);
448
+ if (match && match.type !== refType.type) {
449
+ details.push(
450
+ `{${refType.variable}} is "${refType.type}" in ${reference} but "${match.type}" in ${locale}`
451
+ );
452
+ }
453
+ }
454
+ }
455
+ if (details.length === 0) return null;
456
+ return {
457
+ type: "icu_type_mismatch",
458
+ message: "ICU type mismatch between translations",
459
+ entries: [entry],
460
+ details
461
+ };
462
+ }
192
463
  async function validate(options = {}) {
193
- const { cwd, locales } = options;
194
- console.log(import_chalk2.default.blue("\nValidating translations...\n"));
464
+ const { cwd, locales, strict, unused: checkUnused } = options;
465
+ console.log(import_chalk3.default.blue("\nValidating translations...\n"));
195
466
  const entries = await parseProject({ cwd });
196
467
  const issues = [];
197
468
  const groups = /* @__PURE__ */ new Map();
@@ -227,49 +498,62 @@ async function validate(options = {}) {
227
498
  }
228
499
  }
229
500
  for (const entry of entries) {
230
- const variableSets = Object.entries(entry.translations).map(([locale, text]) => {
231
- const vars = text.match(/\{(\w+)\}/g) || [];
232
- return { locale, vars: vars.sort().join(",") };
233
- });
234
- const uniqueVarSets = [...new Set(variableSets.map((v) => v.vars))];
235
- if (uniqueVarSets.length > 1) {
236
- issues.push({
237
- type: "variable_mismatch",
238
- message: "Variable mismatch between translations",
239
- entries: [entry]
240
- });
501
+ const issue = checkVariableConsistency(entry);
502
+ if (issue) issues.push(issue);
503
+ }
504
+ if (strict) {
505
+ for (const entry of entries) {
506
+ const issue = checkICUTypeConsistency(entry);
507
+ if (issue) issues.push(issue);
241
508
  }
242
509
  }
243
- if (issues.length === 0) {
244
- console.log(import_chalk2.default.green("\u2705 All translations are valid!\n"));
245
- console.log(import_chalk2.default.gray(`Checked ${entries.length} translation(s)`));
510
+ let unusedCount = 0;
511
+ if (checkUnused) {
512
+ const result = await unused({ cwd });
513
+ unusedCount = result.unusedKeys.length;
514
+ }
515
+ if (issues.length === 0 && unusedCount === 0) {
516
+ console.log(import_chalk3.default.green("All translations are valid!\n"));
517
+ console.log(import_chalk3.default.gray(`Checked ${entries.length} translation(s)`));
518
+ if (strict) {
519
+ console.log(import_chalk3.default.gray("(strict mode enabled)"));
520
+ }
521
+ if (checkUnused) {
522
+ console.log(import_chalk3.default.gray("(unused key detection enabled)"));
523
+ }
246
524
  return;
247
525
  }
248
- console.log(import_chalk2.default.red(`\u274C Found ${issues.length} issue(s):
526
+ console.log(import_chalk3.default.red(`Found ${issues.length} issue(s):
249
527
  `));
250
528
  for (const issue of issues) {
251
- const icon = issue.type === "inconsistent" ? "\u26A0\uFE0F" : issue.type === "missing" ? "\u{1F4ED}" : "\u{1F500}";
252
- console.log(`${icon} ${import_chalk2.default.yellow(issue.message)}`);
529
+ console.log(` ${import_chalk3.default.yellow(issue.message)}`);
253
530
  for (const entry of issue.entries) {
254
531
  const relativePath = entry.file.replace(process.cwd() + "/", "");
255
- console.log(import_chalk2.default.gray(` ${relativePath}:${entry.line}`));
532
+ console.log(import_chalk3.default.gray(` ${relativePath}:${entry.line}`));
256
533
  for (const [locale, text] of Object.entries(entry.translations)) {
257
- console.log(` ${import_chalk2.default.cyan(locale)}: ${text}`);
534
+ console.log(` ${import_chalk3.default.cyan(locale)}: ${text}`);
535
+ }
536
+ }
537
+ if (issue.details && issue.details.length > 0) {
538
+ for (const detail of issue.details) {
539
+ console.log(import_chalk3.default.gray(` \u2192 ${detail}`));
258
540
  }
259
541
  }
260
542
  console.log();
261
543
  }
262
- process.exit(1);
544
+ if (issues.length > 0 || unusedCount > 0) {
545
+ process.exit(1);
546
+ }
263
547
  }
264
548
 
265
549
  // src/commands/coverage.ts
266
- var import_chalk3 = __toESM(require("chalk"));
550
+ var import_chalk4 = __toESM(require("chalk"));
267
551
  async function coverage(options) {
268
552
  const { cwd, locales } = options;
269
- console.log(import_chalk3.default.blue("\nAnalyzing translation coverage...\n"));
553
+ console.log(import_chalk4.default.blue("\nAnalyzing translation coverage...\n"));
270
554
  const entries = await parseProject({ cwd });
271
555
  if (entries.length === 0) {
272
- console.log(import_chalk3.default.yellow("No translations found."));
556
+ console.log(import_chalk4.default.yellow("No translations found."));
273
557
  return;
274
558
  }
275
559
  const coverageData = [];
@@ -288,20 +572,20 @@ async function coverage(options) {
288
572
  percentage
289
573
  });
290
574
  }
291
- console.log(import_chalk3.default.bold("Translation Coverage:\n"));
575
+ console.log(import_chalk4.default.bold("Translation Coverage:\n"));
292
576
  const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6);
293
577
  console.log(
294
- import_chalk3.default.gray(
578
+ import_chalk4.default.gray(
295
579
  `${"Locale".padEnd(maxLocaleLen)} ${"Coverage".padStart(10)} ${"Translated".padStart(12)}`
296
580
  )
297
581
  );
298
- console.log(import_chalk3.default.gray("\u2500".repeat(maxLocaleLen + 26)));
582
+ console.log(import_chalk4.default.gray("\u2500".repeat(maxLocaleLen + 26)));
299
583
  for (const data of coverageData) {
300
- const color = data.percentage === 100 ? import_chalk3.default.green : data.percentage >= 80 ? import_chalk3.default.yellow : import_chalk3.default.red;
584
+ const color = data.percentage === 100 ? import_chalk4.default.green : data.percentage >= 80 ? import_chalk4.default.yellow : import_chalk4.default.red;
301
585
  const bar = createProgressBar(data.percentage, 10);
302
586
  const percentStr = `${data.percentage}%`.padStart(4);
303
587
  console.log(
304
- `${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${import_chalk3.default.gray(`${data.translated}/${data.total}`)}`
588
+ `${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${import_chalk4.default.gray(`${data.translated}/${data.total}`)}`
305
589
  );
306
590
  }
307
591
  console.log();
@@ -309,9 +593,9 @@ async function coverage(options) {
309
593
  const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length;
310
594
  const notCovered = coverageData.filter((d) => d.percentage === 0).length;
311
595
  if (fullyCovered === locales.length) {
312
- console.log(import_chalk3.default.green("\u2705 All locales are fully translated!"));
596
+ console.log(import_chalk4.default.green("All locales are fully translated!"));
313
597
  } else {
314
- console.log(import_chalk3.default.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`));
598
+ console.log(import_chalk4.default.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`));
315
599
  }
316
600
  }
317
601
  function createProgressBar(percentage, width) {
@@ -319,11 +603,85 @@ function createProgressBar(percentage, width) {
319
603
  const empty = width - filled;
320
604
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
321
605
  }
606
+
607
+ // src/commands/typegen.ts
608
+ var fs2 = __toESM(require("fs"));
609
+ var path = __toESM(require("path"));
610
+ var import_chalk5 = __toESM(require("chalk"));
611
+ var PLURAL_SUFFIXES2 = ["_zero", "_one", "_two", "_few", "_many", "_other"];
612
+ async function typegen(options = {}) {
613
+ const { cwd, output = "src/i18n.d.ts" } = options;
614
+ console.log(import_chalk5.default.blue("\nGenerating TypeScript types...\n"));
615
+ const dictEntries = await extractProjectDictionaryKeys({ cwd });
616
+ const allKeys = /* @__PURE__ */ new Set();
617
+ for (const entry of dictEntries) {
618
+ for (const key of entry.keys) {
619
+ const fullKey = entry.namespace === "default" ? key : `${entry.namespace}:${key}`;
620
+ allKeys.add(fullKey);
621
+ for (const suffix of PLURAL_SUFFIXES2) {
622
+ if (key.endsWith(suffix)) {
623
+ const baseKey = key.slice(0, -suffix.length);
624
+ const fullBaseKey = entry.namespace === "default" ? baseKey : `${entry.namespace}:${baseKey}`;
625
+ allKeys.add(fullBaseKey);
626
+ }
627
+ }
628
+ }
629
+ }
630
+ if (allKeys.size === 0) {
631
+ console.log(import_chalk5.default.yellow("No dictionary keys found. Make sure loadDictionaries() calls are present."));
632
+ return;
633
+ }
634
+ const sortedKeys = [...allKeys].sort();
635
+ const keyUnion = sortedKeys.map((k) => ` | '${k}'`).join("\n");
636
+ const content = `// Auto-generated by inline-i18n typegen
637
+ // Do not edit manually. Re-run: npx inline-i18n typegen
638
+
639
+ import 'inline-i18n-multi'
640
+
641
+ declare module 'inline-i18n-multi' {
642
+ export type TranslationKey =
643
+ ${keyUnion}
644
+
645
+ export function t(
646
+ key: TranslationKey,
647
+ vars?: TranslationVars,
648
+ locale?: string
649
+ ): string
650
+
651
+ export function hasTranslation(
652
+ key: TranslationKey,
653
+ locale?: string
654
+ ): boolean
655
+ }
656
+ `;
657
+ const outputPath = path.resolve(cwd || process.cwd(), output);
658
+ const outputDir = path.dirname(outputPath);
659
+ if (!fs2.existsSync(outputDir)) {
660
+ fs2.mkdirSync(outputDir, { recursive: true });
661
+ }
662
+ fs2.writeFileSync(outputPath, content, "utf-8");
663
+ console.log(import_chalk5.default.green(`Generated ${sortedKeys.length} translation key types`));
664
+ console.log(import_chalk5.default.gray(`Output: ${outputPath}
665
+ `));
666
+ const sample = sortedKeys.slice(0, 5);
667
+ for (const key of sample) {
668
+ console.log(` ${import_chalk5.default.cyan(key)}`);
669
+ }
670
+ if (sortedKeys.length > 5) {
671
+ console.log(import_chalk5.default.gray(` ... and ${sortedKeys.length - 5} more`));
672
+ }
673
+ }
322
674
  // Annotate the CommonJS export names for ESM import in node:
323
675
  0 && (module.exports = {
324
676
  coverage,
677
+ extractProjectDictionaryKeys,
678
+ extractProjectTCalls,
325
679
  find,
680
+ parseDictionaryKeys,
326
681
  parseProject,
682
+ parseTCalls,
683
+ typegen,
684
+ unused,
327
685
  validate
328
686
  });
329
687
  //# sourceMappingURL=index.js.map