diff --git a/README.md b/README.md index 82aac1b..5af1008 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,14 @@ gitignore({ }) ``` +By default, this plugin will try to look up the directory tree and match the first `.gitignore` file. You can disable this by setting the `root` option to `true`, or specify the `files` option to a specific path. + +```js +gitignore({ + root: true +}) +``` + ## Sponsors

diff --git a/package.json b/package.json index 02fef78..b5dde44 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,12 @@ "prepublishOnly": "nr build", "release": "bumpp && npm publish", "start": "esno src/index.ts", - "test": "vitest", + "test": "vitest --pool=forks", "typecheck": "tsc --noEmit", "prepare": "simple-git-hooks" }, "dependencies": { + "find-up": "^7.0.0", "parse-gitignore": "^2.0.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7006982..a53ae89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + find-up: + specifier: ^7.0.0 + version: 7.0.0 parse-gitignore: specifier: ^2.0.0 version: 2.0.0 @@ -3130,6 +3133,15 @@ packages: path-exists: 4.0.0 dev: true + /find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + dev: false + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3680,6 +3692,13 @@ packages: p-locate: 5.0.0 dev: true + /locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-locate: 6.0.0 + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -4035,6 +4054,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: false + /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} @@ -4056,6 +4082,13 @@ packages: p-limit: 3.1.0 dev: true + /p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-limit: 4.0.0 + dev: false + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4098,6 +4131,11 @@ packages: engines: {node: '>=8'} dev: true + /path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -4783,6 +4821,11 @@ packages: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: false + /unist-util-stringify-position@2.0.3: resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} dependencies: @@ -5086,4 +5129,3 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: true diff --git a/src/index.ts b/src/index.ts index 00de6c5..a19b974 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,28 +1,47 @@ import fs from 'node:fs' +import { findUpSync } from 'find-up' // @ts-expect-error missing types import parse from 'parse-gitignore' export interface FlatGitignoreOptions { + /** + * Path to `.gitignore` files, or files with compatible formats like `.eslintignore`. + */ files?: string | string[] + /** + * Throw an error if gitignore file not found. + */ strict?: boolean + /** + * Mark the current working directory as the root directory, + * disable searching for `.gitignore` files in parent directories. + * + * This option is not effective when `files` is explicitly specified. + * @default false + */ + root?: boolean } export interface FlatConfigItem { ignores: string[] } +const GITIGNORE = '.gitignore' as const + export default function ignore(options: FlatGitignoreOptions = {}): FlatConfigItem { const ignores: string[] = [] const { - files: _files = '.gitignore', + root = false, + files: _files = root ? GITIGNORE : findUpSync(GITIGNORE) || [], strict = true, } = options + const files = Array.isArray(_files) ? _files : [_files] for (const file of files) { - let content: string + let content = '' try { content = fs.readFileSync(file, 'utf8') } @@ -41,6 +60,9 @@ export default function ignore(options: FlatGitignoreOptions = {}): FlatConfigIt } } + if (strict && files.length === 0) + throw new Error('No .gitignore file found') + return { ignores, } diff --git a/test/index.test.ts b/test/index.test.ts index 30aee15..bf44118 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' import ignore from '../src/index' -describe('should', () => { - it('exported', () => { - expect(ignore()) +describe('should execute tests in root folder', () => { + it('should find a gitignore file', () => { + expect(ignore({ root: true })) .toMatchInlineSnapshot(` { "ignores": [ @@ -37,12 +37,12 @@ describe('should', () => { }) it('strict (default) throw', () => { - expect(() => ignore({ files: 'not-exists' })) + expect(() => ignore({ files: 'not-exists', root: true })) .toThrow() }) it('not strict skip missing file', () => { - expect(ignore({ files: 'not-exists', strict: false })) + expect(ignore({ files: 'not-exists', strict: false, root: true })) .toMatchInlineSnapshot(` { "ignores": [], diff --git a/test/workspace-with-gitignore.test.ts b/test/workspace-with-gitignore.test.ts new file mode 100644 index 0000000..59c334a --- /dev/null +++ b/test/workspace-with-gitignore.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest' +import ignore from '../src/index' + +describe('should execute tests in test/workspace-with-gitignore', () => { + process.chdir('test/workspace-with-gitignore') + + it('should find a gitignore file', () => { + expect(ignore()) + .toMatchInlineSnapshot(` + { + "ignores": [ + "gitignoretest", + "**/gitignoretest/**", + ], + } + `) + }) + + it('strict (default) throw', () => { + expect(() => ignore({ files: 'not-exists', root: true })) + .toThrow() + }) + + it('not strict skip missing file', () => { + expect(ignore({ files: 'not-exists', strict: false, root: true })) + .toMatchInlineSnapshot(` + { + "ignores": [], + } + `) + }) + + it('should find a gitignore file in cwd', () => { + expect(ignore({ root: true })) + .toMatchInlineSnapshot(` + { + "ignores": [ + "gitignoretest", + "**/gitignoretest/**", + ], + } + `) + }) + + it('dont fallback to root, strict and return empty array', () => { + expect(ignore({ strict: false, root: true })) + .toMatchInlineSnapshot(` + { + "ignores": [ + "gitignoretest", + "**/gitignoretest/**", + ], + } + `) + }) +}) diff --git a/test/workspace-with-gitignore/.gitignore b/test/workspace-with-gitignore/.gitignore new file mode 100644 index 0000000..ee0c43f --- /dev/null +++ b/test/workspace-with-gitignore/.gitignore @@ -0,0 +1 @@ +gitignoretest \ No newline at end of file diff --git a/test/workspace-without-gitignore.test.ts b/test/workspace-without-gitignore.test.ts new file mode 100644 index 0000000..dbfc040 --- /dev/null +++ b/test/workspace-without-gitignore.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest' +import ignore from '../src/index' + +describe('should execute tests in test/workspace-without-gitignore', () => { + process.chdir('test/workspace-without-gitignore') + + it('should find a gitignore file', () => { + expect(ignore()) + .toMatchInlineSnapshot(` + { + "ignores": [ + ".cache", + "**/.cache/**", + ".DS_Store", + "**/.DS_Store/**", + ".idea", + "**/.idea/**", + "*.log", + "**/*.log/**", + "*.tgz", + "**/*.tgz/**", + "coverage", + "**/coverage/**", + "dist", + "**/dist/**", + "lib-cov", + "**/lib-cov/**", + "logs", + "**/logs/**", + "node_modules", + "**/node_modules/**", + "temp", + "**/temp/**", + "!temp/.gitkeep", + "!temp/.gitkeep/**", + ], + } + `) + }) + + it('strict (default) throw', () => { + expect(() => ignore({ files: 'not-exists', root: true })) + .toThrow() + }) + + it('not strict skip missing file', () => { + expect(ignore({ files: 'not-exists', strict: false, root: true })) + .toMatchInlineSnapshot(` + { + "ignores": [], + } + `) + }) + + it('dont fallback to root, strict and throw error', () => { + expect(() => ignore({ root: true })) + .toThrow() + }) + + it('dont fallback to root, no strict and return empty array', () => { + expect(ignore({ strict: false, root: true })) + .toMatchInlineSnapshot(` + { + "ignores": [], + } + `) + }) +}) diff --git a/test/workspace-without-gitignore/.gitkeep b/test/workspace-without-gitignore/.gitkeep new file mode 100644 index 0000000..e69de29