UNPKG

license-kit

Version:

Aggregate license notes of OSS libraries used in your Node.js project, analyze & visualize OSS licenses with AI-turbocharged tooling

174 lines (173 loc) 7.94 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = visualizeCommandSetup; const node_fs_1 = require("node:fs"); const node_path_1 = __importDefault(require("node:path")); const node_url_1 = require("node:url"); const commander_1 = require("commander"); const express_1 = __importDefault(require("express")); const helmet_1 = __importDefault(require("helmet")); const next_1 = __importDefault(require("next")); const open_1 = __importDefault(require("open")); const signale_1 = require("signale"); const generateLicensesMapping_1 = require("../logic/generateLicensesMapping"); const commandUtils_1 = require("../utils/commandUtils"); const projectUtils_1 = require("../utils/projectUtils"); const isDev = !!process.env.NODE_ENV && process.env.NODE_ENV !== 'production'; const visualizerSignale = new signale_1.Signale({ scope: 'visualize' }); const sseSignale = new signale_1.Signale({ scope: 'SSE' }); const apiSignale = new signale_1.Signale({ scope: 'API' }); const reportSignale = new signale_1.Signale({ scope: 'report' }); function visualizeCommandSetup(program) { return (0, commandUtils_1.curryCommonScanOptions)(program .command('visualize') .description('Launches a local server providing a web license graph visualizer & analyzer app: summarizes the dependency graph state, shows an interactive graph of licenses with possibility to select a subgraph, provides browser built-in AI-turbocharged summary of the dependency graph.') .option('--port [port]', 'Port on which to launch the app', (value) => { const parsedValue = parseInt(value, 10); if (isNaN(parsedValue)) { throw new commander_1.InvalidArgumentError('Not a number.'); } return parsedValue; }, 8094) .option('--h, --host [host]', 'Host on which to launch the app', 'localhost') .option('--a, --auto-open [open]', 'Host on which to launch the app', (value) => value === 'true' || value === '1', true) .option('--root [path]', 'Path to the root of your project', '.')).action(async (options) => { (0, commandUtils_1.validateCommonScanOptions)(options); const expressApp = (0, express_1.default)(); expressApp.use((0, helmet_1.default)({ contentSecurityPolicy: false, })); const eventClients = new Set(); /** Logic - begin */ let lastScanPackageJsonChecksum = null; let lastScanResult = null; function updateLastScanResultIfNeeded() { let result; const currentPackageJsonChecksum = (0, projectUtils_1.getPackageLockChecksum)(options); if (lastScanResult && lastScanPackageJsonChecksum === currentPackageJsonChecksum) { result = lastScanResult; return false; } else { if (lastScanResult) { reportSignale.log(`Project's root ${node_path_1.default.basename((0, projectUtils_1.getLockfilePath)(options))} changed (new checksum: ${currentPackageJsonChecksum}), re-generating report...`); } else { reportSignale.log('Generating report for the first time, please stand by...'); } result = (0, generateLicensesMapping_1.generateLicensesMapping)(options); reportSignale.log('Report generated'); lastScanResult = result; lastScanPackageJsonChecksum = currentPackageJsonChecksum; return true; } } /** Logic - end */ /** API routes - begin */ expressApp.get('/api/report', (req, res) => { if (!lastScanResult) { updateLastScanResultIfNeeded(); } const { licenses, projectName } = lastScanResult; res.json({ report: licenses, projectName, }); }); expressApp.get('/api/events', (req, res) => { res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); res.flushHeaders(); eventClients.add(res); req.on('close', () => { res.end(); }); }); /** API routes - end */ /** Lockfile watcher - begin */ const lockfilePath = (0, projectUtils_1.getLockfilePath)(options); visualizerSignale.log(`Watching '${lockfilePath}' file for changes`); const packageJsonWatcher = (0, node_fs_1.watch)(lockfilePath, (event) => { if (event === 'change') { const didChange = updateLastScanResultIfNeeded(); if (didChange) { sseSignale.log('Sending report updates to clients over SSE'); eventClients.forEach((client) => { client.write(`event: message\ndata: ${JSON.stringify({ type: 'UPDATE', report: lastScanResult.licenses, projectName: lastScanResult.projectName, })}\n\n`); }); } } }); /** Lockfile watcher - end */ let server; await new Promise((resolve) => { server = expressApp.listen(options.port, options.host, async () => { apiSignale.log('Preparing GUI server...'); // Next app const visualizerNextAppDir = node_path_1.default.join(__dirname, '..', '..', ...(isDev ? ['..', 'visualizer'] : [])); const visualizerNextApp = (0, next_1.default)({ dev: isDev, dir: visualizerNextAppDir, customServer: true, httpServer: server, hostname: options.host, port: options.port, }); await visualizerNextApp.prepare(); const visualizerReqHandler = visualizerNextApp.getRequestHandler(); expressApp.use((req, res) => { visualizerReqHandler(req, res, (0, node_url_1.parse)(req.url, true)); }); apiSignale.log(`Server running at http://${options.host}:${options.port}\n`); // open the browser automatically if (options.autoOpen) { await (0, open_1.default)(`http://${options.host}:${options.port}`); } resolve(); }); }); const threadBlocker = (() => { let resolver; const promise = new Promise((resolve) => { resolver = resolve; }); return { resolve: resolver, promise, }; })(); function shutdown() { eventClients.forEach((client) => client.end()); server?.close(); packageJsonWatcher.close(); process.exit(0); } process.on('SIGINT', () => { threadBlocker.resolve(); shutdown(); }); process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', (inputBuff) => { const input = inputBuff.toString().trim().toLowerCase(); if (input === 'q' || input === '\u0003') { threadBlocker.resolve(); } }); visualizerSignale.log('Press "q" or ctrl+c to stop the server'); await threadBlocker.promise; visualizerSignale.log('Stopping the server...'); shutdown(); }); }