Skip to content

Commit

Permalink
feat(config): Migrate default config management to Zod
Browse files Browse the repository at this point in the history
  • Loading branch information
yamadashy committed Nov 10, 2024
1 parent d28f34d commit 9054c15
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 84 deletions.
19 changes: 13 additions & 6 deletions src/cli/actions/defaultAction.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import path from 'node:path';
import { loadFileConfig, mergeConfigs } from '../../config/configLoad.js';
import type {
RepomixConfigCli,
RepomixConfigFile,
RepomixConfigMerged,
RepomixOutputStyle,
import {
type RepomixConfigCli,
type RepomixConfigFile,
type RepomixConfigMerged,
type RepomixOutputStyle,
repomixConfigCliSchema,
} from '../../config/configSchema.js';
import { type PackResult, pack } from '../../core/packager.js';
import { rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';
import { printCompletion, printSecurityCheck, printSummary, printTopFiles } from '../cliPrint.js';
import type { CliOptions } from '../cliRun.js';
Expand Down Expand Up @@ -111,5 +113,10 @@ const buildCliConfig = (options: CliOptions): RepomixConfigCli => {
cliConfig.output = { ...cliConfig.output, style: options.style.toLowerCase() as RepomixOutputStyle };
}

return cliConfig;
try {
return repomixConfigCliSchema.parse(cliConfig);
} catch (error) {
rethrowValidationErrorIfZodError(error, 'Invalid cli arguments');
throw error;
}

Check warning on line 121 in src/cli/actions/defaultAction.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/actions/defaultAction.ts#L119-L121

Added lines #L119 - L121 were not covered by tests
};
8 changes: 6 additions & 2 deletions src/cli/actions/initAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import pc from 'picocolors';
import type { RepomixConfigFile, RepomixOutputStyle } from '../../config/configSchema.js';
import { defaultConfig, defaultFilePathMap } from '../../config/defaultConfig.js';
import {
type RepomixConfigFile,
type RepomixOutputStyle,
defaultConfig,
defaultFilePathMap,
} from '../../config/configSchema.js';
import { getGlobalDirectory } from '../../config/globalDirectory.js';
import { logger } from '../../shared/logger.js';

Expand Down
26 changes: 18 additions & 8 deletions src/config/configLoad.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import * as fs from 'node:fs/promises';
import path from 'node:path';
import { z } from 'zod';
import { RepomixError, rethrowValidationErrorIfZodError } from '../shared/errorHandle.js';
import { logger } from '../shared/logger.js';
import {
type RepomixConfigCli,
type RepomixConfigFile,
type RepomixConfigMerged,
defaultConfig,
defaultFilePathMap,
repomixConfigFileSchema,
repomixConfigMergedSchema,
} from './configSchema.js';
import { defaultConfig, defaultFilePathMap } from './defaultConfig.js';
import { getGlobalDirectory } from './globalDirectory.js';

const defaultConfigPath = 'repomix.config.json';
Expand All @@ -30,6 +32,7 @@ export const loadFileConfig = async (rootDir: string, argConfigPath: string | nu

logger.trace('Loading local config from:', fullPath);

// Check local file existence
const isLocalFileExists = await fs
.stat(fullPath)
.then((stats) => stats.isFile())
Expand All @@ -40,6 +43,7 @@ export const loadFileConfig = async (rootDir: string, argConfigPath: string | nu
}

if (useDefaultConfig) {
// Try to load global config
const globalConfigPath = getGlobalConfigPath();
logger.trace('Loading global config from:', globalConfigPath);

Expand Down Expand Up @@ -82,32 +86,38 @@ export const mergeConfigs = (
fileConfig: RepomixConfigFile,
cliConfig: RepomixConfigCli,
): RepomixConfigMerged => {
logger.trace('Default config:', defaultConfig);

const baseConfig = defaultConfig;

// If the output file path is not provided in the config file or CLI, use the default file path for the style
if (cliConfig.output?.filePath == null && fileConfig.output?.filePath == null) {
const style = cliConfig.output?.style || fileConfig.output?.style || defaultConfig.output.style;
defaultConfig.output.filePath = defaultFilePathMap[style];
const style = cliConfig.output?.style || fileConfig.output?.style || baseConfig.output.style;
baseConfig.output.filePath = defaultFilePathMap[style];

Check warning on line 96 in src/config/configLoad.ts

View check run for this annotation

Codecov / codecov/patch

src/config/configLoad.ts#L95-L96

Added lines #L95 - L96 were not covered by tests

logger.trace('Default output file path is set to:', baseConfig.output.filePath);

Check warning on line 98 in src/config/configLoad.ts

View check run for this annotation

Codecov / codecov/patch

src/config/configLoad.ts#L98

Added line #L98 was not covered by tests
}

const mergedConfig = {
cwd,
output: {
...defaultConfig.output,
...baseConfig.output,
...fileConfig.output,
...cliConfig.output,
},
include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
ignore: {
...defaultConfig.ignore,
...baseConfig.ignore,
...fileConfig.ignore,
...cliConfig.ignore,
customPatterns: [
...(defaultConfig.ignore.customPatterns || []),
...(baseConfig.ignore.customPatterns || []),
...(fileConfig.ignore?.customPatterns || []),
...(cliConfig.ignore?.customPatterns || []),
],
},
include: [...(defaultConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
security: {
...defaultConfig.security,
...baseConfig.security,
...fileConfig.security,
...cliConfig.security,
},
Expand Down
67 changes: 39 additions & 28 deletions src/config/configSchema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { z } from 'zod';
import { RepomixError } from '../shared/errorHandle.js';

// Output style enum
export const repomixOutputStyleSchema = z.enum(['plain', 'xml', 'markdown']);
export type RepomixOutputStyle = z.infer<typeof repomixOutputStyleSchema>;

// Default values map
export const defaultFilePathMap: Record<RepomixOutputStyle, string> = {
plain: 'repomix-output.txt',
markdown: 'repomix-output.md',
xml: 'repomix-output.xml',
} as const;

// Base config schema
export const repomixConfigBaseSchema = z.object({
output: z
.object({
Expand Down Expand Up @@ -32,30 +41,35 @@ export const repomixConfigBaseSchema = z.object({
.optional(),
});

export const repomixConfigDefaultSchema = repomixConfigBaseSchema.and(
z.object({
output: z.object({
filePath: z.string(),
style: repomixOutputStyleSchema,
// Default config schema with default values
export const repomixConfigDefaultSchema = z.object({
output: z
.object({
filePath: z.string().default(defaultFilePathMap.plain),
style: repomixOutputStyleSchema.default('plain'),
headerText: z.string().optional(),
instructionFilePath: z.string().optional(),
removeComments: z.boolean(),
removeEmptyLines: z.boolean(),
topFilesLength: z.number(),
showLineNumbers: z.boolean(),
copyToClipboard: z.boolean(),
}),
include: z.array(z.string()),
ignore: z.object({
useGitignore: z.boolean(),
useDefaultPatterns: z.boolean(),
customPatterns: z.array(z.string()).optional(),
}),
security: z.object({
enableSecurityCheck: z.boolean(),
}),
}),
);
removeComments: z.boolean().default(false),
removeEmptyLines: z.boolean().default(false),
topFilesLength: z.number().int().min(0).default(5),
showLineNumbers: z.boolean().default(false),
copyToClipboard: z.boolean().default(false),
})
.default({}),
include: z.array(z.string()).default([]),
ignore: z
.object({
useGitignore: z.boolean().default(true),
useDefaultPatterns: z.boolean().default(true),
customPatterns: z.array(z.string()).default([]),
})
.default({}),
security: z
.object({
enableSecurityCheck: z.boolean().default(true),
})
.default({}),
});

export const repomixConfigFileSchema = repomixConfigBaseSchema;

Expand All @@ -70,12 +84,9 @@ export const repomixConfigMergedSchema = repomixConfigDefaultSchema
}),
);

export type RepomixOutputStyle = z.infer<typeof repomixOutputStyleSchema>;

export type RepomixConfigDefault = z.infer<typeof repomixConfigDefaultSchema>;

export type RepomixConfigFile = z.infer<typeof repomixConfigFileSchema>;

export type RepomixConfigCli = z.infer<typeof repomixConfigCliSchema>;

export type RepomixConfigMerged = z.infer<typeof repomixConfigMergedSchema>;

export const defaultConfig = repomixConfigDefaultSchema.parse({});
28 changes: 0 additions & 28 deletions src/config/defaultConfig.ts

This file was deleted.

19 changes: 9 additions & 10 deletions tests/config/configSchema.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { outro } from '@clack/prompts';
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { custom, z } from 'zod';
import {
repomixConfigBaseSchema,
repomixConfigCliSchema,
Expand Down Expand Up @@ -71,6 +72,7 @@ describe('configSchema', () => {
},
include: [],
ignore: {
customPatterns: [],
useGitignore: true,
useDefaultPatterns: true,
},
Expand All @@ -82,13 +84,8 @@ describe('configSchema', () => {
});

it('should reject incomplete config', () => {
const incompleteConfig = {
output: {
filePath: 'output.txt',
// Missing required fields
},
};
expect(() => repomixConfigDefaultSchema.parse(incompleteConfig)).toThrow(z.ZodError);
const validConfig = {};
expect(() => repomixConfigDefaultSchema.parse(validConfig)).not.toThrow();
});
});

Expand Down Expand Up @@ -166,8 +163,10 @@ describe('configSchema', () => {

it('should reject merged config missing required fields', () => {
const invalidConfig = {
cwd: '/path/to/project',
// Missing required output field
output: {
filePath: 'output.txt',
// Missing required fields
},
};
expect(() => repomixConfigMergedSchema.parse(invalidConfig)).toThrow(z.ZodError);
});
Expand Down
3 changes: 1 addition & 2 deletions tests/testing/testUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os from 'node:os';
import process from 'node:process';
import type { RepomixConfigMerged } from '../../src/config/configSchema.js';
import { defaultConfig } from '../../src/config/defaultConfig.js';
import { type RepomixConfigMerged, defaultConfig } from '../../src/config/configSchema.js';

type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
Expand Down

0 comments on commit 9054c15

Please sign in to comment.