preship 2.0.10 → 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/README.md CHANGED
@@ -211,6 +211,8 @@ npx preship list --format csv # CSV output
211
211
  npx preship report # Full compliance report
212
212
  npx preship report --license-only # License NOTICE file
213
213
  npx preship report --out compliance.json --format json # JSON export
214
+ npx preship report --format cyclonedx # CycloneDX 1.6 SBOM
215
+ npx preship report --format spdx # SPDX 2.3 SBOM
214
216
  ```
215
217
 
216
218
  ### `preship allow <package>` — Add exceptions
@@ -288,7 +290,32 @@ PreShip scans your source code for accidentally committed credentials:
288
290
  - Lockfiles (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
289
291
  - Minified files and files larger than 1MB
290
292
 
291
- **Shannon entropy analysis** catches secrets that don't match any rule pattern but have suspiciously high randomness (hex strings > 3.5 bits/char, base64 > 4.0 bits/char).
293
+ **Shannon entropy analysis** catches secrets that don't match any rule pattern but have suspiciously high randomness (hex strings > 3.5 bits/char, base64 > 5.8 bits/char).
294
+
295
+ ### SBOM Export (Software Bill of Materials)
296
+
297
+ Generate industry-standard SBOMs from your scan results for compliance and supply chain transparency:
298
+
299
+ ```bash
300
+ npx preship report --format cyclonedx # CycloneDX 1.6 JSON
301
+ npx preship report --format spdx # SPDX 2.3 JSON
302
+ ```
303
+
304
+ **CycloneDX 1.6** output includes:
305
+ - All dependency components with Package URLs (PURLs)
306
+ - License information per component (SPDX expressions)
307
+ - Security vulnerabilities mapped from scan findings
308
+ - Dependency graph (direct and transitive relationships)
309
+ - Tool metadata (preship version, scan timestamp)
310
+
311
+ **SPDX 2.3** output includes:
312
+ - All packages with SPDX IDs and external references (PURLs)
313
+ - License concluded and declared per package
314
+ - Relationship types: `DESCRIBES`, `DEPENDS_ON`, `DEV_DEPENDENCY_OF`
315
+ - Unique document namespace per generation
316
+ - Creator info and license list version
317
+
318
+ Both formats are generated from the same `unifiedScan()` data with zero additional dependencies.
292
319
 
293
320
  ---
294
321
 
@@ -523,7 +550,7 @@ git clone https://github.com/dipen-code/preship.git
523
550
  cd preship
524
551
  npm install
525
552
  npm run build
526
- npm test # 613 tests across 21 test files
553
+ npm test # 684 tests across 24 test files
527
554
  ```
528
555
 
529
556
  ---
package/dist/cli.js CHANGED
@@ -27,10 +27,20 @@ var import_commander = require("commander");
27
27
 
28
28
  // src/scanner.ts
29
29
  var path = __toESM(require("path"));
30
+ var fs = __toESM(require("fs"));
30
31
  var import_core = require("@preship/core");
31
32
  var import_license = require("@preship/license");
32
33
  var import_security = require("@preship/security");
33
34
  var import_secrets = require("@preship/secrets");
35
+ function getCliVersion() {
36
+ try {
37
+ const pkgPath = path.join(__dirname, "..", "package.json");
38
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
39
+ return pkg.version || "0.0.0";
40
+ } catch {
41
+ return "0.0.0";
42
+ }
43
+ }
34
44
  async function scan(options) {
35
45
  const startTime = Date.now();
36
46
  const projectPath = path.resolve(options?.projectPath ?? process.cwd());
@@ -192,7 +202,7 @@ async function unifiedScan(options) {
192
202
  const allPassed = (licenseResult?.passed ?? true) && (securityResult?.passed ?? true) && (secretsResult?.passed ?? true);
193
203
  const totalScanDurationMs = Date.now() - startTime;
194
204
  return {
195
- version: "2.0.0",
205
+ version: getCliVersion(),
196
206
  projectPath,
197
207
  projectType: project.type,
198
208
  framework: project.framework,
@@ -981,7 +991,7 @@ function registerScanCommand(program2) {
981
991
  }
982
992
 
983
993
  // src/commands/init.ts
984
- var fs = __toESM(require("fs"));
994
+ var fs2 = __toESM(require("fs"));
985
995
  var path2 = __toESM(require("path"));
986
996
  function registerInitCommand(program2) {
987
997
  program2.command("init").description("Set up PreShip in the current project").option("--policy <name>", "Policy template: commercial-safe, saas-safe, distribution-safe, strict, permissive-only", "commercial-safe").option("--skip-hooks", "Don't add npm lifecycle hooks to package.json").action((options) => {
@@ -989,12 +999,12 @@ function registerInitCommand(program2) {
989
999
  const projectPath = process.cwd();
990
1000
  const configContent = generateConfigContent(options.policy);
991
1001
  const configPath = path2.join(projectPath, "preship-config.yml");
992
- fs.writeFileSync(configPath, configContent, "utf-8");
1002
+ fs2.writeFileSync(configPath, configContent, "utf-8");
993
1003
  console.log(`\u2705 Created preship-config.yml (policy: ${options.policy})`);
994
1004
  if (!options.skipHooks) {
995
1005
  const pkgPath = path2.join(projectPath, "package.json");
996
- if (fs.existsSync(pkgPath)) {
997
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
1006
+ if (fs2.existsSync(pkgPath)) {
1007
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
998
1008
  pkg.scripts = pkg.scripts || {};
999
1009
  if (pkg.scripts.postinstall) {
1000
1010
  pkg.scripts.postinstall = `preship scan --warn-only && ${pkg.scripts.postinstall}`;
@@ -1006,7 +1016,7 @@ function registerInitCommand(program2) {
1006
1016
  } else {
1007
1017
  pkg.scripts.prebuild = "preship scan";
1008
1018
  }
1009
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1019
+ fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1010
1020
  console.log("\u2705 Added postinstall hook to package.json (warn mode)");
1011
1021
  console.log("\u2705 Added prebuild hook to package.json (block mode)");
1012
1022
  }
@@ -1163,11 +1173,304 @@ function registerListCommand(program2) {
1163
1173
  }
1164
1174
 
1165
1175
  // src/commands/report.ts
1166
- var fs2 = __toESM(require("fs"));
1176
+ var fs3 = __toESM(require("fs"));
1167
1177
  var path3 = __toESM(require("path"));
1178
+
1179
+ // src/formatters/cyclonedx.ts
1180
+ var crypto = __toESM(require("crypto"));
1181
+
1182
+ // src/formatters/purl.ts
1183
+ function generatePurl(name, version) {
1184
+ let encodedName;
1185
+ if (name.startsWith("@")) {
1186
+ const withoutAt = name.slice(1);
1187
+ encodedName = `%40${withoutAt}`;
1188
+ } else {
1189
+ encodedName = name;
1190
+ }
1191
+ if (version) {
1192
+ return `pkg:npm/${encodedName}@${version}`;
1193
+ }
1194
+ return `pkg:npm/${encodedName}`;
1195
+ }
1196
+ function sanitizeSpdxId(value) {
1197
+ return value.replace(/[^a-zA-Z0-9.-]/g, "-");
1198
+ }
1199
+
1200
+ // src/formatters/cyclonedx.ts
1201
+ function formatCycloneDx(result) {
1202
+ const bom = generateCycloneDxBom(result);
1203
+ return JSON.stringify(bom, null, 2);
1204
+ }
1205
+ function generateCycloneDxBom(result) {
1206
+ const projectName = getProjectName2(result.projectPath);
1207
+ const rootBomRef = `pkg:npm/${projectName}`;
1208
+ const allPolicyResults = getLicenseResults(result);
1209
+ const components = allPolicyResults.map((pr) => buildComponent(pr));
1210
+ const directDeps = allPolicyResults.filter((pr) => pr.dependency.isDirect).map((pr) => generateBomRef(pr.dependency.name, pr.dependency.version));
1211
+ const dependencies = [
1212
+ {
1213
+ ref: rootBomRef,
1214
+ dependsOn: directDeps
1215
+ },
1216
+ // Each component with no known transitive deps
1217
+ ...components.map((c) => ({
1218
+ ref: c["bom-ref"]
1219
+ }))
1220
+ ];
1221
+ const vulnerabilities = getVulnerabilities(result, allPolicyResults);
1222
+ const bom = {
1223
+ bomFormat: "CycloneDX",
1224
+ specVersion: "1.6",
1225
+ serialNumber: `urn:uuid:${crypto.randomUUID()}`,
1226
+ version: 1,
1227
+ metadata: {
1228
+ timestamp: result.timestamp,
1229
+ tools: {
1230
+ components: [
1231
+ {
1232
+ type: "application",
1233
+ name: "preship",
1234
+ version: result.version
1235
+ }
1236
+ ]
1237
+ },
1238
+ component: {
1239
+ type: "application",
1240
+ name: projectName,
1241
+ version: "0.0.0",
1242
+ // Root project version not available from scan
1243
+ "bom-ref": rootBomRef
1244
+ }
1245
+ },
1246
+ components,
1247
+ dependencies
1248
+ };
1249
+ if (vulnerabilities.length > 0) {
1250
+ bom.vulnerabilities = vulnerabilities;
1251
+ }
1252
+ return bom;
1253
+ }
1254
+ function getProjectName2(projectPath) {
1255
+ const parts = projectPath.replace(/\\/g, "/").split("/");
1256
+ return parts[parts.length - 1] || "unknown-project";
1257
+ }
1258
+ function getLicenseResults(result) {
1259
+ if (!result.modules.license) return [];
1260
+ const license = result.modules.license;
1261
+ return [
1262
+ ...license.allowed,
1263
+ ...license.warned,
1264
+ ...license.rejected,
1265
+ ...license.unknown
1266
+ ];
1267
+ }
1268
+ function generateBomRef(name, version) {
1269
+ return generatePurl(name, version);
1270
+ }
1271
+ function buildComponent(pr) {
1272
+ const dep = pr.dependency;
1273
+ const component = {
1274
+ type: "library",
1275
+ name: dep.name,
1276
+ version: dep.version,
1277
+ purl: generatePurl(dep.name, dep.version),
1278
+ "bom-ref": generatePurl(dep.name, dep.version)
1279
+ };
1280
+ if (dep.isDevDependency) {
1281
+ component.scope = "optional";
1282
+ } else {
1283
+ component.scope = "required";
1284
+ }
1285
+ if (dep.license && dep.license !== "UNKNOWN") {
1286
+ component.licenses = [
1287
+ {
1288
+ license: {
1289
+ id: dep.license
1290
+ }
1291
+ }
1292
+ ];
1293
+ }
1294
+ return component;
1295
+ }
1296
+ function getVulnerabilities(result, allPolicyResults) {
1297
+ if (!result.modules.security) return [];
1298
+ const security = result.modules.security;
1299
+ const vulnerabilities = [];
1300
+ for (const vuln of security.vulnerabilities) {
1301
+ const bomRef = findBomRefForPackage(vuln.package, vuln.version, allPolicyResults);
1302
+ const cdxVuln = {
1303
+ id: vuln.id,
1304
+ source: {
1305
+ name: getSourceName(vuln.source),
1306
+ url: vuln.references[0]
1307
+ },
1308
+ ratings: [
1309
+ {
1310
+ severity: vuln.severity
1311
+ }
1312
+ ],
1313
+ description: vuln.summary
1314
+ };
1315
+ if (bomRef) {
1316
+ cdxVuln.affects = [{ ref: bomRef }];
1317
+ }
1318
+ if (vuln.fixedVersion) {
1319
+ cdxVuln.recommendation = `Upgrade to version ${vuln.fixedVersion}`;
1320
+ }
1321
+ vulnerabilities.push(cdxVuln);
1322
+ }
1323
+ return vulnerabilities;
1324
+ }
1325
+ function findBomRefForPackage(packageName, version, allPolicyResults) {
1326
+ const found = allPolicyResults.find(
1327
+ (pr) => pr.dependency.name === packageName && pr.dependency.version === version
1328
+ );
1329
+ if (found) {
1330
+ return generatePurl(found.dependency.name, found.dependency.version);
1331
+ }
1332
+ return void 0;
1333
+ }
1334
+ function getSourceName(source) {
1335
+ switch (source) {
1336
+ case "osv":
1337
+ return "OSV";
1338
+ case "github-advisory":
1339
+ return "GitHub Advisory Database";
1340
+ case "npm-audit":
1341
+ return "npm Audit";
1342
+ default:
1343
+ return source;
1344
+ }
1345
+ }
1346
+
1347
+ // src/formatters/spdx.ts
1348
+ var crypto2 = __toESM(require("crypto"));
1349
+ function formatSpdx(result) {
1350
+ const doc = generateSpdxDocument(result);
1351
+ return JSON.stringify(doc, null, 2);
1352
+ }
1353
+ function generateSpdxDocument(result) {
1354
+ const projectName = getProjectName3(result.projectPath);
1355
+ const uuid = crypto2.randomUUID();
1356
+ const allPolicyResults = getLicenseResults2(result);
1357
+ const rootPackage = {
1358
+ SPDXID: "SPDXRef-RootPackage",
1359
+ name: projectName,
1360
+ versionInfo: "0.0.0",
1361
+ downloadLocation: "NOASSERTION",
1362
+ filesAnalyzed: false,
1363
+ licenseConcluded: "NOASSERTION",
1364
+ licenseDeclared: "NOASSERTION",
1365
+ copyrightText: "NOASSERTION",
1366
+ supplier: "NOASSERTION",
1367
+ externalRefs: []
1368
+ };
1369
+ const depPackages = allPolicyResults.map((pr) => buildSpdxPackage(pr));
1370
+ const relationships = [];
1371
+ relationships.push({
1372
+ spdxElementId: "SPDXRef-DOCUMENT",
1373
+ relatedSpdxElement: "SPDXRef-RootPackage",
1374
+ relationshipType: "DESCRIBES"
1375
+ });
1376
+ for (const pr of allPolicyResults) {
1377
+ const depSpdxId = generateSpdxPackageId(pr.dependency.name, pr.dependency.version);
1378
+ if (pr.dependency.isDevDependency) {
1379
+ relationships.push({
1380
+ spdxElementId: depSpdxId,
1381
+ relatedSpdxElement: "SPDXRef-RootPackage",
1382
+ relationshipType: "DEV_DEPENDENCY_OF"
1383
+ });
1384
+ } else if (pr.dependency.isDirect) {
1385
+ relationships.push({
1386
+ spdxElementId: "SPDXRef-RootPackage",
1387
+ relatedSpdxElement: depSpdxId,
1388
+ relationshipType: "DEPENDS_ON"
1389
+ });
1390
+ } else {
1391
+ relationships.push({
1392
+ spdxElementId: "SPDXRef-RootPackage",
1393
+ relatedSpdxElement: depSpdxId,
1394
+ relationshipType: "DEPENDS_ON"
1395
+ });
1396
+ }
1397
+ }
1398
+ return {
1399
+ spdxVersion: "SPDX-2.3",
1400
+ dataLicense: "CC0-1.0",
1401
+ SPDXID: "SPDXRef-DOCUMENT",
1402
+ name: projectName,
1403
+ documentNamespace: `https://preship.dev/spdxdocs/${projectName}-${uuid}`,
1404
+ creationInfo: {
1405
+ created: result.timestamp,
1406
+ creators: [`Tool: preship-${result.version}`],
1407
+ licenseListVersion: "3.22"
1408
+ },
1409
+ packages: [rootPackage, ...depPackages],
1410
+ relationships
1411
+ };
1412
+ }
1413
+ function getProjectName3(projectPath) {
1414
+ const parts = projectPath.replace(/\\/g, "/").split("/");
1415
+ return parts[parts.length - 1] || "unknown-project";
1416
+ }
1417
+ function getLicenseResults2(result) {
1418
+ if (!result.modules.license) return [];
1419
+ const license = result.modules.license;
1420
+ return [
1421
+ ...license.allowed,
1422
+ ...license.warned,
1423
+ ...license.rejected,
1424
+ ...license.unknown
1425
+ ];
1426
+ }
1427
+ function generateSpdxPackageId(name, version) {
1428
+ const sanitizedName = sanitizeSpdxId(name);
1429
+ const sanitizedVersion = sanitizeSpdxId(version);
1430
+ return `SPDXRef-Package-${sanitizedName}-${sanitizedVersion}`;
1431
+ }
1432
+ function buildSpdxPackage(pr) {
1433
+ const dep = pr.dependency;
1434
+ const spdxId = generateSpdxPackageId(dep.name, dep.version);
1435
+ const purl = generatePurl(dep.name, dep.version);
1436
+ const license = dep.license === "UNKNOWN" ? "NOASSERTION" : dep.license;
1437
+ const downloadLocation = `https://registry.npmjs.org/${dep.name}/-/${getPackageBasename(dep.name)}-${dep.version}.tgz`;
1438
+ return {
1439
+ SPDXID: spdxId,
1440
+ name: dep.name,
1441
+ versionInfo: dep.version,
1442
+ downloadLocation,
1443
+ filesAnalyzed: false,
1444
+ licenseConcluded: license,
1445
+ licenseDeclared: license,
1446
+ copyrightText: "NOASSERTION",
1447
+ supplier: "NOASSERTION",
1448
+ externalRefs: [
1449
+ {
1450
+ referenceCategory: "PACKAGE-MANAGER",
1451
+ referenceType: "purl",
1452
+ referenceLocator: purl
1453
+ }
1454
+ ]
1455
+ };
1456
+ }
1457
+ function getPackageBasename(name) {
1458
+ if (name.startsWith("@")) {
1459
+ const parts = name.split("/");
1460
+ return parts[parts.length - 1];
1461
+ }
1462
+ return name;
1463
+ }
1464
+
1465
+ // src/commands/report.ts
1168
1466
  function registerReportCommand(program2) {
1169
- program2.command("report").description("Generate a license attribution/NOTICE file or full scan report").option("--out <path>", "Output file path", "NOTICE.txt").option("--format <type>", "Format: text, json, csv", "text").option("--project <path>", "Path to project root", process.cwd()).option("--license-only", "Generate license-only report (legacy mode)").option("--no-license", "Exclude license information from report").option("--no-security", "Exclude security information from report").option("--no-secrets", "Exclude secrets information from report").action(async (options) => {
1467
+ program2.command("report").description("Generate a license attribution/NOTICE file or full scan report").option("--out <path>", "Output file path", "NOTICE.txt").option("--format <type>", "Format: text, json, csv, cyclonedx, spdx", "text").option("--project <path>", "Path to project root", process.cwd()).option("--license-only", "Generate license-only report (legacy mode)").option("--no-license", "Exclude license information from report").option("--no-security", "Exclude security information from report").option("--no-secrets", "Exclude secrets information from report").action(async (options) => {
1170
1468
  try {
1469
+ const validFormats = ["text", "json", "csv", "cyclonedx", "spdx"];
1470
+ if (!validFormats.includes(options.format)) {
1471
+ console.error(`\u274C Unknown format: "${options.format}". Valid formats: ${validFormats.join(", ")}`);
1472
+ process.exit(2);
1473
+ }
1171
1474
  if (options.licenseOnly) {
1172
1475
  const result2 = await scan({
1173
1476
  projectPath: options.project,
@@ -1199,15 +1502,16 @@ function registerReportCommand(program2) {
1199
1502
  break;
1200
1503
  }
1201
1504
  const outPath2 = path3.resolve(options.out);
1202
- fs2.writeFileSync(outPath2, content2, "utf-8");
1505
+ fs3.writeFileSync(outPath2, content2, "utf-8");
1203
1506
  console.log(`\u2705 Generated ${options.out} with ${allResults.length} package attributions`);
1204
1507
  return;
1205
1508
  }
1509
+ const isSbomFormat = options.format === "cyclonedx" || options.format === "spdx";
1206
1510
  const result = await unifiedScan({
1207
1511
  projectPath: options.project,
1208
1512
  config: {
1209
1513
  scanDevDependencies: true,
1210
- modules: {
1514
+ modules: isSbomFormat ? { license: true, security: true, secrets: true } : {
1211
1515
  license: options.license !== void 0 ? options.license : void 0,
1212
1516
  security: options.security !== void 0 ? options.security : void 0,
1213
1517
  secrets: options.secrets !== void 0 ? options.secrets : void 0
@@ -1222,12 +1526,26 @@ function registerReportCommand(program2) {
1222
1526
  case "csv":
1223
1527
  content = generateUnifiedCsvReport(result);
1224
1528
  break;
1529
+ case "cyclonedx":
1530
+ content = formatCycloneDx(result);
1531
+ break;
1532
+ case "spdx":
1533
+ content = formatSpdx(result);
1534
+ break;
1225
1535
  default:
1226
1536
  content = generateUnifiedTextReport(result);
1227
1537
  break;
1228
1538
  }
1229
- const outPath = path3.resolve(options.out === "NOTICE.txt" ? "preship-report.txt" : options.out);
1230
- fs2.writeFileSync(outPath, content, "utf-8");
1539
+ let defaultOut;
1540
+ if (options.format === "cyclonedx") {
1541
+ defaultOut = "preship-sbom.cdx.json";
1542
+ } else if (options.format === "spdx") {
1543
+ defaultOut = "preship-sbom.spdx.json";
1544
+ } else {
1545
+ defaultOut = "preship-report.txt";
1546
+ }
1547
+ const outPath = path3.resolve(options.out === "NOTICE.txt" ? defaultOut : options.out);
1548
+ fs3.writeFileSync(outPath, content, "utf-8");
1231
1549
  const modulesRan = [];
1232
1550
  if (result.modules.license) modulesRan.push("license");
1233
1551
  if (result.modules.security) modulesRan.push("security");
@@ -1257,8 +1575,8 @@ function generateNoticeText(results, projectPath) {
1257
1575
  for (const lf of licenseFiles) {
1258
1576
  const licensePath = path3.join(r.dependency.path, lf);
1259
1577
  try {
1260
- if (fs2.existsSync(licensePath)) {
1261
- const licenseContent = fs2.readFileSync(licensePath, "utf-8");
1578
+ if (fs3.existsSync(licensePath)) {
1579
+ const licenseContent = fs3.readFileSync(licensePath, "utf-8");
1262
1580
  lines.push("");
1263
1581
  lines.push(licenseContent.trim());
1264
1582
  break;
@@ -1381,7 +1699,7 @@ function escapeCsv2(value) {
1381
1699
  }
1382
1700
 
1383
1701
  // src/commands/allow.ts
1384
- var fs3 = __toESM(require("fs"));
1702
+ var fs4 = __toESM(require("fs"));
1385
1703
  var path4 = __toESM(require("path"));
1386
1704
 
1387
1705
  // ../../node_modules/js-yaml/dist/js-yaml.mjs
@@ -3977,8 +4295,8 @@ function registerAllowCommand(program2) {
3977
4295
  const projectPath = process.cwd();
3978
4296
  const configPath = path4.join(projectPath, "preship-config.yml");
3979
4297
  let config = {};
3980
- if (fs3.existsSync(configPath)) {
3981
- const content = fs3.readFileSync(configPath, "utf-8");
4298
+ if (fs4.existsSync(configPath)) {
4299
+ const content = fs4.readFileSync(configPath, "utf-8");
3982
4300
  const parsed = load(content);
3983
4301
  if (parsed && typeof parsed === "object") {
3984
4302
  config = parsed;
@@ -4007,7 +4325,7 @@ function registerAllowCommand(program2) {
4007
4325
  quotingType: '"',
4008
4326
  forceQuotes: false
4009
4327
  });
4010
- fs3.writeFileSync(configPath, yamlContent, "utf-8");
4328
+ fs4.writeFileSync(configPath, yamlContent, "utf-8");
4011
4329
  } catch (error) {
4012
4330
  const message = error instanceof Error ? error.message : String(error);
4013
4331
  console.error(`\u274C ${message}`);
package/dist/index.js CHANGED
@@ -37,10 +37,20 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/scanner.ts
39
39
  var path = __toESM(require("path"));
40
+ var fs = __toESM(require("fs"));
40
41
  var import_core = require("@preship/core");
41
42
  var import_license = require("@preship/license");
42
43
  var import_security = require("@preship/security");
43
44
  var import_secrets = require("@preship/secrets");
45
+ function getCliVersion() {
46
+ try {
47
+ const pkgPath = path.join(__dirname, "..", "package.json");
48
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
49
+ return pkg.version || "0.0.0";
50
+ } catch {
51
+ return "0.0.0";
52
+ }
53
+ }
44
54
  async function scan(options) {
45
55
  const startTime = Date.now();
46
56
  const projectPath = path.resolve(options?.projectPath ?? process.cwd());
@@ -202,7 +212,7 @@ async function unifiedScan(options) {
202
212
  const allPassed = (licenseResult?.passed ?? true) && (securityResult?.passed ?? true) && (secretsResult?.passed ?? true);
203
213
  const totalScanDurationMs = Date.now() - startTime;
204
214
  return {
205
- version: "2.0.0",
215
+ version: getCliVersion(),
206
216
  projectPath,
207
217
  projectType: project.type,
208
218
  framework: project.framework,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preship",
3
- "version": "2.0.10",
3
+ "version": "2.1.0",
4
4
  "description": "Pre-ship verification for modern dev teams. License compliance, security vulnerability scanning, and secret detection — all in one CLI. Zero config. Fully offline. Free forever.",
5
5
  "keywords": [
6
6
  "license",
@@ -25,7 +25,11 @@
25
25
  "offline",
26
26
  "air-gap",
27
27
  "osv",
28
- "cve"
28
+ "cve",
29
+ "sbom",
30
+ "cyclonedx",
31
+ "spdx",
32
+ "software-bill-of-materials"
29
33
  ],
30
34
  "author": "Cyfox Inc.",
31
35
  "license": "Apache-2.0",