diff --git a/@commitlint/config-workspace-scopes/fixtures/basic/@packages/a/package.json b/@commitlint/config-workspace-scopes/fixtures/basic/@packages/a/package.json new file mode 100644 index 0000000000..7ba283037e --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/basic/@packages/a/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/a", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/basic/@packages/b/package.json b/@commitlint/config-workspace-scopes/fixtures/basic/@packages/b/package.json new file mode 100644 index 0000000000..3fd2cf7616 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/basic/@packages/b/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/b", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/basic/package.json b/@commitlint/config-workspace-scopes/fixtures/basic/package.json new file mode 100644 index 0000000000..1a28bd53f8 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/basic/package.json @@ -0,0 +1,7 @@ +{ + "name": "yarn", + "version": "1.0.0", + "workspaces": [ + "@packages/*" + ] +} diff --git a/@commitlint/config-workspace-scopes/fixtures/empty/package.json b/@commitlint/config-workspace-scopes/fixtures/empty/package.json new file mode 100644 index 0000000000..a0d311fba7 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/empty/package.json @@ -0,0 +1,4 @@ +{ + "name": "empty", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/a/nested-a/package.json b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/a/nested-a/package.json new file mode 100644 index 0000000000..d22626edd3 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/a/nested-a/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/nested-a", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/b/nested-b/package.json b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/b/nested-b/package.json new file mode 100644 index 0000000000..a2006192a7 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/@packages/b/nested-b/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/nested-b", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/package.json b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/package.json new file mode 100644 index 0000000000..21e671fb54 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/nested-workspaces/package.json @@ -0,0 +1,7 @@ +{ + "name": "nested-workspaces", + "version": "1.0.0", + "workspaces": [ + "@packages/**" + ] +} diff --git a/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/a/package.json b/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/a/package.json new file mode 100644 index 0000000000..7ba283037e --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/a/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/a", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/b/package.json b/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/b/package.json new file mode 100644 index 0000000000..3fd2cf7616 --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/scoped/@packages/b/package.json @@ -0,0 +1,4 @@ +{ + "name": "@packages/b", + "version": "1.0.0" +} diff --git a/@commitlint/config-workspace-scopes/fixtures/scoped/lerna.json b/@commitlint/config-workspace-scopes/fixtures/scoped/lerna.json new file mode 100644 index 0000000000..8e0ca65b2e --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/scoped/lerna.json @@ -0,0 +1,5 @@ +{ + "lerna": "4", + "version": "1.0.0", + "packages": ["@packages/*"] +} diff --git a/@commitlint/config-workspace-scopes/fixtures/scoped/package.json b/@commitlint/config-workspace-scopes/fixtures/scoped/package.json new file mode 100644 index 0000000000..2d12e9469c --- /dev/null +++ b/@commitlint/config-workspace-scopes/fixtures/scoped/package.json @@ -0,0 +1,7 @@ +{ + "name": "scoped", + "version": "1.0.0", + "workspaces": [ + "@packages/**" + ] +} diff --git a/@commitlint/config-workspace-scopes/index.js b/@commitlint/config-workspace-scopes/index.js new file mode 100644 index 0000000000..4fc08b4cc4 --- /dev/null +++ b/@commitlint/config-workspace-scopes/index.js @@ -0,0 +1,41 @@ +import {createRequire} from 'module'; +import Path from 'path'; + +import {globSync} from 'glob'; + +const require = createRequire(import.meta.url); + +export default { + utils: {getPackages}, + rules: { + 'scope-enum': (ctx) => + getPackages(ctx).then((packages) => [2, 'always', packages]), + }, +}; + +function getPackages(context) { + return Promise.resolve() + .then(() => { + const ctx = context || {}; + const cwd = ctx.cwd || process.cwd(); + + const {workspaces} = require(Path.join(cwd, 'package.json')); + if (!Array.isArray(workspaces)) { + // no workspaces configured, skipping + return []; + } + + const wsGlobs = workspaces.flatMap((ws) => { + const path = Path.posix.join(ws, 'package.json'); + return globSync(path, {cwd, ignore: ['**/node_modules/**']}); + }); + + return wsGlobs.sort().map((pJson) => require(Path.join(cwd, pJson))); + }) + .then((packages) => { + return packages + .map((pkg) => pkg.name) + .filter(Boolean) + .map((name) => (name.charAt(0) === '@' ? name.split('/')[1] : name)); + }); +} diff --git a/@commitlint/config-workspace-scopes/index.test.js b/@commitlint/config-workspace-scopes/index.test.js new file mode 100644 index 0000000000..bd59dc0cf0 --- /dev/null +++ b/@commitlint/config-workspace-scopes/index.test.js @@ -0,0 +1,86 @@ +import {test, expect} from 'vitest'; +import path from 'path'; +import {fileURLToPath} from 'url'; + +import {npm} from '@commitlint/test'; + +import config from './index.js'; + +const __dirname = path.resolve(fileURLToPath(import.meta.url), '..'); + +test('exports rules key', () => { + expect(config).toHaveProperty('rules'); +}); + +test('rules hold object', () => { + expect(config).toMatchObject({ + rules: expect.any(Object), + }); +}); + +test('rules contain scope-enum', () => { + expect(config).toMatchObject({ + rules: { + 'scope-enum': expect.anything(), + }, + }); +}); + +test('scope-enum is function', () => { + expect(config).toMatchObject({ + rules: { + 'scope-enum': expect.any(Function), + }, + }); +}); + +test('scope-enum does not throw for missing context', async () => { + const {'scope-enum': fn} = config.rules; + await expect(fn()).resolves.toBeTruthy(); +}); + +test('scope-enum has expected severity', async () => { + const {'scope-enum': fn} = config.rules; + const [severity] = await fn(); + expect(severity).toBe(2); +}); + +test('scope-enum has expected modifier', async () => { + const {'scope-enum': fn} = config.rules; + const [, modifier] = await fn(); + expect(modifier).toBe('always'); +}); + +test('returns empty value for empty workspaces', async () => { + const {'scope-enum': fn} = config.rules; + const cwd = await npm.bootstrap('fixtures/empty', __dirname); + const [, , value] = await fn({cwd}); + expect(value).toEqual([]); +}); + +test('returns expected value for basic workspaces', async () => { + const {'scope-enum': fn} = config.rules; + const cwd = await npm.bootstrap('fixtures/basic', __dirname); + + const [, , value] = await fn({cwd}); + expect(value).toEqual(['a', 'b']); +}); + +test('returns expected value for scoped workspaces', async () => { + const {'scope-enum': fn} = config.rules; + const cwd = await npm.bootstrap('fixtures/scoped', __dirname); + + const [, , value] = await fn({cwd}); + expect(value).toEqual(['a', 'b']); +}); + +test('returns expected value for workspaces has nested packages', async () => { + const {'scope-enum': fn} = config.rules; + const cwd = await npm.bootstrap('fixtures/nested-workspaces', __dirname); + + const [, , value] = await fn({cwd}); + expect(value).toEqual(expect.arrayContaining(['nested-a', 'nested-b'])); + expect(value).toEqual( + expect.not.arrayContaining(['dependency-a', 'dependency-b']) + ); +}); diff --git a/@commitlint/config-workspace-scopes/license.md b/@commitlint/config-workspace-scopes/license.md new file mode 100644 index 0000000000..678b1e6e32 --- /dev/null +++ b/@commitlint/config-workspace-scopes/license.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 - present Jan Biasi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/@commitlint/config-workspace-scopes/package.json b/@commitlint/config-workspace-scopes/package.json new file mode 100644 index 0000000000..f3c118408f --- /dev/null +++ b/@commitlint/config-workspace-scopes/package.json @@ -0,0 +1,44 @@ +{ + "name": "@commitlint/config-workspace-scopes", + "type": "module", + "version": "19.7.0", + "description": "Shareable commitlint config enforcing workspace names as scopes", + "main": "index.js", + "files": [ + "index.js" + ], + "scripts": { + "deps": "dep-check", + "pkg": "pkg-check" + }, + "repository": { + "type": "git", + "url": "https://github.com/conventional-changelog/commitlint.git", + "directory": "@commitlint/config-lerna-scopes" + }, + "keywords": [ + "conventional-changelog", + "commitlint", + "commitlint-config", + "npm-workspaces", + "yarn-workspaces" + ], + "author": "Jan Biasi (https://github.com/janbiasi)", + "license": "MIT", + "bugs": { + "url": "https://github.com/conventional-changelog/commitlint/issues" + }, + "homepage": "https://commitlint.js.org/", + "engines": { + "node": ">=v18" + }, + "dependencies": { + "glob": "^10.3.10" + }, + "devDependencies": { + "@commitlint/test": "^19.5.0", + "@commitlint/utils": "^19.5.0", + "@types/glob": "^8.1.0" + }, + "gitHead": "70f7f4688b51774e7ac5e40e896cdaa3f132b2bc" +} diff --git a/@commitlint/config-workspace-scopes/readme.md b/@commitlint/config-workspace-scopes/readme.md new file mode 100644 index 0000000000..a09ed2669d --- /dev/null +++ b/@commitlint/config-workspace-scopes/readme.md @@ -0,0 +1,47 @@ +# @commitlint/config-workspaces-scopes + +Shareable `commitlint` config enforcing workspace names as scopes. +Use with [@commitlint/cli](../cli) and [@commitlint/prompt-cli](../prompt-cli). + +## Getting started + +```sh +npm install --save-dev @commitlint/config-workspace-scopes @commitlint/cli +echo "export default {extends: ['@commitlint/config-workspace-scopes']};" > commitlint.config.js +``` + +## Examples + +```text +❯ cat package.json +{ + "workspaces": ["packages/*"] +} + +❯ cat commitlint.config.js +{ + extends: ['@commitlint/config-workspace-scopes'] +} + +❯ tree packages + +packages +├── api +├── app +└── web + +❯ echo "build(api): change something in api's build" | commitlint +⧗ input: build(api): change something in api's build +✔ found 0 problems, 0 warnings + +❯ echo "test(foo): this won't pass" | commitlint +⧗ input: test(foo): this won't pass +✖ scope must be one of [api, app, web] [scope-enum] +✖ found 1 problems, 0 warnings + +❯ echo "ci: do some general maintenance" | commitlint +⧗ input: ci: do some general maintenance +✔ found 0 problems, 0 warnings +``` + +Consult [Rules reference](https://commitlint.js.org/reference/rules) for a list of available rules. diff --git a/package.json b/package.json index 61f0d00c45..7da3feb325 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "@packages/*" ], "engines": { - "node": ">=v18" + "node": ">=v18", + "npm": ">=7" }, "repository": { "type": "git",