Repository: onflow/flow-cli (go)

Issue:
`-f` flag fails to merge configs, throws error.
### Problem

The `-f` flag, used for merging `flow.json` variants has stopped working. 

## Context 

Here are the files I'm attempting to merge.

https://github.com/onflow/kitty-items/blob/master/flow.testnet.json
https://github.com/onflow/kitty-items/blob/master/flow.json

### Steps to Reproduce

Running this command in the Kitty Items project: 

```
flow project deploy --network testnet -f flow.json -f flow.testnet.json
```

- Results in an error: 
```
❌ Config Error: deployment contains nonexisting network testnet
```

### Acceptance Criteria

The command should deploy the project's contracts without error.

### CLI version

0.27.0

Pull request description:
Bugfix config cross-reference composition
Closes #365 

## Description
This bugfix solves a problem with config composition and cross-referencing. When config was loaded it was also validated and since the validation happened on lower level it didn't validate the whole composed config but only that config file, and in cases where that file referenced another config it failed, validation was now moved to upper layer and it's now done on the composed configuration.
______

For contributor use:

- [x] Targeted PR against `master` branch
- [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [x] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [x] Updated relevant documentation
- [x] Re-reviewed `Files changed` in the Github PR explorer
- [x] Added appropriate labels


Patch:
diff --git a/pkg/flowkit/config/json/config.go b/pkg/flowkit/config/json/config.go
index 0f4c6acb..d190e913 100644
--- a/pkg/flowkit/config/json/config.go
+++ b/pkg/flowkit/config/json/config.go
@@ -67,11 +67,6 @@ func (j *jsonConfig) transformToConfig() (*config.Config, error) {
 		Deployments: deployments,
 	}
 
-	err = conf.Validate()
-	if err != nil {
-		return nil, err
-	}
-
 	return conf, nil
 }
 
diff --git a/pkg/flowkit/config/loader.go b/pkg/flowkit/config/loader.go
index 320e448c..1fe48621 100644
--- a/pkg/flowkit/config/loader.go
+++ b/pkg/flowkit/config/loader.go
@@ -119,9 +119,7 @@ func (l *Loader) LoadConfig(confPath string) (*Config, error) {
 		return nil, fmt.Errorf("parser not found for config: %s", confPath)
 	}
 
-	conf, err := configParser.Deserialize(preProcessed)
-	return conf, err
-
+	return configParser.Deserialize(preProcessed)
 }
 
 // Load loads configuration from one or more file paths.
@@ -209,6 +207,12 @@ func (l *Loader) postprocess(baseConf *Config) (*Config, error) {
 		l.composeConfig(baseConf, accountConf)
 	}
 
+	// validate as part of post processing
+	err := baseConf.Validate()
+	if err != nil {
+		return nil, err
+	}
+
 	return baseConf, nil
 }
 


Test patch:
diff --git a/pkg/flowkit/config/json/config_test.go b/pkg/flowkit/config/json/config_test.go
index e8041a4a..0d995160 100644
--- a/pkg/flowkit/config/json/config_test.go
+++ b/pkg/flowkit/config/json/config_test.go
@@ -76,8 +76,10 @@ func Test_NonExistingContractForDeployment(t *testing.T) {
 	}`)
 
 	parser := NewParser()
-	_, err := parser.Deserialize(b)
+	config, err := parser.Deserialize(b)
+	assert.NoError(t, err)
 
+	err = config.Validate()
 	assert.Equal(t, err.Error(), "deployment contains nonexisting contract FungibleToken")
 }
 
@@ -103,8 +105,10 @@ func Test_NonExistingAccountForDeployment(t *testing.T) {
 	}`)
 
 	parser := NewParser()
-	_, err := parser.Deserialize(b)
+	conf, err := parser.Deserialize(b)
+	assert.NoError(t, err)
 
+	err = conf.Validate()
 	assert.Equal(t, err.Error(), "deployment contains nonexisting account test-1")
 }
 
@@ -128,8 +132,10 @@ func Test_NonExistingNetworkForDeployment(t *testing.T) {
 	}`)
 
 	parser := NewParser()
-	_, err := parser.Deserialize(b)
+	conf, err := parser.Deserialize(b)
+	assert.NoError(t, err)
 
+	err = conf.Validate()
 	assert.Equal(t, err.Error(), "deployment contains nonexisting network foo")
 }
 
@@ -144,7 +150,9 @@ func Test_NonExistingAccountForEmulator(t *testing.T) {
 	}`)
 
 	parser := NewParser()
-	_, err := parser.Deserialize(b)
+	conf, err := parser.Deserialize(b)
+	assert.NoError(t, err)
 
+	err = conf.Validate()
 	assert.Equal(t, err.Error(), "emulator default contains nonexisting service account emulator-account")
 }
diff --git a/pkg/flowkit/config/loader_test.go b/pkg/flowkit/config/loader_test.go
index 52f234e1..21e9b104 100644
--- a/pkg/flowkit/config/loader_test.go
+++ b/pkg/flowkit/config/loader_test.go
@@ -135,6 +135,8 @@ func Test_PreferLocalJson(t *testing.T) {
 	composer.AddConfigParser(json.NewParser())
 
 	conf, loadErr := composer.Load(config.DefaultPaths())
+	assert.NotNil(t, conf)
+	assert.NoError(t, err)
 
 	acc, err := conf.Accounts.ByName("emulator-account")
 	assert.NoError(t, err)
@@ -191,6 +193,49 @@ func Test_ComposeJSON(t *testing.T) {
 	assert.Equal(t, "0x3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", adminAccount.Key.PrivateKey.String())
 }
 
+func Test_ComposeCrossReference(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"test": {
+				"address":"f1d6e0586b0a20c7",
+				"key":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+			}
+		},
+		"deployments": {
+			"testnet": {
+				"test": ["NFT"]
+			}
+		}
+	}`)
+
+	b2 := []byte(`{
+		"networks": {
+			"testnet": "access.devnet.nodes.onflow.org:9000"
+		},
+		"contracts": { "NFT": "./NFT.cdc" }
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "flow.json", b, 0644)
+	err2 := afero.WriteFile(mockFS, "b.json", b2, 0644)
+
+	assert.NoError(t, err)
+	assert.NoError(t, err2)
+
+	composer := config.NewLoader(afero.Afero{Fs: mockFS})
+	composer.AddConfigParser(json.NewParser())
+
+	conf, loadErr := composer.Load([]string{"flow.json", "b.json"})
+
+	assert.NoError(t, loadErr)
+	account, err := conf.Accounts.ByName("test")
+	assert.NoError(t, err)
+	assert.NotNil(t, account)
+
+	deployments := conf.Deployments.ByAccountAndNetwork(account.Name, "testnet")
+	assert.NotNil(t, deployments)
+}
+
 func Test_ComposeJSONOverwrite(t *testing.T) {
 	b := []byte(`{
 		"accounts": {