tokvista 1.10.0 → 1.11.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/dist/bin/tokvista.js +292 -8
- package/package.json +1 -1
package/dist/bin/tokvista.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/bin/tokvista.ts
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import { existsSync, readdirSync } from "fs";
|
|
6
|
-
import { readFile, writeFile } from "fs/promises";
|
|
6
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
7
7
|
import { createServer } from "http";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { createInterface } from "readline";
|
|
@@ -1088,6 +1088,134 @@ function convertTokenFormat(tokens, targetFormat) {
|
|
|
1088
1088
|
}
|
|
1089
1089
|
}
|
|
1090
1090
|
|
|
1091
|
+
// src/bin/scanner.ts
|
|
1092
|
+
import { readdir, readFile } from "fs/promises";
|
|
1093
|
+
import { join, extname } from "path";
|
|
1094
|
+
function isRecord7(value) {
|
|
1095
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1096
|
+
}
|
|
1097
|
+
function isTokenLike5(obj) {
|
|
1098
|
+
return isRecord7(obj) && "value" in obj;
|
|
1099
|
+
}
|
|
1100
|
+
function extractTokenNames(tokens) {
|
|
1101
|
+
const tokenMap = /* @__PURE__ */ new Map();
|
|
1102
|
+
function walk(node, path2 = []) {
|
|
1103
|
+
if (!isRecord7(node)) return;
|
|
1104
|
+
if (path2.length === 0 && Object.keys(node).some((k) => k.includes("/"))) {
|
|
1105
|
+
Object.values(node).forEach((val) => walk(val, []));
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (isTokenLike5(node)) {
|
|
1109
|
+
const tokenPath = path2.join(".");
|
|
1110
|
+
const cssVar = `--${path2.join("-")}`;
|
|
1111
|
+
tokenMap.set(cssVar, tokenPath);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
Object.entries(node).forEach(([key, val]) => {
|
|
1115
|
+
walk(val, [...path2, key]);
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
walk(tokens);
|
|
1119
|
+
return tokenMap;
|
|
1120
|
+
}
|
|
1121
|
+
async function scanFile(filePath, tokenVars) {
|
|
1122
|
+
const content = await readFile(filePath, "utf8");
|
|
1123
|
+
const lines = content.split("\n");
|
|
1124
|
+
const usedVars = /* @__PURE__ */ new Set();
|
|
1125
|
+
const hardcodedColors = [];
|
|
1126
|
+
const hardcodedSpacing = [];
|
|
1127
|
+
const cssVarPattern = /var\((--[\w-]+)\)/g;
|
|
1128
|
+
const hexColorPattern = /#[0-9A-Fa-f]{3,8}\b/g;
|
|
1129
|
+
const rgbPattern = /rgba?\([^)]+\)/g;
|
|
1130
|
+
const spacingPattern = /\b(\d+(?:\.\d+)?(?:px|rem|em))\b/g;
|
|
1131
|
+
lines.forEach((line, index) => {
|
|
1132
|
+
let match;
|
|
1133
|
+
while ((match = cssVarPattern.exec(line)) !== null) {
|
|
1134
|
+
const varName = match[1];
|
|
1135
|
+
if (tokenVars.has(varName)) {
|
|
1136
|
+
usedVars.add(varName);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
while ((match = hexColorPattern.exec(line)) !== null) {
|
|
1140
|
+
const color = match[0];
|
|
1141
|
+
if (!line.includes("0x") && !line.includes("\\u")) {
|
|
1142
|
+
hardcodedColors.push({ line: index + 1, value: color });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
while ((match = rgbPattern.exec(line)) !== null) {
|
|
1146
|
+
hardcodedColors.push({ line: index + 1, value: match[0] });
|
|
1147
|
+
}
|
|
1148
|
+
if (line.includes("padding") || line.includes("margin") || line.includes("gap") || line.includes("width") || line.includes("height")) {
|
|
1149
|
+
while ((match = spacingPattern.exec(line)) !== null) {
|
|
1150
|
+
hardcodedSpacing.push({ line: index + 1, value: match[1] });
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
return { usedVars, hardcodedColors, hardcodedSpacing };
|
|
1155
|
+
}
|
|
1156
|
+
async function scanDirectory(dir, tokenVars, extensions) {
|
|
1157
|
+
const usedVars = /* @__PURE__ */ new Set();
|
|
1158
|
+
const hardcodedColors = [];
|
|
1159
|
+
const hardcodedSpacing = [];
|
|
1160
|
+
let filesScanned = 0;
|
|
1161
|
+
async function scan(currentDir) {
|
|
1162
|
+
try {
|
|
1163
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
1164
|
+
for (const entry of entries) {
|
|
1165
|
+
const fullPath = join(currentDir, entry.name);
|
|
1166
|
+
if (entry.isDirectory()) {
|
|
1167
|
+
if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
await scan(fullPath);
|
|
1171
|
+
} else if (entry.isFile()) {
|
|
1172
|
+
const ext = extname(entry.name);
|
|
1173
|
+
if (extensions.has(ext)) {
|
|
1174
|
+
const result = await scanFile(fullPath, tokenVars);
|
|
1175
|
+
filesScanned++;
|
|
1176
|
+
result.usedVars.forEach((v) => usedVars.add(v));
|
|
1177
|
+
result.hardcodedColors.forEach((h) => hardcodedColors.push({ file: fullPath, ...h }));
|
|
1178
|
+
result.hardcodedSpacing.forEach((h) => hardcodedSpacing.push({ file: fullPath, ...h }));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
await scan(dir);
|
|
1186
|
+
return { usedVars, hardcodedColors, hardcodedSpacing, filesScanned };
|
|
1187
|
+
}
|
|
1188
|
+
async function scanTokenUsage(tokensPath, scanDir, tokens) {
|
|
1189
|
+
const detection = detectTokenFormat(tokens);
|
|
1190
|
+
let normalizedTokens = tokens;
|
|
1191
|
+
if (detection.format !== "token-studio" && detection.format !== "unknown") {
|
|
1192
|
+
normalizedTokens = normalizeTokenFormat(tokens, detection.format);
|
|
1193
|
+
}
|
|
1194
|
+
const tokenMap = extractTokenNames(normalizedTokens);
|
|
1195
|
+
const tokenVars = new Set(tokenMap.keys());
|
|
1196
|
+
const extensions = /* @__PURE__ */ new Set([".css", ".scss", ".sass", ".less", ".tsx", ".jsx", ".ts", ".js", ".vue", ".svelte"]);
|
|
1197
|
+
const scanResult = await scanDirectory(scanDir, tokenVars, extensions);
|
|
1198
|
+
const usedTokens = [];
|
|
1199
|
+
const unusedTokens = [];
|
|
1200
|
+
tokenMap.forEach((tokenPath, cssVar) => {
|
|
1201
|
+
if (scanResult.usedVars.has(cssVar)) {
|
|
1202
|
+
usedTokens.push(tokenPath);
|
|
1203
|
+
} else {
|
|
1204
|
+
unusedTokens.push(tokenPath);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
return {
|
|
1208
|
+
totalTokens: tokenMap.size,
|
|
1209
|
+
usedTokens,
|
|
1210
|
+
unusedTokens,
|
|
1211
|
+
hardcodedColors: scanResult.hardcodedColors.slice(0, 50),
|
|
1212
|
+
// Limit to 50
|
|
1213
|
+
hardcodedSpacing: scanResult.hardcodedSpacing.slice(0, 50),
|
|
1214
|
+
// Limit to 50
|
|
1215
|
+
filesScanned: scanResult.filesScanned
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1091
1219
|
// src/bin/watcher.ts
|
|
1092
1220
|
import { watch } from "fs";
|
|
1093
1221
|
function watchFile(filePath, onChange) {
|
|
@@ -1123,6 +1251,7 @@ Usage:
|
|
|
1123
1251
|
tokvista diff <old.json> <new.json>
|
|
1124
1252
|
tokvista convert <tokens.json> --to <w3c|style-dictionary|supernova> [--output <file>]
|
|
1125
1253
|
tokvista build <tokens.json> --output-dir <dir> [--skip-validation]
|
|
1254
|
+
tokvista scan <directory|tokens.json> [--tokens tokens.json]
|
|
1126
1255
|
|
|
1127
1256
|
Arguments:
|
|
1128
1257
|
tokens.json Path to your tokens file (overrides config.tokens)
|
|
@@ -1137,6 +1266,10 @@ Options:
|
|
|
1137
1266
|
--no-watch Disable live reload (serve only)
|
|
1138
1267
|
--no-preview Skip starting live preview after init
|
|
1139
1268
|
-h, --help Show this help message
|
|
1269
|
+
|
|
1270
|
+
Scan command usage:
|
|
1271
|
+
tokvista scan ./src --tokens tokens.json # Scan directory with specific tokens
|
|
1272
|
+
tokvista scan tokens.json # Scan current directory using tokens file
|
|
1140
1273
|
`);
|
|
1141
1274
|
}
|
|
1142
1275
|
function parsePort(value) {
|
|
@@ -1261,6 +1394,9 @@ function parseArgs(args) {
|
|
|
1261
1394
|
if (args[0] === "build") {
|
|
1262
1395
|
return parseBuildArgs(args.slice(1));
|
|
1263
1396
|
}
|
|
1397
|
+
if (args[0] === "scan") {
|
|
1398
|
+
return parseScanArgs(args.slice(1));
|
|
1399
|
+
}
|
|
1264
1400
|
return parseServeArgs(args);
|
|
1265
1401
|
}
|
|
1266
1402
|
function parseValidateArgs(args) {
|
|
@@ -1394,6 +1530,37 @@ function parseBuildArgs(args) {
|
|
|
1394
1530
|
if (!outputDir) throw new Error("--output-dir is required");
|
|
1395
1531
|
return { command: "build", tokenFileArg, outputDir, skipValidation };
|
|
1396
1532
|
}
|
|
1533
|
+
function parseScanArgs(args) {
|
|
1534
|
+
let scanDir;
|
|
1535
|
+
let tokenFileArg;
|
|
1536
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1537
|
+
const arg = args[index];
|
|
1538
|
+
if (arg === "-h" || arg === "--help") {
|
|
1539
|
+
printHelp();
|
|
1540
|
+
process.exit(0);
|
|
1541
|
+
}
|
|
1542
|
+
if (arg === "--tokens") {
|
|
1543
|
+
const next = args[index + 1];
|
|
1544
|
+
if (!next) throw new Error("Missing value for --tokens");
|
|
1545
|
+
tokenFileArg = next;
|
|
1546
|
+
index += 1;
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
if (arg.startsWith("--tokens=")) {
|
|
1550
|
+
tokenFileArg = arg.slice("--tokens=".length);
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1553
|
+
if (arg.startsWith("-")) {
|
|
1554
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1555
|
+
}
|
|
1556
|
+
if (scanDir) {
|
|
1557
|
+
throw new Error(`Only one directory or token file is supported. Unexpected value: "${arg}"`);
|
|
1558
|
+
}
|
|
1559
|
+
scanDir = arg;
|
|
1560
|
+
}
|
|
1561
|
+
if (!scanDir) throw new Error("Directory to scan or token file is required");
|
|
1562
|
+
return { command: "scan", scanDir, tokenFileArg };
|
|
1563
|
+
}
|
|
1397
1564
|
function parseExportArgs(args) {
|
|
1398
1565
|
let tokenFileArg;
|
|
1399
1566
|
let format;
|
|
@@ -1460,7 +1627,7 @@ async function resolveDefaultInitTitle(cwd) {
|
|
|
1460
1627
|
return "My Design System";
|
|
1461
1628
|
}
|
|
1462
1629
|
try {
|
|
1463
|
-
const raw = await
|
|
1630
|
+
const raw = await readFile2(packageJsonPath, "utf8");
|
|
1464
1631
|
const parsed = JSON.parse(raw);
|
|
1465
1632
|
if (typeof parsed.name === "string") {
|
|
1466
1633
|
return formatTitleFromPackageName(parsed.name);
|
|
@@ -1700,7 +1867,7 @@ async function resolveLogoForRuntime(logoPathValue, configDir, cwd) {
|
|
|
1700
1867
|
if (!existsSync(resolvedLogoPath)) {
|
|
1701
1868
|
throw new Error(`Logo file not found: ${resolvedLogoPath}`);
|
|
1702
1869
|
}
|
|
1703
|
-
const content = await
|
|
1870
|
+
const content = await readFile2(resolvedLogoPath);
|
|
1704
1871
|
const mimeType = toDataUrlMimeType(resolvedLogoPath);
|
|
1705
1872
|
return `data:${mimeType};base64,${content.toString("base64")}`;
|
|
1706
1873
|
}
|
|
@@ -1788,7 +1955,7 @@ async function loadConfigFromFile(configPath) {
|
|
|
1788
1955
|
const sourceLabel = path.basename(configPath);
|
|
1789
1956
|
const extension = path.extname(configPath).toLowerCase();
|
|
1790
1957
|
if (extension === ".json") {
|
|
1791
|
-
const raw = await
|
|
1958
|
+
const raw = await readFile2(configPath, "utf8");
|
|
1792
1959
|
try {
|
|
1793
1960
|
return normalizeConfigObject(JSON.parse(raw), sourceLabel);
|
|
1794
1961
|
} catch (error) {
|
|
@@ -1796,7 +1963,7 @@ async function loadConfigFromFile(configPath) {
|
|
|
1796
1963
|
}
|
|
1797
1964
|
}
|
|
1798
1965
|
if (extension === ".ts") {
|
|
1799
|
-
const raw = await
|
|
1966
|
+
const raw = await readFile2(configPath, "utf8");
|
|
1800
1967
|
const parsed = parseTsConfigSource(raw, sourceLabel);
|
|
1801
1968
|
return normalizeConfigObject(parsed, sourceLabel);
|
|
1802
1969
|
}
|
|
@@ -1937,7 +2104,7 @@ function openBrowser(url) {
|
|
|
1937
2104
|
});
|
|
1938
2105
|
}
|
|
1939
2106
|
async function readTokens(tokenPath) {
|
|
1940
|
-
const raw = await
|
|
2107
|
+
const raw = await readFile2(tokenPath, "utf8");
|
|
1941
2108
|
try {
|
|
1942
2109
|
const parsed = JSON.parse(raw);
|
|
1943
2110
|
if (!parsed || typeof parsed !== "object") {
|
|
@@ -1958,8 +2125,8 @@ async function runServeCommand(cwd, options) {
|
|
|
1958
2125
|
}
|
|
1959
2126
|
const runtimeConfig = await buildRuntimeConfig(config, configPath, cwd);
|
|
1960
2127
|
const [css, appBundle] = await Promise.all([
|
|
1961
|
-
|
|
1962
|
-
|
|
2128
|
+
readFile2(resolveDistAsset("styles.css"), "utf8"),
|
|
2129
|
+
readFile2(resolveDistAsset("cli/browser.js"), "utf8")
|
|
1963
2130
|
]);
|
|
1964
2131
|
let cachedTokens = await readTokens(resolvedTokenPath);
|
|
1965
2132
|
const getHtml = () => buildHtml(cachedTokens, runtimeConfig, css, appBundle, options.watch);
|
|
@@ -2209,6 +2376,119 @@ async function runBuildCommand(cwd, options) {
|
|
|
2209
2376
|
\u2705 Build complete: ${outputDir}
|
|
2210
2377
|
`);
|
|
2211
2378
|
}
|
|
2379
|
+
async function runScanCommand(cwd, options) {
|
|
2380
|
+
const resolvedScanPath = path.resolve(cwd, options.scanDir);
|
|
2381
|
+
if (existsSync(resolvedScanPath)) {
|
|
2382
|
+
const fs = await import("fs");
|
|
2383
|
+
if (!fs.statSync(resolvedScanPath).isDirectory()) {
|
|
2384
|
+
const tokenPath2 = resolvedScanPath;
|
|
2385
|
+
const scanDir2 = cwd;
|
|
2386
|
+
const tokens2 = await readTokens(tokenPath2);
|
|
2387
|
+
console.log(`
|
|
2388
|
+
Scanning ${scanDir2} for token usage...
|
|
2389
|
+
`);
|
|
2390
|
+
const result2 = await scanTokenUsage(tokenPath2, scanDir2, tokens2);
|
|
2391
|
+
const usagePercent2 = (result2.usedTokens.length / result2.totalTokens * 100).toFixed(1);
|
|
2392
|
+
console.log(`\u{1F4CA} Token Usage Report
|
|
2393
|
+
`);
|
|
2394
|
+
console.log(`Files scanned: ${result2.filesScanned}`);
|
|
2395
|
+
console.log(`Total tokens: ${result2.totalTokens}`);
|
|
2396
|
+
console.log(`Used tokens: ${result2.usedTokens.length} (${usagePercent2}%)`);
|
|
2397
|
+
console.log(`Unused tokens: ${result2.unusedTokens.length}
|
|
2398
|
+
`);
|
|
2399
|
+
if (result2.unusedTokens.length > 0) {
|
|
2400
|
+
console.log(`\u26A0\uFE0F Unused Tokens (safe to remove):`);
|
|
2401
|
+
result2.unusedTokens.slice(0, 20).forEach((token) => {
|
|
2402
|
+
console.log(` - ${token}`);
|
|
2403
|
+
});
|
|
2404
|
+
if (result2.unusedTokens.length > 20) {
|
|
2405
|
+
console.log(` ... and ${result2.unusedTokens.length - 20} more`);
|
|
2406
|
+
}
|
|
2407
|
+
console.log("");
|
|
2408
|
+
}
|
|
2409
|
+
if (result2.hardcodedColors.length > 0) {
|
|
2410
|
+
console.log(`\u{1F3A8} Hardcoded Colors (should use tokens):`);
|
|
2411
|
+
result2.hardcodedColors.slice(0, 10).forEach(({ file, line, value }) => {
|
|
2412
|
+
const relPath = path.relative(cwd, file);
|
|
2413
|
+
console.log(` ${relPath}:${line} - ${value}`);
|
|
2414
|
+
});
|
|
2415
|
+
if (result2.hardcodedColors.length > 10) {
|
|
2416
|
+
console.log(` ... and ${result2.hardcodedColors.length - 10} more`);
|
|
2417
|
+
}
|
|
2418
|
+
console.log("");
|
|
2419
|
+
}
|
|
2420
|
+
if (result2.hardcodedSpacing.length > 0) {
|
|
2421
|
+
console.log(`\u{1F4CF} Hardcoded Spacing (should use tokens):`);
|
|
2422
|
+
result2.hardcodedSpacing.slice(0, 10).forEach(({ file, line, value }) => {
|
|
2423
|
+
const relPath = path.relative(cwd, file);
|
|
2424
|
+
console.log(` ${relPath}:${line} - ${value}`);
|
|
2425
|
+
});
|
|
2426
|
+
if (result2.hardcodedSpacing.length > 10) {
|
|
2427
|
+
console.log(` ... and ${result2.hardcodedSpacing.length - 10} more`);
|
|
2428
|
+
}
|
|
2429
|
+
console.log("");
|
|
2430
|
+
}
|
|
2431
|
+
console.log(`\u2705 Scan complete
|
|
2432
|
+
`);
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
const scanDir = resolvedScanPath;
|
|
2437
|
+
const tokenPath = options.tokenFileArg ? path.resolve(cwd, options.tokenFileArg) : path.resolve(cwd, "tokens.json");
|
|
2438
|
+
if (!existsSync(scanDir)) {
|
|
2439
|
+
throw new Error(`Directory not found: ${scanDir}`);
|
|
2440
|
+
}
|
|
2441
|
+
if (!existsSync(tokenPath)) {
|
|
2442
|
+
throw new Error(`Token file not found: ${tokenPath}`);
|
|
2443
|
+
}
|
|
2444
|
+
const tokens = await readTokens(tokenPath);
|
|
2445
|
+
console.log(`
|
|
2446
|
+
Scanning ${scanDir} for token usage...
|
|
2447
|
+
`);
|
|
2448
|
+
const result = await scanTokenUsage(tokenPath, scanDir, tokens);
|
|
2449
|
+
const usagePercent = (result.usedTokens.length / result.totalTokens * 100).toFixed(1);
|
|
2450
|
+
console.log(`\u{1F4CA} Token Usage Report
|
|
2451
|
+
`);
|
|
2452
|
+
console.log(`Files scanned: ${result.filesScanned}`);
|
|
2453
|
+
console.log(`Total tokens: ${result.totalTokens}`);
|
|
2454
|
+
console.log(`Used tokens: ${result.usedTokens.length} (${usagePercent}%)`);
|
|
2455
|
+
console.log(`Unused tokens: ${result.unusedTokens.length}
|
|
2456
|
+
`);
|
|
2457
|
+
if (result.unusedTokens.length > 0) {
|
|
2458
|
+
console.log(`\u26A0\uFE0F Unused Tokens (safe to remove):`);
|
|
2459
|
+
result.unusedTokens.slice(0, 20).forEach((token) => {
|
|
2460
|
+
console.log(` - ${token}`);
|
|
2461
|
+
});
|
|
2462
|
+
if (result.unusedTokens.length > 20) {
|
|
2463
|
+
console.log(` ... and ${result.unusedTokens.length - 20} more`);
|
|
2464
|
+
}
|
|
2465
|
+
console.log("");
|
|
2466
|
+
}
|
|
2467
|
+
if (result.hardcodedColors.length > 0) {
|
|
2468
|
+
console.log(`\u{1F3A8} Hardcoded Colors (should use tokens):`);
|
|
2469
|
+
result.hardcodedColors.slice(0, 10).forEach(({ file, line, value }) => {
|
|
2470
|
+
const relPath = path.relative(cwd, file);
|
|
2471
|
+
console.log(` ${relPath}:${line} - ${value}`);
|
|
2472
|
+
});
|
|
2473
|
+
if (result.hardcodedColors.length > 10) {
|
|
2474
|
+
console.log(` ... and ${result.hardcodedColors.length - 10} more`);
|
|
2475
|
+
}
|
|
2476
|
+
console.log("");
|
|
2477
|
+
}
|
|
2478
|
+
if (result.hardcodedSpacing.length > 0) {
|
|
2479
|
+
console.log(`\u{1F4CF} Hardcoded Spacing (should use tokens):`);
|
|
2480
|
+
result.hardcodedSpacing.slice(0, 10).forEach(({ file, line, value }) => {
|
|
2481
|
+
const relPath = path.relative(cwd, file);
|
|
2482
|
+
console.log(` ${relPath}:${line} - ${value}`);
|
|
2483
|
+
});
|
|
2484
|
+
if (result.hardcodedSpacing.length > 10) {
|
|
2485
|
+
console.log(` ... and ${result.hardcodedSpacing.length - 10} more`);
|
|
2486
|
+
}
|
|
2487
|
+
console.log("");
|
|
2488
|
+
}
|
|
2489
|
+
console.log(`\u2705 Scan complete
|
|
2490
|
+
`);
|
|
2491
|
+
}
|
|
2212
2492
|
async function main() {
|
|
2213
2493
|
try {
|
|
2214
2494
|
const options = parseArgs(process.argv.slice(2));
|
|
@@ -2240,6 +2520,10 @@ async function main() {
|
|
|
2240
2520
|
await runBuildCommand(cwd, options);
|
|
2241
2521
|
return;
|
|
2242
2522
|
}
|
|
2523
|
+
if (options.command === "scan") {
|
|
2524
|
+
await runScanCommand(cwd, options);
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2243
2527
|
await runServeCommand(cwd, options);
|
|
2244
2528
|
} catch (error) {
|
|
2245
2529
|
console.error(error.message);
|