Skip to content

Commit b6d7f1c

Browse files
incrypto32github-actions[bot]YaroShkvorets
authoredFeb 13, 2025··
graph init: add subgraph composition (#1920)
* Fix subgraphs without abi field failing to build * Fix graph init for composed subgraphs * Add changeset * Fix validation not working * Support declared calls in manifest * Lint fix * Address review comments * Dont allow adding new contracts when subgraph is a composed subgraph * Allow init of subgraph datasource subgraphs without the interactive mode * Reduce code duplication between subgraph datasource and normal data source * prevent using --from-contract and --from-source-subgraph flags together * cli: validate protocol and source subgraph relationship * chore(dependencies): updated changesets for modified dependencies * change flag name for source subgraph * Refactor manifest validation util functions * get start block from source manifest * set fromSubgraph to be default value for graph init in interactive mode * fix protocol flag validation * Add init test for subgraphs * Fix error message * chore(dependencies): updated changesets for modified dependencies --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: YaroShkvorets <shkvorets@gmail.com>
1 parent 7f22631 commit b6d7f1c

File tree

10 files changed

+330
-68
lines changed

10 files changed

+330
-68
lines changed
 

‎.changeset/curly-buses-hang.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
Add support for subgraph datasource in `graph init`

‎packages/cli/src/commands/codegen.ts

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export default class CodegenCommand extends Command {
4343
summary: 'IPFS node to use for fetching subgraph data.',
4444
char: 'i',
4545
default: DEFAULT_IPFS_URL,
46-
hidden: true,
4746
}),
4847
'uncrashable-config': Flags.file({
4948
summary: 'Directory for uncrashable config.',

‎packages/cli/src/commands/init.ts

+110-35
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ import EthereumABI from '../protocols/ethereum/abi.js';
2323
import Protocol, { ProtocolName } from '../protocols/index.js';
2424
import { abiEvents } from '../scaffold/schema.js';
2525
import Schema from '../schema.js';
26-
import { createIpfsClient, loadSubgraphSchemaFromIPFS } from '../utils.js';
26+
import {
27+
createIpfsClient,
28+
getMinStartBlock,
29+
loadManifestYaml,
30+
loadSubgraphSchemaFromIPFS,
31+
validateSubgraphNetworkMatch,
32+
} from '../utils.js';
2733
import { validateContract } from '../validation/index.js';
2834
import AddCommand from './add.js';
2935

@@ -54,6 +60,10 @@ export default class InitCommand extends Command {
5460
summary: 'Graph node for which to initialize.',
5561
char: 'g',
5662
}),
63+
'from-subgraph': Flags.string({
64+
description: 'Creates a scaffold based on an existing subgraph.',
65+
exclusive: ['from-example', 'from-contract'],
66+
}),
5767
'from-contract': Flags.string({
5868
description: 'Creates a scaffold based on an existing contract.',
5969
exclusive: ['from-example'],
@@ -88,7 +98,6 @@ export default class InitCommand extends Command {
8898
description: 'Block number to start indexing from.',
8999
// TODO: using a default sets the value and therefore requires --from-contract
90100
// default: '0',
91-
dependsOn: ['from-contract'],
92101
}),
93102

94103
abi: Flags.string({
@@ -110,7 +119,6 @@ export default class InitCommand extends Command {
110119
summary: 'IPFS node to use for fetching subgraph data.',
111120
char: 'i',
112121
default: DEFAULT_IPFS_URL,
113-
hidden: true,
114122
}),
115123
};
116124

@@ -127,6 +135,7 @@ export default class InitCommand extends Command {
127135
protocol,
128136
node: nodeFlag,
129137
'from-contract': fromContract,
138+
'from-subgraph': fromSubgraph,
130139
'contract-name': contractName,
131140
'from-example': fromExample,
132141
'index-events': indexEvents,
@@ -141,11 +150,20 @@ export default class InitCommand extends Command {
141150

142151
initDebugger('Flags: %O', flags);
143152

153+
if (startBlock && !(fromContract || fromSubgraph)) {
154+
this.error('--start-block can only be used with --from-contract or --from-subgraph');
155+
}
156+
157+
if (fromContract && fromSubgraph) {
158+
this.error('Cannot use both --from-contract and --from-subgraph at the same time');
159+
}
160+
144161
if (skipGit) {
145162
this.warn(
146163
'The --skip-git flag will be removed in the next major version. By default we will stop initializing a Git repository.',
147164
);
148165
}
166+
149167
if ((fromContract || spkgPath) && !network && !fromExample) {
150168
this.error('--network is required when using --from-contract or --spkg');
151169
}
@@ -199,16 +217,15 @@ export default class InitCommand extends Command {
199217
let abi!: EthereumABI;
200218

201219
// If all parameters are provided from the command-line,
202-
// go straight to creating the subgraph from an existing contract
203-
if ((fromContract || spkgPath) && protocol && subgraphName && directory && network && node) {
204-
const registry = await loadRegistry();
205-
const contractService = new ContractService(registry);
206-
const sourcifyContractInfo = await contractService.getFromSourcify(
207-
EthereumABI,
208-
network,
209-
fromContract!,
210-
);
211-
220+
// go straight to creating the subgraph from an existing contract or source subgraph
221+
if (
222+
(fromContract || spkgPath || fromSubgraph) &&
223+
protocol &&
224+
subgraphName &&
225+
directory &&
226+
network &&
227+
node
228+
) {
212229
if (!protocolChoices.includes(protocol as ProtocolName)) {
213230
this.error(
214231
`Protocol '${protocol}' is not supported, choose from these options: ${protocolChoices.join(
@@ -220,7 +237,31 @@ export default class InitCommand extends Command {
220237

221238
const protocolInstance = new Protocol(protocol as ProtocolName);
222239

223-
if (protocolInstance.hasABIs()) {
240+
if (fromSubgraph && !protocolInstance.isComposedSubgraph()) {
241+
this.error('--protocol can only be subgraph when using --from-subgraph');
242+
}
243+
244+
if (
245+
fromContract &&
246+
(protocolInstance.isComposedSubgraph() || protocolInstance.isSubstreams())
247+
) {
248+
this.error('--protocol cannot be subgraph or substreams when using --from-contract');
249+
}
250+
251+
if (spkgPath && !protocolInstance.isSubstreams()) {
252+
this.error('--protocol can only be substreams when using --spkg');
253+
}
254+
255+
// Only fetch contract info and ABI for non-source-subgraph cases
256+
if (!fromSubgraph && protocolInstance.hasABIs()) {
257+
const registry = await loadRegistry();
258+
const contractService = new ContractService(registry);
259+
const sourcifyContractInfo = await contractService.getFromSourcify(
260+
EthereumABI,
261+
network,
262+
fromContract!,
263+
);
264+
224265
const ABI = protocolInstance.getABI();
225266
if (abiPath) {
226267
try {
@@ -244,7 +285,7 @@ export default class InitCommand extends Command {
244285
protocolInstance,
245286
abi,
246287
directory,
247-
source: fromContract!,
288+
source: fromSubgraph || fromContract!,
248289
indexEvents,
249290
network,
250291
subgraphName,
@@ -288,7 +329,7 @@ export default class InitCommand extends Command {
288329
abi,
289330
abiPath,
290331
directory,
291-
source: fromContract,
332+
source: fromContract || fromSubgraph,
292333
indexEvents,
293334
fromExample,
294335
subgraphName,
@@ -534,7 +575,7 @@ async function processInitForm(
534575
value: 'contract',
535576
},
536577
{ message: 'Substreams', name: 'substreams', value: 'substreams' },
537-
// { message: 'Subgraph', name: 'subgraph', value: 'subgraph' },
578+
{ message: 'Subgraph', name: 'subgraph', value: 'subgraph' },
538579
].filter(({ name }) => name),
539580
});
540581

@@ -604,6 +645,30 @@ async function processInitForm(
604645
},
605646
});
606647

648+
promptManager.addStep({
649+
type: 'input',
650+
name: 'ipfs',
651+
message: `IPFS node to use for fetching subgraph manifest`,
652+
initial: ipfsUrl,
653+
skip: () => !isComposedSubgraph,
654+
validate: value => {
655+
if (!value) {
656+
return 'IPFS node URL cannot be empty';
657+
}
658+
try {
659+
new URL(value);
660+
return true;
661+
} catch {
662+
return 'Please enter a valid URL';
663+
}
664+
},
665+
result: value => {
666+
ipfsNode = value;
667+
initDebugger.extend('processInitForm')('ipfs: %O', value);
668+
return value;
669+
},
670+
});
671+
607672
promptManager.addStep({
608673
type: 'input',
609674
name: 'source',
@@ -616,9 +681,16 @@ async function processInitForm(
616681
isSubstreams ||
617682
(!protocolInstance.hasContract() && !isComposedSubgraph),
618683
initial: initContract,
619-
validate: async (value: string) => {
684+
validate: async (value: string): Promise<string | boolean> => {
620685
if (isComposedSubgraph) {
621-
return value.startsWith('Qm') ? true : 'Subgraph deployment ID must start with Qm';
686+
const ipfs = createIpfsClient(ipfsNode);
687+
const manifestYaml = await loadManifestYaml(ipfs, value);
688+
const { valid, error } = validateSubgraphNetworkMatch(manifestYaml, network.id);
689+
if (!valid) {
690+
return error || 'Invalid subgraph network match';
691+
}
692+
startBlock ||= getMinStartBlock(manifestYaml)?.toString();
693+
return true;
622694
}
623695
if (initFromExample !== undefined || !protocolInstance.hasContract()) {
624696
return true;
@@ -668,6 +740,7 @@ async function processInitForm(
668740
} else {
669741
abiFromApi = initAbi;
670742
}
743+
671744
// If startBlock is not provided, try to fetch it from Etherscan API
672745
if (!initStartBlock) {
673746
startBlock = await retryWithPrompt(() =>
@@ -699,19 +772,6 @@ async function processInitForm(
699772
},
700773
});
701774

702-
promptManager.addStep({
703-
type: 'input',
704-
name: 'ipfs',
705-
message: `IPFS node to use for fetching subgraph manifest`,
706-
initial: ipfsUrl,
707-
skip: () => !isComposedSubgraph,
708-
result: value => {
709-
ipfsNode = value;
710-
initDebugger.extend('processInitForm')('ipfs: %O', value);
711-
return value;
712-
},
713-
});
714-
715775
promptManager.addStep({
716776
type: 'input',
717777
name: 'spkg',
@@ -751,7 +811,7 @@ async function processInitForm(
751811
isSubstreams ||
752812
!!initAbiPath ||
753813
isComposedSubgraph,
754-
validate: async (value: string) => {
814+
validate: async (value: string): Promise<string | boolean> => {
755815
if (
756816
initFromExample ||
757817
abiFromApi ||
@@ -1199,6 +1259,14 @@ async function initSubgraphFromContract(
11991259
},
12001260
});
12011261

1262+
// Validate network match first
1263+
const manifestYaml = await loadManifestYaml(ipfsClient, source);
1264+
const { valid, error } = validateSubgraphNetworkMatch(manifestYaml, network);
1265+
if (!valid) {
1266+
throw new Error(error || 'Invalid subgraph network match');
1267+
}
1268+
1269+
startBlock ||= getMinStartBlock(manifestYaml)?.toString();
12021270
const schemaString = await loadSubgraphSchemaFromIPFS(ipfsClient, source);
12031271
const schema = await Schema.loadFromString(schemaString);
12041272
entities = schema.getEntityNames();
@@ -1208,8 +1276,9 @@ async function initSubgraphFromContract(
12081276
}
12091277

12101278
if (
1211-
!protocolInstance.isComposedSubgraph() &&
1279+
!isComposedSubgraph &&
12121280
protocolInstance.hasABIs() &&
1281+
abi && // Add check for abi existence
12131282
(abiEvents(abi).size === 0 ||
12141283
// @ts-expect-error TODO: the abiEvents result is expected to be a List, how's it an array?
12151284
abiEvents(abi).length === 0)
@@ -1224,6 +1293,12 @@ async function initSubgraphFromContract(
12241293
`Failed to create subgraph scaffold`,
12251294
`Warnings while creating subgraph scaffold`,
12261295
async spinner => {
1296+
initDebugger('Generating scaffold with ABI:', abi);
1297+
initDebugger('ABI data:', abi?.data);
1298+
if (abi) {
1299+
initDebugger('ABI events:', abiEvents(abi));
1300+
}
1301+
12271302
const scaffold = await generateScaffold(
12281303
{
12291304
protocolInstance,
@@ -1280,7 +1355,7 @@ async function initSubgraphFromContract(
12801355
this.exit(1);
12811356
}
12821357

1283-
while (addContract) {
1358+
while (addContract && !isComposedSubgraph) {
12841359
addContract = await addAnotherContract
12851360
.bind(this)({
12861361
protocolInstance,

‎packages/cli/src/compiler/index.ts

+39-19
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,9 @@ export default class Compiler {
508508
`Failed to write compiled subgraph to ${displayDir}`,
509509
`Warnings while writing compiled subgraph to ${displayDir}`,
510510
async spinner => {
511+
// Add debug log for initial subgraph state
512+
compilerDebug('Initial subgraph state:', subgraph.toJS());
513+
511514
// Copy schema and update its path
512515
subgraph = subgraph.updateIn(['schema', 'file'], schemaFile => {
513516
const schemaFilePath = path.resolve(this.sourceDir, schemaFile as string);
@@ -518,32 +521,49 @@ export default class Compiler {
518521
return path.relative(this.options.outputDir, targetFile);
519522
});
520523

524+
// Add debug log before processing data sources
525+
compilerDebug('Processing dataSources:', subgraph.get('dataSources').toJS());
526+
521527
// Copy data source files and update their paths
522528
subgraph = subgraph.update('dataSources', (dataSources: any[]) =>
523529
dataSources.map(dataSource => {
530+
// Add debug log for each data source
531+
compilerDebug('Processing dataSource:', dataSource.toJS());
532+
524533
let updatedDataSource = dataSource;
525534

526535
if (this.protocol.hasABIs()) {
527-
updatedDataSource = updatedDataSource
528-
// Write data source ABIs to the output directory
529-
.updateIn(['mapping', 'abis'], (abis: any[]) =>
530-
abis.map((abi: any) =>
531-
abi.update('file', (abiFile: string) => {
532-
abiFile = path.resolve(this.sourceDir, abiFile);
533-
const abiData = this.ABI.load(abi.get('name'), abiFile);
534-
return path.relative(
535-
this.options.outputDir,
536-
this._writeSubgraphFile(
537-
abiFile,
538-
JSON.stringify(abiData.data.toJS(), null, 2),
539-
this.sourceDir,
540-
this.subgraphDir(this.options.outputDir, dataSource),
541-
spinner,
542-
),
543-
);
544-
}),
545-
),
536+
// Add debug log for ABIs
537+
compilerDebug(
538+
'Processing ABIs for dataSource:',
539+
dataSource.getIn(['mapping', 'abis'])?.toJS() || 'undefined',
540+
);
541+
542+
updatedDataSource = updatedDataSource.updateIn(['mapping', 'abis'], (abis: any[]) => {
543+
compilerDebug('ABIs value:', Array.isArray(abis) ? abis : 'undefined');
544+
545+
if (!abis) {
546+
compilerDebug('No ABIs found for dataSource');
547+
return immutable.List();
548+
}
549+
550+
return abis.map((abi: any) =>
551+
abi.update('file', (abiFile: string) => {
552+
abiFile = path.resolve(this.sourceDir, abiFile);
553+
const abiData = this.ABI.load(abi.get('name'), abiFile);
554+
return path.relative(
555+
this.options.outputDir,
556+
this._writeSubgraphFile(
557+
abiFile,
558+
JSON.stringify(abiData.data.toJS(), null, 2),
559+
this.sourceDir,
560+
this.subgraphDir(this.options.outputDir, dataSource),
561+
spinner,
562+
),
563+
);
564+
}),
546565
);
566+
});
547567
}
548568

549569
if (protocol.name == 'substreams' || protocol.name == 'substreams/triggers') {

‎packages/cli/src/protocols/subgraph/manifest.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type ContractABI {
6363
type EntityHandler {
6464
handler: String!
6565
entity: String!
66+
calls: JSON
6667
}
6768

6869
type Graft {

‎packages/cli/src/scaffold/index.ts

+47-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import debugFactory from 'debug';
12
import fs from 'fs-extra';
23
import { strings } from 'gluegun';
34
import prettier from 'prettier';
@@ -11,6 +12,8 @@ import { generateEventIndexingHandlers } from './mapping.js';
1112
import { abiEvents, generateEventType, generateExampleEntityType } from './schema.js';
1213
import { generateTestsFiles } from './tests.js';
1314

15+
const scaffoldDebugger = debugFactory('graph-cli:scaffold');
16+
1417
const GRAPH_CLI_VERSION = process.env.GRAPH_CLI_TESTS
1518
? // JSON.stringify should remove this key, we will install the local
1619
// graph-cli for the tests using `npm link` instead of fetching from npm.
@@ -47,18 +50,34 @@ export default class Scaffold {
4750
spkgPath?: string;
4851
entities?: string[];
4952

50-
constructor(options: ScaffoldOptions) {
51-
this.protocol = options.protocol;
52-
this.abi = options.abi;
53-
this.indexEvents = options.indexEvents;
54-
this.contract = options.contract;
55-
this.network = options.network;
56-
this.contractName = options.contractName;
57-
this.subgraphName = options.subgraphName;
58-
this.startBlock = options.startBlock;
59-
this.node = options.node;
60-
this.spkgPath = options.spkgPath;
61-
this.entities = options.entities;
53+
constructor({
54+
protocol,
55+
abi,
56+
contract,
57+
network,
58+
contractName,
59+
startBlock,
60+
subgraphName,
61+
node,
62+
spkgPath,
63+
indexEvents,
64+
entities,
65+
}: ScaffoldOptions) {
66+
this.protocol = protocol;
67+
this.abi = abi;
68+
this.contract = contract;
69+
this.network = network;
70+
this.contractName = contractName;
71+
this.startBlock = startBlock;
72+
this.subgraphName = subgraphName;
73+
this.node = node;
74+
this.spkgPath = spkgPath;
75+
this.indexEvents = indexEvents;
76+
this.entities = entities;
77+
78+
scaffoldDebugger('Scaffold constructor called with ABI:', abi);
79+
scaffoldDebugger('ABI data:', abi?.data);
80+
scaffoldDebugger('ABI file:', abi?.file);
6281
}
6382

6483
async generatePackageJson() {
@@ -203,9 +222,24 @@ dataSources:
203222
}
204223

205224
async generateABIs() {
225+
scaffoldDebugger('Generating ABIs...');
226+
scaffoldDebugger('Protocol has ABIs:', this.protocol.hasABIs());
227+
scaffoldDebugger('ABI data:', this.abi?.data);
228+
scaffoldDebugger('ABI file:', this.abi?.file);
229+
230+
if (!this.protocol.hasABIs()) {
231+
scaffoldDebugger('Protocol does not have ABIs, skipping ABI generation');
232+
return;
233+
}
234+
235+
if (!this.abi?.data) {
236+
scaffoldDebugger('ABI data is undefined, skipping ABI generation');
237+
return;
238+
}
239+
206240
return this.protocol.hasABIs()
207241
? {
208-
[`${this.contractName}.json`]: await prettier.format(JSON.stringify(this.abi?.data), {
242+
[`${this.contractName}.json`]: await prettier.format(JSON.stringify(this.abi.data), {
209243
parser: 'json',
210244
}),
211245
}

‎packages/cli/src/utils.ts

+67
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,70 @@ export async function loadSubgraphSchemaFromIPFS(ipfsClient: any, manifest: stri
3232
throw Error(`Failed to load schema from IPFS ${manifest}`);
3333
}
3434
}
35+
36+
export async function loadManifestFromIPFS(ipfsClient: any, manifest: string) {
37+
try {
38+
const manifestBuffer = ipfsClient.cat(manifest);
39+
let manifestFile = '';
40+
for await (const chunk of manifestBuffer) {
41+
manifestFile += Buffer.from(chunk).toString('utf8');
42+
}
43+
return manifestFile;
44+
} catch (e) {
45+
utilsDebug.extend('loadManifestFromIPFS')(`Failed to load manifest from IPFS ${manifest}`);
46+
utilsDebug.extend('loadManifestFromIPFS')(e);
47+
throw Error(`Failed to load manifest from IPFS ${manifest}`);
48+
}
49+
}
50+
51+
export async function loadManifestYaml(ipfsClient: any, manifest: string): Promise<any> {
52+
const manifestFile = await loadManifestFromIPFS(ipfsClient, manifest);
53+
return yaml.load(manifestFile) as any;
54+
}
55+
56+
/**
57+
* Validates that the network of a source subgraph matches the target network
58+
* @param manifestYaml Parsed manifest YAML
59+
* @param targetNetwork Network of the target subgraph being created
60+
* @returns Object containing validation result and error message if any
61+
*/
62+
export function validateSubgraphNetworkMatch(
63+
manifestYaml: any,
64+
targetNetwork: string,
65+
): { valid: boolean; error?: string } {
66+
// Extract network from data sources
67+
const dataSources = manifestYaml.dataSources || [];
68+
const templates = manifestYaml.templates || [];
69+
const allSources = [...dataSources, ...templates];
70+
71+
if (allSources.length === 0) {
72+
return { valid: true }; // No data sources to validate
73+
}
74+
75+
// Get network from first data source
76+
const sourceNetwork = allSources[0].network;
77+
78+
if (sourceNetwork !== targetNetwork) {
79+
return {
80+
valid: false,
81+
error: `Network mismatch: The source subgraph is indexing the '${sourceNetwork}' network, but you're creating a subgraph for '${targetNetwork}' network. When composing subgraphs, they must index the same network.`,
82+
};
83+
}
84+
85+
return { valid: true };
86+
}
87+
88+
/**
89+
* Gets the minimum startBlock from all dataSources in the manifest
90+
* @param manifestYaml Parsed manifest YAML
91+
* @returns The minimum startBlock or undefined if no startBlock is found
92+
*/
93+
export function getMinStartBlock(manifestYaml: any): number | undefined {
94+
const dataSources = manifestYaml.dataSources || [];
95+
96+
const startBlocks = dataSources
97+
.map((ds: { source?: { startBlock?: number } }) => ds.source?.startBlock)
98+
.filter((block: unknown): block is number => typeof block === 'number');
99+
100+
return startBlocks.length > 0 ? Math.min(...startBlocks) : undefined;
101+
}

‎packages/cli/tests/cli/__snapshots__/init.test.ts.snap

+34
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,40 @@ Make sure to visit the documentation on https://thegraph.com/docs/ for further i
250250
"
251251
`;
252252

253+
exports[`Init > From existing subgraph > From existing subgraph 1`] = `
254+
" › Warning: The --skip-git flag will be removed in the next major version. By
255+
› default we will stop initializing a Git repository.
256+
- Create subgraph scaffold
257+
Generate subgraph
258+
- Create subgraph scaffold
259+
Write subgraph to directory
260+
- Create subgraph scaffold
261+
✔ Create subgraph scaffold
262+
- Install dependencies with yarn
263+
✔ Install dependencies with yarn
264+
- Generate ABI and schema types with yarn codegen
265+
✔ Generate ABI and schema types with yarn codegen
266+
"
267+
`;
268+
269+
exports[`Init > From existing subgraph > From existing subgraph 2`] = `0`;
270+
271+
exports[`Init > From existing subgraph > From existing subgraph 3`] = `
272+
"
273+
Subgraph user/from-existing-subgraph created in subgraph
274+
275+
Next steps:
276+
277+
1. Run \`graph auth\` to authenticate with your deploy key.
278+
279+
2. Type \`cd subgraph\` to enter the subgraph.
280+
281+
3. Run \`yarn deploy\` to deploy the subgraph.
282+
283+
Make sure to visit the documentation on https://thegraph.com/docs/ for further information.
284+
"
285+
`;
286+
253287
exports[`Init > NEAR > From contract 1`] = `
254288
" › Warning: The --skip-git flag will be removed in the next major version. By
255289
› default we will stop initializing a Git repository.

‎packages/cli/tests/cli/init.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,32 @@ describe(
241241
},
242242
);
243243
});
244+
245+
describe('From existing subgraph', () => {
246+
const fromSubgraphBaseDir = path.join(baseDir, 'subgraph');
247+
248+
cliTest(
249+
'From existing subgraph',
250+
[
251+
'init',
252+
'--skip-git',
253+
'--from-subgraph',
254+
'QmSgvtjK6b5GmnSeboH9AMdVrK8YeVrmJ1ESHw3WhYKdDH',
255+
'--network',
256+
'base',
257+
'--protocol',
258+
'subgraph',
259+
'user/from-existing-subgraph',
260+
path.join(fromSubgraphBaseDir, 'subgraph'),
261+
],
262+
path.join('init', 'subgraph', 'subgraph'),
263+
{
264+
exitCode: 0,
265+
timeout: 100_000,
266+
cwd: fromSubgraphBaseDir,
267+
deleteDir: true,
268+
},
269+
);
270+
});
244271
},
245272
);

‎packages/cli/tests/cli/init/subgraph/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)
Please sign in to comment.