@hs-web-team/eslint-config-node 4.0.0 → 4.1.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.
- package/.githooks/commit-msg +28 -0
- package/.github/workflows/release-please.yml +17 -0
- package/.release-please-manifest.json +3 -0
- package/.stylelintrc.json +14 -3
- package/README.md +55 -0
- package/bin/check-peer-deps.cjs +78 -0
- package/browser.js +35 -35
- package/index.js +2 -2
- package/package.json +4 -2
- package/release-please-config.json +8 -0
|
@@ -0,0 +1,28 @@
|
|
|
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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: googleapis/release-please-action@v5
|
|
16
|
+
with:
|
|
17
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
package/.stylelintrc.json
CHANGED
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"scss/dollar-variable-pattern": null,
|
|
14
|
-
"
|
|
15
|
-
|
|
14
|
+
"selector-class-pattern": [
|
|
15
|
+
"^-?[a-z0-9]([a-z0-9]|-(?!-))*$",
|
|
16
|
+
{
|
|
17
|
+
"message": "Class names should only contain lowercase alphanumeric characters and hyphens, with no consecutive hyphens",
|
|
18
|
+
"severity": "warning"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
16
21
|
"declaration-block-no-redundant-longhand-properties": null,
|
|
17
|
-
"scss/at-import-partial-extension": null,
|
|
18
22
|
"alpha-value-notation": null,
|
|
19
23
|
"scss/percent-placeholder-pattern": null,
|
|
20
24
|
"scss/at-mixin-argumentless-call-parentheses": null,
|
|
@@ -35,6 +39,13 @@
|
|
|
35
39
|
{
|
|
36
40
|
"camelCaseSvgKeywords": true
|
|
37
41
|
}
|
|
42
|
+
],
|
|
43
|
+
"selector-disallowed-list": [
|
|
44
|
+
["/(?:^|[\\s>+~])\\s*\\.-[a-z]/i"],
|
|
45
|
+
{
|
|
46
|
+
"message": "Modifier classes (starting with '-') must not be standalone selectors; always combine with a base class (e.g., .prefix-element.-modifier or &.-modifier in nested rules)",
|
|
47
|
+
"severity": "error"
|
|
48
|
+
}
|
|
38
49
|
]
|
|
39
50
|
}
|
|
40
51
|
}
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ This package provides ESLint rules and configurations for **Hubspot Marketing We
|
|
|
13
13
|
- [Accessibility Testing](#accessibility-testing-optional)
|
|
14
14
|
- [Where to use it](#where-to-use-it)
|
|
15
15
|
- [Using the Prettier Scripts](#using-the-prettier-scripts)
|
|
16
|
+
- [Contributing](#contributing)
|
|
16
17
|
<!-- index-end -->
|
|
17
18
|
|
|
18
19
|
## Node.js Setup
|
|
@@ -199,3 +200,57 @@ See [MIGRATION-V2.md](./docs/MIGRATION-V2.md)
|
|
|
199
200
|
## Migration from v2 to v3
|
|
200
201
|
|
|
201
202
|
See [MIGRATION-V3.md](./docs/MIGRATION-V3.md)
|
|
203
|
+
|
|
204
|
+
## Contributing
|
|
205
|
+
|
|
206
|
+
### Conventional Commits
|
|
207
|
+
|
|
208
|
+
This repo uses [Conventional Commits](https://www.conventionalcommits.org) for all commit messages. A `commit-msg` git hook validates the format automatically — run `npm install` once to activate it.
|
|
209
|
+
|
|
210
|
+
**Format:** `type(optional-scope): description`
|
|
211
|
+
|
|
212
|
+
| Type | Use for | Version bump |
|
|
213
|
+
|------|---------|-------------|
|
|
214
|
+
| `feat` | New feature or config rule | Minor (`4.0.0` → `4.1.0`) |
|
|
215
|
+
| `fix` | Bug fix or rule correction | Patch (`4.0.0` → `4.0.1`) |
|
|
216
|
+
| `feat!` or `BREAKING CHANGE:` footer | Breaking API/config change | Major (`4.0.0` → `5.0.0`) |
|
|
217
|
+
| `chore` | Dependency updates, tooling | No release |
|
|
218
|
+
| `docs` | Documentation only | No release |
|
|
219
|
+
| `refactor` | Internal restructuring | No release |
|
|
220
|
+
| `ci` | CI/CD changes | No release |
|
|
221
|
+
| `test` | Test changes | No release |
|
|
222
|
+
|
|
223
|
+
**Examples:**
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
feat: add import/order rule to Node config
|
|
227
|
+
fix: align browser comma-dangle with prettier defaults
|
|
228
|
+
feat(browser): add React 19 peer dep support
|
|
229
|
+
feat!: drop Node 18 support — requires Node 24+
|
|
230
|
+
chore: bump typescript-eslint to v8.60
|
|
231
|
+
docs: update Cypress usage examples
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A breaking change can also be indicated by adding a `BREAKING CHANGE:` footer to any commit type:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
feat: remove deprecated stylelint rules
|
|
238
|
+
|
|
239
|
+
BREAKING CHANGE: Removed rules that were dropped in stylelint-scss@7.
|
|
240
|
+
Projects relying on those rules must update their configs before upgrading.
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Release Process
|
|
244
|
+
|
|
245
|
+
Releases are automated via [release-please](https://github.com/googleapis/release-please). No manual version bumping or GitHub release creation is needed.
|
|
246
|
+
|
|
247
|
+
**How it works:**
|
|
248
|
+
|
|
249
|
+
1. Merge PRs to `main` using conventional commit messages (see above).
|
|
250
|
+
2. After each merge, release-please opens or updates a **Release PR** (titled `chore(main): release X.Y.Z`) that accumulates all unreleased changes, bumps `package.json`, and generates a `CHANGELOG.md` entry.
|
|
251
|
+
3. When you are ready to publish, **merge the Release PR**.
|
|
252
|
+
4. release-please creates a GitHub release and git tag automatically.
|
|
253
|
+
5. The `release.yml` workflow fires on the new release and publishes to npm.
|
|
254
|
+
|
|
255
|
+
**To release a prerelease (`-next.N`):**
|
|
256
|
+
Update the version in `package.json` manually on a branch (e.g. `4.1.0-next.0`) and create a GitHub release by hand — the existing `release.yml` workflow will publish it with the `next` tag on npm. Prereleases are not yet managed by release-please in this repo.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const projectRoot = process.env.INIT_CWD || process.env.npm_config_local_prefix;
|
|
7
|
+
|
|
8
|
+
// Skip if we can't determine the consuming project's root, or if postinstall
|
|
9
|
+
// is running inside this package's own development environment.
|
|
10
|
+
if (!projectRoot || projectRoot === path.resolve(__dirname, '..')) {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ownPkg = require('../package.json');
|
|
15
|
+
|
|
16
|
+
const stylelintPeers = Object.entries(ownPkg.peerDependencies || {}).filter(([name]) =>
|
|
17
|
+
name.startsWith('stylelint')
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Extracts the minimum required major version from a semver range like ^17.0.0 or >=17.1.1.
|
|
21
|
+
const minMajor = (range) => {
|
|
22
|
+
const match = range.match(/(\d+)/);
|
|
23
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isInstalled = (pkg) =>
|
|
27
|
+
fs.existsSync(path.join(projectRoot, 'node_modules', pkg, 'package.json'));
|
|
28
|
+
|
|
29
|
+
// Only warn about missing stylelint plugins if stylelint itself is installed —
|
|
30
|
+
// consumers who don't use stylelint at all don't need any of these.
|
|
31
|
+
const stylelintInUse = isInstalled('stylelint');
|
|
32
|
+
|
|
33
|
+
const outdated = [];
|
|
34
|
+
const missing = [];
|
|
35
|
+
|
|
36
|
+
for (const [pkg, requiredRange] of stylelintPeers) {
|
|
37
|
+
const pkgJsonPath = path.join(projectRoot, 'node_modules', pkg, 'package.json');
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
40
|
+
if (stylelintInUse && pkg !== 'stylelint') {
|
|
41
|
+
missing.push({ pkg, requiredRange });
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { version } = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
47
|
+
const installedMajor = parseInt(version.split('.')[0], 10);
|
|
48
|
+
|
|
49
|
+
if (installedMajor < minMajor(requiredRange)) {
|
|
50
|
+
outdated.push({ pkg, version, requiredRange });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (outdated.length === 0 && missing.length === 0) process.exit(0);
|
|
55
|
+
|
|
56
|
+
const pkgName = ownPkg.name;
|
|
57
|
+
|
|
58
|
+
if (missing.length > 0) {
|
|
59
|
+
console.warn(`\n⚠️ ${pkgName}: missing stylelint peer dependencies.\n`);
|
|
60
|
+
console.warn(' These packages are required for stylelint to work correctly:');
|
|
61
|
+
for (const { pkg, requiredRange } of missing) {
|
|
62
|
+
console.warn(` • ${pkg} (required: ${requiredRange})`);
|
|
63
|
+
}
|
|
64
|
+
const installCmd = missing.map(({ pkg, requiredRange }) => `${pkg}@"${requiredRange}"`).join(' ');
|
|
65
|
+
console.warn(`\n Run: npm install --save-dev ${installCmd}`);
|
|
66
|
+
console.warn(' Linting will fail without these packages.\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (outdated.length > 0) {
|
|
70
|
+
console.warn(`\n⚠️ ${pkgName}: outdated stylelint peer dependencies detected.\n`);
|
|
71
|
+
console.warn(' These packages are installed at versions that are no longer compatible:');
|
|
72
|
+
for (const { pkg, version, requiredRange } of outdated) {
|
|
73
|
+
console.warn(` • ${pkg}@${version} → required: ${requiredRange}`);
|
|
74
|
+
}
|
|
75
|
+
const upgradeCmd = outdated.map(({ pkg, requiredRange }) => `${pkg}@"${requiredRange}"`).join(' ');
|
|
76
|
+
console.warn(`\n Run: npm install ${upgradeCmd}`);
|
|
77
|
+
console.warn(' Failure to upgrade may cause unexpected lint errors.\n');
|
|
78
|
+
}
|
package/browser.js
CHANGED
|
@@ -7,7 +7,7 @@ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
|
|
|
7
7
|
|
|
8
8
|
// Base rules adapted from the browser config
|
|
9
9
|
const baseRules = {
|
|
10
|
-
'comma-dangle': 'warn',
|
|
10
|
+
'comma-dangle': ['warn', 'always-multiline'],
|
|
11
11
|
'no-param-reassign': ['warn', { props: false }],
|
|
12
12
|
'arrow-parens': 0,
|
|
13
13
|
'no-plusplus': 0,
|
|
@@ -19,8 +19,8 @@ const baseRules = {
|
|
|
19
19
|
allow: [
|
|
20
20
|
'_hsg',
|
|
21
21
|
'_hsq',
|
|
22
|
-
]
|
|
23
|
-
}
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
24
|
],
|
|
25
25
|
'no-trailing-spaces': ['error', { skipBlankLines: true }],
|
|
26
26
|
'no-unused-expressions': ['warn', { allowTernary: true }],
|
|
@@ -29,15 +29,15 @@ const baseRules = {
|
|
|
29
29
|
{
|
|
30
30
|
code: 120,
|
|
31
31
|
ignoreStrings: true,
|
|
32
|
-
ignoreTemplateLiterals: true
|
|
33
|
-
}
|
|
32
|
+
ignoreTemplateLiterals: true,
|
|
33
|
+
},
|
|
34
34
|
],
|
|
35
35
|
'operator-linebreak': 0,
|
|
36
36
|
'implicit-arrow-linebreak': 0,
|
|
37
37
|
indent: 0,
|
|
38
38
|
'object-curly-newline': 0,
|
|
39
39
|
'function-paren-newline': 0,
|
|
40
|
-
'nonblock-statement-body-position': 0
|
|
40
|
+
'nonblock-statement-body-position': 0,
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// React-specific rules
|
|
@@ -50,10 +50,10 @@ const reactRules = {
|
|
|
50
50
|
2,
|
|
51
51
|
{
|
|
52
52
|
component: true,
|
|
53
|
-
html: false
|
|
54
|
-
}
|
|
53
|
+
html: false,
|
|
54
|
+
},
|
|
55
55
|
],
|
|
56
|
-
'react/forbid-prop-types': 0
|
|
56
|
+
'react/forbid-prop-types': 0,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// Common ignore patterns
|
|
@@ -63,13 +63,13 @@ const commonIgnores = [
|
|
|
63
63
|
'**/build/**',
|
|
64
64
|
'**/.next/**',
|
|
65
65
|
'**/coverage/**',
|
|
66
|
-
'eslint.config.js'
|
|
66
|
+
'eslint.config.js',
|
|
67
67
|
];
|
|
68
68
|
|
|
69
69
|
export default [
|
|
70
70
|
// Global ignores
|
|
71
71
|
{
|
|
72
|
-
ignores: commonIgnores
|
|
72
|
+
ignores: commonIgnores,
|
|
73
73
|
},
|
|
74
74
|
// Base config for all JavaScript files
|
|
75
75
|
js.configs.recommended,
|
|
@@ -85,17 +85,17 @@ export default [
|
|
|
85
85
|
// Custom browser globals from original config
|
|
86
86
|
$: true,
|
|
87
87
|
jQuery: true,
|
|
88
|
-
Invoca: true
|
|
88
|
+
Invoca: true,
|
|
89
89
|
},
|
|
90
90
|
parserOptions: {
|
|
91
91
|
ecmaFeatures: {
|
|
92
|
-
jsx: true
|
|
93
|
-
}
|
|
94
|
-
}
|
|
92
|
+
jsx: true,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
95
|
},
|
|
96
96
|
rules: {
|
|
97
|
-
...baseRules
|
|
98
|
-
}
|
|
97
|
+
...baseRules,
|
|
98
|
+
},
|
|
99
99
|
},
|
|
100
100
|
// React configuration
|
|
101
101
|
{
|
|
@@ -103,24 +103,24 @@ export default [
|
|
|
103
103
|
plugins: {
|
|
104
104
|
react: reactPlugin,
|
|
105
105
|
'react-hooks': reactHooksPlugin,
|
|
106
|
-
'jsx-a11y': jsxA11yPlugin
|
|
106
|
+
'jsx-a11y': jsxA11yPlugin,
|
|
107
107
|
},
|
|
108
108
|
settings: {
|
|
109
109
|
react: {
|
|
110
|
-
version: 'detect'
|
|
111
|
-
}
|
|
110
|
+
version: 'detect',
|
|
111
|
+
},
|
|
112
112
|
},
|
|
113
113
|
rules: {
|
|
114
114
|
...reactPlugin.configs.recommended.rules,
|
|
115
115
|
...reactHooksPlugin.configs.recommended.rules,
|
|
116
116
|
...jsxA11yPlugin.configs.recommended.rules,
|
|
117
|
-
...reactRules
|
|
118
|
-
}
|
|
117
|
+
...reactRules,
|
|
118
|
+
},
|
|
119
119
|
},
|
|
120
120
|
// TypeScript config
|
|
121
121
|
...tseslint.configs.recommended.map(config => ({
|
|
122
122
|
...config,
|
|
123
|
-
files: ['**/*.{ts,mts,cts,tsx}']
|
|
123
|
+
files: ['**/*.{ts,mts,cts,tsx}'],
|
|
124
124
|
})),
|
|
125
125
|
{
|
|
126
126
|
files: ['**/*.{ts,mts,cts,tsx}'],
|
|
@@ -133,17 +133,17 @@ export default [
|
|
|
133
133
|
...globals.jest,
|
|
134
134
|
$: true,
|
|
135
135
|
jQuery: true,
|
|
136
|
-
Invoca: true
|
|
136
|
+
Invoca: true,
|
|
137
137
|
},
|
|
138
138
|
parserOptions: {
|
|
139
139
|
ecmaFeatures: {
|
|
140
|
-
jsx: true
|
|
141
|
-
}
|
|
142
|
-
}
|
|
140
|
+
jsx: true,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
143
|
},
|
|
144
144
|
rules: {
|
|
145
|
-
...baseRules
|
|
146
|
-
}
|
|
145
|
+
...baseRules,
|
|
146
|
+
},
|
|
147
147
|
},
|
|
148
148
|
// React configuration for TypeScript files
|
|
149
149
|
{
|
|
@@ -151,18 +151,18 @@ export default [
|
|
|
151
151
|
plugins: {
|
|
152
152
|
react: reactPlugin,
|
|
153
153
|
'react-hooks': reactHooksPlugin,
|
|
154
|
-
'jsx-a11y': jsxA11yPlugin
|
|
154
|
+
'jsx-a11y': jsxA11yPlugin,
|
|
155
155
|
},
|
|
156
156
|
settings: {
|
|
157
157
|
react: {
|
|
158
|
-
version: 'detect'
|
|
159
|
-
}
|
|
158
|
+
version: 'detect',
|
|
159
|
+
},
|
|
160
160
|
},
|
|
161
161
|
rules: {
|
|
162
162
|
...reactPlugin.configs.recommended.rules,
|
|
163
163
|
...reactHooksPlugin.configs.recommended.rules,
|
|
164
164
|
...jsxA11yPlugin.configs.recommended.rules,
|
|
165
|
-
...reactRules
|
|
166
|
-
}
|
|
167
|
-
}
|
|
165
|
+
...reactRules,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
168
|
];
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hs-web-team/eslint-config-node",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "HubSpot Marketing WebTeam shared configurations for ESLint, Prettier, Stylelint, and Cypress",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,8 +29,10 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
+
"postinstall": "node bin/check-peer-deps.cjs",
|
|
32
33
|
"lint": "npx eslint -c ./index.js *.js --fix",
|
|
33
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
34
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
35
|
+
"prepare": "git rev-parse --git-dir > /dev/null 2>&1 && git config core.hooksPath .githooks || true"
|
|
34
36
|
},
|
|
35
37
|
"engines": {
|
|
36
38
|
"node": ">=24"
|