diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..610c2a54 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm test diff --git a/package-lock.json b/package-lock.json index 78506f18..1c769411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-select": "^5.7.3", "react-toastify": "^9.0.8", "react-tooltip": "^5.10.4", + "scroll-into-view-if-needed": "^3.1.0", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", "sqlite3": "^5.1.6", @@ -93,6 +94,7 @@ "eslint-plugin-css-import-order": "^1.1.0", "eslint-plugin-import": "^2.26.0", "fork-ts-checker-webpack-plugin": "^7.2.13", + "husky": "^8.0.3", "jest": "^29.6.1", "jest-environment-jsdom": "^29.6.2", "json-loader": "^0.5.7", @@ -9116,6 +9118,11 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -14154,6 +14161,21 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -25500,6 +25522,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index df40ec07..96a43f54 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "lint": "eslint --ext .ts,.tsx .", "test": "jest", "test:watch": "jest --watch", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "prepare": "husky install" }, "devDependencies": { "@electron-forge/cli": "^6.1.1", @@ -65,6 +66,7 @@ "eslint-plugin-css-import-order": "^1.1.0", "eslint-plugin-import": "^2.26.0", "fork-ts-checker-webpack-plugin": "^7.2.13", + "husky": "^8.0.3", "jest": "^29.6.1", "jest-environment-jsdom": "^29.6.2", "json-loader": "^0.5.7", @@ -118,6 +120,7 @@ "react-select": "^5.7.3", "react-toastify": "^9.0.8", "react-tooltip": "^5.10.4", + "scroll-into-view-if-needed": "^3.1.0", "sequelize": "^6.23.2", "sequelize-cli": "^6.5.1", "sqlite3": "^5.1.6", diff --git a/src/components/FileTree/FileTree.css b/src/components/FileTree/FileTree.css index 0209d71f..8803e721 100644 --- a/src/components/FileTree/FileTree.css +++ b/src/components/FileTree/FileTree.css @@ -2,7 +2,6 @@ padding: 15px; border: 0; min-height: 80vh !important; - /* border-right: 2px solid black; */ } .file-tree-loader { @@ -18,9 +17,7 @@ } .filetree-node-wrapper { - scroll-margin-left: 150px; - scroll-margin-top: 150px; - scroll-margin-bottom: 150px; + scroll-margin-left: 20px; } .rc-tree-child-tree { diff --git a/src/components/FileTree/FileTree.tsx b/src/components/FileTree/FileTree.tsx index 3d60fa22..d33874c7 100644 --- a/src/components/FileTree/FileTree.tsx +++ b/src/components/FileTree/FileTree.tsx @@ -1,7 +1,8 @@ import RcTree from "rc-tree"; import { Key } from "rc-tree/lib/interface"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useLayoutEffect, useState } from "react"; import { Element } from "react-scroll"; +import scrollIntoView from "scroll-into-view-if-needed"; import EllipticLoader from "../EllipticLoader"; import SwitcherIcon from "./SwitcherIcon"; @@ -10,38 +11,76 @@ import { FileDataNode } from "../../services/workbenchDB"; import "./FileTree.css"; +const FOCUS_ATTEMPT_DELAY = 500; + const FileTree = (props: React.HTMLProps) => { const { db, initialized, importedSqliteFilePath, currentPath, + startProcessing, + endProcessing, updateCurrentPath, } = useWorkbenchDB(); const [treeData, setTreeData] = useState(null); const [expandedKeys, setExpandedKeys] = useState([]); - useEffect(() => { + useLayoutEffect(() => { + if (currentPath.length === 0) return; + + // Show working indicator while the FileTree Node is being rendered and focused + startProcessing(); + setExpandedKeys((keys) => { return [...keys, currentPath.substring(0, currentPath.lastIndexOf("/"))]; }); - if (currentPath.length) { - setTimeout(() => { + + function scrollTreeNode(targetNode: HTMLElement) { + scrollIntoView(targetNode, { + scrollMode: "if-needed", + behavior: "smooth", + block: "center", + inline: "start", + }); + } + + // Timeout ensures that targetNode is accessed only after its rendered + let pendingScrollerTimeoutId: NodeJS.Timeout; + + const alreadyRenderedTargetNode = + document.getElementsByName(currentPath)[0]; + + if (alreadyRenderedTargetNode) { + // Immediate scroll possible + scrollTreeNode(alreadyRenderedTargetNode); + } else { + // Wait for target node to render + pendingScrollerTimeoutId = setTimeout(() => { const targetNode = document.getElementsByName(currentPath)[0]; + if (targetNode) { - targetNode.scrollIntoView({ - behavior: "smooth", - block: "nearest", - inline: "start", - }); + pendingScrollerTimeoutId = setTimeout(() => { + scrollTreeNode(targetNode); + }, FOCUS_ATTEMPT_DELAY); + + // Hide working indicator after the FileTree Node is focused + endProcessing(); } - }, 1500); + }); } + + return () => { + clearTimeout(pendingScrollerTimeoutId); + }; }, [currentPath]); useEffect(() => { - if (!initialized || !db || !importedSqliteFilePath) return; + if (!initialized || !db || !importedSqliteFilePath) { + setTreeData(null); + return; + } db.findAllJSTree().then((treeData) => { // Wrap with react-scroll wrapper @@ -98,10 +137,7 @@ const FileTree = (props: React.HTMLProps) => { selectedKeys={[currentPath]} onSelect={(keys, info) => { if (keys && keys[0]) { - selectPath( - keys[0].toString(), - (info.node as any as FileDataNode).type as PathType - ); + selectPath(keys[0].toString(), info.node.type as PathType); } }} motion={{ diff --git a/src/constants/IpcConnection.ts b/src/constants/IpcConnection.ts index c00b90ab..21309b48 100644 --- a/src/constants/IpcConnection.ts +++ b/src/constants/IpcConnection.ts @@ -5,33 +5,35 @@ export interface ErrorInfo { } export const OPEN_DIALOG_CHANNEL = { - JSON: 'open-json-file', - SQLITE_PATH_FOR_JSON: 'choose-sqlite-path-for-json-file', - SQLITE: 'open-sqlite-file', - SAVE_SQLITE: 'save-sqlite-file', -} + JSON: "open-json-file", + SQLITE_PATH_FOR_JSON: "choose-sqlite-path-for-json-file", + SQLITE: "open-sqlite-file", + SAVE_SQLITE: "save-sqlite-file", +}; export const IMPORT_REPLY_CHANNEL = { - JSON: 'import-json-reply', - SQLITE: 'import-sqlite-reply', -} + JSON: "import-json-reply", + SQLITE: "import-sqlite-reply", +}; export const SAVE_REPLY_CHANNEL = { - SQLITE: 'save-sqlite-reply', -} + SQLITE: "save-sqlite-reply", +}; export const UTIL_CHANNEL = { - SET_CURRENT_FILE_TITLE: 'set-current-file-title', -} + SET_CURRENT_FILE_TITLE: "set-current-file-title", + RESET_FILE_TITLE: "reset-current-file-title", + CLOSE_FILE: "close-file", +}; -export const NAVIGATION_CHANNEL = 'NAVIGATE_TO'; +export const NAVIGATION_CHANNEL = "NAVIGATE_TO"; export type NAVIGATION_CHANNEL_MESSAGE = string; export const GENERAL_ACTIONS = { - ZOOM_IN: 'zoom_in', - ZOOM_OUT: 'zoom_out', - ZOOM_RESET: 'zoom_reset', -} + ZOOM_IN: "zoom_in", + ZOOM_OUT: "zoom_out", + ZOOM_RESET: "zoom_reset", +}; export interface SQLITE_PATH_FOR_JSON_REQUEST_FORMAT { jsonFilePath: string; @@ -45,4 +47,4 @@ export interface SQLITE_IMPORT_REPLY_FORMAT { } export interface SQLITE_SAVE_REPLY_FORMAT { sqliteFilePath: string; -} \ No newline at end of file +} diff --git a/src/constants/general.ts b/src/constants/general.ts index 3f9471ae..7594aa46 100644 --- a/src/constants/general.ts +++ b/src/constants/general.ts @@ -1,3 +1,5 @@ import packageJson from "../../package.json"; +export const WORKBENCH_TITLE = "Scancode workbench"; + export const WORKBENCH_VERSION = packageJson.version; \ No newline at end of file diff --git a/src/contexts/dbContext.tsx b/src/contexts/dbContext.tsx index 24c0db94..935f58d4 100644 --- a/src/contexts/dbContext.tsx +++ b/src/contexts/dbContext.tsx @@ -42,6 +42,7 @@ interface WorkbenchContextProperties extends BasicValueState { processingQuery: boolean; startImport: () => void; abortImport: () => void; + closeFile: () => void; startProcessing: () => void; endProcessing: () => void; sqliteParser: (sqliteFilePath: string, preventNavigation?: boolean) => void; @@ -71,6 +72,7 @@ export const defaultWorkbenchContextValue: WorkbenchContextProperties = { updateLoadingStatus: () => null, startImport: () => null, abortImport: () => null, + closeFile: () => null, startProcessing: () => null, endProcessing: () => null, updateCurrentPath: () => null, @@ -113,6 +115,7 @@ export const WorkbenchDBProvider = ( const startImport = () => { updateLoadingStatus(0); + setProcessingQuery(false); setValue({ db: null, initialized: false, @@ -123,6 +126,19 @@ export const WorkbenchDBProvider = ( const abortImport = () => updateLoadingStatus(null); + const closeFile = () => { + updateLoadingStatus(null); + setProcessingQuery(false); + setValue({ + db: null, + initialized: false, + importedSqliteFilePath: null, + scanInfo: null, + }); + navigate(ROUTES.HOME); + ipcRenderer.send(UTIL_CHANNEL.RESET_FILE_TITLE); + }; + const updateWorkbenchDB = async (db: WorkbenchDB, sqliteFilePath: string) => { updateLoadingStatus(100); setValue({ @@ -440,6 +456,7 @@ export const WorkbenchDBProvider = ( } } ); + ipcRenderer.on(UTIL_CHANNEL.CLOSE_FILE, closeFile); // Remove all listeners on window unmount return () => { @@ -461,6 +478,7 @@ export const WorkbenchDBProvider = ( importJsonFile, startImport, abortImport, + closeFile, startProcessing: () => setProcessingQuery(true), endProcessing: () => setProcessingQuery(false), updateCurrentPath, diff --git a/src/main.ts b/src/main.ts index 6725eb2e..73c03d05 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ import { import getTemplate from "./mainMenu"; import { setUpGlobalIpcListeners, setUpWindowListeners } from "./mainActions"; +import { WORKBENCH_TITLE } from "./constants/general"; // This allows TypeScript to pick up the magic constant that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on @@ -50,7 +51,7 @@ export const createWindow = (): void => { // Create the browser window. const mainWindow = new BrowserWindow({ - title: "Scancode workbench", + title: WORKBENCH_TITLE, width: 1200, height: 800, x, diff --git a/src/mainActions.ts b/src/mainActions.ts index cae7fd60..75ff4a2c 100644 --- a/src/mainActions.ts +++ b/src/mainActions.ts @@ -13,6 +13,7 @@ import { UTIL_CHANNEL, } from "./constants/IpcConnection"; import { figureOutDefaultSqliteFilePath } from "./utils/paths"; +import { WORKBENCH_TITLE } from "./constants/general"; export function chooseSqlitePathForJsonImport( mainWindow: BrowserWindow, @@ -113,6 +114,10 @@ export function saveSqliteFile(mainWindow: BrowserWindow) { }); } +export function closeFile(mainWindow: BrowserWindow) { + mainWindow.webContents.send(UTIL_CHANNEL.CLOSE_FILE); +} + export function showErrorDialog(err: ErrorInfo) { console.log("Showing error to user:", err); dialog.showErrorBox(err.title, err.message); @@ -123,7 +128,7 @@ export function setCurrentFileTitle(mainWindow: BrowserWindow, title: string) { console.log("Main window not found:", title, mainWindow); return; } - const titleString = "Scancode Workbench" + (title ? ` - ${title}` : ""); + const titleString = WORKBENCH_TITLE + (title ? ` - ${title}` : ""); mainWindow.setTitle(titleString); } @@ -148,6 +153,9 @@ export function setUpGlobalIpcListeners() { ipcMain.on(UTIL_CHANNEL.SET_CURRENT_FILE_TITLE, (e, title: string) => setCurrentFileTitle(getSenderWindow(e), title) ); + ipcMain.on(UTIL_CHANNEL.RESET_FILE_TITLE, (e) => + setCurrentFileTitle(getSenderWindow(e), "") + ); ipcMain.on(OPEN_ERROR_DIALOG_CHANNEL, (_, err: ErrorInfo) => showErrorDialog(err) ); diff --git a/src/mainMenu.ts b/src/mainMenu.ts index 0830f304..f06c1348 100644 --- a/src/mainMenu.ts +++ b/src/mainMenu.ts @@ -1,6 +1,11 @@ import { GENERAL_ACTIONS, NAVIGATION_CHANNEL } from "./constants/IpcConnection"; import { app, BrowserWindow, MenuItem, shell } from "electron"; -import { importJsonFile, openSqliteFile, saveSqliteFile } from "./mainActions"; +import { + closeFile, + importJsonFile, + openSqliteFile, + saveSqliteFile, +} from "./mainActions"; import { ROUTES } from "./constants/routes"; import { createWindow } from "./main"; import { WORKBENCH_VERSION } from "./constants/general"; @@ -46,6 +51,12 @@ function getTemplate() { click: (_: MenuItem, currentWindow: BrowserWindow) => importJsonFile(currentWindow), }, + { + label: "Close File", + // accelerator: "CmdOrCtrl+", + click: (_: MenuItem, currentWindow: BrowserWindow) => + closeFile(currentWindow), + }, // @TODO-discuss This is duplicated in App's menu tab, is it necessary under file tab also ?? // ...( // isMac ? [ diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 030de6fc..ce61752b 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -3,7 +3,9 @@ import electron from "electron"; import * as electronFs from "fs"; import { toast } from "react-toastify"; import React, { useMemo, useState } from "react"; +import { Button } from "react-bootstrap"; import { + faClose, faCogs, faDatabase, faFileCode, @@ -32,9 +34,11 @@ const Home = () => { db, loadingStatus, initialized, + scanInfo, + importedSqliteFilePath, jsonParser, sqliteParser, - importedSqliteFilePath, + closeFile, } = useWorkbenchDB(); const [historyRefreshToken, setRefreshToken] = useState(0); @@ -114,6 +118,21 @@ const Home = () => { OR Simply drop your json / sqlite file anywhere in the app !! + {db && initialized && ( +
+
+ Imported scan - {scanInfo.json_file_name} + +
+
+ )}

Recent files
diff --git a/src/pages/Home/home.css b/src/pages/Home/home.css index f85aabdd..313f668c 100644 --- a/src/pages/Home/home.css +++ b/src/pages/Home/home.css @@ -41,16 +41,25 @@ .drop-instruction { text-align: center; width: 100%; - margin-top: 50px; + margin-top: 25px; color: var(--lightTextColor); } .quicklinks, +.currentscan, .history { - margin-top: 15px; padding-left: 2rem; } +.currentscan { + margin-top: 35px; + margin-bottom: 5px; +} + +.quicklinks { + margin-top: 10px; +} + .history table { min-width: 80%; max-width: 95%; diff --git a/src/pages/ScanInfo/ScanInfo.tsx b/src/pages/ScanInfo/ScanInfo.tsx index d7b00717..48c07ccd 100644 --- a/src/pages/ScanInfo/ScanInfo.tsx +++ b/src/pages/ScanInfo/ScanInfo.tsx @@ -19,6 +19,8 @@ const ScanInfo = () => { {scanInfo ? ( + {scanInfo.json_file_name} + {scanInfo.tool_name}{scanInfo.tool_version} diff --git a/src/services/importedJsonTypes.ts b/src/services/importedJsonTypes.ts index 758cbb7d..e6149d53 100644 --- a/src/services/importedJsonTypes.ts +++ b/src/services/importedJsonTypes.ts @@ -1,6 +1,7 @@ import { JSON_Type } from "./models/databaseUtils"; export interface ParsedJsonHeader { + json_file_name: string; tool_name: string; tool_version: string; notice: string; diff --git a/src/services/models/header.ts b/src/services/models/header.ts index 2804b242..37e421ec 100644 --- a/src/services/models/header.ts +++ b/src/services/models/header.ts @@ -19,6 +19,7 @@ import { JSON_Type, jsonDataType } from "./databaseUtils"; export interface HeaderAttributes { id: number; + json_file_name: string; tool_name: string; tool_version: string; notice: string; @@ -52,6 +53,7 @@ export default function headerModel(sequelize: Sequelize) { primaryKey: true, type: DataTypes.INTEGER, }, + json_file_name: DataTypes.STRING, tool_name: DataTypes.STRING, tool_version: DataTypes.STRING, notice: DataTypes.STRING, diff --git a/src/services/workbenchDB.ts b/src/services/workbenchDB.ts index 619b839e..5a5aadab 100644 --- a/src/services/workbenchDB.ts +++ b/src/services/workbenchDB.ts @@ -61,7 +61,7 @@ const { version: workbenchVersion } = packageJson; * * The config will load an existing database or will create a new, empty * database if none exists. For a new database, the data is loaded from a JSON - * file by calling addFromJson(jsonFileName). + * file by calling addFromJson(jsonFilePath). * * @param config * @param config.dbName @@ -271,16 +271,16 @@ export class WorkbenchDB { // Add rows to the flattened files table from a ScanCode json object addFromJson( - jsonFileName: string, + jsonFilePath: string, onProgressUpdate: (progress: number) => void ): Promise { - if (!jsonFileName) { - throw new Error("Invalid json file name: " + jsonFileName); + if (!jsonFilePath) { + throw new Error("Invalid json file name: " + jsonFilePath); } - // console.log("Adding from json with params", { jsonFileName, workbenchVersion, onProgressUpdate }); + // console.log("Adding from json with params", { jsonFilePath, workbenchVersion, onProgressUpdate }); - const stream = fs.createReadStream(jsonFileName, { encoding: "utf8" }); + const stream = fs.createReadStream(jsonFilePath, { encoding: "utf8" }); let files_count = 0; let dirs_count = 0; let index = 0; @@ -303,7 +303,7 @@ export class WorkbenchDB { let batchCount = 0; let TopLevelData: TopLevelDataFormat = { header: null, - parsedHeader: this._parseHeader(workbenchVersion, {}), + parsedHeader: this._parseHeader(jsonFilePath, workbenchVersion, {}), packages: [], dependencies: [], license_clues: [], @@ -318,7 +318,10 @@ export class WorkbenchDB { stream .pipe(JSONStream.parse("files.*")) // files field is piped to 'data' & rest to 'header' (Includes other top level fields) .on("header", (rawTopLevelData: any) => { - TopLevelData = this._parseTopLevelFields(rawTopLevelData); + TopLevelData = this._parseTopLevelFields( + jsonFilePath, + rawTopLevelData + ); files_count = Number(TopLevelData.parsedHeader.files_count); promiseChain = promiseChain @@ -443,11 +446,18 @@ export class WorkbenchDB { } // Helper function for parsing Toplevel data - _parseTopLevelFields(rawTopLevelData: any): TopLevelDataFormat { + _parseTopLevelFields( + jsonFilePath: string, + rawTopLevelData: any + ): TopLevelDataFormat { const header = rawTopLevelData.headers ? rawTopLevelData.headers[0] || {} : {}; - const parsedHeader = this._parseHeader(workbenchVersion, header); + const parsedHeader = this._parseHeader( + jsonFilePath, + workbenchVersion, + header + ); const packages = rawTopLevelData.packages || []; const dependencies = rawTopLevelData.dependencies || []; const license_detections: TopLevelLicenseDetection[] = ( @@ -493,10 +503,11 @@ export class WorkbenchDB { }; } - _parseHeader(workbenchVersion: string, header: any) { + _parseHeader(jsonFilePath: string, workbenchVersion: string, header: any) { const input = header.options?.input || []; delete header.options?.input; const parsedHeader: ParsedJsonHeader = { + json_file_name: path.basename(jsonFilePath), tool_name: header.tool_name, tool_version: header.tool_version, notice: header.notice, diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 5122a721..ef8733f9 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -33,6 +33,7 @@ export enum ScanOptionKeys { } export interface ScanInfo { + json_file_name: string; tool_name: string; tool_version: string; notice: string; @@ -65,6 +66,7 @@ export function parseScanInfo( ); const parsedScanInfo: ScanInfo = { + json_file_name: rawInfo.getDataValue("json_file_name") || "Not available", tool_name: rawInfo.getDataValue("tool_name") || "", tool_version: rawInfo.getDataValue("tool_version") || "", notice: rawInfo.getDataValue("notice") || "", diff --git a/tests/test-scans/headers/expectedHeaders.ts b/tests/test-scans/headers/expectedHeaders.ts index cb9d61fb..23a524a3 100644 --- a/tests/test-scans/headers/expectedHeaders.ts +++ b/tests/test-scans/headers/expectedHeaders.ts @@ -8,6 +8,7 @@ export const HeaderSamples: { { jsonFileName: "headerless.json", expectedHeaders: { + json_file_name: "headerless.json", tool_name: "", tool_version: "", notice: "", @@ -33,6 +34,7 @@ export const HeaderSamples: { { jsonFileName: "minimal.json", expectedHeaders: { + json_file_name: "minimal.json", tool_name: "scancode-toolkit", tool_version: "32.0.0rc3", notice: @@ -60,6 +62,7 @@ export const HeaderSamples: { { jsonFileName: "withOptions.json", expectedHeaders: { + json_file_name: "withOptions.json", tool_name: "scancode-toolkit", tool_version: "32.0.0rc3", notice: