From 7d2262fb439298e73d2651aabae7c6dc6e2107d5 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 28 Sep 2026 15:48:44 +0300 Subject: [PATCH] fix: resolve build imports before linting Resolve dist imports in runtime scripts against their TypeScript sources. Keep missing-module checks enabled and cover absent build output in the lint gate. Verified npm ci, prisma generate and full verify with dist absent at startup. --- eslint.config.mjs | 12 ++++++++++++ test/eslint-config.cjs | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 1e01eab..998a57b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -60,6 +60,18 @@ export default defineConfig( files: ['**/*.mjs'], extends: [node.configs['flat/recommended-module']], }, + { + files: ['scripts/generate-openapi.cjs', 'test/fixtures/tbank-sync-crash.cjs', 'test/runtime-check.cjs'], + rules: { + // These scripts run after build; lint validates their source paths before dist exists. + 'n/no-missing-require': ['error', { + resolverConfig: { + alias: { '../dist': import.meta.dirname, '../../dist': import.meta.dirname }, + extensionAlias: { '.js': ['.ts', '.js'] }, + }, + }], + }, + }, { files: ['**/*.ts'], extends: [tseslint.configs.recommendedTypeChecked, importX.flatConfigs.typescript], diff --git a/test/eslint-config.cjs b/test/eslint-config.cjs index c574386..592f8c0 100644 --- a/test/eslint-config.cjs +++ b/test/eslint-config.cjs @@ -1,9 +1,38 @@ const { ESLint } = require('eslint'); const assert = require('node:assert/strict'); +const { mkdtempSync, rmSync, writeFileSync } = require('node:fs'); +const { basename, join } = require('node:path'); const { test } = require('node:test'); const eslint = new ESLint(); +test('runtime scripts resolve build imports against sources before dist exists', async () => { + const directory = mkdtempSync(join(process.cwd(), '.eslint-require-')); + try { + // eslint-disable-next-line security/detect-non-literal-fs-filename -- Test-owned temporary source with no compiled counterpart. + writeFileSync(join(directory, 'entry.ts'), 'export const value = true;'); + for (const [filePath, prefix] of [ + ['scripts/generate-openapi.cjs', '../'], + ['test/runtime-check.cjs', '../'], + ['test/fixtures/tbank-sync-crash.cjs', '../../'], + ]) { + const source = `${prefix}dist/${basename(directory)}/entry`; + const [result] = await eslint.lintText( + `require('${source}.js'); require('${source}'); require('node:fs'); require('eslint'); require('${source}-missing.js'); require('${prefix}missing-local.cjs'); require('wealthledger-missing-package');`, + { filePath }, + ); + assert.equal(result.fatalErrorCount, 0, filePath); + const missing = result.messages.filter(({ ruleId }) => ruleId === 'n/no-missing-require'); + assert.equal(missing.length, 3, filePath); + for (const target of [`${source}-missing.js`, `${prefix}missing-local.cjs`, 'wealthledger-missing-package']) { + assert.ok(missing.some(({ message }) => message.includes(`'${target}'`)), target); + } + } + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); + // Protect the quality gate's file scopes when configs or plugin versions change. test('typed application code enforces async and unsafe-type checks', async () => { const [result] = await eslint.lintText(