diff --git a/examples/nuxt-app/.gitignore b/examples/nuxt-app/.gitignore new file mode 100644 index 0000000..4a7f73a --- /dev/null +++ b/examples/nuxt-app/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/examples/nuxt-app/README.md b/examples/nuxt-app/README.md new file mode 100644 index 0000000..25b5821 --- /dev/null +++ b/examples/nuxt-app/README.md @@ -0,0 +1,75 @@ +# Nuxt Minimal Starter + +Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. + +## Setup + +Make sure to install dependencies: + +```bash +# npm +npm install + +# pnpm +pnpm install + +# yarn +yarn install + +# bun +bun install +``` + +## Development Server + +Start the development server on `http://localhost:3000`: + +```bash +# npm +npm run dev + +# pnpm +pnpm dev + +# yarn +yarn dev + +# bun +bun run dev +``` + +## Production + +Build the application for production: + +```bash +# npm +npm run build + +# pnpm +pnpm build + +# yarn +yarn build + +# bun +bun run build +``` + +Locally preview production build: + +```bash +# npm +npm run preview + +# pnpm +pnpm preview + +# yarn +yarn preview + +# bun +bun run preview +``` + +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/examples/nuxt-app/app.vue b/examples/nuxt-app/app.vue new file mode 100644 index 0000000..09f935b --- /dev/null +++ b/examples/nuxt-app/app.vue @@ -0,0 +1,6 @@ + diff --git a/examples/nuxt-app/nuxt.config.ts b/examples/nuxt-app/nuxt.config.ts new file mode 100644 index 0000000..b2f16b5 --- /dev/null +++ b/examples/nuxt-app/nuxt.config.ts @@ -0,0 +1,5 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: '2024-11-01', + devtools: { enabled: true }, +}) diff --git a/examples/nuxt-app/package.json b/examples/nuxt-app/package.json new file mode 100644 index 0000000..971afba --- /dev/null +++ b/examples/nuxt-app/package.json @@ -0,0 +1,17 @@ +{ + "name": "nuxt-app", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare" + }, + "dependencies": { + "nuxt": "^3.16.2", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + } +} diff --git a/examples/nuxt-app/public/favicon.ico b/examples/nuxt-app/public/favicon.ico new file mode 100644 index 0000000..18993ad Binary files /dev/null and b/examples/nuxt-app/public/favicon.ico differ diff --git a/examples/nuxt-app/public/robots.txt b/examples/nuxt-app/public/robots.txt new file mode 100644 index 0000000..0ad279c --- /dev/null +++ b/examples/nuxt-app/public/robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: diff --git a/examples/nuxt-app/server/tsconfig.json b/examples/nuxt-app/server/tsconfig.json new file mode 100644 index 0000000..b9ed69c --- /dev/null +++ b/examples/nuxt-app/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +} diff --git a/examples/nuxt-app/tsconfig.json b/examples/nuxt-app/tsconfig.json new file mode 100644 index 0000000..a746f2a --- /dev/null +++ b/examples/nuxt-app/tsconfig.json @@ -0,0 +1,4 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "extends": "./.nuxt/tsconfig.json" +} diff --git a/packages/steiger/package.json b/packages/steiger/package.json index 09fba59..8aa48df 100644 --- a/packages/steiger/package.json +++ b/packages/steiger/package.json @@ -54,6 +54,7 @@ "patronum": "^2.3.0", "picocolors": "^1.1.1", "prexit": "^2.3.0", + "tsconfck": "^3.1.4", "yargs": "^17.7.2", "zod": "^3.24.1", "zod-validation-error": "^3.4.0" diff --git a/packages/steiger/src/app.ts b/packages/steiger/src/app.ts index 05687d3..8d1f97b 100644 --- a/packages/steiger/src/app.ts +++ b/packages/steiger/src/app.ts @@ -8,6 +8,7 @@ import { $enabledRules, getEnabledRules, getGlobalIgnores } from './models/confi import { runRule } from './features/run-rule' import { removeGlobalIgnoreFromVfs } from './features/remove-global-ignores-from-vfs' import { calculateFinalSeverities } from './features/calculate-diagnostic-severities' +import { handleError } from './features/handle-error' // TODO: make this part of a plugin function getRuleDescriptionUrl(ruleName: string) { @@ -18,26 +19,30 @@ function getRuleDescriptionUrl(ruleName: string) { } async function runRules({ vfs, rules }: { vfs: Folder; rules: Array }) { - const vfsWithoutGlobalIgnores = removeGlobalIgnoreFromVfs(vfs, getGlobalIgnores()) + try { + const vfsWithoutGlobalIgnores = removeGlobalIgnoreFromVfs(vfs, getGlobalIgnores()) - const ruleResults = await Promise.all(rules.map((rule) => runRule(vfsWithoutGlobalIgnores, rule))) - return ruleResults.flatMap(({ diagnostics }, ruleResultsIndex) => { - const ruleName = rules[ruleResultsIndex].name - const severities = calculateFinalSeverities( - vfsWithoutGlobalIgnores, - ruleName, - diagnostics.map((d) => d.location.path), - ) - - return diagnostics - .sort((a, b) => a.location.path.localeCompare(b.location.path)) - .map((d, index) => ({ - ...d, + const ruleResults = await Promise.all(rules.map((rule) => runRule(vfsWithoutGlobalIgnores, rule))) + return ruleResults.flatMap(({ diagnostics }, ruleResultsIndex) => { + const ruleName = rules[ruleResultsIndex].name + const severities = calculateFinalSeverities( + vfsWithoutGlobalIgnores, ruleName, - getRuleDescriptionUrl, - severity: severities[index], - })) - }) + diagnostics.map((d) => d.location.path), + ) + + return diagnostics + .sort((a, b) => a.location.path.localeCompare(b.location.path)) + .map((d, index) => ({ + ...d, + ruleName, + getRuleDescriptionUrl, + severity: severities[index], + })) + }) + } catch (error) { + handleError(error, { vfs }) + } } export const linter = { diff --git a/packages/steiger/src/features/handle-error.ts b/packages/steiger/src/features/handle-error.ts new file mode 100644 index 0000000..f9d988e --- /dev/null +++ b/packages/steiger/src/features/handle-error.ts @@ -0,0 +1,48 @@ +import { readFileSync } from 'fs' +import { join } from 'path' +import { TSConfckParseError } from 'tsconfck' +import { Folder } from '@steiger/types' + +interface ErrorContext { + vfs: Folder +} + +interface PackageJson { + dependencies?: Record + devDependencies?: Record +} + +const readPackageJSON = (path: string) => JSON.parse(readFileSync(join(path, 'package.json'), 'utf-8')) as PackageJson + +async function checkNuxtConfigError(error: unknown, { vfs }: ErrorContext) { + if (!(error instanceof TSConfckParseError)) return false + if (error.code !== 'EXTENDS_RESOLVE') return false + if (!error.cause?.message?.includes('.nuxt/tsconfig')) return false + + try { + const projectRootPath = vfs.path + const packageJson = readPackageJSON(projectRootPath) + const hasNuxt = !!(packageJson.dependencies?.nuxt || packageJson.devDependencies?.nuxt) + if (!hasNuxt) return false + + console.error('\n\x1b[31mError: Unable to find Nuxt TypeScript configuration\x1b[0m') + console.error('\nThis appears to be a Nuxt project, but the TypeScript configuration files are missing.') + console.error('These files are auto-generated when you install dependencies.') + console.error('\nTo fix this:') + console.error('1. Run \x1b[36mnpm install\x1b[0m or \x1b[36myarn\x1b[0m or \x1b[36mpnpm install\x1b[0m') + console.error('2. Then run Steiger again\n') + return true + } catch { + return false + } +} + +export function handleError(error: unknown, context: ErrorContext): never { + const isHandled = [checkNuxtConfigError].some((checker) => checker(error, context)) + + if (isHandled) { + process.exit(1) + } + + throw error +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0001567..9924141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,6 +157,9 @@ importers: prexit: specifier: ^2.3.0 version: 2.3.0 + tsconfck: + specifier: ^3.1.4 + version: 3.1.4(typescript@5.7.3) yargs: specifier: ^17.7.2 version: 17.7.2