@@ -4,6 +4,7 @@ import * as path from 'node:path';
44import {
55 getFlowsToRunInSequence ,
66 isFlowFile ,
7+ isWorkspaceConfigFile ,
78 processDependencies ,
89 readDirectory ,
910 readTestYamlFileAsJson ,
@@ -198,16 +199,18 @@ function extractDeviceCloudOverrides(
198199/**
199200 * Generate execution plan for a single flow file
200201 * @param normalizedInput - Normalized path to the flow file
201- * @param configFile - Optional custom config file path
202+ * @param resolvedConfigFile - Optional absolute path to a custom config file
202203 * @returns Execution plan for the single file with dependencies
203204 */
204205async function planSingleFile (
205206 normalizedInput : string ,
206- configFile ?: string ,
207+ resolvedConfigFile ?: string ,
207208) : Promise < IExecutionPlan > {
209+ const inputBasename = path . basename ( normalizedInput ) ;
208210 if (
209- normalizedInput . endsWith ( 'config.yaml' ) ||
210- normalizedInput . endsWith ( 'config.yml' )
211+ inputBasename === 'config.yaml' ||
212+ inputBasename === 'config.yml' ||
213+ isWorkspaceConfigFile ( normalizedInput )
211214 ) {
212215 throw new Error (
213216 'If using config.yaml, pass the workspace folder path, not the config file or a custom path via --config' ,
@@ -224,13 +227,14 @@ async function planSingleFile(
224227 }
225228
226229 let workspaceConfig : IWorkspaceConfig | undefined ;
227- if ( configFile ) {
228- const configFilePath = path . resolve ( process . cwd ( ) , configFile ) ;
229- if ( ! fs . existsSync ( configFilePath ) ) {
230- throw new Error ( `Config file does not exist: ${ configFilePath } ` ) ;
230+ if ( resolvedConfigFile ) {
231+ if ( ! fs . existsSync ( resolvedConfigFile ) ) {
232+ throw new Error ( `Config file does not exist: ${ resolvedConfigFile } ` ) ;
231233 }
232234
233- workspaceConfig = readYamlFileAsJson ( configFilePath ) as IWorkspaceConfig ;
235+ workspaceConfig = readYamlFileAsJson (
236+ resolvedConfigFile ,
237+ ) as IWorkspaceConfig ;
234238 }
235239
236240 const checkedDependancies = await checkDependencies ( normalizedInput ) ;
@@ -249,17 +253,32 @@ async function planSingleFile(
249253 * @param workspaceConfig - Workspace configuration containing flow globs
250254 * @param normalizedInput - Normalized path to the workspace directory
251255 * @param unfilteredFlowFiles - List of all discovered flow files
252- * @param configFile - Optional custom config file path
256+ * @param resolvedConfigFile - Optional absolute path to a custom config file
253257 * @param excludeFlows - --exclude-flows patterns to re-apply to glob matches
254258 * @returns Filtered list of flow file paths matching the globs
255259 */
256260async function applyFlowGlobs (
257261 workspaceConfig : IWorkspaceConfig ,
258262 normalizedInput : string ,
259263 unfilteredFlowFiles : string [ ] ,
260- configFile ?: string ,
264+ resolvedConfigFile ?: string ,
261265 excludeFlows ?: string [ ] ,
262266) : Promise < string [ ] > {
267+ // Both branches compare absolute paths, so `--config ./x.yml` and
268+ // `--config /abs/x.yml` behave identically, and a same-named file in a
269+ // sibling directory is never mistaken for the active config.
270+ const activeConfig = resolvedConfigFile
271+ ? path . normalize ( resolvedConfigFile )
272+ : undefined ;
273+ const isExcludedConfig = ( absolutePath : string ) : boolean => {
274+ const base = path . basename ( absolutePath ) ;
275+ if ( base === 'config.yaml' || base === 'config.yml' ) return true ;
276+ return (
277+ activeConfig !== undefined &&
278+ path . normalize ( absolutePath ) === activeConfig
279+ ) ;
280+ } ;
281+
263282 if ( workspaceConfig . flows ) {
264283 const globs = workspaceConfig . flows . map ( ( g ) => g ) ;
265284 // fs.globSync lands in Node 22; the CLI's `engines.node` already requires it.
@@ -273,31 +292,19 @@ async function applyFlowGlobs(
273292 }
274293 } ) ;
275294
295+ // Resolve before filtering: glob matches are relative to normalizedInput,
296+ // so comparing them against the config path only ever worked when the
297+ // config happened to sit at the workspace root.
276298 const globbedFlowFiles = matchedFiles
277- . filter ( ( file : string ) => {
278- if ( file === 'config.yaml' || file === 'config.yml' ) return false ;
279- if ( configFile && file === path . basename ( configFile ) ) return false ;
280- if ( ! file . endsWith ( '.yaml' ) && ! file . endsWith ( '.yml' ) ) return false ;
281- const pathParts = file . split ( path . sep ) ;
282- for ( const part of pathParts ) {
283- if ( part . endsWith ( '.app' ) ) return false ;
284- }
285-
286- return true ;
287- } )
288- . map ( ( file ) => path . resolve ( normalizedInput , file ) ) ;
299+ . map ( ( file ) => path . resolve ( normalizedInput , file ) )
300+ . filter ( ( file ) => ! isExcludedConfig ( file ) && isFlowFile ( file ) ) ;
289301
290302 // Re-globbing from disk bypasses the earlier --exclude-flows filter, so
291303 // re-apply it here or excluded flows sneak back in via `flows:` globs.
292304 return filterFlowFiles ( globbedFlowFiles , excludeFlows ) ;
293305 }
294306
295- return unfilteredFlowFiles . filter (
296- ( file ) =>
297- ! file . endsWith ( 'config.yaml' ) &&
298- ! file . endsWith ( 'config.yml' ) &&
299- ( ! configFile || ! file . endsWith ( configFile ) ) ,
300- ) ;
307+ return unfilteredFlowFiles . filter ( ( file ) => ! isExcludedConfig ( file ) ) ;
301308}
302309
303310/**
@@ -382,6 +389,9 @@ export async function plan(options: PlanOptions): Promise<IExecutionPlan> {
382389 } = options ;
383390 const normalizedInput = path . normalize ( input ) ;
384391 const flowMetadata : Record < string , Record < string , unknown > > = { } ;
392+ const resolvedConfigFile = configFile
393+ ? path . resolve ( process . cwd ( ) , configFile )
394+ : undefined ;
385395
386396 if ( ! fs . existsSync ( normalizedInput ) ) {
387397 throw new Error (
@@ -390,7 +400,7 @@ export async function plan(options: PlanOptions): Promise<IExecutionPlan> {
390400 }
391401
392402 if ( fs . lstatSync ( normalizedInput ) . isFile ( ) ) {
393- return planSingleFile ( normalizedInput , configFile ) ;
403+ return planSingleFile ( normalizedInput , resolvedConfigFile ) ;
394404 }
395405
396406 let unfilteredFlowFiles = await readDirectory ( normalizedInput , isFlowFile ) ;
@@ -405,13 +415,14 @@ export async function plan(options: PlanOptions): Promise<IExecutionPlan> {
405415 unfilteredFlowFiles = filterFlowFiles ( unfilteredFlowFiles , excludeFlows ) ;
406416
407417 let workspaceConfig : IWorkspaceConfig ;
408- if ( configFile ) {
409- const configFilePath = path . resolve ( process . cwd ( ) , configFile ) ;
410- if ( ! fs . existsSync ( configFilePath ) ) {
411- throw new Error ( `Config file does not exist: ${ configFilePath } ` ) ;
418+ if ( resolvedConfigFile ) {
419+ if ( ! fs . existsSync ( resolvedConfigFile ) ) {
420+ throw new Error ( `Config file does not exist: ${ resolvedConfigFile } ` ) ;
412421 }
413422
414- workspaceConfig = readYamlFileAsJson ( configFilePath ) as IWorkspaceConfig ;
423+ workspaceConfig = readYamlFileAsJson (
424+ resolvedConfigFile ,
425+ ) as IWorkspaceConfig ;
415426 } else {
416427 workspaceConfig = getWorkspaceConfig ( normalizedInput , unfilteredFlowFiles ) ;
417428 }
@@ -420,10 +431,32 @@ export async function plan(options: PlanOptions): Promise<IExecutionPlan> {
420431 workspaceConfig ,
421432 normalizedInput ,
422433 unfilteredFlowFiles ,
423- configFile ,
434+ resolvedConfigFile ,
424435 excludeFlows ,
425436 ) ;
426437
438+ // The exclusions above are filename-based: they only catch
439+ // `config.yaml`/`config.yml` and the active --config target. Any other
440+ // workspace config sharing the folder (a second CI workflow's, say) is still
441+ // on the list and would be parsed as a flow, so drop config-shaped files by
442+ // shape — see dcd-cli#99.
443+ const configShapedFiles = new Set (
444+ unfilteredFlowFiles . filter ( ( file ) => isWorkspaceConfigFile ( file ) ) ,
445+ ) ;
446+ if ( configShapedFiles . size > 0 ) {
447+ if ( debug ) {
448+ console . log (
449+ `[DEBUG] Skipping ${ configShapedFiles . size } workspace config file(s): ${ [
450+ ...configShapedFiles ,
451+ ] . join ( ', ' ) } `,
452+ ) ;
453+ }
454+
455+ unfilteredFlowFiles = unfilteredFlowFiles . filter (
456+ ( file ) => ! configShapedFiles . has ( file ) ,
457+ ) ;
458+ }
459+
427460 if ( unfilteredFlowFiles . length === 0 ) {
428461 const error = workspaceConfig . flows
429462 ? new Error (
0 commit comments