preship 1.0.5 → 2.0.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/cli.js +765 -54
- package/dist/index.d.ts +12 -3
- package/dist/index.js +121 -2
- package/package.json +13 -5
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,8 @@ var import_commander = require("commander");
|
|
|
29
29
|
var path = __toESM(require("path"));
|
|
30
30
|
var import_core = require("@preship/core");
|
|
31
31
|
var import_license = require("@preship/license");
|
|
32
|
+
var import_security = require("@preship/security");
|
|
33
|
+
var import_secrets = require("@preship/secrets");
|
|
32
34
|
async function scan(options) {
|
|
33
35
|
const startTime = Date.now();
|
|
34
36
|
const projectPath = path.resolve(options?.projectPath ?? process.cwd());
|
|
@@ -84,6 +86,121 @@ async function scan(options) {
|
|
|
84
86
|
scanDurationMs
|
|
85
87
|
};
|
|
86
88
|
}
|
|
89
|
+
async function unifiedScan(options) {
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
const projectPath = path.resolve(options?.projectPath ?? process.cwd());
|
|
92
|
+
let config = (0, import_core.loadConfig)(projectPath);
|
|
93
|
+
if (options?.config) {
|
|
94
|
+
config = {
|
|
95
|
+
...config,
|
|
96
|
+
...options.config,
|
|
97
|
+
modules: { ...config.modules, ...options.config.modules },
|
|
98
|
+
security: { ...config.security, ...options.config.security },
|
|
99
|
+
secrets: { ...config.secrets, ...options.config.secrets }
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const modules = {
|
|
103
|
+
license: config.modules?.license ?? true,
|
|
104
|
+
security: config.modules?.security ?? true,
|
|
105
|
+
secrets: config.modules?.secrets ?? true
|
|
106
|
+
};
|
|
107
|
+
if (!modules.license && !modules.security && !modules.secrets) {
|
|
108
|
+
console.warn("\u26A0\uFE0F No scan modules enabled. Use --no-license, --no-security, --no-secrets to selectively disable.");
|
|
109
|
+
}
|
|
110
|
+
const mode = config.mode ?? "auto";
|
|
111
|
+
const policy = config.policy ?? "commercial-safe";
|
|
112
|
+
const projects = (0, import_core.detectProjects)(projectPath);
|
|
113
|
+
const project = projects[0];
|
|
114
|
+
const packageJsonPath = path.join(project.path, "package.json");
|
|
115
|
+
let parsedDeps;
|
|
116
|
+
switch (project.type) {
|
|
117
|
+
case "npm":
|
|
118
|
+
parsedDeps = (0, import_core.parseNpmLockfile)(project.lockFile, packageJsonPath);
|
|
119
|
+
break;
|
|
120
|
+
case "yarn":
|
|
121
|
+
parsedDeps = (0, import_core.parseYarnLockfile)(project.lockFile, packageJsonPath);
|
|
122
|
+
break;
|
|
123
|
+
case "pnpm":
|
|
124
|
+
parsedDeps = (0, import_core.parsePnpmLockfile)(project.lockFile, packageJsonPath);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
throw new Error(`Unsupported project type: ${project.type}`);
|
|
128
|
+
}
|
|
129
|
+
if (!config.scanDevDependencies) {
|
|
130
|
+
parsedDeps = parsedDeps.filter((dep) => !dep.isDevDependency);
|
|
131
|
+
}
|
|
132
|
+
let licenseResult;
|
|
133
|
+
let securityResult;
|
|
134
|
+
let secretsResult;
|
|
135
|
+
const promises = [];
|
|
136
|
+
if (modules.license) {
|
|
137
|
+
promises.push(
|
|
138
|
+
(async () => {
|
|
139
|
+
const licenseStart = Date.now();
|
|
140
|
+
const dependencies = await (0, import_license.resolveLicenses)(parsedDeps, projectPath, {
|
|
141
|
+
mode,
|
|
142
|
+
networkTimeout: config.networkTimeout ?? 5e3,
|
|
143
|
+
networkConcurrency: config.networkConcurrency ?? 10,
|
|
144
|
+
cache: config.cache ?? true,
|
|
145
|
+
cacheTTL: config.cacheTTL ?? 604800,
|
|
146
|
+
scanTimeout: config.scanTimeout ?? 6e4
|
|
147
|
+
});
|
|
148
|
+
const results = (0, import_license.evaluatePolicy)(dependencies, config);
|
|
149
|
+
const allowed = results.filter((r) => r.verdict === "allowed");
|
|
150
|
+
const warned = results.filter((r) => r.verdict === "warned");
|
|
151
|
+
const rejected = results.filter((r) => r.verdict === "rejected");
|
|
152
|
+
const unknown = results.filter((r) => r.verdict === "unknown");
|
|
153
|
+
licenseResult = {
|
|
154
|
+
projectPath,
|
|
155
|
+
projectType: project.type,
|
|
156
|
+
framework: project.framework,
|
|
157
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
+
totalPackages: dependencies.length,
|
|
159
|
+
allowed,
|
|
160
|
+
warned,
|
|
161
|
+
rejected,
|
|
162
|
+
unknown,
|
|
163
|
+
passed: rejected.length === 0,
|
|
164
|
+
scanDurationMs: Date.now() - licenseStart
|
|
165
|
+
};
|
|
166
|
+
})()
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (modules.security) {
|
|
170
|
+
promises.push(
|
|
171
|
+
(async () => {
|
|
172
|
+
const securityConfig = (0, import_security.mergeSecurityConfig)(config.security);
|
|
173
|
+
securityResult = await (0, import_security.scanSecurity)(parsedDeps, projectPath, securityConfig, mode);
|
|
174
|
+
})()
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (modules.secrets) {
|
|
178
|
+
promises.push(
|
|
179
|
+
(async () => {
|
|
180
|
+
secretsResult = await (0, import_secrets.scanSecrets)(projectPath, config.secrets);
|
|
181
|
+
})()
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
await Promise.all(promises);
|
|
185
|
+
const allPassed = (licenseResult?.passed ?? true) && (securityResult?.passed ?? true) && (secretsResult?.passed ?? true);
|
|
186
|
+
const totalScanDurationMs = Date.now() - startTime;
|
|
187
|
+
return {
|
|
188
|
+
version: "2.0.0",
|
|
189
|
+
projectPath,
|
|
190
|
+
projectType: project.type,
|
|
191
|
+
framework: project.framework,
|
|
192
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
+
passed: allPassed,
|
|
194
|
+
mode,
|
|
195
|
+
policy,
|
|
196
|
+
modules: {
|
|
197
|
+
license: licenseResult,
|
|
198
|
+
security: securityResult,
|
|
199
|
+
secrets: secretsResult
|
|
200
|
+
},
|
|
201
|
+
totalScanDurationMs
|
|
202
|
+
};
|
|
203
|
+
}
|
|
87
204
|
|
|
88
205
|
// src/output/terminal.ts
|
|
89
206
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -95,9 +212,9 @@ function formatTerminalScan(result, warnOnly = false) {
|
|
|
95
212
|
}
|
|
96
213
|
lines.push("");
|
|
97
214
|
lines.push(import_chalk.default.bold("\u{1F50D} PreShip: Scanning project..."));
|
|
98
|
-
lines.push(` Project: ${import_chalk.default.cyan(getProjectName(result))}${result.framework ? ` (${result.framework})` : ""}`);
|
|
99
|
-
lines.push(` Lock file: ${import_chalk.default.cyan(getLockFileName(result))} (${result.projectType})`);
|
|
100
|
-
lines.push(` Policy: ${import_chalk.default.cyan(getPolicyName(
|
|
215
|
+
lines.push(` Project: ${import_chalk.default.cyan(getProjectName(result.projectPath))}${result.framework ? ` (${result.framework})` : ""}`);
|
|
216
|
+
lines.push(` Lock file: ${import_chalk.default.cyan(getLockFileName(result.projectType))} (${result.projectType})`);
|
|
217
|
+
lines.push(` Policy: ${import_chalk.default.cyan(getPolicyName())}`);
|
|
101
218
|
lines.push("");
|
|
102
219
|
lines.push(`\u{1F4E6} Scanned ${import_chalk.default.bold(String(result.totalPackages))} packages in ${(result.scanDurationMs / 1e3).toFixed(1)}s`);
|
|
103
220
|
lines.push("");
|
|
@@ -146,7 +263,7 @@ function formatTerminalList(result) {
|
|
|
146
263
|
const allResults = [...result.allowed, ...result.warned, ...result.rejected, ...result.unknown];
|
|
147
264
|
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
148
265
|
lines.push("");
|
|
149
|
-
lines.push(`\u{1F4E6} ${result.totalPackages} packages in ${getProjectName(result)}`);
|
|
266
|
+
lines.push(`\u{1F4E6} ${result.totalPackages} packages in ${getProjectName(result.projectPath)}`);
|
|
150
267
|
lines.push("");
|
|
151
268
|
const table = new import_cli_table3.default({
|
|
152
269
|
head: ["Package", "Version", "License", "Source", "Status"],
|
|
@@ -181,6 +298,135 @@ function formatTerminalList(result) {
|
|
|
181
298
|
lines.push("");
|
|
182
299
|
return lines.join("\n");
|
|
183
300
|
}
|
|
301
|
+
function formatTerminalUnified(result, warnOnly = false) {
|
|
302
|
+
const lines = [];
|
|
303
|
+
if (warnOnly) {
|
|
304
|
+
return formatWarnOnlyUnified(result);
|
|
305
|
+
}
|
|
306
|
+
lines.push("");
|
|
307
|
+
lines.push(import_chalk.default.bold("\u{1F50D} PreShip: Pre-ship verification"));
|
|
308
|
+
lines.push(` Project: ${import_chalk.default.cyan(getProjectName(result.projectPath))}${result.framework ? ` (${result.framework})` : ""}`);
|
|
309
|
+
lines.push(` Lock file: ${import_chalk.default.cyan(getLockFileName(result.projectType))} (${result.projectType})`);
|
|
310
|
+
lines.push(` Policy: ${import_chalk.default.cyan(result.policy)}`);
|
|
311
|
+
lines.push(` Mode: ${import_chalk.default.cyan(result.mode)}`);
|
|
312
|
+
const enabledModules = [];
|
|
313
|
+
if (result.modules.license) enabledModules.push("license");
|
|
314
|
+
if (result.modules.security) enabledModules.push("security");
|
|
315
|
+
if (result.modules.secrets) enabledModules.push("secrets");
|
|
316
|
+
if (enabledModules.length === 0) {
|
|
317
|
+
lines.push(` Modules: ${import_chalk.default.yellow("none (all disabled)")}`);
|
|
318
|
+
lines.push("");
|
|
319
|
+
lines.push(import_chalk.default.yellow("\u26A0\uFE0F No scan modules are enabled. Nothing to check."));
|
|
320
|
+
lines.push(import_chalk.default.dim(" Enable modules in preship-config.yml or remove --no-* flags."));
|
|
321
|
+
lines.push("");
|
|
322
|
+
lines.push("\u2501".repeat(60));
|
|
323
|
+
lines.push(import_chalk.default.yellow.bold("\u26A0\uFE0F RESULT: No modules enabled \u2014 scan skipped"));
|
|
324
|
+
lines.push("");
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
lines.push(` Modules: ${import_chalk.default.cyan(enabledModules.join(", "))}`);
|
|
328
|
+
lines.push("");
|
|
329
|
+
if (result.modules.license) {
|
|
330
|
+
const license = result.modules.license;
|
|
331
|
+
lines.push(import_chalk.default.bold.underline("\u{1F4DC} License Compliance"));
|
|
332
|
+
lines.push(` Scanned ${import_chalk.default.bold(String(license.totalPackages))} packages in ${(license.scanDurationMs / 1e3).toFixed(1)}s`);
|
|
333
|
+
lines.push("");
|
|
334
|
+
if (license.rejected.length === 0 && license.warned.length === 0 && license.unknown.length === 0) {
|
|
335
|
+
lines.push(import_chalk.default.green(` \u2705 All ${license.totalPackages} packages passed license check`));
|
|
336
|
+
} else {
|
|
337
|
+
if (license.allowed.length > 0) {
|
|
338
|
+
lines.push(import_chalk.default.green(` \u2705 ${license.allowed.length} packages \u2014 Allowed`));
|
|
339
|
+
}
|
|
340
|
+
if (license.warned.length > 0) {
|
|
341
|
+
lines.push(import_chalk.default.yellow(` \u26A0\uFE0F ${license.warned.length} package${license.warned.length > 1 ? "s" : ""} \u2014 Warning (weak copyleft)`));
|
|
342
|
+
formatPolicyResultsIndented(license.warned, lines, import_chalk.default.yellow);
|
|
343
|
+
}
|
|
344
|
+
if (license.rejected.length > 0) {
|
|
345
|
+
lines.push(import_chalk.default.red(` \u274C ${license.rejected.length} package${license.rejected.length > 1 ? "s" : ""} \u2014 Rejected`));
|
|
346
|
+
formatPolicyResultsIndented(license.rejected, lines, import_chalk.default.red);
|
|
347
|
+
}
|
|
348
|
+
if (license.unknown.length > 0) {
|
|
349
|
+
lines.push(import_chalk.default.yellow(` \u26A0\uFE0F ${license.unknown.length} package${license.unknown.length > 1 ? "s" : ""} \u2014 Unknown License`));
|
|
350
|
+
formatPolicyResultsIndented(license.unknown, lines, import_chalk.default.yellow);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
lines.push("");
|
|
354
|
+
}
|
|
355
|
+
if (result.modules.security) {
|
|
356
|
+
const security = result.modules.security;
|
|
357
|
+
lines.push(import_chalk.default.bold.underline("\u{1F6E1}\uFE0F Security Vulnerabilities"));
|
|
358
|
+
lines.push(` Scanned ${import_chalk.default.bold(String(security.totalPackages))} packages in ${(security.scanDurationMs / 1e3).toFixed(1)}s`);
|
|
359
|
+
lines.push("");
|
|
360
|
+
if (security.findings.length === 0) {
|
|
361
|
+
lines.push(import_chalk.default.green(" \u2705 No security issues found"));
|
|
362
|
+
} else {
|
|
363
|
+
const statParts = [];
|
|
364
|
+
if (security.stats.critical > 0) statParts.push(import_chalk.default.red(`${security.stats.critical} critical`));
|
|
365
|
+
if (security.stats.high > 0) statParts.push(import_chalk.default.red(`${security.stats.high} high`));
|
|
366
|
+
if (security.stats.medium > 0) statParts.push(import_chalk.default.yellow(`${security.stats.medium} medium`));
|
|
367
|
+
if (security.stats.low > 0) statParts.push(import_chalk.default.dim(`${security.stats.low} low`));
|
|
368
|
+
if (security.stats.deprecated > 0) statParts.push(import_chalk.default.yellow(`${security.stats.deprecated} deprecated`));
|
|
369
|
+
if (security.stats.outdated > 0) statParts.push(import_chalk.default.yellow(`${security.stats.outdated} outdated`));
|
|
370
|
+
if (security.stats.unmaintained > 0) statParts.push(import_chalk.default.yellow(`${security.stats.unmaintained} unmaintained`));
|
|
371
|
+
lines.push(` ${statParts.join(" \u2502 ")}`);
|
|
372
|
+
lines.push("");
|
|
373
|
+
const vulnFindings = security.findings.filter((f) => f.type === "vulnerability");
|
|
374
|
+
const healthFindings = security.findings.filter((f) => f.type !== "vulnerability");
|
|
375
|
+
if (vulnFindings.length > 0) {
|
|
376
|
+
formatSecurityFindings(vulnFindings, lines, "Vulnerabilities");
|
|
377
|
+
}
|
|
378
|
+
if (healthFindings.length > 0) {
|
|
379
|
+
formatSecurityFindings(healthFindings, lines, "Health Issues");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
lines.push("");
|
|
383
|
+
}
|
|
384
|
+
if (result.modules.secrets) {
|
|
385
|
+
const secrets = result.modules.secrets;
|
|
386
|
+
lines.push(import_chalk.default.bold.underline("\u{1F511} Secret Detection"));
|
|
387
|
+
lines.push(` Scanned ${import_chalk.default.bold(String(secrets.filesScanned))} files in ${(secrets.scanDurationMs / 1e3).toFixed(1)}s`);
|
|
388
|
+
lines.push("");
|
|
389
|
+
if (secrets.findings.length === 0) {
|
|
390
|
+
lines.push(import_chalk.default.green(" \u2705 No leaked secrets found"));
|
|
391
|
+
} else {
|
|
392
|
+
const statParts = [];
|
|
393
|
+
if (secrets.stats.critical > 0) statParts.push(import_chalk.default.red(`${secrets.stats.critical} critical`));
|
|
394
|
+
if (secrets.stats.high > 0) statParts.push(import_chalk.default.red(`${secrets.stats.high} high`));
|
|
395
|
+
if (secrets.stats.medium > 0) statParts.push(import_chalk.default.yellow(`${secrets.stats.medium} medium`));
|
|
396
|
+
if (secrets.stats.low > 0) statParts.push(import_chalk.default.dim(`${secrets.stats.low} low`));
|
|
397
|
+
lines.push(` ${statParts.join(" \u2502 ")}`);
|
|
398
|
+
lines.push("");
|
|
399
|
+
formatSecretFindings(secrets.findings, lines);
|
|
400
|
+
}
|
|
401
|
+
lines.push("");
|
|
402
|
+
}
|
|
403
|
+
lines.push("\u2501".repeat(60));
|
|
404
|
+
if (result.passed) {
|
|
405
|
+
lines.push(import_chalk.default.green.bold(`\u2705 RESULT: All checks passed (${(result.totalScanDurationMs / 1e3).toFixed(1)}s)`));
|
|
406
|
+
} else {
|
|
407
|
+
const failedModules = [];
|
|
408
|
+
if (result.modules.license && !result.modules.license.passed) failedModules.push("license");
|
|
409
|
+
if (result.modules.security && !result.modules.security.passed) failedModules.push("security");
|
|
410
|
+
if (result.modules.secrets && !result.modules.secrets.passed) failedModules.push("secrets");
|
|
411
|
+
lines.push(import_chalk.default.red.bold(`\u274C RESULT: Failed \u2014 ${failedModules.join(", ")} check${failedModules.length > 1 ? "s" : ""} did not pass`));
|
|
412
|
+
lines.push("");
|
|
413
|
+
lines.push(import_chalk.default.dim("\u{1F4A1} To fix:"));
|
|
414
|
+
if (result.modules.license && !result.modules.license.passed) {
|
|
415
|
+
lines.push(import_chalk.default.dim(" \u2022 Remove or replace rejected packages"));
|
|
416
|
+
lines.push(import_chalk.default.dim(' \u2022 Or add exceptions: preship allow <package> --reason "justification"'));
|
|
417
|
+
}
|
|
418
|
+
if (result.modules.security && !result.modules.security.passed) {
|
|
419
|
+
lines.push(import_chalk.default.dim(" \u2022 Update packages with known vulnerabilities"));
|
|
420
|
+
lines.push(import_chalk.default.dim(" \u2022 Replace deprecated or unmaintained packages"));
|
|
421
|
+
}
|
|
422
|
+
if (result.modules.secrets && !result.modules.secrets.passed) {
|
|
423
|
+
lines.push(import_chalk.default.dim(" \u2022 Remove leaked secrets from source code"));
|
|
424
|
+
lines.push(import_chalk.default.dim(" \u2022 Use environment variables or secret managers instead"));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
lines.push("");
|
|
428
|
+
return lines.join("\n");
|
|
429
|
+
}
|
|
184
430
|
function formatWarnOnly(result) {
|
|
185
431
|
const lines = [];
|
|
186
432
|
lines.push("\u{1F4E6} PreShip: Quick license check...");
|
|
@@ -197,6 +443,39 @@ function formatWarnOnly(result) {
|
|
|
197
443
|
}
|
|
198
444
|
return lines.join("\n");
|
|
199
445
|
}
|
|
446
|
+
function formatWarnOnlyUnified(result) {
|
|
447
|
+
const lines = [];
|
|
448
|
+
lines.push("\u{1F4E6} PreShip: Quick check...");
|
|
449
|
+
lines.push("");
|
|
450
|
+
let issueCount = 0;
|
|
451
|
+
if (result.modules.license) {
|
|
452
|
+
for (const r of [...result.modules.license.rejected, ...result.modules.license.warned]) {
|
|
453
|
+
lines.push(`\u26A0\uFE0F [license] ${r.dependency.name}@${r.dependency.version} \u2014 ${r.dependency.license} (${r.reason})`);
|
|
454
|
+
issueCount++;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (result.modules.security) {
|
|
458
|
+
for (const f of result.modules.security.findings) {
|
|
459
|
+
const icon = f.severity === "critical" || f.severity === "high" ? "\u274C" : "\u26A0\uFE0F";
|
|
460
|
+
lines.push(`${icon} [security] ${f.package}@${f.version} \u2014 ${f.message}`);
|
|
461
|
+
issueCount++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (result.modules.secrets) {
|
|
465
|
+
for (const f of result.modules.secrets.findings) {
|
|
466
|
+
const icon = f.severity === "critical" || f.severity === "high" ? "\u274C" : "\u26A0\uFE0F";
|
|
467
|
+
lines.push(`${icon} [secrets] ${f.file}:${f.line} \u2014 ${f.description} (${f.match})`);
|
|
468
|
+
issueCount++;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (issueCount === 0) {
|
|
472
|
+
lines.push(`\u2705 All checks passed. Run 'preship scan' for full report.`);
|
|
473
|
+
} else {
|
|
474
|
+
lines.push("");
|
|
475
|
+
lines.push(`${issueCount} issue${issueCount > 1 ? "s" : ""} found. Run 'preship scan' for full report.`);
|
|
476
|
+
}
|
|
477
|
+
return lines.join("\n");
|
|
478
|
+
}
|
|
200
479
|
function formatPolicyResults(results, lines, colorFn) {
|
|
201
480
|
for (let i = 0; i < results.length; i++) {
|
|
202
481
|
const r = results[i];
|
|
@@ -207,6 +486,109 @@ function formatPolicyResults(results, lines, colorFn) {
|
|
|
207
486
|
lines.push(import_chalk.default.dim(`${reasonPrefix}Reason: ${getShortReason(r)}`));
|
|
208
487
|
}
|
|
209
488
|
}
|
|
489
|
+
function formatPolicyResultsIndented(results, lines, colorFn) {
|
|
490
|
+
for (let i = 0; i < results.length; i++) {
|
|
491
|
+
const r = results[i];
|
|
492
|
+
const isLast = i === results.length - 1;
|
|
493
|
+
const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
|
|
494
|
+
const reasonPrefix = isLast ? " " : " \u2502 ";
|
|
495
|
+
lines.push(colorFn(`${prefix}${r.dependency.name}@${r.dependency.version} \u2014 ${r.dependency.license}`));
|
|
496
|
+
lines.push(import_chalk.default.dim(`${reasonPrefix}Reason: ${getShortReason(r)}`));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function formatSecurityFindings(findings, lines, heading) {
|
|
500
|
+
const MAX_FINDINGS = 15;
|
|
501
|
+
const sorted = [...findings].sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
|
|
502
|
+
lines.push(import_chalk.default.dim(` ${heading}:`));
|
|
503
|
+
const showCount = Math.min(sorted.length, MAX_FINDINGS);
|
|
504
|
+
for (let i = 0; i < showCount; i++) {
|
|
505
|
+
const f = sorted[i];
|
|
506
|
+
const isLast = i === showCount - 1 && sorted.length <= MAX_FINDINGS;
|
|
507
|
+
const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
|
|
508
|
+
const severityColor = getSeverityColor(f.severity);
|
|
509
|
+
const severityBadge = severityColor(`[${f.severity.toUpperCase()}]`);
|
|
510
|
+
lines.push(`${prefix}${severityBadge} ${f.package}@${f.version}`);
|
|
511
|
+
lines.push(import_chalk.default.dim(`${isLast ? " " : " \u2502 "}${f.message}`));
|
|
512
|
+
}
|
|
513
|
+
if (sorted.length > MAX_FINDINGS) {
|
|
514
|
+
const remaining = sorted.length - MAX_FINDINGS;
|
|
515
|
+
lines.push(import_chalk.default.dim(` \u2514\u2500\u2500 ... and ${remaining} more ${heading.toLowerCase()}`));
|
|
516
|
+
lines.push(import_chalk.default.dim(" Run with --format json for full details"));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function formatSecretFindings(findings, lines) {
|
|
520
|
+
const MAX_FINDINGS_PER_FILE = 5;
|
|
521
|
+
const MAX_FILES = 10;
|
|
522
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const f of findings) {
|
|
524
|
+
const existing = byFile.get(f.file) || [];
|
|
525
|
+
existing.push(f);
|
|
526
|
+
byFile.set(f.file, existing);
|
|
527
|
+
}
|
|
528
|
+
const sortedFiles = [...byFile.entries()].sort((a, b) => {
|
|
529
|
+
const aSeverity = Math.min(...a[1].map((f) => severityRank(f.severity)));
|
|
530
|
+
const bSeverity = Math.min(...b[1].map((f) => severityRank(f.severity)));
|
|
531
|
+
return aSeverity - bSeverity;
|
|
532
|
+
});
|
|
533
|
+
let filesShown = 0;
|
|
534
|
+
let totalHidden = 0;
|
|
535
|
+
for (const [file, fileFindings] of sortedFiles) {
|
|
536
|
+
if (filesShown >= MAX_FILES) {
|
|
537
|
+
totalHidden += fileFindings.length;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
filesShown++;
|
|
541
|
+
lines.push(import_chalk.default.dim(` ${file}:`));
|
|
542
|
+
const showCount = Math.min(fileFindings.length, MAX_FINDINGS_PER_FILE);
|
|
543
|
+
for (let i = 0; i < showCount; i++) {
|
|
544
|
+
const f = fileFindings[i];
|
|
545
|
+
const isLast = i === showCount - 1 && fileFindings.length <= MAX_FINDINGS_PER_FILE;
|
|
546
|
+
const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
|
|
547
|
+
const severityColor = getSeverityColor(f.severity);
|
|
548
|
+
const severityBadge = severityColor(`[${f.severity.toUpperCase()}]`);
|
|
549
|
+
lines.push(`${prefix}${severityBadge} Line ${f.line}: ${f.description}`);
|
|
550
|
+
lines.push(import_chalk.default.dim(`${isLast ? " " : " \u2502 "}Match: ${f.match}`));
|
|
551
|
+
}
|
|
552
|
+
if (fileFindings.length > MAX_FINDINGS_PER_FILE) {
|
|
553
|
+
const remaining = fileFindings.length - MAX_FINDINGS_PER_FILE;
|
|
554
|
+
lines.push(import_chalk.default.dim(` \u2514\u2500\u2500 ... and ${remaining} more finding${remaining > 1 ? "s" : ""} in this file`));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (totalHidden > 0) {
|
|
558
|
+
const hiddenFiles = sortedFiles.length - MAX_FILES;
|
|
559
|
+
lines.push("");
|
|
560
|
+
lines.push(import_chalk.default.dim(` ... and ${totalHidden} more finding${totalHidden > 1 ? "s" : ""} in ${hiddenFiles} more file${hiddenFiles > 1 ? "s" : ""}`));
|
|
561
|
+
lines.push(import_chalk.default.dim(" Run with --format json for full details"));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function severityRank(severity) {
|
|
565
|
+
switch (severity) {
|
|
566
|
+
case "critical":
|
|
567
|
+
return 0;
|
|
568
|
+
case "high":
|
|
569
|
+
return 1;
|
|
570
|
+
case "medium":
|
|
571
|
+
return 2;
|
|
572
|
+
case "low":
|
|
573
|
+
return 3;
|
|
574
|
+
default:
|
|
575
|
+
return 4;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function getSeverityColor(severity) {
|
|
579
|
+
switch (severity) {
|
|
580
|
+
case "critical":
|
|
581
|
+
return import_chalk.default.red.bold;
|
|
582
|
+
case "high":
|
|
583
|
+
return import_chalk.default.red;
|
|
584
|
+
case "medium":
|
|
585
|
+
return import_chalk.default.yellow;
|
|
586
|
+
case "low":
|
|
587
|
+
return import_chalk.default.dim;
|
|
588
|
+
default:
|
|
589
|
+
return import_chalk.default.white;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
210
592
|
function getShortReason(r) {
|
|
211
593
|
if (r.dependency.license === "UNKNOWN") return "unable to detect license";
|
|
212
594
|
if (r.verdict === "rejected") return "strong copyleft \u2014 requires source disclosure";
|
|
@@ -249,12 +631,12 @@ function getSourceLabel(source) {
|
|
|
249
631
|
return "\u2014";
|
|
250
632
|
}
|
|
251
633
|
}
|
|
252
|
-
function getProjectName(
|
|
253
|
-
const parts =
|
|
634
|
+
function getProjectName(projectPath) {
|
|
635
|
+
const parts = projectPath.split("/");
|
|
254
636
|
return parts[parts.length - 1] || "project";
|
|
255
637
|
}
|
|
256
|
-
function getLockFileName(
|
|
257
|
-
switch (
|
|
638
|
+
function getLockFileName(projectType) {
|
|
639
|
+
switch (projectType) {
|
|
258
640
|
case "npm":
|
|
259
641
|
return "package-lock.json";
|
|
260
642
|
case "yarn":
|
|
@@ -265,7 +647,7 @@ function getLockFileName(result) {
|
|
|
265
647
|
return "unknown";
|
|
266
648
|
}
|
|
267
649
|
}
|
|
268
|
-
function getPolicyName(
|
|
650
|
+
function getPolicyName() {
|
|
269
651
|
return "commercial-safe";
|
|
270
652
|
}
|
|
271
653
|
|
|
@@ -287,6 +669,9 @@ function formatJsonList(result) {
|
|
|
287
669
|
}));
|
|
288
670
|
return JSON.stringify({ totalPackages: result.totalPackages, dependencies: deps }, null, 2);
|
|
289
671
|
}
|
|
672
|
+
function formatJsonUnified(result) {
|
|
673
|
+
return JSON.stringify(result, null, 2);
|
|
674
|
+
}
|
|
290
675
|
|
|
291
676
|
// src/output/csv.ts
|
|
292
677
|
function formatCsvScan(result) {
|
|
@@ -310,6 +695,72 @@ function formatCsvScan(result) {
|
|
|
310
695
|
function formatCsvList(result) {
|
|
311
696
|
return formatCsvScan(result);
|
|
312
697
|
}
|
|
698
|
+
function formatCsvUnified(result) {
|
|
699
|
+
const sections = [];
|
|
700
|
+
if (!result.modules.license && !result.modules.security && !result.modules.secrets) {
|
|
701
|
+
return "# No modules enabled \u2014 scan skipped";
|
|
702
|
+
}
|
|
703
|
+
if (result.modules.license) {
|
|
704
|
+
const lines = [];
|
|
705
|
+
lines.push("# License Compliance");
|
|
706
|
+
lines.push("Module,Package,Version,License,Source,Verdict,Reason,IsDirect,IsDevDependency");
|
|
707
|
+
const allResults = [
|
|
708
|
+
...result.modules.license.allowed,
|
|
709
|
+
...result.modules.license.warned,
|
|
710
|
+
...result.modules.license.rejected,
|
|
711
|
+
...result.modules.license.unknown
|
|
712
|
+
];
|
|
713
|
+
for (const r of allResults) {
|
|
714
|
+
lines.push([
|
|
715
|
+
"license",
|
|
716
|
+
escapeCsv(r.dependency.name),
|
|
717
|
+
escapeCsv(r.dependency.version),
|
|
718
|
+
escapeCsv(r.dependency.license),
|
|
719
|
+
escapeCsv(r.dependency.licenseSource),
|
|
720
|
+
escapeCsv(r.verdict),
|
|
721
|
+
escapeCsv(r.reason),
|
|
722
|
+
String(r.dependency.isDirect),
|
|
723
|
+
String(r.dependency.isDevDependency)
|
|
724
|
+
].join(","));
|
|
725
|
+
}
|
|
726
|
+
sections.push(lines.join("\n"));
|
|
727
|
+
}
|
|
728
|
+
if (result.modules.security) {
|
|
729
|
+
const lines = [];
|
|
730
|
+
lines.push("# Security Findings");
|
|
731
|
+
lines.push("Module,Package,Version,Type,Severity,Message");
|
|
732
|
+
for (const f of result.modules.security.findings) {
|
|
733
|
+
lines.push([
|
|
734
|
+
"security",
|
|
735
|
+
escapeCsv(f.package),
|
|
736
|
+
escapeCsv(f.version),
|
|
737
|
+
escapeCsv(f.type),
|
|
738
|
+
escapeCsv(f.severity),
|
|
739
|
+
escapeCsv(f.message)
|
|
740
|
+
].join(","));
|
|
741
|
+
}
|
|
742
|
+
sections.push(lines.join("\n"));
|
|
743
|
+
}
|
|
744
|
+
if (result.modules.secrets) {
|
|
745
|
+
const lines = [];
|
|
746
|
+
lines.push("# Secret Detection Findings");
|
|
747
|
+
lines.push("Module,File,Line,Column,RuleId,Severity,Description,Match");
|
|
748
|
+
for (const f of result.modules.secrets.findings) {
|
|
749
|
+
lines.push([
|
|
750
|
+
"secrets",
|
|
751
|
+
escapeCsv(f.file),
|
|
752
|
+
String(f.line),
|
|
753
|
+
String(f.column),
|
|
754
|
+
escapeCsv(f.ruleId),
|
|
755
|
+
escapeCsv(f.severity),
|
|
756
|
+
escapeCsv(f.description),
|
|
757
|
+
escapeCsv(f.match)
|
|
758
|
+
].join(","));
|
|
759
|
+
}
|
|
760
|
+
sections.push(lines.join("\n"));
|
|
761
|
+
}
|
|
762
|
+
return sections.join("\n\n");
|
|
763
|
+
}
|
|
313
764
|
function escapeCsv(value) {
|
|
314
765
|
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
315
766
|
return `"${value.replace(/"/g, '""')}"`;
|
|
@@ -319,9 +770,47 @@ function escapeCsv(value) {
|
|
|
319
770
|
|
|
320
771
|
// src/commands/scan.ts
|
|
321
772
|
function registerScanCommand(program2) {
|
|
322
|
-
program2.command("scan").description("Scan the current project for license
|
|
773
|
+
program2.command("scan").description("Scan the current project for license, security, and secret issues").option("--format <type>", "Output format: table, json, csv", "table").option("--strict", "Exit code 1 on warnings too (not just rejections)").option("--dev", "Include devDependencies in scan").option("--project <path>", "Path to project root", process.cwd()).option("--config <path>", "Path to config file").option("--warn-only", "Never exit with error (for postinstall hook usage)").option("--silent", "No output, only exit code").option("--mode <type>", "Resolution mode: auto (online+local fallback), online (registry only), local (offline only)", "auto").option("--no-cache", "Disable license cache (.preship-cache.json)").option("--cache-ttl <seconds>", "Cache TTL in seconds (default: 604800 = 7 days)", parseInt).option("--scan-timeout <ms>", "Total scan timeout in milliseconds (default: 60000, 0 = disabled)", parseInt).option("--license-only", "Run license scan only (legacy mode)").option("--no-license", "Disable license scanning").option("--no-security", "Disable security vulnerability scanning").option("--no-secrets", "Disable secret detection scanning").option("--security-severity <level>", "Security policy level: default, strict, lenient").option("--security-fail-on <severity>", "Minimum severity to fail: critical, high, medium, low").option("--no-outdated", "Disable outdated package checks").option("--no-deprecated", "Disable deprecated package checks").option("--no-unmaintained", "Disable unmaintained package checks").action(async (options) => {
|
|
323
774
|
try {
|
|
324
|
-
|
|
775
|
+
if (options.licenseOnly) {
|
|
776
|
+
const result2 = await scan({
|
|
777
|
+
projectPath: options.project,
|
|
778
|
+
config: {
|
|
779
|
+
scanDevDependencies: options.dev || void 0,
|
|
780
|
+
output: options.format !== "table" ? options.format : void 0,
|
|
781
|
+
mode: options.mode !== "auto" ? options.mode : void 0,
|
|
782
|
+
cache: options.cache !== void 0 ? options.cache : void 0,
|
|
783
|
+
cacheTTL: options.cacheTtl !== void 0 ? options.cacheTtl : void 0,
|
|
784
|
+
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
if (!options.silent) {
|
|
788
|
+
switch (options.format) {
|
|
789
|
+
case "json":
|
|
790
|
+
console.log(formatJsonScan(result2));
|
|
791
|
+
break;
|
|
792
|
+
case "csv":
|
|
793
|
+
console.log(formatCsvScan(result2));
|
|
794
|
+
break;
|
|
795
|
+
default:
|
|
796
|
+
console.log(formatTerminalScan(result2, options.warnOnly));
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (options.warnOnly) {
|
|
801
|
+
process.exit(0);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (result2.rejected.length > 0) {
|
|
805
|
+
process.exit(1);
|
|
806
|
+
} else if (options.strict && result2.warned.length > 0) {
|
|
807
|
+
process.exit(1);
|
|
808
|
+
} else {
|
|
809
|
+
process.exit(0);
|
|
810
|
+
}
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const result = await unifiedScan({
|
|
325
814
|
projectPath: options.project,
|
|
326
815
|
config: {
|
|
327
816
|
scanDevDependencies: options.dev || void 0,
|
|
@@ -329,19 +818,33 @@ function registerScanCommand(program2) {
|
|
|
329
818
|
mode: options.mode !== "auto" ? options.mode : void 0,
|
|
330
819
|
cache: options.cache !== void 0 ? options.cache : void 0,
|
|
331
820
|
cacheTTL: options.cacheTtl !== void 0 ? options.cacheTtl : void 0,
|
|
332
|
-
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0
|
|
821
|
+
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0,
|
|
822
|
+
// Module toggles from CLI flags
|
|
823
|
+
modules: {
|
|
824
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
825
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
826
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
827
|
+
},
|
|
828
|
+
// Security config from CLI flags
|
|
829
|
+
security: {
|
|
830
|
+
severity: options.securitySeverity || void 0,
|
|
831
|
+
failOn: options.securityFailOn || void 0,
|
|
832
|
+
checkOutdated: options.outdated !== void 0 ? options.outdated : void 0,
|
|
833
|
+
checkDeprecated: options.deprecated !== void 0 ? options.deprecated : void 0,
|
|
834
|
+
checkUnmaintained: options.unmaintained !== void 0 ? options.unmaintained : void 0
|
|
835
|
+
}
|
|
333
836
|
}
|
|
334
837
|
});
|
|
335
838
|
if (!options.silent) {
|
|
336
839
|
switch (options.format) {
|
|
337
840
|
case "json":
|
|
338
|
-
console.log(
|
|
841
|
+
console.log(formatJsonUnified(result));
|
|
339
842
|
break;
|
|
340
843
|
case "csv":
|
|
341
|
-
console.log(
|
|
844
|
+
console.log(formatCsvUnified(result));
|
|
342
845
|
break;
|
|
343
846
|
default:
|
|
344
|
-
console.log(
|
|
847
|
+
console.log(formatTerminalUnified(result, options.warnOnly));
|
|
345
848
|
break;
|
|
346
849
|
}
|
|
347
850
|
}
|
|
@@ -349,10 +852,17 @@ function registerScanCommand(program2) {
|
|
|
349
852
|
process.exit(0);
|
|
350
853
|
return;
|
|
351
854
|
}
|
|
352
|
-
if (result.
|
|
353
|
-
process.exit(1);
|
|
354
|
-
} else if (options.strict && result.warned.length > 0) {
|
|
855
|
+
if (!result.passed) {
|
|
355
856
|
process.exit(1);
|
|
857
|
+
} else if (options.strict) {
|
|
858
|
+
const hasLicenseWarnings = (result.modules.license?.warned?.length ?? 0) > 0;
|
|
859
|
+
const hasSecurityFindings = (result.modules.security?.findings?.length ?? 0) > 0;
|
|
860
|
+
const hasSecretFindings = (result.modules.secrets?.findings?.length ?? 0) > 0;
|
|
861
|
+
if (hasLicenseWarnings || hasSecurityFindings || hasSecretFindings) {
|
|
862
|
+
process.exit(1);
|
|
863
|
+
} else {
|
|
864
|
+
process.exit(0);
|
|
865
|
+
}
|
|
356
866
|
} else {
|
|
357
867
|
process.exit(0);
|
|
358
868
|
}
|
|
@@ -370,7 +880,7 @@ function registerScanCommand(program2) {
|
|
|
370
880
|
var fs = __toESM(require("fs"));
|
|
371
881
|
var path2 = __toESM(require("path"));
|
|
372
882
|
function registerInitCommand(program2) {
|
|
373
|
-
program2.command("init").description("Set up PreShip in the current project").option("--policy <name>", "Policy template: commercial-safe, strict, permissive-only", "commercial-safe").option("--skip-hooks", "Don't add npm lifecycle hooks to package.json").action((options) => {
|
|
883
|
+
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) => {
|
|
374
884
|
try {
|
|
375
885
|
const projectPath = process.cwd();
|
|
376
886
|
const configContent = generateConfigContent(options.policy);
|
|
@@ -407,7 +917,7 @@ function registerInitCommand(program2) {
|
|
|
407
917
|
});
|
|
408
918
|
}
|
|
409
919
|
function generateConfigContent(policy) {
|
|
410
|
-
return `# PreShip
|
|
920
|
+
return `# PreShip Configuration
|
|
411
921
|
# Docs: https://github.com/dipen-code/preship
|
|
412
922
|
#
|
|
413
923
|
# Policy: ${policy}
|
|
@@ -415,6 +925,14 @@ ${getPolicyDescription(policy)}
|
|
|
415
925
|
|
|
416
926
|
policy: ${policy}
|
|
417
927
|
|
|
928
|
+
# ===== Module Toggles =====
|
|
929
|
+
# Enable or disable individual scan modules (all enabled by default)
|
|
930
|
+
# modules:
|
|
931
|
+
# license: true
|
|
932
|
+
# security: true
|
|
933
|
+
# secrets: true
|
|
934
|
+
|
|
935
|
+
# ===== License Compliance =====
|
|
418
936
|
# Scan devDependencies too? (default: false)
|
|
419
937
|
# scanDevDependencies: false
|
|
420
938
|
|
|
@@ -435,12 +953,32 @@ policy: ${policy}
|
|
|
435
953
|
# reason: "Used internally only, not distributed"
|
|
436
954
|
# approvedBy: your-name
|
|
437
955
|
# date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
956
|
+
|
|
957
|
+
# ===== Security Scanning =====
|
|
958
|
+
# security:
|
|
959
|
+
# severity: default # Policy level: default, strict, lenient
|
|
960
|
+
# failOn: high # Minimum severity to fail: critical, high, medium, low
|
|
961
|
+
# checkOutdated: true # Flag outdated packages
|
|
962
|
+
# checkDeprecated: true # Flag deprecated packages
|
|
963
|
+
# checkUnmaintained: true # Flag unmaintained packages
|
|
964
|
+
# outdatedMajorThreshold: 5 # Major versions behind to flag (direct deps only)
|
|
965
|
+
# unmaintainedYears: 4 # Years without publish to flag
|
|
966
|
+
|
|
967
|
+
# ===== Secret Detection =====
|
|
968
|
+
# secrets:
|
|
969
|
+
# scanPaths: [] # Paths to scan (default: entire project)
|
|
970
|
+
# allowPaths: [] # Paths to skip (e.g., test fixtures)
|
|
971
|
+
# allowRules: [] # Disable specific rules (e.g., generic-password)
|
|
438
972
|
`;
|
|
439
973
|
}
|
|
440
974
|
function getPolicyDescription(policy) {
|
|
441
975
|
switch (policy) {
|
|
442
976
|
case "commercial-safe":
|
|
443
977
|
return "# Rejects strong copyleft licenses (GPL, AGPL, EUPL)\n# Warns on weak copyleft (LGPL, MPL)\n# Allows permissive licenses (MIT, Apache-2.0, BSD, ISC)";
|
|
978
|
+
case "saas-safe":
|
|
979
|
+
return "# Safe for SaaS applications\n# Rejects strong copyleft and network-trigger licenses (AGPL, SSPL)\n# Warns on weak copyleft (LGPL, MPL)";
|
|
980
|
+
case "distribution-safe":
|
|
981
|
+
return "# Safe for distributed software (binaries, desktop apps)\n# Rejects ALL copyleft licenses that pose risk when distributing\n# Only allows permissive + MPL-2.0 (file-level copyleft)";
|
|
444
982
|
case "strict":
|
|
445
983
|
return "# Rejects all copyleft licenses including weak copyleft\n# Only allows permissive licenses";
|
|
446
984
|
case "permissive-only":
|
|
@@ -452,31 +990,64 @@ function getPolicyDescription(policy) {
|
|
|
452
990
|
|
|
453
991
|
// src/commands/list.ts
|
|
454
992
|
function registerListCommand(program2) {
|
|
455
|
-
program2.command("list").description("List all dependencies with their licenses").option("--format <type>", "Output format: table, json, csv", "table").option("--dev", "Include devDependencies").option("--filter <license>", "Filter by license (e.g., --filter GPL-3.0)").option("--project <path>", "Path to project root", process.cwd()).action(async (options) => {
|
|
993
|
+
program2.command("list").description("List all dependencies with their licenses, vulnerabilities, and secrets").option("--format <type>", "Output format: table, json, csv", "table").option("--dev", "Include devDependencies").option("--filter <license>", "Filter by license (e.g., --filter GPL-3.0)").option("--project <path>", "Path to project root", process.cwd()).option("--license-only", "Show license information only (legacy mode)").option("--no-license", "Disable license module").option("--no-security", "Disable security module").option("--no-secrets", "Disable secrets module").action(async (options) => {
|
|
456
994
|
try {
|
|
457
|
-
|
|
995
|
+
if (options.licenseOnly) {
|
|
996
|
+
const result2 = await scan({
|
|
997
|
+
projectPath: options.project,
|
|
998
|
+
config: {
|
|
999
|
+
scanDevDependencies: options.dev || void 0
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
if (options.filter) {
|
|
1003
|
+
const filter = options.filter.toUpperCase();
|
|
1004
|
+
result2.allowed = result2.allowed.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1005
|
+
result2.warned = result2.warned.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1006
|
+
result2.rejected = result2.rejected.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1007
|
+
result2.unknown = result2.unknown.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1008
|
+
result2.totalPackages = result2.allowed.length + result2.warned.length + result2.rejected.length + result2.unknown.length;
|
|
1009
|
+
}
|
|
1010
|
+
switch (options.format) {
|
|
1011
|
+
case "json":
|
|
1012
|
+
console.log(formatJsonList(result2));
|
|
1013
|
+
break;
|
|
1014
|
+
case "csv":
|
|
1015
|
+
console.log(formatCsvList(result2));
|
|
1016
|
+
break;
|
|
1017
|
+
default:
|
|
1018
|
+
console.log(formatTerminalList(result2));
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const result = await unifiedScan({
|
|
458
1024
|
projectPath: options.project,
|
|
459
1025
|
config: {
|
|
460
|
-
scanDevDependencies: options.dev || void 0
|
|
1026
|
+
scanDevDependencies: options.dev || void 0,
|
|
1027
|
+
modules: {
|
|
1028
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
1029
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
1030
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
1031
|
+
}
|
|
461
1032
|
}
|
|
462
1033
|
});
|
|
463
|
-
if (options.filter) {
|
|
1034
|
+
if (options.filter && result.modules.license) {
|
|
464
1035
|
const filter = options.filter.toUpperCase();
|
|
465
|
-
result.allowed = result.allowed.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
466
|
-
result.warned = result.warned.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
467
|
-
result.rejected = result.rejected.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
468
|
-
result.unknown = result.unknown.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
469
|
-
result.totalPackages = result.allowed.length + result.warned.length + result.rejected.length + result.unknown.length;
|
|
1036
|
+
result.modules.license.allowed = result.modules.license.allowed.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1037
|
+
result.modules.license.warned = result.modules.license.warned.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1038
|
+
result.modules.license.rejected = result.modules.license.rejected.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1039
|
+
result.modules.license.unknown = result.modules.license.unknown.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
1040
|
+
result.modules.license.totalPackages = result.modules.license.allowed.length + result.modules.license.warned.length + result.modules.license.rejected.length + result.modules.license.unknown.length;
|
|
470
1041
|
}
|
|
471
1042
|
switch (options.format) {
|
|
472
1043
|
case "json":
|
|
473
|
-
console.log(
|
|
1044
|
+
console.log(formatJsonUnified(result));
|
|
474
1045
|
break;
|
|
475
1046
|
case "csv":
|
|
476
|
-
console.log(
|
|
1047
|
+
console.log(formatCsvUnified(result));
|
|
477
1048
|
break;
|
|
478
1049
|
default:
|
|
479
|
-
console.log(
|
|
1050
|
+
console.log(formatTerminalUnified(result));
|
|
480
1051
|
break;
|
|
481
1052
|
}
|
|
482
1053
|
} catch (error) {
|
|
@@ -491,40 +1062,73 @@ function registerListCommand(program2) {
|
|
|
491
1062
|
var fs2 = __toESM(require("fs"));
|
|
492
1063
|
var path3 = __toESM(require("path"));
|
|
493
1064
|
function registerReportCommand(program2) {
|
|
494
|
-
program2.command("report").description("Generate a license attribution/NOTICE file").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()).action(async (options) => {
|
|
1065
|
+
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) => {
|
|
495
1066
|
try {
|
|
496
|
-
|
|
1067
|
+
if (options.licenseOnly) {
|
|
1068
|
+
const result2 = await scan({
|
|
1069
|
+
projectPath: options.project,
|
|
1070
|
+
config: { scanDevDependencies: true }
|
|
1071
|
+
});
|
|
1072
|
+
const allResults = [...result2.allowed, ...result2.warned, ...result2.rejected, ...result2.unknown];
|
|
1073
|
+
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
1074
|
+
let content2;
|
|
1075
|
+
switch (options.format) {
|
|
1076
|
+
case "json":
|
|
1077
|
+
content2 = JSON.stringify(
|
|
1078
|
+
allResults.map((r) => ({
|
|
1079
|
+
name: r.dependency.name,
|
|
1080
|
+
version: r.dependency.version,
|
|
1081
|
+
license: r.dependency.license,
|
|
1082
|
+
source: r.dependency.licenseSource
|
|
1083
|
+
})),
|
|
1084
|
+
null,
|
|
1085
|
+
2
|
|
1086
|
+
);
|
|
1087
|
+
break;
|
|
1088
|
+
case "csv":
|
|
1089
|
+
content2 = "Package,Version,License\n" + allResults.map(
|
|
1090
|
+
(r) => `${r.dependency.name},${r.dependency.version},${r.dependency.license}`
|
|
1091
|
+
).join("\n");
|
|
1092
|
+
break;
|
|
1093
|
+
default:
|
|
1094
|
+
content2 = generateNoticeText(allResults, result2.projectPath);
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
const outPath2 = path3.resolve(options.out);
|
|
1098
|
+
fs2.writeFileSync(outPath2, content2, "utf-8");
|
|
1099
|
+
console.log(`\u2705 Generated ${options.out} with ${allResults.length} package attributions`);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const result = await unifiedScan({
|
|
497
1103
|
projectPath: options.project,
|
|
498
|
-
config: {
|
|
1104
|
+
config: {
|
|
1105
|
+
scanDevDependencies: true,
|
|
1106
|
+
modules: {
|
|
1107
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
1108
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
1109
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
499
1112
|
});
|
|
500
|
-
const allResults = [...result.allowed, ...result.warned, ...result.rejected, ...result.unknown];
|
|
501
|
-
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
502
1113
|
let content;
|
|
503
1114
|
switch (options.format) {
|
|
504
1115
|
case "json":
|
|
505
|
-
content = JSON.stringify(
|
|
506
|
-
allResults.map((r) => ({
|
|
507
|
-
name: r.dependency.name,
|
|
508
|
-
version: r.dependency.version,
|
|
509
|
-
license: r.dependency.license,
|
|
510
|
-
source: r.dependency.licenseSource
|
|
511
|
-
})),
|
|
512
|
-
null,
|
|
513
|
-
2
|
|
514
|
-
);
|
|
1116
|
+
content = JSON.stringify(result, null, 2);
|
|
515
1117
|
break;
|
|
516
1118
|
case "csv":
|
|
517
|
-
content =
|
|
518
|
-
(r) => `${r.dependency.name},${r.dependency.version},${r.dependency.license}`
|
|
519
|
-
).join("\n");
|
|
1119
|
+
content = generateUnifiedCsvReport(result);
|
|
520
1120
|
break;
|
|
521
1121
|
default:
|
|
522
|
-
content =
|
|
1122
|
+
content = generateUnifiedTextReport(result);
|
|
523
1123
|
break;
|
|
524
1124
|
}
|
|
525
|
-
const outPath = path3.resolve(options.out);
|
|
1125
|
+
const outPath = path3.resolve(options.out === "NOTICE.txt" ? "preship-report.txt" : options.out);
|
|
526
1126
|
fs2.writeFileSync(outPath, content, "utf-8");
|
|
527
|
-
|
|
1127
|
+
const modulesRan = [];
|
|
1128
|
+
if (result.modules.license) modulesRan.push("license");
|
|
1129
|
+
if (result.modules.security) modulesRan.push("security");
|
|
1130
|
+
if (result.modules.secrets) modulesRan.push("secrets");
|
|
1131
|
+
console.log(`\u2705 Generated ${path3.basename(outPath)} (modules: ${modulesRan.join(", ")})`);
|
|
528
1132
|
} catch (error) {
|
|
529
1133
|
const message = error instanceof Error ? error.message : String(error);
|
|
530
1134
|
console.error(`\u274C ${message}`);
|
|
@@ -564,6 +1168,113 @@ function generateNoticeText(results, projectPath) {
|
|
|
564
1168
|
lines.push("=".repeat(80));
|
|
565
1169
|
return lines.join("\n");
|
|
566
1170
|
}
|
|
1171
|
+
function generateUnifiedTextReport(result) {
|
|
1172
|
+
const lines = [];
|
|
1173
|
+
lines.push("PRESHIP SCAN REPORT");
|
|
1174
|
+
lines.push(`Generated: ${result.timestamp}`);
|
|
1175
|
+
lines.push(`Project: ${result.projectPath}`);
|
|
1176
|
+
lines.push(`Mode: ${result.mode}`);
|
|
1177
|
+
lines.push(`Policy: ${result.policy}`);
|
|
1178
|
+
lines.push(`Overall: ${result.passed ? "PASSED" : "FAILED"}`);
|
|
1179
|
+
lines.push("");
|
|
1180
|
+
if (result.modules.license) {
|
|
1181
|
+
const license = result.modules.license;
|
|
1182
|
+
lines.push("=".repeat(80));
|
|
1183
|
+
lines.push("LICENSE COMPLIANCE");
|
|
1184
|
+
lines.push("=".repeat(80));
|
|
1185
|
+
lines.push(`Total packages: ${license.totalPackages}`);
|
|
1186
|
+
lines.push(`Passed: ${license.passed}`);
|
|
1187
|
+
lines.push(`Allowed: ${license.allowed.length}`);
|
|
1188
|
+
lines.push(`Warned: ${license.warned.length}`);
|
|
1189
|
+
lines.push(`Rejected: ${license.rejected.length}`);
|
|
1190
|
+
lines.push(`Unknown: ${license.unknown.length}`);
|
|
1191
|
+
lines.push("");
|
|
1192
|
+
const allResults = [...license.allowed, ...license.warned, ...license.rejected, ...license.unknown];
|
|
1193
|
+
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
1194
|
+
for (const r of allResults) {
|
|
1195
|
+
lines.push(` ${r.dependency.name}@${r.dependency.version} \u2014 ${r.dependency.license} [${r.verdict}]`);
|
|
1196
|
+
}
|
|
1197
|
+
lines.push("");
|
|
1198
|
+
}
|
|
1199
|
+
if (result.modules.security) {
|
|
1200
|
+
const security = result.modules.security;
|
|
1201
|
+
lines.push("=".repeat(80));
|
|
1202
|
+
lines.push("SECURITY VULNERABILITIES");
|
|
1203
|
+
lines.push("=".repeat(80));
|
|
1204
|
+
lines.push(`Total packages scanned: ${security.totalPackages}`);
|
|
1205
|
+
lines.push(`Passed: ${security.passed}`);
|
|
1206
|
+
lines.push(`Findings: ${security.findings.length}`);
|
|
1207
|
+
lines.push("");
|
|
1208
|
+
if (security.findings.length > 0) {
|
|
1209
|
+
for (const f of security.findings) {
|
|
1210
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.package}@${f.version} \u2014 ${f.type}: ${f.message}`);
|
|
1211
|
+
}
|
|
1212
|
+
lines.push("");
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (result.modules.secrets) {
|
|
1216
|
+
const secrets = result.modules.secrets;
|
|
1217
|
+
lines.push("=".repeat(80));
|
|
1218
|
+
lines.push("SECRET DETECTION");
|
|
1219
|
+
lines.push("=".repeat(80));
|
|
1220
|
+
lines.push(`Files scanned: ${secrets.filesScanned}`);
|
|
1221
|
+
lines.push(`Passed: ${secrets.passed}`);
|
|
1222
|
+
lines.push(`Findings: ${secrets.findings.length}`);
|
|
1223
|
+
lines.push("");
|
|
1224
|
+
if (secrets.findings.length > 0) {
|
|
1225
|
+
for (const f of secrets.findings) {
|
|
1226
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.file}:${f.line} \u2014 ${f.description} (${f.match})`);
|
|
1227
|
+
}
|
|
1228
|
+
lines.push("");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
lines.push("=".repeat(80));
|
|
1232
|
+
lines.push(`Scan duration: ${(result.totalScanDurationMs / 1e3).toFixed(1)}s`);
|
|
1233
|
+
return lines.join("\n");
|
|
1234
|
+
}
|
|
1235
|
+
function generateUnifiedCsvReport(result) {
|
|
1236
|
+
const sections = [];
|
|
1237
|
+
if (result.modules.license) {
|
|
1238
|
+
const lines = [];
|
|
1239
|
+
lines.push("# License Compliance");
|
|
1240
|
+
lines.push("Module,Package,Version,License,Verdict,Reason");
|
|
1241
|
+
const allResults = [
|
|
1242
|
+
...result.modules.license.allowed,
|
|
1243
|
+
...result.modules.license.warned,
|
|
1244
|
+
...result.modules.license.rejected,
|
|
1245
|
+
...result.modules.license.unknown
|
|
1246
|
+
];
|
|
1247
|
+
for (const r of allResults) {
|
|
1248
|
+
lines.push(`license,${escapeCsv2(r.dependency.name)},${escapeCsv2(r.dependency.version)},${escapeCsv2(r.dependency.license)},${r.verdict},${escapeCsv2(r.reason)}`);
|
|
1249
|
+
}
|
|
1250
|
+
sections.push(lines.join("\n"));
|
|
1251
|
+
}
|
|
1252
|
+
if (result.modules.security) {
|
|
1253
|
+
const lines = [];
|
|
1254
|
+
lines.push("# Security Findings");
|
|
1255
|
+
lines.push("Module,Package,Version,Type,Severity,Message");
|
|
1256
|
+
for (const f of result.modules.security.findings) {
|
|
1257
|
+
lines.push(`security,${escapeCsv2(f.package)},${escapeCsv2(f.version)},${f.type},${f.severity},${escapeCsv2(f.message)}`);
|
|
1258
|
+
}
|
|
1259
|
+
sections.push(lines.join("\n"));
|
|
1260
|
+
}
|
|
1261
|
+
if (result.modules.secrets) {
|
|
1262
|
+
const lines = [];
|
|
1263
|
+
lines.push("# Secret Detection Findings");
|
|
1264
|
+
lines.push("Module,File,Line,RuleId,Severity,Description");
|
|
1265
|
+
for (const f of result.modules.secrets.findings) {
|
|
1266
|
+
lines.push(`secrets,${escapeCsv2(f.file)},${f.line},${f.ruleId},${f.severity},${escapeCsv2(f.description)}`);
|
|
1267
|
+
}
|
|
1268
|
+
sections.push(lines.join("\n"));
|
|
1269
|
+
}
|
|
1270
|
+
return sections.join("\n\n");
|
|
1271
|
+
}
|
|
1272
|
+
function escapeCsv2(value) {
|
|
1273
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
1274
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
1275
|
+
}
|
|
1276
|
+
return value;
|
|
1277
|
+
}
|
|
567
1278
|
|
|
568
1279
|
// src/commands/allow.ts
|
|
569
1280
|
var fs3 = __toESM(require("fs"));
|
|
@@ -3203,7 +3914,7 @@ function registerAllowCommand(program2) {
|
|
|
3203
3914
|
|
|
3204
3915
|
// src/cli.ts
|
|
3205
3916
|
var program = new import_commander.Command();
|
|
3206
|
-
program.name("preship").description("
|
|
3917
|
+
program.name("preship").description("Pre-ship verification: license compliance, security scanning, and secret detection \u2014 all before you ship.").version("2.0.2");
|
|
3207
3918
|
registerScanCommand(program);
|
|
3208
3919
|
registerInitCommand(program);
|
|
3209
3920
|
registerListCommand(program);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { PreshipConfig, ScanResult } from '@preship/core';
|
|
2
|
-
export { Dependency, DetectedProject, ExceptionEntry, LicenseSource, ParsedDependency, PolicyResult, PolicyTemplate, PolicyVerdict, PreshipConfig, ProjectType, ResolverMode, ScanResult } from '@preship/core';
|
|
1
|
+
import { PreshipConfig, ScanResult, UnifiedScanResult } from '@preship/core';
|
|
2
|
+
export { Dependency, DetectedProject, ExceptionEntry, LicenseSource, ModuleConfig, PackageHealth, ParsedDependency, PolicyResult, PolicyTemplate, PolicyVerdict, PreshipConfig, ProjectType, ResolverMode, ScanResult, SecretFinding, SecretRule, SecretSeverity, SecretsConfig, SecretsResult, SecurityConfig, SecurityFinding, SecurityPolicyLevel, SecurityResult, SecuritySeverity, SecurityVulnerability, UnifiedScanResult } from '@preship/core';
|
|
3
3
|
|
|
4
4
|
interface ScanOptions {
|
|
5
5
|
projectPath?: string;
|
|
6
6
|
config?: Partial<PreshipConfig>;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Legacy scan function — license-only scan for backwards compatibility.
|
|
10
|
+
* Returns ScanResult (license module output only).
|
|
11
|
+
*/
|
|
8
12
|
declare function scan(options?: ScanOptions): Promise<ScanResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Unified scan — runs all enabled modules (license, security, secrets).
|
|
15
|
+
* Returns UnifiedScanResult with combined results from all modules.
|
|
16
|
+
*/
|
|
17
|
+
declare function unifiedScan(options?: ScanOptions): Promise<UnifiedScanResult>;
|
|
9
18
|
|
|
10
|
-
export { type ScanOptions, scan };
|
|
19
|
+
export { type ScanOptions, scan, unifiedScan };
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
scan: () => scan
|
|
33
|
+
scan: () => scan,
|
|
34
|
+
unifiedScan: () => unifiedScan
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(index_exports);
|
|
36
37
|
|
|
@@ -38,6 +39,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
38
39
|
var path = __toESM(require("path"));
|
|
39
40
|
var import_core = require("@preship/core");
|
|
40
41
|
var import_license = require("@preship/license");
|
|
42
|
+
var import_security = require("@preship/security");
|
|
43
|
+
var import_secrets = require("@preship/secrets");
|
|
41
44
|
async function scan(options) {
|
|
42
45
|
const startTime = Date.now();
|
|
43
46
|
const projectPath = path.resolve(options?.projectPath ?? process.cwd());
|
|
@@ -93,7 +96,123 @@ async function scan(options) {
|
|
|
93
96
|
scanDurationMs
|
|
94
97
|
};
|
|
95
98
|
}
|
|
99
|
+
async function unifiedScan(options) {
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
const projectPath = path.resolve(options?.projectPath ?? process.cwd());
|
|
102
|
+
let config = (0, import_core.loadConfig)(projectPath);
|
|
103
|
+
if (options?.config) {
|
|
104
|
+
config = {
|
|
105
|
+
...config,
|
|
106
|
+
...options.config,
|
|
107
|
+
modules: { ...config.modules, ...options.config.modules },
|
|
108
|
+
security: { ...config.security, ...options.config.security },
|
|
109
|
+
secrets: { ...config.secrets, ...options.config.secrets }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const modules = {
|
|
113
|
+
license: config.modules?.license ?? true,
|
|
114
|
+
security: config.modules?.security ?? true,
|
|
115
|
+
secrets: config.modules?.secrets ?? true
|
|
116
|
+
};
|
|
117
|
+
if (!modules.license && !modules.security && !modules.secrets) {
|
|
118
|
+
console.warn("\u26A0\uFE0F No scan modules enabled. Use --no-license, --no-security, --no-secrets to selectively disable.");
|
|
119
|
+
}
|
|
120
|
+
const mode = config.mode ?? "auto";
|
|
121
|
+
const policy = config.policy ?? "commercial-safe";
|
|
122
|
+
const projects = (0, import_core.detectProjects)(projectPath);
|
|
123
|
+
const project = projects[0];
|
|
124
|
+
const packageJsonPath = path.join(project.path, "package.json");
|
|
125
|
+
let parsedDeps;
|
|
126
|
+
switch (project.type) {
|
|
127
|
+
case "npm":
|
|
128
|
+
parsedDeps = (0, import_core.parseNpmLockfile)(project.lockFile, packageJsonPath);
|
|
129
|
+
break;
|
|
130
|
+
case "yarn":
|
|
131
|
+
parsedDeps = (0, import_core.parseYarnLockfile)(project.lockFile, packageJsonPath);
|
|
132
|
+
break;
|
|
133
|
+
case "pnpm":
|
|
134
|
+
parsedDeps = (0, import_core.parsePnpmLockfile)(project.lockFile, packageJsonPath);
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unsupported project type: ${project.type}`);
|
|
138
|
+
}
|
|
139
|
+
if (!config.scanDevDependencies) {
|
|
140
|
+
parsedDeps = parsedDeps.filter((dep) => !dep.isDevDependency);
|
|
141
|
+
}
|
|
142
|
+
let licenseResult;
|
|
143
|
+
let securityResult;
|
|
144
|
+
let secretsResult;
|
|
145
|
+
const promises = [];
|
|
146
|
+
if (modules.license) {
|
|
147
|
+
promises.push(
|
|
148
|
+
(async () => {
|
|
149
|
+
const licenseStart = Date.now();
|
|
150
|
+
const dependencies = await (0, import_license.resolveLicenses)(parsedDeps, projectPath, {
|
|
151
|
+
mode,
|
|
152
|
+
networkTimeout: config.networkTimeout ?? 5e3,
|
|
153
|
+
networkConcurrency: config.networkConcurrency ?? 10,
|
|
154
|
+
cache: config.cache ?? true,
|
|
155
|
+
cacheTTL: config.cacheTTL ?? 604800,
|
|
156
|
+
scanTimeout: config.scanTimeout ?? 6e4
|
|
157
|
+
});
|
|
158
|
+
const results = (0, import_license.evaluatePolicy)(dependencies, config);
|
|
159
|
+
const allowed = results.filter((r) => r.verdict === "allowed");
|
|
160
|
+
const warned = results.filter((r) => r.verdict === "warned");
|
|
161
|
+
const rejected = results.filter((r) => r.verdict === "rejected");
|
|
162
|
+
const unknown = results.filter((r) => r.verdict === "unknown");
|
|
163
|
+
licenseResult = {
|
|
164
|
+
projectPath,
|
|
165
|
+
projectType: project.type,
|
|
166
|
+
framework: project.framework,
|
|
167
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
168
|
+
totalPackages: dependencies.length,
|
|
169
|
+
allowed,
|
|
170
|
+
warned,
|
|
171
|
+
rejected,
|
|
172
|
+
unknown,
|
|
173
|
+
passed: rejected.length === 0,
|
|
174
|
+
scanDurationMs: Date.now() - licenseStart
|
|
175
|
+
};
|
|
176
|
+
})()
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (modules.security) {
|
|
180
|
+
promises.push(
|
|
181
|
+
(async () => {
|
|
182
|
+
const securityConfig = (0, import_security.mergeSecurityConfig)(config.security);
|
|
183
|
+
securityResult = await (0, import_security.scanSecurity)(parsedDeps, projectPath, securityConfig, mode);
|
|
184
|
+
})()
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (modules.secrets) {
|
|
188
|
+
promises.push(
|
|
189
|
+
(async () => {
|
|
190
|
+
secretsResult = await (0, import_secrets.scanSecrets)(projectPath, config.secrets);
|
|
191
|
+
})()
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
await Promise.all(promises);
|
|
195
|
+
const allPassed = (licenseResult?.passed ?? true) && (securityResult?.passed ?? true) && (secretsResult?.passed ?? true);
|
|
196
|
+
const totalScanDurationMs = Date.now() - startTime;
|
|
197
|
+
return {
|
|
198
|
+
version: "2.0.0",
|
|
199
|
+
projectPath,
|
|
200
|
+
projectType: project.type,
|
|
201
|
+
framework: project.framework,
|
|
202
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
203
|
+
passed: allPassed,
|
|
204
|
+
mode,
|
|
205
|
+
policy,
|
|
206
|
+
modules: {
|
|
207
|
+
license: licenseResult,
|
|
208
|
+
security: securityResult,
|
|
209
|
+
secrets: secretsResult
|
|
210
|
+
},
|
|
211
|
+
totalScanDurationMs
|
|
212
|
+
};
|
|
213
|
+
}
|
|
96
214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
97
215
|
0 && (module.exports = {
|
|
98
|
-
scan
|
|
216
|
+
scan,
|
|
217
|
+
unifiedScan
|
|
99
218
|
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "preship",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.2",
|
|
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",
|
|
7
7
|
"compliance",
|
|
8
8
|
"scanner",
|
|
9
|
+
"security",
|
|
10
|
+
"vulnerability",
|
|
11
|
+
"secrets",
|
|
12
|
+
"credentials",
|
|
9
13
|
"gpl",
|
|
10
14
|
"agpl",
|
|
11
15
|
"eupl",
|
|
@@ -19,7 +23,9 @@
|
|
|
19
23
|
"fossa-alternative",
|
|
20
24
|
"snyk-alternative",
|
|
21
25
|
"offline",
|
|
22
|
-
"air-gap"
|
|
26
|
+
"air-gap",
|
|
27
|
+
"osv",
|
|
28
|
+
"cve"
|
|
23
29
|
],
|
|
24
30
|
"author": "Cyfox Inc.",
|
|
25
31
|
"license": "Apache-2.0",
|
|
@@ -49,8 +55,10 @@
|
|
|
49
55
|
"lint": "tsc --noEmit"
|
|
50
56
|
},
|
|
51
57
|
"dependencies": {
|
|
52
|
-
"@preship/core": "
|
|
53
|
-
"@preship/license": "
|
|
58
|
+
"@preship/core": "2.0.1",
|
|
59
|
+
"@preship/license": "2.0.0",
|
|
60
|
+
"@preship/security": "1.0.1",
|
|
61
|
+
"@preship/secrets": "1.0.2",
|
|
54
62
|
"chalk": "^4.1.2",
|
|
55
63
|
"cli-table3": "^0.6.5",
|
|
56
64
|
"commander": "^12.1.0"
|