diff --git a/src/cli/actions/defaultAction.ts b/src/cli/actions/defaultAction.ts index fa07959da..8ce60554d 100644 --- a/src/cli/actions/defaultAction.ts +++ b/src/cli/actions/defaultAction.ts @@ -6,8 +6,9 @@ import { type RepomixOutputStyle, repomixConfigCliSchema, } from '../../config/configSchema.js'; +import { readFilePathsFromStdin } from '../../core/file/fileStdin.js'; import type { PackResult } from '../../core/packager.js'; -import { rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js'; +import { RepomixError, rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js'; import { logger } from '../../shared/logger.js'; import { splitPatterns } from '../../shared/patternUtils.js'; import { initTaskRunner } from '../../shared/processConcurrency.js'; @@ -48,6 +49,23 @@ export const runDefaultAction = async ( const config: RepomixConfigMerged = mergeConfigs(cwd, fileConfig, cliConfig); logger.trace('Merged config:', config); + // Handle stdin processing in main process (before worker creation) + // This is necessary because child_process workers don't inherit stdin + let stdinFilePaths: string[] | undefined; + if (cliOptions.stdin) { + // Validate directory arguments for stdin mode + const firstDir = directories[0] ?? '.'; + if (directories.length > 1 || firstDir !== '.') { + throw new RepomixError( + 'When using --stdin, do not specify directory arguments. File paths will be read from stdin.', + ); + } + + const stdinResult = await readFilePathsFromStdin(cwd); + stdinFilePaths = stdinResult.filePaths; + logger.trace(`Read ${stdinFilePaths.length} file paths from stdin in main process`); + } + // Create worker task runner const taskRunner = initTaskRunner({ numOfTasks: 1, @@ -59,13 +77,13 @@ export const runDefaultAction = async ( // Wait for worker to be ready (Bun compatibility) await waitForWorkerReady(taskRunner); - // Create task for worker (now with pre-loaded config) + // Create task for worker (now with pre-loaded config and stdin file paths) const task: DefaultActionTask = { directories, cwd, config, cliOptions, - isStdin: !!cliOptions.stdin, + stdinFilePaths, }; // Run the task in worker (spinner is handled inside worker) diff --git a/src/cli/actions/workers/defaultActionWorker.ts b/src/cli/actions/workers/defaultActionWorker.ts index c69af69a3..3d4c03576 100644 --- a/src/cli/actions/workers/defaultActionWorker.ts +++ b/src/cli/actions/workers/defaultActionWorker.ts @@ -1,8 +1,6 @@ import path from 'node:path'; import type { RepomixConfigMerged } from '../../../config/configSchema.js'; -import { readFilePathsFromStdin } from '../../../core/file/fileStdin.js'; import { type PackResult, pack } from '../../../core/packager.js'; -import { RepomixError } from '../../../shared/errorHandle.js'; import { logger, setLogLevelByWorkerData } from '../../../shared/logger.js'; import { Spinner } from '../../cliSpinner.js'; import type { CliOptions } from '../../types.js'; @@ -16,7 +14,7 @@ export interface DefaultActionTask { cwd: string; config: RepomixConfigMerged; cliOptions: CliOptions; - isStdin: boolean; + stdinFilePaths?: string[]; } export interface PingTask { @@ -46,7 +44,7 @@ async function defaultActionWorker( } // At this point, task is guaranteed to be DefaultActionTask - const { directories, cwd, config, cliOptions, isStdin } = task; + const { directories, cwd, config, cliOptions, stdinFilePaths } = task; logger.trace('Worker: Using pre-loaded config:', config); @@ -57,17 +55,10 @@ async function defaultActionWorker( let packResult: PackResult; try { - if (isStdin) { - // Handle stdin processing - // Validate directory arguments for stdin mode - const firstDir = directories[0] ?? '.'; - if (directories.length > 1 || firstDir !== '.') { - throw new RepomixError( - 'When using --stdin, do not specify directory arguments. File paths will be read from stdin.', - ); - } - - const stdinResult = await readFilePathsFromStdin(cwd); + if (stdinFilePaths) { + // Handle stdin processing with file paths from main process + // File paths were already read from stdin in the main process + logger.trace(`Worker: Processing ${stdinFilePaths.length} files from stdin`); // Use pack with predefined files from stdin packResult = await pack( @@ -77,7 +68,7 @@ async function defaultActionWorker( spinner.update(message); }, {}, - stdinResult.filePaths, + stdinFilePaths, ); } else { // Handle directory processing diff --git a/tests/cli/actions/defaultAction.test.ts b/tests/cli/actions/defaultAction.test.ts index 632276496..eff766868 100644 --- a/tests/cli/actions/defaultAction.test.ts +++ b/tests/cli/actions/defaultAction.test.ts @@ -4,6 +4,7 @@ import { buildCliConfig, runDefaultAction } from '../../../src/cli/actions/defau import { Spinner } from '../../../src/cli/cliSpinner.js'; import type { CliOptions } from '../../../src/cli/types.js'; import * as configLoader from '../../../src/config/configLoad.js'; +import * as fileStdin from '../../../src/core/file/fileStdin.js'; import * as packageJsonParser from '../../../src/core/file/packageJsonParse.js'; import * as packager from '../../../src/core/packager.js'; @@ -13,6 +14,7 @@ import { createMockConfig } from '../../testing/testUtils.js'; vi.mock('../../../src/core/packager'); vi.mock('../../../src/config/configLoad'); vi.mock('../../../src/core/file/packageJsonParse'); +vi.mock('../../../src/core/file/fileStdin'); vi.mock('../../../src/shared/logger'); vi.mock('../../../src/shared/processConcurrency'); @@ -85,6 +87,10 @@ describe('defaultAction', () => { }, }), ); + vi.mocked(fileStdin.readFilePathsFromStdin).mockResolvedValue({ + filePaths: ['test1.txt', 'test2.txt'], + emptyDirPaths: [], + }); vi.mocked(packager.pack).mockResolvedValue({ totalFiles: 10, totalCharacters: 1000, @@ -168,7 +174,6 @@ describe('defaultAction', () => { cliOptions: expect.objectContaining({ include: '*.js,*.ts', }), - isStdin: false, }); }); @@ -188,7 +193,7 @@ describe('defaultAction', () => { cliOptions: expect.objectContaining({ stdin: true, }), - isStdin: true, + stdinFilePaths: expect.any(Array), }); }); diff --git a/tests/cli/actions/workers/defaultActionWorker.test.ts b/tests/cli/actions/workers/defaultActionWorker.test.ts index a915d726f..efc83efaa 100644 --- a/tests/cli/actions/workers/defaultActionWorker.test.ts +++ b/tests/cli/actions/workers/defaultActionWorker.test.ts @@ -8,12 +8,9 @@ import defaultActionWorker, { } from '../../../../src/cli/actions/workers/defaultActionWorker.js'; import type { CliOptions } from '../../../../src/cli/types.js'; import type { RepomixConfigMerged } from '../../../../src/config/configSchema.js'; -import { readFilePathsFromStdin } from '../../../../src/core/file/fileStdin.js'; import { pack } from '../../../../src/core/packager.js'; -import { RepomixError } from '../../../../src/shared/errorHandle.js'; // Mock dependencies -vi.mock('../../../../src/core/file/fileStdin.js'); vi.mock('../../../../src/core/packager.js'); vi.mock('../../../../src/shared/logger.js', () => ({ logger: { @@ -30,7 +27,6 @@ vi.mock('../../../../src/cli/cliSpinner.js', () => ({ })), })); -const mockReadFilePathsFromStdin = vi.mocked(readFilePathsFromStdin); const mockPack = vi.mocked(pack); describe('defaultActionWorker', () => { @@ -149,7 +145,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockResolvedValueOnce(mockPackResult); @@ -173,7 +168,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockResolvedValueOnce(mockPackResult); @@ -193,7 +187,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockResolvedValueOnce(mockPackResult); @@ -205,25 +198,19 @@ describe('defaultActionWorker', () => { }); describe('stdin processing', () => { - it('should process stdin successfully with current directory', async () => { + it('should process stdin successfully with file paths from main process', async () => { const task: DefaultActionTask = { directories: ['.'], cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: true, + stdinFilePaths: ['file1.txt', 'file2.txt'], }; - const stdinResult = { - filePaths: ['file1.txt', 'file2.txt'], - emptyDirPaths: [], - }; - mockReadFilePathsFromStdin.mockResolvedValueOnce(stdinResult); mockPack.mockResolvedValueOnce(mockPackResult); const result = (await defaultActionWorker(task)) as DefaultActionWorkerResult; - expect(mockReadFilePathsFromStdin).toHaveBeenCalledWith('/test/project'); expect(mockPack).toHaveBeenCalledWith(['/test/project'], mockConfig, expect.any(Function), {}, [ 'file1.txt', 'file2.txt', @@ -240,51 +227,15 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: true, + stdinFilePaths: ['file1.txt'], }; - const stdinResult = { - filePaths: ['file1.txt'], - emptyDirPaths: [], - }; - mockReadFilePathsFromStdin.mockResolvedValueOnce(stdinResult); mockPack.mockResolvedValueOnce(mockPackResult); await defaultActionWorker(task); - expect(mockReadFilePathsFromStdin).toHaveBeenCalledWith('/test/project'); expect(mockPack).toHaveBeenCalledWith(['/test/project'], mockConfig, expect.any(Function), {}, ['file1.txt']); }); - - it('should throw error when multiple directories are specified with stdin', async () => { - const task: DefaultActionTask = { - directories: ['src', 'tests'], - cwd: '/test/project', - config: mockConfig, - cliOptions: mockCliOptions, - isStdin: true, - }; - - await expect(defaultActionWorker(task)).rejects.toThrow(RepomixError); - await expect(defaultActionWorker(task)).rejects.toThrow( - 'When using --stdin, do not specify directory arguments. File paths will be read from stdin.', - ); - }); - - it('should throw error when non-current directory is specified with stdin', async () => { - const task: DefaultActionTask = { - directories: ['src'], - cwd: '/test/project', - config: mockConfig, - cliOptions: mockCliOptions, - isStdin: true, - }; - - await expect(defaultActionWorker(task)).rejects.toThrow(RepomixError); - await expect(defaultActionWorker(task)).rejects.toThrow( - 'When using --stdin, do not specify directory arguments. File paths will be read from stdin.', - ); - }); }); describe('error handling', () => { @@ -294,7 +245,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; const packError = new Error('Pack failed'); @@ -303,35 +253,14 @@ describe('defaultActionWorker', () => { await expect(defaultActionWorker(task)).rejects.toThrow('Pack failed'); }); - it('should handle stdin read errors', async () => { - const task: DefaultActionTask = { - directories: ['.'], - cwd: '/test/project', - config: mockConfig, - cliOptions: mockCliOptions, - isStdin: true, - }; - - const stdinError = new Error('Stdin read failed'); - mockReadFilePathsFromStdin.mockRejectedValueOnce(stdinError); - - await expect(defaultActionWorker(task)).rejects.toThrow('Stdin read failed'); - }); - it('should handle pack errors during stdin processing', async () => { const task: DefaultActionTask = { directories: ['.'], cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: true, - }; - - const stdinResult = { - filePaths: ['file1.txt'], - emptyDirPaths: [], + stdinFilePaths: ['file1.txt'], }; - mockReadFilePathsFromStdin.mockResolvedValueOnce(stdinResult); const packError = new Error('Pack failed during stdin'); mockPack.mockRejectedValueOnce(packError); @@ -347,7 +276,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockImplementationOnce(async (_paths, _config, progressCallback) => { @@ -376,7 +304,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockRejectedValueOnce(new Error('Pack failed')); @@ -397,7 +324,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockResolvedValueOnce(mockPackResult); @@ -421,7 +347,6 @@ describe('defaultActionWorker', () => { cwd: '/test/project', config: mockConfig, cliOptions: mockCliOptions, - isStdin: false, }; mockPack.mockResolvedValueOnce(mockPackResult);