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