@kinetixui/cli 0.15.0 → 0.16.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 +210 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc9 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/commands/add.ts
|
|
8
8
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -412,24 +412,183 @@ async function inspect(name, options) {
|
|
|
412
412
|
console.log(`Add it: ${pc4.cyan(`npx @kinetixui/cli add ${item.name}`)}`);
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
// src/commands/
|
|
415
|
+
// src/commands/lint.ts
|
|
416
|
+
import { existsSync as existsSync8 } from "fs";
|
|
417
|
+
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
418
|
+
import path8 from "path";
|
|
416
419
|
import pc5 from "picocolors";
|
|
420
|
+
|
|
421
|
+
// src/lib/lint.ts
|
|
422
|
+
function stripComments(text) {
|
|
423
|
+
const noBlocks = text.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "));
|
|
424
|
+
return noBlocks.replace(/(^|[^:])\/\/.*$/gm, (_m, pre) => pre);
|
|
425
|
+
}
|
|
426
|
+
var HEX = "(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})";
|
|
427
|
+
var COLOR_UTILITY_PREFIXES = [
|
|
428
|
+
"bg",
|
|
429
|
+
"text",
|
|
430
|
+
"border",
|
|
431
|
+
"fill",
|
|
432
|
+
"stroke",
|
|
433
|
+
"ring",
|
|
434
|
+
"from",
|
|
435
|
+
"via",
|
|
436
|
+
"to",
|
|
437
|
+
"outline",
|
|
438
|
+
"decoration",
|
|
439
|
+
"caret",
|
|
440
|
+
"accent",
|
|
441
|
+
"shadow",
|
|
442
|
+
"divide"
|
|
443
|
+
];
|
|
444
|
+
var COLOR_PATTERNS = [
|
|
445
|
+
// bg-[#1a2b3c], text-[#fff], ring-[#1a2b3c80] …
|
|
446
|
+
new RegExp(`\\b(?:${COLOR_UTILITY_PREFIXES.join("|")})-\\[#${HEX}\\]`, "g"),
|
|
447
|
+
// style={{ color: "#1a2b3c" }} / a plain CSS declaration `color: #1a2b3c;`
|
|
448
|
+
new RegExp(`:\\s*["']?#${HEX}["']?`, "g")
|
|
449
|
+
];
|
|
450
|
+
var SPACING_UTILITY_PATTERN = "-?(?:p|m)[trblxy]?|gap(?:-[xy])?|space-[xy]|inset(?:-[xy])?|top|right|bottom|left";
|
|
451
|
+
var SPACING_VALUE = "-?[\\d.]+(?:px|rem|em|%)?";
|
|
452
|
+
var SPACING_PATTERNS = [
|
|
453
|
+
new RegExp(`\\b(?:${SPACING_UTILITY_PATTERN})-\\[${SPACING_VALUE}\\]`, "g")
|
|
454
|
+
];
|
|
455
|
+
function lineOf(text, index) {
|
|
456
|
+
let line = 1;
|
|
457
|
+
for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;
|
|
458
|
+
return line;
|
|
459
|
+
}
|
|
460
|
+
function snippetAround(text, index, length) {
|
|
461
|
+
const lineStart = text.lastIndexOf("\n", index) + 1;
|
|
462
|
+
let lineEnd = text.indexOf("\n", index);
|
|
463
|
+
if (lineEnd === -1) lineEnd = text.length;
|
|
464
|
+
return text.slice(lineStart, lineEnd).trim().slice(0, 120) || text.slice(index, index + length);
|
|
465
|
+
}
|
|
466
|
+
function lintText(file, rawText) {
|
|
467
|
+
const text = stripComments(rawText);
|
|
468
|
+
const violations = [];
|
|
469
|
+
const seen = /* @__PURE__ */ new Set();
|
|
470
|
+
const scan = (patterns, kind) => {
|
|
471
|
+
for (const pattern of patterns) {
|
|
472
|
+
pattern.lastIndex = 0;
|
|
473
|
+
let match;
|
|
474
|
+
while (match = pattern.exec(text)) {
|
|
475
|
+
const line = lineOf(text, match.index);
|
|
476
|
+
const key = `${kind}:${line}:${match.index}`;
|
|
477
|
+
if (!seen.has(key)) {
|
|
478
|
+
seen.add(key);
|
|
479
|
+
violations.push({ file, line, kind, snippet: snippetAround(text, match.index, match[0].length) });
|
|
480
|
+
}
|
|
481
|
+
if (match[0].length === 0) pattern.lastIndex++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
scan(COLOR_PATTERNS, "color");
|
|
486
|
+
scan(SPACING_PATTERNS, "spacing");
|
|
487
|
+
violations.sort((a, b) => a.line - b.line);
|
|
488
|
+
return violations;
|
|
489
|
+
}
|
|
490
|
+
var LINT_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js", ".css"];
|
|
491
|
+
|
|
492
|
+
// src/commands/lint.ts
|
|
493
|
+
var SUGGESTION = {
|
|
494
|
+
color: "consider a semantic token utility instead (e.g. bg-primary, text-foreground)",
|
|
495
|
+
spacing: "consider a token-backed spacing utility instead (e.g. p-4, gap-2)"
|
|
496
|
+
};
|
|
497
|
+
async function collectFiles(dir) {
|
|
498
|
+
if (!existsSync8(dir)) return [];
|
|
499
|
+
const files = [];
|
|
500
|
+
const walk = async (current) => {
|
|
501
|
+
const entries = await readdir2(current, { withFileTypes: true });
|
|
502
|
+
for (const entry of entries) {
|
|
503
|
+
const full = path8.join(current, entry.name);
|
|
504
|
+
if (entry.isDirectory()) {
|
|
505
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
506
|
+
await walk(full);
|
|
507
|
+
} else if (entry.isFile() && LINT_EXTENSIONS.includes(path8.extname(entry.name))) {
|
|
508
|
+
files.push(full);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
await walk(dir);
|
|
513
|
+
return files;
|
|
514
|
+
}
|
|
515
|
+
async function lint(targetArg, options) {
|
|
516
|
+
const cwd = process.cwd();
|
|
517
|
+
let dirs;
|
|
518
|
+
if (targetArg) {
|
|
519
|
+
dirs = [path8.resolve(cwd, targetArg)];
|
|
520
|
+
} else {
|
|
521
|
+
if (!existsSync8(path8.join(cwd, CONFIG_FILE))) {
|
|
522
|
+
throw new Error(`No ${CONFIG_FILE} found \u2014 run "kinetixui init" first, or pass a path: kinetixui lint <path>`);
|
|
523
|
+
}
|
|
524
|
+
const config = await readConfig(cwd);
|
|
525
|
+
if (!config) throw new Error(`${CONFIG_FILE} exists but isn't valid JSON.`);
|
|
526
|
+
dirs = [resolveAliasDir(cwd, config, "components"), resolveAliasDir(cwd, config, "ui")];
|
|
527
|
+
}
|
|
528
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
529
|
+
const files = [];
|
|
530
|
+
for (const dir of dirs) {
|
|
531
|
+
for (const file of await collectFiles(dir)) {
|
|
532
|
+
if (!seenFiles.has(file)) {
|
|
533
|
+
seenFiles.add(file);
|
|
534
|
+
files.push(file);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (files.length === 0) {
|
|
539
|
+
console.log(pc5.dim(`No ${LINT_EXTENSIONS.join("/")} files found in ${dirs.map((d) => path8.relative(cwd, d) || ".").join(", ")}.`));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
let totalViolations = 0;
|
|
543
|
+
let filesWithViolations = 0;
|
|
544
|
+
for (const file of files) {
|
|
545
|
+
const text = await readFile2(file, "utf8");
|
|
546
|
+
const violations = lintText(file, text);
|
|
547
|
+
if (violations.length === 0) continue;
|
|
548
|
+
filesWithViolations++;
|
|
549
|
+
const rel = path8.relative(cwd, file);
|
|
550
|
+
for (const v of violations) {
|
|
551
|
+
totalViolations++;
|
|
552
|
+
console.log(
|
|
553
|
+
`${pc5.yellow("!")} ${pc5.bold(`${rel}:${v.line}`)} \u2014 hardcoded ${v.kind}: ${pc5.dim(v.snippet)}`
|
|
554
|
+
);
|
|
555
|
+
console.log(` ${pc5.dim(SUGGESTION[v.kind])}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
console.log();
|
|
559
|
+
if (totalViolations === 0) {
|
|
560
|
+
console.log(pc5.green("\u2714"), `${files.length} file${files.length === 1 ? "" : "s"} scanned, no hardcoded colors or spacing found.`);
|
|
561
|
+
} else {
|
|
562
|
+
console.log(
|
|
563
|
+
`${totalViolations} violation${totalViolations === 1 ? "" : "s"} in ${filesWithViolations} of ${files.length} file${files.length === 1 ? "" : "s"} scanned.`
|
|
564
|
+
);
|
|
565
|
+
console.log(
|
|
566
|
+
pc5.dim(
|
|
567
|
+
"Checks unknown/deprecated tokens, accessibility violations and cross-platform inconsistencies aren't covered yet \u2014 see https://kinetixui.com/docs/cli#lint."
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
if (options.fail) process.exitCode = 1;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/commands/list.ts
|
|
575
|
+
import pc6 from "picocolors";
|
|
417
576
|
async function list(options) {
|
|
418
577
|
const items = await fetchRegistryIndex(options.registry);
|
|
419
578
|
const components = items.filter((item) => item.type === "registry:ui").sort((a, b) => a.name.localeCompare(b.name));
|
|
420
579
|
console.log(`${components.length} component(s):`);
|
|
421
580
|
console.log();
|
|
422
581
|
for (const item of components) {
|
|
423
|
-
const description = item.description ?
|
|
424
|
-
console.log(` ${
|
|
582
|
+
const description = item.description ? pc6.dim(` \u2014 ${item.description}`) : "";
|
|
583
|
+
console.log(` ${pc6.cyan(item.name)}${description}`);
|
|
425
584
|
}
|
|
426
585
|
console.log();
|
|
427
|
-
console.log(`Add one: ${
|
|
428
|
-
console.log(`Add everything: ${
|
|
586
|
+
console.log(`Add one: ${pc6.cyan("npx @kinetixui/cli add <name>")}`);
|
|
587
|
+
console.log(`Add everything: ${pc6.cyan("npx @kinetixui/cli add --all")}`);
|
|
429
588
|
}
|
|
430
589
|
|
|
431
590
|
// src/commands/parity.ts
|
|
432
|
-
import
|
|
591
|
+
import pc7 from "picocolors";
|
|
433
592
|
var PLATFORMS = ["React", "SwiftUI", "Compose", "Flutter"];
|
|
434
593
|
var PLATFORM_ABBR = {
|
|
435
594
|
React: "RE",
|
|
@@ -447,11 +606,11 @@ async function parity(names, options) {
|
|
|
447
606
|
}) : components;
|
|
448
607
|
const nameWidth = Math.max(9, ...rows.map((r) => r.name.length));
|
|
449
608
|
const header = `${"COMPONENT".padEnd(nameWidth)} ${PLATFORMS.map((p) => PLATFORM_ABBR[p]).join(" ")} STATUS`;
|
|
450
|
-
console.log(
|
|
609
|
+
console.log(pc7.dim(header));
|
|
451
610
|
for (const item of rows) {
|
|
452
611
|
const on = new Set(item.platforms ?? PLATFORMS);
|
|
453
|
-
const marks = PLATFORMS.map((p) => on.has(p) ?
|
|
454
|
-
const status = item.status ?
|
|
612
|
+
const marks = PLATFORMS.map((p) => on.has(p) ? pc7.green("\u2713 ") : pc7.dim("\u2014 "));
|
|
613
|
+
const status = item.status ? pc7.yellow(item.status) : "";
|
|
455
614
|
console.log(`${item.name.padEnd(nameWidth)} ${marks.join(" ")} ${status}`);
|
|
456
615
|
}
|
|
457
616
|
console.log();
|
|
@@ -459,10 +618,10 @@ async function parity(names, options) {
|
|
|
459
618
|
}
|
|
460
619
|
|
|
461
620
|
// src/commands/theme.ts
|
|
462
|
-
import { existsSync as
|
|
463
|
-
import { mkdir as mkdir2, readFile as
|
|
464
|
-
import
|
|
465
|
-
import
|
|
621
|
+
import { existsSync as existsSync9 } from "fs";
|
|
622
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
623
|
+
import path9 from "path";
|
|
624
|
+
import pc8 from "picocolors";
|
|
466
625
|
|
|
467
626
|
// src/lib/color-math.ts
|
|
468
627
|
function hexToRgb(hex) {
|
|
@@ -652,57 +811,57 @@ function scaffoldThemeFile(name) {
|
|
|
652
811
|
// src/commands/theme.ts
|
|
653
812
|
var THEME_DIR = "kinetixui-themes";
|
|
654
813
|
function csvPath(cwd, name) {
|
|
655
|
-
return
|
|
814
|
+
return path9.join(cwd, THEME_DIR, `${name}.csv`);
|
|
656
815
|
}
|
|
657
816
|
function cssPath(cwd, name) {
|
|
658
|
-
return
|
|
817
|
+
return path9.join(cwd, THEME_DIR, `${name}.css`);
|
|
659
818
|
}
|
|
660
819
|
async function themeCreate(name, options) {
|
|
661
820
|
const cwd = process.cwd();
|
|
662
821
|
const file = csvPath(cwd, name);
|
|
663
|
-
if (
|
|
664
|
-
throw new Error(`${
|
|
822
|
+
if (existsSync9(file) && !options.force) {
|
|
823
|
+
throw new Error(`${path9.relative(cwd, file)} already exists. Pass --force to overwrite it.`);
|
|
665
824
|
}
|
|
666
|
-
await mkdir2(
|
|
825
|
+
await mkdir2(path9.dirname(file), { recursive: true });
|
|
667
826
|
await writeFile3(file, scaffoldThemeFile(name), "utf8");
|
|
668
|
-
console.log(
|
|
827
|
+
console.log(pc8.green("\u2714"), `Wrote ${path9.relative(cwd, file)}`);
|
|
669
828
|
console.log(`Uncomment and edit the tokens you want to override, then run:`);
|
|
670
|
-
console.log(
|
|
829
|
+
console.log(pc8.dim(` kinetixui theme build ${name}`));
|
|
671
830
|
}
|
|
672
831
|
async function themeBuild(name, options) {
|
|
673
832
|
const cwd = process.cwd();
|
|
674
833
|
const src = csvPath(cwd, name);
|
|
675
|
-
if (!
|
|
676
|
-
throw new Error(`${
|
|
834
|
+
if (!existsSync9(src)) {
|
|
835
|
+
throw new Error(`${path9.relative(cwd, src)} doesn't exist. Run "kinetixui theme create ${name}" first.`);
|
|
677
836
|
}
|
|
678
|
-
const text = await
|
|
837
|
+
const text = await readFile3(src, "utf8");
|
|
679
838
|
const { values: parsed, errors, unknownTokens } = parseThemeFile(text);
|
|
680
|
-
for (const err of errors) console.log(
|
|
681
|
-
for (const unknown of unknownTokens) console.log(
|
|
839
|
+
for (const err of errors) console.log(pc8.yellow("!"), err);
|
|
840
|
+
for (const unknown of unknownTokens) console.log(pc8.yellow("!"), `"${unknown}" isn't a known token \u2014 skipped.`);
|
|
682
841
|
const values = deriveForegrounds(parsed);
|
|
683
842
|
const setCount = Object.keys(values).length;
|
|
684
843
|
if (setCount === 0) {
|
|
685
|
-
throw new Error(`No tokens set in ${
|
|
844
|
+
throw new Error(`No tokens set in ${path9.relative(cwd, src)} \u2014 nothing to build. Uncomment at least one line.`);
|
|
686
845
|
}
|
|
687
846
|
const dest = cssPath(cwd, name);
|
|
688
847
|
await writeFile3(dest, toCssBlock(values), "utf8");
|
|
689
|
-
console.log(
|
|
690
|
-
console.log(`Paste it after ${
|
|
848
|
+
console.log(pc8.green("\u2714"), `Wrote ${path9.relative(cwd, dest)} (${setCount} token${setCount === 1 ? "" : "s"})`);
|
|
849
|
+
console.log(`Paste it after ${pc8.dim('@import "@kinetixui/tokens/css";')} in your global CSS.`);
|
|
691
850
|
console.log();
|
|
692
|
-
console.log(
|
|
851
|
+
console.log(pc8.dim("WCAG AA contrast (4.5:1)"));
|
|
693
852
|
const results = checkContrast(values);
|
|
694
853
|
let failed = 0;
|
|
695
854
|
for (const { pair, ratio, pass } of results) {
|
|
696
|
-
const mark = pass ?
|
|
855
|
+
const mark = pass ? pc8.green("\u2714") : pc8.red("\u2716");
|
|
697
856
|
if (!pass) failed++;
|
|
698
|
-
console.log(`${mark} ${pair[0]} / ${pair[1]}`,
|
|
857
|
+
console.log(`${mark} ${pair[0]} / ${pair[1]}`, pc8.dim(`${ratio.toFixed(2)}:1`));
|
|
699
858
|
}
|
|
700
859
|
if (results.length === 0) {
|
|
701
|
-
console.log(
|
|
860
|
+
console.log(pc8.dim("(no checkable pairs \u2014 set at least one base + foreground, or just a base to auto-derive one)"));
|
|
702
861
|
}
|
|
703
862
|
console.log();
|
|
704
863
|
console.log("Native (SwiftUI/Compose/Flutter) theme output isn't built yet \u2014 this compiles to CSS only.");
|
|
705
|
-
console.log(
|
|
864
|
+
console.log(pc8.dim("See https://kinetixui.com/docs/cli#theme for the current scope."));
|
|
706
865
|
if (failed > 0 && options.failOnContrast) {
|
|
707
866
|
process.exitCode = 1;
|
|
708
867
|
}
|
|
@@ -711,7 +870,7 @@ async function themeBuild(name, options) {
|
|
|
711
870
|
// package.json
|
|
712
871
|
var package_default = {
|
|
713
872
|
name: "@kinetixui/cli",
|
|
714
|
-
version: "0.
|
|
873
|
+
version: "0.16.0",
|
|
715
874
|
description: "Add KinetixUI components and tokens to your project.",
|
|
716
875
|
type: "module",
|
|
717
876
|
license: "MIT",
|
|
@@ -755,7 +914,7 @@ program.command("init").description("Set up kinetixui.json and pull in the token
|
|
|
755
914
|
try {
|
|
756
915
|
await init({ registry: opts.registry, yes: opts.yes });
|
|
757
916
|
} catch (err) {
|
|
758
|
-
console.error(
|
|
917
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
759
918
|
process.exitCode = 1;
|
|
760
919
|
}
|
|
761
920
|
});
|
|
@@ -763,7 +922,7 @@ program.command("add").description("Add one or more components to your project."
|
|
|
763
922
|
try {
|
|
764
923
|
await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
|
|
765
924
|
} catch (err) {
|
|
766
|
-
console.error(
|
|
925
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
767
926
|
process.exitCode = 1;
|
|
768
927
|
}
|
|
769
928
|
});
|
|
@@ -771,7 +930,7 @@ program.command("list").description("List every component available in the regis
|
|
|
771
930
|
try {
|
|
772
931
|
await list({ registry: opts.registry });
|
|
773
932
|
} catch (err) {
|
|
774
|
-
console.error(
|
|
933
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
775
934
|
process.exitCode = 1;
|
|
776
935
|
}
|
|
777
936
|
});
|
|
@@ -779,7 +938,7 @@ program.command("inspect").description("Show everything the registry knows about
|
|
|
779
938
|
try {
|
|
780
939
|
await inspect(name, { registry: opts.registry });
|
|
781
940
|
} catch (err) {
|
|
782
|
-
console.error(
|
|
941
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
783
942
|
process.exitCode = 1;
|
|
784
943
|
}
|
|
785
944
|
});
|
|
@@ -787,7 +946,15 @@ program.command("parity").description("Show which platforms carry each component
|
|
|
787
946
|
try {
|
|
788
947
|
await parity(components, { registry: opts.registry });
|
|
789
948
|
} catch (err) {
|
|
790
|
-
console.error(
|
|
949
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
950
|
+
process.exitCode = 1;
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
program.command("lint").description("Scan for hardcoded colors and spacing that should be semantic tokens.").argument("[path]", "directory to scan \u2014 defaults to the components/ui aliases from kinetixui.json").option("--no-fail", "exit 0 even if violations are found").action(async (target, opts) => {
|
|
954
|
+
try {
|
|
955
|
+
await lint(target, { fail: opts.fail });
|
|
956
|
+
} catch (err) {
|
|
957
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
791
958
|
process.exitCode = 1;
|
|
792
959
|
}
|
|
793
960
|
});
|
|
@@ -795,7 +962,7 @@ program.command("doctor").description("Check kinetixui.json, its aliases, and th
|
|
|
795
962
|
try {
|
|
796
963
|
await doctor();
|
|
797
964
|
} catch (err) {
|
|
798
|
-
console.error(
|
|
965
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
799
966
|
process.exitCode = 1;
|
|
800
967
|
}
|
|
801
968
|
});
|
|
@@ -804,7 +971,7 @@ theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with e
|
|
|
804
971
|
try {
|
|
805
972
|
await themeCreate(name, { force: opts.force });
|
|
806
973
|
} catch (err) {
|
|
807
|
-
console.error(
|
|
974
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
808
975
|
process.exitCode = 1;
|
|
809
976
|
}
|
|
810
977
|
});
|
|
@@ -812,7 +979,7 @@ theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS a
|
|
|
812
979
|
try {
|
|
813
980
|
await themeBuild(name, { failOnContrast: opts.failOnContrast });
|
|
814
981
|
} catch (err) {
|
|
815
|
-
console.error(
|
|
982
|
+
console.error(pc9.red("\u2716"), err.message);
|
|
816
983
|
process.exitCode = 1;
|
|
817
984
|
}
|
|
818
985
|
});
|