lockhawk 0.2.5 → 0.2.7
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 +77 -64
- package/dist/chunk-IJMHOVBV.js +3 -0
- package/dist/chunk-OIXS6N3H.js +10 -0
- package/dist/db-FFGMJJW7.js +9 -0
- package/dist/index.js +2 -291
- package/dist/report-O5PRSFAL.js +8 -0
- package/dist/report-shell.html +58 -0
- package/dist/scan-X3WQY53T.js +9 -0
- package/dist/serve-WIIUVGR4.js +8 -0
- package/package.json +13 -6
- package/dist/db-3KKJNIY5.js +0 -66
- package/dist/db-3KKJNIY5.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/db-3KKJNIY5.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/commands/db.ts
|
|
4
|
-
import ora from "ora";
|
|
5
|
-
import {
|
|
6
|
-
offlineDbDir,
|
|
7
|
-
readOfflineMeta,
|
|
8
|
-
resolveCacheDir,
|
|
9
|
-
updateOfflineDatabase
|
|
10
|
-
} from "@lockhawk/core";
|
|
11
|
-
async function runDbUpdate(opts) {
|
|
12
|
-
const cacheDir = resolveCacheDir(opts.cacheDir);
|
|
13
|
-
const useSpinner = Boolean(process.stderr.isTTY) && !process.env.CI;
|
|
14
|
-
const spinner = useSpinner ? ora({ stream: process.stderr, text: "Updating database\u2026" }).start() : void 0;
|
|
15
|
-
try {
|
|
16
|
-
const { status, meta } = await updateOfflineDatabase({
|
|
17
|
-
cacheDir,
|
|
18
|
-
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19
|
-
force: opts.force,
|
|
20
|
-
onProgress: (message) => {
|
|
21
|
-
if (spinner) spinner.text = message;
|
|
22
|
-
else process.stderr.write(`${message}
|
|
23
|
-
`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
const summary = `${status === "updated" ? "Updated" : "Already up to date"} \u2014 ${meta.recordCount} advisories across ${meta.packageCount} packages.`;
|
|
27
|
-
if (spinner) spinner.succeed(summary);
|
|
28
|
-
else process.stderr.write(`${summary}
|
|
29
|
-
`);
|
|
30
|
-
} catch (err) {
|
|
31
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
-
if (spinner) spinner.fail(`Database update failed: ${message}`);
|
|
33
|
-
else process.stderr.write(`Database update failed: ${message}
|
|
34
|
-
`);
|
|
35
|
-
process.exitCode = 3;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function runDbStatus(opts) {
|
|
39
|
-
const cacheDir = resolveCacheDir(opts.cacheDir);
|
|
40
|
-
const meta = await readOfflineMeta(cacheDir);
|
|
41
|
-
if (!meta) {
|
|
42
|
-
process.stdout.write("No offline database found. Run `lockhawk db update` to create one.\n");
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const ageHours = Math.round((Date.now() - Date.parse(meta.lastUpdated)) / 36e5);
|
|
46
|
-
process.stdout.write(
|
|
47
|
-
[
|
|
48
|
-
`Ecosystem: ${meta.ecosystem}`,
|
|
49
|
-
`Source: ${meta.source}`,
|
|
50
|
-
`Advisories: ${meta.recordCount}`,
|
|
51
|
-
`Packages: ${meta.packageCount}`,
|
|
52
|
-
`Last updated: ${meta.lastUpdated} (${ageHours}h ago)`,
|
|
53
|
-
`Location: ${offlineDbDir(cacheDir)}`
|
|
54
|
-
].join("\n") + "\n"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
function runDbPath(opts) {
|
|
58
|
-
process.stdout.write(`${offlineDbDir(resolveCacheDir(opts.cacheDir))}
|
|
59
|
-
`);
|
|
60
|
-
}
|
|
61
|
-
export {
|
|
62
|
-
runDbPath,
|
|
63
|
-
runDbStatus,
|
|
64
|
-
runDbUpdate
|
|
65
|
-
};
|
|
66
|
-
//# sourceMappingURL=db-3KKJNIY5.js.map
|
package/dist/db-3KKJNIY5.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/db.ts"],"sourcesContent":["import ora from 'ora';\nimport {\n offlineDbDir,\n readOfflineMeta,\n resolveCacheDir,\n updateOfflineDatabase,\n} from '@lockhawk/core';\n\ninterface DbOptions {\n cacheDir?: string;\n force?: boolean;\n}\n\n/** `db update` — download/refresh the offline OSV database. */\nexport async function runDbUpdate(opts: DbOptions): Promise<void> {\n const cacheDir = resolveCacheDir(opts.cacheDir);\n const useSpinner = Boolean(process.stderr.isTTY) && !process.env.CI;\n const spinner = useSpinner\n ? ora({ stream: process.stderr, text: 'Updating database…' }).start()\n : undefined;\n try {\n const { status, meta } = await updateOfflineDatabase({\n cacheDir,\n nowIso: new Date().toISOString(),\n force: opts.force,\n onProgress: (message) => {\n if (spinner) spinner.text = message;\n else process.stderr.write(`${message}\\n`);\n },\n });\n const summary = `${status === 'updated' ? 'Updated' : 'Already up to date'} — ${meta.recordCount} advisories across ${meta.packageCount} packages.`;\n if (spinner) spinner.succeed(summary);\n else process.stderr.write(`${summary}\\n`);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (spinner) spinner.fail(`Database update failed: ${message}`);\n else process.stderr.write(`Database update failed: ${message}\\n`);\n process.exitCode = 3;\n }\n}\n\n/** `db status` — report freshness of the offline database. */\nexport async function runDbStatus(opts: DbOptions): Promise<void> {\n const cacheDir = resolveCacheDir(opts.cacheDir);\n const meta = await readOfflineMeta(cacheDir);\n if (!meta) {\n process.stdout.write('No offline database found. Run `lockhawk db update` to create one.\\n');\n return;\n }\n const ageHours = Math.round((Date.now() - Date.parse(meta.lastUpdated)) / 3_600_000);\n process.stdout.write(\n [\n `Ecosystem: ${meta.ecosystem}`,\n `Source: ${meta.source}`,\n `Advisories: ${meta.recordCount}`,\n `Packages: ${meta.packageCount}`,\n `Last updated: ${meta.lastUpdated} (${ageHours}h ago)`,\n `Location: ${offlineDbDir(cacheDir)}`,\n ].join('\\n') + '\\n',\n );\n}\n\n/** `db path` — print the offline database directory. */\nexport function runDbPath(opts: DbOptions): void {\n process.stdout.write(`${offlineDbDir(resolveCacheDir(opts.cacheDir))}\\n`);\n}\n"],"mappings":";;;AAAA,OAAO,SAAS;AAChB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,eAAsB,YAAY,MAAgC;AAChE,QAAM,WAAW,gBAAgB,KAAK,QAAQ;AAC9C,QAAM,aAAa,QAAQ,QAAQ,OAAO,KAAK,KAAK,CAAC,QAAQ,IAAI;AACjE,QAAM,UAAU,aACZ,IAAI,EAAE,QAAQ,QAAQ,QAAQ,MAAM,0BAAqB,CAAC,EAAE,MAAM,IAClE;AACJ,MAAI;AACF,UAAM,EAAE,QAAQ,KAAK,IAAI,MAAM,sBAAsB;AAAA,MACnD;AAAA,MACA,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC/B,OAAO,KAAK;AAAA,MACZ,YAAY,CAAC,YAAY;AACvB,YAAI,QAAS,SAAQ,OAAO;AAAA,YACvB,SAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,MAC1C;AAAA,IACF,CAAC;AACD,UAAM,UAAU,GAAG,WAAW,YAAY,YAAY,oBAAoB,WAAM,KAAK,WAAW,sBAAsB,KAAK,YAAY;AACvI,QAAI,QAAS,SAAQ,QAAQ,OAAO;AAAA,QAC/B,SAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,EAC1C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,QAAI,QAAS,SAAQ,KAAK,2BAA2B,OAAO,EAAE;AAAA,QACzD,SAAQ,OAAO,MAAM,2BAA2B,OAAO;AAAA,CAAI;AAChE,YAAQ,WAAW;AAAA,EACrB;AACF;AAGA,eAAsB,YAAY,MAAgC;AAChE,QAAM,WAAW,gBAAgB,KAAK,QAAQ;AAC9C,QAAM,OAAO,MAAM,gBAAgB,QAAQ;AAC3C,MAAI,CAAC,MAAM;AACT,YAAQ,OAAO,MAAM,sEAAsE;AAC3F;AAAA,EACF;AACA,QAAM,WAAW,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,WAAW,KAAK,IAAS;AACnF,UAAQ,OAAO;AAAA,IACb;AAAA,MACE,iBAAiB,KAAK,SAAS;AAAA,MAC/B,iBAAiB,KAAK,MAAM;AAAA,MAC5B,iBAAiB,KAAK,WAAW;AAAA,MACjC,iBAAiB,KAAK,YAAY;AAAA,MAClC,iBAAiB,KAAK,WAAW,KAAK,QAAQ;AAAA,MAC9C,iBAAiB,aAAa,QAAQ,CAAC;AAAA,IACzC,EAAE,KAAK,IAAI,IAAI;AAAA,EACjB;AACF;AAGO,SAAS,UAAU,MAAuB;AAC/C,UAAQ,OAAO,MAAM,GAAG,aAAa,gBAAgB,KAAK,QAAQ,CAAC,CAAC;AAAA,CAAI;AAC1E;","names":[]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/scan.ts","../src/report/render.ts","../src/report/table.ts","../src/config.ts","../src/commands/report.ts","../src/commands/serve.ts"],"sourcesContent":["import { Command, Option } from 'commander';\nimport { runScan } from './commands/scan.js';\nimport type { ScanCliOptions } from './commands/scan.js';\nimport type { Format } from './report/render.js';\nimport { runReport } from './commands/report.js';\nimport { runServe } from './commands/serve.js';\n\nconst program = new Command();\n\nprogram\n .name('lockhawk')\n .description(\n 'Fast, free, accurate npm dependency vulnerability scanner (local + CI/CD), powered by OSV.dev',\n )\n .version('0.1.0');\n\nconst severityChoices = ['none', 'low', 'medium', 'high', 'critical'];\n\nprogram\n .command('scan', { isDefault: true })\n .description('Scan a project for vulnerable dependencies')\n .argument('[path]', 'project directory to scan', '.')\n .addOption(\n new Option('-f, --format <format>', 'output format')\n .choices(['table', 'json', 'sarif', 'html', 'junit'])\n .default('table'),\n )\n .option('-o, --output <file>', 'write the report to a file instead of stdout')\n .addOption(\n new Option('--severity-threshold <level>', 'minimum severity to report').choices(\n severityChoices,\n ),\n )\n .addOption(\n new Option('--fail-on <level>', 'minimum severity that causes a non-zero exit')\n .choices(severityChoices)\n .default('high'),\n )\n .option('--offline', 'use only the local offline database (no network)')\n .option('--online', 'force live OSV.dev queries')\n .option('--strict-network', 'fail the scan on network errors instead of degrading gracefully')\n .option('--prod-only', 'ignore dev dependencies')\n .option('--ignore <ids...>', 'advisory ids to suppress')\n .option('--ignore-file <path>', 'path to a .lockhawkignore file')\n .option('--cache-dir <dir>', 'override the cache directory')\n .option('--cache-ttl <hours>', 'cache freshness window in hours', (v) => Number(v))\n .option('--no-cache', 'bypass the on-disk cache')\n .option('--concurrency <n>', 'max concurrent OSV requests', (v) => Number(v))\n .action((path: string, opts: ScanCliOptions & { format: Format }) => runScan(path, opts));\n\nconst db = program.command('db').description('Manage the offline OSV database');\ndb.command('update')\n .description('Download or refresh the offline npm advisory database')\n .option('--cache-dir <dir>', 'override the cache directory')\n .option('--force', 'ignore conditional caching and re-download')\n .action((opts) => import('./commands/db.js').then((m) => m.runDbUpdate(opts)));\ndb.command('status')\n .description('Show offline database freshness')\n .option('--cache-dir <dir>', 'override the cache directory')\n .action((opts) => import('./commands/db.js').then((m) => m.runDbStatus(opts)));\ndb.command('path')\n .description('Print the offline database directory')\n .option('--cache-dir <dir>', 'override the cache directory')\n .action((opts) => import('./commands/db.js').then((m) => m.runDbPath(opts)));\n\nprogram\n .command('report')\n .description('Re-render a saved JSON result into another format')\n .requiredOption('-i, --input <file>', 'path to a JSON scan result')\n .addOption(\n new Option('-f, --format <format>', 'output format')\n .choices(['table', 'json', 'sarif', 'html', 'junit'])\n .default('html'),\n )\n .option('-o, --output <file>', 'write the report to a file instead of stdout')\n .action((opts) => runReport(opts));\n\nprogram\n .command('serve')\n .description('Run a scan and open an interactive dashboard locally')\n .argument('[path]', 'project directory to scan', '.')\n .option('-i, --input <file>', 'serve a saved JSON result instead of scanning')\n .option('-p, --port <port>', 'port to listen on', (v) => Number(v), 7777)\n .option('--no-open', 'do not open the browser automatically')\n .option('--offline', 'use only the local offline database')\n .option('--online', 'force live OSV.dev queries')\n .action((path: string, opts) => runServe(path, opts));\n\nprogram.parseAsync().catch((err) => {\n process.stderr.write(`lockhawk: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 3;\n});\n","import { writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport ora from 'ora';\nimport { LockfileError, scan, shouldFail } from '@lockhawk/core';\nimport type { ScanOptions, ScanResult, Severity, SourceMode } from '@lockhawk/core';\nimport { renderResult } from '../report/render.js';\nimport type { Format } from '../report/render.js';\nimport { loadFileConfig, readIgnoreFile } from '../config.js';\n\nexport interface ScanCliOptions {\n format?: Format;\n output?: string;\n severityThreshold?: Severity;\n failOn?: Severity;\n offline?: boolean;\n online?: boolean;\n strictNetwork?: boolean;\n prodOnly?: boolean;\n ignore?: string[];\n ignoreFile?: string;\n cacheDir?: string;\n cacheTtl?: number;\n cache?: boolean; // commander sets false for --no-cache\n concurrency?: number;\n}\n\n/** Stable exit-code contract (documented for CI). */\nexport const EXIT = { OK: 0, FINDINGS: 1, USAGE: 2, INTERNAL: 3, NETWORK: 4 } as const;\n\nexport async function runScan(pathArg: string, cli: ScanCliOptions): Promise<void> {\n const dir = resolve(pathArg || '.');\n const fileConfig = loadFileConfig(dir);\n\n const mode: SourceMode = cli.offline\n ? 'offline'\n : cli.online\n ? 'online'\n : (fileConfig.mode ?? 'auto');\n const format: Format = cli.format ?? 'table';\n const failOn: Severity = cli.failOn ?? fileConfig.failOn ?? 'high';\n\n const ignore = [\n ...(cli.ignore ?? []),\n ...(fileConfig.ignore ?? []),\n ...readIgnoreFile(cli.ignoreFile ?? join(dir, '.lockhawkignore')),\n ];\n\n const useSpinner = format === 'table' && Boolean(process.stderr.isTTY) && !process.env.CI;\n const spinner = useSpinner\n ? ora({ stream: process.stderr, text: 'Scanning…' }).start()\n : undefined;\n\n const options: ScanOptions = {\n path: dir,\n mode,\n cacheDir: cli.cacheDir,\n cacheTtlHours: cli.cacheTtl,\n noCache: cli.cache === false,\n concurrency: cli.concurrency,\n prodOnly: cli.prodOnly ?? fileConfig.prodOnly,\n ignore,\n strictNetwork: cli.strictNetwork,\n severityThreshold: cli.severityThreshold ?? fileConfig.severityThreshold,\n onProgress: (message) => {\n if (spinner) spinner.text = message;\n },\n };\n\n let result: ScanResult;\n try {\n result = await scan(options);\n } catch (err) {\n spinner?.stop();\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`lockhawk: ${message}\\n`);\n if (err instanceof LockfileError) process.exitCode = EXIT.USAGE;\n else process.exitCode = cli.strictNetwork ? EXIT.NETWORK : EXIT.INTERNAL;\n return;\n }\n spinner?.stop();\n\n const rendered = await renderResult(result, format);\n if (cli.output) {\n writeFileSync(cli.output, rendered.endsWith('\\n') ? rendered : `${rendered}\\n`);\n process.stderr.write(`Report written to ${cli.output}\\n${briefSummary(result)}\\n`);\n } else {\n process.stdout.write(`${rendered}\\n`);\n }\n\n process.exitCode = shouldFail(result.summary, failOn) ? EXIT.FINDINGS : EXIT.OK;\n}\n\nfunction briefSummary(result: ScanResult): string {\n const s = result.summary;\n if (s.total === 0) return '✓ No known vulnerabilities found.';\n const parts = (['critical', 'high', 'medium', 'low', 'unknown'] as const)\n .filter((level) => s[level] > 0)\n .map((level) => `${s[level]} ${level}`);\n return `Found ${s.total} ${s.total === 1 ? 'vulnerability' : 'vulnerabilities'} (${parts.join(', ')}).`;\n}\n","import { toHtml, toJson, toJunit, toSarifString } from '@lockhawk/core';\nimport type { ScanResult } from '@lockhawk/core';\nimport { renderTable } from './table.js';\n\nexport type Format = 'table' | 'json' | 'sarif' | 'html' | 'junit';\n\n/** Render a scan result into the requested output format. */\nexport async function renderResult(result: ScanResult, format: Format): Promise<string> {\n switch (format) {\n case 'json':\n return toJson(result);\n case 'sarif':\n return toSarifString(result);\n case 'junit':\n return toJunit(result);\n case 'html':\n return toHtml(result);\n default:\n return renderTable(result);\n }\n}\n","import chalk from 'chalk';\nimport Table from 'cli-table3';\nimport type { ScanResult, Severity } from '@lockhawk/core';\n\nconst paint: Record<Severity, (s: string) => string> = {\n critical: (s) => chalk.bgRed.white.bold(` ${s} `),\n high: (s) => chalk.hex('#d9822b').bold(s),\n medium: (s) => chalk.yellow(s),\n low: (s) => chalk.blue(s),\n unknown: (s) => chalk.gray(s),\n none: (s) => chalk.green(s),\n};\n\n/** Render a scan result as a colorized terminal report. */\nexport function renderTable(result: ScanResult): string {\n const lines: string[] = [];\n const { summary, target, stats, database } = result;\n\n lines.push(\n chalk.bold(`\\nlockhawk`) +\n chalk.dim(\n ` · ${target.root.name}${target.root.version ? `@${target.root.version}` : ''} · ${target.manager} · ${stats.totalPackages} packages`,\n ),\n );\n\n for (const warning of database.warnings) lines.push(chalk.yellow(`⚠ ${warning}`));\n\n if (result.findings.length === 0) {\n lines.push(chalk.green.bold('\\n✓ No known vulnerabilities found.\\n'));\n lines.push(chalk.dim(footer(result)));\n return lines.join('\\n');\n }\n\n const table = new Table({\n head: ['Severity', 'Package', 'Advisory', 'Fixed in', 'Path'].map((h) => chalk.dim(h)),\n style: { head: [], border: [] },\n colWidths: [12, 26, 22, 12, 40],\n wordWrap: true,\n });\n\n for (const f of result.findings) {\n const sev = f.severity;\n const sevText = `${sev.level}${sev.score ? ` ${sev.score}` : ''}`;\n const path = f.dependencyPaths[0]?.join(' › ') ?? f.packageName;\n table.push([\n paint[sev.level](sevText),\n `${f.packageName}\\n${chalk.dim(f.version)}${f.direct ? chalk.dim(' (direct)') : ''}`,\n f.id,\n f.fixedVersions[0] ?? chalk.dim('none'),\n chalk.dim(path),\n ]);\n }\n\n lines.push('');\n lines.push(table.toString());\n lines.push('');\n lines.push(summaryLine(summary));\n lines.push(chalk.dim(footer(result)));\n return lines.join('\\n');\n}\n\nfunction summaryLine(summary: ScanResult['summary']): string {\n const parts: string[] = [];\n if (summary.critical) parts.push(paint.critical(`${summary.critical} critical`));\n if (summary.high) parts.push(paint.high(`${summary.high} high`));\n if (summary.medium) parts.push(paint.medium(`${summary.medium} medium`));\n if (summary.low) parts.push(paint.low(`${summary.low} low`));\n if (summary.unknown) parts.push(paint.unknown(`${summary.unknown} unknown`));\n return (\n chalk.bold(\n `Found ${summary.total} ${summary.total === 1 ? 'vulnerability' : 'vulnerabilities'}`,\n ) +\n (parts.length ? ` (${parts.join(', ')})` : '') +\n chalk.dim(` · ${summary.fixable} fixable`)\n );\n}\n\nfunction footer(result: ScanResult): string {\n const age = result.database.ageHours !== undefined ? `, ${result.database.ageHours}h old` : '';\n return `\\nData from OSV.dev · database: ${result.database.source}${age} · scanned in ${result.stats.durationMs ?? 0}ms`;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { cosmiconfigSync } from 'cosmiconfig';\nimport type { Severity, SourceMode } from '@lockhawk/core';\n\n/** Configuration resolvable from a config file (`.lockhawkrc`, `package.json#lockhawk`, …). */\nexport interface FileConfig {\n mode?: SourceMode;\n failOn?: Severity;\n severityThreshold?: Severity;\n prodOnly?: boolean;\n ignore?: string[];\n concurrency?: number;\n cacheTtlHours?: number;\n}\n\n/** Load a config file by searching upward from `cwd`. Returns `{}` if none found. */\nexport function loadFileConfig(cwd: string): FileConfig {\n try {\n const result = cosmiconfigSync('lockhawk').search(cwd);\n return (result?.config as FileConfig) ?? {};\n } catch {\n return {};\n }\n}\n\n/**\n * Parse a `.lockhawkignore` file. Each non-comment line is an advisory id with an\n * optional expiry date (`GHSA-xxxx 2026-12-31`); expired entries are dropped so\n * suppressions don't silently outlive their review.\n */\nexport function readIgnoreFile(path: string, now: Date = new Date()): string[] {\n if (!existsSync(path)) return [];\n const ids: string[] = [];\n for (const raw of readFileSync(path, 'utf8').split(/\\r?\\n/)) {\n const line = raw.replace(/#.*$/, '').trim();\n if (!line) continue;\n const [id, expiry] = line.split(/\\s+/);\n if (!id) continue;\n if (expiry) {\n const when = Date.parse(expiry);\n if (!Number.isNaN(when) && when < now.getTime()) continue; // expired → no longer ignored\n }\n ids.push(id);\n }\n return ids;\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport type { ScanResult } from '@lockhawk/core';\nimport { renderResult } from '../report/render.js';\nimport type { Format } from '../report/render.js';\n\ninterface ReportOptions {\n input: string;\n format?: Format;\n output?: string;\n}\n\n/** `report` — re-render a previously saved JSON result into another format. */\nexport async function runReport(opts: ReportOptions): Promise<void> {\n let result: ScanResult;\n try {\n result = JSON.parse(readFileSync(opts.input, 'utf8')) as ScanResult;\n } catch (err) {\n process.stderr.write(\n `lockhawk: could not read ${opts.input}: ${err instanceof Error ? err.message : err}\\n`,\n );\n process.exitCode = 2;\n return;\n }\n\n const rendered = await renderResult(result, opts.format ?? 'html');\n if (opts.output) {\n writeFileSync(opts.output, rendered.endsWith('\\n') ? rendered : `${rendered}\\n`);\n process.stderr.write(`Report written to ${opts.output}\\n`);\n } else {\n process.stdout.write(`${rendered}\\n`);\n }\n}\n","import { createServer } from 'node:http';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport open from 'open';\nimport { scan, toHtml, toJson } from '@lockhawk/core';\nimport type { ScanResult, SourceMode } from '@lockhawk/core';\n\ninterface ServeOptions {\n input?: string;\n port?: number;\n open?: boolean; // commander sets false for --no-open\n offline?: boolean;\n online?: boolean;\n}\n\n/** `serve` — run a scan (or load a saved result) and serve the dashboard locally. */\nexport async function runServe(pathArg: string, opts: ServeOptions): Promise<void> {\n let result: ScanResult;\n if (opts.input) {\n result = JSON.parse(readFileSync(opts.input, 'utf8')) as ScanResult;\n } else {\n const mode: SourceMode = opts.offline ? 'offline' : opts.online ? 'online' : 'auto';\n process.stderr.write('Scanning…\\n');\n result = await scan({ path: resolve(pathArg || '.'), mode });\n }\n\n const html = toHtml(result);\n const json = toJson(result);\n\n const server = createServer(createDashboardHandler(html, json));\n\n const port = opts.port ?? 7777;\n // Bind to loopback only — the dashboard embeds the full dependency tree and\n // scan result, which should not be exposed to the local network.\n server.listen(port, '127.0.0.1', () => {\n const url = `http://localhost:${port}`;\n process.stderr.write(\n `\\nlockhawk dashboard running at ${url}\\n${summaryLine(result)}\\nPress Ctrl+C to stop.\\n`,\n );\n if (opts.open !== false) void open(url).catch(() => undefined);\n });\n}\n\n/**\n * Build the request handler for the dashboard server.\n *\n * Every `serve` run rescans and re-renders the dashboard from scratch, but it is\n * always served from the same `http://localhost:<port>/` URL. Without an explicit\n * caching policy a browser treats the response as cacheable and, on a re-run,\n * shows the *previously* rendered dashboard instead of fetching the new one. We\n * send `Cache-Control: no-store` so the browser always pulls the current report.\n */\nexport function createDashboardHandler(\n html: string,\n json: string,\n): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n res.setHeader('cache-control', 'no-store, max-age=0');\n if (req.url && req.url.startsWith('/api/result')) {\n res.setHeader('content-type', 'application/json');\n res.end(json);\n return;\n }\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(html);\n };\n}\n\nfunction summaryLine(result: ScanResult): string {\n const s = result.summary;\n return s.total === 0\n ? '✓ No known vulnerabilities found.'\n : `${s.total} findings — ${s.critical} critical, ${s.high} high, ${s.medium} medium, ${s.low} low.`;\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","readFileSync","writeFileSync","readFileSync","resolve","scan","toHtml","toJson","summaryLine"]}
|