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 +29 -2
- package/dist/cli.js +336 -18
- package/dist/index.js +11 -1
- package/package.json +6 -2
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 >
|
|
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 #
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 (
|
|
997
|
-
const pkg = JSON.parse(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1230
|
-
|
|
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 (
|
|
1261
|
-
const licenseContent =
|
|
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
|
|
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 (
|
|
3981
|
-
const content =
|
|
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
|
-
|
|
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:
|
|
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
|
|
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",
|