@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 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 to prevent leaks.
202
- * @param promise The promise to execute
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 to prevent leaks.
202
- * @param promise The promise to execute
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 to prevent leaks.
203
- * @param promise The promise to execute
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(promise, timeoutMs, message) {
207
+ withTimeout(promiseFactory, timeoutMs, message) {
208
+ const controller = new AbortController();
208
209
  let timeoutId;
209
210
  const timeoutPromise = new Promise((_, reject) => {
210
- timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
211
+ timeoutId = setTimeout(() => {
212
+ controller.abort();
213
+ reject(new Error(message));
214
+ }, timeoutMs);
211
215
  });
212
- return Promise.race([promise, timeoutPromise]).finally(() => {
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.withTimeout(
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
- try {
860
- auditOutput = execSync("yarn audit --json", {
861
- cwd: projectPath,
862
- encoding: "utf-8",
863
- stdio: ["pipe", "pipe", "pipe"],
864
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
865
- timeout: this.auditTimeoutMs
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
- try {
887
- auditOutput = execSync("npm audit --json", {
888
- cwd: projectPath,
889
- encoding: "utf-8",
890
- stdio: ["pipe", "pipe", "pipe"],
891
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
892
- timeout: this.auditTimeoutMs
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
- try {
904
- auditOutput = execSync("npm audit --json", {
905
- cwd: projectPath,
906
- encoding: "utf-8",
907
- stdio: ["pipe", "pipe", "pipe"],
908
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
909
- timeout: this.auditTimeoutMs
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 to prevent leaks.
167
- * @param promise The promise to execute
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(promise, timeoutMs, message) {
171
+ withTimeout(promiseFactory, timeoutMs, message) {
172
+ const controller = new AbortController();
172
173
  let timeoutId;
173
174
  const timeoutPromise = new Promise((_, reject) => {
174
- timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
175
+ timeoutId = setTimeout(() => {
176
+ controller.abort();
177
+ reject(new Error(message));
178
+ }, timeoutMs);
175
179
  });
176
- return Promise.race([promise, timeoutPromise]).finally(() => {
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.withTimeout(
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
- try {
824
- auditOutput = execSync("yarn audit --json", {
825
- cwd: projectPath,
826
- encoding: "utf-8",
827
- stdio: ["pipe", "pipe", "pipe"],
828
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
829
- timeout: this.auditTimeoutMs
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
- try {
851
- auditOutput = execSync("npm audit --json", {
852
- cwd: projectPath,
853
- encoding: "utf-8",
854
- stdio: ["pipe", "pipe", "pipe"],
855
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
856
- timeout: this.auditTimeoutMs
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
- try {
868
- auditOutput = execSync("npm audit --json", {
869
- cwd: projectPath,
870
- encoding: "utf-8",
871
- stdio: ["pipe", "pipe", "pipe"],
872
- maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
873
- timeout: this.auditTimeoutMs
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceon-oy/monitor-sdk",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Client SDK for Ceon Monitor - Error tracking, health monitoring, security events, and vulnerability scanning",
5
5
  "author": "Ceon",
6
6
  "license": "MIT",