@graphprotocol/graph-cli 0.30.2 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphprotocol/graph-cli",
3
- "version": "0.30.2",
3
+ "version": "0.31.0",
4
4
  "license": "(Apache-2.0 OR MIT)",
5
5
  "description": "CLI for building for and deploying to The Graph",
6
6
  "dependencies": {
@@ -8,7 +8,7 @@
8
8
  "binary-install-raw": "0.0.13",
9
9
  "chalk": "3.0.0",
10
10
  "chokidar": "3.5.1",
11
- "debug": "4.1.1",
11
+ "debug": "4.3.1",
12
12
  "docker-compose": "0.23.4",
13
13
  "dockerode": "2.5.8",
14
14
  "fs-extra": "9.0.0",
@@ -17,7 +17,7 @@
17
17
  "graphql": "15.5.0",
18
18
  "immutable": "3.8.2",
19
19
  "ipfs-http-client": "34.0.0",
20
- "jayson": "3.2.0",
20
+ "jayson": "3.6.6",
21
21
  "js-yaml": "3.13.1",
22
22
  "node-fetch": "2.6.0",
23
23
  "pkginfo": "0.4.1",
@@ -59,6 +59,9 @@ const getEtherscanLikeAPIUrl = (network) => {
59
59
  case "aurora": return `https://api.aurorascan.dev/api`
60
60
  case "aurora-testnet": return `https://api-testnet.aurorascan.dev/api`
61
61
  case "optimism-kovan": return `https://api-kovan-optimistic.etherscan.io/api`
62
+ case "optimism": return `https://api-optimistic.etherscan.io/api`
63
+ case "avalanche": return `https://api.snowtrace.io/api`;
64
+ case "fuji": return `https://api-testnet.snowtrace.io/api`;
62
65
  default: return `https://api-${network}.etherscan.io/api`
63
66
  }
64
67
  }
@@ -6,7 +6,7 @@ const Compiler = require('../compiler')
6
6
  // Helper function to construct a subgraph compiler
7
7
  const createCompiler = (
8
8
  manifest,
9
- { ipfs, outputDir, outputFormat, skipMigrations, blockIpfsMethods, protocol }
9
+ { ipfs, headers, outputDir, outputFormat, skipMigrations, blockIpfsMethods, protocol }
10
10
  ) => {
11
11
  // Parse the IPFS URL
12
12
  let url
@@ -25,6 +25,7 @@ The IPFS URL must be of the following format: http(s)://host[:port]/[path]`)
25
25
  host: url.hostname,
26
26
  port: url.port,
27
27
  'api-path': url.pathname.replace(/\/$/, '') + '/api/v0/',
28
+ headers,
28
29
  })
29
30
  : undefined
30
31
 
@@ -107,7 +107,19 @@ function hasChanges(identifierName, network, networkConfig, dataSource) {
107
107
  return networkChanged || addressChanged || startBlockChanged
108
108
  }
109
109
 
110
+ const updateNetworksFile = async (toolbox, network, dataSource, address, networksFile) => {
111
+ await toolbox.patching.update(networksFile, (config) => {
112
+ if(Object.keys(config).includes(network)) {
113
+ Object.assign(config[network], { [dataSource]: { address } })
114
+ } else {
115
+ Object.assign(config, { [network]: { [dataSource]: { address } }})
116
+ }
117
+ return config
118
+ })
119
+ }
120
+
110
121
  module.exports = {
111
122
  updateSubgraphNetwork,
112
- initNetworksConfig
123
+ initNetworksConfig,
124
+ updateNetworksFile
113
125
  }
@@ -79,12 +79,12 @@ const writeScaffold = async (scaffold, directory, spinner) => {
79
79
  await writeScaffoldDirectory(scaffold, directory, spinner)
80
80
  }
81
81
 
82
- const writeABI = async (abi, contractName, abiPath = `./abis/${contractName}.json`) => {
82
+ const writeABI = async (abi, contractName) => {
83
83
  let data = prettier.format(JSON.stringify(abi.data), {
84
84
  parser: 'json',
85
85
  })
86
86
 
87
- await fs.writeFile(abiPath, data, { encoding: 'utf-8' })
87
+ await fs.writeFile(`./abis/${contractName}.json`, data, { encoding: 'utf-8' })
88
88
  }
89
89
 
90
90
  const writeSchema = async (abi, protocol, schemaPath, entities) => {
@@ -9,6 +9,7 @@ const { generateDataSource, writeABI, writeSchema, writeMapping } = require('../
9
9
  const { loadAbiFromEtherscan, loadAbiFromBlockScout } = require('../command-helpers/abi')
10
10
  const EthereumABI = require('../protocols/ethereum/abi')
11
11
  const { fixParameters } = require('../command-helpers/gluegun')
12
+ const { updateNetworksFile } = require('../command-helpers/network')
12
13
 
13
14
  const HELP = `
14
15
  ${chalk.bold('graph add')} <address> [<subgraph-manifest default: "./subgraph.yaml">]
@@ -18,6 +19,7 @@ ${chalk.dim('Options:')}
18
19
  --abi <path> Path to the contract ABI (default: download from Etherscan)
19
20
  --contract-name Name of the contract (default: Contract)
20
21
  --merge-entities Whether to merge entities with the same name (default: false)
22
+ --network-file <path> Networks file (default: "./networks.json")
21
23
  -h, --help Show usage information
22
24
  `
23
25
 
@@ -33,20 +35,12 @@ module.exports = {
33
35
  contractName,
34
36
  h,
35
37
  help,
36
- mergeEntities
38
+ mergeEntities,
39
+ networkFile
37
40
  } = toolbox.parameters.options
38
41
 
39
- let address = toolbox.parameters.first
40
- let manifestPath = toolbox.parameters.second || './subgraph.yaml'
41
42
  contractName = contractName || 'Contract'
42
43
 
43
- // Validate the address
44
- if (!address) {
45
- print.error('No contract address provided')
46
- process.exitCode = 1
47
- return
48
- }
49
-
50
44
  try {
51
45
  fixParameters(toolbox.parameters, {
52
46
  h,
@@ -59,12 +53,22 @@ module.exports = {
59
53
  return
60
54
  }
61
55
 
56
+ let address = toolbox.parameters.first || toolbox.parameters.array[0]
57
+ let manifestPath = toolbox.parameters.second || toolbox.parameters.array[1] || './subgraph.yaml'
58
+
62
59
  // Show help text if requested
63
60
  if (help || h) {
64
61
  print.info(HELP)
65
62
  return
66
63
  }
67
64
 
65
+ // Validate the address
66
+ if (!address) {
67
+ print.error('No contract address provided')
68
+ process.exitCode = 1
69
+ return
70
+ }
71
+
68
72
  const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifestPath)
69
73
  let protocol = Protocol.fromDataSources(dataSourcesAndTemplates)
70
74
  let manifest = await Subgraph.load(manifestPath, { protocol })
@@ -95,7 +99,7 @@ module.exports = {
95
99
  let { collisionEntities, onlyCollisions, abiData } = updateEventNamesOnCollision(ethabi, entities, contractName, mergeEntities)
96
100
  ethabi.data = abiData
97
101
 
98
- await writeABI(ethabi, contractName, abi)
102
+ await writeABI(ethabi, contractName)
99
103
  await writeSchema(ethabi, protocol, result.getIn(['schema', 'file']), collisionEntities)
100
104
  await writeMapping(ethabi, protocol, contractName, collisionEntities)
101
105
 
@@ -120,6 +124,10 @@ module.exports = {
120
124
 
121
125
  await Subgraph.write(result, manifestPath)
122
126
 
127
+ // Update networks.json
128
+ const networksFile = networkFile || "./networks.json"
129
+ await updateNetworksFile(toolbox, network, contractName, address, networksFile)
130
+
123
131
  // Detect Yarn and/or NPM
124
132
  let yarn = await system.which('yarn')
125
133
  let npm = await system.which('npm')
@@ -22,18 +22,19 @@ ${chalk.bold('graph deploy')} [options] ${chalk.bold('<subgraph-name>')} ${chalk
22
22
 
23
23
  Options:
24
24
 
25
- --product <subgraph-studio|hosted-service>
25
+ --product <subgraph-studio|hosted-service>
26
26
  Selects the product to which to deploy
27
- --studio Shortcut for --product subgraph-studio
28
- -g, --node <node> Graph node to which to deploy
29
- --deploy-key <key> User deploy key
30
- -l --version-label <label> Version label used for the deployment
31
- -h, --help Show usage information
32
- -i, --ipfs <node> Upload build results to an IPFS node (default: ${DEFAULT_IPFS_URL})
33
- --debug-fork ID of a remote subgraph whose store will be GraphQL queried
34
- -o, --output-dir <path> Output directory for build results (default: build/)
35
- --skip-migrations Skip subgraph migrations (default: false)
36
- -w, --watch Regenerate types when subgraph files change (default: false)
27
+ --studio Shortcut for --product subgraph-studio
28
+ -g, --node <node> Graph node to which to deploy
29
+ --deploy-key <key> User deploy key
30
+ -l --version-label <label> Version label used for the deployment
31
+ -h, --help Show usage information
32
+ -i, --ipfs <node> Upload build results to an IPFS node (default: ${DEFAULT_IPFS_URL})
33
+ -hdr, --headers <map> Add custom headers that will be used by the IPFS HTTP client (default: {})
34
+ --debug-fork ID of a remote subgraph whose store will be GraphQL queried
35
+ -o, --output-dir <path> Output directory for build results (default: build/)
36
+ --skip-migrations Skip subgraph migrations (default: false)
37
+ -w, --watch Regenerate types when subgraph files change (default: false)
37
38
  `
38
39
 
39
40
  const processForm = async (
@@ -91,6 +92,8 @@ module.exports = {
91
92
  i,
92
93
  help,
93
94
  ipfs,
95
+ headers,
96
+ hdr,
94
97
  node,
95
98
  o,
96
99
  outputDir,
@@ -103,11 +106,20 @@ module.exports = {
103
106
  // Support both long and short option variants
104
107
  help = help || h
105
108
  ipfs = ipfs || i || DEFAULT_IPFS_URL
109
+ headers = headers || hdr || "{}"
106
110
  node = node || g
107
111
  outputDir = outputDir || o
108
112
  watch = watch || w
109
113
  versionLabel = versionLabel || l
110
114
 
115
+ try {
116
+ headers = JSON.parse(headers)
117
+ } catch (e) {
118
+ print.error("Please make sure headers is a valid JSON value")
119
+ process.exitCode = 1
120
+ return
121
+ }
122
+
111
123
  let subgraphName, manifest
112
124
  try {
113
125
  ;[subgraphName, manifest] = fixParameters(toolbox.parameters, {
@@ -216,6 +228,7 @@ module.exports = {
216
228
 
217
229
  let compiler = createCompiler(manifest, {
218
230
  ipfs,
231
+ headers,
219
232
  outputDir,
220
233
  outputFormat: 'wasm',
221
234
  skipMigrations,
@@ -1,10 +1,9 @@
1
1
  const chalk = require('chalk')
2
- const fetch = require('node-fetch')
3
- const immutable = require('immutable')
4
2
  const os = require('os')
5
3
  const path = require('path')
6
4
  const toolbox = require('gluegun/toolbox')
7
- const yaml = require('yaml')
5
+ const fs = require('fs')
6
+ const graphCli = require('../cli')
8
7
 
9
8
  const {
10
9
  getSubgraphBasename,
@@ -423,9 +422,9 @@ module.exports = {
423
422
  contractName,
424
423
  node,
425
424
  studio,
426
- product,
425
+ product
427
426
  },
428
- { commands },
427
+ { commands, addContract: false },
429
428
  )
430
429
  }
431
430
 
@@ -484,9 +483,9 @@ module.exports = {
484
483
  contractName: inputs.contractName,
485
484
  node,
486
485
  studio: inputs.studio,
487
- product: inputs.product,
486
+ product: inputs.product
488
487
  },
489
- { commands },
488
+ { commands, addContract: true },
490
489
  )
491
490
  }
492
491
  },
@@ -720,9 +719,9 @@ const initSubgraphFromContract = async (
720
719
  contractName,
721
720
  node,
722
721
  studio,
723
- product,
722
+ product
724
723
  },
725
- { commands },
724
+ { commands, addContract },
726
725
  ) => {
727
726
  let { print } = toolbox
728
727
 
@@ -815,5 +814,85 @@ const initSubgraphFromContract = async (
815
814
  return
816
815
  }
817
816
 
817
+ while (addContract) {
818
+ addContract = await addAnotherContract(toolbox, { protocolInstance, directory })
819
+ }
820
+
818
821
  printNextSteps(toolbox, { subgraphName, directory }, { commands })
819
822
  }
823
+
824
+ const addAnotherContract = async (toolbox, { protocolInstance, directory }) => {
825
+ const addContractConfirmation = await toolbox.prompt.confirm('Add another contract?')
826
+
827
+ if (addContractConfirmation) {
828
+ let abiFromFile
829
+ let ProtocolContract = protocolInstance.getContract()
830
+
831
+ let questions = [
832
+ {
833
+ type: 'input',
834
+ name: 'contract',
835
+ message: () => `Contract ${ProtocolContract.identifierName()}`,
836
+ validate: async (value) => {
837
+ // Validate whether the contract is valid
838
+ const { valid, error } = validateContract(value, ProtocolContract)
839
+ return valid ? true : error
840
+ },
841
+ },
842
+ {
843
+ type: 'select',
844
+ name: 'localAbi',
845
+ message: 'Provide local ABI path?',
846
+ choices: ['yes', 'no'],
847
+ result: (value) => {
848
+ abiFromFile = value === 'yes' ? true : false
849
+ return abiFromFile
850
+ },
851
+ },
852
+ {
853
+ type: 'input',
854
+ name: 'abi',
855
+ message: 'ABI file (path)',
856
+ skip: () => abiFromFile === false
857
+ },
858
+ {
859
+ type: 'input',
860
+ name: 'contractName',
861
+ message: 'Contract Name',
862
+ initial: 'Contract',
863
+ validate: (value) => value && value.length > 0,
864
+ },
865
+ ]
866
+
867
+ // Get the cwd before process.chdir in order to switch back in the end of command execution
868
+ const cwd = process.cwd();
869
+
870
+ try {
871
+ let { abi, contract, contractName } = await toolbox.prompt.ask(questions)
872
+
873
+ if (fs.existsSync(directory)) {
874
+ process.chdir(directory)
875
+ }
876
+
877
+ let commandLine = ['add', contract, '--contract-name', contractName]
878
+
879
+ if (abiFromFile) {
880
+ if (abi.includes(directory)) {
881
+ commandLine.push('--abi', path.normalize(abi.replace(directory, '')))
882
+ } else {
883
+ commandLine.push('--abi', abi)
884
+ }
885
+ }
886
+
887
+ await graphCli.run(commandLine)
888
+ } catch (e) {
889
+ toolbox.print.error(e)
890
+ process.exit(1)
891
+ }
892
+ finally {
893
+ process.chdir(cwd)
894
+ }
895
+ }
896
+
897
+ return addContractConfirmation
898
+ }
@@ -13,12 +13,12 @@ const HELP = `
13
13
  ${chalk.bold('graph test')} ${chalk.dim('[options]')} ${chalk.bold('<datasource>')}
14
14
 
15
15
  ${chalk.dim('Options:')}
16
- -c, --coverage Run the tests in coverage mode. Works with v0.2.1 and above
16
+ -c, --coverage Run the tests in coverage mode
17
17
  -d, --docker Run the tests in a docker container(Note: Please execute from the root folder of the subgraph)
18
18
  -f --force Binary - overwrites folder + file when downloading. Docker - rebuilds the docker image
19
19
  -h, --help Show usage information
20
20
  -l, --logs Logs to the console information about the OS, CPU model and download url (debugging purposes)
21
- -r, --recompile Force-recompile tests (Not available in 0.2.2 and earlier versions)
21
+ -r, --recompile Force-recompile tests
22
22
  -v, --version <tag> Choose the version of the rust binary that you want to be downloaded/used
23
23
  `
24
24
 
@@ -43,16 +43,16 @@ module.exports = {
43
43
  version,
44
44
  } = toolbox.parameters.options
45
45
 
46
- let opts = new Map()
46
+ let opts = {}
47
47
  // Support both long and short option variants
48
- opts.set("coverage", coverage || c)
49
- opts.set("docker", docker || d)
50
- opts.set("force", force || f)
51
- opts.set("help", help || h)
52
- opts.set("logs", logs || l)
53
- opts.set("recompile", recompile || r)
54
- opts.set("version", version || v)
55
-
48
+ opts.coverage = coverage || c
49
+ opts.docker = docker || d
50
+ opts.force = force || f
51
+ opts.help = help || h
52
+ opts.logs = logs || l
53
+ opts.recompile = recompile || r
54
+ opts.version = version || v
55
+ opts.testsFolder = './tests'
56
56
  // Fix if a boolean flag (e.g -c, --coverage) has an argument
57
57
  try {
58
58
  fixParameters(toolbox.parameters, {
@@ -76,18 +76,49 @@ module.exports = {
76
76
  }
77
77
 
78
78
  let datasource = toolbox.parameters.first || toolbox.parameters.array[0]
79
-
80
79
  // Show help text if requested
81
- if (opts.get("help")) {
80
+ if (opts.help) {
82
81
  print.info(HELP)
83
82
  return
84
83
  }
85
84
 
86
- let result = await fetch('https://api.github.com/repos/LimeChain/matchstick/releases/latest')
87
- let json = await result.json()
88
- opts.set("latestVersion", json.tag_name)
85
+ // Check if matchstick.yaml config exists
86
+ if(filesystem.exists('matchstick.yaml')) {
87
+ try {
88
+ // Load the config
89
+ let config = await yaml.load(filesystem.read('matchstick.yaml', 'utf8'))
90
+
91
+ // Check if matchstick.yaml and testsFolder not null
92
+ if(config && config.testsFolder) {
93
+ // assign test folder from matchstick.yaml if present
94
+ opts.testsFolder = config.testsFolder
95
+ }
96
+ } catch (error) {
97
+ print.info('A problem occurred while reading matchstick.yaml. Please attend to the errors below:')
98
+ print.error(error.message)
99
+ process.exit(1)
100
+ }
101
+ }
102
+
103
+ opts.cachePath = path.join(opts.testsFolder, '.latest.json')
104
+ setVersionFromCache(opts)
105
+
106
+ // Fetch the latest version tag if version is not specified with -v/--version or if the version is not cached
107
+ if (opts.force || (!opts.version && !opts.latestVersion)) {
108
+ print.info("Fetching latest version tag")
109
+ let result = await fetch('https://api.github.com/repos/LimeChain/matchstick/releases/latest')
110
+ let json = await result.json()
111
+ opts.latestVersion = json.tag_name
112
+
113
+ filesystem.file(opts.cachePath, {
114
+ content: {
115
+ version: json.tag_name,
116
+ timestamp: Date.now()
117
+ }
118
+ })
119
+ }
89
120
 
90
- if(opts.get("docker")) {
121
+ if(opts.docker) {
91
122
  runDocker(datasource, opts)
92
123
  } else {
93
124
  runBinary(datasource, opts)
@@ -95,13 +126,25 @@ module.exports = {
95
126
  }
96
127
  }
97
128
 
129
+ async function setVersionFromCache(opts) {
130
+ if(filesystem.exists(opts.cachePath) == 'file') {
131
+ let cached = filesystem.read(opts.cachePath, 'json')
132
+ // Get the cache age in days
133
+ let cacheAge = (Date.now() - cached.timestamp) / (1000 * 60 * 60 * 24)
134
+ // If cache age is less than 1 day, use the cached version
135
+ if (cacheAge < 1) {
136
+ opts.latestVersion = cached.version
137
+ }
138
+ }
139
+ }
140
+
98
141
  async function runBinary(datasource, opts) {
99
- let coverageOpt = opts.get("coverage")
100
- let forceOpt = opts.get("force")
101
- let logsOpt = opts.get("logs")
102
- let versionOpt = opts.get("version")
103
- let latestVersion = opts.get("latestVersion")
104
- let recompileOpt = opts.get("recompile")
142
+ let coverageOpt = opts.coverage
143
+ let forceOpt = opts.force
144
+ let logsOpt = opts.logs
145
+ let versionOpt = opts.version
146
+ let latestVersion = opts.latestVersion
147
+ let recompileOpt = opts.recompile
105
148
 
106
149
  const platform = await getPlatform(logsOpt)
107
150
 
@@ -126,10 +169,10 @@ async function getPlatform(logsOpt) {
126
169
  const arch = os.arch()
127
170
  const cpuCore = os.cpus()[0]
128
171
  const isM1 = (arch === 'arm64' && /Apple (M1|processor)/.test(cpuCore.model))
129
- const linuxInfo = type === 'Linux' ? await getLinuxInfo() : new Map()
130
- const linuxDistro = linuxInfo.get('name')
131
- const release = linuxInfo.get('version') || os.release()
132
- const majorVersion = parseInt(linuxInfo.get('version'), 10) || semver.major(release)
172
+ const linuxInfo = type === 'Linux' ? await getLinuxInfo() : {}
173
+ const linuxDistro = linuxInfo.name
174
+ const release = linuxInfo.version || os.release()
175
+ const majorVersion = parseInt(linuxInfo.version, 10) || semver.major(release)
133
176
 
134
177
  if (logsOpt) {
135
178
  print.info(`OS type: ${linuxDistro || type}\nOS arch: ${arch}\nOS release: ${release}\nOS major version: ${majorVersion}\nCPU model: ${cpuCore.model}`)
@@ -148,6 +191,8 @@ async function getPlatform(logsOpt) {
148
191
  } else if (type === 'Linux') {
149
192
  if (majorVersion === 18) {
150
193
  return 'binary-linux-18'
194
+ } if (majorVersion === 22) {
195
+ return 'binary-linux-22'
151
196
  } else {
152
197
  return 'binary-linux-20'
153
198
  }
@@ -163,13 +208,13 @@ async function getLinuxInfo() {
163
208
  try {
164
209
  let result = await system.run("cat /etc/*-release | grep -E '(^VERSION|^NAME)='", {trim: true})
165
210
  let infoArray = result.replace(/['"]+/g, '').split('\n').map(p => p.split('='))
166
- let infoMap = new Map();
211
+ let linuxInfo = {}
167
212
 
168
213
  infoArray.forEach((val) => {
169
- infoMap.set(val[0].toLowerCase(), val[1])
214
+ linuxInfo[val[0].toLowerCase()] = val[1]
170
215
  });
171
216
 
172
- return infoMap
217
+ return linuxInfo
173
218
  } catch (error) {
174
219
  print.error(`Error fetching the Linux version:\n ${error}`)
175
220
  process.exit(1)
@@ -177,39 +222,21 @@ async function getLinuxInfo() {
177
222
  }
178
223
 
179
224
  async function runDocker(datasource, opts) {
180
- let coverageOpt = opts.get("coverage")
181
- let forceOpt = opts.get("force")
182
- let versionOpt = opts.get("version")
183
- let latestVersion = opts.get("latestVersion")
184
- let recompileOpt = opts.get("recompile")
225
+ let coverageOpt = opts.coverage
226
+ let forceOpt = opts.force
227
+ let versionOpt = opts.version
228
+ let latestVersion = opts.latestVersion
229
+ let recompileOpt = opts.recompile
185
230
 
186
231
  // Remove binary-install-raw binaries, because docker has permission issues
187
232
  // when building the docker images
188
- await filesystem.remove("./node_modules/binary-install-raw/bin")
233
+ await filesystem.remove('./node_modules/binary-install-raw/bin')
189
234
 
190
235
  // Get current working directory
191
236
  let current_folder = await filesystem.cwd()
192
237
 
193
238
  // Declate dockerfilePath with default location
194
- let dockerfilePath = "./tests/.docker/Dockerfile"
195
-
196
- // Check if matchstick.yaml config exists
197
- if(filesystem.exists('matchstick.yaml')) {
198
- try {
199
- // Load the config
200
- let config = await yaml.load(filesystem.read('matchstick.yaml', 'utf8'))
201
-
202
- // Check if matchstick.yaml is not empty
203
- if(config != null) {
204
- // If a custom tests folder is declared update dockerfilePath
205
- dockerfilePath = path.join(config.testsFolder || 'tests', '.docker/Dockerfile')
206
- }
207
- } catch (error) {
208
- print.info('A problem occurred while reading matchstick.yaml. Please attend to the errors below:')
209
- print.error(error.message)
210
- process.exit(1)
211
- }
212
- }
239
+ let dockerfilePath = path.join(opts.testsFolder || 'tests', '.docker/Dockerfile')
213
240
 
214
241
  // Check if the Dockerfil already exists
215
242
  let dockerfileExists = filesystem.exists(dockerfilePath)
@@ -52,6 +52,7 @@ module.exports = class Protocol {
52
52
  'matic',
53
53
  'mumbai',
54
54
  'fantom',
55
+ 'fantom-testnet',
55
56
  'bsc',
56
57
  'chapel',
57
58
  'clover',
@@ -60,6 +61,8 @@ module.exports = class Protocol {
60
61
  'celo',
61
62
  'celo-alfajores',
62
63
  'fuse',
64
+ 'moonbeam',
65
+ 'moonriver',
63
66
  'mbase',
64
67
  'arbitrum-one',
65
68
  'arbitrum-rinkeby',