Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false
25 changes: 25 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Bug report
description: The gate crashed, or reported something wrong
labels: [bug]
body:
- type: textarea
id: what
attributes:
label: What happened
description: The command you ran and its output (or the relevant part of .harness/report.json).
validations:
required: true
- type: textarea
id: repro
attributes:
label: Minimal reproduction
description: The smallest file (and path, e.g. app/components/OpX.vue) that triggers it.
render: vue
- type: input
id: versions
attributes:
label: Versions
description: nuxt-harness, Nuxt, Node, package manager
placeholder: nuxt-harness 0.5.0, Nuxt 4.6.0, Node 22, pnpm 10
validations:
required: true
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Security vulnerability
url: https://github.com/edumapper/nuxt-harness/security/advisories/new
about: Report vulnerabilities privately, not in a public issue.
24 changes: 24 additions & 0 deletions .github/ISSUE_TEMPLATE/false_positive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: False positive
description: A rule flagged code that is correct
labels: [false-positive]
body:
- type: input
id: rule
attributes:
label: Rule
placeholder: arch/no-smart-calls
validations:
required: true
- type: textarea
id: code
attributes:
label: Code and file path
render: vue
validations:
required: true
- type: textarea
id: why
attributes:
label: Why it is correct
validations:
required: true
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/rule_proposal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Rule proposal
description: Suggest a new check, or a change to an existing one
labels: [rule-proposal]
body:
- type: textarea
id: problem
attributes:
label: The problem it catches
description: What goes wrong in real code (bonus points for agent-written code), and why review alone misses it.
validations:
required: true
- type: textarea
id: examples
attributes:
label: Invalid and valid examples
render: vue
validations:
required: true
- type: textarea
id: scope
attributes:
label: Default or opt-in?
description: Does it hold for any Nuxt 4 app, or does it depend on a module, library or team convention?
10 changes: 10 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## What and why

<!-- What changes, and the problem it solves. Link the issue if there is one. -->

## Checklist

- [ ] `bun run test` passes
- [ ] New or changed rules have valid and invalid cases in `test/rules.test.ts`
- [ ] Docs updated (`skills/nuxt-harness/references/`), `CHANGELOG.md` under *Unreleased*
- [ ] Stack-specific behavior is opt-in; a plain Nuxt 4 app still passes
22 changes: 18 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ permissions:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [22, 24]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0

- name: Tag matches package version
if: startsWith(github.ref, 'refs/tags/')
run: |
version="$(bun -p "require('./package.json').version")"
version="$(node -p "require('./package.json').version")"
test "${GITHUB_REF_NAME}" = "v${version}" \
|| { echo "::error::tag ${GITHUB_REF_NAME} != v${version}"; exit 1; }

Expand All @@ -32,8 +39,15 @@ jobs:
- name: Smoke test packed tarball
run: |
npm pack --pack-destination "$RUNNER_TEMP"
mkdir -p "$RUNNER_TEMP/app/app/components" && cd "$RUNNER_TEMP/app" && git init -q
tarball="$(ls "$RUNNER_TEMP"/edumapper-nuxt-harness-*.tgz)"

# The example app passes the gate as-is.
cp -r examples/basic "$RUNNER_TEMP/example" && cd "$RUNNER_TEMP/example" && git init -q
npx --yes --package="$tarball" nuxt-harness fast --all

# A violation fails it.
mkdir -p "$RUNNER_TEMP/bad/app/components" && cd "$RUNNER_TEMP/bad" && git init -q
printf '<script setup lang="ts">\nconst v: any = 1\n</script>\n' > app/components/Bad.vue
if npx --yes --package="$RUNNER_TEMP"/edumapper-nuxt-harness-*.tgz nuxt-harness fast --all; then
if npx --yes --package="$tarball" nuxt-harness fast --all; then
echo "::error::fast gate passed on a file with a violation"; exit 1
fi
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
[Semantic Versioning](https://semver.org/) (pre-1.0: minor versions may break).

## [Unreleased]

## [0.5.0]

### Breaking

- Design-system rules are opt-in and ship no tokens. `no-hardcoded-color`,
`no-off-palette-color-class` and `no-nested-border-box` only run with `designSystem` in the
config; the allowed palettes come from `designSystem.palettes`.
- `navigateTo('/…')` string paths are only reported when i18n is on (auto-detected from
`@nuxtjs/i18n`).
- `readBody` is the only body reader the static check knows; `readValidatedBody` is the
recommended fix.

### Added

- `nuxt-harness.config.{mjs,js,json}` and `harness(options)`: `srcDir`, `i18n`,
`authComposables`, `dataComposables`, `allowServerImportsInApp`, `designSystem`.
- Nuxt layers (`layers/<name>/…`) and the Nuxt 3 layout (`srcDir: '.'`) are covered.
- `no-presenter-in-page` recognizes directory-prefixed (`<ShopOrcCart>`), lazy and kebab-case
component names.
- `no-auth-gate-outside-middleware` defaults cover nuxt-auth-utils, @sidebase/nuxt-auth and
@nuxtjs/supabase; TanStack Query primitives are treated like Pinia Colada's.
- `full` runs scripts with the app's package manager and finds `vitest.config.{ts,mts,js,mjs}`.
- `examples/basic`, docs examples linted in CI, contributing guide, security policy.

### Changed

- All rule messages and docs are in English; doc links point to this repository.

## [0.4.0]

- `nuxt-harness stop`: Claude Code Stop hook that gates the end of a turn.

## [0.3.1]

- MIT license.

## [0.3.0]

- Standalone package with its own CI, tests and release tags.
59 changes: 59 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Contributing

Thanks for helping improve nuxt-harness. Bug reports, false positives, rule proposals and
pull requests are all welcome.

## Setup

```bash
git clone https://github.com/edumapper/nuxt-harness.git
cd nuxt-harness
bun install # npm install works too
bun run test
```

The source is plain ESM JavaScript with JSDoc types (`// @ts-check`), Node ≥ 22. There is no
build step: what's in `src/` is what ships.

## Layout

```
bin/nuxt-harness.js CLI entry
src/run.js gate orchestration: fast, full, hook, stop
src/config.js options, defaults, auto-detection, nuxt-harness.config loading
src/eslint.js flat config: harness(), typeAware(), standalone()
src/rules/ custom ESLint rules (the `arch/` plugin)
src/checks.js static checks (plain file scans)
src/crap.js CRAP score from complexity × vitest coverage
skills/nuxt-harness/ agent skill + reference docs (shipped in the package)
examples/basic/ minimal Nuxt 4 app that must pass the gate
test/ vitest: rules (RuleTester), checks, config, CLI hooks, docs
```

## Adding or changing a rule

1. Write the rule in `src/rules/<name>.js`. Messages follow `❌ what → 💡 why → 🛠 fix → 📖 doc`
and link to its section in `skills/nuxt-harness/references/eslint-rules.md` (`docsUrl()`).
2. Template rules must go through `context.sourceCode.parserServices.defineTemplateBodyVisitor`
— a top-level `VElement`/`VAttribute` visitor is never called.
3. Register it in `src/eslint.js`. Rules under `arch/` can't be disabled with `eslint-disable`;
a core or plugin rule the harness turns on must be added to `HARNESS_OWNED` in `src/checks.js`.
4. Add valid **and** invalid cases to `test/rules.test.ts`. Every template rule needs at least
one invalid case, so a visitor that never runs fails the suite.
5. Document it in `eslint-rules.md`. If it is stack-specific (an i18n module, an auth library, a
design system), make it opt-in through an option in `src/config.js`: the defaults must pass
any plain Nuxt 4 app (`test/hooks.test.ts` checks this).

Examples in `gold-standards.md` and `architecture.md` are linted by `test/docs.test.ts`; keep
them passing.

## Pull requests

- Use [Conventional Commits](https://www.conventionalcommits.org/) for titles (`feat:`, `fix:`, `docs:` …).
- Add a line to `CHANGELOG.md` under *Unreleased*.
- `bun run test` must pass.

## Releasing (maintainers)

1. Move *Unreleased* in `CHANGELOG.md` under the new version and bump `version` in `package.json`.
2. Merge to `main`, then push the tag `v<version>`. CI checks that the tag matches the version.
Loading
Loading