@preship/core 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type ProjectType = 'npm' | 'yarn' | 'pnpm';
1
+ type ProjectType = 'npm' | 'yarn' | 'pnpm' | 'flutter' | 'maven' | 'gradle' | 'cocoapods' | 'spm';
2
2
  interface DetectedProject {
3
3
  type: ProjectType;
4
4
  path: string;
@@ -14,7 +14,7 @@ interface Dependency {
14
14
  isDirect: boolean;
15
15
  isDevDependency: boolean;
16
16
  }
17
- type LicenseSource = 'package-json-license-field' | 'license-file' | 'package-json-licenses-field' | 'npm-registry' | 'github-api' | 'cache' | 'unknown';
17
+ type LicenseSource = 'package-json-license-field' | 'license-file' | 'package-json-licenses-field' | 'npm-registry' | 'pub-registry' | 'maven-central' | 'cocoapods-cdn' | 'github-api' | 'cache' | 'unknown';
18
18
  interface ParsedDependency {
19
19
  name: string;
20
20
  version: string;
@@ -205,6 +205,130 @@ declare function parseYarnLockfile(lockfilePath: string, packageJsonPath: string
205
205
 
206
206
  declare function parsePnpmLockfile(lockfilePath: string, packageJsonPath: string): ParsedDependency[];
207
207
 
208
+ /**
209
+ * Parse a Flutter/Dart pubspec.lock file and return all dependencies.
210
+ *
211
+ * pubspec.lock uses a well-defined YAML structure:
212
+ * packages:
213
+ * <name>:
214
+ * dependency: "direct main"
215
+ * description:
216
+ * name: <name>
217
+ * ...
218
+ * source: hosted
219
+ * version: "1.2.3"
220
+ *
221
+ * We use a simple line-based parser (no js-yaml dependency needed)
222
+ * because pubspec.lock has a consistent, predictable structure.
223
+ *
224
+ * SDK packages (source: sdk) are skipped — these are Flutter/Dart
225
+ * built-ins like `flutter`, `sky_engine`, `flutter_test`.
226
+ *
227
+ * @param lockfilePath - Absolute path to pubspec.lock
228
+ * @param pubspecYamlPath - Absolute path to pubspec.yaml
229
+ * @returns Array of ParsedDependency
230
+ */
231
+ declare function parsePubspecLockfile(lockfilePath: string, pubspecYamlPath: string): ParsedDependency[];
232
+
233
+ /**
234
+ * Parse a Maven pom.xml file and return all dependencies.
235
+ *
236
+ * Maven doesn't have a lockfile, so we parse pom.xml directly.
237
+ * Extracts <dependency> blocks from the <dependencies> section,
238
+ * resolving ${property} version references from <properties>.
239
+ *
240
+ * Skips:
241
+ * - <scope>provided</scope> and <scope>system</scope> (always)
242
+ * - <scope>test</scope> (marked as dev dependency)
243
+ *
244
+ * Package name format: groupId:artifactId
245
+ * (e.g., "org.springframework.boot:spring-boot-starter-web")
246
+ *
247
+ * @param pomPath - Absolute path to pom.xml
248
+ * @param _manifestPath - Same as pomPath (Maven uses pom.xml as both)
249
+ * @returns Array of ParsedDependency
250
+ */
251
+ declare function parseMavenPom(pomPath: string, _manifestPath: string): ParsedDependency[];
252
+
253
+ /**
254
+ * Parse a Gradle project's dependencies.
255
+ *
256
+ * Tries sources in priority order:
257
+ * 1. gradle.lockfile (opt-in, most reliable)
258
+ * 2. gradle/libs.versions.toml (version catalog)
259
+ * 3. build.gradle or build.gradle.kts (regex fallback)
260
+ *
261
+ * Package name format: groupId:artifactId
262
+ * (e.g., "org.springframework.boot:spring-boot-starter-web")
263
+ *
264
+ * @param lockfilePath - Path to gradle.lockfile or build.gradle(.kts)
265
+ * @param manifestPath - Path to build.gradle or build.gradle.kts
266
+ * @returns Array of ParsedDependency
267
+ */
268
+ declare function parseGradleLockfile(lockfilePath: string, manifestPath: string): ParsedDependency[];
269
+
270
+ /**
271
+ * Parse a CocoaPods Podfile.lock and return all dependencies.
272
+ *
273
+ * Podfile.lock is a YAML-like format with consistent structure:
274
+ *
275
+ * PODS:
276
+ * - AFNetworking (4.0.1):
277
+ * - AFNetworking/NSURLSession (= 4.0.1)
278
+ * - AFNetworking/Reachability (= 4.0.1)
279
+ * - Alamofire (5.9.1)
280
+ *
281
+ * DEPENDENCIES:
282
+ * - AFNetworking (~> 4.0)
283
+ * - Alamofire
284
+ *
285
+ * SPEC REPOS:
286
+ * ...
287
+ *
288
+ * We use a line-based parser (no YAML library needed) since the
289
+ * format is predictable.
290
+ *
291
+ * Subspecs (e.g., AFNetworking/NSURLSession) are skipped — only
292
+ * top-level pods are returned.
293
+ *
294
+ * @param lockfilePath - Absolute path to Podfile.lock
295
+ * @param _manifestPath - Absolute path to Podfile (unused, for API consistency)
296
+ * @returns Array of ParsedDependency
297
+ */
298
+ declare function parsePodfileLock(lockfilePath: string, _manifestPath: string): ParsedDependency[];
299
+
300
+ /**
301
+ * Parse a Swift Package Manager Package.resolved file.
302
+ *
303
+ * Supports:
304
+ * - v2 format (Swift 5.6+): { "pins": [...] }
305
+ * - v3 format: same structure with "originHash"
306
+ * - v1 format (legacy): { "object": { "pins": [...] } }
307
+ *
308
+ * Skips:
309
+ * - Branch-based pins (no version)
310
+ * - Local packages (kind: "localSourceControl")
311
+ *
312
+ * All SPM deps are treated as direct (Package.resolved doesn't
313
+ * distinguish direct vs transitive).
314
+ *
315
+ * The `location` field contains the Git URL, useful for
316
+ * GitHub-based license resolution.
317
+ *
318
+ * @param resolvedPath - Absolute path to Package.resolved
319
+ * @param _manifestPath - Absolute path to Package.swift (unused, for API consistency)
320
+ * @returns Array of ParsedDependency
321
+ */
322
+ declare function parsePackageResolved(resolvedPath: string, _manifestPath: string): ParsedDependency[];
323
+ /**
324
+ * Extract the Git location URL for a pin.
325
+ * Used by the license resolver to find GitHub repos.
326
+ *
327
+ * @param resolvedPath - Absolute path to Package.resolved
328
+ * @returns Map of package identity → location URL
329
+ */
330
+ declare function extractSpmLocations(resolvedPath: string): Map<string, string>;
331
+
208
332
  /**
209
333
  * Encrypted Project-Level License Cache
210
334
  *
@@ -360,4 +484,4 @@ interface ScanTimeoutController {
360
484
  */
361
485
  declare function createScanTimeoutController(timeout?: number): ScanTimeoutController;
362
486
 
363
- export { AdaptiveRateLimiter, type CacheEntry, type CacheFile, type Dependency, type DetectedProject, type ExceptionEntry, type LicenseSource, type ModuleConfig, type PackageHealth, type ParsedDependency, type PolicyResult, type PolicyTemplate, type PolicyVerdict, type PreshipConfig, type ProjectType, type ResolverMode, type ScanResult, type ScanTimeoutController, type SecretFinding, type SecretRule, type SecretSeverity, type SecretsConfig, type SecretsResult, type SecurityConfig, type SecurityFinding, type SecurityPolicyLevel, type SecurityResult, type SecuritySeverity, type SecurityVulnerability, type UnifiedScanResult, cacheKey, createEmptyCache, createScanTimeoutController, detectProjects, getCacheEntry, getDefaultConfig, getOrCreateKey, loadCache, loadConfig, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile, saveCache, setCacheEntry };
487
+ export { AdaptiveRateLimiter, type CacheEntry, type CacheFile, type Dependency, type DetectedProject, type ExceptionEntry, type LicenseSource, type ModuleConfig, type PackageHealth, type ParsedDependency, type PolicyResult, type PolicyTemplate, type PolicyVerdict, type PreshipConfig, type ProjectType, type ResolverMode, type ScanResult, type ScanTimeoutController, type SecretFinding, type SecretRule, type SecretSeverity, type SecretsConfig, type SecretsResult, type SecurityConfig, type SecurityFinding, type SecurityPolicyLevel, type SecurityResult, type SecuritySeverity, type SecurityVulnerability, type UnifiedScanResult, cacheKey, createEmptyCache, createScanTimeoutController, detectProjects, extractSpmLocations, getCacheEntry, getDefaultConfig, getOrCreateKey, loadCache, loadConfig, parseGradleLockfile, parseMavenPom, parseNpmLockfile, parsePackageResolved, parsePnpmLockfile, parsePodfileLock, parsePubspecLockfile, parseYarnLockfile, saveCache, setCacheEntry };
package/dist/index.js CHANGED
@@ -35,13 +35,19 @@ __export(index_exports, {
35
35
  createEmptyCache: () => createEmptyCache,
36
36
  createScanTimeoutController: () => createScanTimeoutController,
37
37
  detectProjects: () => detectProjects,
38
+ extractSpmLocations: () => extractSpmLocations,
38
39
  getCacheEntry: () => getCacheEntry,
39
40
  getDefaultConfig: () => getDefaultConfig,
40
41
  getOrCreateKey: () => getOrCreateKey,
41
42
  loadCache: () => loadCache,
42
43
  loadConfig: () => loadConfig,
44
+ parseGradleLockfile: () => parseGradleLockfile,
45
+ parseMavenPom: () => parseMavenPom,
43
46
  parseNpmLockfile: () => parseNpmLockfile,
47
+ parsePackageResolved: () => parsePackageResolved,
44
48
  parsePnpmLockfile: () => parsePnpmLockfile,
49
+ parsePodfileLock: () => parsePodfileLock,
50
+ parsePubspecLockfile: () => parsePubspecLockfile,
45
51
  parseYarnLockfile: () => parseYarnLockfile,
46
52
  saveCache: () => saveCache,
47
53
  setCacheEntry: () => setCacheEntry
@@ -378,10 +384,30 @@ function loadConfig(projectPath) {
378
384
  // src/detector.ts
379
385
  var fs2 = __toESM(require("fs"));
380
386
  var path2 = __toESM(require("path"));
387
+ var NODE_LOCK_FILES = [
388
+ { filename: "package-lock.json", type: "npm", manifest: "package.json" },
389
+ { filename: "yarn.lock", type: "yarn", manifest: "package.json" },
390
+ { filename: "pnpm-lock.yaml", type: "pnpm", manifest: "package.json" }
391
+ ];
392
+ var FLUTTER_LOCK_FILES = [
393
+ { filename: "pubspec.lock", type: "flutter", manifest: "pubspec.yaml" }
394
+ ];
395
+ var JAVA_LOCK_FILES = [
396
+ { filename: "gradle.lockfile", type: "gradle", manifest: "build.gradle" },
397
+ { filename: "gradle.lockfile", type: "gradle", manifest: "build.gradle.kts" },
398
+ { filename: "build.gradle", type: "gradle", manifest: "build.gradle" },
399
+ { filename: "build.gradle.kts", type: "gradle", manifest: "build.gradle.kts" },
400
+ { filename: "pom.xml", type: "maven", manifest: "pom.xml" }
401
+ ];
402
+ var IOS_LOCK_FILES = [
403
+ { filename: "Podfile.lock", type: "cocoapods", manifest: "Podfile" },
404
+ { filename: "Package.resolved", type: "spm", manifest: "Package.swift" }
405
+ ];
381
406
  var LOCK_FILES = [
382
- { filename: "package-lock.json", type: "npm" },
383
- { filename: "yarn.lock", type: "yarn" },
384
- { filename: "pnpm-lock.yaml", type: "pnpm" }
407
+ ...NODE_LOCK_FILES,
408
+ ...FLUTTER_LOCK_FILES,
409
+ ...JAVA_LOCK_FILES,
410
+ ...IOS_LOCK_FILES
385
411
  ];
386
412
  function detectFramework(packageJsonPath) {
387
413
  try {
@@ -391,8 +417,15 @@ function detectFramework(packageJsonPath) {
391
417
  ...pkg.dependencies || {},
392
418
  ...pkg.devDependencies || {}
393
419
  };
420
+ if (allDeps["expo"]) return "expo";
421
+ if (allDeps["react-native"]) return "react-native";
422
+ if (allDeps["electron"]) return "electron";
423
+ if (allDeps["@tauri-apps/api"] || allDeps["tauri"]) return "tauri";
394
424
  if (allDeps["next"]) return "nextjs";
395
425
  if (allDeps["nuxt"]) return "nuxtjs";
426
+ if (allDeps["@remix-run/react"] || allDeps["remix"]) return "remix";
427
+ if (allDeps["astro"]) return "astro";
428
+ if (allDeps["gatsby"]) return "gatsby";
396
429
  if (allDeps["react"]) return "react";
397
430
  if (allDeps["vue"]) return "vue";
398
431
  if (allDeps["@angular/core"]) return "angular";
@@ -400,25 +433,113 @@ function detectFramework(packageJsonPath) {
400
433
  if (allDeps["express"]) return "express";
401
434
  if (allDeps["fastify"]) return "fastify";
402
435
  if (allDeps["@nestjs/core"] || allDeps["nestjs"]) return "nestjs";
436
+ if (allDeps["@hapi/hapi"]) return "hapi";
437
+ if (allDeps["koa"]) return "koa";
403
438
  return void 0;
404
439
  } catch {
405
440
  return void 0;
406
441
  }
407
442
  }
443
+ function detectFlutterFramework(pubspecYamlPath, projectRoot) {
444
+ try {
445
+ const content = fs2.readFileSync(pubspecYamlPath, "utf-8");
446
+ const hasFlutterSdk = /^\s{2}flutter:\s*$/m.test(content) && /sdk:\s*flutter/m.test(content);
447
+ if (hasFlutterSdk) {
448
+ const hasWebDir = fs2.existsSync(path2.join(projectRoot, "web"));
449
+ if (hasWebDir) return "flutter-web";
450
+ const hasDesktopDir = fs2.existsSync(path2.join(projectRoot, "linux")) || fs2.existsSync(path2.join(projectRoot, "windows")) || fs2.existsSync(path2.join(projectRoot, "macos"));
451
+ if (hasDesktopDir) return "flutter-desktop";
452
+ return "flutter";
453
+ }
454
+ const hasShelf = /shelf/m.test(content);
455
+ const hasDartFrog = /dart_frog/m.test(content);
456
+ if (hasShelf || hasDartFrog) return "dart-server";
457
+ return "dart-cli";
458
+ } catch {
459
+ return void 0;
460
+ }
461
+ }
462
+ function detectJavaFramework(manifestPath, _projectRoot) {
463
+ try {
464
+ const content = fs2.readFileSync(manifestPath, "utf-8");
465
+ if (/com\.android\.(application|library)/m.test(content)) return "android-java";
466
+ if (/spring-boot-starter/m.test(content)) return "spring-boot";
467
+ if (/org\.springframework/m.test(content)) return "spring";
468
+ if (/io\.quarkus/m.test(content)) return "quarkus";
469
+ if (/io\.micronaut/m.test(content)) return "micronaut";
470
+ return "java";
471
+ } catch {
472
+ return "java";
473
+ }
474
+ }
475
+ function detectIosFramework(manifestPath, projectType) {
476
+ if (projectType === "spm") return "ios-spm";
477
+ try {
478
+ const content = fs2.readFileSync(manifestPath, "utf-8");
479
+ if (/platform\s*:tvos/m.test(content)) return "tvos-native";
480
+ if (/platform\s*:osx/m.test(content) || /platform\s*:macos/m.test(content)) return "macos-native";
481
+ return "ios-native";
482
+ } catch {
483
+ return "ios-native";
484
+ }
485
+ }
408
486
  function detectProjects(rootPath) {
409
487
  const absRoot = path2.resolve(rootPath);
410
488
  const packageJsonPath = path2.join(absRoot, "package.json");
411
- if (!fs2.existsSync(packageJsonPath)) {
489
+ const pubspecYamlPath = path2.join(absRoot, "pubspec.yaml");
490
+ const pomXmlPath = path2.join(absRoot, "pom.xml");
491
+ const buildGradlePath = path2.join(absRoot, "build.gradle");
492
+ const buildGradleKtsPath = path2.join(absRoot, "build.gradle.kts");
493
+ const podfilePath = path2.join(absRoot, "Podfile");
494
+ const packageSwiftPath = path2.join(absRoot, "Package.swift");
495
+ const hasPackageJson = fs2.existsSync(packageJsonPath);
496
+ const hasPubspecYaml = fs2.existsSync(pubspecYamlPath);
497
+ const hasPomXml = fs2.existsSync(pomXmlPath);
498
+ const hasBuildGradle = fs2.existsSync(buildGradlePath) || fs2.existsSync(buildGradleKtsPath);
499
+ const hasPodfile = fs2.existsSync(podfilePath);
500
+ const hasPackageSwift = fs2.existsSync(packageSwiftPath);
501
+ if (!hasPackageJson && !hasPubspecYaml && !hasPomXml && !hasBuildGradle && !hasPodfile && !hasPackageSwift) {
412
502
  throw new Error(
413
- `No package.json found in ${absRoot}.
414
- PreShip needs a Node.js project to scan.
503
+ `No supported project manifest found in ${absRoot}.
504
+ PreShip supports:
505
+ - Node.js (package.json)
506
+ - Flutter/Dart (pubspec.yaml)
507
+ - Maven (pom.xml)
508
+ - Gradle (build.gradle / build.gradle.kts)
509
+ - CocoaPods (Podfile)
510
+ - Swift Package Manager (Package.swift)
415
511
  Make sure you're running from the project root.`
416
512
  );
417
513
  }
418
514
  const detected = [];
419
515
  for (const entry of LOCK_FILES) {
420
516
  const lockFilePath = path2.join(absRoot, entry.filename);
421
- if (fs2.existsSync(lockFilePath)) {
517
+ const manifestPath = path2.join(absRoot, entry.manifest);
518
+ if (!fs2.existsSync(lockFilePath) || !fs2.existsSync(manifestPath)) {
519
+ continue;
520
+ }
521
+ if (entry.type === "flutter") {
522
+ detected.push({
523
+ type: entry.type,
524
+ path: absRoot,
525
+ lockFile: lockFilePath,
526
+ framework: detectFlutterFramework(pubspecYamlPath, absRoot)
527
+ });
528
+ } else if (entry.type === "maven" || entry.type === "gradle") {
529
+ detected.push({
530
+ type: entry.type,
531
+ path: absRoot,
532
+ lockFile: lockFilePath,
533
+ framework: detectJavaFramework(manifestPath, absRoot)
534
+ });
535
+ } else if (entry.type === "cocoapods" || entry.type === "spm") {
536
+ detected.push({
537
+ type: entry.type,
538
+ path: absRoot,
539
+ lockFile: lockFilePath,
540
+ framework: detectIosFramework(manifestPath, entry.type)
541
+ });
542
+ } else {
422
543
  detected.push({
423
544
  type: entry.type,
424
545
  path: absRoot,
@@ -428,11 +549,42 @@ function detectProjects(rootPath) {
428
549
  }
429
550
  }
430
551
  if (detected.length === 0) {
431
- throw new Error(
432
- `No supported lock file found in ${absRoot}.
552
+ if (hasPackageJson) {
553
+ throw new Error(
554
+ `No supported lock file found in ${absRoot}.
433
555
  PreShip needs a lock file to scan dependencies.
434
556
  Run 'npm install', 'yarn install', or 'pnpm install' first.`
435
- );
557
+ );
558
+ } else if (hasPubspecYaml) {
559
+ throw new Error(
560
+ `No pubspec.lock found in ${absRoot}.
561
+ PreShip needs a lock file to scan dependencies.
562
+ Run 'flutter pub get' or 'dart pub get' first.`
563
+ );
564
+ } else if (hasPomXml) {
565
+ throw new Error(
566
+ `Maven project detected in ${absRoot} but scanning is ready.
567
+ PreShip will parse pom.xml directly (no lockfile needed).`
568
+ );
569
+ } else if (hasBuildGradle) {
570
+ throw new Error(
571
+ `No gradle.lockfile found in ${absRoot}.
572
+ PreShip needs a lockfile or build file to scan dependencies.
573
+ For best results, enable lockfile: add 'dependencyLocking { lockAllConfigurations() }' to build.gradle.`
574
+ );
575
+ } else if (hasPodfile) {
576
+ throw new Error(
577
+ `No Podfile.lock found in ${absRoot}.
578
+ PreShip needs a lock file to scan dependencies.
579
+ Run 'pod install' first.`
580
+ );
581
+ } else {
582
+ throw new Error(
583
+ `No Package.resolved found in ${absRoot}.
584
+ PreShip needs a resolved file to scan dependencies.
585
+ Run 'swift package resolve' first.`
586
+ );
587
+ }
436
588
  }
437
589
  return [detected[0]];
438
590
  }
@@ -782,18 +934,595 @@ function parseV5Key(key) {
782
934
  return { name, pkgVersion };
783
935
  }
784
936
 
785
- // src/cache.ts
786
- var crypto = __toESM(require("crypto"));
937
+ // src/parsers/flutter.ts
787
938
  var fs6 = __toESM(require("fs"));
939
+ function parsePubspecLockfile(lockfilePath, pubspecYamlPath) {
940
+ let lockContent;
941
+ try {
942
+ lockContent = fs6.readFileSync(lockfilePath, "utf-8");
943
+ } catch {
944
+ throw new Error(
945
+ `Failed to read ${lockfilePath}.
946
+ The lock file may be missing or corrupted. Try running 'flutter pub get'.`
947
+ );
948
+ }
949
+ let pubspecContent;
950
+ try {
951
+ pubspecContent = fs6.readFileSync(pubspecYamlPath, "utf-8");
952
+ } catch {
953
+ throw new Error(
954
+ `Failed to read ${pubspecYamlPath}.
955
+ Make sure pubspec.yaml exists and is readable.`
956
+ );
957
+ }
958
+ const { directDeps, devDeps } = parsePubspecYamlDeps(pubspecContent);
959
+ const packages = parsePubspecLockPackages(lockContent);
960
+ const results = [];
961
+ const seen = /* @__PURE__ */ new Set();
962
+ for (const [name, entry] of packages) {
963
+ if (entry.source === "sdk") {
964
+ continue;
965
+ }
966
+ if (!entry.version) {
967
+ continue;
968
+ }
969
+ const dedupKey = `${name}@${entry.version}`;
970
+ if (seen.has(dedupKey)) {
971
+ continue;
972
+ }
973
+ seen.add(dedupKey);
974
+ const isDirect = entry.dependency === "direct main" || entry.dependency === "direct dev" || entry.dependency === "direct overridden";
975
+ const isDevDependency = entry.dependency === "direct dev" || !isDirect && devDeps.has(name) && !directDeps.has(name);
976
+ results.push({
977
+ name,
978
+ version: entry.version,
979
+ isDirect,
980
+ isDevDependency
981
+ });
982
+ }
983
+ return results;
984
+ }
985
+ function parsePubspecYamlDeps(content) {
986
+ const directDeps = /* @__PURE__ */ new Set();
987
+ const devDeps = /* @__PURE__ */ new Set();
988
+ const lines = content.split("\n");
989
+ let currentSection = null;
990
+ for (const line of lines) {
991
+ const trimmed = line.trimEnd();
992
+ if (trimmed === "" || trimmed.startsWith("#")) {
993
+ continue;
994
+ }
995
+ if (!trimmed.startsWith(" ") && !trimmed.startsWith(" ")) {
996
+ if (trimmed === "dependencies:" || trimmed.startsWith("dependencies:")) {
997
+ currentSection = "dependencies";
998
+ continue;
999
+ }
1000
+ if (trimmed === "dev_dependencies:" || trimmed.startsWith("dev_dependencies:")) {
1001
+ currentSection = "dev_dependencies";
1002
+ continue;
1003
+ }
1004
+ currentSection = null;
1005
+ continue;
1006
+ }
1007
+ if (currentSection) {
1008
+ const depMatch = trimmed.match(/^ ([a-zA-Z_][a-zA-Z0-9_]*):/);
1009
+ if (depMatch) {
1010
+ const depName = depMatch[1];
1011
+ if (currentSection === "dependencies") {
1012
+ directDeps.add(depName);
1013
+ } else {
1014
+ devDeps.add(depName);
1015
+ }
1016
+ }
1017
+ }
1018
+ }
1019
+ return { directDeps, devDeps };
1020
+ }
1021
+ function parsePubspecLockPackages(content) {
1022
+ const packages = /* @__PURE__ */ new Map();
1023
+ const lines = content.split("\n");
1024
+ let inPackages = false;
1025
+ let currentPackage = null;
1026
+ let currentEntry = {};
1027
+ for (let i = 0; i < lines.length; i++) {
1028
+ const line = lines[i];
1029
+ const trimmedEnd = line.trimEnd();
1030
+ if (trimmedEnd === "packages:") {
1031
+ inPackages = true;
1032
+ continue;
1033
+ }
1034
+ if (!inPackages) {
1035
+ continue;
1036
+ }
1037
+ if (trimmedEnd !== "" && !trimmedEnd.startsWith(" ") && !trimmedEnd.startsWith(" ")) {
1038
+ if (currentPackage && currentEntry.dependency && currentEntry.source && currentEntry.version) {
1039
+ packages.set(currentPackage, currentEntry);
1040
+ }
1041
+ break;
1042
+ }
1043
+ if (trimmedEnd === "") {
1044
+ continue;
1045
+ }
1046
+ const packageNameMatch = line.match(/^ ([a-zA-Z_][a-zA-Z0-9_-]*):\s*$/);
1047
+ if (packageNameMatch) {
1048
+ if (currentPackage && currentEntry.dependency && currentEntry.source && currentEntry.version) {
1049
+ packages.set(currentPackage, currentEntry);
1050
+ }
1051
+ currentPackage = packageNameMatch[1];
1052
+ currentEntry = {};
1053
+ continue;
1054
+ }
1055
+ if (currentPackage && line.startsWith(" ") && !line.startsWith(" ")) {
1056
+ const propMatch = line.match(/^ (\w+):\s*"?([^"]*)"?\s*$/);
1057
+ if (propMatch) {
1058
+ const [, key, value] = propMatch;
1059
+ if (key === "dependency") {
1060
+ currentEntry.dependency = value;
1061
+ } else if (key === "source") {
1062
+ currentEntry.source = value;
1063
+ } else if (key === "version") {
1064
+ currentEntry.version = value;
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ if (currentPackage && currentEntry.dependency && currentEntry.source && currentEntry.version) {
1070
+ packages.set(currentPackage, currentEntry);
1071
+ }
1072
+ return packages;
1073
+ }
1074
+
1075
+ // src/parsers/maven.ts
1076
+ var fs7 = __toESM(require("fs"));
1077
+ function parseMavenPom(pomPath, _manifestPath) {
1078
+ let content;
1079
+ try {
1080
+ content = fs7.readFileSync(pomPath, "utf-8");
1081
+ } catch {
1082
+ throw new Error(
1083
+ `Failed to read ${pomPath}.
1084
+ The pom.xml file may be missing or corrupted.`
1085
+ );
1086
+ }
1087
+ const properties = parseProperties(content);
1088
+ const dependencies = parseDependencies(content, properties);
1089
+ return dependencies;
1090
+ }
1091
+ function parseProperties(content) {
1092
+ const props = /* @__PURE__ */ new Map();
1093
+ const propsMatch = content.match(/<properties>([\s\S]*?)<\/properties>/);
1094
+ if (!propsMatch) return props;
1095
+ const propsBlock = propsMatch[1];
1096
+ const propRegex = /<([a-zA-Z0-9._-]+)>(.*?)<\/\1>/g;
1097
+ let match;
1098
+ while ((match = propRegex.exec(propsBlock)) !== null) {
1099
+ props.set(match[1], match[2].trim());
1100
+ }
1101
+ return props;
1102
+ }
1103
+ function resolveVersion(version, properties) {
1104
+ if (!version) return void 0;
1105
+ return version.replace(/\$\{([^}]+)\}/g, (_, propName) => {
1106
+ return properties.get(propName) ?? `\${${propName}}`;
1107
+ });
1108
+ }
1109
+ function parseDependencies(content, properties) {
1110
+ const results = [];
1111
+ const seen = /* @__PURE__ */ new Set();
1112
+ const withoutMgmt = content.replace(
1113
+ /<dependencyManagement>[\s\S]*?<\/dependencyManagement>/g,
1114
+ ""
1115
+ );
1116
+ const depsBlockRegex = /<dependencies>([\s\S]*?)<\/dependencies>/g;
1117
+ let depsMatch;
1118
+ while ((depsMatch = depsBlockRegex.exec(withoutMgmt)) !== null) {
1119
+ const depsBlock = depsMatch[1];
1120
+ const depRegex = /<dependency>([\s\S]*?)<\/dependency>/g;
1121
+ let depMatch;
1122
+ while ((depMatch = depRegex.exec(depsBlock)) !== null) {
1123
+ const depBlock = depMatch[1];
1124
+ const groupId = extractXmlValue(depBlock, "groupId");
1125
+ const artifactId = extractXmlValue(depBlock, "artifactId");
1126
+ const rawVersion = extractXmlValue(depBlock, "version");
1127
+ const scope = extractXmlValue(depBlock, "scope");
1128
+ if (!groupId || !artifactId) continue;
1129
+ if (scope === "provided" || scope === "system") continue;
1130
+ const version = resolveVersion(rawVersion, properties) ?? "UNKNOWN";
1131
+ const name = `${groupId}:${artifactId}`;
1132
+ const isDevDependency = scope === "test";
1133
+ const dedupKey = `${name}@${version}`;
1134
+ if (seen.has(dedupKey)) continue;
1135
+ seen.add(dedupKey);
1136
+ results.push({
1137
+ name,
1138
+ version,
1139
+ isDirect: true,
1140
+ // pom.xml only lists direct dependencies
1141
+ isDevDependency
1142
+ });
1143
+ }
1144
+ }
1145
+ return results;
1146
+ }
1147
+ function extractXmlValue(block, tag) {
1148
+ const match = block.match(new RegExp(`<${tag}>\\s*(.*?)\\s*</${tag}>`));
1149
+ return match ? match[1].trim() : void 0;
1150
+ }
1151
+
1152
+ // src/parsers/gradle.ts
1153
+ var fs8 = __toESM(require("fs"));
788
1154
  var path3 = __toESM(require("path"));
1155
+ function parseGradleLockfile(lockfilePath, manifestPath) {
1156
+ const lockFileName = path3.basename(lockfilePath);
1157
+ if (lockFileName === "gradle.lockfile") {
1158
+ return parseGradleLockfileFormat(lockfilePath);
1159
+ }
1160
+ const projectDir = path3.dirname(manifestPath);
1161
+ const versionCatalogPath = path3.join(projectDir, "gradle", "libs.versions.toml");
1162
+ if (fs8.existsSync(versionCatalogPath)) {
1163
+ const catalogDeps = parseVersionCatalog(versionCatalogPath);
1164
+ if (catalogDeps.length > 0) return catalogDeps;
1165
+ }
1166
+ return parseBuildGradleFile(manifestPath);
1167
+ }
1168
+ function parseGradleLockfileFormat(lockfilePath) {
1169
+ let content;
1170
+ try {
1171
+ content = fs8.readFileSync(lockfilePath, "utf-8");
1172
+ } catch {
1173
+ throw new Error(
1174
+ `Failed to read ${lockfilePath}.
1175
+ The gradle.lockfile may be missing or corrupted.`
1176
+ );
1177
+ }
1178
+ const results = [];
1179
+ const seen = /* @__PURE__ */ new Set();
1180
+ for (const line of content.split("\n")) {
1181
+ const trimmed = line.trim();
1182
+ if (!trimmed || trimmed.startsWith("#") || trimmed === "empty=") continue;
1183
+ const eqIdx = trimmed.indexOf("=");
1184
+ if (eqIdx === -1) continue;
1185
+ const coordPart = trimmed.slice(0, eqIdx);
1186
+ const configPart = trimmed.slice(eqIdx + 1);
1187
+ const parts = coordPart.split(":");
1188
+ if (parts.length < 3) continue;
1189
+ const groupId = parts[0];
1190
+ const artifactId = parts[1];
1191
+ const version = parts[2];
1192
+ const name = `${groupId}:${artifactId}`;
1193
+ const dedupKey = `${name}@${version}`;
1194
+ if (seen.has(dedupKey)) continue;
1195
+ seen.add(dedupKey);
1196
+ const configs = configPart.split(",").map((c) => c.trim().toLowerCase());
1197
+ const isTestOnly = configs.length > 0 && configs.every(
1198
+ (c) => c.includes("test") || c.includes("androidtest") || c.includes("unittest")
1199
+ );
1200
+ results.push({
1201
+ name,
1202
+ version,
1203
+ isDirect: true,
1204
+ // Lockfile doesn't distinguish, assume direct
1205
+ isDevDependency: isTestOnly
1206
+ });
1207
+ }
1208
+ return results;
1209
+ }
1210
+ function parseVersionCatalog(catalogPath) {
1211
+ let content;
1212
+ try {
1213
+ content = fs8.readFileSync(catalogPath, "utf-8");
1214
+ } catch {
1215
+ return [];
1216
+ }
1217
+ const versions = /* @__PURE__ */ new Map();
1218
+ const versionsSection = extractTomlSection(content, "versions");
1219
+ if (versionsSection) {
1220
+ for (const line of versionsSection.split("\n")) {
1221
+ const match = line.match(/^\s*([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"/);
1222
+ if (match) {
1223
+ versions.set(match[1], match[2]);
1224
+ }
1225
+ }
1226
+ }
1227
+ const results = [];
1228
+ const seen = /* @__PURE__ */ new Set();
1229
+ const librariesSection = extractTomlSection(content, "libraries");
1230
+ if (!librariesSection) return results;
1231
+ for (const line of librariesSection.split("\n")) {
1232
+ const trimmed = line.trim();
1233
+ if (!trimmed || trimmed.startsWith("#")) continue;
1234
+ const moduleMatch = trimmed.match(/module\s*=\s*"([^"]+)"/);
1235
+ if (!moduleMatch) {
1236
+ const groupMatch = trimmed.match(/group\s*=\s*"([^"]+)"/);
1237
+ const nameMatch = trimmed.match(/name\s*=\s*"([^"]+)"/);
1238
+ if (!groupMatch || !nameMatch) continue;
1239
+ const name2 = `${groupMatch[1]}:${nameMatch[1]}`;
1240
+ const version2 = resolveTomlVersion(trimmed, versions);
1241
+ if (!version2) continue;
1242
+ const dedupKey2 = `${name2}@${version2}`;
1243
+ if (seen.has(dedupKey2)) continue;
1244
+ seen.add(dedupKey2);
1245
+ results.push({ name: name2, version: version2, isDirect: true, isDevDependency: false });
1246
+ continue;
1247
+ }
1248
+ const name = moduleMatch[1];
1249
+ const version = resolveTomlVersion(trimmed, versions);
1250
+ if (!version) continue;
1251
+ const dedupKey = `${name}@${version}`;
1252
+ if (seen.has(dedupKey)) continue;
1253
+ seen.add(dedupKey);
1254
+ results.push({ name, version, isDirect: true, isDevDependency: false });
1255
+ }
1256
+ return results;
1257
+ }
1258
+ function extractTomlSection(content, sectionName) {
1259
+ const sectionRegex = new RegExp(`^\\[${sectionName}\\]\\s*$`, "m");
1260
+ const match = content.match(sectionRegex);
1261
+ if (!match || match.index === void 0) return null;
1262
+ const start = match.index + match[0].length;
1263
+ const nextSection = content.slice(start).match(/^\[/m);
1264
+ const end = nextSection?.index !== void 0 ? start + nextSection.index : content.length;
1265
+ return content.slice(start, end);
1266
+ }
1267
+ function resolveTomlVersion(line, versions) {
1268
+ const refMatch = line.match(/version\.ref\s*=\s*"([^"]+)"/);
1269
+ if (refMatch) {
1270
+ return versions.get(refMatch[1]);
1271
+ }
1272
+ const versionMatch = line.match(/version\s*=\s*"([^"]+)"/);
1273
+ if (versionMatch) {
1274
+ return versionMatch[1];
1275
+ }
1276
+ return void 0;
1277
+ }
1278
+ function parseBuildGradleFile(buildFilePath) {
1279
+ let content;
1280
+ try {
1281
+ content = fs8.readFileSync(buildFilePath, "utf-8");
1282
+ } catch {
1283
+ throw new Error(
1284
+ `Failed to read ${buildFilePath}.
1285
+ The build file may be missing or corrupted.`
1286
+ );
1287
+ }
1288
+ const results = [];
1289
+ const seen = /* @__PURE__ */ new Set();
1290
+ const depConfigs = [
1291
+ "implementation",
1292
+ "api",
1293
+ "compileOnly",
1294
+ "runtimeOnly",
1295
+ "testImplementation",
1296
+ "testCompileOnly",
1297
+ "testRuntimeOnly",
1298
+ "androidTestImplementation",
1299
+ "debugImplementation",
1300
+ "releaseImplementation"
1301
+ ];
1302
+ for (const config of depConfigs) {
1303
+ const patterns = [
1304
+ new RegExp(`${config}\\s*\\(\\s*["']([^"']+)["']\\s*\\)`, "g"),
1305
+ new RegExp(`${config}\\s+["']([^"']+)["']`, "g")
1306
+ ];
1307
+ for (const pattern of patterns) {
1308
+ let match;
1309
+ while ((match = pattern.exec(content)) !== null) {
1310
+ const dep = match[1];
1311
+ const parts = dep.split(":");
1312
+ if (parts.length < 3) continue;
1313
+ const name = `${parts[0]}:${parts[1]}`;
1314
+ const version = parts[2];
1315
+ const dedupKey = `${name}@${version}`;
1316
+ if (seen.has(dedupKey)) continue;
1317
+ seen.add(dedupKey);
1318
+ const isTest = config.toLowerCase().includes("test");
1319
+ results.push({
1320
+ name,
1321
+ version,
1322
+ isDirect: true,
1323
+ isDevDependency: isTest
1324
+ });
1325
+ }
1326
+ }
1327
+ const mapPattern = new RegExp(
1328
+ `${config}\\s+group:\\s*["']([^"']+)["']\\s*,\\s*name:\\s*["']([^"']+)["']\\s*,\\s*version:\\s*["']([^"']+)["']`,
1329
+ "g"
1330
+ );
1331
+ let mapMatch;
1332
+ while ((mapMatch = mapPattern.exec(content)) !== null) {
1333
+ const name = `${mapMatch[1]}:${mapMatch[2]}`;
1334
+ const version = mapMatch[3];
1335
+ const dedupKey = `${name}@${version}`;
1336
+ if (seen.has(dedupKey)) continue;
1337
+ seen.add(dedupKey);
1338
+ const isTest = config.toLowerCase().includes("test");
1339
+ results.push({
1340
+ name,
1341
+ version,
1342
+ isDirect: true,
1343
+ isDevDependency: isTest
1344
+ });
1345
+ }
1346
+ }
1347
+ return results;
1348
+ }
1349
+
1350
+ // src/parsers/cocoapods.ts
1351
+ var fs9 = __toESM(require("fs"));
1352
+ function parsePodfileLock(lockfilePath, _manifestPath) {
1353
+ let content;
1354
+ try {
1355
+ content = fs9.readFileSync(lockfilePath, "utf-8");
1356
+ } catch {
1357
+ throw new Error(
1358
+ `Failed to read ${lockfilePath}.
1359
+ The Podfile.lock may be missing or corrupted. Try running 'pod install'.`
1360
+ );
1361
+ }
1362
+ const pods = parsePodsSection(content);
1363
+ const directDeps = parseDependenciesSection(content);
1364
+ const results = [];
1365
+ const seen = /* @__PURE__ */ new Set();
1366
+ for (const [name, version] of pods) {
1367
+ if (name.includes("/")) continue;
1368
+ const dedupKey = `${name}@${version}`;
1369
+ if (seen.has(dedupKey)) continue;
1370
+ seen.add(dedupKey);
1371
+ const isDirect = directDeps.has(name);
1372
+ results.push({
1373
+ name,
1374
+ version,
1375
+ isDirect,
1376
+ isDevDependency: false
1377
+ // CocoaPods doesn't have dev dependency concept
1378
+ });
1379
+ }
1380
+ return results;
1381
+ }
1382
+ function parsePodsSection(content) {
1383
+ const pods = /* @__PURE__ */ new Map();
1384
+ const lines = content.split("\n");
1385
+ let inPodsSection = false;
1386
+ for (const line of lines) {
1387
+ const trimmed = line.trimEnd();
1388
+ if (trimmed === "PODS:") {
1389
+ inPodsSection = true;
1390
+ continue;
1391
+ }
1392
+ if (inPodsSection && trimmed !== "" && !trimmed.startsWith(" ") && !trimmed.startsWith(" ")) {
1393
+ break;
1394
+ }
1395
+ if (!inPodsSection) continue;
1396
+ if (trimmed === "") continue;
1397
+ const podMatch = trimmed.match(/^ - ([^\s(/]+(?:\/[^\s(]+)?)\s+\(([^)]+)\)/);
1398
+ if (podMatch) {
1399
+ pods.set(podMatch[1], podMatch[2]);
1400
+ }
1401
+ }
1402
+ return pods;
1403
+ }
1404
+ function parseDependenciesSection(content) {
1405
+ const directDeps = /* @__PURE__ */ new Set();
1406
+ const lines = content.split("\n");
1407
+ let inDepsSection = false;
1408
+ for (const line of lines) {
1409
+ const trimmed = line.trimEnd();
1410
+ if (trimmed === "DEPENDENCIES:") {
1411
+ inDepsSection = true;
1412
+ continue;
1413
+ }
1414
+ if (inDepsSection && trimmed !== "" && !trimmed.startsWith(" ") && !trimmed.startsWith(" ")) {
1415
+ break;
1416
+ }
1417
+ if (!inDepsSection) continue;
1418
+ if (trimmed === "") continue;
1419
+ const depMatch = trimmed.match(/^ - ([^\s(/]+)/);
1420
+ if (depMatch) {
1421
+ directDeps.add(depMatch[1]);
1422
+ }
1423
+ }
1424
+ return directDeps;
1425
+ }
1426
+
1427
+ // src/parsers/spm.ts
1428
+ var fs10 = __toESM(require("fs"));
1429
+ function parsePackageResolved(resolvedPath, _manifestPath) {
1430
+ let content;
1431
+ try {
1432
+ content = fs10.readFileSync(resolvedPath, "utf-8");
1433
+ } catch {
1434
+ throw new Error(
1435
+ `Failed to read ${resolvedPath}.
1436
+ The Package.resolved file may be missing or corrupted.
1437
+ Run 'swift package resolve' to regenerate it.`
1438
+ );
1439
+ }
1440
+ let parsed;
1441
+ try {
1442
+ parsed = JSON.parse(content);
1443
+ } catch {
1444
+ throw new Error(
1445
+ `Failed to parse ${resolvedPath}: Invalid JSON.
1446
+ The Package.resolved file may be corrupted.`
1447
+ );
1448
+ }
1449
+ const pins = extractPins(parsed);
1450
+ if (!pins) return [];
1451
+ const results = [];
1452
+ const seen = /* @__PURE__ */ new Set();
1453
+ for (const pin of pins) {
1454
+ if (pin.kind === "localSourceControl") continue;
1455
+ const version = pin.state?.version;
1456
+ if (!version) continue;
1457
+ const name = pin.identity;
1458
+ if (!name) continue;
1459
+ const dedupKey = `${name}@${version}`;
1460
+ if (seen.has(dedupKey)) continue;
1461
+ seen.add(dedupKey);
1462
+ results.push({
1463
+ name,
1464
+ version,
1465
+ isDirect: true,
1466
+ // Package.resolved doesn't distinguish
1467
+ isDevDependency: false
1468
+ });
1469
+ }
1470
+ return results;
1471
+ }
1472
+ function extractPins(parsed) {
1473
+ if (Array.isArray(parsed.pins)) {
1474
+ return parsed.pins;
1475
+ }
1476
+ if (parsed.object && Array.isArray(parsed.object.pins)) {
1477
+ return parsed.object.pins.map((pin) => ({
1478
+ identity: pin.package,
1479
+ kind: "remoteSourceControl",
1480
+ location: pin.repositoryURL || "",
1481
+ state: {
1482
+ revision: pin.state?.revision,
1483
+ version: pin.state?.version,
1484
+ branch: pin.state?.branch
1485
+ }
1486
+ }));
1487
+ }
1488
+ return null;
1489
+ }
1490
+ function extractSpmLocations(resolvedPath) {
1491
+ const locations = /* @__PURE__ */ new Map();
1492
+ let content;
1493
+ try {
1494
+ content = fs10.readFileSync(resolvedPath, "utf-8");
1495
+ } catch {
1496
+ return locations;
1497
+ }
1498
+ let parsed;
1499
+ try {
1500
+ parsed = JSON.parse(content);
1501
+ } catch {
1502
+ return locations;
1503
+ }
1504
+ const pins = extractPins(parsed);
1505
+ if (!pins) return locations;
1506
+ for (const pin of pins) {
1507
+ if (pin.identity && pin.location) {
1508
+ locations.set(pin.identity, pin.location);
1509
+ }
1510
+ }
1511
+ return locations;
1512
+ }
1513
+
1514
+ // src/cache.ts
1515
+ var crypto = __toESM(require("crypto"));
1516
+ var fs11 = __toESM(require("fs"));
1517
+ var path4 = __toESM(require("path"));
789
1518
  var KEY_FILENAME = ".preship-key";
790
1519
  var ALGORITHM = "aes-256-cbc";
791
1520
  var IV_LENGTH = 16;
792
1521
  function getOrCreateKey(projectPath) {
793
- const keyPath = path3.join(projectPath, KEY_FILENAME);
1522
+ const keyPath = path4.join(projectPath, KEY_FILENAME);
794
1523
  try {
795
- if (fs6.existsSync(keyPath)) {
796
- const hex = fs6.readFileSync(keyPath, "utf-8").trim();
1524
+ if (fs11.existsSync(keyPath)) {
1525
+ const hex = fs11.readFileSync(keyPath, "utf-8").trim();
797
1526
  if (hex.length === 64) {
798
1527
  return Buffer.from(hex, "hex");
799
1528
  }
@@ -802,7 +1531,7 @@ function getOrCreateKey(projectPath) {
802
1531
  }
803
1532
  const key = crypto.randomBytes(32);
804
1533
  try {
805
- fs6.writeFileSync(keyPath, key.toString("hex"), { mode: 384 });
1534
+ fs11.writeFileSync(keyPath, key.toString("hex"), { mode: 384 });
806
1535
  } catch {
807
1536
  }
808
1537
  return key;
@@ -838,10 +1567,10 @@ function createEmptyCache() {
838
1567
  }
839
1568
  function loadCache(cacheFilePath, key) {
840
1569
  try {
841
- if (!fs6.existsSync(cacheFilePath)) {
1570
+ if (!fs11.existsSync(cacheFilePath)) {
842
1571
  return createEmptyCache();
843
1572
  }
844
- const encryptedContent = fs6.readFileSync(cacheFilePath, "utf-8").trim();
1573
+ const encryptedContent = fs11.readFileSync(cacheFilePath, "utf-8").trim();
845
1574
  if (!encryptedContent) {
846
1575
  return createEmptyCache();
847
1576
  }
@@ -860,8 +1589,8 @@ function saveCache(cacheFilePath, cache, key) {
860
1589
  const jsonStr = JSON.stringify(cache);
861
1590
  const encrypted = encrypt(jsonStr, key);
862
1591
  const tmpPath = cacheFilePath + ".tmp";
863
- fs6.writeFileSync(tmpPath, encrypted, "utf-8");
864
- fs6.renameSync(tmpPath, cacheFilePath);
1592
+ fs11.writeFileSync(tmpPath, encrypted, "utf-8");
1593
+ fs11.renameSync(tmpPath, cacheFilePath);
865
1594
  } catch {
866
1595
  }
867
1596
  }
@@ -1050,13 +1779,19 @@ function createScanTimeoutController(timeout) {
1050
1779
  createEmptyCache,
1051
1780
  createScanTimeoutController,
1052
1781
  detectProjects,
1782
+ extractSpmLocations,
1053
1783
  getCacheEntry,
1054
1784
  getDefaultConfig,
1055
1785
  getOrCreateKey,
1056
1786
  loadCache,
1057
1787
  loadConfig,
1788
+ parseGradleLockfile,
1789
+ parseMavenPom,
1058
1790
  parseNpmLockfile,
1791
+ parsePackageResolved,
1059
1792
  parsePnpmLockfile,
1793
+ parsePodfileLock,
1794
+ parsePubspecLockfile,
1060
1795
  parseYarnLockfile,
1061
1796
  saveCache,
1062
1797
  setCacheEntry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preship/core",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Core infrastructure for PreShip — types, config, detection, parsing, caching, and rate limiting",
5
5
  "author": "Cyfox Inc.",
6
6
  "license": "Apache-2.0",