Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/cli/actions/defaultAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<DefaultActionTask | PingTask, DefaultActionWorkerResult | PingResult>({
numOfTasks: 1,
Expand All @@ -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)
Expand Down
23 changes: 7 additions & 16 deletions src/cli/actions/workers/defaultActionWorker.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,7 +14,7 @@ export interface DefaultActionTask {
cwd: string;
config: RepomixConfigMerged;
cliOptions: CliOptions;
isStdin: boolean;
stdinFilePaths?: string[];
}

export interface PingTask {
Expand Down Expand Up @@ -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);

Expand All @@ -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(
Expand All @@ -77,7 +68,7 @@ async function defaultActionWorker(
spinner.update(message);
},
{},
stdinResult.filePaths,
stdinFilePaths,
);
} else {
// Handle directory processing
Expand Down
9 changes: 7 additions & 2 deletions tests/cli/actions/defaultAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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');

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -168,7 +174,6 @@ describe('defaultAction', () => {
cliOptions: expect.objectContaining({
include: '*.js,*.ts',
}),
isStdin: false,
});
});

Expand All @@ -188,7 +193,7 @@ describe('defaultAction', () => {
cliOptions: expect.objectContaining({
stdin: true,
}),
isStdin: true,
stdinFilePaths: expect.any(Array),
});
});

Expand Down
83 changes: 4 additions & 79 deletions tests/cli/actions/workers/defaultActionWorker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -30,7 +27,6 @@ vi.mock('../../../../src/cli/cliSpinner.js', () => ({
})),
}));

const mockReadFilePathsFromStdin = vi.mocked(readFilePathsFromStdin);
const mockPack = vi.mocked(pack);

describe('defaultActionWorker', () => {
Expand Down Expand Up @@ -149,7 +145,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockResolvedValueOnce(mockPackResult);
Expand All @@ -173,7 +168,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockResolvedValueOnce(mockPackResult);
Expand All @@ -193,7 +187,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockResolvedValueOnce(mockPackResult);
Expand All @@ -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',
Expand All @@ -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', () => {
Expand All @@ -294,7 +245,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

const packError = new Error('Pack failed');
Expand All @@ -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);
Expand All @@ -347,7 +276,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockImplementationOnce(async (_paths, _config, progressCallback) => {
Expand Down Expand Up @@ -376,7 +304,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockRejectedValueOnce(new Error('Pack failed'));
Expand All @@ -397,7 +324,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockResolvedValueOnce(mockPackResult);
Expand All @@ -421,7 +347,6 @@ describe('defaultActionWorker', () => {
cwd: '/test/project',
config: mockConfig,
cliOptions: mockCliOptions,
isStdin: false,
};

mockPack.mockResolvedValueOnce(mockPackResult);
Expand Down
Loading