@hs-web-team/eslint-config-node 4.2.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ import markdown from '@eslint/markdown';
2
+ import maxLines from './rules/max-lines.js';
3
+
4
+ // First-party ESLint plugin holding lint rules for Claude Code context files:
5
+ // `CLAUDE.md` project-context files and the `SKILL.md` files under
6
+ // `.claude/skills/`. Rules are referenced as `claude-code/<rule>` wherever the
7
+ // plugin is registered.
8
+ export const claudeCodePlugin = {
9
+ meta: { name: 'claude-code' },
10
+ rules: {
11
+ 'max-lines': maxLines,
12
+ },
13
+ };
14
+
15
+ // Self-reference via `configs.recommended`, following the same pattern as
16
+ // hsWebTeamPlugin (and typescript-eslint / eslint-plugin-react). It carries the
17
+ // flat-config blocks that scope the rules to the right files. Severity is `warn`
18
+ // so it never blocks; consumers can bump a rule to `error` or turn it `off`.
19
+ //
20
+ // `@eslint/markdown` supplies the `markdown/gfm` language — without it ESLint
21
+ // would try to parse these files as JavaScript and error.
22
+ //
23
+ // Skills carry more detail than a `CLAUDE.md`, so `SKILL.md` gets a higher
24
+ // (500-line) guideline than `CLAUDE.md` (100 lines), with tailored advice.
25
+ claudeCodePlugin.configs = {
26
+ recommended: [
27
+ {
28
+ files: ['**/CLAUDE.md'],
29
+ language: 'markdown/gfm',
30
+ plugins: { markdown, 'claude-code': claudeCodePlugin },
31
+ rules: {
32
+ 'claude-code/max-lines': [
33
+ 'warn',
34
+ { max: 100, advice: 'Trim it or split sections into files Claude Code can import on demand.' },
35
+ ],
36
+ },
37
+ },
38
+ {
39
+ files: ['**/.claude/skills/**/SKILL.md'],
40
+ language: 'markdown/gfm',
41
+ plugins: { markdown, 'claude-code': claudeCodePlugin },
42
+ rules: {
43
+ 'claude-code/max-lines': [
44
+ 'warn',
45
+ { max: 500, advice: 'Trim it or move detail into reference files the skill loads on demand.' },
46
+ ],
47
+ },
48
+ },
49
+ ],
50
+ };
@@ -0,0 +1,52 @@
1
+ const DEFAULT_MAX = 100;
2
+ const DEFAULT_ADVICE = 'Trim it or split sections into files Claude Code can import on demand.';
3
+
4
+ export default {
5
+ meta: {
6
+ type: 'suggestion',
7
+ docs: {
8
+ description: 'Warn when a doc file exceeds a maximum number of lines',
9
+ recommended: true,
10
+ },
11
+ schema: [
12
+ {
13
+ type: 'object',
14
+ properties: {
15
+ max: { type: 'integer', minimum: 1 },
16
+ advice: { type: 'string' },
17
+ },
18
+ additionalProperties: false,
19
+ },
20
+ ],
21
+ messages: {
22
+ tooManyLines: '{{file}} has {{count}} lines, over the {{max}}-line guideline. {{advice}}',
23
+ },
24
+ },
25
+ create(context) {
26
+ const { max = DEFAULT_MAX, advice = DEFAULT_ADVICE } = context.options[0] || {};
27
+ const { sourceCode } = context;
28
+ // Report against the file's basename so the message names the offending
29
+ // file (e.g. CLAUDE.md or SKILL.md) regardless of its directory.
30
+ const file = (context.filename || '').split('/').pop() || 'File';
31
+
32
+ return {
33
+ root() {
34
+ const { lines } = sourceCode;
35
+ // `lines` splits on line breaks; a trailing newline yields a final
36
+ // empty entry that should not count as a content line.
37
+ const count = lines.length > 0 && lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
38
+
39
+ if (count > max) {
40
+ context.report({
41
+ loc: {
42
+ start: { line: max + 1, column: 0 },
43
+ end: { line: lines.length, column: lines[lines.length - 1].length },
44
+ },
45
+ messageId: 'tooManyLines',
46
+ data: { file, count, max, advice },
47
+ });
48
+ }
49
+ },
50
+ };
51
+ },
52
+ };
@@ -0,0 +1,21 @@
1
+ import { noAbbreviations } from './rules/no-abbreviations.js';
2
+ import { noReduceAccumulatorCopy } from './rules/no-reduce-accumulator-copy.js';
3
+
4
+ export const hsWebTeamPlugin = {
5
+ rules: {
6
+ 'no-abbreviations': noAbbreviations,
7
+ 'no-reduce-accumulator-copy': noReduceAccumulatorCopy,
8
+ },
9
+ };
10
+
11
+ // Self-reference allows the plugin to register itself via configs.recommended,
12
+ // following the same pattern as typescript-eslint and eslint-plugin-react.
13
+ hsWebTeamPlugin.configs = {
14
+ recommended: {
15
+ plugins: { 'hs-web-team': hsWebTeamPlugin },
16
+ rules: {
17
+ 'hs-web-team/no-abbreviations': 'warn',
18
+ 'hs-web-team/no-reduce-accumulator-copy': 'error',
19
+ },
20
+ },
21
+ };
@@ -0,0 +1,135 @@
1
+ const DEFAULT_EXCEPTIONS = new Set(['e', '_']);
2
+ const DEFAULT_MIN_LENGTH = 2;
3
+
4
+ const SORT_METHODS = new Set(['sort', 'toSorted']);
5
+ const ARRAY_ITERATION_METHODS = new Set([
6
+ 'map',
7
+ 'flatMap',
8
+ 'forEach',
9
+ 'filter',
10
+ 'find',
11
+ 'findIndex',
12
+ 'findLast',
13
+ 'findLastIndex',
14
+ 'some',
15
+ 'every',
16
+ // reduce/reduceRight are intentionally excluded: their callback signature is
17
+ // (accumulator, currentValue, currentIndex, …), so position 1 is the current
18
+ // *value*, not the index. The i-at-position-1 heuristic does not apply.
19
+ ]);
20
+
21
+ /**
22
+ * Returns true if the VariableDeclarator is in the init of a for/for...of/for...in statement.
23
+ * Walks the parent chain directly — no scope API needed.
24
+ *
25
+ * @param {import('eslint').Rule.Node} declaratorNode - VariableDeclarator node
26
+ */
27
+ function isDeclaredInForLoopInit(declaratorNode) {
28
+ const declaration = declaratorNode.parent; // VariableDeclaration
29
+ const enclosing = declaration?.parent;
30
+ if (!enclosing) return false;
31
+ return (
32
+ (enclosing.type === 'ForStatement' && enclosing.init === declaration) ||
33
+ enclosing.type === 'ForInStatement' ||
34
+ enclosing.type === 'ForOfStatement'
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Returns true if funcNode is a direct inline callback to a method whose name is in methodNames.
40
+ * Only matches non-computed method calls (arr.sort(...), not arr['sort'](...)).
41
+ *
42
+ * @param {import('eslint').Rule.Node} funcNode - ArrowFunctionExpression or FunctionExpression
43
+ * @param {Set<string>} methodNames
44
+ */
45
+ function isCallbackToMethod(funcNode, methodNames) {
46
+ const callExpr = funcNode.parent;
47
+ if (callExpr?.type !== 'CallExpression') return false;
48
+ if (!callExpr.arguments.includes(funcNode)) return false;
49
+ const { callee } = callExpr;
50
+ return (
51
+ callee?.type === 'MemberExpression' &&
52
+ !callee.computed &&
53
+ methodNames.has(callee.property.name)
54
+ );
55
+ }
56
+
57
+ /** @type {import('eslint').Rule.RuleModule} */
58
+ export const noAbbreviations = {
59
+ meta: {
60
+ type: 'suggestion',
61
+ docs: {
62
+ description: 'Disallow abbreviated identifier names (e.g. `w` instead of `warnings`)',
63
+ url: 'https://docs.hubwt.com/docs/developers/coding-on-the-web-team/coding-standards/javascript-standards/#avoid-abbreviation',
64
+ recommended: true,
65
+ },
66
+ messages: {
67
+ tooShort:
68
+ 'Identifier "{{name}}" is too short ({{length}} < {{min}}). Use a descriptive name.',
69
+ },
70
+ schema: [
71
+ {
72
+ type: 'object',
73
+ properties: {
74
+ exceptions: { type: 'array', items: { type: 'string' }, uniqueItems: true },
75
+ minLength: { type: 'integer', minimum: 1 },
76
+ },
77
+ additionalProperties: false,
78
+ },
79
+ ],
80
+ },
81
+
82
+ create(context) {
83
+ const options = context.options[0] ?? {};
84
+ const exceptions = new Set([...DEFAULT_EXCEPTIONS, ...(options.exceptions ?? [])]);
85
+ const minLength = options.minLength ?? DEFAULT_MIN_LENGTH;
86
+
87
+ return {
88
+ Identifier(node) {
89
+ const { name, parent } = node;
90
+
91
+ if (name.length >= minLength) return;
92
+ if (exceptions.has(name)) return;
93
+
94
+ // Only flag declaration sites — not usage sites.
95
+ //
96
+ // Object destructuring shorthand (const { w } = response) is intentionally
97
+ // excluded: `w` may be an external API property name the developer doesn't
98
+ // control. The idiomatic fix — const { w: warning } = response — is correct
99
+ // but non-obvious enough to leave as opt-in. Array destructuring IS included
100
+ // because the element binding name is always chosen by the developer.
101
+ const isDeclaration =
102
+ (parent.type === 'VariableDeclarator' && parent.id === node) ||
103
+ (parent.type === 'ArrayPattern' && parent.elements.includes(node)) ||
104
+ (parent.type === 'FunctionDeclaration' && parent.id === node) ||
105
+ (parent.type === 'FunctionDeclaration' && parent.params.includes(node)) ||
106
+ (parent.type === 'ArrowFunctionExpression' && parent.params.includes(node)) ||
107
+ (parent.type === 'FunctionExpression' && parent.params.includes(node));
108
+
109
+ if (!isDeclaration) return;
110
+
111
+ // For-loop variables (i, j, k…) are exempt regardless of length.
112
+ if (parent.type === 'VariableDeclarator' && isDeclaredInForLoopInit(parent)) return;
113
+
114
+ // a and b are conventional sort-comparator names — exempt only inside .sort()/.toSorted().
115
+ if ((name === 'a' || name === 'b') && isCallbackToMethod(parent, SORT_METHODS)) return;
116
+
117
+ // i is a conventional index name — exempt only when it is the index param (position 1)
118
+ // of an array iteration callback: items.map((item, i) => ...).
119
+ // Position 1 is the index slot for every array iteration method except reduce/reduceRight
120
+ // (where it's position 2), but naming the reduce index `i` is uncommon enough to skip.
121
+ if (
122
+ name === 'i' &&
123
+ parent.params?.indexOf(node) === 1 &&
124
+ isCallbackToMethod(parent, ARRAY_ITERATION_METHODS)
125
+ ) return;
126
+
127
+ context.report({
128
+ node,
129
+ messageId: 'tooShort',
130
+ data: { name, length: name.length, min: minLength },
131
+ });
132
+ },
133
+ };
134
+ },
135
+ };
@@ -0,0 +1,90 @@
1
+ const RECOMMENDATION =
2
+ 'Prefer a for...of loop with in-place mutation, or a purpose-built method (flatMap, Object.groupBy, flat) if it fits your pattern.';
3
+
4
+ function isReduceCall(node) {
5
+ if (!node) return false;
6
+
7
+ const callee = node.callee;
8
+ if (!callee || !callee.property || !callee.property.name) {
9
+ return false;
10
+ }
11
+
12
+ return (
13
+ callee.property.name === 'reduce' || callee.property.name === 'reduceRight'
14
+ );
15
+ }
16
+
17
+ function isReduceAccumulator(def) {
18
+ return (
19
+ def &&
20
+ def.type === 'Parameter' &&
21
+ def.node.parent &&
22
+ isReduceCall(def.node.parent) &&
23
+ def.node.params &&
24
+ def.node.params.length &&
25
+ // def.name is the Identifier AST node for the definition; this identity
26
+ // check confirms it is the first parameter (the accumulator), not a later one.
27
+ def.name === def.node.params[0]
28
+ );
29
+ }
30
+
31
+ export const noReduceAccumulatorCopy = {
32
+ meta: {
33
+ type: 'problem',
34
+ docs: {
35
+ description:
36
+ 'Disallow O(n²) accumulator copies (spread, concat) inside reduce/reduceRight callbacks',
37
+ recommended: true,
38
+ },
39
+ messages: {
40
+ AccumulatorSpread: `Spreading the reduce accumulator is O(n²). ${RECOMMENDATION}`,
41
+ AccumulatorShallowCopy: `Calling .{{ method }}() on the reduce accumulator is O(n²). ${RECOMMENDATION}`,
42
+ },
43
+ schema: [],
44
+ },
45
+
46
+ create(context) {
47
+ const { sourceCode } = context;
48
+
49
+ function checkIsAccumulatorSpread(node) {
50
+ if (!node.argument || node.argument.type !== 'Identifier') return;
51
+
52
+ const scope = sourceCode.getScope(node);
53
+ const spreadee = scope.set.get(node.argument.name);
54
+ const spreadeeDef =
55
+ spreadee && spreadee.defs && spreadee.defs.length && spreadee.defs[0];
56
+
57
+ if (isReduceAccumulator(spreadeeDef)) {
58
+ context.report({ node, messageId: 'AccumulatorSpread' });
59
+ }
60
+ }
61
+
62
+ function checkIsAccumulatorShallowCopy(node) {
63
+ if (
64
+ node.callee.type !== 'MemberExpression' ||
65
+ !node.callee.property ||
66
+ !node.callee.object ||
67
+ node.callee.object.type !== 'Identifier'
68
+ ) {
69
+ return;
70
+ }
71
+
72
+ const method = node.callee.property.name;
73
+ if (method !== 'concat') return;
74
+
75
+ const scope = sourceCode.getScope(node);
76
+ const variable = scope.set.get(node.callee.object.name);
77
+ const definition =
78
+ variable && variable.defs && variable.defs.length && variable.defs[0];
79
+
80
+ if (isReduceAccumulator(definition)) {
81
+ context.report({ node, messageId: 'AccumulatorShallowCopy', data: { method } });
82
+ }
83
+ }
84
+
85
+ return {
86
+ SpreadElement: checkIsAccumulatorSpread,
87
+ CallExpression: checkIsAccumulatorShallowCopy,
88
+ };
89
+ },
90
+ };
@@ -1,28 +0,0 @@
1
- #!/bin/sh
2
-
3
- commit_msg=$(head -1 "$1")
4
-
5
- # Skip merge and revert commits (auto-generated messages)
6
- case "$commit_msg" in
7
- Merge\ *|Revert\ *) exit 0 ;;
8
- esac
9
-
10
- pattern="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-zA-Z0-9_/-]+\))?!?: .+"
11
-
12
- if ! echo "$commit_msg" | grep -qE "$pattern"; then
13
- echo ""
14
- echo "ERROR: Commit message does not follow Conventional Commits format."
15
- echo ""
16
- echo " Expected: type(optional-scope): description"
17
- echo " Types: feat | fix | docs | style | refactor | test | chore | ci | perf | revert | build"
18
- echo ""
19
- echo " Examples:"
20
- echo " feat: add new rule for import order"
21
- echo " fix: correct comma-dangle config for browser"
22
- echo " feat!: drop support for Node 18 (breaking change -> major bump)"
23
- echo " feat(browser): add React 19 support"
24
- echo ""
25
- echo " See: https://www.conventionalcommits.org"
26
- echo ""
27
- exit 1
28
- fi
@@ -1 +0,0 @@
1
- * @HubSpotWebTeam/webteamdevs
@@ -1,24 +0,0 @@
1
- name: Merge to Main
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- workflow_dispatch:
7
-
8
- jobs:
9
- build:
10
- # The type of runner that the job will run on
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- node: [24]
15
-
16
- name: Node ${{ matrix.node }} lint
17
- steps:
18
- - uses: actions/checkout@v6
19
- - name: Run unit tests
20
- uses: actions/setup-node@v6
21
- with:
22
- node-version: ${{ matrix.node }}
23
- - run: npm install
24
- - run: npm run lint
@@ -1,24 +0,0 @@
1
- name: Pull Request
2
-
3
- on:
4
- pull_request:
5
- branches: [main]
6
- workflow_dispatch:
7
-
8
- jobs:
9
- build:
10
- # The type of runner that the job will run on
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- node: [24]
15
-
16
- name: Node ${{ matrix.node }} sample
17
- steps:
18
- - uses: actions/checkout@v6
19
- - name: Run unit tests
20
- uses: actions/setup-node@v6
21
- with:
22
- node-version: ${{ matrix.node }}
23
- - run: npm install
24
- - run: npm run lint
@@ -1,44 +0,0 @@
1
- name: Release Please
2
-
3
- on:
4
- push:
5
- branches: [main]
6
-
7
- permissions:
8
- contents: write
9
- pull-requests: write
10
- id-token: write # Required for OIDC npm publish
11
-
12
- jobs:
13
- release-please:
14
- runs-on: ubuntu-latest
15
- outputs:
16
- release_created: ${{ steps.release.outputs.release_created }}
17
- steps:
18
- - uses: googleapis/release-please-action@v5
19
- id: release
20
- with:
21
- token: ${{ secrets.GITHUB_TOKEN }}
22
-
23
- publish:
24
- needs: release-please
25
- if: ${{ needs.release-please.outputs.release_created == 'true' }}
26
- runs-on: ubuntu-latest
27
- steps:
28
- - uses: actions/checkout@v6
29
- - uses: actions/setup-node@v6
30
- with:
31
- node-version: 24
32
- registry-url: 'https://registry.npmjs.org'
33
- - run: npm ci
34
- - run: npm run build --if-present
35
- - name: Publish to npm
36
- run: |
37
- VERSION=$(node -p "require('./package.json').version")
38
- if [[ "$VERSION" =~ -next\. ]] || [[ "$VERSION" =~ -(alpha|beta|rc)\. ]]; then
39
- echo "Publishing prerelease $VERSION with tag 'next'"
40
- npm publish --tag next
41
- else
42
- echo "Publishing stable $VERSION"
43
- npm publish
44
- fi
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- v24
@@ -1,3 +0,0 @@
1
- {
2
- ".": "4.2.1"
3
- }
package/CHANGELOG.md DELETED
@@ -1,15 +0,0 @@
1
- # Changelog
2
-
3
- ## [4.2.1](https://github.com/HubSpotWebTeam/wt-eslint-node/compare/v4.2.0...v4.2.1) (2026-06-05)
4
-
5
-
6
- ### Bug Fixes
7
-
8
- * re-trigger publish after release-please CI fix ([45fe949](https://github.com/HubSpotWebTeam/wt-eslint-node/commit/45fe94930c06d61e6f8f3015bc4e6518e187aaa1))
9
-
10
- ## [4.2.0](https://github.com/HubSpotWebTeam/wt-eslint-node/compare/v4.1.0...v4.2.0) (2026-06-05)
11
-
12
-
13
- ### Features
14
-
15
- * warn on hardcoded color, font-size, and font-weight values ([#37](https://github.com/HubSpotWebTeam/wt-eslint-node/issues/37)) ([783a320](https://github.com/HubSpotWebTeam/wt-eslint-node/commit/783a320a3d0e858cd370670be452aaa847ba9c39))
package/CLAUDE.md DELETED
@@ -1,145 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Overview
6
-
7
- This is a configuration package (`@hs-web-team/eslint-config-node`) for HubSpot Marketing WebTeam projects. It provides shared configurations for:
8
- - **ESLint** rules for Node.js and browser/React projects
9
- - **Prettier** formatting configuration
10
- - **Stylelint** rules for SCSS/CSS linting
11
- - **Cypress** E2E testing configuration
12
-
13
- ## Key Commands
14
-
15
- ### Linting
16
-
17
- ```bash
18
- npm run lint
19
- ```
20
-
21
- Runs ESLint on the codebase with auto-fix enabled. Uses the configuration defined in `index.js`.
22
-
23
- ### Testing in Projects
24
- When testing changes to this package in downstream projects, you'll typically:
25
- 1. Make changes to the ESLint config in `index.js`
26
- 2. Run `npm link` in this repository
27
- 3. Run `npm link @hs-web-team/eslint-config-node` in the consuming project
28
- 4. Test the linting behavior in the consuming project
29
-
30
- ## Architecture
31
-
32
- ### Core Configuration Files
33
-
34
- #### `index.js` - Node.js Configuration
35
- - The main ESLint configuration export using ESLint 9's flat config format
36
- - Uses `@eslint/js`, `typescript-eslint`, and `globals` packages
37
- - Exports an array of configuration objects (flat config format)
38
- - Structure:
39
- 1. Global ignores object
40
- 2. `js.configs.recommended` for JavaScript baseline
41
- 3. JavaScript-specific config for `**/*.{js,mjs,cjs}` files
42
- 4. Spreads `tseslint.configs.recommended` for TypeScript baseline
43
- 5. TypeScript-specific config for `**/*.{ts,mts,cts,tsx}` files
44
- - Includes Node.js, ES2022, and Jest globals
45
- - Common ignores: `node_modules`, `.serverless`, `.webpack`, `dist`, `eslint.config.js`
46
- - Key custom rules: `no-console` (allows info/warn/error), `max-len` (120 chars), camelcase disabled
47
-
48
- #### `browser.js` - Browser/React Configuration
49
- - Browser and React-focused ESLint configuration using ESLint 9's flat config format
50
- - Uses `@eslint/js`, `typescript-eslint`, `globals`, and React-specific plugins
51
- - Includes `eslint-plugin-react`, `eslint-plugin-react-hooks`, and `eslint-plugin-jsx-a11y`
52
- - Supports both JavaScript/JSX and TypeScript/TSX files
53
- - Structure:
54
- 1. Global ignores (node_modules, dist, build, .next, coverage)
55
- 2. Base JavaScript/JSX config with browser globals
56
- 3. React plugin configuration
57
- 4. TypeScript config with browser globals
58
- 5. React plugin configuration for TypeScript files
59
- - Includes browser, ES2021, and Jest globals, plus custom globals ($, jQuery, Invoca)
60
- - Key custom rules: Similar base rules to Node config, plus React-specific rules
61
-
62
- ### Prettier Integration
63
- - **`bin/add-prettier-scripts.js`**: Executable script that consuming projects can run to set up Prettier
64
- - Uses CommonJS (`require`) syntax (different from the main package which is ESM)
65
- - Adds `prettier:check` and `prettier:write` scripts to package.json
66
- - Creates `.prettierrc.js` file that imports from this package's `.prettierrc.json`
67
- - Creates `.prettierignore` file with sensible defaults (npm-shrinkwrap.json, package-lock.json, .eslintrc, *.html)
68
- - **`.prettierrc.json`**: Shared Prettier configuration
69
- - 120 char width, single quotes, 2-space tabs, trailing commas
70
- - Arrow parens: avoid, LF line endings, single attribute per line
71
- - Override for YAML files: uses double quotes instead of single
72
-
73
- ### Stylelint Configuration
74
- - **`.stylelintrc.json`**: Shared Stylelint configuration for SCSS/CSS linting
75
- - Extends `stylelint-config-standard-scss` for standard SCSS linting rules
76
- - Customized rules for HubSpot projects:
77
- - Disables strict class name patterns and variable naming patterns
78
- - Allows zero units (e.g., `0px`), redundant longhand properties, and duplicate selectors
79
- - Configures SCSS-specific rules to ignore common directives (`@function`, `@if`, `@mixin`, etc.)
80
- - Enforces lowercase keywords with camelCase for SVG keywords
81
- - Sets media feature range notation to "context" with warning severity
82
- - Consuming projects extend this via `.stylelintrc.json`: `{ "extends": "@hs-web-team/eslint-config-node/.stylelintrc.json" }`
83
- - Documentation: `examples/stylelint-usage.md`
84
-
85
- ### Cypress Configuration
86
- - **`cypress.config.cjs`**: Shared Cypress configuration for E2E testing
87
- - Uses CommonJS (`require`) syntax
88
- - Core settings:
89
- - Disables Chrome web security for cross-origin testing
90
- - Timeouts: 20s default command, page load, and response timeouts
91
- - Viewport: 1920x1080
92
- - Retries: 1 retry in run mode, 0 in open mode
93
- - Screenshots on failure enabled, video disabled by default
94
- - Uses **esbuild** preprocessor for fast bundling (much faster than webpack)
95
- - Includes Cucumber preprocessor setup with native esbuild support
96
- - Native TypeScript support via esbuild (no additional loader needed)
97
- - `@badeball/cypress-cucumber-preprocessor`, `@bahmutov/cypress-esbuild-preprocessor`, `esbuild`, and `js-yaml` are all bundled — consumers only need to install `cypress`
98
- - Spec pattern: `cypress/e2e/*.cy.js`
99
- - Exported utilities:
100
- - `config`: Main Cypress configuration object
101
- - `envs`: Environment constants (DEV, QA, PROD)
102
- - `getDevBaseUrl()`: Reads baseUrl from `hubspot.config.yml` DEV portal
103
- - `getBaseUrls()`: Reads URLs from `.ci/config.yml` for multiple environments
104
- - HubSpot-specific: Designed to work with HubSpot CMS projects using `hubspot.config.yml`
105
- - Documentation: `examples/cypress-usage.md`
106
-
107
- ### Package Details
108
- - **Type**: ESM module (`"type": "module"`)
109
- - **Node requirement**: >= 24
110
- - **Exports**:
111
- - Main export (`.`): `index.js` - Node.js ESLint configuration
112
- - Browser export (`./browser`): `browser.js` - Browser/React ESLint configuration
113
- - Prettier export (`./.prettierrc.json`): `.prettierrc.json` - Prettier configuration
114
- - Stylelint export (`./.stylelintrc.json`): `.stylelintrc.json` - Stylelint configuration
115
- - Cypress export (`./cypress.config`): `cypress.config.cjs` (runtime), `cypress.config.d.ts` (types)
116
- - **Binary command**: `add-prettier` maps to `bin/add-prettier-scripts.js`
117
-
118
- ### Migration Context
119
- This package is currently on v3, which uses ESLint 9's flat config format. The project has migrated from:
120
- - v1 → v2: See `docs/MIGRATION-V2.md`
121
- - v2 → v3: See `docs/MIGRATION-V3.md` (ESLint 9 flat config migration requiring Node.js 24+)
122
-
123
- ## Important Notes
124
-
125
- - This package supports multiple use cases:
126
- - **ESLint**: Backend Node.js projects (default export) and browser/React projects (`/browser` export)
127
- - **Prettier**: Code formatting (`./.prettierrc.json` export)
128
- - **Stylelint**: SCSS/CSS linting (`./.stylelintrc.json` export)
129
- - **Cypress**: E2E testing (`./cypress.config` export)
130
- - ESLint configuration uses ESLint 9's flat config format (not the legacy `.eslintrc` format)
131
- - Downstream projects extend configs:
132
- - ESLint (Node.js): `...wtConfig` in `eslint.config.js`
133
- - ESLint (Browser): `...wtBrowserConfig` in `eslint.config.js`
134
- - Prettier: Import via `.prettierrc.js` using `require('@hs-web-team/eslint-config-node/.prettierrc.json')`
135
- - Stylelint: Extend in `.stylelintrc.json` with `"extends": "@hs-web-team/eslint-config-node/.stylelintrc.json"`
136
- - Cypress: Import and spread in `cypress.config.js`
137
- - Mixed module systems:
138
- - Main package is ESM (`index.js`, `browser.js`)
139
- - Utility scripts use CommonJS (`bin/add-prettier-scripts.js`, `cypress.config.cjs`)
140
- - CI runs on Node 24 (see `.github/workflows/pr.yml`)
141
- - No automated tests currently (`npm test` will fail with "Error: no test specified")
142
- - Detailed documentation available in `examples/`:
143
- - `browser-usage.md` - Browser/React ESLint configuration
144
- - `stylelint-usage.md` - Stylelint configuration
145
- - `cypress-usage.md` - Cypress E2E testing configuration
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
- "release-type": "node",
4
- "include-component-in-tag": false,
5
- "packages": {
6
- ".": {}
7
- }
8
- }