node@v1
Hygiene checks for Node.js / npm / pnpm / yarn projects. Adopt it with:
extends: - alint://bundled/node@v1 Gated with when: facts.has_node (true if any package.json
exists anywhere in the tree) plus a per-rule
scope_filter: { has_ancestor: package.json } on per-file
content rules so they only apply to files inside a Node package
— useful in polyglot monorepos where Node packages sit
alongside Rust / Python / Go subdirectories. Override
has_node with your own facts: block if you need a different
heuristic (e.g. detect deno.json or bun.lock).
What it checks
Section titled "What it checks"8 rules. Each links to its section below, which explains the check and shows its definition.
| Rule | Reports |
|---|---|
node-package-json-existserror | Node project: package.json at the root is required. |
node-has-lockfilewarning | A lockfile should be committed (package-lock.json / pnpm-lock.yaml / yarn.lock / bun.lock). |
node-no-tracked-node-moduleserror | node_modules/ must not be committed; add it to .gitignore. |
node-no-tracked-distinfo | Build-output directories shouldn't be tracked. |
node-engine-or-nvmrcinfo | Pin the Node.js version with .nvmrc, .node-version, or .tool-versions so local and CI installs match. |
node-sources-final-newlineinfo | File must end with a single \n. |
node-sources-no-trailing-whitespaceinfo | No line may end with space or tab. |
node-sources-no-bidierror | Trojan Source (CVE-2021-42574): bidi override chars are rejected in JS/TS sources. |
All 8 rules run only when facts.has_node holds, so the ruleset stays quiet in repositories it doesn't apply to.
node-package-json-exists
Section titled "node-package-json-exists"- kind:
file_exists - level:
error - when:
facts.has_node
Node project: package.json at the root is required.
- id: node-package-json-exists when: facts.has_node kind: file_exists paths: package.json root_only: true level: error message: "Node project: package.json at the root is required."node-has-lockfile
Section titled "node-has-lockfile"Accept any of the four common lockfiles — npm, pnpm, yarn, bun. At least one should be committed for reproducible installs.
- kind:
file_exists - level:
warning - when:
facts.has_node - policy: https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json
A lockfile should be committed (package-lock.json / pnpm-lock.yaml / yarn.lock / bun.lock).
- id: node-has-lockfile when: facts.has_node kind: file_exists paths: - "package-lock.json" - "pnpm-lock.yaml" - "yarn.lock" - "bun.lock" - "bun.lockb" root_only: true level: warning message: "A lockfile should be committed (package-lock.json / pnpm-lock.yaml / yarn.lock / bun.lock)." policy_url: "https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json" node-no-tracked-node-modules
Section titled "node-no-tracked-node-modules"- kind:
dir_absent - level:
error - when:
facts.has_node
node_modules/must not be committed; add it to .gitignore.
- id: node-no-tracked-node-modules when: facts.has_node kind: dir_absent paths: "**/node_modules" level: error message: "`node_modules/` must not be committed; add it to .gitignore." node-no-tracked-dist
Section titled "node-no-tracked-dist"Common build-output directory names. Users with legitimate reasons to ship a built dist/ (e.g. a typed-package preview) can set this rule's level: off.
Changed in v0.9.18: gated on has_ancestor: package.json to scope the check per-JS-package — without this gate the rule fires on every dist/ repo-wide once any package.json exists, which causes false positives in polyglot monorepos (Rust crate source dirs named dist/, etc.).
- kind:
dir_absent - level:
info - when:
facts.has_node
Build-output directories shouldn't be tracked. Set
level: offif this one is intentionally shipped.
- id: node-no-tracked-dist when: facts.has_node kind: dir_absent paths: ["**/dist" , "**/.next" , "**/.nuxt" , "**/coverage" , "**/.turbo" ] scope_filter: has_ancestor: package.json level: info message: >- Build-output directories shouldn't be tracked. Set `level: off` if this one is intentionally shipped.node-engine-or-nvmrc
Section titled "node-engine-or-nvmrc"- kind:
file_exists - level:
info - when:
facts.has_node
Pin the Node.js version with
.nvmrc,.node-version, or.tool-versionsso local and CI installs match.
- id: node-engine-or-nvmrc when: facts.has_node kind: file_exists paths: [".nvmrc", ".node-version", ".tool-versions"] root_only: true level: info message: >- Pin the Node.js version with `.nvmrc`, `.node-version`, or `.tool-versions` so local and CI installs match.node-sources-final-newline
Section titled "node-sources-final-newline"- kind:
final_newline - level:
info - when:
facts.has_node - fix:
file_append_final_newline(applied byalint fix)
- id: node-sources-final-newline when: facts.has_node kind: final_newline paths: ["src/**/*.{js,jsx,ts,tsx,mjs,cjs}" , "lib/**/*.{js,jsx,ts,tsx,mjs,cjs}" ] scope_filter: has_ancestor: package.json level: info fix: file_append_final_newline: {}node-sources-no-trailing-whitespace
Section titled "node-sources-no-trailing-whitespace"- kind:
no_trailing_whitespace - level:
info - when:
facts.has_node - fix:
file_trim_trailing_whitespace(applied byalint fix)
- id: node-sources-no-trailing-whitespace when: facts.has_node kind: no_trailing_whitespace paths: ["src/**/*.{js,jsx,ts,tsx,mjs,cjs}" , "lib/**/*.{js,jsx,ts,tsx,mjs,cjs}" ] scope_filter: has_ancestor: package.json level: info fix: file_trim_trailing_whitespace: {}node-sources-no-bidi
Section titled "node-sources-no-bidi"- kind:
no_bidi_controls - level:
error - when:
facts.has_node - policy: https://trojansource.codes/
Trojan Source (CVE-2021-42574): bidi override chars are rejected in JS/TS sources.
- id: node-sources-no-bidi when: facts.has_node kind: no_bidi_controls paths: ["src/**/*.{js,jsx,ts,tsx,mjs,cjs}" , "lib/**/*.{js,jsx,ts,tsx,mjs,cjs}" ] scope_filter: has_ancestor: package.json level: error message: "Trojan Source (CVE-2021-42574): bidi override chars are rejected in JS/TS sources." policy_url: "https://trojansource.codes/" The when: clauses above read these facts. Each is resolved once per run; alint facts prints what they resolved to in your repository.
facts: - id: has_node any_file_exists: [package.json, "**/package.json" ]Customize
Section titled "Customize"Every rule here can be overridden by id from your own .alint.yml: change its level, or set level: off to drop it. An id that doesn't exist is an error at config load, so a typo can't silently pass.
extends: - alint://bundled/node@v1 rules: - id: node-package-json-exists level: off - id: node-has-lockfile level: errorTo take only part of the ruleset, filter it where you extend it with only: or except::
extends: - url: alint://bundled/node@v1 except: [node-package-json-exists]Bundled rulesets covers versioning and how rulesets combine.
Source
Section titled "Source"The full ruleset definition, comments included, is committed at crates/alint-dsl/rulesets/v1/node.yml in the alint repo.