@getcodesentinel/codesentinel 1.17.0 → 1.17.2
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/README.md +1 -0
- package/dist/index.js +367 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,12 @@ import { Command, Option } from "commander";
|
|
|
7
7
|
import { existsSync, readFileSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { setTimeout as sleep } from "timers/promises";
|
|
10
|
+
import { join as join4 } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join as join2 } from "path";
|
|
13
|
+
import { createHash } from "crypto";
|
|
14
|
+
import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "fs/promises";
|
|
15
|
+
import { join as join3 } from "path";
|
|
10
16
|
var round4 = (value) => Number(value.toFixed(4));
|
|
11
17
|
var normalizeNodes = (nodes) => {
|
|
12
18
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -614,6 +620,23 @@ var parseLockfileExtraction = (lockfileKind, lockfileRaw, directSpecs) => {
|
|
|
614
620
|
throw new Error("unsupported_lockfile_format");
|
|
615
621
|
}
|
|
616
622
|
};
|
|
623
|
+
var cachedFetch = async (options) => {
|
|
624
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
625
|
+
const cachedEntry = options.cacheStore === null ? null : await options.cacheStore.get(options.key);
|
|
626
|
+
if (cachedEntry !== null && nowMs - cachedEntry.fetchedAtMs <= options.ttlMs) {
|
|
627
|
+
return cachedEntry.value;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
const fresh = await options.fetchFresh();
|
|
631
|
+
if (fresh !== null) {
|
|
632
|
+
void options.cacheStore?.set(options.key, { value: fresh, fetchedAtMs: nowMs }).catch(() => {
|
|
633
|
+
});
|
|
634
|
+
return fresh;
|
|
635
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
return cachedEntry?.value ?? null;
|
|
639
|
+
};
|
|
617
640
|
var parseRetryAfterMs = (value) => {
|
|
618
641
|
if (value === null) {
|
|
619
642
|
return null;
|
|
@@ -640,8 +663,202 @@ var fetchJsonWithRetry = async (url, options) => {
|
|
|
640
663
|
}
|
|
641
664
|
return null;
|
|
642
665
|
};
|
|
666
|
+
var resolveCodesentinelCacheDir = (env = process.env) => {
|
|
667
|
+
const explicit = env["CODESENTINEL_CACHE_DIR"]?.trim();
|
|
668
|
+
if (explicit !== void 0 && explicit.length > 0) {
|
|
669
|
+
return explicit;
|
|
670
|
+
}
|
|
671
|
+
if (process.platform === "win32") {
|
|
672
|
+
const localAppData = env["LOCALAPPDATA"]?.trim();
|
|
673
|
+
if (localAppData !== void 0 && localAppData.length > 0) {
|
|
674
|
+
return join2(localAppData, "codesentinel", "Cache");
|
|
675
|
+
}
|
|
676
|
+
return join2(homedir(), "AppData", "Local", "codesentinel", "Cache");
|
|
677
|
+
}
|
|
678
|
+
const xdgCacheHome = env["XDG_CACHE_HOME"]?.trim();
|
|
679
|
+
if (xdgCacheHome !== void 0 && xdgCacheHome.length > 0) {
|
|
680
|
+
return join2(xdgCacheHome, "codesentinel");
|
|
681
|
+
}
|
|
682
|
+
return join2(homedir(), ".cache", "codesentinel");
|
|
683
|
+
};
|
|
684
|
+
var parseCacheEntryPayload = (value) => {
|
|
685
|
+
if (typeof value !== "object" || value === null) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
const payload = value;
|
|
689
|
+
if (typeof payload.key !== "string" || payload.key.length === 0) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
if (typeof payload.fetchedAtMs !== "number" || !Number.isFinite(payload.fetchedAtMs)) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
key: payload.key,
|
|
697
|
+
fetchedAtMs: payload.fetchedAtMs,
|
|
698
|
+
value: payload.value
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
var DEFAULT_OPTIONS = {
|
|
702
|
+
maxBytes: 100 * 1024 * 1024,
|
|
703
|
+
maxEntryBytes: 4 * 1024 * 1024,
|
|
704
|
+
sweepIntervalWrites: 25
|
|
705
|
+
};
|
|
706
|
+
var FileCacheStore = class {
|
|
707
|
+
constructor(directoryPath, options = DEFAULT_OPTIONS) {
|
|
708
|
+
this.directoryPath = directoryPath;
|
|
709
|
+
this.options = options;
|
|
710
|
+
}
|
|
711
|
+
byKey = /* @__PURE__ */ new Map();
|
|
712
|
+
inFlightWrites = /* @__PURE__ */ new Map();
|
|
713
|
+
writesSinceSweep = 0;
|
|
714
|
+
sweepPromise = Promise.resolve();
|
|
715
|
+
toEntryPath(key) {
|
|
716
|
+
const digest = createHash("sha256").update(key).digest("hex");
|
|
717
|
+
return join3(this.directoryPath, `${digest}.json`);
|
|
718
|
+
}
|
|
719
|
+
async writeEntry(key, entry) {
|
|
720
|
+
const filePath = this.toEntryPath(key);
|
|
721
|
+
await mkdir(this.directoryPath, { recursive: true });
|
|
722
|
+
const tempPath = `${filePath}.tmp`;
|
|
723
|
+
const payload = {
|
|
724
|
+
key,
|
|
725
|
+
fetchedAtMs: entry.fetchedAtMs,
|
|
726
|
+
value: entry.value
|
|
727
|
+
};
|
|
728
|
+
const raw = JSON.stringify(payload);
|
|
729
|
+
if (Buffer.byteLength(raw, "utf8") > this.options.maxEntryBytes) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
await writeFile(tempPath, raw, "utf8");
|
|
733
|
+
await rename(tempPath, filePath);
|
|
734
|
+
}
|
|
735
|
+
async sweepIfNeeded() {
|
|
736
|
+
this.writesSinceSweep += 1;
|
|
737
|
+
if (this.writesSinceSweep < this.options.sweepIntervalWrites) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
this.writesSinceSweep = 0;
|
|
741
|
+
this.sweepPromise = this.sweepPromise.catch(() => {
|
|
742
|
+
}).then(async () => {
|
|
743
|
+
await this.evictToSizeLimit();
|
|
744
|
+
});
|
|
745
|
+
await this.sweepPromise;
|
|
746
|
+
}
|
|
747
|
+
async evictToSizeLimit() {
|
|
748
|
+
let entries;
|
|
749
|
+
try {
|
|
750
|
+
const dirEntries = await readdir(this.directoryPath, { withFileTypes: true });
|
|
751
|
+
entries = (await Promise.all(
|
|
752
|
+
dirEntries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
|
|
753
|
+
const path = join3(this.directoryPath, entry.name);
|
|
754
|
+
const info = await stat(path);
|
|
755
|
+
return { path, size: info.size, mtimeMs: info.mtimeMs };
|
|
756
|
+
})
|
|
757
|
+
)).filter((entry) => Number.isFinite(entry.size) && entry.size > 0);
|
|
758
|
+
} catch {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
let totalBytes = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
762
|
+
if (totalBytes <= this.options.maxBytes) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
766
|
+
for (const entry of entries) {
|
|
767
|
+
if (totalBytes <= this.options.maxBytes) {
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
await unlink(entry.path);
|
|
772
|
+
totalBytes -= entry.size;
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async get(key) {
|
|
778
|
+
if (this.byKey.has(key)) {
|
|
779
|
+
return this.byKey.get(key) ?? null;
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
const raw = await readFile(this.toEntryPath(key), "utf8");
|
|
783
|
+
const parsed = parseCacheEntryPayload(JSON.parse(raw));
|
|
784
|
+
if (parsed === null || parsed.key !== key) {
|
|
785
|
+
this.byKey.set(key, null);
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
const value = { fetchedAtMs: parsed.fetchedAtMs, value: parsed.value };
|
|
789
|
+
this.byKey.set(key, value);
|
|
790
|
+
return value;
|
|
791
|
+
} catch {
|
|
792
|
+
this.byKey.set(key, null);
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async set(key, entry) {
|
|
797
|
+
const normalized = entry;
|
|
798
|
+
this.byKey.set(key, normalized);
|
|
799
|
+
const previous = this.inFlightWrites.get(key) ?? Promise.resolve();
|
|
800
|
+
const next = previous.catch(() => {
|
|
801
|
+
}).then(async () => {
|
|
802
|
+
await this.writeEntry(key, normalized);
|
|
803
|
+
await this.sweepIfNeeded();
|
|
804
|
+
});
|
|
805
|
+
this.inFlightWrites.set(key, next);
|
|
806
|
+
await next;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
var SIX_HOURS_MS = 6 * 60 * 60 * 1e3;
|
|
810
|
+
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
811
|
+
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
812
|
+
var DEFAULT_MAX_ENTRY_BYTES = 4 * 1024 * 1024;
|
|
813
|
+
var DEFAULT_SWEEP_INTERVAL_WRITES = 25;
|
|
814
|
+
var cacheStoreSingleton;
|
|
815
|
+
var parsePositiveIntegerFromEnv = (value, fallback) => {
|
|
816
|
+
if (value === void 0) {
|
|
817
|
+
return fallback;
|
|
818
|
+
}
|
|
819
|
+
const parsed = Number.parseInt(value, 10);
|
|
820
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
821
|
+
return fallback;
|
|
822
|
+
}
|
|
823
|
+
return parsed;
|
|
824
|
+
};
|
|
825
|
+
var cacheDisabled = (env) => {
|
|
826
|
+
const mode = env["CODESENTINEL_CACHE_MODE"]?.trim().toLowerCase();
|
|
827
|
+
return mode === "none";
|
|
828
|
+
};
|
|
829
|
+
var getNpmMetadataCacheStore = () => {
|
|
830
|
+
if (cacheStoreSingleton !== void 0) {
|
|
831
|
+
return cacheStoreSingleton;
|
|
832
|
+
}
|
|
833
|
+
if (cacheDisabled(process.env)) {
|
|
834
|
+
cacheStoreSingleton = null;
|
|
835
|
+
return cacheStoreSingleton;
|
|
836
|
+
}
|
|
837
|
+
const path = join4(resolveCodesentinelCacheDir(process.env), "npm-metadata-v2");
|
|
838
|
+
cacheStoreSingleton = new FileCacheStore(path, {
|
|
839
|
+
maxBytes: parsePositiveIntegerFromEnv(
|
|
840
|
+
process.env["CODESENTINEL_CACHE_MAX_BYTES"],
|
|
841
|
+
DEFAULT_MAX_BYTES
|
|
842
|
+
),
|
|
843
|
+
maxEntryBytes: parsePositiveIntegerFromEnv(
|
|
844
|
+
process.env["CODESENTINEL_CACHE_MAX_ENTRY_BYTES"],
|
|
845
|
+
DEFAULT_MAX_ENTRY_BYTES
|
|
846
|
+
),
|
|
847
|
+
sweepIntervalWrites: parsePositiveIntegerFromEnv(
|
|
848
|
+
process.env["CODESENTINEL_CACHE_SWEEP_INTERVAL_WRITES"],
|
|
849
|
+
DEFAULT_SWEEP_INTERVAL_WRITES
|
|
850
|
+
)
|
|
851
|
+
});
|
|
852
|
+
return cacheStoreSingleton;
|
|
853
|
+
};
|
|
854
|
+
var getPackumentCacheTtlMs = () => parsePositiveIntegerFromEnv(process.env["CODESENTINEL_CACHE_TTL_PACKUMENT_MS"], SIX_HOURS_MS);
|
|
855
|
+
var getWeeklyDownloadsCacheTtlMs = () => parsePositiveIntegerFromEnv(process.env["CODESENTINEL_CACHE_TTL_DOWNLOADS_MS"], ONE_DAY_MS);
|
|
856
|
+
var toMetadataPackumentCacheKey = (name) => `npm:packument:metadata:${name}`;
|
|
857
|
+
var toGraphPackumentCacheKey = (name) => `npm:packument:graph:${name}`;
|
|
858
|
+
var toWeeklyDownloadsCacheKey = (name) => `npm:downloads:last-week:${name}`;
|
|
643
859
|
var MAX_RETRIES = 3;
|
|
644
860
|
var RETRY_BASE_DELAY_MS = 500;
|
|
861
|
+
var PACKUMENT_CACHE_STORE = getNpmMetadataCacheStore();
|
|
645
862
|
var parsePrerelease = (value) => {
|
|
646
863
|
if (value === void 0 || value.length === 0) {
|
|
647
864
|
return [];
|
|
@@ -892,23 +1109,62 @@ var resolveRangeVersion = (versions, requested) => {
|
|
|
892
1109
|
}
|
|
893
1110
|
return null;
|
|
894
1111
|
};
|
|
1112
|
+
var slimPackumentForGraph = (payload) => {
|
|
1113
|
+
const versions = payload.versions ?? {};
|
|
1114
|
+
const dependenciesByVersion = {};
|
|
1115
|
+
const versionNames = Object.keys(versions);
|
|
1116
|
+
for (const [version2, manifest] of Object.entries(versions)) {
|
|
1117
|
+
const dependenciesRaw = manifest?.dependencies ?? {};
|
|
1118
|
+
const dependencies = {};
|
|
1119
|
+
for (const [dependencyName, dependencyRange] of Object.entries(dependenciesRaw)) {
|
|
1120
|
+
if (dependencyName.length > 0 && dependencyRange.length > 0) {
|
|
1121
|
+
dependencies[dependencyName] = dependencyRange;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (Object.keys(dependencies).length > 0) {
|
|
1125
|
+
dependenciesByVersion[version2] = dependencies;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
const slim = {
|
|
1129
|
+
versionNames,
|
|
1130
|
+
dependenciesByVersion
|
|
1131
|
+
};
|
|
1132
|
+
if (payload["dist-tags"] !== void 0) {
|
|
1133
|
+
slim["dist-tags"] = payload["dist-tags"];
|
|
1134
|
+
}
|
|
1135
|
+
return slim;
|
|
1136
|
+
};
|
|
895
1137
|
var fetchPackument = async (name) => {
|
|
896
1138
|
const encodedName = encodeURIComponent(name);
|
|
897
1139
|
try {
|
|
898
|
-
return await
|
|
899
|
-
|
|
900
|
-
|
|
1140
|
+
return await cachedFetch({
|
|
1141
|
+
key: toGraphPackumentCacheKey(name),
|
|
1142
|
+
ttlMs: getPackumentCacheTtlMs(),
|
|
1143
|
+
cacheStore: PACKUMENT_CACHE_STORE,
|
|
1144
|
+
fetchFresh: async () => {
|
|
1145
|
+
const payload = await fetchJsonWithRetry(
|
|
1146
|
+
`https://registry.npmjs.org/${encodedName}`,
|
|
1147
|
+
{
|
|
1148
|
+
retries: MAX_RETRIES,
|
|
1149
|
+
baseDelayMs: RETRY_BASE_DELAY_MS
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
if (payload === null) {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
return slimPackumentForGraph(payload);
|
|
1156
|
+
}
|
|
901
1157
|
});
|
|
902
1158
|
} catch {
|
|
903
1159
|
return null;
|
|
904
1160
|
}
|
|
905
1161
|
};
|
|
906
1162
|
var resolveRequestedVersion = (packument, requested) => {
|
|
907
|
-
const
|
|
908
|
-
const
|
|
1163
|
+
const versionKeys = [...packument.versionNames];
|
|
1164
|
+
const versionSet = new Set(versionKeys);
|
|
909
1165
|
const tags = packument["dist-tags"] ?? {};
|
|
910
1166
|
const latest = tags["latest"];
|
|
911
|
-
if (requested !== null &&
|
|
1167
|
+
if (requested !== null && versionSet.has(requested)) {
|
|
912
1168
|
return {
|
|
913
1169
|
version: requested,
|
|
914
1170
|
resolution: "exact",
|
|
@@ -917,7 +1173,7 @@ var resolveRequestedVersion = (packument, requested) => {
|
|
|
917
1173
|
}
|
|
918
1174
|
if (requested !== null) {
|
|
919
1175
|
const tagged = tags[requested];
|
|
920
|
-
if (tagged !== void 0 &&
|
|
1176
|
+
if (tagged !== void 0 && versionSet.has(tagged)) {
|
|
921
1177
|
return {
|
|
922
1178
|
version: tagged,
|
|
923
1179
|
resolution: "tag",
|
|
@@ -927,7 +1183,7 @@ var resolveRequestedVersion = (packument, requested) => {
|
|
|
927
1183
|
}
|
|
928
1184
|
if (requested !== null) {
|
|
929
1185
|
const matched = resolveRangeVersion(versionKeys, requested);
|
|
930
|
-
if (matched !== null &&
|
|
1186
|
+
if (matched !== null && versionSet.has(matched)) {
|
|
931
1187
|
return {
|
|
932
1188
|
version: matched,
|
|
933
1189
|
resolution: "range",
|
|
@@ -935,7 +1191,7 @@ var resolveRequestedVersion = (packument, requested) => {
|
|
|
935
1191
|
};
|
|
936
1192
|
}
|
|
937
1193
|
}
|
|
938
|
-
if (latest !== void 0 &&
|
|
1194
|
+
if (latest !== void 0 && versionSet.has(latest)) {
|
|
939
1195
|
return {
|
|
940
1196
|
version: latest,
|
|
941
1197
|
resolution: "latest",
|
|
@@ -1016,8 +1272,8 @@ var resolveRegistryGraphFromDirectSpecs = async (directSpecs, options) => {
|
|
|
1016
1272
|
if (nodesByKey.has(nodeKey)) {
|
|
1017
1273
|
continue;
|
|
1018
1274
|
}
|
|
1019
|
-
const
|
|
1020
|
-
const dependencies = Object.entries(
|
|
1275
|
+
const manifestDependencies = packument.dependenciesByVersion[resolved.version] ?? {};
|
|
1276
|
+
const dependencies = Object.entries(manifestDependencies).filter(
|
|
1021
1277
|
([dependencyName, dependencyRange]) => dependencyName.length > 0 && dependencyRange.length > 0
|
|
1022
1278
|
).sort((a, b) => a[0].localeCompare(b[0]));
|
|
1023
1279
|
nodesByKey.set(nodeKey, {
|
|
@@ -1301,7 +1557,7 @@ var analyzeDependencyCandidate = async (input, metadataProvider) => {
|
|
|
1301
1557
|
external
|
|
1302
1558
|
};
|
|
1303
1559
|
};
|
|
1304
|
-
var
|
|
1560
|
+
var ONE_DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
1305
1561
|
var MAX_RETRIES2 = 3;
|
|
1306
1562
|
var RETRY_BASE_DELAY_MS2 = 500;
|
|
1307
1563
|
var round42 = (value) => Number(value.toFixed(4));
|
|
@@ -1314,12 +1570,18 @@ var parseDate = (iso) => {
|
|
|
1314
1570
|
};
|
|
1315
1571
|
var NpmRegistryMetadataProvider = class {
|
|
1316
1572
|
cache = /* @__PURE__ */ new Map();
|
|
1573
|
+
cacheStore = getNpmMetadataCacheStore();
|
|
1317
1574
|
async fetchWeeklyDownloads(name) {
|
|
1318
1575
|
const encodedName = encodeURIComponent(name);
|
|
1319
|
-
const payload = await
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1576
|
+
const payload = await cachedFetch({
|
|
1577
|
+
key: toWeeklyDownloadsCacheKey(name),
|
|
1578
|
+
ttlMs: getWeeklyDownloadsCacheTtlMs(),
|
|
1579
|
+
cacheStore: this.cacheStore,
|
|
1580
|
+
fetchFresh: async () => await fetchJsonWithRetry(
|
|
1581
|
+
`https://api.npmjs.org/downloads/point/last-week/${encodedName}`,
|
|
1582
|
+
{ retries: MAX_RETRIES2, baseDelayMs: RETRY_BASE_DELAY_MS2 }
|
|
1583
|
+
)
|
|
1584
|
+
});
|
|
1323
1585
|
if (payload === null) {
|
|
1324
1586
|
return null;
|
|
1325
1587
|
}
|
|
@@ -1336,10 +1598,31 @@ var NpmRegistryMetadataProvider = class {
|
|
|
1336
1598
|
}
|
|
1337
1599
|
try {
|
|
1338
1600
|
const encodedName = encodeURIComponent(name);
|
|
1339
|
-
const payload = await
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1601
|
+
const payload = await cachedFetch({
|
|
1602
|
+
key: toMetadataPackumentCacheKey(name),
|
|
1603
|
+
ttlMs: getPackumentCacheTtlMs(),
|
|
1604
|
+
cacheStore: this.cacheStore,
|
|
1605
|
+
fetchFresh: async () => {
|
|
1606
|
+
const fresh = await fetchJsonWithRetry(
|
|
1607
|
+
`https://registry.npmjs.org/${encodedName}`,
|
|
1608
|
+
{
|
|
1609
|
+
retries: MAX_RETRIES2,
|
|
1610
|
+
baseDelayMs: RETRY_BASE_DELAY_MS2
|
|
1611
|
+
}
|
|
1612
|
+
);
|
|
1613
|
+
if (fresh === null) {
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
const slim = {};
|
|
1617
|
+
if (fresh.time !== void 0) {
|
|
1618
|
+
slim.time = fresh.time;
|
|
1619
|
+
}
|
|
1620
|
+
if (fresh.maintainers !== void 0) {
|
|
1621
|
+
slim.maintainers = fresh.maintainers;
|
|
1622
|
+
}
|
|
1623
|
+
return slim;
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1343
1626
|
if (payload === null) {
|
|
1344
1627
|
this.cache.set(key, null);
|
|
1345
1628
|
return null;
|
|
@@ -1348,7 +1631,7 @@ var NpmRegistryMetadataProvider = class {
|
|
|
1348
1631
|
const publishDates = Object.entries(timeEntries).filter(([tag]) => tag !== "created" && tag !== "modified").map(([, date]) => parseDate(date)).filter((value) => value !== null).sort((a, b) => a - b);
|
|
1349
1632
|
const modifiedAt = parseDate(timeEntries["modified"]);
|
|
1350
1633
|
const now = Date.now();
|
|
1351
|
-
const daysSinceLastRelease = modifiedAt === null ? null : Math.max(0, round42((now - modifiedAt) /
|
|
1634
|
+
const daysSinceLastRelease = modifiedAt === null ? null : Math.max(0, round42((now - modifiedAt) / ONE_DAY_MS2));
|
|
1352
1635
|
let releaseFrequencyDays = null;
|
|
1353
1636
|
if (publishDates.length >= 2) {
|
|
1354
1637
|
const totalIntervals = publishDates.length - 1;
|
|
@@ -1360,7 +1643,7 @@ var NpmRegistryMetadataProvider = class {
|
|
|
1360
1643
|
sum += current - previous;
|
|
1361
1644
|
}
|
|
1362
1645
|
}
|
|
1363
|
-
releaseFrequencyDays = round42(sum / totalIntervals /
|
|
1646
|
+
releaseFrequencyDays = round42(sum / totalIntervals / ONE_DAY_MS2);
|
|
1364
1647
|
}
|
|
1365
1648
|
const maintainers = payload.maintainers ?? [];
|
|
1366
1649
|
const maintainerCount = maintainers.length > 0 ? maintainers.length : null;
|
|
@@ -1417,6 +1700,21 @@ var toRiskTier = (score) => {
|
|
|
1417
1700
|
}
|
|
1418
1701
|
return "very_high";
|
|
1419
1702
|
};
|
|
1703
|
+
var toHealthTier = (score) => {
|
|
1704
|
+
if (score < 20) {
|
|
1705
|
+
return "critical";
|
|
1706
|
+
}
|
|
1707
|
+
if (score < 40) {
|
|
1708
|
+
return "weak";
|
|
1709
|
+
}
|
|
1710
|
+
if (score < 60) {
|
|
1711
|
+
return "fair";
|
|
1712
|
+
}
|
|
1713
|
+
if (score < 80) {
|
|
1714
|
+
return "good";
|
|
1715
|
+
}
|
|
1716
|
+
return "excellent";
|
|
1717
|
+
};
|
|
1420
1718
|
var factorLabelById = {
|
|
1421
1719
|
"repository.structural": "Structural complexity",
|
|
1422
1720
|
"repository.evolution": "Change volatility",
|
|
@@ -1653,6 +1951,7 @@ var createReport = (snapshot, diff) => {
|
|
|
1653
1951
|
riskScore: snapshot.analysis.risk.riskScore,
|
|
1654
1952
|
normalizedScore: snapshot.analysis.risk.normalizedScore,
|
|
1655
1953
|
riskTier: toRiskTier(snapshot.analysis.risk.riskScore),
|
|
1954
|
+
healthTier: toHealthTier(snapshot.analysis.health.healthScore),
|
|
1656
1955
|
confidence: repositoryConfidence(snapshot),
|
|
1657
1956
|
dimensionScores: repositoryDimensionScores(snapshot)
|
|
1658
1957
|
},
|
|
@@ -1720,6 +2019,7 @@ var renderTextReport = (report) => {
|
|
|
1720
2019
|
lines.push(` riskScore: ${report.repository.riskScore}`);
|
|
1721
2020
|
lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
|
|
1722
2021
|
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
2022
|
+
lines.push(` healthTier: ${report.repository.healthTier}`);
|
|
1723
2023
|
lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
|
|
1724
2024
|
lines.push("");
|
|
1725
2025
|
lines.push("Dimension Scores (0-100)");
|
|
@@ -1813,6 +2113,7 @@ var renderMarkdownReport = (report) => {
|
|
|
1813
2113
|
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
1814
2114
|
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
1815
2115
|
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
2116
|
+
lines.push(`- healthTier: \`${report.repository.healthTier}\``);
|
|
1816
2117
|
lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
|
|
1817
2118
|
lines.push("");
|
|
1818
2119
|
lines.push("## Dimension Scores (0-100)");
|
|
@@ -1925,7 +2226,7 @@ var formatReport = (report, format) => {
|
|
|
1925
2226
|
|
|
1926
2227
|
// ../governance/dist/index.js
|
|
1927
2228
|
import { mkdirSync, rmSync } from "fs";
|
|
1928
|
-
import { basename, join as
|
|
2229
|
+
import { basename, join as join5, resolve } from "path";
|
|
1929
2230
|
import { execFile } from "child_process";
|
|
1930
2231
|
import { promisify } from "util";
|
|
1931
2232
|
var EXIT_CODES = {
|
|
@@ -2446,7 +2747,7 @@ var tryRunGit = async (repositoryPath, args) => {
|
|
|
2446
2747
|
}
|
|
2447
2748
|
};
|
|
2448
2749
|
var buildWorktreePath = (repoRoot, sha) => {
|
|
2449
|
-
const tmpRoot =
|
|
2750
|
+
const tmpRoot = join5(repoRoot, SENTINEL_TMP_DIR, WORKTREE_DIR);
|
|
2450
2751
|
mkdirSync(tmpRoot, { recursive: true });
|
|
2451
2752
|
const baseName = `baseline-${sha.slice(0, 12)}-${process.pid}`;
|
|
2452
2753
|
const candidate = resolve(tmpRoot, baseName);
|
|
@@ -2538,11 +2839,26 @@ var resolveAutoBaselineRef = async (input) => {
|
|
|
2538
2839
|
|
|
2539
2840
|
// src/index.ts
|
|
2540
2841
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2541
|
-
import { readFile as
|
|
2842
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
2542
2843
|
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
2543
2844
|
import { fileURLToPath } from "url";
|
|
2544
2845
|
|
|
2545
2846
|
// src/application/format-analyze-output.ts
|
|
2847
|
+
var toHealthTier2 = (score) => {
|
|
2848
|
+
if (score < 20) {
|
|
2849
|
+
return "critical";
|
|
2850
|
+
}
|
|
2851
|
+
if (score < 40) {
|
|
2852
|
+
return "weak";
|
|
2853
|
+
}
|
|
2854
|
+
if (score < 60) {
|
|
2855
|
+
return "fair";
|
|
2856
|
+
}
|
|
2857
|
+
if (score < 80) {
|
|
2858
|
+
return "good";
|
|
2859
|
+
}
|
|
2860
|
+
return "excellent";
|
|
2861
|
+
};
|
|
2546
2862
|
var createSummaryShape = (summary) => ({
|
|
2547
2863
|
targetPath: summary.structural.targetPath,
|
|
2548
2864
|
structural: summary.structural.metrics,
|
|
@@ -2582,6 +2898,7 @@ var createSummaryShape = (summary) => ({
|
|
|
2582
2898
|
},
|
|
2583
2899
|
health: {
|
|
2584
2900
|
healthScore: summary.health.healthScore,
|
|
2901
|
+
healthTier: toHealthTier2(summary.health.healthScore),
|
|
2585
2902
|
normalizedScore: summary.health.normalizedScore,
|
|
2586
2903
|
dimensions: summary.health.dimensions,
|
|
2587
2904
|
topIssues: summary.health.topIssues.slice(0, 5)
|
|
@@ -2931,13 +3248,13 @@ var parseLogLevel = (value) => {
|
|
|
2931
3248
|
|
|
2932
3249
|
// src/application/check-for-updates.ts
|
|
2933
3250
|
import { spawn } from "child_process";
|
|
2934
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2935
|
-
import { homedir } from "os";
|
|
2936
|
-
import { dirname, join as
|
|
3251
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3252
|
+
import { homedir as homedir2 } from "os";
|
|
3253
|
+
import { dirname, join as join6 } from "path";
|
|
2937
3254
|
import { stderr, stdin } from "process";
|
|
2938
3255
|
import { clearScreenDown, cursorTo, emitKeypressEvents } from "readline";
|
|
2939
3256
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2940
|
-
var UPDATE_CACHE_PATH =
|
|
3257
|
+
var UPDATE_CACHE_PATH = join6(homedir2(), ".cache", "codesentinel", "update-check.json");
|
|
2941
3258
|
var SEMVER_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<prerelease>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2942
3259
|
var ANSI = {
|
|
2943
3260
|
reset: "\x1B[0m",
|
|
@@ -3061,7 +3378,7 @@ var parseNpmViewVersionOutput = (output) => {
|
|
|
3061
3378
|
};
|
|
3062
3379
|
var readCache = async () => {
|
|
3063
3380
|
try {
|
|
3064
|
-
const raw = await
|
|
3381
|
+
const raw = await readFile2(UPDATE_CACHE_PATH, "utf8");
|
|
3065
3382
|
const parsed = JSON.parse(raw);
|
|
3066
3383
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.lastCheckedAt === "string") {
|
|
3067
3384
|
return { lastCheckedAt: parsed.lastCheckedAt };
|
|
@@ -3072,8 +3389,8 @@ var readCache = async () => {
|
|
|
3072
3389
|
return null;
|
|
3073
3390
|
};
|
|
3074
3391
|
var writeCache = async (cache) => {
|
|
3075
|
-
await
|
|
3076
|
-
await
|
|
3392
|
+
await mkdir2(dirname(UPDATE_CACHE_PATH), { recursive: true });
|
|
3393
|
+
await writeFile2(UPDATE_CACHE_PATH, JSON.stringify(cache), "utf8");
|
|
3077
3394
|
};
|
|
3078
3395
|
var shouldRunUpdateCheck = (input) => {
|
|
3079
3396
|
if (!input.isInteractive) {
|
|
@@ -6590,7 +6907,7 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
|
|
|
6590
6907
|
};
|
|
6591
6908
|
|
|
6592
6909
|
// src/application/run-check-command.ts
|
|
6593
|
-
import { readFile as
|
|
6910
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
6594
6911
|
|
|
6595
6912
|
// src/application/build-analysis-snapshot.ts
|
|
6596
6913
|
var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
|
|
@@ -6674,7 +6991,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
6674
6991
|
let diff;
|
|
6675
6992
|
if (options.baselinePath !== void 0) {
|
|
6676
6993
|
logger.info(`loading baseline snapshot: ${options.baselinePath}`);
|
|
6677
|
-
const baselineRaw = await
|
|
6994
|
+
const baselineRaw = await readFile3(options.baselinePath, "utf8");
|
|
6678
6995
|
try {
|
|
6679
6996
|
baseline = parseSnapshot(baselineRaw);
|
|
6680
6997
|
} catch (error) {
|
|
@@ -6700,7 +7017,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
6700
7017
|
options.outputFormat
|
|
6701
7018
|
);
|
|
6702
7019
|
if (options.outputPath !== void 0) {
|
|
6703
|
-
await
|
|
7020
|
+
await writeFile3(options.outputPath, rendered, "utf8");
|
|
6704
7021
|
logger.info(`check output written: ${options.outputPath}`);
|
|
6705
7022
|
}
|
|
6706
7023
|
return {
|
|
@@ -6713,7 +7030,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
6713
7030
|
};
|
|
6714
7031
|
|
|
6715
7032
|
// src/application/run-ci-command.ts
|
|
6716
|
-
import { readFile as
|
|
7033
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
6717
7034
|
import { relative as relative2, resolve as resolve4 } from "path";
|
|
6718
7035
|
var isPathOutsideBase = (value) => {
|
|
6719
7036
|
return value === ".." || value.startsWith("../") || value.startsWith("..\\");
|
|
@@ -6740,7 +7057,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
6740
7057
|
logger
|
|
6741
7058
|
);
|
|
6742
7059
|
if (options.snapshotPath !== void 0) {
|
|
6743
|
-
await
|
|
7060
|
+
await writeFile4(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
|
|
6744
7061
|
logger.info(`snapshot written: ${options.snapshotPath}`);
|
|
6745
7062
|
}
|
|
6746
7063
|
let baseline;
|
|
@@ -6813,7 +7130,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
6813
7130
|
diff = compareSnapshots(current, baseline);
|
|
6814
7131
|
} else if (options.baselinePath !== void 0) {
|
|
6815
7132
|
logger.info(`loading baseline snapshot: ${options.baselinePath}`);
|
|
6816
|
-
const baselineRaw = await
|
|
7133
|
+
const baselineRaw = await readFile4(options.baselinePath, "utf8");
|
|
6817
7134
|
try {
|
|
6818
7135
|
baseline = parseSnapshot(baselineRaw);
|
|
6819
7136
|
} catch (error) {
|
|
@@ -6835,7 +7152,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
6835
7152
|
|
|
6836
7153
|
${ciMarkdown}`;
|
|
6837
7154
|
if (options.reportPath !== void 0) {
|
|
6838
|
-
await
|
|
7155
|
+
await writeFile4(options.reportPath, markdownSummary, "utf8");
|
|
6839
7156
|
logger.info(`report written: ${options.reportPath}`);
|
|
6840
7157
|
}
|
|
6841
7158
|
const machineReadable = {
|
|
@@ -6847,7 +7164,7 @@ ${ciMarkdown}`;
|
|
|
6847
7164
|
exitCode: gateResult.exitCode
|
|
6848
7165
|
};
|
|
6849
7166
|
if (options.jsonOutputPath !== void 0) {
|
|
6850
|
-
await
|
|
7167
|
+
await writeFile4(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
|
|
6851
7168
|
logger.info(`ci machine output written: ${options.jsonOutputPath}`);
|
|
6852
7169
|
}
|
|
6853
7170
|
return {
|
|
@@ -6861,7 +7178,7 @@ ${ciMarkdown}`;
|
|
|
6861
7178
|
};
|
|
6862
7179
|
|
|
6863
7180
|
// src/application/run-report-command.ts
|
|
6864
|
-
import { readFile as
|
|
7181
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
6865
7182
|
var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
|
|
6866
7183
|
logger.info("building analysis snapshot");
|
|
6867
7184
|
const current = await buildAnalysisSnapshot(
|
|
@@ -6875,7 +7192,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
|
|
|
6875
7192
|
logger
|
|
6876
7193
|
);
|
|
6877
7194
|
if (options.snapshotPath !== void 0) {
|
|
6878
|
-
await
|
|
7195
|
+
await writeFile5(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
|
|
6879
7196
|
logger.info(`snapshot written: ${options.snapshotPath}`);
|
|
6880
7197
|
}
|
|
6881
7198
|
let report;
|
|
@@ -6883,14 +7200,14 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
|
|
|
6883
7200
|
report = createReport(current);
|
|
6884
7201
|
} else {
|
|
6885
7202
|
logger.info(`loading baseline snapshot: ${options.comparePath}`);
|
|
6886
|
-
const baselineRaw = await
|
|
7203
|
+
const baselineRaw = await readFile5(options.comparePath, "utf8");
|
|
6887
7204
|
const baseline = parseSnapshot(baselineRaw);
|
|
6888
7205
|
const diff = compareSnapshots(current, baseline);
|
|
6889
7206
|
report = createReport(current, diff);
|
|
6890
7207
|
}
|
|
6891
7208
|
const rendered = formatReport(report, options.format);
|
|
6892
7209
|
if (options.outputPath !== void 0) {
|
|
6893
|
-
await
|
|
7210
|
+
await writeFile5(options.outputPath, rendered, "utf8");
|
|
6894
7211
|
logger.info(`report written: ${options.outputPath}`);
|
|
6895
7212
|
}
|
|
6896
7213
|
return { report, rendered };
|
|
@@ -7012,6 +7329,7 @@ var renderReportHighlightsText = (report) => {
|
|
|
7012
7329
|
lines.push(` healthScore: ${report.health.healthScore}`);
|
|
7013
7330
|
lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
|
|
7014
7331
|
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
7332
|
+
lines.push(` healthTier: ${report.repository.healthTier}`);
|
|
7015
7333
|
lines.push("");
|
|
7016
7334
|
lines.push("Top Hotspots");
|
|
7017
7335
|
for (const hotspot of report.hotspots.slice(0, 5)) {
|
|
@@ -7028,6 +7346,7 @@ var renderReportHighlightsMarkdown = (report) => {
|
|
|
7028
7346
|
lines.push(`- healthScore: \`${report.health.healthScore}\``);
|
|
7029
7347
|
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
7030
7348
|
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
7349
|
+
lines.push(`- healthTier: \`${report.repository.healthTier}\``);
|
|
7031
7350
|
lines.push("");
|
|
7032
7351
|
lines.push("## Top Hotspots");
|
|
7033
7352
|
for (const hotspot of report.hotspots.slice(0, 5)) {
|
|
@@ -7045,6 +7364,7 @@ var renderCompactText = (report, explainSummary) => {
|
|
|
7045
7364
|
lines.push(` riskScore: ${report.repository.riskScore}`);
|
|
7046
7365
|
lines.push(` healthScore: ${report.health.healthScore}`);
|
|
7047
7366
|
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
7367
|
+
lines.push(` healthTier: ${report.repository.healthTier}`);
|
|
7048
7368
|
lines.push(
|
|
7049
7369
|
` dimensions: structural=${report.repository.dimensionScores.structural ?? "n/a"}, evolution=${report.repository.dimensionScores.evolution ?? "n/a"}, external=${report.repository.dimensionScores.external ?? "n/a"}, interactions=${report.repository.dimensionScores.interactions ?? "n/a"}`
|
|
7050
7370
|
);
|
|
@@ -7071,6 +7391,7 @@ var renderCompactMarkdown = (report, explainSummary) => {
|
|
|
7071
7391
|
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
7072
7392
|
lines.push(`- healthScore: \`${report.health.healthScore}\``);
|
|
7073
7393
|
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
7394
|
+
lines.push(`- healthTier: \`${report.repository.healthTier}\``);
|
|
7074
7395
|
lines.push(
|
|
7075
7396
|
`- dimensions: structural=\`${report.repository.dimensionScores.structural ?? "n/a"}\`, evolution=\`${report.repository.dimensionScores.evolution ?? "n/a"}\`, external=\`${report.repository.dimensionScores.external ?? "n/a"}\`, interactions=\`${report.repository.dimensionScores.interactions ?? "n/a"}\``
|
|
7076
7397
|
);
|
|
@@ -7269,12 +7590,12 @@ program.command("run").argument("[path]", "path to the project to analyze").addO
|
|
|
7269
7590
|
...options.trace === true ? { trace: explain.trace } : {}
|
|
7270
7591
|
});
|
|
7271
7592
|
if (options.snapshot !== void 0) {
|
|
7272
|
-
await
|
|
7593
|
+
await writeFile6(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
|
|
7273
7594
|
logger.info(`snapshot written: ${options.snapshot}`);
|
|
7274
7595
|
}
|
|
7275
7596
|
const report = options.compare === void 0 ? createReport(snapshot) : createReport(
|
|
7276
7597
|
snapshot,
|
|
7277
|
-
compareSnapshots(snapshot, parseSnapshot(await
|
|
7598
|
+
compareSnapshots(snapshot, parseSnapshot(await readFile6(options.compare, "utf8")))
|
|
7278
7599
|
);
|
|
7279
7600
|
if (options.format === "json") {
|
|
7280
7601
|
const analyzeSummaryOutput = formatAnalyzeOutput(explain.summary, "summary");
|