@ceon-oy/monitor-sdk 1.1.1 → 1.1.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/dist/index.d.mts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +113 -60
- package/dist/index.mjs +113 -60
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -198,8 +198,8 @@ declare class MonitorClient {
|
|
|
198
198
|
*/
|
|
199
199
|
private sanitizePath;
|
|
200
200
|
/**
|
|
201
|
-
* Execute a promise with a timeout. Properly cleans up the timer
|
|
202
|
-
* @param
|
|
201
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
202
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
203
203
|
* @param timeoutMs Timeout in milliseconds
|
|
204
204
|
* @param message Error message if timeout occurs
|
|
205
205
|
*/
|
|
@@ -249,6 +249,8 @@ declare class MonitorClient {
|
|
|
249
249
|
/**
|
|
250
250
|
* Enrich technologies with latest version information from npm registry.
|
|
251
251
|
* Only runs if versionCheckEnabled is true.
|
|
252
|
+
* @param technologies List of technologies to enrich
|
|
253
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
252
254
|
*/
|
|
253
255
|
private enrichWithLatestVersions;
|
|
254
256
|
syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
|
|
@@ -259,10 +261,14 @@ declare class MonitorClient {
|
|
|
259
261
|
* Fetch the latest version of a package from npm registry.
|
|
260
262
|
* Returns null if the package cannot be found or the request fails.
|
|
261
263
|
* Uses npmRegistryUrl config option if provided.
|
|
264
|
+
* @param packageName The package name to fetch
|
|
265
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
262
266
|
*/
|
|
263
267
|
private fetchLatestVersion;
|
|
264
268
|
/**
|
|
265
269
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
270
|
+
* @param packageNames List of package names to fetch versions for
|
|
271
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
266
272
|
*/
|
|
267
273
|
private fetchLatestVersions;
|
|
268
274
|
private sendTechnologies;
|
|
@@ -332,6 +338,11 @@ declare class MonitorClient {
|
|
|
332
338
|
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
333
339
|
*/
|
|
334
340
|
private handleAuditExecError;
|
|
341
|
+
/**
|
|
342
|
+
* Run a command with a reliable timeout using spawn.
|
|
343
|
+
* More reliable than execSync timeout in containerized environments.
|
|
344
|
+
*/
|
|
345
|
+
private runCommandWithTimeout;
|
|
335
346
|
/**
|
|
336
347
|
* Run npm audit and send results to the monitoring server.
|
|
337
348
|
* This scans the project for known vulnerabilities in dependencies.
|
package/dist/index.d.ts
CHANGED
|
@@ -198,8 +198,8 @@ declare class MonitorClient {
|
|
|
198
198
|
*/
|
|
199
199
|
private sanitizePath;
|
|
200
200
|
/**
|
|
201
|
-
* Execute a promise with a timeout. Properly cleans up the timer
|
|
202
|
-
* @param
|
|
201
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
202
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
203
203
|
* @param timeoutMs Timeout in milliseconds
|
|
204
204
|
* @param message Error message if timeout occurs
|
|
205
205
|
*/
|
|
@@ -249,6 +249,8 @@ declare class MonitorClient {
|
|
|
249
249
|
/**
|
|
250
250
|
* Enrich technologies with latest version information from npm registry.
|
|
251
251
|
* Only runs if versionCheckEnabled is true.
|
|
252
|
+
* @param technologies List of technologies to enrich
|
|
253
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
252
254
|
*/
|
|
253
255
|
private enrichWithLatestVersions;
|
|
254
256
|
syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
|
|
@@ -259,10 +261,14 @@ declare class MonitorClient {
|
|
|
259
261
|
* Fetch the latest version of a package from npm registry.
|
|
260
262
|
* Returns null if the package cannot be found or the request fails.
|
|
261
263
|
* Uses npmRegistryUrl config option if provided.
|
|
264
|
+
* @param packageName The package name to fetch
|
|
265
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
262
266
|
*/
|
|
263
267
|
private fetchLatestVersion;
|
|
264
268
|
/**
|
|
265
269
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
270
|
+
* @param packageNames List of package names to fetch versions for
|
|
271
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
266
272
|
*/
|
|
267
273
|
private fetchLatestVersions;
|
|
268
274
|
private sendTechnologies;
|
|
@@ -332,6 +338,11 @@ declare class MonitorClient {
|
|
|
332
338
|
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
333
339
|
*/
|
|
334
340
|
private handleAuditExecError;
|
|
341
|
+
/**
|
|
342
|
+
* Run a command with a reliable timeout using spawn.
|
|
343
|
+
* More reliable than execSync timeout in containerized environments.
|
|
344
|
+
*/
|
|
345
|
+
private runCommandWithTimeout;
|
|
335
346
|
/**
|
|
336
347
|
* Run npm audit and send results to the monitoring server.
|
|
337
348
|
* This scans the project for known vulnerabilities in dependencies.
|
package/dist/index.js
CHANGED
|
@@ -199,17 +199,21 @@ var MonitorClient = class {
|
|
|
199
199
|
return ".../" + segments.slice(-maxSegments).join("/");
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
|
-
* Execute a promise with a timeout. Properly cleans up the timer
|
|
203
|
-
* @param
|
|
202
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
203
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
204
204
|
* @param timeoutMs Timeout in milliseconds
|
|
205
205
|
* @param message Error message if timeout occurs
|
|
206
206
|
*/
|
|
207
|
-
withTimeout(
|
|
207
|
+
withTimeout(promiseFactory, timeoutMs, message) {
|
|
208
|
+
const controller = new AbortController();
|
|
208
209
|
let timeoutId;
|
|
209
210
|
const timeoutPromise = new Promise((_, reject) => {
|
|
210
|
-
timeoutId = setTimeout(() =>
|
|
211
|
+
timeoutId = setTimeout(() => {
|
|
212
|
+
controller.abort();
|
|
213
|
+
reject(new Error(message));
|
|
214
|
+
}, timeoutMs);
|
|
211
215
|
});
|
|
212
|
-
return Promise.race([
|
|
216
|
+
return Promise.race([promiseFactory(controller.signal), timeoutPromise]).finally(() => {
|
|
213
217
|
clearTimeout(timeoutId);
|
|
214
218
|
});
|
|
215
219
|
}
|
|
@@ -493,7 +497,7 @@ var MonitorClient = class {
|
|
|
493
497
|
console.log("[MonitorClient] Starting technology sync...");
|
|
494
498
|
try {
|
|
495
499
|
await this.withTimeout(
|
|
496
|
-
this.performDependencySync(),
|
|
500
|
+
(signal) => this.performDependencySync(signal),
|
|
497
501
|
CONFIG_LIMITS.SYNC_DEPENDENCIES_TIMEOUT_MS,
|
|
498
502
|
"Technology sync timed out"
|
|
499
503
|
);
|
|
@@ -502,19 +506,23 @@ var MonitorClient = class {
|
|
|
502
506
|
console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
|
|
503
507
|
}
|
|
504
508
|
}
|
|
505
|
-
async performDependencySync() {
|
|
509
|
+
async performDependencySync(signal) {
|
|
506
510
|
if (this.dependencySources && this.dependencySources.length > 0) {
|
|
507
511
|
for (const source of this.dependencySources) {
|
|
512
|
+
if (signal?.aborted) {
|
|
513
|
+
console.log("[MonitorClient] Technology sync cancelled");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
508
516
|
const technologies = await this.readPackageJsonFromPath(source.path);
|
|
509
517
|
if (technologies.length === 0) continue;
|
|
510
|
-
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
|
|
518
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
511
519
|
await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
|
|
512
520
|
console.log(`[MonitorClient] Technology sync completed for ${source.environment}`);
|
|
513
521
|
}
|
|
514
522
|
} else {
|
|
515
523
|
const technologies = await this.readPackageJson();
|
|
516
524
|
if (technologies.length === 0) return;
|
|
517
|
-
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
|
|
525
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
518
526
|
await this.sendTechnologies(enrichedTechnologies);
|
|
519
527
|
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
520
528
|
}
|
|
@@ -522,19 +530,20 @@ var MonitorClient = class {
|
|
|
522
530
|
/**
|
|
523
531
|
* Enrich technologies with latest version information from npm registry.
|
|
524
532
|
* Only runs if versionCheckEnabled is true.
|
|
533
|
+
* @param technologies List of technologies to enrich
|
|
534
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
525
535
|
*/
|
|
526
|
-
async enrichWithLatestVersions(technologies) {
|
|
536
|
+
async enrichWithLatestVersions(technologies, signal) {
|
|
527
537
|
if (!this.versionCheckEnabled) {
|
|
528
538
|
return technologies;
|
|
529
539
|
}
|
|
540
|
+
if (signal?.aborted) {
|
|
541
|
+
return technologies;
|
|
542
|
+
}
|
|
530
543
|
try {
|
|
531
544
|
console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
|
|
532
545
|
const packageNames = technologies.map((t) => t.name);
|
|
533
|
-
const latestVersions = await this.
|
|
534
|
-
this.fetchLatestVersions(packageNames),
|
|
535
|
-
CONFIG_LIMITS.TECH_VERSION_FETCH_TIMEOUT_MS,
|
|
536
|
-
"Version fetch timed out after 30 seconds"
|
|
537
|
-
);
|
|
546
|
+
const latestVersions = await this.fetchLatestVersions(packageNames, signal);
|
|
538
547
|
const foundCount = [...latestVersions.values()].filter((v) => v !== null).length;
|
|
539
548
|
console.log(`[MonitorClient] Fetched latest versions: ${foundCount}/${technologies.length} packages`);
|
|
540
549
|
return technologies.map((tech) => ({
|
|
@@ -616,13 +625,16 @@ var MonitorClient = class {
|
|
|
616
625
|
* Fetch the latest version of a package from npm registry.
|
|
617
626
|
* Returns null if the package cannot be found or the request fails.
|
|
618
627
|
* Uses npmRegistryUrl config option if provided.
|
|
628
|
+
* @param packageName The package name to fetch
|
|
629
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
619
630
|
*/
|
|
620
|
-
async fetchLatestVersion(packageName) {
|
|
631
|
+
async fetchLatestVersion(packageName, signal) {
|
|
621
632
|
try {
|
|
633
|
+
if (signal?.aborted) return null;
|
|
622
634
|
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
623
635
|
const response = await fetch(`${this.npmRegistryUrl}/${encodedName}`, {
|
|
624
636
|
headers: { "Accept": "application/json" },
|
|
625
|
-
signal: AbortSignal.timeout(this.registryTimeoutMs)
|
|
637
|
+
signal: signal ?? AbortSignal.timeout(this.registryTimeoutMs)
|
|
626
638
|
});
|
|
627
639
|
if (!response.ok) return null;
|
|
628
640
|
const data = await response.json();
|
|
@@ -633,19 +645,25 @@ var MonitorClient = class {
|
|
|
633
645
|
}
|
|
634
646
|
/**
|
|
635
647
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
648
|
+
* @param packageNames List of package names to fetch versions for
|
|
649
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
636
650
|
*/
|
|
637
|
-
async fetchLatestVersions(packageNames) {
|
|
651
|
+
async fetchLatestVersions(packageNames, signal) {
|
|
638
652
|
const results = /* @__PURE__ */ new Map();
|
|
639
653
|
const concurrencyLimit = CONFIG_LIMITS.REGISTRY_CONCURRENCY_LIMIT;
|
|
640
654
|
const totalBatches = Math.ceil(packageNames.length / concurrencyLimit);
|
|
641
655
|
for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
|
|
656
|
+
if (signal?.aborted) {
|
|
657
|
+
console.log("[MonitorClient] Version fetch cancelled");
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
642
660
|
const batchNumber = Math.floor(i / concurrencyLimit) + 1;
|
|
643
661
|
console.log(`[MonitorClient] Fetching versions batch ${batchNumber}/${totalBatches}...`);
|
|
644
662
|
const batch = packageNames.slice(i, i + concurrencyLimit);
|
|
645
663
|
const batchResults = await Promise.all(
|
|
646
664
|
batch.map(async (name) => ({
|
|
647
665
|
name,
|
|
648
|
-
version: await this.fetchLatestVersion(name)
|
|
666
|
+
version: await this.fetchLatestVersion(name, signal)
|
|
649
667
|
}))
|
|
650
668
|
);
|
|
651
669
|
for (const { name, version } of batchResults) {
|
|
@@ -793,6 +811,53 @@ var MonitorClient = class {
|
|
|
793
811
|
}
|
|
794
812
|
throw err;
|
|
795
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Run a command with a reliable timeout using spawn.
|
|
816
|
+
* More reliable than execSync timeout in containerized environments.
|
|
817
|
+
*/
|
|
818
|
+
async runCommandWithTimeout(command, args, options) {
|
|
819
|
+
const childProcess = await import("child_process");
|
|
820
|
+
const { spawn } = childProcess;
|
|
821
|
+
return new Promise((resolve) => {
|
|
822
|
+
const proc = spawn(command, args, {
|
|
823
|
+
cwd: options.cwd,
|
|
824
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
825
|
+
shell: false
|
|
826
|
+
});
|
|
827
|
+
let stdout = "";
|
|
828
|
+
let stderr = "";
|
|
829
|
+
let timedOut = false;
|
|
830
|
+
let killed = false;
|
|
831
|
+
const timeoutId = setTimeout(() => {
|
|
832
|
+
timedOut = true;
|
|
833
|
+
killed = true;
|
|
834
|
+
proc.kill("SIGTERM");
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
if (!proc.killed) {
|
|
837
|
+
proc.kill("SIGKILL");
|
|
838
|
+
}
|
|
839
|
+
}, 1e3);
|
|
840
|
+
}, options.timeout);
|
|
841
|
+
proc.stdout?.on("data", (data) => {
|
|
842
|
+
if (stdout.length < options.maxBuffer) {
|
|
843
|
+
stdout += data.toString();
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
proc.stderr?.on("data", (data) => {
|
|
847
|
+
if (stderr.length < options.maxBuffer) {
|
|
848
|
+
stderr += data.toString();
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
proc.on("close", () => {
|
|
852
|
+
clearTimeout(timeoutId);
|
|
853
|
+
resolve({ stdout, timedOut });
|
|
854
|
+
});
|
|
855
|
+
proc.on("error", () => {
|
|
856
|
+
clearTimeout(timeoutId);
|
|
857
|
+
resolve({ stdout, timedOut: killed });
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
}
|
|
796
861
|
/**
|
|
797
862
|
* Run npm audit and send results to the monitoring server.
|
|
798
863
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -806,14 +871,11 @@ var MonitorClient = class {
|
|
|
806
871
|
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
807
872
|
return null;
|
|
808
873
|
}
|
|
809
|
-
let execSync;
|
|
810
874
|
let path;
|
|
811
875
|
let fs;
|
|
812
876
|
try {
|
|
813
|
-
const childProcess = await import("child_process");
|
|
814
877
|
const pathModule = await import("path");
|
|
815
878
|
const fsModule = await import("fs");
|
|
816
|
-
execSync = childProcess.execSync;
|
|
817
879
|
path = pathModule;
|
|
818
880
|
fs = fsModule;
|
|
819
881
|
} catch {
|
|
@@ -851,24 +913,21 @@ var MonitorClient = class {
|
|
|
851
913
|
return null;
|
|
852
914
|
}
|
|
853
915
|
const packageManager = this.detectPackageManager(projectPath, fs, path);
|
|
854
|
-
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager}`);
|
|
916
|
+
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager} (timeout: ${this.auditTimeoutMs}ms)`);
|
|
855
917
|
let auditOutput;
|
|
856
918
|
let vulnerabilities;
|
|
857
919
|
let totalDeps = 0;
|
|
858
920
|
if (packageManager === "yarn") {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
} catch (err) {
|
|
868
|
-
const output = this.handleAuditExecError(err, "yarn");
|
|
869
|
-
if (output === null) return null;
|
|
870
|
-
auditOutput = output;
|
|
921
|
+
const result2 = await this.runCommandWithTimeout("yarn", ["audit", "--json"], {
|
|
922
|
+
cwd: projectPath,
|
|
923
|
+
timeout: this.auditTimeoutMs,
|
|
924
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
925
|
+
});
|
|
926
|
+
if (result2.timedOut) {
|
|
927
|
+
console.error(`[MonitorClient] yarn audit timed out after ${this.auditTimeoutMs}ms`);
|
|
928
|
+
return null;
|
|
871
929
|
}
|
|
930
|
+
auditOutput = result2.stdout;
|
|
872
931
|
vulnerabilities = this.parseYarnAuditOutput(auditOutput);
|
|
873
932
|
const lines = auditOutput.trim().split("\n");
|
|
874
933
|
for (const line of lines) {
|
|
@@ -883,36 +942,30 @@ var MonitorClient = class {
|
|
|
883
942
|
}
|
|
884
943
|
} else if (packageManager === "pnpm") {
|
|
885
944
|
console.log("[MonitorClient] pnpm detected, using npm audit (pnpm compatible)");
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
} catch (err) {
|
|
895
|
-
const output = this.handleAuditExecError(err, "npm");
|
|
896
|
-
if (output === null) return null;
|
|
897
|
-
auditOutput = output;
|
|
945
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
946
|
+
cwd: projectPath,
|
|
947
|
+
timeout: this.auditTimeoutMs,
|
|
948
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
949
|
+
});
|
|
950
|
+
if (result2.timedOut) {
|
|
951
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
952
|
+
return null;
|
|
898
953
|
}
|
|
954
|
+
auditOutput = result2.stdout;
|
|
899
955
|
const auditData = JSON.parse(auditOutput);
|
|
900
956
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
901
957
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
902
958
|
} else {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
} catch (err) {
|
|
912
|
-
const output = this.handleAuditExecError(err, "npm");
|
|
913
|
-
if (output === null) return null;
|
|
914
|
-
auditOutput = output;
|
|
959
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
960
|
+
cwd: projectPath,
|
|
961
|
+
timeout: this.auditTimeoutMs,
|
|
962
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
963
|
+
});
|
|
964
|
+
if (result2.timedOut) {
|
|
965
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
966
|
+
return null;
|
|
915
967
|
}
|
|
968
|
+
auditOutput = result2.stdout;
|
|
916
969
|
const auditData = JSON.parse(auditOutput);
|
|
917
970
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
918
971
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
@@ -959,7 +1012,7 @@ var MonitorClient = class {
|
|
|
959
1012
|
console.log(`[MonitorClient] Starting multi-path audit (${this.auditPaths.length} paths)...`);
|
|
960
1013
|
try {
|
|
961
1014
|
const result = await this.withTimeout(
|
|
962
|
-
this.performMultiPathAudit(),
|
|
1015
|
+
() => this.performMultiPathAudit(),
|
|
963
1016
|
CONFIG_LIMITS.AUDIT_MULTI_PATH_TIMEOUT_MS,
|
|
964
1017
|
"Multi-path audit timed out"
|
|
965
1018
|
);
|
package/dist/index.mjs
CHANGED
|
@@ -163,17 +163,21 @@ var MonitorClient = class {
|
|
|
163
163
|
return ".../" + segments.slice(-maxSegments).join("/");
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
|
-
* Execute a promise with a timeout. Properly cleans up the timer
|
|
167
|
-
* @param
|
|
166
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
167
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
168
168
|
* @param timeoutMs Timeout in milliseconds
|
|
169
169
|
* @param message Error message if timeout occurs
|
|
170
170
|
*/
|
|
171
|
-
withTimeout(
|
|
171
|
+
withTimeout(promiseFactory, timeoutMs, message) {
|
|
172
|
+
const controller = new AbortController();
|
|
172
173
|
let timeoutId;
|
|
173
174
|
const timeoutPromise = new Promise((_, reject) => {
|
|
174
|
-
timeoutId = setTimeout(() =>
|
|
175
|
+
timeoutId = setTimeout(() => {
|
|
176
|
+
controller.abort();
|
|
177
|
+
reject(new Error(message));
|
|
178
|
+
}, timeoutMs);
|
|
175
179
|
});
|
|
176
|
-
return Promise.race([
|
|
180
|
+
return Promise.race([promiseFactory(controller.signal), timeoutPromise]).finally(() => {
|
|
177
181
|
clearTimeout(timeoutId);
|
|
178
182
|
});
|
|
179
183
|
}
|
|
@@ -457,7 +461,7 @@ var MonitorClient = class {
|
|
|
457
461
|
console.log("[MonitorClient] Starting technology sync...");
|
|
458
462
|
try {
|
|
459
463
|
await this.withTimeout(
|
|
460
|
-
this.performDependencySync(),
|
|
464
|
+
(signal) => this.performDependencySync(signal),
|
|
461
465
|
CONFIG_LIMITS.SYNC_DEPENDENCIES_TIMEOUT_MS,
|
|
462
466
|
"Technology sync timed out"
|
|
463
467
|
);
|
|
@@ -466,19 +470,23 @@ var MonitorClient = class {
|
|
|
466
470
|
console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
|
|
467
471
|
}
|
|
468
472
|
}
|
|
469
|
-
async performDependencySync() {
|
|
473
|
+
async performDependencySync(signal) {
|
|
470
474
|
if (this.dependencySources && this.dependencySources.length > 0) {
|
|
471
475
|
for (const source of this.dependencySources) {
|
|
476
|
+
if (signal?.aborted) {
|
|
477
|
+
console.log("[MonitorClient] Technology sync cancelled");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
472
480
|
const technologies = await this.readPackageJsonFromPath(source.path);
|
|
473
481
|
if (technologies.length === 0) continue;
|
|
474
|
-
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
|
|
482
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
475
483
|
await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
|
|
476
484
|
console.log(`[MonitorClient] Technology sync completed for ${source.environment}`);
|
|
477
485
|
}
|
|
478
486
|
} else {
|
|
479
487
|
const technologies = await this.readPackageJson();
|
|
480
488
|
if (technologies.length === 0) return;
|
|
481
|
-
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
|
|
489
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
482
490
|
await this.sendTechnologies(enrichedTechnologies);
|
|
483
491
|
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
484
492
|
}
|
|
@@ -486,19 +494,20 @@ var MonitorClient = class {
|
|
|
486
494
|
/**
|
|
487
495
|
* Enrich technologies with latest version information from npm registry.
|
|
488
496
|
* Only runs if versionCheckEnabled is true.
|
|
497
|
+
* @param technologies List of technologies to enrich
|
|
498
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
489
499
|
*/
|
|
490
|
-
async enrichWithLatestVersions(technologies) {
|
|
500
|
+
async enrichWithLatestVersions(technologies, signal) {
|
|
491
501
|
if (!this.versionCheckEnabled) {
|
|
492
502
|
return technologies;
|
|
493
503
|
}
|
|
504
|
+
if (signal?.aborted) {
|
|
505
|
+
return technologies;
|
|
506
|
+
}
|
|
494
507
|
try {
|
|
495
508
|
console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
|
|
496
509
|
const packageNames = technologies.map((t) => t.name);
|
|
497
|
-
const latestVersions = await this.
|
|
498
|
-
this.fetchLatestVersions(packageNames),
|
|
499
|
-
CONFIG_LIMITS.TECH_VERSION_FETCH_TIMEOUT_MS,
|
|
500
|
-
"Version fetch timed out after 30 seconds"
|
|
501
|
-
);
|
|
510
|
+
const latestVersions = await this.fetchLatestVersions(packageNames, signal);
|
|
502
511
|
const foundCount = [...latestVersions.values()].filter((v) => v !== null).length;
|
|
503
512
|
console.log(`[MonitorClient] Fetched latest versions: ${foundCount}/${technologies.length} packages`);
|
|
504
513
|
return technologies.map((tech) => ({
|
|
@@ -580,13 +589,16 @@ var MonitorClient = class {
|
|
|
580
589
|
* Fetch the latest version of a package from npm registry.
|
|
581
590
|
* Returns null if the package cannot be found or the request fails.
|
|
582
591
|
* Uses npmRegistryUrl config option if provided.
|
|
592
|
+
* @param packageName The package name to fetch
|
|
593
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
583
594
|
*/
|
|
584
|
-
async fetchLatestVersion(packageName) {
|
|
595
|
+
async fetchLatestVersion(packageName, signal) {
|
|
585
596
|
try {
|
|
597
|
+
if (signal?.aborted) return null;
|
|
586
598
|
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
587
599
|
const response = await fetch(`${this.npmRegistryUrl}/${encodedName}`, {
|
|
588
600
|
headers: { "Accept": "application/json" },
|
|
589
|
-
signal: AbortSignal.timeout(this.registryTimeoutMs)
|
|
601
|
+
signal: signal ?? AbortSignal.timeout(this.registryTimeoutMs)
|
|
590
602
|
});
|
|
591
603
|
if (!response.ok) return null;
|
|
592
604
|
const data = await response.json();
|
|
@@ -597,19 +609,25 @@ var MonitorClient = class {
|
|
|
597
609
|
}
|
|
598
610
|
/**
|
|
599
611
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
612
|
+
* @param packageNames List of package names to fetch versions for
|
|
613
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
600
614
|
*/
|
|
601
|
-
async fetchLatestVersions(packageNames) {
|
|
615
|
+
async fetchLatestVersions(packageNames, signal) {
|
|
602
616
|
const results = /* @__PURE__ */ new Map();
|
|
603
617
|
const concurrencyLimit = CONFIG_LIMITS.REGISTRY_CONCURRENCY_LIMIT;
|
|
604
618
|
const totalBatches = Math.ceil(packageNames.length / concurrencyLimit);
|
|
605
619
|
for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
|
|
620
|
+
if (signal?.aborted) {
|
|
621
|
+
console.log("[MonitorClient] Version fetch cancelled");
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
606
624
|
const batchNumber = Math.floor(i / concurrencyLimit) + 1;
|
|
607
625
|
console.log(`[MonitorClient] Fetching versions batch ${batchNumber}/${totalBatches}...`);
|
|
608
626
|
const batch = packageNames.slice(i, i + concurrencyLimit);
|
|
609
627
|
const batchResults = await Promise.all(
|
|
610
628
|
batch.map(async (name) => ({
|
|
611
629
|
name,
|
|
612
|
-
version: await this.fetchLatestVersion(name)
|
|
630
|
+
version: await this.fetchLatestVersion(name, signal)
|
|
613
631
|
}))
|
|
614
632
|
);
|
|
615
633
|
for (const { name, version } of batchResults) {
|
|
@@ -757,6 +775,53 @@ var MonitorClient = class {
|
|
|
757
775
|
}
|
|
758
776
|
throw err;
|
|
759
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* Run a command with a reliable timeout using spawn.
|
|
780
|
+
* More reliable than execSync timeout in containerized environments.
|
|
781
|
+
*/
|
|
782
|
+
async runCommandWithTimeout(command, args, options) {
|
|
783
|
+
const childProcess = await import("child_process");
|
|
784
|
+
const { spawn } = childProcess;
|
|
785
|
+
return new Promise((resolve) => {
|
|
786
|
+
const proc = spawn(command, args, {
|
|
787
|
+
cwd: options.cwd,
|
|
788
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
789
|
+
shell: false
|
|
790
|
+
});
|
|
791
|
+
let stdout = "";
|
|
792
|
+
let stderr = "";
|
|
793
|
+
let timedOut = false;
|
|
794
|
+
let killed = false;
|
|
795
|
+
const timeoutId = setTimeout(() => {
|
|
796
|
+
timedOut = true;
|
|
797
|
+
killed = true;
|
|
798
|
+
proc.kill("SIGTERM");
|
|
799
|
+
setTimeout(() => {
|
|
800
|
+
if (!proc.killed) {
|
|
801
|
+
proc.kill("SIGKILL");
|
|
802
|
+
}
|
|
803
|
+
}, 1e3);
|
|
804
|
+
}, options.timeout);
|
|
805
|
+
proc.stdout?.on("data", (data) => {
|
|
806
|
+
if (stdout.length < options.maxBuffer) {
|
|
807
|
+
stdout += data.toString();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
proc.stderr?.on("data", (data) => {
|
|
811
|
+
if (stderr.length < options.maxBuffer) {
|
|
812
|
+
stderr += data.toString();
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
proc.on("close", () => {
|
|
816
|
+
clearTimeout(timeoutId);
|
|
817
|
+
resolve({ stdout, timedOut });
|
|
818
|
+
});
|
|
819
|
+
proc.on("error", () => {
|
|
820
|
+
clearTimeout(timeoutId);
|
|
821
|
+
resolve({ stdout, timedOut: killed });
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
}
|
|
760
825
|
/**
|
|
761
826
|
* Run npm audit and send results to the monitoring server.
|
|
762
827
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -770,14 +835,11 @@ var MonitorClient = class {
|
|
|
770
835
|
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
771
836
|
return null;
|
|
772
837
|
}
|
|
773
|
-
let execSync;
|
|
774
838
|
let path;
|
|
775
839
|
let fs;
|
|
776
840
|
try {
|
|
777
|
-
const childProcess = await import("child_process");
|
|
778
841
|
const pathModule = await import("path");
|
|
779
842
|
const fsModule = await import("fs");
|
|
780
|
-
execSync = childProcess.execSync;
|
|
781
843
|
path = pathModule;
|
|
782
844
|
fs = fsModule;
|
|
783
845
|
} catch {
|
|
@@ -815,24 +877,21 @@ var MonitorClient = class {
|
|
|
815
877
|
return null;
|
|
816
878
|
}
|
|
817
879
|
const packageManager = this.detectPackageManager(projectPath, fs, path);
|
|
818
|
-
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager}`);
|
|
880
|
+
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager} (timeout: ${this.auditTimeoutMs}ms)`);
|
|
819
881
|
let auditOutput;
|
|
820
882
|
let vulnerabilities;
|
|
821
883
|
let totalDeps = 0;
|
|
822
884
|
if (packageManager === "yarn") {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
} catch (err) {
|
|
832
|
-
const output = this.handleAuditExecError(err, "yarn");
|
|
833
|
-
if (output === null) return null;
|
|
834
|
-
auditOutput = output;
|
|
885
|
+
const result2 = await this.runCommandWithTimeout("yarn", ["audit", "--json"], {
|
|
886
|
+
cwd: projectPath,
|
|
887
|
+
timeout: this.auditTimeoutMs,
|
|
888
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
889
|
+
});
|
|
890
|
+
if (result2.timedOut) {
|
|
891
|
+
console.error(`[MonitorClient] yarn audit timed out after ${this.auditTimeoutMs}ms`);
|
|
892
|
+
return null;
|
|
835
893
|
}
|
|
894
|
+
auditOutput = result2.stdout;
|
|
836
895
|
vulnerabilities = this.parseYarnAuditOutput(auditOutput);
|
|
837
896
|
const lines = auditOutput.trim().split("\n");
|
|
838
897
|
for (const line of lines) {
|
|
@@ -847,36 +906,30 @@ var MonitorClient = class {
|
|
|
847
906
|
}
|
|
848
907
|
} else if (packageManager === "pnpm") {
|
|
849
908
|
console.log("[MonitorClient] pnpm detected, using npm audit (pnpm compatible)");
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
} catch (err) {
|
|
859
|
-
const output = this.handleAuditExecError(err, "npm");
|
|
860
|
-
if (output === null) return null;
|
|
861
|
-
auditOutput = output;
|
|
909
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
910
|
+
cwd: projectPath,
|
|
911
|
+
timeout: this.auditTimeoutMs,
|
|
912
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
913
|
+
});
|
|
914
|
+
if (result2.timedOut) {
|
|
915
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
916
|
+
return null;
|
|
862
917
|
}
|
|
918
|
+
auditOutput = result2.stdout;
|
|
863
919
|
const auditData = JSON.parse(auditOutput);
|
|
864
920
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
865
921
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
866
922
|
} else {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
} catch (err) {
|
|
876
|
-
const output = this.handleAuditExecError(err, "npm");
|
|
877
|
-
if (output === null) return null;
|
|
878
|
-
auditOutput = output;
|
|
923
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
924
|
+
cwd: projectPath,
|
|
925
|
+
timeout: this.auditTimeoutMs,
|
|
926
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
927
|
+
});
|
|
928
|
+
if (result2.timedOut) {
|
|
929
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
930
|
+
return null;
|
|
879
931
|
}
|
|
932
|
+
auditOutput = result2.stdout;
|
|
880
933
|
const auditData = JSON.parse(auditOutput);
|
|
881
934
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
882
935
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
@@ -923,7 +976,7 @@ var MonitorClient = class {
|
|
|
923
976
|
console.log(`[MonitorClient] Starting multi-path audit (${this.auditPaths.length} paths)...`);
|
|
924
977
|
try {
|
|
925
978
|
const result = await this.withTimeout(
|
|
926
|
-
this.performMultiPathAudit(),
|
|
979
|
+
() => this.performMultiPathAudit(),
|
|
927
980
|
CONFIG_LIMITS.AUDIT_MULTI_PATH_TIMEOUT_MS,
|
|
928
981
|
"Multi-path audit timed out"
|
|
929
982
|
);
|
package/package.json
CHANGED