diff --git a/eslint.config.js b/eslint.config.js
index 14a692a..92cbf7e 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -20,7 +20,6 @@ module.exports = [
'dist/**',
'build/**',
'coverage/**',
- 'scripts/**',
'src/renderer/bundle.js',
'src/renderer/bundle.js.map',
'src/renderer/bundle.js.LICENSE.txt',
@@ -31,6 +30,36 @@ module.exports = [
],
},
js.configs.recommended,
+ {
+ files: [
+ 'scripts/**/*.js',
+ '*.config.js',
+ 'eslint.config.js',
+ '.eslintrc.js',
+ '.babelrc.js',
+ 'tests/.eslintrc.js',
+ ],
+ languageOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'commonjs',
+ globals: {
+ ...globals.node,
+ },
+ },
+ rules: {
+ 'no-unused-vars': 'off',
+ 'no-case-declarations': 'off',
+ 'no-useless-escape': 'off',
+ },
+ },
+ {
+ files: ['scripts/capture-ui-screenshot.js'],
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ },
+ },
+ },
{
files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'],
languageOptions: {
@@ -58,7 +87,16 @@ module.exports = [
'import/order': [
'error',
{
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+ groups: [
+ 'builtin',
+ 'external',
+ 'internal',
+ 'parent',
+ 'sibling',
+ 'index',
+ 'object',
+ 'type',
+ ],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
diff --git a/package.json b/package.json
index ee47d6e..0e5c8e1 100644
--- a/package.json
+++ b/package.json
@@ -18,14 +18,14 @@
"predev": "npm run build:ts && node scripts/clean-dev-assets.js",
"dev": "node scripts/index.js dev",
"clear-assets": "rimraf src/renderer/bundle.js src/renderer/bundle.js.map src/renderer/bundle.js.LICENSE.txt src/renderer/output.css",
- "lint": "npm run format:check && eslint src tests --cache --max-warnings 0 && npm run lint:md && npm run test:catalog && npm run changelog:validate",
+ "lint": "npm run format:check && eslint src tests scripts eslint.config.js .eslintrc.js .babelrc.js babel.config.js jest.config.js playwright.config.ts postcss.config.js prettier.config.js tailwind.config.js webpack.config.js --cache --max-warnings 0 && npm run lint:md && npm run test:catalog && npm run changelog:validate",
"lint:md": "npm run lint:md:links && npm run lint:md:style",
"lint:md:links": "node scripts/lint-markdown-links.js",
"lint:md:style": "markdownlint \"**/*.{md,mdx}\" --config .markdownlint.json --ignore node_modules --ignore dist",
"changelog:validate": "node scripts/validate-changelog.js",
"lint:tests": "eslint tests --cache --max-warnings 0",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,html,css}\"",
- "format:check": "prettier --check --end-of-line auto \"**/*.{json,md,html,css}\"",
+ "format:check": "prettier --check --end-of-line auto \"**/*.{json,md,html,css}\" && prettier --check --end-of-line auto \"scripts/**/*.js\" \"*.config.js\" \"eslint.config.js\" \".eslintrc.js\" \".babelrc.js\" \"jest.config.js\" \"postcss.config.js\" \"prettier.config.js\" \"tailwind.config.js\" \"webpack.config.js\" \"playwright.config.ts\"",
"test": "jest --config jest.config.js --passWithNoTests",
"test:watch": "jest --watch --config jest.config.js --passWithNoTests",
"test:stress": "jest --config jest.config.js --runInBand --testMatch=\"**/tests/stress/**/*.{js,jsx,ts,tsx}\" --verbose",
@@ -77,7 +77,7 @@
"*.{json,md,html,css}": [
"prettier --write"
],
- "{src,tests}/**/*.{js,jsx,ts,tsx}": [
+ "{src,tests,scripts}/**/*.{js,jsx,ts,tsx}": [
"eslint --fix"
]
},
diff --git a/scripts/audit-actions-freshness.js b/scripts/audit-actions-freshness.js
index d292bc8..8c0ce50 100644
--- a/scripts/audit-actions-freshness.js
+++ b/scripts/audit-actions-freshness.js
@@ -121,8 +121,7 @@ function readWorkflowFiles(workflowDirectory) {
const entries = fs
.readdirSync(directoryPath, { withFileTypes: true })
.filter(
- (entry) =>
- entry.isFile() && (entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))
+ (entry) => entry.isFile() && (entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))
)
.map((entry) => entry.name)
.sort((left, right) => left.localeCompare(right));
@@ -203,7 +202,9 @@ async function githubRequest({ endpoint, token, method = 'GET', body = null }) {
if (!response.ok) {
const detail = data && typeof data === 'object' && data.message ? data.message : responseText;
- const error = new Error(`GitHub API ${method} ${endpoint} failed (${response.status}): ${detail}`);
+ const error = new Error(
+ `GitHub API ${method} ${endpoint} failed (${response.status}): ${detail}`
+ );
error.status = response.status;
throw error;
}
@@ -406,7 +407,8 @@ async function ensureTrackingPullRequestBranch({
endpoint: `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/git/ref/heads/${toGitHubRefPath(defaultBranch)}`,
token,
});
- const defaultBranchSha = defaultBranchRef && defaultBranchRef.object ? defaultBranchRef.object.sha : '';
+ const defaultBranchSha =
+ defaultBranchRef && defaultBranchRef.object ? defaultBranchRef.object.sha : '';
if (!defaultBranchSha) {
throw new Error(`Could not resolve latest commit on ${defaultBranch}.`);
}
diff --git a/scripts/capture-ui-screenshot.js b/scripts/capture-ui-screenshot.js
index 7b967ab..c827476 100644
--- a/scripts/capture-ui-screenshot.js
+++ b/scripts/capture-ui-screenshot.js
@@ -14,7 +14,13 @@ const DEFAULT_SCREENSHOT_NAME = `ui-${process.platform}-${process.arch}.png`;
const FIXED_MTIME = 1700000000000;
function loadSecretScannerHelpers() {
- const compiledSecretScannerPath = path.join(ROOT_DIR, 'build', 'ts', 'utils', 'secret-scanner.js');
+ const compiledSecretScannerPath = path.join(
+ ROOT_DIR,
+ 'build',
+ 'ts',
+ 'utils',
+ 'secret-scanner.js'
+ );
try {
return require(compiledSecretScannerPath);
@@ -49,7 +55,10 @@ function sanitizeScreenshotName(nameCandidate) {
typeof nameCandidate === 'string' && nameCandidate.trim()
? nameCandidate.trim()
: DEFAULT_SCREENSHOT_NAME;
- const baseName = path.basename(rawName).replace(/[^a-zA-Z0-9._-]/g, '-').replace(/^\.+/, '');
+ const baseName = path
+ .basename(rawName)
+ .replace(/[^a-zA-Z0-9._-]/g, '-')
+ .replace(/^\.+/, '');
const withExtension = baseName.toLowerCase().endsWith('.png') ? baseName : `${baseName}.png`;
if (!withExtension || withExtension === '.png') {
@@ -361,7 +370,8 @@ async function setupMockElectronApi(page) {
localStorage.setItem('configContent', mockConfig);
const cloneTree = (treeItems) => JSON.parse(JSON.stringify(treeItems));
- const delay = (durationMs) => new Promise((resolve) => window.setTimeout(resolve, durationMs));
+ const delay = (durationMs) =>
+ new Promise((resolve) => window.setTimeout(resolve, durationMs));
window.electronAPI = {
getDefaultConfig: async () => mockConfig,
@@ -371,14 +381,19 @@ async function setupMockElectronApi(page) {
typeof configContent === 'string' && configContent.trim()
? configContent
: localStorage.getItem('configContent') || '';
- const excludeSensitiveFiles = !/(^|\n)\s*enable_secret_scanning\s*:\s*false\b/i.test(
- activeConfig
- ) && !/(^|\n)\s*exclude_suspicious_files\s*:\s*false\b/i.test(activeConfig);
+ const configLines = activeConfig
+ .split('\n')
+ .map((line) => line.trim().toLowerCase().replaceAll(' ', '').replaceAll('\t', ''));
+ const hasSecretScanningDisabled = configLines.includes('enable_secret_scanning:false');
+ const hasSuspiciousFilesDisabled = configLines.includes('exclude_suspicious_files:false');
+ const excludeSensitiveFiles = !hasSecretScanningDisabled && !hasSuspiciousFilesDisabled;
const tree = excludeSensitiveFiles ? mockFilteredDirectoryTree : mockDirectoryTree;
return cloneTree(tree);
},
analyzeRepository: async (options = {}) => {
- const selectedFilePaths = Array.isArray(options?.selectedFiles) ? options.selectedFiles : [];
+ const selectedFilePaths = Array.isArray(options?.selectedFiles)
+ ? options.selectedFiles
+ : [];
const filesInfo = selectedFilePaths.map((filePath, index) => {
const normalizedPath = String(filePath);
const relativePath = normalizedPath.startsWith(`${mockRootPath}/`)
@@ -402,7 +417,9 @@ async function setupMockElectronApi(page) {
const filesInfo = inputFilesInfo.map((file, index) => ({
path: String(file?.path || `src/file-${index + 1}.ts`),
tokens:
- Number.isFinite(file?.tokens) && Number(file.tokens) > 0 ? Number(file.tokens) : 120 * (index + 1),
+ Number.isFinite(file?.tokens) && Number(file.tokens) > 0
+ ? Number(file.tokens)
+ : 120 * (index + 1),
isBinary: false,
}));
const totalTokens = filesInfo.reduce((sum, file) => sum + file.tokens, 0);
@@ -422,7 +439,8 @@ async function setupMockElectronApi(page) {
'# Repository Analysis',
'',
...filesInfo.map(
- (file) => `## ${file.path}\n\n\`\`\`ts\n// Preview for ${file.path}\n\`\`\`\nTokens: ${file.tokens}\n`
+ (file) =>
+ `## ${file.path}\n\n\`\`\`ts\n// Preview for ${file.path}\n\`\`\`\nTokens: ${file.tokens}\n`
),
'--END--',
].join('\n');
@@ -516,7 +534,10 @@ async function captureAppStateScreenshots(page) {
});
await runStep('Verify secret files are hidden by default', async () => {
- await page.waitForFunction((selector) => !document.querySelector(selector), UI_SELECTORS.secretFileEntry);
+ await page.waitForFunction(
+ (selector) => !document.querySelector(selector),
+ UI_SELECTORS.secretFileEntry
+ );
});
await runStep('Capture source tab screenshot', async () => {
diff --git a/scripts/lib/actions-freshness.js b/scripts/lib/actions-freshness.js
index cf7f478..047ba82 100644
--- a/scripts/lib/actions-freshness.js
+++ b/scripts/lib/actions-freshness.js
@@ -31,12 +31,9 @@ function extractUsesValue(line) {
return '';
}
- const withoutPrefix = normalized
- .slice(USES_LINE_PATTERN.length)
- .trimStart();
+ const withoutPrefix = normalized.slice(USES_LINE_PATTERN.length).trimStart();
const commentStart = withoutPrefix.search(/\s#/);
- const rawValue =
- commentStart >= 0 ? withoutPrefix.slice(0, commentStart) : withoutPrefix;
+ const rawValue = commentStart >= 0 ? withoutPrefix.slice(0, commentStart) : withoutPrefix;
return normalizeReferenceValue(rawValue.trim());
}
@@ -131,10 +128,7 @@ function escapeMarkdownTableCell(value) {
return '';
}
- return String(value)
- .replace(/\\/g, '\\\\')
- .replace(/\r?\n/g, '
')
- .replace(/\|/g, '\\|');
+ return String(value).replace(/\\/g, '\\\\').replace(/\r?\n/g, '
').replace(/\|/g, '\\|');
}
function buildMarkdownReport(report) {
@@ -202,7 +196,9 @@ function buildMarkdownReport(report) {
}
if (report.staleCount === 0 && report.resolutionErrors.length === 0) {
- lines.push('All pinned GitHub Actions references are current against latest upstream releases.');
+ lines.push(
+ 'All pinned GitHub Actions references are current against latest upstream releases.'
+ );
lines.push('');
}
diff --git a/scripts/lib/security.js b/scripts/lib/security.js
index 2e8bddc..c21d7e0 100755
--- a/scripts/lib/security.js
+++ b/scripts/lib/security.js
@@ -99,7 +99,9 @@ function resolveCommand(command, localCandidates = []) {
}
for (const candidate of localCandidates) {
- const absolutePath = path.isAbsolute(candidate) ? candidate : path.join(utils.ROOT_DIR, candidate);
+ const absolutePath = path.isAbsolute(candidate)
+ ? candidate
+ : path.join(utils.ROOT_DIR, candidate);
if (fs.existsSync(absolutePath)) {
return absolutePath;
}
@@ -285,7 +287,9 @@ function resolveTokenFromFile() {
return '';
}
- const tokenFilePath = path.isAbsolute(tokenFile) ? tokenFile : path.join(utils.ROOT_DIR, tokenFile);
+ const tokenFilePath = path.isAbsolute(tokenFile)
+ ? tokenFile
+ : path.join(utils.ROOT_DIR, tokenFile);
if (!fs.existsSync(tokenFilePath)) {
return '';
}
@@ -508,7 +512,8 @@ async function runMendScan() {
assertAllowedExecutable(mendPath);
const pkg = readPackageMetadata();
- const project = process.env.MEND_PROJECT || process.env.BINARY_NAME || pkg.name || 'ai-code-fusion';
+ const project =
+ process.env.MEND_PROJECT || process.env.BINARY_NAME || pkg.name || 'ai-code-fusion';
const version = process.env.MEND_PROJECT_VERSION || process.env.VERSION || pkg.version || '0.0.0';
const commandName = process.platform === 'win32' ? 'mend-scan.exe' : 'mend-scan';
const args = ['scan', '--project', project, '--version', version];
diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js
index 87550d2..3a385b8 100755
--- a/scripts/lib/utils.js
+++ b/scripts/lib/utils.js
@@ -230,7 +230,9 @@ function printHelp() {
console.log(' prometheus:verify - Verify pushed stress metrics in Prometheus');
console.log(' perf-test - Run stress tests, push metrics, and verify Prometheus');
console.log(' lint - Run linter');
- console.log(' lint:md - Validate markdown links, image paths, and no decorative icons');
+ console.log(
+ ' lint:md - Validate markdown links, image paths, and no decorative icons'
+ );
console.log(' format - Format code');
console.log(' validate - Run all code quality checks');
console.log(' qa - Run lint + tests + security checks');
diff --git a/scripts/lint-markdown-links.js b/scripts/lint-markdown-links.js
index 52dedc6..d02e82b 100755
--- a/scripts/lint-markdown-links.js
+++ b/scripts/lint-markdown-links.js
@@ -62,7 +62,11 @@ function normalizeTarget(rawTarget) {
function resolveTargetPath(markdownFilePath, target) {
const [pathWithoutAnchor] = target.split('#');
- if (!pathWithoutAnchor || isExternalTarget(pathWithoutAnchor) || pathWithoutAnchor.startsWith('#')) {
+ if (
+ !pathWithoutAnchor ||
+ isExternalTarget(pathWithoutAnchor) ||
+ pathWithoutAnchor.startsWith('#')
+ ) {
return null;
}
@@ -131,7 +135,11 @@ function lintMarkdownFile(markdownFilePath) {
const rawTargets = extractTargetsFromLine(line);
for (const rawTarget of rawTargets) {
const normalizedTarget = normalizeTarget(rawTarget);
- if (!normalizedTarget || isExternalTarget(normalizedTarget) || normalizedTarget.startsWith('#')) {
+ if (
+ !normalizedTarget ||
+ isExternalTarget(normalizedTarget) ||
+ normalizedTarget.startsWith('#')
+ ) {
continue;
}
@@ -177,12 +185,16 @@ function run() {
const relativeFilePath = path.relative(ROOT_DIR, error.filePath);
if (error.kind === 'decorative-icon') {
- console.error(`- ${relativeFilePath}:${error.lineNumber} -> decorative icon found: ${error.lineText}`);
+ console.error(
+ `- ${relativeFilePath}:${error.lineNumber} -> decorative icon found: ${error.lineText}`
+ );
continue;
}
const relativeResolvedPath = path.relative(ROOT_DIR, error.resolvedPath);
- console.error(`- ${relativeFilePath}:${error.lineNumber} -> ${error.target} (missing: ${relativeResolvedPath})`);
+ console.error(
+ `- ${relativeFilePath}:${error.lineNumber} -> ${error.target} (missing: ${relativeResolvedPath})`
+ );
}
process.exit(1);
}
diff --git a/scripts/publish-stress-metrics.js b/scripts/publish-stress-metrics.js
index d390450..5eadc80 100755
--- a/scripts/publish-stress-metrics.js
+++ b/scripts/publish-stress-metrics.js
@@ -200,7 +200,9 @@ function buildPrometheusPayload(records, options = {}) {
}
}
- lines.push(`# HELP ${METRIC_PREFIX}_file_count Number of files exercised by the stress scenario.`);
+ lines.push(
+ `# HELP ${METRIC_PREFIX}_file_count Number of files exercised by the stress scenario.`
+ );
lines.push(`# TYPE ${METRIC_PREFIX}_file_count gauge`);
for (const record of records) {
diff --git a/scripts/run-perf-metrics-job.js b/scripts/run-perf-metrics-job.js
index e8b2f6e..9adc1f1 100644
--- a/scripts/run-perf-metrics-job.js
+++ b/scripts/run-perf-metrics-job.js
@@ -127,10 +127,12 @@ async function runPerfMetricsJob(options = {}) {
);
}
- const jobName = (env.PUSHGATEWAY_JOB || DEFAULT_PUSHGATEWAY_JOB).trim() || DEFAULT_PUSHGATEWAY_JOB;
+ const jobName =
+ (env.PUSHGATEWAY_JOB || DEFAULT_PUSHGATEWAY_JOB).trim() || DEFAULT_PUSHGATEWAY_JOB;
const instanceName =
(env.PUSHGATEWAY_INSTANCE || '').trim() || buildDefaultInstanceName(nowFn(), hostName);
- const strictMode = (env.PUSHGATEWAY_STRICT || 'true').trim().toLowerCase() === 'false' ? 'false' : 'true';
+ const strictMode =
+ (env.PUSHGATEWAY_STRICT || 'true').trim().toLowerCase() === 'false' ? 'false' : 'true';
const timeoutMs = toFiniteNumber(env.PROMETHEUS_VERIFY_TIMEOUT_MS) || 60_000;
const pollIntervalMs = toFiniteNumber(env.PROMETHEUS_VERIFY_POLL_INTERVAL_MS) || 5_000;
const minPublishTimestampSeconds = Math.floor(nowFn() / 1000);
diff --git a/scripts/sonar-scan.js b/scripts/sonar-scan.js
index dc5ed83..1b4332a 100755
--- a/scripts/sonar-scan.js
+++ b/scripts/sonar-scan.js
@@ -203,7 +203,8 @@ function runWithNativeScanner(scannerOptions, token) {
const scannerOptionsForArgs = { ...scannerOptions };
delete scannerOptionsForArgs['sonar.token'];
const args = Object.entries(scannerOptionsForArgs).map(([key, value]) => `-D${key}=${value}`);
- const useWindowsShell = process.platform === 'win32' && scannerBinary.toLowerCase().endsWith('.bat');
+ const useWindowsShell =
+ process.platform === 'win32' && scannerBinary.toLowerCase().endsWith('.bat');
const scannerEnv = { ...process.env };
if (token) {
scannerEnv.SONAR_TOKEN = token;
@@ -278,7 +279,11 @@ function resolveNativeScannerPath() {
function isNpmScannerWrapperPath(scannerPath) {
const normalizedPath = path.normalize(scannerPath).toLowerCase();
const wrapperSuffix = path
- .join('node_modules', '.bin', process.platform === 'win32' ? 'sonar-scanner.cmd' : 'sonar-scanner')
+ .join(
+ 'node_modules',
+ '.bin',
+ process.platform === 'win32' ? 'sonar-scanner.cmd' : 'sonar-scanner'
+ )
.toLowerCase();
return normalizedPath.endsWith(wrapperSuffix);
}
@@ -397,9 +402,7 @@ try {
console.log(
'2. Check if the project exists on the server or if you have permission to create it'
);
- console.log(
- '3. Verify the token has not expired and is valid for the specified project key'
- );
+ console.log('3. Verify the token has not expired and is valid for the specified project key');
process.exit(1);
} else {
console.log('SonarQube scan completed successfully!');
diff --git a/scripts/validate-test-catalog.js b/scripts/validate-test-catalog.js
index 254e7a5..b69068e 100644
--- a/scripts/validate-test-catalog.js
+++ b/scripts/validate-test-catalog.js
@@ -218,7 +218,11 @@ function run() {
try {
catalogPath = resolvePathWithinRoot(catalogArg, DEFAULT_CATALOG_PATH, 'Catalog path');
- jestConfigPath = resolvePathWithinRoot(jestConfigArg, DEFAULT_JEST_CONFIG_PATH, 'Jest config path');
+ jestConfigPath = resolvePathWithinRoot(
+ jestConfigArg,
+ DEFAULT_JEST_CONFIG_PATH,
+ 'Jest config path'
+ );
} catch (error) {
console.error('Test catalog validation failed:');
console.error(`- ${error.message}`);
diff --git a/scripts/verify-prometheus-metrics.js b/scripts/verify-prometheus-metrics.js
index 67f9b2a..bbb6443 100644
--- a/scripts/verify-prometheus-metrics.js
+++ b/scripts/verify-prometheus-metrics.js
@@ -70,29 +70,25 @@ function requestJson(endpointUrl, options = {}) {
}
};
- const request = client.request(
- endpointUrl,
- { method: 'GET' },
- (response) => {
- const responseChunks = [];
- response.on('data', (chunk) => responseChunks.push(chunk));
- response.on('end', () => {
- const responseBody = Buffer.concat(responseChunks).toString('utf8');
- if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
- rejectOnce(
- new Error(`Prometheus returned HTTP ${response.statusCode || 'unknown status'}`)
- );
- return;
- }
+ const request = client.request(endpointUrl, { method: 'GET' }, (response) => {
+ const responseChunks = [];
+ response.on('data', (chunk) => responseChunks.push(chunk));
+ response.on('end', () => {
+ const responseBody = Buffer.concat(responseChunks).toString('utf8');
+ if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
+ rejectOnce(
+ new Error(`Prometheus returned HTTP ${response.statusCode || 'unknown status'}`)
+ );
+ return;
+ }
- try {
- resolveOnce(JSON.parse(responseBody));
- } catch (error) {
- rejectOnce(new Error('Failed to parse Prometheus response body as JSON'));
- }
- });
- }
- );
+ try {
+ resolveOnce(JSON.parse(responseBody));
+ } catch (error) {
+ rejectOnce(new Error('Failed to parse Prometheus response body as JSON'));
+ }
+ });
+ });
request.setTimeout(requestTimeoutMs, () => {
request.destroy(new Error(`Prometheus request timed out after ${requestTimeoutMs}ms`));
@@ -170,7 +166,11 @@ async function waitForStressMetrics(options) {
const parsedPollIntervalMs = toFiniteNumber(pollIntervalMs) || DEFAULT_POLL_INTERVAL_MS;
const parsedRequestTimeoutMs = toFiniteNumber(requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS;
const boundedRequestTimeoutMs = Math.max(1, Math.min(parsedRequestTimeoutMs, parsedTimeoutMs));
- const queries = buildMetricQueries(metricName, String(jobName).trim(), String(instanceName).trim());
+ const queries = buildMetricQueries(
+ metricName,
+ String(jobName).trim(),
+ String(instanceName).trim()
+ );
const deadline = nowFn() + parsedTimeoutMs;
let lastError = null;
@@ -203,7 +203,9 @@ async function waitForStressMetrics(options) {
}
const errorSuffix =
- lastError instanceof Error ? ` Last error: ${lastError.message}` : ' No Prometheus samples matched.';
+ lastError instanceof Error
+ ? ` Last error: ${lastError.message}`
+ : ' No Prometheus samples matched.';
const attemptedQueries = queries.join(' | ');
throw new Error(
diff --git a/tests/catalog.md b/tests/catalog.md
index bcf4a09..c1f3331 100644
--- a/tests/catalog.md
+++ b/tests/catalog.md
@@ -39,6 +39,7 @@ Purpose: quick map of what is covered, why it exists, and which command to run.
| `tests/unit/scripts/security.test.js` | `scripts/lib/security.js` | Command safety validation, Windows path acceptance for approved executables |
| `tests/unit/scripts/actions-freshness.test.js` | `scripts/lib/actions-freshness.js` | Workflow `uses:` reference parsing, pinning classification, freshness markdown report output |
| `tests/unit/scripts/eslint-config.test.js` | `eslint.config.js` | Guard scoped unicorn/sonarjs strict-pack configuration and test exclusions |
+| `tests/unit/scripts/lint-gates.test.js` | `package.json` + `eslint.config.js` | Ensure lint/format gates include scripts + config coverage and staged-lint scope |
| `tests/unit/scripts/electron-eslint-rules.test.js` | `eslint-rules/electron-security.js` | Validate custom Electron safety lint rules (BrowserWindow flags, IPC channels, renderer bans) |
| `tests/unit/scripts/sonar-options.test.js` | `scripts/lib/sonar-options.js` | Sonar scanner option merge behavior and CPD exclusion defaults |
| `tests/unit/scripts/publish-stress-metrics.test.js` | `scripts/publish-stress-metrics.js` | Prometheus payload generation and Pushgateway publication safeguards |
diff --git a/tests/unit/scripts/eslint-config.test.js b/tests/unit/scripts/eslint-config.test.js
index fd8ef7b..6df3cb2 100644
--- a/tests/unit/scripts/eslint-config.test.js
+++ b/tests/unit/scripts/eslint-config.test.js
@@ -34,4 +34,11 @@ describe('eslint phase 2 strict packs config', () => {
"'electron-security/no-electron-import-in-renderer': 'error'"
);
});
+
+ test('includes scripts/config lint scope and does not globally ignore scripts', () => {
+ expect(eslintConfigSource).toContain("'scripts/**/*.js'");
+ expect(eslintConfigSource).toContain("'*.config.js'");
+ expect(eslintConfigSource).toContain("sourceType: 'commonjs'");
+ expect(eslintConfigSource).not.toContain("'scripts/**',");
+ });
});
diff --git a/tests/unit/scripts/lint-gates.test.js b/tests/unit/scripts/lint-gates.test.js
new file mode 100644
index 0000000..d11981e
--- /dev/null
+++ b/tests/unit/scripts/lint-gates.test.js
@@ -0,0 +1,31 @@
+const fs = jest.requireActual('fs');
+const path = jest.requireActual('path');
+
+const packageJsonPath = path.resolve(__dirname, '../../../package.json');
+const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
+
+describe('lint and format gate scripts', () => {
+ test('lint command covers scripts and config files', () => {
+ const lintScript = packageJson.scripts.lint;
+
+ expect(lintScript).toContain('eslint src tests scripts');
+ expect(lintScript).toContain('eslint.config.js');
+ expect(lintScript).toContain('.eslintrc.js');
+ expect(lintScript).toContain('playwright.config.ts');
+ expect(lintScript).toContain('--max-warnings 0');
+ });
+
+ test('format:check enforces JS/TS formatting for scripts and configs', () => {
+ const formatCheckScript = packageJson.scripts['format:check'];
+
+ expect(formatCheckScript).toContain('scripts/**/*.js');
+ expect(formatCheckScript).toContain('*.config.js');
+ expect(formatCheckScript).toContain('playwright.config.ts');
+ });
+
+ test('lint-staged includes scripts path coverage', () => {
+ const lintStagedKeys = Object.keys(packageJson['lint-staged'] ?? {});
+
+ expect(lintStagedKeys).toContain('{src,tests,scripts}/**/*.{js,jsx,ts,tsx}');
+ });
+});