preship 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +715 -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,62 @@ 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
|
+
lines.push(import_chalk.default.dim(` ${heading}:`));
|
|
501
|
+
for (let i = 0; i < findings.length; i++) {
|
|
502
|
+
const f = findings[i];
|
|
503
|
+
const isLast = i === findings.length - 1;
|
|
504
|
+
const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
|
|
505
|
+
const severityColor = getSeverityColor(f.severity);
|
|
506
|
+
const severityBadge = severityColor(`[${f.severity.toUpperCase()}]`);
|
|
507
|
+
lines.push(`${prefix}${severityBadge} ${f.package}@${f.version}`);
|
|
508
|
+
lines.push(import_chalk.default.dim(`${isLast ? " " : " \u2502 "}${f.message}`));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function formatSecretFindings(findings, lines) {
|
|
512
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
513
|
+
for (const f of findings) {
|
|
514
|
+
const existing = byFile.get(f.file) || [];
|
|
515
|
+
existing.push(f);
|
|
516
|
+
byFile.set(f.file, existing);
|
|
517
|
+
}
|
|
518
|
+
for (const [file, fileFindings] of byFile) {
|
|
519
|
+
lines.push(import_chalk.default.dim(` ${file}:`));
|
|
520
|
+
for (let i = 0; i < fileFindings.length; i++) {
|
|
521
|
+
const f = fileFindings[i];
|
|
522
|
+
const isLast = i === fileFindings.length - 1;
|
|
523
|
+
const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
|
|
524
|
+
const severityColor = getSeverityColor(f.severity);
|
|
525
|
+
const severityBadge = severityColor(`[${f.severity.toUpperCase()}]`);
|
|
526
|
+
lines.push(`${prefix}${severityBadge} Line ${f.line}: ${f.description}`);
|
|
527
|
+
lines.push(import_chalk.default.dim(`${isLast ? " " : " \u2502 "}Match: ${f.match}`));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function getSeverityColor(severity) {
|
|
532
|
+
switch (severity) {
|
|
533
|
+
case "critical":
|
|
534
|
+
return import_chalk.default.red.bold;
|
|
535
|
+
case "high":
|
|
536
|
+
return import_chalk.default.red;
|
|
537
|
+
case "medium":
|
|
538
|
+
return import_chalk.default.yellow;
|
|
539
|
+
case "low":
|
|
540
|
+
return import_chalk.default.dim;
|
|
541
|
+
default:
|
|
542
|
+
return import_chalk.default.white;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
210
545
|
function getShortReason(r) {
|
|
211
546
|
if (r.dependency.license === "UNKNOWN") return "unable to detect license";
|
|
212
547
|
if (r.verdict === "rejected") return "strong copyleft \u2014 requires source disclosure";
|
|
@@ -249,12 +584,12 @@ function getSourceLabel(source) {
|
|
|
249
584
|
return "\u2014";
|
|
250
585
|
}
|
|
251
586
|
}
|
|
252
|
-
function getProjectName(
|
|
253
|
-
const parts =
|
|
587
|
+
function getProjectName(projectPath) {
|
|
588
|
+
const parts = projectPath.split("/");
|
|
254
589
|
return parts[parts.length - 1] || "project";
|
|
255
590
|
}
|
|
256
|
-
function getLockFileName(
|
|
257
|
-
switch (
|
|
591
|
+
function getLockFileName(projectType) {
|
|
592
|
+
switch (projectType) {
|
|
258
593
|
case "npm":
|
|
259
594
|
return "package-lock.json";
|
|
260
595
|
case "yarn":
|
|
@@ -265,7 +600,7 @@ function getLockFileName(result) {
|
|
|
265
600
|
return "unknown";
|
|
266
601
|
}
|
|
267
602
|
}
|
|
268
|
-
function getPolicyName(
|
|
603
|
+
function getPolicyName() {
|
|
269
604
|
return "commercial-safe";
|
|
270
605
|
}
|
|
271
606
|
|
|
@@ -287,6 +622,9 @@ function formatJsonList(result) {
|
|
|
287
622
|
}));
|
|
288
623
|
return JSON.stringify({ totalPackages: result.totalPackages, dependencies: deps }, null, 2);
|
|
289
624
|
}
|
|
625
|
+
function formatJsonUnified(result) {
|
|
626
|
+
return JSON.stringify(result, null, 2);
|
|
627
|
+
}
|
|
290
628
|
|
|
291
629
|
// src/output/csv.ts
|
|
292
630
|
function formatCsvScan(result) {
|
|
@@ -310,6 +648,72 @@ function formatCsvScan(result) {
|
|
|
310
648
|
function formatCsvList(result) {
|
|
311
649
|
return formatCsvScan(result);
|
|
312
650
|
}
|
|
651
|
+
function formatCsvUnified(result) {
|
|
652
|
+
const sections = [];
|
|
653
|
+
if (!result.modules.license && !result.modules.security && !result.modules.secrets) {
|
|
654
|
+
return "# No modules enabled \u2014 scan skipped";
|
|
655
|
+
}
|
|
656
|
+
if (result.modules.license) {
|
|
657
|
+
const lines = [];
|
|
658
|
+
lines.push("# License Compliance");
|
|
659
|
+
lines.push("Module,Package,Version,License,Source,Verdict,Reason,IsDirect,IsDevDependency");
|
|
660
|
+
const allResults = [
|
|
661
|
+
...result.modules.license.allowed,
|
|
662
|
+
...result.modules.license.warned,
|
|
663
|
+
...result.modules.license.rejected,
|
|
664
|
+
...result.modules.license.unknown
|
|
665
|
+
];
|
|
666
|
+
for (const r of allResults) {
|
|
667
|
+
lines.push([
|
|
668
|
+
"license",
|
|
669
|
+
escapeCsv(r.dependency.name),
|
|
670
|
+
escapeCsv(r.dependency.version),
|
|
671
|
+
escapeCsv(r.dependency.license),
|
|
672
|
+
escapeCsv(r.dependency.licenseSource),
|
|
673
|
+
escapeCsv(r.verdict),
|
|
674
|
+
escapeCsv(r.reason),
|
|
675
|
+
String(r.dependency.isDirect),
|
|
676
|
+
String(r.dependency.isDevDependency)
|
|
677
|
+
].join(","));
|
|
678
|
+
}
|
|
679
|
+
sections.push(lines.join("\n"));
|
|
680
|
+
}
|
|
681
|
+
if (result.modules.security) {
|
|
682
|
+
const lines = [];
|
|
683
|
+
lines.push("# Security Findings");
|
|
684
|
+
lines.push("Module,Package,Version,Type,Severity,Message");
|
|
685
|
+
for (const f of result.modules.security.findings) {
|
|
686
|
+
lines.push([
|
|
687
|
+
"security",
|
|
688
|
+
escapeCsv(f.package),
|
|
689
|
+
escapeCsv(f.version),
|
|
690
|
+
escapeCsv(f.type),
|
|
691
|
+
escapeCsv(f.severity),
|
|
692
|
+
escapeCsv(f.message)
|
|
693
|
+
].join(","));
|
|
694
|
+
}
|
|
695
|
+
sections.push(lines.join("\n"));
|
|
696
|
+
}
|
|
697
|
+
if (result.modules.secrets) {
|
|
698
|
+
const lines = [];
|
|
699
|
+
lines.push("# Secret Detection Findings");
|
|
700
|
+
lines.push("Module,File,Line,Column,RuleId,Severity,Description,Match");
|
|
701
|
+
for (const f of result.modules.secrets.findings) {
|
|
702
|
+
lines.push([
|
|
703
|
+
"secrets",
|
|
704
|
+
escapeCsv(f.file),
|
|
705
|
+
String(f.line),
|
|
706
|
+
String(f.column),
|
|
707
|
+
escapeCsv(f.ruleId),
|
|
708
|
+
escapeCsv(f.severity),
|
|
709
|
+
escapeCsv(f.description),
|
|
710
|
+
escapeCsv(f.match)
|
|
711
|
+
].join(","));
|
|
712
|
+
}
|
|
713
|
+
sections.push(lines.join("\n"));
|
|
714
|
+
}
|
|
715
|
+
return sections.join("\n\n");
|
|
716
|
+
}
|
|
313
717
|
function escapeCsv(value) {
|
|
314
718
|
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
315
719
|
return `"${value.replace(/"/g, '""')}"`;
|
|
@@ -319,9 +723,47 @@ function escapeCsv(value) {
|
|
|
319
723
|
|
|
320
724
|
// src/commands/scan.ts
|
|
321
725
|
function registerScanCommand(program2) {
|
|
322
|
-
program2.command("scan").description("Scan the current project for license
|
|
726
|
+
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").action(async (options) => {
|
|
323
727
|
try {
|
|
324
|
-
|
|
728
|
+
if (options.licenseOnly) {
|
|
729
|
+
const result2 = await scan({
|
|
730
|
+
projectPath: options.project,
|
|
731
|
+
config: {
|
|
732
|
+
scanDevDependencies: options.dev || void 0,
|
|
733
|
+
output: options.format !== "table" ? options.format : void 0,
|
|
734
|
+
mode: options.mode !== "auto" ? options.mode : void 0,
|
|
735
|
+
cache: options.cache !== void 0 ? options.cache : void 0,
|
|
736
|
+
cacheTTL: options.cacheTtl !== void 0 ? options.cacheTtl : void 0,
|
|
737
|
+
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
if (!options.silent) {
|
|
741
|
+
switch (options.format) {
|
|
742
|
+
case "json":
|
|
743
|
+
console.log(formatJsonScan(result2));
|
|
744
|
+
break;
|
|
745
|
+
case "csv":
|
|
746
|
+
console.log(formatCsvScan(result2));
|
|
747
|
+
break;
|
|
748
|
+
default:
|
|
749
|
+
console.log(formatTerminalScan(result2, options.warnOnly));
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (options.warnOnly) {
|
|
754
|
+
process.exit(0);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (result2.rejected.length > 0) {
|
|
758
|
+
process.exit(1);
|
|
759
|
+
} else if (options.strict && result2.warned.length > 0) {
|
|
760
|
+
process.exit(1);
|
|
761
|
+
} else {
|
|
762
|
+
process.exit(0);
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const result = await unifiedScan({
|
|
325
767
|
projectPath: options.project,
|
|
326
768
|
config: {
|
|
327
769
|
scanDevDependencies: options.dev || void 0,
|
|
@@ -329,19 +771,30 @@ function registerScanCommand(program2) {
|
|
|
329
771
|
mode: options.mode !== "auto" ? options.mode : void 0,
|
|
330
772
|
cache: options.cache !== void 0 ? options.cache : void 0,
|
|
331
773
|
cacheTTL: options.cacheTtl !== void 0 ? options.cacheTtl : void 0,
|
|
332
|
-
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0
|
|
774
|
+
scanTimeout: options.scanTimeout !== void 0 ? options.scanTimeout : void 0,
|
|
775
|
+
// Module toggles from CLI flags
|
|
776
|
+
modules: {
|
|
777
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
778
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
779
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
780
|
+
},
|
|
781
|
+
// Security config from CLI flags
|
|
782
|
+
security: {
|
|
783
|
+
severity: options.securitySeverity || void 0,
|
|
784
|
+
failOn: options.securityFailOn || void 0
|
|
785
|
+
}
|
|
333
786
|
}
|
|
334
787
|
});
|
|
335
788
|
if (!options.silent) {
|
|
336
789
|
switch (options.format) {
|
|
337
790
|
case "json":
|
|
338
|
-
console.log(
|
|
791
|
+
console.log(formatJsonUnified(result));
|
|
339
792
|
break;
|
|
340
793
|
case "csv":
|
|
341
|
-
console.log(
|
|
794
|
+
console.log(formatCsvUnified(result));
|
|
342
795
|
break;
|
|
343
796
|
default:
|
|
344
|
-
console.log(
|
|
797
|
+
console.log(formatTerminalUnified(result, options.warnOnly));
|
|
345
798
|
break;
|
|
346
799
|
}
|
|
347
800
|
}
|
|
@@ -349,10 +802,17 @@ function registerScanCommand(program2) {
|
|
|
349
802
|
process.exit(0);
|
|
350
803
|
return;
|
|
351
804
|
}
|
|
352
|
-
if (result.
|
|
353
|
-
process.exit(1);
|
|
354
|
-
} else if (options.strict && result.warned.length > 0) {
|
|
805
|
+
if (!result.passed) {
|
|
355
806
|
process.exit(1);
|
|
807
|
+
} else if (options.strict) {
|
|
808
|
+
const hasLicenseWarnings = (result.modules.license?.warned?.length ?? 0) > 0;
|
|
809
|
+
const hasSecurityFindings = (result.modules.security?.findings?.length ?? 0) > 0;
|
|
810
|
+
const hasSecretFindings = (result.modules.secrets?.findings?.length ?? 0) > 0;
|
|
811
|
+
if (hasLicenseWarnings || hasSecurityFindings || hasSecretFindings) {
|
|
812
|
+
process.exit(1);
|
|
813
|
+
} else {
|
|
814
|
+
process.exit(0);
|
|
815
|
+
}
|
|
356
816
|
} else {
|
|
357
817
|
process.exit(0);
|
|
358
818
|
}
|
|
@@ -370,7 +830,7 @@ function registerScanCommand(program2) {
|
|
|
370
830
|
var fs = __toESM(require("fs"));
|
|
371
831
|
var path2 = __toESM(require("path"));
|
|
372
832
|
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) => {
|
|
833
|
+
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
834
|
try {
|
|
375
835
|
const projectPath = process.cwd();
|
|
376
836
|
const configContent = generateConfigContent(options.policy);
|
|
@@ -407,7 +867,7 @@ function registerInitCommand(program2) {
|
|
|
407
867
|
});
|
|
408
868
|
}
|
|
409
869
|
function generateConfigContent(policy) {
|
|
410
|
-
return `# PreShip
|
|
870
|
+
return `# PreShip Configuration
|
|
411
871
|
# Docs: https://github.com/dipen-code/preship
|
|
412
872
|
#
|
|
413
873
|
# Policy: ${policy}
|
|
@@ -415,6 +875,14 @@ ${getPolicyDescription(policy)}
|
|
|
415
875
|
|
|
416
876
|
policy: ${policy}
|
|
417
877
|
|
|
878
|
+
# ===== Module Toggles =====
|
|
879
|
+
# Enable or disable individual scan modules (all enabled by default)
|
|
880
|
+
# modules:
|
|
881
|
+
# license: true
|
|
882
|
+
# security: true
|
|
883
|
+
# secrets: true
|
|
884
|
+
|
|
885
|
+
# ===== License Compliance =====
|
|
418
886
|
# Scan devDependencies too? (default: false)
|
|
419
887
|
# scanDevDependencies: false
|
|
420
888
|
|
|
@@ -435,12 +903,32 @@ policy: ${policy}
|
|
|
435
903
|
# reason: "Used internally only, not distributed"
|
|
436
904
|
# approvedBy: your-name
|
|
437
905
|
# date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
906
|
+
|
|
907
|
+
# ===== Security Scanning =====
|
|
908
|
+
# security:
|
|
909
|
+
# severity: default # Policy level: default, strict, lenient
|
|
910
|
+
# failOn: high # Minimum severity to fail: critical, high, medium, low
|
|
911
|
+
# checkOutdated: true # Flag outdated packages
|
|
912
|
+
# checkDeprecated: true # Flag deprecated packages
|
|
913
|
+
# checkUnmaintained: true # Flag unmaintained packages
|
|
914
|
+
# outdatedMajorThreshold: 3 # Major versions behind to flag
|
|
915
|
+
# unmaintainedYears: 2 # Years without publish to flag
|
|
916
|
+
|
|
917
|
+
# ===== Secret Detection =====
|
|
918
|
+
# secrets:
|
|
919
|
+
# scanPaths: [] # Paths to scan (default: entire project)
|
|
920
|
+
# allowPaths: [] # Paths to skip (e.g., test fixtures)
|
|
921
|
+
# allowRules: [] # Disable specific rules (e.g., generic-password)
|
|
438
922
|
`;
|
|
439
923
|
}
|
|
440
924
|
function getPolicyDescription(policy) {
|
|
441
925
|
switch (policy) {
|
|
442
926
|
case "commercial-safe":
|
|
443
927
|
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)";
|
|
928
|
+
case "saas-safe":
|
|
929
|
+
return "# Safe for SaaS applications\n# Rejects strong copyleft and network-trigger licenses (AGPL, SSPL)\n# Warns on weak copyleft (LGPL, MPL)";
|
|
930
|
+
case "distribution-safe":
|
|
931
|
+
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
932
|
case "strict":
|
|
445
933
|
return "# Rejects all copyleft licenses including weak copyleft\n# Only allows permissive licenses";
|
|
446
934
|
case "permissive-only":
|
|
@@ -452,31 +940,64 @@ function getPolicyDescription(policy) {
|
|
|
452
940
|
|
|
453
941
|
// src/commands/list.ts
|
|
454
942
|
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) => {
|
|
943
|
+
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
944
|
try {
|
|
457
|
-
|
|
945
|
+
if (options.licenseOnly) {
|
|
946
|
+
const result2 = await scan({
|
|
947
|
+
projectPath: options.project,
|
|
948
|
+
config: {
|
|
949
|
+
scanDevDependencies: options.dev || void 0
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
if (options.filter) {
|
|
953
|
+
const filter = options.filter.toUpperCase();
|
|
954
|
+
result2.allowed = result2.allowed.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
955
|
+
result2.warned = result2.warned.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
956
|
+
result2.rejected = result2.rejected.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
957
|
+
result2.unknown = result2.unknown.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
958
|
+
result2.totalPackages = result2.allowed.length + result2.warned.length + result2.rejected.length + result2.unknown.length;
|
|
959
|
+
}
|
|
960
|
+
switch (options.format) {
|
|
961
|
+
case "json":
|
|
962
|
+
console.log(formatJsonList(result2));
|
|
963
|
+
break;
|
|
964
|
+
case "csv":
|
|
965
|
+
console.log(formatCsvList(result2));
|
|
966
|
+
break;
|
|
967
|
+
default:
|
|
968
|
+
console.log(formatTerminalList(result2));
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const result = await unifiedScan({
|
|
458
974
|
projectPath: options.project,
|
|
459
975
|
config: {
|
|
460
|
-
scanDevDependencies: options.dev || void 0
|
|
976
|
+
scanDevDependencies: options.dev || void 0,
|
|
977
|
+
modules: {
|
|
978
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
979
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
980
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
981
|
+
}
|
|
461
982
|
}
|
|
462
983
|
});
|
|
463
|
-
if (options.filter) {
|
|
984
|
+
if (options.filter && result.modules.license) {
|
|
464
985
|
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;
|
|
986
|
+
result.modules.license.allowed = result.modules.license.allowed.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
987
|
+
result.modules.license.warned = result.modules.license.warned.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
988
|
+
result.modules.license.rejected = result.modules.license.rejected.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
989
|
+
result.modules.license.unknown = result.modules.license.unknown.filter((r) => r.dependency.license.toUpperCase().includes(filter));
|
|
990
|
+
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
991
|
}
|
|
471
992
|
switch (options.format) {
|
|
472
993
|
case "json":
|
|
473
|
-
console.log(
|
|
994
|
+
console.log(formatJsonUnified(result));
|
|
474
995
|
break;
|
|
475
996
|
case "csv":
|
|
476
|
-
console.log(
|
|
997
|
+
console.log(formatCsvUnified(result));
|
|
477
998
|
break;
|
|
478
999
|
default:
|
|
479
|
-
console.log(
|
|
1000
|
+
console.log(formatTerminalUnified(result));
|
|
480
1001
|
break;
|
|
481
1002
|
}
|
|
482
1003
|
} catch (error) {
|
|
@@ -491,40 +1012,73 @@ function registerListCommand(program2) {
|
|
|
491
1012
|
var fs2 = __toESM(require("fs"));
|
|
492
1013
|
var path3 = __toESM(require("path"));
|
|
493
1014
|
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) => {
|
|
1015
|
+
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
1016
|
try {
|
|
496
|
-
|
|
1017
|
+
if (options.licenseOnly) {
|
|
1018
|
+
const result2 = await scan({
|
|
1019
|
+
projectPath: options.project,
|
|
1020
|
+
config: { scanDevDependencies: true }
|
|
1021
|
+
});
|
|
1022
|
+
const allResults = [...result2.allowed, ...result2.warned, ...result2.rejected, ...result2.unknown];
|
|
1023
|
+
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
1024
|
+
let content2;
|
|
1025
|
+
switch (options.format) {
|
|
1026
|
+
case "json":
|
|
1027
|
+
content2 = JSON.stringify(
|
|
1028
|
+
allResults.map((r) => ({
|
|
1029
|
+
name: r.dependency.name,
|
|
1030
|
+
version: r.dependency.version,
|
|
1031
|
+
license: r.dependency.license,
|
|
1032
|
+
source: r.dependency.licenseSource
|
|
1033
|
+
})),
|
|
1034
|
+
null,
|
|
1035
|
+
2
|
|
1036
|
+
);
|
|
1037
|
+
break;
|
|
1038
|
+
case "csv":
|
|
1039
|
+
content2 = "Package,Version,License\n" + allResults.map(
|
|
1040
|
+
(r) => `${r.dependency.name},${r.dependency.version},${r.dependency.license}`
|
|
1041
|
+
).join("\n");
|
|
1042
|
+
break;
|
|
1043
|
+
default:
|
|
1044
|
+
content2 = generateNoticeText(allResults, result2.projectPath);
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
const outPath2 = path3.resolve(options.out);
|
|
1048
|
+
fs2.writeFileSync(outPath2, content2, "utf-8");
|
|
1049
|
+
console.log(`\u2705 Generated ${options.out} with ${allResults.length} package attributions`);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const result = await unifiedScan({
|
|
497
1053
|
projectPath: options.project,
|
|
498
|
-
config: {
|
|
1054
|
+
config: {
|
|
1055
|
+
scanDevDependencies: true,
|
|
1056
|
+
modules: {
|
|
1057
|
+
license: options.license !== void 0 ? options.license : void 0,
|
|
1058
|
+
security: options.security !== void 0 ? options.security : void 0,
|
|
1059
|
+
secrets: options.secrets !== void 0 ? options.secrets : void 0
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
499
1062
|
});
|
|
500
|
-
const allResults = [...result.allowed, ...result.warned, ...result.rejected, ...result.unknown];
|
|
501
|
-
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
502
1063
|
let content;
|
|
503
1064
|
switch (options.format) {
|
|
504
1065
|
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
|
-
);
|
|
1066
|
+
content = JSON.stringify(result, null, 2);
|
|
515
1067
|
break;
|
|
516
1068
|
case "csv":
|
|
517
|
-
content =
|
|
518
|
-
(r) => `${r.dependency.name},${r.dependency.version},${r.dependency.license}`
|
|
519
|
-
).join("\n");
|
|
1069
|
+
content = generateUnifiedCsvReport(result);
|
|
520
1070
|
break;
|
|
521
1071
|
default:
|
|
522
|
-
content =
|
|
1072
|
+
content = generateUnifiedTextReport(result);
|
|
523
1073
|
break;
|
|
524
1074
|
}
|
|
525
|
-
const outPath = path3.resolve(options.out);
|
|
1075
|
+
const outPath = path3.resolve(options.out === "NOTICE.txt" ? "preship-report.txt" : options.out);
|
|
526
1076
|
fs2.writeFileSync(outPath, content, "utf-8");
|
|
527
|
-
|
|
1077
|
+
const modulesRan = [];
|
|
1078
|
+
if (result.modules.license) modulesRan.push("license");
|
|
1079
|
+
if (result.modules.security) modulesRan.push("security");
|
|
1080
|
+
if (result.modules.secrets) modulesRan.push("secrets");
|
|
1081
|
+
console.log(`\u2705 Generated ${path3.basename(outPath)} (modules: ${modulesRan.join(", ")})`);
|
|
528
1082
|
} catch (error) {
|
|
529
1083
|
const message = error instanceof Error ? error.message : String(error);
|
|
530
1084
|
console.error(`\u274C ${message}`);
|
|
@@ -564,6 +1118,113 @@ function generateNoticeText(results, projectPath) {
|
|
|
564
1118
|
lines.push("=".repeat(80));
|
|
565
1119
|
return lines.join("\n");
|
|
566
1120
|
}
|
|
1121
|
+
function generateUnifiedTextReport(result) {
|
|
1122
|
+
const lines = [];
|
|
1123
|
+
lines.push("PRESHIP SCAN REPORT");
|
|
1124
|
+
lines.push(`Generated: ${result.timestamp}`);
|
|
1125
|
+
lines.push(`Project: ${result.projectPath}`);
|
|
1126
|
+
lines.push(`Mode: ${result.mode}`);
|
|
1127
|
+
lines.push(`Policy: ${result.policy}`);
|
|
1128
|
+
lines.push(`Overall: ${result.passed ? "PASSED" : "FAILED"}`);
|
|
1129
|
+
lines.push("");
|
|
1130
|
+
if (result.modules.license) {
|
|
1131
|
+
const license = result.modules.license;
|
|
1132
|
+
lines.push("=".repeat(80));
|
|
1133
|
+
lines.push("LICENSE COMPLIANCE");
|
|
1134
|
+
lines.push("=".repeat(80));
|
|
1135
|
+
lines.push(`Total packages: ${license.totalPackages}`);
|
|
1136
|
+
lines.push(`Passed: ${license.passed}`);
|
|
1137
|
+
lines.push(`Allowed: ${license.allowed.length}`);
|
|
1138
|
+
lines.push(`Warned: ${license.warned.length}`);
|
|
1139
|
+
lines.push(`Rejected: ${license.rejected.length}`);
|
|
1140
|
+
lines.push(`Unknown: ${license.unknown.length}`);
|
|
1141
|
+
lines.push("");
|
|
1142
|
+
const allResults = [...license.allowed, ...license.warned, ...license.rejected, ...license.unknown];
|
|
1143
|
+
allResults.sort((a, b) => a.dependency.name.localeCompare(b.dependency.name));
|
|
1144
|
+
for (const r of allResults) {
|
|
1145
|
+
lines.push(` ${r.dependency.name}@${r.dependency.version} \u2014 ${r.dependency.license} [${r.verdict}]`);
|
|
1146
|
+
}
|
|
1147
|
+
lines.push("");
|
|
1148
|
+
}
|
|
1149
|
+
if (result.modules.security) {
|
|
1150
|
+
const security = result.modules.security;
|
|
1151
|
+
lines.push("=".repeat(80));
|
|
1152
|
+
lines.push("SECURITY VULNERABILITIES");
|
|
1153
|
+
lines.push("=".repeat(80));
|
|
1154
|
+
lines.push(`Total packages scanned: ${security.totalPackages}`);
|
|
1155
|
+
lines.push(`Passed: ${security.passed}`);
|
|
1156
|
+
lines.push(`Findings: ${security.findings.length}`);
|
|
1157
|
+
lines.push("");
|
|
1158
|
+
if (security.findings.length > 0) {
|
|
1159
|
+
for (const f of security.findings) {
|
|
1160
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.package}@${f.version} \u2014 ${f.type}: ${f.message}`);
|
|
1161
|
+
}
|
|
1162
|
+
lines.push("");
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (result.modules.secrets) {
|
|
1166
|
+
const secrets = result.modules.secrets;
|
|
1167
|
+
lines.push("=".repeat(80));
|
|
1168
|
+
lines.push("SECRET DETECTION");
|
|
1169
|
+
lines.push("=".repeat(80));
|
|
1170
|
+
lines.push(`Files scanned: ${secrets.filesScanned}`);
|
|
1171
|
+
lines.push(`Passed: ${secrets.passed}`);
|
|
1172
|
+
lines.push(`Findings: ${secrets.findings.length}`);
|
|
1173
|
+
lines.push("");
|
|
1174
|
+
if (secrets.findings.length > 0) {
|
|
1175
|
+
for (const f of secrets.findings) {
|
|
1176
|
+
lines.push(` [${f.severity.toUpperCase()}] ${f.file}:${f.line} \u2014 ${f.description} (${f.match})`);
|
|
1177
|
+
}
|
|
1178
|
+
lines.push("");
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
lines.push("=".repeat(80));
|
|
1182
|
+
lines.push(`Scan duration: ${(result.totalScanDurationMs / 1e3).toFixed(1)}s`);
|
|
1183
|
+
return lines.join("\n");
|
|
1184
|
+
}
|
|
1185
|
+
function generateUnifiedCsvReport(result) {
|
|
1186
|
+
const sections = [];
|
|
1187
|
+
if (result.modules.license) {
|
|
1188
|
+
const lines = [];
|
|
1189
|
+
lines.push("# License Compliance");
|
|
1190
|
+
lines.push("Module,Package,Version,License,Verdict,Reason");
|
|
1191
|
+
const allResults = [
|
|
1192
|
+
...result.modules.license.allowed,
|
|
1193
|
+
...result.modules.license.warned,
|
|
1194
|
+
...result.modules.license.rejected,
|
|
1195
|
+
...result.modules.license.unknown
|
|
1196
|
+
];
|
|
1197
|
+
for (const r of allResults) {
|
|
1198
|
+
lines.push(`license,${escapeCsv2(r.dependency.name)},${escapeCsv2(r.dependency.version)},${escapeCsv2(r.dependency.license)},${r.verdict},${escapeCsv2(r.reason)}`);
|
|
1199
|
+
}
|
|
1200
|
+
sections.push(lines.join("\n"));
|
|
1201
|
+
}
|
|
1202
|
+
if (result.modules.security) {
|
|
1203
|
+
const lines = [];
|
|
1204
|
+
lines.push("# Security Findings");
|
|
1205
|
+
lines.push("Module,Package,Version,Type,Severity,Message");
|
|
1206
|
+
for (const f of result.modules.security.findings) {
|
|
1207
|
+
lines.push(`security,${escapeCsv2(f.package)},${escapeCsv2(f.version)},${f.type},${f.severity},${escapeCsv2(f.message)}`);
|
|
1208
|
+
}
|
|
1209
|
+
sections.push(lines.join("\n"));
|
|
1210
|
+
}
|
|
1211
|
+
if (result.modules.secrets) {
|
|
1212
|
+
const lines = [];
|
|
1213
|
+
lines.push("# Secret Detection Findings");
|
|
1214
|
+
lines.push("Module,File,Line,RuleId,Severity,Description");
|
|
1215
|
+
for (const f of result.modules.secrets.findings) {
|
|
1216
|
+
lines.push(`secrets,${escapeCsv2(f.file)},${f.line},${f.ruleId},${f.severity},${escapeCsv2(f.description)}`);
|
|
1217
|
+
}
|
|
1218
|
+
sections.push(lines.join("\n"));
|
|
1219
|
+
}
|
|
1220
|
+
return sections.join("\n\n");
|
|
1221
|
+
}
|
|
1222
|
+
function escapeCsv2(value) {
|
|
1223
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
1224
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
1225
|
+
}
|
|
1226
|
+
return value;
|
|
1227
|
+
}
|
|
567
1228
|
|
|
568
1229
|
// src/commands/allow.ts
|
|
569
1230
|
var fs3 = __toESM(require("fs"));
|
|
@@ -3203,7 +3864,7 @@ function registerAllowCommand(program2) {
|
|
|
3203
3864
|
|
|
3204
3865
|
// src/cli.ts
|
|
3205
3866
|
var program = new import_commander.Command();
|
|
3206
|
-
program.name("preship").description("
|
|
3867
|
+
program.name("preship").description("Pre-ship verification: license compliance, security scanning, and secret detection \u2014 all before you ship.").version("2.0.0");
|
|
3207
3868
|
registerScanCommand(program);
|
|
3208
3869
|
registerInitCommand(program);
|
|
3209
3870
|
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.0",
|
|
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.0",
|
|
59
|
+
"@preship/license": "2.0.0",
|
|
60
|
+
"@preship/security": "1.0.0",
|
|
61
|
+
"@preship/secrets": "1.0.0",
|
|
54
62
|
"chalk": "^4.1.2",
|
|
55
63
|
"cli-table3": "^0.6.5",
|
|
56
64
|
"commander": "^12.1.0"
|