Skip to content

Commit 069c84d

Browse files
committed
Update for CI failures
1 parent e62513c commit 069c84d

File tree

18 files changed

+180
-106
lines changed

18 files changed

+180
-106
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Setup Node.js
2626
uses: actions/setup-node@v4
2727
with:
28-
node-version: '18'
28+
node-version: '20'
2929
registry-url: 'https://registry.npmjs.org'
3030

3131
- name: Cache dependencies

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
node-version: [16, 18, 20]
15+
node-version: [18.x, 20.x]
1616

1717
defaults:
1818
run:
@@ -82,7 +82,7 @@ jobs:
8282
- name: Setup Node.js
8383
uses: actions/setup-node@v4
8484
with:
85-
node-version: '18'
85+
node-version: '20'
8686

8787
- name: Install dependencies
8888
run: npm ci

claude-config-composer/biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"noConfusingVoidType": "error",
3737
"noConsole": "off",
3838
"noEmptyBlockStatements": "error",
39+
"noControlCharactersInRegex": "off",
3940
"useIterableCallbackReturn": "off"
4041
},
4142
"complexity": {

claude-config-composer/src/cli/commands/generate.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,8 @@ export class GenerateCommand {
129129

130130
// Validate output directory if provided
131131
if (options.output) {
132-
// For absolute paths (like in tests), just check they exist or can be created
133-
// For relative paths, validate them properly
134-
if (!path.isAbsolute(options.output)) {
135-
PathValidator.validatePath(options.output);
136-
}
132+
// Validate output path (allows absolute paths)
133+
PathValidator.validateOutputPath(options.output);
137134
}
138135
} catch (error) {
139136
if (error instanceof ValidationError) {
@@ -173,7 +170,7 @@ export class GenerateCommand {
173170
// If explicit output is provided, validate and use it
174171
if (options.output) {
175172
try {
176-
return PathValidator.validatePath(options.output);
173+
return PathValidator.validateOutputPath(options.output);
177174
} catch (error) {
178175
throw new ValidationError(
179176
`Invalid output directory: ${error instanceof PathValidationError ? error.message : 'Unknown error'}`,
@@ -186,7 +183,7 @@ export class GenerateCommand {
186183
if (await GenerateCommand.isClaudeConfigRepo()) {
187184
const configName = configs.join('-');
188185
const testPath = path.join('.test-output', configName);
189-
return PathValidator.validatePath(testPath);
186+
return PathValidator.validateOutputPath(testPath);
190187
}
191188

192189
// Default: output to current directory (production use)
@@ -220,7 +217,7 @@ export class GenerateCommand {
220217
}
221218

222219
// Validate the directory path before creating
223-
const validatedPath = PathValidator.validatePath(dirPath);
220+
const validatedPath = PathValidator.validateOutputPath(dirPath);
224221
if (validatedPath) {
225222
await fs.mkdir(validatedPath, { recursive: true });
226223
}

claude-config-composer/src/generator/config-generator.ts

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,30 @@ export class ConfigGenerator {
2626
showProgress: boolean = true
2727
): Promise<void> {
2828
// Handle output directory path
29-
// Always normalize the path, whether absolute or relative
30-
// This avoids the need for process.cwd() which can fail in CI
31-
if (!outputDir) {
32-
outputDir = '.';
29+
// Resolve to absolute path immediately while we can
30+
// This prevents issues when the current directory is deleted during execution
31+
let resolvedOutputDir = outputDir || '.';
32+
33+
// Resolve to absolute path early, before any async operations
34+
// Use a try-catch in case process.cwd() fails
35+
let absoluteOutputDir: string;
36+
try {
37+
absoluteOutputDir = path.resolve(resolvedOutputDir);
38+
} catch {
39+
// If we can't resolve (e.g., cwd deleted), use the path as-is if absolute
40+
// or throw a clear error if it's relative
41+
if (path.isAbsolute(resolvedOutputDir)) {
42+
absoluteOutputDir = path.normalize(resolvedOutputDir);
43+
} else {
44+
throw new Error(
45+
`Cannot resolve relative path '${resolvedOutputDir}' - current directory may not exist. ` +
46+
'Please provide an absolute path or ensure the current directory is valid.'
47+
);
48+
}
3349
}
3450

35-
// Normalize the path - this works for both absolute and relative paths
36-
// and doesn't require process.cwd()
37-
outputDir = path.normalize(outputDir);
51+
// Use absoluteOutputDir from here on
52+
resolvedOutputDir = absoluteOutputDir;
3853

3954
// Validate input configurations
4055
try {
@@ -79,34 +94,15 @@ export class ConfigGenerator {
7994
if (spinner) spinner.start(steps[currentStep]);
8095

8196
// Create directories
82-
// For absolute outputDir paths, join directly
83-
// For relative paths, createSafeDirectory handles validation
84-
let claudeDir: string;
85-
let agentsDir: string;
86-
let commandsDir: string;
87-
let hooksDir: string;
88-
89-
if (path.isAbsolute(outputDir)) {
90-
// For absolute paths (e.g., test temp directories), join directly
91-
claudeDir = path.join(outputDir, '.claude');
92-
agentsDir = path.join(outputDir, '.claude', 'agents');
93-
commandsDir = path.join(outputDir, '.claude', 'commands');
94-
hooksDir = path.join(outputDir, '.claude', 'hooks');
95-
} else {
96-
// For relative paths, construct the full paths
97-
claudeDir = path.join(outputDir, '.claude');
98-
agentsDir = path.join(outputDir, '.claude', 'agents');
99-
commandsDir = path.join(outputDir, '.claude', 'commands');
100-
hooksDir = path.join(outputDir, '.claude', 'hooks');
101-
}
102-
103-
// Create all directories
104-
await fs.mkdir(claudeDir, { recursive: true });
105-
await fs.mkdir(agentsDir, { recursive: true });
106-
await fs.mkdir(commandsDir, { recursive: true });
107-
await fs.mkdir(hooksDir, { recursive: true });
108-
109-
await fs.mkdir(outputDir, { recursive: true });
97+
// Since resolvedOutputDir is now always absolute, we can safely join paths
98+
const claudeDir = path.join(resolvedOutputDir, '.claude');
99+
const agentsDir = path.join(claudeDir, 'agents');
100+
const commandsDir = path.join(claudeDir, 'commands');
101+
const hooksDir = path.join(claudeDir, 'hooks');
102+
103+
// Create all directories with absolute paths
104+
// Create parent directory first to ensure it exists
105+
await fs.mkdir(resolvedOutputDir, { recursive: true });
110106
await fs.mkdir(claudeDir, { recursive: true });
111107
await fs.mkdir(agentsDir, { recursive: true });
112108
await fs.mkdir(commandsDir, { recursive: true });
@@ -126,8 +122,8 @@ export class ConfigGenerator {
126122

127123
if (claudeMdConfigs.length > 0) {
128124
const mergedClaudeMd = this.configMerger.merge(claudeMdConfigs);
129-
// Write directly to outputDir without additional path validation since outputDir is already validated
130-
const claudeMdPath = path.join(outputDir, 'CLAUDE.md');
125+
// Write directly to resolvedOutputDir without additional path validation since resolvedOutputDir is already validated
126+
const claudeMdPath = path.join(resolvedOutputDir, 'CLAUDE.md');
131127
await fs.writeFile(claudeMdPath, mergedClaudeMd);
132128
}
133129
if (spinner) spinner.succeed(steps[currentStep]);
@@ -144,9 +140,7 @@ export class ConfigGenerator {
144140
const agentName = typeof validatedAgent.name === 'string' ? validatedAgent.name : 'unknown';
145141
const filename = `${PathValidator.validateFilename(agentName.toLowerCase().replace(/[^a-z0-9-]/g, '-'))}.md`;
146142
const content = this.componentMerger.generateAgentFile(validatedAgent);
147-
// Ensure directory exists before writing
148-
await fs.mkdir(agentsDir, { recursive: true });
149-
// Write directly to agentsDir
143+
// Write directly to agentsDir (already created above)
150144
const agentPath = path.join(agentsDir, filename);
151145
await fs.writeFile(agentPath, content);
152146
}
@@ -166,9 +160,7 @@ export class ConfigGenerator {
166160
typeof validatedCommand.name === 'string' ? validatedCommand.name : 'unknown';
167161
const filename = `${PathValidator.validateFilename(commandName.toLowerCase().replace(/[^a-z0-9-]/g, '-'))}.md`;
168162
const content = this.componentMerger.generateCommandFile(validatedCommand);
169-
// Ensure directory exists before writing
170-
await fs.mkdir(commandsDir, { recursive: true });
171-
// Write directly to commandsDir
163+
// Write directly to commandsDir (already created above)
172164
const commandPath = path.join(commandsDir, filename);
173165
await fs.writeFile(commandPath, content);
174166
}
@@ -181,8 +173,7 @@ export class ConfigGenerator {
181173
const hookGroups = parsedConfigs.map(c => c.parsed.hooks);
182174
const mergedHooks = this.componentMerger.mergeHooks(hookGroups);
183175

184-
// Ensure hooks directory exists
185-
await fs.mkdir(hooksDir, { recursive: true });
176+
// Process hooks (directory already created above)
186177
for (const hook of mergedHooks) {
187178
// Validate and sanitize hook data
188179
const validatedHook = InputValidator.validateHook(hook);
@@ -221,7 +212,7 @@ export class ConfigGenerator {
221212
const settingsPath = await PathValidator.createSafeFilePath(
222213
'settings.json',
223214
'.claude',
224-
outputDir
215+
resolvedOutputDir
225216
);
226217

227218
await fs.writeFile(settingsPath, JSON.stringify(validatedSettings, null, 2));
@@ -232,7 +223,7 @@ export class ConfigGenerator {
232223
// Validate the generated structure
233224
currentStep++;
234225
if (spinner) spinner.start(steps[currentStep]);
235-
const validation = await this.validateGeneratedStructure(outputDir);
226+
const validation = await this.validateGeneratedStructure(resolvedOutputDir);
236227
if (!validation.valid) {
237228
if (spinner) spinner.warn(`${steps[currentStep]} - ${validation.errors.length} warnings`);
238229
validation.errors.forEach(error => console.warn(chalk.yellow(` - ${error}`)));

claude-config-composer/src/merger/component-merger.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,30 +274,38 @@ export class ComponentMerger {
274274
return merged;
275275
}
276276

277-
private isValidHookEntry(entry: any): boolean {
278-
return entry && typeof entry === 'object' && 'hooks' in entry && Array.isArray(entry.hooks);
277+
private isValidHookEntry(entry: unknown): boolean {
278+
return (
279+
entry !== null &&
280+
typeof entry === 'object' &&
281+
'hooks' in entry &&
282+
Array.isArray((entry as { hooks: unknown }).hooks)
283+
);
279284
}
280285

281-
private normalizeHookEntry(entry: any): HookEntry {
286+
private normalizeHookEntry(entry: unknown): HookEntry {
282287
const normalized: HookEntry = {
283288
hooks: [],
284289
};
285290

291+
const entryObj = entry as Record<string, unknown>;
292+
286293
// Only include matcher if it exists and is not empty
287-
if (entry.matcher && entry.matcher !== '') {
288-
normalized.matcher = entry.matcher;
294+
if (entryObj.matcher && entryObj.matcher !== '') {
295+
normalized.matcher = entryObj.matcher as string;
289296
}
290297

291298
// Process hooks array
292-
if (Array.isArray(entry.hooks)) {
293-
for (const hook of entry.hooks) {
299+
if (Array.isArray(entryObj.hooks)) {
300+
for (const hook of entryObj.hooks) {
294301
if (hook && typeof hook === 'object' && 'command' in hook) {
302+
const hookObj = hook as Record<string, unknown>;
295303
const hookCommand: HookCommand = {
296-
type: hook.type || 'command',
297-
command: hook.command,
304+
type: 'command',
305+
command: hookObj.command as string,
298306
};
299-
if (hook.timeout) {
300-
hookCommand.timeout = hook.timeout;
307+
if (hookObj.timeout) {
308+
hookCommand.timeout = hookObj.timeout as number;
301309
}
302310
normalized.hooks.push(hookCommand);
303311
}

claude-config-composer/src/merger/config-merger.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ConfigMetadata } from '../registry/config-registry';
2+
import type { PackageDependencies } from '../types/config';
23
import { ConfigurationError, ErrorHandler, ValidationError } from '../utils/error-handler';
34

45
interface Section {
@@ -164,7 +165,13 @@ export class ConfigMerger {
164165
.trim();
165166
}
166167

167-
merge(configs: Array<{ content: string; metadata: ConfigMetadata; dependencies?: any }>): string {
168+
merge(
169+
configs: Array<{
170+
content: string;
171+
metadata: ConfigMetadata;
172+
dependencies?: PackageDependencies;
173+
}>
174+
): string {
168175
return ErrorHandler.wrapSync(() => {
169176
// Validate inputs
170177
if (!configs || !Array.isArray(configs)) {
@@ -637,7 +644,7 @@ export class ConfigMerger {
637644

638645
private addMetadata(
639646
output: string[],
640-
configs: Array<{ metadata: ConfigMetadata; dependencies?: any }>
647+
configs: Array<{ metadata: ConfigMetadata; dependencies?: PackageDependencies }>
641648
) {
642649
output.push('');
643650
output.push('---');
@@ -662,7 +669,7 @@ export class ConfigMerger {
662669
const allPeerDeps = new Map<string, Set<string>>();
663670
const allEngines = new Map<string, Set<string>>();
664671

665-
for (const { metadata, dependencies } of configsWithDeps) {
672+
for (const { dependencies } of configsWithDeps) {
666673
if (dependencies?.peerDependencies) {
667674
for (const [pkg, version] of Object.entries(dependencies.peerDependencies)) {
668675
if (!allPeerDeps.has(pkg)) allPeerDeps.set(pkg, new Set());

claude-config-composer/src/parser/config-parser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export class ConfigParser {
154154
const hooks: Hook[] = [];
155155
let settings: Settings | null = null;
156156
let claudeMd: string | null = null;
157-
let dependencies: any;
157+
let dependencies: Record<string, string> | undefined;
158158

159159
// Validate config path
160160
if (!configPath || typeof configPath !== 'string') {
@@ -403,7 +403,7 @@ export class ConfigParser {
403403
if (frontmatterEnd > 0) {
404404
const frontmatter = lines.slice(1, frontmatterEnd).join('\n');
405405
try {
406-
metadata = yaml.load(frontmatter) as any;
406+
metadata = yaml.load(frontmatter) as Record<string, unknown>;
407407
if (!metadata || typeof metadata !== 'object') {
408408
metadata = {};
409409
}
@@ -489,7 +489,7 @@ export class ConfigParser {
489489
if (frontmatterEnd > 0) {
490490
const frontmatter = lines.slice(1, frontmatterEnd).join('\n');
491491
try {
492-
metadata = yaml.load(frontmatter) as any;
492+
metadata = yaml.load(frontmatter) as Record<string, unknown>;
493493
if (!metadata || typeof metadata !== 'object') {
494494
metadata = {};
495495
}

claude-config-composer/src/types/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ export interface ConfigComponent {
8787
metadata?: ConfigMetadata;
8888
}
8989

90+
export interface PackageDependencies {
91+
dependencies?: Record<string, string>;
92+
devDependencies?: Record<string, string>;
93+
peerDependencies?: Record<string, string>;
94+
optionalDependencies?: Record<string, string>;
95+
engines?: Record<string, string>;
96+
}
97+
9098
export interface ConfigMetadata {
9199
title?: string;
92100
description?: string;

claude-config-composer/src/ui/cli-visuals.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ export function createLoadingBar(title: string) {
110110
let gradientTitle: string;
111111
if (gradient && typeof gradient === 'function') {
112112
try {
113-
const gradientFn = (gradient as any)(['#EB6359', '#E2DDD9', '#1F1F25']);
113+
const gradientFn = (gradient as unknown as (colors: string[]) => (text: string) => string)([
114+
'#EB6359',
115+
'#E2DDD9',
116+
'#1F1F25',
117+
]);
114118
gradientTitle = gradientFn(title);
115119
} catch {
116120
gradientTitle = chalk.hex('#EB6359')(title);
@@ -135,7 +139,10 @@ export function showLivePreview(configs: string[]) {
135139
let title: string;
136140
if (gradient && typeof gradient === 'function') {
137141
try {
138-
const gradientFn = (gradient as any)(['#EB6359', '#E2DDD9']);
142+
const gradientFn = (gradient as unknown as (colors: string[]) => (text: string) => string)([
143+
'#EB6359',
144+
'#E2DDD9',
145+
]);
139146
title = gradientFn('LIVE PREVIEW - Your Configuration Blueprint');
140147
} catch {
141148
title = chalk.hex('#EB6359')('LIVE PREVIEW - Your Configuration Blueprint');

0 commit comments

Comments
 (0)