From d178ac9471a34332e608c954ea1ea526af0c9b38 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 4 Aug 2024 12:17:02 +0900 Subject: [PATCH 1/2] chore(deps): Update node version to 20.16.0 --- .node-version | 2 +- .tool-versions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.node-version b/.node-version index 9075659..8ce7030 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.15.0 +20.16.0 diff --git a/.tool-versions b/.tool-versions index 87c4859..c2bc75a 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 20.15.0 +nodejs 20.16.0 From 9ebeed48f95d145a5a7e3b919bde052739119d4b Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 4 Aug 2024 13:31:37 +0900 Subject: [PATCH 2/2] fix(ignore): Fix .gitignore/.repopackignore compliance and improve file filtering logic --- .repopackignore | 2 +- README.md | 8 +- src/utils/defaultIgnore.ts | 76 +++++++------ src/utils/filterUtils.ts | 48 ++++---- tests/utils/filterUtils.test.ts | 189 ++++++++++++++++++++------------ 5 files changed, 186 insertions(+), 137 deletions(-) diff --git a/.repopackignore b/.repopackignore index 8f27557..3b97365 100644 --- a/.repopackignore +++ b/.repopackignore @@ -1,4 +1,4 @@ node_modules .yarn .eslinttcache -tests/fixtures/output +tests/fixtures diff --git a/README.md b/README.md index 0076846..41bf418 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,10 @@ To pack specific files or directories using glob patterns: repopack --include "src/**/*.ts,**/*.md" ``` -To exclude specific files or directories using .gitignore syntax: +To exclude specific files or directories: ```bash -repopack --ignore "*.log,tmp/" +repopack --ignore "**/*.log,tmp/" ``` Once you have generated the packed file, you can use it with Generative AI tools like Claude, ChatGPT, and Gemini. @@ -232,7 +232,7 @@ Create a `repopack.config.json` file in your project root for custom configurati |`include`| Patterns of files to include (using glob syntax) |`[]`| |`ignore.useGitignore`| Whether to use patterns from the project's `.gitignore` file |`true`| |`ignore.useDefaultPatterns`| Whether to use default ignore patterns |`true`| -|`ignore.customPatterns`| Additional patterns to ignore (using .gitignore syntax) |`[]`| +|`ignore.customPatterns`| Additional patterns to ignore (using glob patterns) |`[]`| Example configuration: @@ -250,7 +250,7 @@ Example configuration: "ignore": { "useGitignore": true, "useDefaultPatterns": true, - "customPatterns": ["additional-folder", "*.log"] + "customPatterns": ["additional-folder", "**/*.log"] } } ``` diff --git a/src/utils/defaultIgnore.ts b/src/utils/defaultIgnore.ts index ee4bb25..b67cdd8 100644 --- a/src/utils/defaultIgnore.ts +++ b/src/utils/defaultIgnore.ts @@ -1,58 +1,56 @@ export const defaultIgnoreList = [ // Version control - '.git', - '.gitignore', - '.gitattributes', - '.hg', + '.git/**', + '.hg/**', '.hgignore', - '.svn', + '.svn/**', // Dependency directories - 'node_modules', - 'bower_components', + 'node_modules/**', + 'bower_components/**', // Logs - 'logs', - '*.log', + 'logs/**', + '**/*.log', 'npm-debug.log*', 'yarn-debug.log*', 'yarn-error.log*', // Runtime data - 'pids', + 'pids/**', '*.pid', '*.seed', '*.pid.lock', // Directory for instrumented libs generated by jscoverage/JSCover - 'lib-cov', + 'lib-cov/**', // Coverage directory used by tools like istanbul - 'coverage', + 'coverage/**', // nyc test coverage - '.nyc_output', + '.nyc_output/**', // Grunt intermediate storage - '.grunt', + '.grunt/**', // Bower dependency directory - 'bower_components', + 'bower_components/**', // node-waf configuration '.lock-wscript', // Compiled binary addons - 'build/Release', + 'build/Release/**', // Dependency directories - 'jspm_packages/', + 'jspm_packages/**', // TypeScript v1 declaration files - 'typings/', + 'typings/**', // Optional npm cache directory - '.npm', + '.npm/**', // Optional eslint cache '.eslintcache', @@ -64,7 +62,7 @@ export const defaultIgnoreList = [ '*.tgz', // Yarn files - '.yarn/*', + '.yarn/**', // Yarn Integrity file '.yarn-integrity', @@ -73,37 +71,37 @@ export const defaultIgnoreList = [ '.env', // next.js build output - '.next', + '.next/**', // nuxt.js build output - '.nuxt', + '.nuxt/**', // vuepress build output - '.vuepress/dist', + '.vuepress/dist/**', // Serverless directories - '.serverless/', + '.serverless/**', // FuseBox cache - '.fusebox/', + '.fusebox/**', // DynamoDB Local files - '.dynamodb/', + '.dynamodb/**', // TypeScript output - 'dist', + 'dist/**', // OS generated files - '.DS_Store', - 'Thumbs.db', + '**/.DS_Store', + '**/Thumbs.db', // Editor directories and files - '.idea', - '.vscode', - '*.swp', - '*.swo', - '*.swn', - '*.bak', + '.idea/**', + '.vscode/**', + '**/*.swp', + '**/*.swo', + '**/*.swn', + '**/*.bak', // Package manager locks 'package-lock.json', @@ -111,12 +109,12 @@ export const defaultIgnoreList = [ 'pnpm-lock.yaml', // Build outputs - 'build', - 'out', + 'build/**', + 'out/**', // Temporary files - 'tmp', - 'temp', + 'tmp/**', + 'temp/**', // repopack output 'repopack-output.txt', diff --git a/src/utils/filterUtils.ts b/src/utils/filterUtils.ts index cba9f01..ae2c584 100644 --- a/src/utils/filterUtils.ts +++ b/src/utils/filterUtils.ts @@ -8,21 +8,24 @@ import { defaultIgnoreList } from './defaultIgnore.js'; export async function filterFiles(rootDir: string, config: RepopackConfigMerged): Promise { const includePatterns = config.include.length > 0 ? config.include : ['**/*']; - const ignorePatterns = await getAllIgnorePatterns(rootDir, config); + const ignorePatterns = await getIgnorePatterns(config); + const ignoreFilePaths = await getIgnoreFilePaths(rootDir, config); logger.trace('Include patterns:', includePatterns); logger.trace('Ignore patterns:', ignorePatterns); + logger.trace('Ignore files:', ignoreFilePaths); try { const filePaths = await globby(includePatterns, { cwd: rootDir, ignore: ignorePatterns, - // Ignore option is in accordance with .gitignore rules - gitignore: true, - dot: true, + ignoreFiles: ignoreFilePaths, + // result options onlyFiles: true, - followSymbolicLinks: false, absolute: false, + // glob options + dot: true, + followSymbolicLinks: false, }); logger.trace(`Filtered ${filePaths.length} files`); @@ -40,18 +43,25 @@ export function parseIgnoreContent(content: string): string[] { .filter((line) => line && !line.startsWith('#')); } -export async function getIgnorePatterns(filename: string, rootDir: string, fsModule = fs): Promise { - const ignorePath = path.join(rootDir, filename); - try { - const ignoreContent = await fsModule.readFile(ignorePath, 'utf-8'); - return parseIgnoreContent(ignoreContent); - } catch (error) { - logger.debug(`No ${filename} file found or unable to read it.`); - return []; +export async function getIgnoreFilePaths(rootDir: string, config: RepopackConfigMerged): Promise { + let ignoreFilePaths: string[] = []; + + if (config.ignore.useGitignore) { + ignoreFilePaths.push(path.join(rootDir, '.gitignore')); } + + const existsRepopackIgnore = await fs + .access(path.join(rootDir, '.repopackignore')) + .then(() => true) + .catch(() => false); + if (existsRepopackIgnore) { + ignoreFilePaths.push(path.join(rootDir, '.repopackignore')); + } + + return ignoreFilePaths; } -export async function getAllIgnorePatterns(rootDir: string, config: RepopackConfigMerged): Promise { +export async function getIgnorePatterns(config: RepopackConfigMerged): Promise { let ignorePatterns: string[] = []; // Add default ignore patterns @@ -59,16 +69,6 @@ export async function getAllIgnorePatterns(rootDir: string, config: RepopackConf ignorePatterns = [...ignorePatterns, ...defaultIgnoreList]; } - // Add .gitignore patterns - const gitignorePatterns = await getIgnorePatterns('.gitignore', rootDir); - if (config.ignore.useGitignore) { - ignorePatterns = [...ignorePatterns, ...gitignorePatterns]; - } - - // Add .repopackignore patterns - const repopackIgnorePatterns = await getIgnorePatterns('.repopackignore', rootDir); - ignorePatterns = [...ignorePatterns, ...repopackIgnorePatterns]; - // Add custom ignore patterns if (config.ignore.customPatterns) { ignorePatterns = [...ignorePatterns, ...config.ignore.customPatterns]; diff --git a/tests/utils/filterUtils.test.ts b/tests/utils/filterUtils.test.ts index 0c53da9..93b1fcc 100644 --- a/tests/utils/filterUtils.test.ts +++ b/tests/utils/filterUtils.test.ts @@ -1,47 +1,120 @@ import { expect, test, vi, describe, beforeEach } from 'vitest'; -import { getIgnorePatterns, parseIgnoreContent, getAllIgnorePatterns } from '../../src/utils/filterUtils.js'; +import { getIgnorePatterns, parseIgnoreContent, getIgnoreFilePaths, filterFiles } from '../../src/utils/filterUtils.js'; import path from 'path'; import * as fs from 'fs/promises'; import { createMockConfig } from '../testing/testUtils.js'; +import { globby } from 'globby'; vi.mock('fs/promises'); +vi.mock('globby'); -describe('ignoreUtils', () => { +describe('filterUtils', () => { beforeEach(() => { vi.resetAllMocks(); }); + describe('getIgnoreFilePaths', () => { + test('should return correct paths when .gitignore and .repopackignore exist', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: true, + useDefaultPatterns: true, + customPatterns: [], + }, + }); + + vi.mocked(fs.access).mockResolvedValue(undefined); + + const paths = await getIgnoreFilePaths('/mock/root', mockConfig); + + expect(paths).toEqual([ + path.join('/mock/root', '.gitignore'), + path.join('/mock/root', '.repopackignore'), + ]); + }); + + test('should not include .gitignore when useGitignore is false', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: false, + useDefaultPatterns: true, + customPatterns: [], + }, + }); + + vi.mocked(fs.access).mockResolvedValue(undefined); + + const paths = await getIgnoreFilePaths('/mock/root', mockConfig); + + expect(paths).toEqual([path.join('/mock/root', '.repopackignore')]); + }); + + test('should handle missing .repopackignore', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: true, + useDefaultPatterns: true, + customPatterns: [], + }, + }); + + vi.mocked(fs.access).mockImplementation((path) => { + if (path.toString().includes('.repopackignore')) { + return Promise.reject(new Error('File not found')); + } + return Promise.resolve(undefined); + }); + + const paths = await getIgnoreFilePaths('/mock/root', mockConfig); + + expect(paths).toEqual([path.join('/mock/root', '.gitignore')]); + }); + }); + describe('getIgnorePatterns', () => { - test('should read and parse .gitignore file', async () => { - const mockContent = ` -# Comment -node_modules -*.log -.DS_Store - `; - vi.mocked(fs.readFile).mockResolvedValue(mockContent); + test('should return default patterns when useDefaultPatterns is true', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: true, + useDefaultPatterns: true, + customPatterns: [], + }, + }); - const patterns = await getIgnorePatterns('.gitignore', '/mock/root'); + const patterns = await getIgnorePatterns(mockConfig); - expect(fs.readFile).toHaveBeenCalledWith(path.join('/mock/root', '.gitignore'), 'utf-8'); - expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + expect(patterns.length).toBeGreaterThan(0); + expect(patterns).toContain('node_modules/**'); }); - test('should return empty array if ignore file is not found', async () => { - vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found')); + test('should include custom patterns', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: true, + useDefaultPatterns: false, + customPatterns: ['*.custom', 'temp/'], + }, + }); - const patterns = await getIgnorePatterns('.repopackignore', '/mock/root'); + const patterns = await getIgnorePatterns(mockConfig); - expect(patterns).toEqual([]); + expect(patterns).toEqual(['*.custom', 'temp/']); }); - test('should handle CRLF line endings', async () => { - const mockContent = 'node_modules\r\n*.log\r\n.DS_Store'; - vi.mocked(fs.readFile).mockResolvedValue(mockContent); + test('should combine default and custom patterns', async () => { + const mockConfig = createMockConfig({ + ignore: { + useGitignore: true, + useDefaultPatterns: true, + customPatterns: ['*.custom', 'temp/'], + }, + }); - const patterns = await getIgnorePatterns('.gitignore', '/mock/root'); + const patterns = await getIgnorePatterns(mockConfig); - expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + expect(patterns).toContain('node_modules/**'); + expect(patterns).toContain('*.custom'); + expect(patterns).toContain('temp/'); }); }); @@ -69,59 +142,37 @@ node_modules }); }); - describe('getAllIgnorePatterns', () => { - test('should merge patterns from .gitignore and .repopackignore when useGitignore is true', async () => { - const mockConfig = createMockConfig({ - ignore: { - useGitignore: true, - useDefaultPatterns: false, - customPatterns: [], - }, - }); - - vi.mocked(fs.readFile) - .mockResolvedValueOnce('node_modules\n*.log') // .gitignore - .mockResolvedValueOnce('dist\n*.tmp'); // .repopackignore - - const patterns = await getAllIgnorePatterns('/mock/root', mockConfig); - - expect(patterns).toEqual(['node_modules', '*.log', 'dist', '*.tmp']); - }); - - test('should only use .repopackignore when useGitignore is false', async () => { - const mockConfig = createMockConfig({ - ignore: { - useGitignore: false, - useDefaultPatterns: false, - customPatterns: [], - }, - }); - - vi.mocked(fs.readFile) - .mockResolvedValueOnce('node_modules\n*.log') // .gitignore (should be ignored) - .mockResolvedValueOnce('dist\n*.tmp'); // .repopackignore - - const patterns = await getAllIgnorePatterns('/mock/root', mockConfig); - - expect(patterns).toEqual(['dist', '*.tmp']); - }); - - test('should include custom patterns when provided', async () => { + describe('filterFiles', () => { + test('should call globby with correct parameters', async () => { const mockConfig = createMockConfig({ + include: ['**/*.js'], ignore: { useGitignore: true, - useDefaultPatterns: false, - customPatterns: ['*.custom', 'temp/'], + useDefaultPatterns: true, + customPatterns: ['*.custom'], }, }); - vi.mocked(fs.readFile) - .mockResolvedValueOnce('node_modules\n*.log') // .gitignore - .mockResolvedValueOnce('dist\n*.tmp'); // .repopackignore - - const patterns = await getAllIgnorePatterns('/mock/root', mockConfig); - - expect(patterns).toEqual(['node_modules', '*.log', 'dist', '*.tmp', '*.custom', 'temp/']); + vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']); + vi.mocked(fs.access).mockResolvedValue(undefined); + + await filterFiles('/mock/root', mockConfig); + + expect(globby).toHaveBeenCalledWith( + ['**/*.js'], + expect.objectContaining({ + cwd: '/mock/root', + ignore: expect.arrayContaining(['*.custom']), + ignoreFiles: expect.arrayContaining([ + path.join('/mock/root', '.gitignore'), + path.join('/mock/root', '.repopackignore'), + ]), + onlyFiles: true, + absolute: false, + dot: true, + followSymbolicLinks: false, + }) + ); }); }); });