Skip to content

Commit a2ca7de

Browse files
maestro: handle config.yaml and config.yml
1 parent d17f2ce commit a2ca7de

File tree

2 files changed

+88
-12
lines changed

2 files changed

+88
-12
lines changed

src/providers/maestro.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
466466
(f) =>
467467
f.endsWith('.yaml') || f.endsWith('.yml'),
468468
).filter(
469-
(f) => !f.endsWith('config.yaml'),
469+
(f) => !this.isConfigFile(f),
470470
).length,
471471
shardSplit: this.options.shardSplit,
472472
});
@@ -508,15 +508,20 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
508508
});
509509

510510
const flowFiles: string[] = [];
511-
const configPath = path.join(directory, 'config.yaml');
511+
let configPath: string | null = null;
512512
let config: MaestroConfig | null = null;
513513

514-
// Check for config.yaml
515-
try {
516-
const configContent = await fs.promises.readFile(configPath, 'utf-8');
517-
config = yaml.load(configContent) as MaestroConfig;
518-
} catch {
519-
// No config.yaml, that's fine
514+
// Check for config.yaml or config.yml
515+
for (const configName of ['config.yaml', 'config.yml']) {
516+
const candidatePath = path.join(directory, configName);
517+
try {
518+
const configContent = await fs.promises.readFile(candidatePath, 'utf-8');
519+
config = yaml.load(configContent) as MaestroConfig;
520+
configPath = candidatePath;
521+
break; // Use the first config file found
522+
} catch {
523+
// Config file doesn't exist, try next
524+
}
520525
}
521526

522527
// If config specifies flows, use those
@@ -532,7 +537,7 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
532537
const ext = path.extname(entry.name).toLowerCase();
533538
if (
534539
(ext === '.yaml' || ext === '.yml') &&
535-
entry.name !== 'config.yaml'
540+
!this.isConfigFile(entry.name)
536541
) {
537542
flowFiles.push(path.join(directory, entry.name));
538543
}
@@ -547,8 +552,8 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
547552
dependencies.forEach((dep) => allFiles.add(dep));
548553
}
549554

550-
// Include config.yaml if it exists
551-
if (config) {
555+
// Include config file if it exists
556+
if (configPath) {
552557
allFiles.add(configPath);
553558
}
554559

@@ -597,6 +602,14 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
597602
return dependencies;
598603
}
599604

605+
/**
606+
* Check if a file path is a Maestro config file (config.yaml or config.yml)
607+
*/
608+
private isConfigFile(filePath: string): boolean {
609+
const basename = path.basename(filePath);
610+
return basename === 'config.yaml' || basename === 'config.yml';
611+
}
612+
600613
/**
601614
* Check if a string looks like a file path (relative path with extension)
602615
*/
@@ -1116,7 +1129,7 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
11161129
for (const filePath of relativePaths) {
11171130
const ext = path.extname(filePath).toLowerCase();
11181131
if (ext === '.yaml' || ext === '.yml') {
1119-
if (filePath === 'config.yaml' || filePath.endsWith('/config.yaml')) {
1132+
if (this.isConfigFile(filePath)) {
11201133
groups['Config files'].push(filePath);
11211134
} else {
11221135
groups['Flow files'].push(filePath);

tests/providers/maestro.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2937,6 +2937,69 @@ flows:
29372937
path.join(projectDir, 'web', 'flows', 'test.yaml'),
29382938
);
29392939
});
2940+
2941+
it('should include config.yml (alternative extension) in discovered files when it exists', async () => {
2942+
const configContent = `
2943+
flows:
2944+
- "app/flows/**"
2945+
`;
2946+
const projectDir = path.resolve(path.sep, 'project');
2947+
// First call (config.yaml) fails, second call (config.yml) succeeds
2948+
fs.promises.readFile = jest.fn()
2949+
.mockRejectedValueOnce(new Error('ENOENT: no such file'))
2950+
.mockResolvedValueOnce(configContent);
2951+
fs.promises.readdir = jest.fn().mockResolvedValue([]);
2952+
fs.promises.access = jest.fn().mockResolvedValue(undefined);
2953+
(glob as jest.Mock).mockResolvedValue([
2954+
path.join(projectDir, 'app', 'flows', 'login.yaml'),
2955+
]);
2956+
2957+
const files = await maestro['discoverFlows'](projectDir);
2958+
2959+
expect(files).toContain(path.join(projectDir, 'config.yml'));
2960+
expect(files).not.toContain(path.join(projectDir, 'config.yaml'));
2961+
expect(files).toContain(
2962+
path.join(projectDir, 'app', 'flows', 'login.yaml'),
2963+
);
2964+
});
2965+
2966+
it('should prefer config.yaml over config.yml when both exist', async () => {
2967+
const configContent = `
2968+
flows:
2969+
- "flows/**"
2970+
`;
2971+
const projectDir = path.resolve(path.sep, 'project');
2972+
// First call (config.yaml) succeeds
2973+
fs.promises.readFile = jest.fn().mockResolvedValue(configContent);
2974+
fs.promises.readdir = jest.fn().mockResolvedValue([]);
2975+
fs.promises.access = jest.fn().mockResolvedValue(undefined);
2976+
(glob as jest.Mock).mockResolvedValue([
2977+
path.join(projectDir, 'flows', 'test.yaml'),
2978+
]);
2979+
2980+
const files = await maestro['discoverFlows'](projectDir);
2981+
2982+
// Should only include config.yaml, not config.yml
2983+
expect(files).toContain(path.join(projectDir, 'config.yaml'));
2984+
expect(files).not.toContain(path.join(projectDir, 'config.yml'));
2985+
});
2986+
2987+
it('should exclude config.yml from flow files when no config exists', async () => {
2988+
const projectDir = path.resolve(path.sep, 'project');
2989+
// Both config.yaml and config.yml fail
2990+
fs.promises.readFile = jest.fn()
2991+
.mockRejectedValue(new Error('ENOENT: no such file'));
2992+
fs.promises.readdir = jest.fn().mockResolvedValue([
2993+
{ name: 'flow1.yaml', isFile: () => true },
2994+
{ name: 'config.yml', isFile: () => true }, // Should be excluded as a config file
2995+
]);
2996+
fs.promises.access = jest.fn().mockResolvedValue(undefined);
2997+
2998+
const files = await maestro['discoverFlows'](projectDir);
2999+
3000+
expect(files).toContain(path.join(projectDir, 'flow1.yaml'));
3001+
expect(files).not.toContain(path.join(projectDir, 'config.yml'));
3002+
});
29403003
});
29413004

29423005
describe('Flow Status Display', () => {

0 commit comments

Comments
 (0)