guardrail-cli 2.4.13 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -8
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts +1 -0
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts.map +1 -1
- package/dist/commands/scan-vulnerabilities-enhanced.js.map +1 -1
- package/dist/commands/scan-vulnerabilities-osv.d.ts +1 -1
- package/dist/commands/scan-vulnerabilities-osv.d.ts.map +1 -1
- package/dist/commands/scan-vulnerabilities-osv.js +2 -2
- package/dist/commands/scan-vulnerabilities-osv.js.map +1 -1
- package/dist/commands/secrets-allowlist.js +12 -11
- package/dist/commands/secrets-allowlist.js.map +1 -1
- package/dist/index.js +506 -65
- package/dist/index.js.map +1 -1
- package/dist/init/ci-generator.d.ts.map +1 -1
- package/dist/init/ci-generator.js.map +1 -1
- package/dist/runtime/exit-codes.d.ts +2 -0
- package/dist/runtime/exit-codes.d.ts.map +1 -1
- package/dist/runtime/exit-codes.js +2 -0
- package/dist/runtime/exit-codes.js.map +1 -1
- package/dist/scanner/parallel.js +1 -1
- package/dist/scanner/parallel.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -164,6 +164,8 @@ const styles = {
|
|
|
164
164
|
bgBlue: '\x1b[44m',
|
|
165
165
|
bgMagenta: '\x1b[45m',
|
|
166
166
|
bgCyan: '\x1b[46m',
|
|
167
|
+
// Symbols
|
|
168
|
+
bullet: '•',
|
|
167
169
|
};
|
|
168
170
|
// Styled text helpers
|
|
169
171
|
const style = {
|
|
@@ -300,25 +302,58 @@ function printStatusBadge(status) {
|
|
|
300
302
|
};
|
|
301
303
|
console.log(` ${badges[status] || badges.free}`);
|
|
302
304
|
}
|
|
303
|
-
// Enterprise-styled prompt helpers
|
|
305
|
+
// Enterprise-styled prompt helpers with arrow key navigation
|
|
304
306
|
async function promptSelect(message, choices) {
|
|
305
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
306
307
|
return new Promise((resolve) => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const num = `${styles.cyan}${styles.bold}[${i + 1}]${styles.reset}`;
|
|
312
|
-
const badge = c.badge ? ` ${c.badge}` : '';
|
|
313
|
-
console.log(` ${styles.dim}${box.vertical}${styles.reset} ${num} ${c.name}${badge}`);
|
|
314
|
-
});
|
|
315
|
-
console.log(` ${styles.dim}${box.bottomLeft}${box.horizontal.repeat(50)}${styles.reset}`);
|
|
316
|
-
console.log('');
|
|
317
|
-
rl.question(` ${styles.brightCyan}❯${styles.reset} Enter choice ${styles.dim}(1-${choices.length})${styles.reset}: `, (answer) => {
|
|
318
|
-
rl.close();
|
|
319
|
-
const idx = parseInt(answer, 10) - 1;
|
|
320
|
-
resolve(choices[Math.max(0, Math.min(idx, choices.length - 1))].value);
|
|
308
|
+
const rl = readline.createInterface({
|
|
309
|
+
input: process.stdin,
|
|
310
|
+
output: process.stdout,
|
|
311
|
+
terminal: true
|
|
321
312
|
});
|
|
313
|
+
let selectedIndex = 0;
|
|
314
|
+
const renderMenu = () => {
|
|
315
|
+
console.clear();
|
|
316
|
+
console.log('');
|
|
317
|
+
console.log(` ${styles.brightCyan}${styles.bold}?${styles.reset} ${styles.bold}${message}${styles.reset}`);
|
|
318
|
+
console.log(` ${styles.dim}${box.teeLeft}${box.horizontal.repeat(50)}${styles.reset}`);
|
|
319
|
+
choices.forEach((choice, i) => {
|
|
320
|
+
const isSelected = i === selectedIndex;
|
|
321
|
+
const prefix = isSelected ? `${styles.brightCyan}${styles.bold}❯${styles.reset}` : ' ';
|
|
322
|
+
const badge = choice.badge ? ` ${choice.badge}` : '';
|
|
323
|
+
const color = isSelected ? styles.brightWhite : styles.dim;
|
|
324
|
+
console.log(` ${styles.dim}${box.vertical}${styles.reset} ${prefix} ${color}${choice.name}${badge}${styles.reset}`);
|
|
325
|
+
});
|
|
326
|
+
console.log(` ${styles.dim}${box.bottomLeft}${box.horizontal.repeat(50)}${styles.reset}`);
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log(` ${styles.dim}Use ↑↓ arrows to move, Enter to select${styles.reset}`);
|
|
329
|
+
};
|
|
330
|
+
renderMenu();
|
|
331
|
+
// Handle keypress events
|
|
332
|
+
readline.emitKeypressEvents(process.stdin);
|
|
333
|
+
process.stdin.setRawMode(true);
|
|
334
|
+
const onKeyPress = (str, key) => {
|
|
335
|
+
if (key.name === 'up') {
|
|
336
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : choices.length - 1;
|
|
337
|
+
renderMenu();
|
|
338
|
+
}
|
|
339
|
+
else if (key.name === 'down') {
|
|
340
|
+
selectedIndex = selectedIndex < choices.length - 1 ? selectedIndex + 1 : 0;
|
|
341
|
+
renderMenu();
|
|
342
|
+
}
|
|
343
|
+
else if (key.name === 'return' || key.name === 'enter') {
|
|
344
|
+
process.stdin.setRawMode(false);
|
|
345
|
+
process.stdin.removeListener('keypress', onKeyPress);
|
|
346
|
+
rl.close();
|
|
347
|
+
resolve(choices[selectedIndex].value);
|
|
348
|
+
}
|
|
349
|
+
else if (key.ctrl && key.name === 'c') {
|
|
350
|
+
process.stdin.setRawMode(false);
|
|
351
|
+
process.stdin.removeListener('keypress', onKeyPress);
|
|
352
|
+
rl.close();
|
|
353
|
+
process.exit(0);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
process.stdin.on('keypress', onKeyPress);
|
|
322
357
|
});
|
|
323
358
|
}
|
|
324
359
|
async function promptInput(message, defaultValue) {
|
|
@@ -1494,7 +1529,7 @@ program
|
|
|
1494
1529
|
console.log(` ${styles.dim}List backups:${styles.reset} ${styles.bold}guardrail fix rollback --list${styles.reset}`);
|
|
1495
1530
|
console.log('');
|
|
1496
1531
|
}
|
|
1497
|
-
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.
|
|
1532
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.USER_ERROR, 'Run ID required');
|
|
1498
1533
|
}
|
|
1499
1534
|
if (!options.json) {
|
|
1500
1535
|
console.log('');
|
|
@@ -1667,38 +1702,156 @@ program
|
|
|
1667
1702
|
});
|
|
1668
1703
|
}
|
|
1669
1704
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
`${styles.dim}Embed code:${styles.reset} ${shipResult.embedCode}`,
|
|
1676
|
-
];
|
|
1677
|
-
const framedBadge = frameLines(badgeLines, { padding: 2 });
|
|
1678
|
-
console.log(framedBadge.join('\n'));
|
|
1705
|
+
// Show badge embed code
|
|
1706
|
+
if (shipResult.embedCode) {
|
|
1707
|
+
console.log(`${styles.bold}BADGE EMBED CODE${styles.reset}`);
|
|
1708
|
+
printDivider();
|
|
1709
|
+
console.log(` ${styles.dim}${shipResult.embedCode}${styles.reset}`);
|
|
1679
1710
|
console.log('');
|
|
1680
1711
|
}
|
|
1681
1712
|
}
|
|
1682
1713
|
if (options.output) {
|
|
1683
|
-
const
|
|
1714
|
+
const output = {
|
|
1684
1715
|
ship: shipResult,
|
|
1685
1716
|
mockproof: mockproofResult,
|
|
1686
|
-
|
|
1717
|
+
timestamp: new Date().toISOString(),
|
|
1718
|
+
project: {
|
|
1719
|
+
name: projectName,
|
|
1720
|
+
path: projectPath
|
|
1721
|
+
}
|
|
1687
1722
|
};
|
|
1688
|
-
(0, fs_1.writeFileSync)(options.output, JSON.stringify(
|
|
1689
|
-
console.log(
|
|
1723
|
+
(0, fs_1.writeFileSync)(options.output, JSON.stringify(output, null, 2));
|
|
1724
|
+
console.log(`${styles.dim}Report saved to:${styles.reset} ${options.output}`);
|
|
1725
|
+
}
|
|
1726
|
+
// Exit with appropriate code
|
|
1727
|
+
const exitCode = shipResult.verdict === 'ship' ? exit_codes_1.ExitCode.SUCCESS : exit_codes_1.ExitCode.POLICY_FAIL;
|
|
1728
|
+
(0, exit_codes_1.exitWith)(exitCode);
|
|
1729
|
+
}
|
|
1730
|
+
catch (error) {
|
|
1731
|
+
console.error(`${styles.brightRed}Error:${styles.reset} ${error.message}`);
|
|
1732
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Ship check failed');
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
// Pro Ship command (Pro feature - $99/month)
|
|
1736
|
+
program
|
|
1737
|
+
.command('ship:pro')
|
|
1738
|
+
.description('Pro Ship Check - Comprehensive scanning with all services (Pro $99/mo)')
|
|
1739
|
+
.option('-p, --path <path>', 'Project path to analyze', '.')
|
|
1740
|
+
.option('-f, --format <format>', 'Output format: table, json, markdown', 'table')
|
|
1741
|
+
.option('-o, --output <file>', 'Output file path')
|
|
1742
|
+
.option('--url <baseUrl>', 'Base URL for reality mode scanning')
|
|
1743
|
+
.option('--no-reality', 'Skip reality mode scan')
|
|
1744
|
+
.option('--no-security', 'Skip security scan')
|
|
1745
|
+
.option('--no-performance', 'Skip performance check')
|
|
1746
|
+
.option('--no-accessibility', 'Skip accessibility check')
|
|
1747
|
+
.option('--badge', 'Generate dynamic badge', true)
|
|
1748
|
+
.action(async (options) => {
|
|
1749
|
+
const config = requireAuth('pro');
|
|
1750
|
+
printLogo();
|
|
1751
|
+
const projectPath = (0, path_1.resolve)(options.path);
|
|
1752
|
+
const projectName = (0, path_1.basename)(projectPath);
|
|
1753
|
+
(0, ui_1.printCommandHeader)({
|
|
1754
|
+
title: 'PRO SHIP CHECK',
|
|
1755
|
+
icon: icons.ship,
|
|
1756
|
+
projectName,
|
|
1757
|
+
projectPath,
|
|
1758
|
+
metadata: [
|
|
1759
|
+
{ key: 'Reality Mode', value: !options.noReality ? 'Enabled' : 'Disabled' },
|
|
1760
|
+
{ key: 'Security Scan', value: !options.noSecurity ? 'Enabled' : 'Disabled' },
|
|
1761
|
+
{ key: 'Performance', value: !options.noPerformance ? 'Enabled' : 'Disabled' },
|
|
1762
|
+
{ key: 'Accessibility', value: !options.noAccessibility ? 'Enabled' : 'Disabled' },
|
|
1763
|
+
{ key: 'Dynamic Badge', value: options.badge ? 'Enabled' : 'Disabled' },
|
|
1764
|
+
],
|
|
1765
|
+
tier: (await config).tier,
|
|
1766
|
+
authenticated: !!(await config).apiKey,
|
|
1767
|
+
});
|
|
1768
|
+
try {
|
|
1769
|
+
// Import pro ship scanner
|
|
1770
|
+
const { ProShipScanner } = require('guardrail-ship');
|
|
1771
|
+
const proShipScanner = new ProShipScanner();
|
|
1772
|
+
const scanConfig = {
|
|
1773
|
+
projectPath,
|
|
1774
|
+
baseUrl: options.url,
|
|
1775
|
+
includeRealityMode: !options.noReality,
|
|
1776
|
+
includeSecurityScan: !options.noSecurity,
|
|
1777
|
+
includePerformanceCheck: !options.noPerformance,
|
|
1778
|
+
includeAccessibilityCheck: !options.noAccessibility,
|
|
1779
|
+
};
|
|
1780
|
+
console.log(`${styles.dim}Running comprehensive scan...${styles.reset}`);
|
|
1781
|
+
console.log('');
|
|
1782
|
+
const result = await proShipScanner.runComprehensiveScan(scanConfig);
|
|
1783
|
+
if (options.format === 'json') {
|
|
1784
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
// Display comprehensive results
|
|
1788
|
+
const verdictColor = result.verdict === 'SHIP' ? styles.brightGreen :
|
|
1789
|
+
result.verdict === 'NO-SHIP' ? styles.brightRed : styles.brightYellow;
|
|
1790
|
+
const verdictIcon = result.verdict === 'SHIP' ? icons.success :
|
|
1791
|
+
result.verdict === 'NO-SHIP' ? icons.error : icons.warning;
|
|
1792
|
+
const verdictLines = [
|
|
1793
|
+
`${verdictColor}${styles.bold}${verdictIcon} ${result.verdict}${styles.reset}`,
|
|
1794
|
+
'',
|
|
1795
|
+
`${styles.dim}Overall Score:${styles.reset} ${styles.bold}${result.overallScore}${styles.reset}/100`,
|
|
1796
|
+
`${styles.dim}Scans Completed:${styles.reset} ${result.summary.totalScans}/${result.summary.totalScans}`,
|
|
1797
|
+
`${styles.dim}Passed:${styles.reset} ${styles.brightGreen}${result.summary.passedScans}${styles.reset}`,
|
|
1798
|
+
`${styles.dim}Failed:${styles.reset} ${styles.brightRed}${result.summary.failedScans}${styles.reset}`,
|
|
1799
|
+
`${styles.dim}Critical Issues:${styles.reset} ${styles.brightRed}${result.summary.criticalIssues}${styles.reset}`,
|
|
1800
|
+
`${styles.dim}Warnings:${styles.reset} ${styles.brightYellow}${result.summary.warnings}${styles.reset}`,
|
|
1801
|
+
`${styles.dim}Duration:${styles.reset} ${(result.summary.totalDuration / 1000).toFixed(2)}s`,
|
|
1802
|
+
];
|
|
1803
|
+
const framedVerdict = frameLines(verdictLines, { padding: 2 });
|
|
1804
|
+
console.log(framedVerdict.join('\n'));
|
|
1805
|
+
console.log('');
|
|
1806
|
+
// Show individual scan results
|
|
1807
|
+
console.log(`${styles.bold}SCAN RESULTS${styles.reset}`);
|
|
1808
|
+
printDivider();
|
|
1809
|
+
result.scans.forEach((scan, index) => {
|
|
1810
|
+
const statusColor = scan.status === 'pass' ? styles.brightGreen :
|
|
1811
|
+
scan.status === 'fail' ? styles.brightRed :
|
|
1812
|
+
scan.status === 'warning' ? styles.brightYellow : styles.brightRed;
|
|
1813
|
+
const statusIcon = scan.status === 'pass' ? icons.success :
|
|
1814
|
+
scan.status === 'fail' ? icons.error :
|
|
1815
|
+
scan.status === 'warning' ? icons.warning : icons.error;
|
|
1816
|
+
console.log(`${styles.cyan}${index + 1}.${styles.reset} ${styles.bold}${scan.name}${styles.reset}`);
|
|
1817
|
+
console.log(` Status: ${statusColor}${statusIcon} ${scan.status.toUpperCase()}${styles.reset}`);
|
|
1818
|
+
console.log(` Score: ${styles.bold}${scan.score}${styles.reset}/100`);
|
|
1819
|
+
console.log(` Duration: ${(scan.duration / 1000).toFixed(2)}s`);
|
|
1820
|
+
if (scan.criticalIssues > 0) {
|
|
1821
|
+
console.log(` Critical: ${styles.brightRed}${scan.criticalIssues}${styles.reset}`);
|
|
1822
|
+
}
|
|
1823
|
+
if (scan.warnings > 0) {
|
|
1824
|
+
console.log(` Warnings: ${styles.brightYellow}${scan.warnings}${styles.reset}`);
|
|
1825
|
+
}
|
|
1826
|
+
console.log('');
|
|
1827
|
+
});
|
|
1828
|
+
// Show recommendation
|
|
1829
|
+
console.log(`${styles.bold}RECOMMENDATION${styles.reset}`);
|
|
1830
|
+
printDivider();
|
|
1831
|
+
console.log(`${styles.dim}${result.recommendation}${styles.reset}`);
|
|
1690
1832
|
console.log('');
|
|
1833
|
+
// Show badge info
|
|
1834
|
+
if (options.badge && result.badge) {
|
|
1835
|
+
console.log(`${styles.bold}DYNAMIC BADGE${styles.reset}`);
|
|
1836
|
+
printDivider();
|
|
1837
|
+
console.log(`${styles.dim}SVG URL:${styles.reset} ${result.badge.svgUrl}`);
|
|
1838
|
+
console.log(`${styles.dim}JSON URL:${styles.reset} ${result.badge.jsonUrl}`);
|
|
1839
|
+
console.log(`${styles.dim}Embed Code:${styles.reset}`);
|
|
1840
|
+
console.log(` ${styles.dim}${result.badge.embedCode}${styles.reset}`);
|
|
1841
|
+
console.log('');
|
|
1842
|
+
}
|
|
1691
1843
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1844
|
+
if (options.output) {
|
|
1845
|
+
(0, fs_1.writeFileSync)(options.output, JSON.stringify(result, null, 2));
|
|
1846
|
+
console.log(`${styles.dim}Report saved to:${styles.reset} ${options.output}`);
|
|
1695
1847
|
}
|
|
1848
|
+
// Exit with appropriate code
|
|
1849
|
+
const exitCode = result.verdict === 'SHIP' ? exit_codes_1.ExitCode.SUCCESS : exit_codes_1.ExitCode.POLICY_FAIL;
|
|
1850
|
+
(0, exit_codes_1.exitWith)(exitCode);
|
|
1696
1851
|
}
|
|
1697
1852
|
catch (error) {
|
|
1698
|
-
console.
|
|
1699
|
-
|
|
1700
|
-
console.log('');
|
|
1701
|
-
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Ship check execution failed');
|
|
1853
|
+
console.error(`${styles.brightRed}Error:${styles.reset} ${error.message}`);
|
|
1854
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Pro ship check failed');
|
|
1702
1855
|
}
|
|
1703
1856
|
});
|
|
1704
1857
|
// Reality command (Starter+ feature)
|
|
@@ -1755,22 +1908,53 @@ program
|
|
|
1755
1908
|
// Check dependencies first
|
|
1756
1909
|
const depCheck = checkPlaywrightDependencies(projectPath);
|
|
1757
1910
|
if (!depCheck.playwrightInstalled) {
|
|
1758
|
-
console.log(` ${styles.
|
|
1911
|
+
console.log(` ${styles.brightYellow}${icons.warning} Playwright not installed${styles.reset}`);
|
|
1759
1912
|
console.log('');
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1913
|
+
// Try to install automatically
|
|
1914
|
+
console.log(` ${styles.brightCyan}${icons.info} Attempting automatic installation...${styles.reset}`);
|
|
1915
|
+
const installResult = await installPlaywrightDependencies(projectPath);
|
|
1916
|
+
if (!installResult.success) {
|
|
1917
|
+
console.log(` ${styles.brightRed}${icons.error} Auto-installation failed: ${installResult.error}${styles.reset}`);
|
|
1918
|
+
console.log('');
|
|
1919
|
+
console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
|
|
1920
|
+
depCheck.installCommands.forEach(cmd => {
|
|
1921
|
+
console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
|
|
1922
|
+
});
|
|
1923
|
+
console.log('');
|
|
1924
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
|
|
1925
|
+
}
|
|
1926
|
+
console.log(` ${styles.brightGreen}${icons.success} Playwright installed successfully${styles.reset}`);
|
|
1764
1927
|
console.log('');
|
|
1765
|
-
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
|
|
1766
1928
|
}
|
|
1767
1929
|
if (!depCheck.browsersInstalled) {
|
|
1768
1930
|
console.log(` ${styles.brightYellow}${icons.warning} Playwright browsers not installed${styles.reset}`);
|
|
1769
1931
|
console.log('');
|
|
1770
|
-
|
|
1771
|
-
console.log(`
|
|
1772
|
-
|
|
1773
|
-
|
|
1932
|
+
// Try to install browsers only
|
|
1933
|
+
console.log(` ${styles.brightCyan}${icons.info} Installing browsers...${styles.reset}`);
|
|
1934
|
+
try {
|
|
1935
|
+
await new Promise((resolve, reject) => {
|
|
1936
|
+
const browserInstall = spawn('npx', ['playwright', 'install'], {
|
|
1937
|
+
cwd: projectPath,
|
|
1938
|
+
stdio: 'pipe'
|
|
1939
|
+
});
|
|
1940
|
+
browserInstall.on('close', (code) => {
|
|
1941
|
+
if (code === 0) {
|
|
1942
|
+
console.log(` ${styles.brightGreen}${icons.success} Browsers installed successfully${styles.reset}`);
|
|
1943
|
+
resolve();
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
reject(new Error('browser install failed'));
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
browserInstall.on('error', reject);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
catch (error) {
|
|
1953
|
+
console.log(` ${styles.brightRed}${icons.error} Browser installation failed: ${error.message}${styles.reset}`);
|
|
1954
|
+
console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
|
|
1955
|
+
console.log('');
|
|
1956
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright browsers not installed');
|
|
1957
|
+
}
|
|
1774
1958
|
}
|
|
1775
1959
|
// Create artifact directory for recorded test
|
|
1776
1960
|
const artifacts = createArtifactDirectory(projectPath, options.flow);
|
|
@@ -1852,22 +2036,60 @@ program
|
|
|
1852
2036
|
console.log('');
|
|
1853
2037
|
const depCheck = checkPlaywrightDependencies(projectPath);
|
|
1854
2038
|
if (!depCheck.playwrightInstalled) {
|
|
1855
|
-
console.log(` ${styles.
|
|
2039
|
+
console.log(` ${styles.brightYellow}${icons.warning} Playwright not installed${styles.reset}`);
|
|
1856
2040
|
console.log('');
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2041
|
+
// Try to install automatically
|
|
2042
|
+
console.log(` ${styles.brightCyan}${icons.info} Attempting automatic installation...${styles.reset}`);
|
|
2043
|
+
const installResult = await installPlaywrightDependencies(projectPath);
|
|
2044
|
+
if (!installResult.success) {
|
|
2045
|
+
console.log(` ${styles.brightRed}${icons.error} Auto-installation failed: ${installResult.error}${styles.reset}`);
|
|
2046
|
+
console.log('');
|
|
2047
|
+
console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
|
|
2048
|
+
depCheck.installCommands.forEach(cmd => {
|
|
2049
|
+
console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
|
|
2050
|
+
});
|
|
2051
|
+
console.log('');
|
|
2052
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
|
|
2053
|
+
}
|
|
2054
|
+
// Re-check after installation
|
|
2055
|
+
const newDepCheck = checkPlaywrightDependencies(projectPath);
|
|
2056
|
+
if (!newDepCheck.playwrightInstalled) {
|
|
2057
|
+
console.log(` ${styles.brightRed}${icons.error} Installation verification failed${styles.reset}`);
|
|
2058
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright installation failed');
|
|
2059
|
+
}
|
|
2060
|
+
console.log(` ${styles.brightGreen}${icons.success} Playwright installed successfully${styles.reset}`);
|
|
1861
2061
|
console.log('');
|
|
1862
|
-
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
|
|
1863
2062
|
}
|
|
1864
2063
|
if (!depCheck.browsersInstalled) {
|
|
1865
2064
|
console.log(` ${styles.brightYellow}${icons.warning} Playwright browsers not installed${styles.reset}`);
|
|
1866
2065
|
console.log('');
|
|
1867
|
-
|
|
1868
|
-
console.log(`
|
|
1869
|
-
|
|
1870
|
-
|
|
2066
|
+
// Try to install browsers only
|
|
2067
|
+
console.log(` ${styles.brightCyan}${icons.info} Installing browsers...${styles.reset}`);
|
|
2068
|
+
try {
|
|
2069
|
+
const { spawn } = require('child_process');
|
|
2070
|
+
await new Promise((resolve, reject) => {
|
|
2071
|
+
const browserInstall = spawn('npx', ['playwright', 'install'], {
|
|
2072
|
+
cwd: projectPath,
|
|
2073
|
+
stdio: 'pipe'
|
|
2074
|
+
});
|
|
2075
|
+
browserInstall.on('close', (code) => {
|
|
2076
|
+
if (code === 0) {
|
|
2077
|
+
console.log(` ${styles.brightGreen}${icons.success} Browsers installed successfully${styles.reset}`);
|
|
2078
|
+
resolve();
|
|
2079
|
+
}
|
|
2080
|
+
else {
|
|
2081
|
+
reject(new Error('browser install failed'));
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
browserInstall.on('error', reject);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
catch (error) {
|
|
2088
|
+
console.log(` ${styles.brightRed}${icons.error} Browser installation failed: ${error.message}${styles.reset}`);
|
|
2089
|
+
console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
|
|
2090
|
+
console.log('');
|
|
2091
|
+
process.exit(2);
|
|
2092
|
+
}
|
|
1871
2093
|
}
|
|
1872
2094
|
console.log(` ${styles.brightGreen}${icons.success}${styles.reset} Playwright installed`);
|
|
1873
2095
|
console.log(` ${styles.brightGreen}${icons.success}${styles.reset} Browsers available`);
|
|
@@ -2674,23 +2896,64 @@ async function runScanEnterprise(projectPath, options) {
|
|
|
2674
2896
|
since: options.since,
|
|
2675
2897
|
baseline: options.baseline,
|
|
2676
2898
|
});
|
|
2899
|
+
// Adapter functions for baseline management
|
|
2900
|
+
const secretToBaselineFinding = (secret) => ({
|
|
2901
|
+
type: secret.type,
|
|
2902
|
+
category: 'secret',
|
|
2903
|
+
title: secret.type,
|
|
2904
|
+
file: secret.file,
|
|
2905
|
+
line: secret.line,
|
|
2906
|
+
match: secret.match,
|
|
2907
|
+
snippet: secret.match,
|
|
2908
|
+
});
|
|
2909
|
+
const vulnToBaselineFinding = (vuln) => ({
|
|
2910
|
+
type: 'vulnerability',
|
|
2911
|
+
category: vuln.severity,
|
|
2912
|
+
title: vuln.title || vuln.cve,
|
|
2913
|
+
file: vuln.path || 'package.json',
|
|
2914
|
+
line: 1,
|
|
2915
|
+
match: vuln.cve,
|
|
2916
|
+
snippet: `${vuln.package}@${vuln.version}`,
|
|
2917
|
+
});
|
|
2918
|
+
const baselineToSecretFinding = (finding) => ({
|
|
2919
|
+
type: finding.type || 'unknown',
|
|
2920
|
+
file: finding.file,
|
|
2921
|
+
line: finding.line,
|
|
2922
|
+
risk: 'medium', // Default risk
|
|
2923
|
+
confidence: 0.8,
|
|
2924
|
+
entropy: 0,
|
|
2925
|
+
match: finding.match || '',
|
|
2926
|
+
isTest: false,
|
|
2927
|
+
recommendation: 'Review and remediate',
|
|
2928
|
+
});
|
|
2929
|
+
const baselineToVulnFinding = (finding) => ({
|
|
2930
|
+
package: finding.snippet?.split('@')[0] || 'unknown',
|
|
2931
|
+
version: finding.snippet?.split('@')[1] || 'unknown',
|
|
2932
|
+
severity: finding.category || 'medium',
|
|
2933
|
+
cve: finding.match || 'unknown',
|
|
2934
|
+
title: finding.title,
|
|
2935
|
+
fixedIn: 'unknown',
|
|
2936
|
+
path: finding.file,
|
|
2937
|
+
});
|
|
2677
2938
|
if (options.baseline) {
|
|
2678
2939
|
if (results.secrets) {
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2940
|
+
const secretFindings = results.secrets.findings.map(secretToBaselineFinding);
|
|
2941
|
+
const { filtered, suppressed } = BaselineManager.filterFindings(secretFindings, options.baseline);
|
|
2942
|
+
results.secrets.findings = filtered.map(baselineToSecretFinding);
|
|
2681
2943
|
results.secrets.summary.total = filtered.length;
|
|
2682
2944
|
results.secrets.suppressedByBaseline = suppressed;
|
|
2683
2945
|
}
|
|
2684
2946
|
if (results.vulnerabilities) {
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2947
|
+
const vulnFindings = results.vulnerabilities.findings.map(vulnToBaselineFinding);
|
|
2948
|
+
const { filtered, suppressed } = BaselineManager.filterFindings(vulnFindings, options.baseline);
|
|
2949
|
+
results.vulnerabilities.findings = filtered.map(baselineToVulnFinding);
|
|
2687
2950
|
const summary = {
|
|
2688
2951
|
critical: filtered.filter((f) => f.severity === 'critical').length,
|
|
2689
2952
|
high: filtered.filter((f) => f.severity === 'high').length,
|
|
2690
2953
|
medium: filtered.filter((f) => f.severity === 'medium').length,
|
|
2691
2954
|
low: filtered.filter((f) => f.severity === 'low').length,
|
|
2692
2955
|
};
|
|
2693
|
-
results.vulnerabilities.summary = summary;
|
|
2956
|
+
results.vulnerabilities.summary = { ...results.vulnerabilities.summary, ...summary };
|
|
2694
2957
|
results.vulnerabilities.suppressedByBaseline = suppressed;
|
|
2695
2958
|
}
|
|
2696
2959
|
}
|
|
@@ -2893,9 +3156,15 @@ async function initProject(projectPath, options) {
|
|
|
2893
3156
|
if (!validation.success) {
|
|
2894
3157
|
s3.stop(false, 'Configuration validation failed');
|
|
2895
3158
|
console.log(` ${styles.brightRed}${icons.error}${styles.reset} Config validation errors:`);
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
3159
|
+
const validationError = validation;
|
|
3160
|
+
if (validationError.error && Array.isArray(validationError.error.errors)) {
|
|
3161
|
+
validationError.error.errors.forEach((err) => {
|
|
3162
|
+
console.log(` ${styles.dim}${err.path?.join('.') || 'field'}:${styles.reset} ${err.message}`);
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
else {
|
|
3166
|
+
console.log(` ${styles.dim}Unknown validation error${styles.reset}`);
|
|
3167
|
+
}
|
|
2899
3168
|
return;
|
|
2900
3169
|
}
|
|
2901
3170
|
// Atomic write
|
|
@@ -3162,6 +3431,54 @@ function outputComplianceResults(results, options) {
|
|
|
3162
3431
|
}
|
|
3163
3432
|
console.log(` ${styles.dim}Run${styles.reset} ${styles.bold}guardrail scan:compliance --framework gdpr${styles.reset} ${styles.dim}for other frameworks.${styles.reset}\n`);
|
|
3164
3433
|
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Install Playwright dependencies automatically
|
|
3436
|
+
*/
|
|
3437
|
+
async function installPlaywrightDependencies(projectPath) {
|
|
3438
|
+
const { spawn } = require('child_process');
|
|
3439
|
+
try {
|
|
3440
|
+
console.log(` ${styles.brightCyan}${icons.info} Installing Playwright...${styles.reset}`);
|
|
3441
|
+
// Install @playwright/test
|
|
3442
|
+
await new Promise((resolve, reject) => {
|
|
3443
|
+
const npmInstall = spawn('npm', ['install', '-D', '@playwright/test'], {
|
|
3444
|
+
cwd: projectPath,
|
|
3445
|
+
stdio: 'pipe'
|
|
3446
|
+
});
|
|
3447
|
+
npmInstall.on('close', (code) => {
|
|
3448
|
+
if (code === 0) {
|
|
3449
|
+
console.log(` ${styles.brightGreen}${icons.success} Playwright package installed${styles.reset}`);
|
|
3450
|
+
resolve();
|
|
3451
|
+
}
|
|
3452
|
+
else {
|
|
3453
|
+
reject(new Error('npm install failed'));
|
|
3454
|
+
}
|
|
3455
|
+
});
|
|
3456
|
+
npmInstall.on('error', reject);
|
|
3457
|
+
});
|
|
3458
|
+
// Install browsers
|
|
3459
|
+
console.log(` ${styles.brightCyan}${icons.info} Installing Playwright browsers...${styles.reset}`);
|
|
3460
|
+
await new Promise((resolve, reject) => {
|
|
3461
|
+
const browserInstall = spawn('npx', ['playwright', 'install'], {
|
|
3462
|
+
cwd: projectPath,
|
|
3463
|
+
stdio: 'pipe'
|
|
3464
|
+
});
|
|
3465
|
+
browserInstall.on('close', (code) => {
|
|
3466
|
+
if (code === 0) {
|
|
3467
|
+
console.log(` ${styles.brightGreen}${icons.success} Playwright browsers installed${styles.reset}`);
|
|
3468
|
+
resolve();
|
|
3469
|
+
}
|
|
3470
|
+
else {
|
|
3471
|
+
reject(new Error('browser install failed'));
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3474
|
+
browserInstall.on('error', reject);
|
|
3475
|
+
});
|
|
3476
|
+
return { success: true };
|
|
3477
|
+
}
|
|
3478
|
+
catch (error) {
|
|
3479
|
+
return { success: false, error: error.message };
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3165
3482
|
async function runInteractiveMenu() {
|
|
3166
3483
|
const cfg = loadConfig();
|
|
3167
3484
|
while (true) {
|
|
@@ -3172,6 +3489,9 @@ async function runInteractiveMenu() {
|
|
|
3172
3489
|
{ name: `${styles.brightGreen}${icons.scan}${styles.reset} Vulnerability Scan ${styles.dim}Check dependencies for CVEs${styles.reset}`, value: 'scan_vulns' },
|
|
3173
3490
|
{ name: `${styles.brightYellow}${icons.compliance}${styles.reset} Compliance Scan ${proBadge} ${styles.dim}SOC2/GDPR/HIPAA${styles.reset}`, value: 'scan_compliance' },
|
|
3174
3491
|
{ name: `${styles.brightBlue}${icons.sbom}${styles.reset} Generate SBOM ${proBadge} ${styles.dim}Software bill of materials${styles.reset}`, value: 'sbom' },
|
|
3492
|
+
{ name: `${styles.brightMagenta}${icons.reality}${styles.reset} Reality Mode ${styles.dim}Browser testing & auth flows${styles.reset}`, value: 'reality' },
|
|
3493
|
+
{ name: `${styles.brightGreen}${icons.ship}${styles.reset} Ship Check ${styles.dim}Pre-deployment validation${styles.reset}`, value: 'ship' },
|
|
3494
|
+
{ name: `${styles.cyan}${icons.autopilot}${styles.reset} Initialize Project ${styles.dim}Setup Guardrail configuration${styles.reset}`, value: 'init' },
|
|
3175
3495
|
{ name: `${styles.brightMagenta}${icons.auth}${styles.reset} Auth / Status ${styles.dim}Login, logout, view status${styles.reset}`, value: 'auth' },
|
|
3176
3496
|
{ name: `${styles.dim}${icons.error} Exit${styles.reset}`, value: 'exit' },
|
|
3177
3497
|
]);
|
|
@@ -3319,6 +3639,127 @@ async function runInteractiveMenu() {
|
|
|
3319
3639
|
console.log(`${c.success('✓')} SBOM written to ${output}\n`);
|
|
3320
3640
|
continue;
|
|
3321
3641
|
}
|
|
3642
|
+
if (action === 'reality') {
|
|
3643
|
+
requireAuth('starter');
|
|
3644
|
+
const url = await promptInput('Base URL of running app', 'http://localhost:3000');
|
|
3645
|
+
const flow = await promptSelect('Flow to test', [
|
|
3646
|
+
{ name: 'Authentication Flow', value: 'auth' },
|
|
3647
|
+
{ name: 'Checkout Flow', value: 'checkout' },
|
|
3648
|
+
{ name: 'Dashboard Flow', value: 'dashboard' },
|
|
3649
|
+
]);
|
|
3650
|
+
const mode = await promptSelect('Mode', [
|
|
3651
|
+
{ name: 'Generate test only', value: 'generate' },
|
|
3652
|
+
{ name: 'Generate and run', value: 'run' },
|
|
3653
|
+
{ name: 'Record user actions', value: 'record' },
|
|
3654
|
+
]);
|
|
3655
|
+
console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail reality --url "${url}" --flow ${flow}${mode === 'run' ? ' --run' : mode === 'record' ? ' --record' : ''}`)}\n`);
|
|
3656
|
+
printLogo();
|
|
3657
|
+
console.log(`\n${c.bold('🌐 REALITY MODE')}\n`);
|
|
3658
|
+
// Check dependencies and install if needed
|
|
3659
|
+
const { checkPlaywrightDependencies } = require('./reality/reality-runner');
|
|
3660
|
+
const depCheck = checkPlaywrightDependencies(projectPath);
|
|
3661
|
+
if (!depCheck.playwrightInstalled || !depCheck.browsersInstalled) {
|
|
3662
|
+
console.log(` ${styles.brightYellow}${icons.warning} Playwright dependencies missing${styles.reset}`);
|
|
3663
|
+
console.log('');
|
|
3664
|
+
const shouldInstall = await promptConfirm('Install Playwright dependencies automatically?', true);
|
|
3665
|
+
if (shouldInstall) {
|
|
3666
|
+
const installResult = await installPlaywrightDependencies(projectPath);
|
|
3667
|
+
if (!installResult.success) {
|
|
3668
|
+
console.log(` ${styles.brightRed}${icons.error} Failed to install: ${installResult.error}${styles.reset}`);
|
|
3669
|
+
console.log('');
|
|
3670
|
+
console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
|
|
3671
|
+
depCheck.installCommands.forEach(cmd => {
|
|
3672
|
+
console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
|
|
3673
|
+
});
|
|
3674
|
+
console.log('');
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
else {
|
|
3679
|
+
console.log(` ${styles.dim}Installation skipped. Run manually when ready.${styles.reset}`);
|
|
3680
|
+
console.log('');
|
|
3681
|
+
continue;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
// Execute reality mode based on selection
|
|
3685
|
+
const { spawn } = require('child_process');
|
|
3686
|
+
const args = ['reality', '--url', url, '--flow', flow];
|
|
3687
|
+
if (mode === 'run')
|
|
3688
|
+
args.push('--run');
|
|
3689
|
+
if (mode === 'record')
|
|
3690
|
+
args.push('--record');
|
|
3691
|
+
const realityProc = spawn('guardrail', args, {
|
|
3692
|
+
stdio: 'inherit',
|
|
3693
|
+
shell: process.platform === 'win32',
|
|
3694
|
+
cwd: projectPath
|
|
3695
|
+
});
|
|
3696
|
+
realityProc.on('close', (code) => {
|
|
3697
|
+
if (code === 0) {
|
|
3698
|
+
console.log(`\n ${styles.brightGreen}${icons.success} Reality mode completed${styles.reset}`);
|
|
3699
|
+
}
|
|
3700
|
+
else {
|
|
3701
|
+
console.log(`\n ${styles.brightRed}${icons.error} Reality mode failed${styles.reset}`);
|
|
3702
|
+
}
|
|
3703
|
+
});
|
|
3704
|
+
continue;
|
|
3705
|
+
}
|
|
3706
|
+
if (action === 'ship') {
|
|
3707
|
+
requireAuth();
|
|
3708
|
+
const baseline = await promptConfirm('Use baseline file?', false);
|
|
3709
|
+
const output = await promptConfirm('Generate ship report?', true);
|
|
3710
|
+
const outputPath = output ? defaultReportPath(projectPath, 'ship', 'json') : undefined;
|
|
3711
|
+
console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail ship -p "${projectPath}"${baseline ? ' --baseline .guardrail/baseline.json' : ''}${outputPath ? ` --output "${outputPath}"` : ''}`)}\n`);
|
|
3712
|
+
printLogo();
|
|
3713
|
+
console.log(`\n${c.bold('🚀 SHIP CHECK')}\n`);
|
|
3714
|
+
// Import ship functionality
|
|
3715
|
+
const { runShipCheck } = require('guardrail-ship');
|
|
3716
|
+
try {
|
|
3717
|
+
const shipResult = await runShipCheck(projectPath, {
|
|
3718
|
+
baseline: baseline ? '.guardrail/baseline.json' : undefined,
|
|
3719
|
+
output: outputPath
|
|
3720
|
+
});
|
|
3721
|
+
if (shipResult.verdict === 'ship') {
|
|
3722
|
+
console.log(` ${styles.brightGreen}${icons.success} Ready to ship!${styles.reset}`);
|
|
3723
|
+
}
|
|
3724
|
+
else {
|
|
3725
|
+
console.log(` ${styles.brightYellow}${icons.warning} Issues need to be addressed before shipping${styles.reset}`);
|
|
3726
|
+
}
|
|
3727
|
+
if (outputPath) {
|
|
3728
|
+
console.log(` ${styles.dim}Report saved to ${outputPath}${styles.reset}`);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
catch (error) {
|
|
3732
|
+
console.log(` ${styles.brightRed}${icons.error} Ship check failed: ${error.message}${styles.reset}`);
|
|
3733
|
+
}
|
|
3734
|
+
console.log('');
|
|
3735
|
+
continue;
|
|
3736
|
+
}
|
|
3737
|
+
if (action === 'init') {
|
|
3738
|
+
const template = await promptSelect('Configuration template', [
|
|
3739
|
+
{ name: 'Startup - Fast, minimal setup', value: 'startup' },
|
|
3740
|
+
{ name: 'Enterprise - Strict, compliant', value: 'enterprise' },
|
|
3741
|
+
{ name: 'OSS - Supply chain focus', value: 'oss' },
|
|
3742
|
+
]);
|
|
3743
|
+
const setupCI = await promptConfirm('Setup CI/CD integration?', false);
|
|
3744
|
+
const setupHooks = await promptConfirm('Install git hooks?', false);
|
|
3745
|
+
console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail init -p "${projectPath}" --template ${template}${setupCI ? ' --ci' : ''}${setupHooks ? ' --hooks' : ''}`)}\n`);
|
|
3746
|
+
printLogo();
|
|
3747
|
+
console.log(`\n${c.bold('🔧 INITIALIZING PROJECT')}\n`);
|
|
3748
|
+
try {
|
|
3749
|
+
await initProject(projectPath, {
|
|
3750
|
+
template,
|
|
3751
|
+
ci: setupCI,
|
|
3752
|
+
hooks: setupHooks,
|
|
3753
|
+
interactive: true
|
|
3754
|
+
});
|
|
3755
|
+
console.log(` ${styles.brightGreen}${icons.success} Project initialized successfully${styles.reset}`);
|
|
3756
|
+
}
|
|
3757
|
+
catch (error) {
|
|
3758
|
+
console.log(` ${styles.brightRed}${icons.error} Initialization failed: ${error.message}${styles.reset}`);
|
|
3759
|
+
}
|
|
3760
|
+
console.log('');
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3322
3763
|
}
|
|
3323
3764
|
}
|
|
3324
3765
|
// Register cache management commands
|