@anraktech/sync 0.5.0 → 0.7.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/cli.js +184 -30
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6
6
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
7
|
-
import { existsSync as
|
|
7
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
8
8
|
import { resolve as resolve3 } from "path";
|
|
9
|
-
import
|
|
9
|
+
import chalk4 from "chalk";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -416,7 +416,7 @@ function resetCache() {
|
|
|
416
416
|
|
|
417
417
|
// src/watcher.ts
|
|
418
418
|
import { watch } from "chokidar";
|
|
419
|
-
import { readdirSync, statSync, existsSync as
|
|
419
|
+
import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync3 } from "fs";
|
|
420
420
|
import { join as join3, relative as relative2, basename as basename4, resolve as resolve2 } from "path";
|
|
421
421
|
|
|
422
422
|
// src/uploader.ts
|
|
@@ -621,6 +621,7 @@ import { createInterface } from "readline/promises";
|
|
|
621
621
|
import { stdin, stdout } from "process";
|
|
622
622
|
import { homedir as homedir2, platform } from "os";
|
|
623
623
|
import { resolve, join as join2 } from "path";
|
|
624
|
+
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
624
625
|
import chalk2 from "chalk";
|
|
625
626
|
var HOME = homedir2();
|
|
626
627
|
var IS_MAC = platform() === "darwin";
|
|
@@ -652,17 +653,39 @@ function normalizePath(folderPath) {
|
|
|
652
653
|
return resolve(HOME, trimmed);
|
|
653
654
|
}
|
|
654
655
|
var TOOLS = [
|
|
656
|
+
{
|
|
657
|
+
type: "function",
|
|
658
|
+
function: {
|
|
659
|
+
name: "browse_folder",
|
|
660
|
+
description: "List files and subfolders in a local directory. Shows file names, sizes, types, and whether they are syncable. Use FIRST when user wants to see what's in a folder before syncing.",
|
|
661
|
+
parameters: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
folderPath: {
|
|
665
|
+
type: "string",
|
|
666
|
+
description: "Absolute path to the folder to browse"
|
|
667
|
+
},
|
|
668
|
+
filter: {
|
|
669
|
+
type: "string",
|
|
670
|
+
enum: ["all", "syncable", "folders"],
|
|
671
|
+
description: "Filter: 'all' shows everything, 'syncable' shows only supported file types (PDF, DOCX, etc.), 'folders' shows only subfolders. Default: all"
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
required: ["folderPath"]
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
},
|
|
655
678
|
{
|
|
656
679
|
type: "function",
|
|
657
680
|
function: {
|
|
658
681
|
name: "scan_folder",
|
|
659
|
-
description: "
|
|
682
|
+
description: "Sync all supported files from a local folder to a matching legal case. Use when user explicitly asks to sync, upload, or push files. If unsure what's in the folder, use browse_folder first.",
|
|
660
683
|
parameters: {
|
|
661
684
|
type: "object",
|
|
662
685
|
properties: {
|
|
663
686
|
folderPath: {
|
|
664
687
|
type: "string",
|
|
665
|
-
description: "Absolute path to the folder
|
|
688
|
+
description: "Absolute path to the folder to sync"
|
|
666
689
|
}
|
|
667
690
|
},
|
|
668
691
|
required: ["folderPath"]
|
|
@@ -718,7 +741,7 @@ Server: ${config.apiUrl}
|
|
|
718
741
|
Home directory: ${HOME}
|
|
719
742
|
Platform: ${IS_MAC ? "macOS" : platform()}
|
|
720
743
|
|
|
721
|
-
You can
|
|
744
|
+
You can browse local folders, scan & sync files, list cases, show sync status, and more.
|
|
722
745
|
|
|
723
746
|
Rules:
|
|
724
747
|
- Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
|
|
@@ -728,12 +751,64 @@ Rules:
|
|
|
728
751
|
"desktop" \u2192 ${join2(HOME, "Desktop")}
|
|
729
752
|
"documents" \u2192 ${join2(HOME, "Documents")}
|
|
730
753
|
"~/SomeFolder" \u2192 ${HOME}/SomeFolder
|
|
754
|
+
- When user says "look at" or "check" a folder, use browse_folder FIRST to show what's there.
|
|
755
|
+
- When user says "sync" or "upload", use scan_folder to actually sync.
|
|
756
|
+
- When browsing, highlight which files are syncable (PDF, DOCX, XLSX, etc.) vs not.
|
|
757
|
+
- Present file lists in a clean table/list format with names and sizes.
|
|
731
758
|
- Call tools to perform actions. Summarize results naturally after.
|
|
732
759
|
- If a tool returns an error, report it clearly to the user.
|
|
733
760
|
- Do NOT use thinking tags or reasoning tags in your output.`;
|
|
734
761
|
}
|
|
735
762
|
async function executeTool(name, args, ctx) {
|
|
736
763
|
switch (name) {
|
|
764
|
+
case "browse_folder": {
|
|
765
|
+
const rawPath = args.folderPath;
|
|
766
|
+
if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
|
|
767
|
+
const folderPath = normalizePath(rawPath);
|
|
768
|
+
if (!existsSync2(folderPath)) {
|
|
769
|
+
return JSON.stringify({ error: `Folder not found: ${folderPath}` });
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const s = statSync(folderPath);
|
|
773
|
+
if (!s.isDirectory()) {
|
|
774
|
+
return JSON.stringify({ error: `Not a directory: ${folderPath}` });
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
return JSON.stringify({ error: `Cannot access: ${folderPath}` });
|
|
778
|
+
}
|
|
779
|
+
const filter = args.filter || "all";
|
|
780
|
+
const entries = readdirSync(folderPath);
|
|
781
|
+
const items = [];
|
|
782
|
+
for (const entry of entries) {
|
|
783
|
+
if (isIgnoredFile(entry)) continue;
|
|
784
|
+
const fullPath = join2(folderPath, entry);
|
|
785
|
+
let st;
|
|
786
|
+
try {
|
|
787
|
+
st = statSync(fullPath);
|
|
788
|
+
} catch {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (st.isDirectory()) {
|
|
792
|
+
if (filter !== "syncable") {
|
|
793
|
+
items.push({ name: entry, type: "folder" });
|
|
794
|
+
}
|
|
795
|
+
} else if (st.isFile()) {
|
|
796
|
+
if (filter === "folders") continue;
|
|
797
|
+
const syncable = isSupportedFile(entry);
|
|
798
|
+
if (filter === "syncable" && !syncable) continue;
|
|
799
|
+
const sizeKB = Math.round(st.size / 1024);
|
|
800
|
+
const size = sizeKB > 1024 ? `${(sizeKB / 1024).toFixed(1)} MB` : `${sizeKB} KB`;
|
|
801
|
+
items.push({ name: entry, type: "file", size, syncable });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return JSON.stringify({
|
|
805
|
+
folder: folderPath,
|
|
806
|
+
totalItems: items.length,
|
|
807
|
+
items: items.slice(0, 50),
|
|
808
|
+
// Cap at 50 to avoid token explosion
|
|
809
|
+
truncated: items.length > 50
|
|
810
|
+
});
|
|
811
|
+
}
|
|
737
812
|
case "scan_folder": {
|
|
738
813
|
const rawPath = args.folderPath;
|
|
739
814
|
if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
|
|
@@ -950,7 +1025,7 @@ async function scanFolder(config) {
|
|
|
950
1025
|
function walk(dir) {
|
|
951
1026
|
let entries;
|
|
952
1027
|
try {
|
|
953
|
-
entries =
|
|
1028
|
+
entries = readdirSync2(dir);
|
|
954
1029
|
} catch {
|
|
955
1030
|
return;
|
|
956
1031
|
}
|
|
@@ -959,7 +1034,7 @@ async function scanFolder(config) {
|
|
|
959
1034
|
const fullPath = join3(dir, entry);
|
|
960
1035
|
let s;
|
|
961
1036
|
try {
|
|
962
|
-
s =
|
|
1037
|
+
s = statSync2(fullPath);
|
|
963
1038
|
} catch {
|
|
964
1039
|
continue;
|
|
965
1040
|
}
|
|
@@ -992,11 +1067,11 @@ async function pushSync(config) {
|
|
|
992
1067
|
}
|
|
993
1068
|
async function scanExternalFolder(config, folderPath, cases) {
|
|
994
1069
|
const absPath = resolve2(folderPath);
|
|
995
|
-
if (!
|
|
1070
|
+
if (!existsSync3(absPath)) {
|
|
996
1071
|
log.error(`Folder not found: ${absPath}`);
|
|
997
1072
|
return;
|
|
998
1073
|
}
|
|
999
|
-
if (!
|
|
1074
|
+
if (!statSync2(absPath).isDirectory()) {
|
|
1000
1075
|
log.error(`Not a directory: ${absPath}`);
|
|
1001
1076
|
return;
|
|
1002
1077
|
}
|
|
@@ -1005,7 +1080,7 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1005
1080
|
function walk(dir) {
|
|
1006
1081
|
let entries;
|
|
1007
1082
|
try {
|
|
1008
|
-
entries =
|
|
1083
|
+
entries = readdirSync2(dir);
|
|
1009
1084
|
} catch {
|
|
1010
1085
|
return;
|
|
1011
1086
|
}
|
|
@@ -1014,7 +1089,7 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1014
1089
|
const fullPath = join3(dir, entry);
|
|
1015
1090
|
let s;
|
|
1016
1091
|
try {
|
|
1017
|
-
s =
|
|
1092
|
+
s = statSync2(fullPath);
|
|
1018
1093
|
} catch {
|
|
1019
1094
|
continue;
|
|
1020
1095
|
}
|
|
@@ -1136,6 +1211,84 @@ async function startWatching(config) {
|
|
|
1136
1211
|
process.on("SIGTERM", shutdown);
|
|
1137
1212
|
}
|
|
1138
1213
|
|
|
1214
|
+
// src/updater.ts
|
|
1215
|
+
import { execFileSync } from "child_process";
|
|
1216
|
+
import chalk3 from "chalk";
|
|
1217
|
+
var PACKAGE_NAME = "@anraktech/sync";
|
|
1218
|
+
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
1219
|
+
var updateCache = { lastCheck: 0, latestVersion: null };
|
|
1220
|
+
function compareVersions(current, latest) {
|
|
1221
|
+
const a = current.split(".").map(Number);
|
|
1222
|
+
const b = latest.split(".").map(Number);
|
|
1223
|
+
for (let i = 0; i < 3; i++) {
|
|
1224
|
+
if ((b[i] ?? 0) > (a[i] ?? 0)) return 1;
|
|
1225
|
+
if ((b[i] ?? 0) < (a[i] ?? 0)) return -1;
|
|
1226
|
+
}
|
|
1227
|
+
return 0;
|
|
1228
|
+
}
|
|
1229
|
+
async function fetchLatestVersion() {
|
|
1230
|
+
try {
|
|
1231
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
1232
|
+
signal: AbortSignal.timeout(5e3)
|
|
1233
|
+
});
|
|
1234
|
+
if (!res.ok) return null;
|
|
1235
|
+
const data = await res.json();
|
|
1236
|
+
return data.version ?? null;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
function performUpdate(currentVersion, latestVersion) {
|
|
1242
|
+
console.log("");
|
|
1243
|
+
console.log(
|
|
1244
|
+
chalk3.dim(` Update available: ${currentVersion} \u2192 `) + chalk3.green(latestVersion)
|
|
1245
|
+
);
|
|
1246
|
+
console.log(chalk3.dim(" Updating..."));
|
|
1247
|
+
try {
|
|
1248
|
+
execFileSync("npm", ["install", "-g", `${PACKAGE_NAME}@${latestVersion}`], {
|
|
1249
|
+
stdio: "pipe",
|
|
1250
|
+
timeout: 6e4
|
|
1251
|
+
});
|
|
1252
|
+
console.log(chalk3.green(" Updated successfully. Restart to use the new version."));
|
|
1253
|
+
console.log("");
|
|
1254
|
+
return true;
|
|
1255
|
+
} catch {
|
|
1256
|
+
try {
|
|
1257
|
+
execFileSync("npm", ["install", "-g", `${PACKAGE_NAME}@${latestVersion}`, "--prefix", process.env.HOME + "/.npm-global"], {
|
|
1258
|
+
stdio: "pipe",
|
|
1259
|
+
timeout: 6e4
|
|
1260
|
+
});
|
|
1261
|
+
console.log(chalk3.green(" Updated successfully. Restart to use the new version."));
|
|
1262
|
+
console.log("");
|
|
1263
|
+
return true;
|
|
1264
|
+
} catch {
|
|
1265
|
+
console.log(
|
|
1266
|
+
chalk3.dim(" Auto-update failed. Run manually: ") + chalk3.cyan(`npm install -g ${PACKAGE_NAME}`)
|
|
1267
|
+
);
|
|
1268
|
+
console.log("");
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async function checkForUpdates(currentVersion) {
|
|
1274
|
+
try {
|
|
1275
|
+
const now = Date.now();
|
|
1276
|
+
if (now - updateCache.lastCheck < CHECK_INTERVAL_MS && updateCache.latestVersion) {
|
|
1277
|
+
if (compareVersions(currentVersion, updateCache.latestVersion) > 0) {
|
|
1278
|
+
performUpdate(currentVersion, updateCache.latestVersion);
|
|
1279
|
+
}
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const latestVersion = await fetchLatestVersion();
|
|
1283
|
+
updateCache = { lastCheck: now, latestVersion };
|
|
1284
|
+
if (!latestVersion) return;
|
|
1285
|
+
if (compareVersions(currentVersion, latestVersion) > 0) {
|
|
1286
|
+
performUpdate(currentVersion, latestVersion);
|
|
1287
|
+
}
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1139
1292
|
// src/cli.ts
|
|
1140
1293
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1141
1294
|
import { fileURLToPath } from "url";
|
|
@@ -1143,14 +1296,15 @@ import { dirname as dirname2, join as join4 } from "path";
|
|
|
1143
1296
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
1144
1297
|
var __dirname2 = dirname2(__filename2);
|
|
1145
1298
|
var pkg = JSON.parse(readFileSync2(join4(__dirname2, "..", "package.json"), "utf-8"));
|
|
1299
|
+
await checkForUpdates(pkg.version);
|
|
1146
1300
|
var program = new Command();
|
|
1147
1301
|
program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
|
|
1148
1302
|
program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
|
|
1149
1303
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1150
1304
|
try {
|
|
1151
|
-
console.log(
|
|
1305
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
|
|
1152
1306
|
const apiUrl = await rl.question(
|
|
1153
|
-
` AnrakLegal URL ${
|
|
1307
|
+
` AnrakLegal URL ${chalk4.dim("(https://anrak.legal)")}: `
|
|
1154
1308
|
) || "https://anrak.legal";
|
|
1155
1309
|
log.info("Connecting to server...");
|
|
1156
1310
|
const serverConfig = await fetchServerConfig(apiUrl);
|
|
@@ -1181,15 +1335,15 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
1181
1335
|
const rl2 = createInterface2({ input: stdin2, output: stdout2 });
|
|
1182
1336
|
const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
|
|
1183
1337
|
const watchInput = await rl2.question(
|
|
1184
|
-
` Watch folder ${
|
|
1338
|
+
` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
|
|
1185
1339
|
);
|
|
1186
1340
|
const watchFolder = resolve3(watchInput || defaultFolder);
|
|
1187
1341
|
rl2.close();
|
|
1188
|
-
if (!
|
|
1342
|
+
if (!existsSync4(watchFolder)) {
|
|
1189
1343
|
log.warn(
|
|
1190
1344
|
`Folder ${watchFolder} does not exist \u2014 it will be created when you add files`
|
|
1191
1345
|
);
|
|
1192
|
-
} else if (!
|
|
1346
|
+
} else if (!statSync3(watchFolder).isDirectory()) {
|
|
1193
1347
|
log.error(`${watchFolder} is not a directory`);
|
|
1194
1348
|
process.exit(1);
|
|
1195
1349
|
}
|
|
@@ -1207,7 +1361,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
1207
1361
|
log.info(`Config saved to ${getConfigDir()}`);
|
|
1208
1362
|
log.info(`Watching: ${watchFolder}`);
|
|
1209
1363
|
console.log(
|
|
1210
|
-
|
|
1364
|
+
chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
|
|
1211
1365
|
);
|
|
1212
1366
|
} catch (err) {
|
|
1213
1367
|
log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -1242,7 +1396,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
|
|
|
1242
1396
|
});
|
|
1243
1397
|
program.command("start").description("Start watching for file changes and syncing").action(async () => {
|
|
1244
1398
|
const config = requireConfig();
|
|
1245
|
-
console.log(
|
|
1399
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync\n"));
|
|
1246
1400
|
log.info(`Watching: ${config.watchFolder}`);
|
|
1247
1401
|
log.info(`Server: ${config.apiUrl}`);
|
|
1248
1402
|
console.log("");
|
|
@@ -1255,7 +1409,7 @@ program.command("start").description("Start watching for file changes and syncin
|
|
|
1255
1409
|
});
|
|
1256
1410
|
program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
|
|
1257
1411
|
const config = requireConfig();
|
|
1258
|
-
console.log(
|
|
1412
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
|
|
1259
1413
|
log.info(`Folder: ${config.watchFolder}`);
|
|
1260
1414
|
log.info(`Server: ${config.apiUrl}`);
|
|
1261
1415
|
console.log("");
|
|
@@ -1269,24 +1423,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
|
|
|
1269
1423
|
program.command("status").description("Show sync status").action(async () => {
|
|
1270
1424
|
const config = requireConfig();
|
|
1271
1425
|
const stats = getStats();
|
|
1272
|
-
console.log(
|
|
1426
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
|
|
1273
1427
|
console.log(` Server: ${config.apiUrl}`);
|
|
1274
1428
|
console.log(` Watch folder: ${config.watchFolder}`);
|
|
1275
1429
|
console.log(` Config: ${getConfigDir()}`);
|
|
1276
1430
|
console.log("");
|
|
1277
1431
|
console.log(` Files tracked: ${stats.totalFiles}`);
|
|
1278
|
-
console.log(` Synced: ${
|
|
1279
|
-
console.log(` Pending: ${
|
|
1280
|
-
console.log(` Errors: ${
|
|
1432
|
+
console.log(` Synced: ${chalk4.green(stats.synced)}`);
|
|
1433
|
+
console.log(` Pending: ${chalk4.yellow(stats.pending)}`);
|
|
1434
|
+
console.log(` Errors: ${chalk4.red(stats.errors)}`);
|
|
1281
1435
|
console.log(` Mapped folders: ${stats.mappedFolders}`);
|
|
1282
1436
|
try {
|
|
1283
1437
|
const cases = await listCases(config);
|
|
1284
1438
|
console.log(`
|
|
1285
1439
|
Server cases: ${cases.length}`);
|
|
1286
|
-
console.log(` Auth: ${
|
|
1440
|
+
console.log(` Auth: ${chalk4.green("valid")}`);
|
|
1287
1441
|
} catch {
|
|
1288
1442
|
console.log(`
|
|
1289
|
-
Auth: ${
|
|
1443
|
+
Auth: ${chalk4.red("expired \u2014 run anrak-sync login")}`);
|
|
1290
1444
|
}
|
|
1291
1445
|
console.log("");
|
|
1292
1446
|
});
|
|
@@ -1294,13 +1448,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
1294
1448
|
const config = requireConfig();
|
|
1295
1449
|
const mappings = getAllMappings();
|
|
1296
1450
|
const entries = Object.entries(mappings);
|
|
1297
|
-
console.log(
|
|
1451
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
|
|
1298
1452
|
if (entries.length === 0) {
|
|
1299
1453
|
log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
|
|
1300
1454
|
} else {
|
|
1301
1455
|
for (const [folder, mapping] of entries) {
|
|
1302
1456
|
console.log(
|
|
1303
|
-
` ${
|
|
1457
|
+
` ${chalk4.cyan(folder)} -> ${mapping.caseNumber} (${chalk4.dim(mapping.caseName)})`
|
|
1304
1458
|
);
|
|
1305
1459
|
}
|
|
1306
1460
|
}
|
|
@@ -1309,9 +1463,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
1309
1463
|
const mappedIds = new Set(entries.map(([, m]) => m.caseId));
|
|
1310
1464
|
const unmapped = cases.filter((c) => !mappedIds.has(c.id));
|
|
1311
1465
|
if (unmapped.length > 0) {
|
|
1312
|
-
console.log(
|
|
1466
|
+
console.log(chalk4.dim("\n Unmapped server cases:"));
|
|
1313
1467
|
for (const c of unmapped) {
|
|
1314
|
-
console.log(` ${
|
|
1468
|
+
console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
|
|
1315
1469
|
}
|
|
1316
1470
|
}
|
|
1317
1471
|
} catch {
|