@anraktech/sync 0.1.0 → 0.2.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/cli.js +230 -36
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { createInterface } from "readline/promises";
|
|
6
|
-
import { stdin, stdout } from "process";
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import { resolve } from "path";
|
|
9
|
-
import
|
|
5
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
6
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
7
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
8
|
+
import { resolve as resolve2 } from "path";
|
|
9
|
+
import chalk3 from "chalk";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -86,7 +86,7 @@ function openBrowser(url) {
|
|
|
86
86
|
exec(`${cmd} "${url}"`);
|
|
87
87
|
}
|
|
88
88
|
function findFreePort() {
|
|
89
|
-
return new Promise((
|
|
89
|
+
return new Promise((resolve3, reject) => {
|
|
90
90
|
const srv = createServer();
|
|
91
91
|
srv.listen(0, () => {
|
|
92
92
|
const addr = srv.address();
|
|
@@ -96,14 +96,14 @@ function findFreePort() {
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
const port = addr.port;
|
|
99
|
-
srv.close(() =>
|
|
99
|
+
srv.close(() => resolve3(port));
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
async function browserLogin(apiUrl) {
|
|
104
104
|
const port = await findFreePort();
|
|
105
105
|
return new Promise(
|
|
106
|
-
(
|
|
106
|
+
(resolve3, reject) => {
|
|
107
107
|
let server;
|
|
108
108
|
const timeout = setTimeout(() => {
|
|
109
109
|
server?.close();
|
|
@@ -150,7 +150,7 @@ async function browserLogin(apiUrl) {
|
|
|
150
150
|
const tokens = await resp.json();
|
|
151
151
|
clearTimeout(timeout);
|
|
152
152
|
server.close();
|
|
153
|
-
|
|
153
|
+
resolve3(tokens);
|
|
154
154
|
} catch (err) {
|
|
155
155
|
clearTimeout(timeout);
|
|
156
156
|
server.close();
|
|
@@ -338,11 +338,11 @@ function persist() {
|
|
|
338
338
|
if (cache) saveCache(cache);
|
|
339
339
|
}
|
|
340
340
|
function hashFile(filePath) {
|
|
341
|
-
return new Promise((
|
|
341
|
+
return new Promise((resolve3, reject) => {
|
|
342
342
|
const hash = createHash("sha256");
|
|
343
343
|
const stream = createReadStream(filePath);
|
|
344
344
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
345
|
-
stream.on("end", () =>
|
|
345
|
+
stream.on("end", () => resolve3(hash.digest("hex")));
|
|
346
346
|
stream.on("error", reject);
|
|
347
347
|
});
|
|
348
348
|
}
|
|
@@ -416,8 +416,11 @@ function resetCache() {
|
|
|
416
416
|
|
|
417
417
|
// src/watcher.ts
|
|
418
418
|
import { watch } from "chokidar";
|
|
419
|
-
import { readdirSync, statSync } from "fs";
|
|
420
|
-
import { join as join2, relative as relative2, basename as basename4 } from "path";
|
|
419
|
+
import { readdirSync, statSync, existsSync as existsSync2 } from "fs";
|
|
420
|
+
import { join as join2, relative as relative2, basename as basename4, resolve } from "path";
|
|
421
|
+
import { createInterface } from "readline/promises";
|
|
422
|
+
import { stdin, stdout } from "process";
|
|
423
|
+
import chalk2 from "chalk";
|
|
421
424
|
|
|
422
425
|
// src/uploader.ts
|
|
423
426
|
import { stat as stat2 } from "fs/promises";
|
|
@@ -664,6 +667,176 @@ async function pushSync(config) {
|
|
|
664
667
|
`Sync complete: ${result.uploaded} uploaded, ${result.failed} failed`
|
|
665
668
|
);
|
|
666
669
|
}
|
|
670
|
+
async function scanExternalFolder(config, folderPath, cases) {
|
|
671
|
+
const absPath = resolve(folderPath);
|
|
672
|
+
if (!existsSync2(absPath)) {
|
|
673
|
+
log.error(`Folder not found: ${absPath}`);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (!statSync(absPath).isDirectory()) {
|
|
677
|
+
log.error(`Not a directory: ${absPath}`);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
log.info(`Scanning ${absPath}...`);
|
|
681
|
+
let fileCount = 0;
|
|
682
|
+
function walk(dir) {
|
|
683
|
+
let entries;
|
|
684
|
+
try {
|
|
685
|
+
entries = readdirSync(dir);
|
|
686
|
+
} catch {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
for (const entry of entries) {
|
|
690
|
+
if (isIgnoredFile(entry)) continue;
|
|
691
|
+
const fullPath = join2(dir, entry);
|
|
692
|
+
let s;
|
|
693
|
+
try {
|
|
694
|
+
s = statSync(fullPath);
|
|
695
|
+
} catch {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (s.isDirectory()) {
|
|
699
|
+
walk(fullPath);
|
|
700
|
+
} else if (s.isFile() && isSupportedFile(entry)) {
|
|
701
|
+
fileCount++;
|
|
702
|
+
void enqueue(fullPath, join2(absPath, ".."));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
walk(absPath);
|
|
707
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
708
|
+
const queued = queueSize();
|
|
709
|
+
log.info(`Found ${fileCount} files, ${queued} need syncing`);
|
|
710
|
+
if (queued > 0) {
|
|
711
|
+
const result = await processQueue(config, cases);
|
|
712
|
+
log.info(`Synced: ${result.uploaded} uploaded, ${result.failed} failed`);
|
|
713
|
+
} else {
|
|
714
|
+
log.success("Everything up to date");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
|
|
718
|
+
const PROMPT = chalk2.blue("anrak> ");
|
|
719
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
720
|
+
function showHelp() {
|
|
721
|
+
console.log("");
|
|
722
|
+
console.log(chalk2.bold(" Available commands:"));
|
|
723
|
+
console.log("");
|
|
724
|
+
console.log(` ${chalk2.cyan("scan <folder>")} Scan a folder and sync its files to a case`);
|
|
725
|
+
console.log(` ${chalk2.cyan("rescan")} Re-scan the watch folder for new files`);
|
|
726
|
+
console.log(` ${chalk2.cyan("cases")} List cases on the server`);
|
|
727
|
+
console.log(` ${chalk2.cyan("status")} Show sync statistics`);
|
|
728
|
+
console.log(` ${chalk2.cyan("mappings")} Show folder \u2192 case mappings`);
|
|
729
|
+
console.log(` ${chalk2.cyan("refresh")} Refresh cases from server`);
|
|
730
|
+
console.log(` ${chalk2.cyan("help")} Show this help`);
|
|
731
|
+
console.log(` ${chalk2.cyan("quit")} Stop syncing and exit`);
|
|
732
|
+
console.log("");
|
|
733
|
+
}
|
|
734
|
+
async function handleCommand(input) {
|
|
735
|
+
const trimmed = input.trim();
|
|
736
|
+
if (!trimmed) return;
|
|
737
|
+
const [cmd, ...args] = trimmed.split(/\s+/);
|
|
738
|
+
const arg = args.join(" ");
|
|
739
|
+
switch (cmd.toLowerCase()) {
|
|
740
|
+
case "scan": {
|
|
741
|
+
if (!arg) {
|
|
742
|
+
log.warn("Usage: scan <folder path>");
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
await scanExternalFolder(config, arg, getCases());
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
case "rescan": {
|
|
749
|
+
await triggerScan();
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
case "cases": {
|
|
753
|
+
await refreshCases();
|
|
754
|
+
const cases = getCases();
|
|
755
|
+
if (cases.length === 0) {
|
|
756
|
+
log.info("No cases found on server");
|
|
757
|
+
} else {
|
|
758
|
+
console.log("");
|
|
759
|
+
for (const c of cases) {
|
|
760
|
+
const docCount = c.documents?.length ?? 0;
|
|
761
|
+
console.log(
|
|
762
|
+
` ${chalk2.cyan(c.caseNumber)} ${c.caseName} ${chalk2.dim(`(${docCount} docs)`)}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
console.log("");
|
|
766
|
+
}
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
case "status": {
|
|
770
|
+
const stats = getStats();
|
|
771
|
+
console.log("");
|
|
772
|
+
console.log(` ${chalk2.bold("Watch folder:")} ${config.watchFolder}`);
|
|
773
|
+
console.log(` ${chalk2.bold("Files tracked:")} ${stats.totalFiles}`);
|
|
774
|
+
console.log(` ${chalk2.bold("Synced:")} ${chalk2.green(String(stats.synced))}`);
|
|
775
|
+
console.log(` ${chalk2.bold("Pending:")} ${chalk2.yellow(String(stats.pending))}`);
|
|
776
|
+
console.log(` ${chalk2.bold("Errors:")} ${chalk2.red(String(stats.errors))}`);
|
|
777
|
+
console.log(` ${chalk2.bold("Mapped folders:")} ${stats.mappedFolders}`);
|
|
778
|
+
console.log(` ${chalk2.bold("Queue:")} ${queueSize()}`);
|
|
779
|
+
console.log("");
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
case "mappings":
|
|
783
|
+
case "map": {
|
|
784
|
+
const mappings = getAllMappings();
|
|
785
|
+
const entries = Object.entries(mappings);
|
|
786
|
+
if (entries.length === 0) {
|
|
787
|
+
log.info("No folder mappings yet");
|
|
788
|
+
} else {
|
|
789
|
+
console.log("");
|
|
790
|
+
for (const [folder, m] of entries) {
|
|
791
|
+
console.log(
|
|
792
|
+
` ${chalk2.cyan(folder)} \u2192 ${m.caseNumber} ${chalk2.dim(`(${m.caseName})`)}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
console.log("");
|
|
796
|
+
}
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
case "refresh": {
|
|
800
|
+
log.info("Refreshing cases from server...");
|
|
801
|
+
await refreshCases();
|
|
802
|
+
log.success(`Found ${getCases().length} case(s)`);
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "help":
|
|
806
|
+
case "?": {
|
|
807
|
+
showHelp();
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case "quit":
|
|
811
|
+
case "exit":
|
|
812
|
+
case "q": {
|
|
813
|
+
process.emit("SIGINT");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
default: {
|
|
817
|
+
log.warn(`Unknown command: ${cmd}. Type ${chalk2.cyan("help")} for available commands.`);
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
showHelp();
|
|
823
|
+
async function promptLoop() {
|
|
824
|
+
while (true) {
|
|
825
|
+
let input;
|
|
826
|
+
try {
|
|
827
|
+
input = await rl.question(PROMPT);
|
|
828
|
+
} catch {
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
try {
|
|
832
|
+
await handleCommand(input);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
void promptLoop();
|
|
839
|
+
}
|
|
667
840
|
async function startWatching(config) {
|
|
668
841
|
const folder = config.watchFolder;
|
|
669
842
|
log.info(`Scanning ${folder}...`);
|
|
@@ -684,7 +857,6 @@ async function startWatching(config) {
|
|
|
684
857
|
}
|
|
685
858
|
}, 5 * 60 * 1e3);
|
|
686
859
|
log.info(`Watching for changes...`);
|
|
687
|
-
log.dim("Press Ctrl+C to stop\n");
|
|
688
860
|
const watcher = watch(folder, {
|
|
689
861
|
ignored: /(^|[\/\\])(\.|~\$|Thumbs\.db|desktop\.ini)/,
|
|
690
862
|
persistent: true,
|
|
@@ -732,6 +904,22 @@ async function startWatching(config) {
|
|
|
732
904
|
watcher.on("error", (err) => {
|
|
733
905
|
log.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
|
|
734
906
|
});
|
|
907
|
+
startInteractiveAgent(
|
|
908
|
+
config,
|
|
909
|
+
() => cases,
|
|
910
|
+
async () => {
|
|
911
|
+
cases = await listCases(config);
|
|
912
|
+
},
|
|
913
|
+
async () => {
|
|
914
|
+
log.info(`Re-scanning ${folder}...`);
|
|
915
|
+
const result = await scanFolder(config);
|
|
916
|
+
log.info(`Scanned ${result.scanned} files, ${result.queued} need syncing`);
|
|
917
|
+
if (result.queued > 0) {
|
|
918
|
+
const syncResult = await processQueue(config, cases);
|
|
919
|
+
log.info(`Synced: ${syncResult.uploaded} uploaded, ${syncResult.failed} failed`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
);
|
|
735
923
|
const shutdown = () => {
|
|
736
924
|
log.info("Shutting down...");
|
|
737
925
|
clearInterval(refreshInterval);
|
|
@@ -746,14 +934,20 @@ async function startWatching(config) {
|
|
|
746
934
|
}
|
|
747
935
|
|
|
748
936
|
// src/cli.ts
|
|
937
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
938
|
+
import { fileURLToPath } from "url";
|
|
939
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
940
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
941
|
+
var __dirname2 = dirname2(__filename2);
|
|
942
|
+
var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf-8"));
|
|
749
943
|
var program = new Command();
|
|
750
|
-
program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(
|
|
944
|
+
program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
|
|
751
945
|
program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
|
|
752
|
-
const rl =
|
|
946
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
753
947
|
try {
|
|
754
|
-
console.log(
|
|
948
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
|
|
755
949
|
const apiUrl = await rl.question(
|
|
756
|
-
` AnrakLegal URL ${
|
|
950
|
+
` AnrakLegal URL ${chalk3.dim("(https://anrak.legal)")}: `
|
|
757
951
|
) || "https://anrak.legal";
|
|
758
952
|
log.info("Connecting to server...");
|
|
759
953
|
const serverConfig = await fetchServerConfig(apiUrl);
|
|
@@ -781,14 +975,14 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
781
975
|
tokens = await browserLogin(apiUrl);
|
|
782
976
|
}
|
|
783
977
|
log.success("Authenticated");
|
|
784
|
-
const rl2 =
|
|
978
|
+
const rl2 = createInterface2({ input: stdin2, output: stdout2 });
|
|
785
979
|
const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
|
|
786
980
|
const watchInput = await rl2.question(
|
|
787
|
-
` Watch folder ${
|
|
981
|
+
` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
|
|
788
982
|
);
|
|
789
|
-
const watchFolder =
|
|
983
|
+
const watchFolder = resolve2(watchInput || defaultFolder);
|
|
790
984
|
rl2.close();
|
|
791
|
-
if (!
|
|
985
|
+
if (!existsSync3(watchFolder)) {
|
|
792
986
|
log.warn(
|
|
793
987
|
`Folder ${watchFolder} does not exist \u2014 it will be created when you add files`
|
|
794
988
|
);
|
|
@@ -810,7 +1004,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
810
1004
|
log.info(`Config saved to ${getConfigDir()}`);
|
|
811
1005
|
log.info(`Watching: ${watchFolder}`);
|
|
812
1006
|
console.log(
|
|
813
|
-
|
|
1007
|
+
chalk3.dim("\n Run `anrak-sync start` to begin syncing\n")
|
|
814
1008
|
);
|
|
815
1009
|
} catch (err) {
|
|
816
1010
|
log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -822,7 +1016,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
|
|
|
822
1016
|
try {
|
|
823
1017
|
let tokens;
|
|
824
1018
|
if (opts.password) {
|
|
825
|
-
const rl =
|
|
1019
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
826
1020
|
try {
|
|
827
1021
|
const email = await rl.question(" Email: ");
|
|
828
1022
|
const password = await rl.question(" Password: ");
|
|
@@ -845,7 +1039,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
|
|
|
845
1039
|
});
|
|
846
1040
|
program.command("start").description("Start watching for file changes and syncing").action(async () => {
|
|
847
1041
|
const config = requireConfig();
|
|
848
|
-
console.log(
|
|
1042
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync\n"));
|
|
849
1043
|
log.info(`Watching: ${config.watchFolder}`);
|
|
850
1044
|
log.info(`Server: ${config.apiUrl}`);
|
|
851
1045
|
console.log("");
|
|
@@ -858,7 +1052,7 @@ program.command("start").description("Start watching for file changes and syncin
|
|
|
858
1052
|
});
|
|
859
1053
|
program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
|
|
860
1054
|
const config = requireConfig();
|
|
861
|
-
console.log(
|
|
1055
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
|
|
862
1056
|
log.info(`Folder: ${config.watchFolder}`);
|
|
863
1057
|
log.info(`Server: ${config.apiUrl}`);
|
|
864
1058
|
console.log("");
|
|
@@ -872,24 +1066,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
|
|
|
872
1066
|
program.command("status").description("Show sync status").action(async () => {
|
|
873
1067
|
const config = requireConfig();
|
|
874
1068
|
const stats = getStats();
|
|
875
|
-
console.log(
|
|
1069
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
|
|
876
1070
|
console.log(` Server: ${config.apiUrl}`);
|
|
877
1071
|
console.log(` Watch folder: ${config.watchFolder}`);
|
|
878
1072
|
console.log(` Config: ${getConfigDir()}`);
|
|
879
1073
|
console.log("");
|
|
880
1074
|
console.log(` Files tracked: ${stats.totalFiles}`);
|
|
881
|
-
console.log(` Synced: ${
|
|
882
|
-
console.log(` Pending: ${
|
|
883
|
-
console.log(` Errors: ${
|
|
1075
|
+
console.log(` Synced: ${chalk3.green(stats.synced)}`);
|
|
1076
|
+
console.log(` Pending: ${chalk3.yellow(stats.pending)}`);
|
|
1077
|
+
console.log(` Errors: ${chalk3.red(stats.errors)}`);
|
|
884
1078
|
console.log(` Mapped folders: ${stats.mappedFolders}`);
|
|
885
1079
|
try {
|
|
886
1080
|
const cases = await listCases(config);
|
|
887
1081
|
console.log(`
|
|
888
1082
|
Server cases: ${cases.length}`);
|
|
889
|
-
console.log(` Auth: ${
|
|
1083
|
+
console.log(` Auth: ${chalk3.green("valid")}`);
|
|
890
1084
|
} catch {
|
|
891
1085
|
console.log(`
|
|
892
|
-
Auth: ${
|
|
1086
|
+
Auth: ${chalk3.red("expired \u2014 run anrak-sync login")}`);
|
|
893
1087
|
}
|
|
894
1088
|
console.log("");
|
|
895
1089
|
});
|
|
@@ -897,13 +1091,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
897
1091
|
const config = requireConfig();
|
|
898
1092
|
const mappings = getAllMappings();
|
|
899
1093
|
const entries = Object.entries(mappings);
|
|
900
|
-
console.log(
|
|
1094
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
|
|
901
1095
|
if (entries.length === 0) {
|
|
902
1096
|
log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
|
|
903
1097
|
} else {
|
|
904
1098
|
for (const [folder, mapping] of entries) {
|
|
905
1099
|
console.log(
|
|
906
|
-
` ${
|
|
1100
|
+
` ${chalk3.cyan(folder)} -> ${mapping.caseNumber} (${chalk3.dim(mapping.caseName)})`
|
|
907
1101
|
);
|
|
908
1102
|
}
|
|
909
1103
|
}
|
|
@@ -912,9 +1106,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
912
1106
|
const mappedIds = new Set(entries.map(([, m]) => m.caseId));
|
|
913
1107
|
const unmapped = cases.filter((c) => !mappedIds.has(c.id));
|
|
914
1108
|
if (unmapped.length > 0) {
|
|
915
|
-
console.log(
|
|
1109
|
+
console.log(chalk3.dim("\n Unmapped server cases:"));
|
|
916
1110
|
for (const c of unmapped) {
|
|
917
|
-
console.log(` ${
|
|
1111
|
+
console.log(` ${chalk3.dim(c.caseNumber)} ${chalk3.dim(c.caseName)}`);
|
|
918
1112
|
}
|
|
919
1113
|
}
|
|
920
1114
|
} catch {
|