nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: compose-spec/compose-go
Pull request #620 (2024-04-19 11:09:35)

detect self-reference and better report depends_on cycles
fixes https://github.com/docker/compose/issues/11586


Patch:
diff --git a/graph/cycle.go b/graph/cycle.go
index d96785c..60c37ce 100644
--- a/graph/cycle.go
+++ b/graph/cycle.go
@@ -50,8 +50,8 @@ func (g *graph[T]) checkCycle() error {
 func searchCycle[T any](path []string, v *vertex[T]) error {
 	names := utils.MapKeys(v.children)
 	for _, name := range names {
-		if i := slices.Index(path, name); i > 0 {
-			return fmt.Errorf("dependency cycle detected: %s", strings.Join(path[i:], " -> "))
+		if i := slices.Index(path, name); i >= 0 {
+			return fmt.Errorf("dependency cycle detected: %s -> %s", strings.Join(path[i:], " -> "), name)
 		}
 		ch := v.children[name]
 		err := searchCycle(append(path, name), ch)


Test patch:
diff --git a/graph/graph_test.go b/graph/graph_test.go
index b6d88ed..1df41a1 100644
--- a/graph/graph_test.go
+++ b/graph/graph_test.go
@@ -325,7 +325,7 @@ func Test_detectCycle(t *testing.T) {
 	graph := exampleGraph()
 	graph.addEdge("B", "D")
 	err := graph.checkCycle()
-	assert.Error(t, err, "dependency cycle detected: D -> C -> B")
+	assert.Error(t, err, "dependency cycle detected: B -> D -> C -> B")
 }
 
 func TestWith_RootNodesAndUp(t *testing.T) {
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 89796b6..df6beb9 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -2625,7 +2625,21 @@ func TestLoadDependsOnCycle(t *testing.T) {
 			},
 		},
 	})
-	assert.Error(t, err, "dependency cycle detected: service2 -> service3 -> service1", err)
+	assert.Error(t, err, "dependency cycle detected: service1 -> service2 -> service3 -> service1", err)
+}
+
+func TestLoadDependsOnSelf(t *testing.T) {
+	workingDir, err := os.Getwd()
+	assert.NilError(t, err)
+	_, err = LoadWithContext(context.Background(), types.ConfigDetails{
+		WorkingDir: filepath.Join(workingDir, "testdata"),
+		ConfigFiles: []types.ConfigFile{
+			{
+				Filename: filepath.Join(workingDir, "testdata", "compose-depends-on-self.yaml"),
+			},
+		},
+	})
+	assert.Error(t, err, "dependency cycle detected: service1 -> service1", err)
 }
 
 func TestLoadWithDependsOn(t *testing.T) {
diff --git a/loader/testdata/compose-depends-on-self.yaml b/loader/testdata/compose-depends-on-self.yaml
new file mode 100644
index 0000000..14bc76d
--- /dev/null
+++ b/loader/testdata/compose-depends-on-self.yaml
@@ -0,0 +1,6 @@
+name : depends-on-cycle
+services:
+  service1:
+    image: service1
+    depends_on:
+      - service1
Repository: compose-spec/compose-go
Pull request #637 (2024-06-07 16:15:44)

networks.enable_ipv6: make it optional
This field isn't marked as required in the compose-spec, but it's implemented as a basic `bool` in this package. This means it automatically defaults to `false` when not specified.

A recent change was made to Docker Engine to allow users to define a daemon-wide default value for the equivalent API field. Without this change, Compose would always send a value to the Engine API, preventing it from defining its own default value.

See https://github.com/moby/moby/pull/47867.

ndeloof: LGTM as this reflects change in the API https://github.com/moby/moby/pull/47867/files#diff-28bc7f6688f2249afb33f1c3b4558c3bf1bfcda87f1b8e385bc8a5f04b8bdfeaL454-R454

Patch:
diff --git a/types/types.go b/types/types.go
index a5d9feb..56af93e 100644
--- a/types/types.go
+++ b/types/types.go
@@ -698,7 +698,7 @@ type NetworkConfig struct {
 	Internal   bool       `yaml:"internal,omitempty" json:"internal,omitempty"`
 	Attachable bool       `yaml:"attachable,omitempty" json:"attachable,omitempty"`
 	Labels     Labels     `yaml:"labels,omitempty" json:"labels,omitempty"`
-	EnableIPv6 bool       `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"`
+	EnableIPv6 *bool      `yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"`
 	Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
 }
 


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index aed2353..da8ba2d 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -1689,6 +1689,7 @@ networks:
 
 	workingDir, err := os.Getwd()
 	assert.NilError(t, err)
+	enableIPv6 := true
 	expected := &types.Project{
 		Name:       "load-network-link-local-ips",
 		WorkingDir: workingDir,
@@ -1711,7 +1712,7 @@ networks:
 			"network1": {
 				Name:       "network1",
 				Driver:     "bridge",
-				EnableIPv6: true,
+				EnableIPv6: &enableIPv6,
 				Ipam: types.IPAMConfig{
 					Config: []*types.IPAMPool{
 						{Subnet: "10.1.0.0/16"},
Repository: compose-spec/compose-go
Pull request #659 (2024-07-09 13:16:22)

Add x- extensions to depends_on
Add x-* extensions to `depends_on` in compose-spec.

fixes: https://github.com/docker/compose/issues/11930


Patch:
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index 69bbfcc..11a733b 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -197,6 +197,7 @@
                 "^[a-zA-Z0-9._-]+$": {
                   "type": "object",
                   "additionalProperties": false,
+                  "patternProperties": {"^x-": {}},
                   "properties": {
                     "restart": {"type": ["boolean", "string"]},
                     "required": {


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 5c93e69..c947211 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -314,6 +314,12 @@ configs:
     external: true
     x-config-ext: config
 services:
+  another:
+    image: busybox
+    depends_on:
+      foo:
+        condition: service_started
+        x-depends-on: depends
   foo:
     image: busybox
     x-foo: bar
@@ -325,7 +331,7 @@ services:
 	assert.Check(t, is.DeepEqual(types.Extensions{
 		"x-project": "project",
 	}, actual.Extensions))
-	assert.Check(t, is.Len(actual.Services, 1))
+	assert.Check(t, is.Len(actual.Services, 2))
 	service := actual.Services["foo"]
 	assert.Check(t, is.Equal("busybox", service.Image))
 
@@ -341,6 +347,11 @@ services:
 	assert.Check(t, is.DeepEqual(types.Extensions{
 		"x-healthcheck": "health",
 	}, service.HealthCheck.Extensions))
+
+	another := actual.Services["another"]
+	assert.Check(t, is.DeepEqual(types.Extensions{
+		"x-depends-on": "depends",
+	}, another.DependsOn["foo"].Extensions))
 }
 
 func TestLoadExtends(t *testing.T) {
Repository: compose-spec/compose-go
Pull request #665 (2024-07-22 09:05:32)

resolveRelativePaths can run with non-canonical model
closes https://github.com/docker/compose/issues/11994


Patch:
diff --git a/loader/extends.go b/loader/extends.go
index 21730f1..b299045 100644
--- a/loader/extends.go
+++ b/loader/extends.go
@@ -190,7 +190,7 @@ func getExtendsBaseFromFile(
 			)
 		}
 
-		// Attempt to make a canonical model so ResolveRelativePaths can operate on source:target short syntaxes
+		// Attempt to make a canonical model so ResolveRelativePaths can operate on source:target short syntax
 		source, err = transform.Canonical(source, true)
 		if err != nil {
 			return nil, nil, err
diff --git a/paths/resolve.go b/paths/resolve.go
index 3bb97f8..303f39e 100644
--- a/paths/resolve.go
+++ b/paths/resolve.go
@@ -116,24 +116,30 @@ func (r *relativePathsResolver) absPath(value any) (any, error) {
 		}
 		return v, nil
 	}
+
 	return nil, fmt.Errorf("unexpected type %T", value)
 }
 
 func (r *relativePathsResolver) absVolumeMount(a any) (any, error) {
-	vol := a.(map[string]any)
-	if vol["type"] != types.VolumeTypeBind {
+	switch vol := a.(type) {
+	case map[string]any:
+		if vol["type"] != types.VolumeTypeBind {
+			return vol, nil
+		}
+		src, ok := vol["source"]
+		if !ok {
+			return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
+		}
+		abs, err := r.maybeUnixPath(src.(string))
+		if err != nil {
+			return nil, err
+		}
+		vol["source"] = abs
 		return vol, nil
+	default:
+		// not using canonical format, skip
+		return a, nil
 	}
-	src, ok := vol["source"]
-	if !ok {
-		return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
-	}
-	abs, err := r.maybeUnixPath(src.(string))
-	if err != nil {
-		return nil, err
-	}
-	vol["source"] = abs
-	return vol, nil
 }
 
 func (r *relativePathsResolver) volumeDriverOpts(a any) (any, error) {


Test patch:
diff --git a/loader/extends_test.go b/loader/extends_test.go
index 3990b3d..8f25f35 100644
--- a/loader/extends_test.go
+++ b/loader/extends_test.go
@@ -484,3 +484,20 @@ services:
 	assert.NilError(t, err)
 	assert.Check(t, p.Services["test"].Command == nil)
 }
+
+func TestExtendsWithInterpolation(t *testing.T) {
+	yaml := `
+name: test-extends-with-interpolation
+services:
+  test:
+    extends: { file: testdata/extends/interpolated.yaml, service: foo }
+`
+	p, err := Load(types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{{
+			Content:  []byte(yaml),
+			Filename: "-",
+		}},
+	})
+	assert.NilError(t, err)
+	assert.Check(t, p.Services["test"].Volumes[0].Source == "/dev/null")
+}
diff --git a/loader/testdata/extends/interpolated.yaml b/loader/testdata/extends/interpolated.yaml
new file mode 100644
index 0000000..17d0ac5
--- /dev/null
+++ b/loader/testdata/extends/interpolated.yaml
@@ -0,0 +1,5 @@
+services:
+  foo:
+    image: bash
+    volumes:
+      - ${SOURCE:-/dev/null}:/tmp/foo:ro
\ No newline at end of file
Repository: compose-spec/compose-go
Pull request #672 (2024-08-06 12:53:06)

Add extensions to watch.trigger
Add Extensions to watch object

This is necessary in order to first introduce `x-initalSync` inside a watch confioguration related to:
- https://github.com/docker/compose/pull/12047


Patch:
diff --git a/schema/compose-spec.json b/schema/compose-spec.json
index 5ec6218..335cbe0 100644
--- a/schema/compose-spec.json
+++ b/schema/compose-spec.json
@@ -480,7 +480,9 @@
               "path": {"type": "string"},
               "action": {"type": "string", "enum": ["rebuild", "sync", "sync+restart"]},
               "target": {"type": "string"}
-            }
+            },
+            "additionalProperties": false,
+            "patternProperties": {"^x-": {}}
           },
           "additionalProperties": false,
           "patternProperties": {"^x-": {}}
diff --git a/types/derived.gen.go b/types/derived.gen.go
index dd1ea8a..c78b17c 100644
--- a/types/derived.gen.go
+++ b/types/derived.gen.go
@@ -1814,6 +1814,12 @@ func deriveDeepCopy_40(dst, src *Trigger) {
 		}
 		copy(dst.Ignore, src.Ignore)
 	}
+	if src.Extensions != nil {
+		dst.Extensions = make(map[string]any, len(src.Extensions))
+		src.Extensions.DeepCopy(dst.Extensions)
+	} else {
+		dst.Extensions = nil
+	}
 }
 
 // deriveDeepCopy_41 recursively copies the contents of src into dst.
diff --git a/types/develop.go b/types/develop.go
index 6eeef7a..24e142c 100644
--- a/types/develop.go
+++ b/types/develop.go
@@ -31,8 +31,9 @@ const (
 )
 
 type Trigger struct {
-	Path   string      `yaml:"path" json:"path"`
-	Action WatchAction `yaml:"action" json:"action"`
-	Target string      `yaml:"target,omitempty" json:"target,omitempty"`
-	Ignore []string    `yaml:"ignore,omitempty" json:"ignore,omitempty"`
+	Path       string      `yaml:"path" json:"path"`
+	Action     WatchAction `yaml:"action" json:"action"`
+	Target     string      `yaml:"target,omitempty" json:"target,omitempty"`
+	Ignore     []string    `yaml:"ignore,omitempty" json:"ignore,omitempty"`
+	Extensions Extensions  `yaml:"#extensions,inline,omitempty" json:"-"`
 }


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index c947211..2cb1d49 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -2885,6 +2885,7 @@ services:
         # sync static content
         - path: ./webapp/html
           action: sync
+          x-initialSync: true
           target: /var/www
           ignore:
             - node_modules/
@@ -2920,6 +2921,9 @@ services:
 				Action: types.WatchActionSync,
 				Target: "/var/www",
 				Ignore: []string{"node_modules/"},
+				Extensions: types.Extensions{
+					"x-initialSync": true,
+				},
 			},
 		},
 	})
Repository: compose-spec/compose-go
Pull request #683 (2024-09-17 12:38:29)

Reapply "accept dashes in variable names"
This reverts commit aa1db26a987c000b7842013d6f64f0f294d2caf8.

closes https://github.com/docker/compose/issues/12123


Patch:
diff --git a/dotenv/fixtures/special.env b/dotenv/fixtures/special.env
new file mode 100644
index 0000000..5499b0c
--- /dev/null
+++ b/dotenv/fixtures/special.env
@@ -0,0 +1,3 @@
+VAR.WITH.DOTS=dots
+VAR_WITH_UNDERSCORES=underscores
+VAR-WITH-DASHES=dashes
diff --git a/dotenv/parser.go b/dotenv/parser.go
index b1055c9..85dda73 100644
--- a/dotenv/parser.go
+++ b/dotenv/parser.go
@@ -119,7 +119,7 @@ loop:
 			offset = i + 1
 			inherited = rune == '\n'
 			break loop
-		case '_', '.', '[', ']':
+		case '_', '.', '-', '[', ']':
 		default:
 			// variable name should match [A-Za-z0-9_.-]
 			if unicode.IsLetter(rune) || unicode.IsNumber(rune) {


Test patch:
diff --git a/dotenv/godotenv_test.go b/dotenv/godotenv_test.go
index 84f443e..0d77906 100644
--- a/dotenv/godotenv_test.go
+++ b/dotenv/godotenv_test.go
@@ -691,6 +691,14 @@ func TestUTF8BOM(t *testing.T) {
 	loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
 }
 
+func TestDash(t *testing.T) {
+	loadEnvAndCompareValues(t, Load, "fixtures/special.env", map[string]string{
+		"VAR-WITH-DASHES":      "dashes",
+		"VAR.WITH.DOTS":        "dots",
+		"VAR_WITH_UNDERSCORES": "underscores",
+	}, noopPresets)
+}
+
 func TestGetEnvFromFile(t *testing.T) {
 	wd := t.TempDir()
 	f := filepath.Join(wd, ".env")
diff --git a/dotenv/parser_test.go b/dotenv/parser_test.go
index 6764d58..54580e2 100644
--- a/dotenv/parser_test.go
+++ b/dotenv/parser_test.go
@@ -13,17 +13,17 @@ var testInput = `
 a=b
 a[1]=c
 a.propertyKey=d
-árvíztűrőTÜKÖRFÚRÓGÉP=ÁRVÍZTŰRŐtükörfúrógép
+árvíztűrő-TÜKÖRFÚRÓGÉP=ÁRVÍZTŰRŐ-tükörfúrógép
 `
 
 func TestParseBytes(t *testing.T) {
 	p := newParser()
 
 	expectedOutput := map[string]string{
-		"a":                     "b",
-		"a[1]":                  "c",
-		"a.propertyKey":         "d",
-		"árvíztűrőTÜKÖRFÚRÓGÉP": "ÁRVÍZTŰRŐtükörfúrógép",
+		"a":                      "b",
+		"a[1]":                   "c",
+		"a.propertyKey":          "d",
+		"árvíztűrő-TÜKÖRFÚRÓGÉP": "ÁRVÍZTŰRŐ-tükörfúrógép",
 	}
 
 	out := map[string]string{}
Repository: compose-spec/compose-go
Pull request #687 (2024-09-30 08:05:44)

allow use of x-* service names as depends_on keys
fix https://github.com/docker/compose/issues/12171


Patch:
diff --git a/loader/loader.go b/loader/loader.go
index 25d2d0c..db415a3 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -675,6 +675,7 @@ func NormalizeProjectName(s string) string {
 
 var userDefinedKeys = []tree.Path{
 	"services",
+	"services.*.depends_on",
 	"volumes",
 	"networks",
 	"secrets",
@@ -687,7 +688,7 @@ func processExtensions(dict map[string]any, p tree.Path, extensions map[string]a
 	for key, value := range dict {
 		skip := false
 		for _, uk := range userDefinedKeys {
-			if uk.Matches(p) {
+			if p.Matches(uk) {
 				skip = true
 				break
 			}
@@ -770,14 +771,14 @@ func secretConfigDecoderHook(from, to reflect.Type, data interface{}) (interface
 	// Check if the input is a map and we're decoding into a SecretConfig
 	if from.Kind() == reflect.Map && to == reflect.TypeOf(types.SecretConfig{}) {
 		if v, ok := data.(map[string]interface{}); ok {
-			if ext, ok := v["#extensions"].(map[string]interface{}); ok {
+			if ext, ok := v[consts.Extensions].(map[string]interface{}); ok {
 				if val, ok := ext[types.SecretConfigXValue].(string); ok {
 					// Return a map with the Content field populated
 					v["Content"] = val
 					delete(ext, types.SecretConfigXValue)
 
 					if len(ext) == 0 {
-						delete(v, "#extensions")
+						delete(v, consts.Extensions)
 					}
 				}
 			}


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index cba477f..7b9e167 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -3409,3 +3409,24 @@ services:
 		"myhost": []string{"0.0.0.1", "0.0.0.2"},
 	})
 }
+
+func TestLoadDependsOnX(t *testing.T) {
+	p, err := loadYAML(`
+name: load-depends-on-x
+services:
+  test:
+    image: test
+    depends_on:
+      - x-foo
+  x-foo:
+    image: foo
+`)
+	assert.NilError(t, err)
+	test := p.Services["test"]
+	assert.DeepEqual(t, test.DependsOn, types.DependsOnConfig{
+		"x-foo": types.ServiceDependency{
+			Condition: types.ServiceConditionStarted,
+			Required:  true,
+		},
+	})
+}
diff --git a/schema/schema_test.go b/schema/schema_test.go
index 902805b..766f4dd 100644
--- a/schema/schema_test.go
+++ b/schema/schema_test.go
@@ -66,6 +66,13 @@ func TestValidateAllowsXFields(t *testing.T) {
 			"bar": dict{
 				"x-extra-stuff": dict{},
 			},
+			"foo": dict{
+				"depends_on": dict{
+					"x-dependency": dict{
+						"condition": "service_started",
+					},
+				},
+			},
 		},
 		"volumes": dict{
 			"bar": dict{
Repository: compose-spec/compose-go
Pull request #704 (2024-10-29 18:52:52)

fix(reset): Add cycle detection fix for services that are prefixed with the name of a preceding service
Fixed the cycle detector https://github.com/compose-spec/compose-go/pull/703  

It detects cycle for services that are prefixed with the name of a preceding service
https://github.com/docker/compose/issues/12235#issuecomment-2445030390


Patch:
diff --git a/loader/reset.go b/loader/reset.go
index 213d0e8..8dd5e1b 100644
--- a/loader/reset.go
+++ b/loader/reset.go
@@ -63,7 +63,7 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
 	}
 
 	// Mark the current node as visited
-	p.visitedNodes[node] = pathStr
+	p.visitedNodes[node] = pathStr + "."
 
 	// If the node is an alias, We need to process the alias field in order to consider the !override and !reset tags
 	if node.Kind == yaml.AliasNode {


Test patch:
diff --git a/loader/reset_test.go b/loader/reset_test.go
index 3b070c3..57a24d9 100644
--- a/loader/reset_test.go
+++ b/loader/reset_test.go
@@ -83,22 +83,71 @@ networks:
 }
 
 func TestResetCycle(t *testing.T) {
-	_, err := Load(
-		types.ConfigDetails{
-			ConfigFiles: []types.ConfigFile{
-				{
-					Filename: "(inline)",
-					Content: []byte(`
+	tests := []struct {
+		name        string
+		config      string
+		expectError bool
+		errorMsg    string
+	}{
+		{
+			name: "no cycle",
+			config: `
+name: test
+services:
+  a: &a
+    image: alpine
+  a2: *a
+`,
+			expectError: false,
+			errorMsg:    "",
+		},
+		{
+			name: "no cycle reversed",
+			config: `
+name: test
+services:
+  a2: &a
+    image: alpine
+  a: *a
+`,
+			expectError: false,
+			errorMsg:    "",
+		},
+		{
+			name: "healthcheck_cycle",
+			config: `
 x-healthcheck: &healthcheck
   egress-service:
     <<: *healthcheck
-`),
-				},
-			},
-		}, func(options *Options) {
-			options.SkipNormalization = true
-			options.SkipConsistencyCheck = true
+`,
+			expectError: true,
+			errorMsg:    "cycle detected at path: x-healthcheck.egress-service",
 		},
-	)
-	assert.Error(t, err, "cycle detected at path: x-healthcheck.egress-service")
+	}
+
+	for _, tt := range tests {
+		t.Run(
+			tt.name, func(t *testing.T) {
+				_, err := Load(
+					types.ConfigDetails{
+						ConfigFiles: []types.ConfigFile{
+							{
+								Filename: "(inline)",
+								Content:  []byte(tt.config),
+							},
+						},
+					}, func(options *Options) {
+						options.SkipNormalization = true
+						options.SkipConsistencyCheck = true
+					},
+				)
+
+				if tt.expectError {
+					assert.Error(t, err, tt.errorMsg)
+				} else {
+					assert.NilError(t, err)
+				}
+			},
+		)
+	}
 }
Repository: compose-spec/compose-go
Pull request #705 (2024-10-30 05:13:01)

fix(loader): Add fix for stdin config read
Issue: https://github.com/docker/compose/issues/12248

This PR https://github.com/compose-spec/compose-go/pull/701 broke stdin config file read because it prepends working dir to the "-" and it tries to load the path.



Patch:
diff --git a/cli/options.go b/cli/options.go
index b2ab60e..caa0766 100644
--- a/cli/options.go
+++ b/cli/options.go
@@ -414,7 +414,7 @@ func (o *ProjectOptions) ReadConfigFiles(ctx context.Context, workingDir string,
 	for i, c := range config.ConfigFiles {
 		var err error
 		var b []byte
-		if c.Filename == "-" {
+		if c.IsStdin() {
 			b, err = io.ReadAll(os.Stdin)
 			if err != nil {
 				return nil, err
diff --git a/loader/loader.go b/loader/loader.go
index 62030f2..8fb9508 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -318,6 +318,13 @@ func LoadConfigFiles(ctx context.Context, configFiles []string, workingDir strin
 	opts.ResourceLoaders = append(opts.ResourceLoaders, localResourceLoader{})
 
 	for i, p := range configFiles {
+		if p == "-" {
+			config.ConfigFiles[i] = types.ConfigFile{
+				Filename: p,
+			}
+			continue
+		}
+
 		for _, loader := range opts.ResourceLoaders {
 			_, isLocalResourceLoader := loader.(localResourceLoader)
 			if !loader.Accept(p) {
diff --git a/types/config.go b/types/config.go
index a2382fb..d73d2b9 100644
--- a/types/config.go
+++ b/types/config.go
@@ -67,6 +67,10 @@ type ConfigFile struct {
 	Config map[string]interface{}
 }
 
+func (cf ConfigFile) IsStdin() bool {
+	return cf.Filename == "-"
+}
+
 func ToConfigFiles(path []string) (f []ConfigFile) {
 	for _, p := range path {
 		f = append(f, ConfigFile{Filename: p})


Test patch:
diff --git a/cli/options_test.go b/cli/options_test.go
index 31edf9b..5dbba5d 100644
--- a/cli/options_test.go
+++ b/cli/options_test.go
@@ -242,6 +242,36 @@ func TestProjectComposefilesFromWorkingDir(t *testing.T) {
 	})
 }
 
+func TestProjectComposefilesFromStdin(t *testing.T) {
+	composeData := `
+services:
+  simple:
+    image: nginx
+`
+	originalStdin := os.Stdin
+	r, w, _ := os.Pipe()
+	defer func() {
+		os.Stdin = originalStdin
+	}()
+
+	w.WriteString(composeData)
+	w.Close()
+
+	os.Stdin = r
+
+	opts, err := NewProjectOptions(
+		[]string{
+			"-",
+		}, WithName("my_project"),
+	)
+	assert.NilError(t, err)
+	p, err := opts.LoadProject(context.TODO())
+	assert.NilError(t, err)
+	service, err := p.GetService("simple")
+	assert.NilError(t, err)
+	assert.Equal(t, service.Image, "nginx")
+}
+
 func TestProjectWithDotEnv(t *testing.T) {
 	wd, err := os.Getwd()
 	assert.NilError(t, err)