Skip to content

Commit 326d3f0

Browse files
authored
Merge branch 'main' into fix/init-directory-already-exists
2 parents 4eaf6a2 + 08914a8 commit 326d3f0

File tree

14 files changed

+256
-622
lines changed

14 files changed

+256
-622
lines changed

.changeset/cold-snails-bake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': patch
3+
---
4+
5+
Using `graph add` with `localhost` network now prompts the user for input

.changeset/gold-deers-kick.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
Breaking changes to the CLI to prepare for the sunset of the hosted service.
6+
7+
- `graph auth`
8+
- Removed `--product` flag
9+
- Removed `--studio` flag
10+
- Removed `node` argument
11+
- `graph deploy`
12+
- Removed `--product` flag
13+
- Removed `--studio` flag
14+
- Removed `--from-hosted-service` flag
15+
- `graph init`
16+
- Removed `--product` flag
17+
- Removed `--studio` flag
18+
- Removed `--allow-simple-name` flag

.changeset/wild-weeks-protect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphprotocol/graph-cli': minor
3+
---
4+
5+
Add warning for available CLI updates

packages/cli/README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ As of today, the command line interface supports the following commands:
2929
The Graph CLI takes a subgraph manifest (defaults to `subgraph.yaml`) with references to:
3030

3131
- A GraphQL schema,
32-
- Smart contract ABIs, and
33-
- Mappings written in AssemblyScript.
32+
- Smart contract ABIs,
33+
- Mappings written in AssemblyScript for traditional subgraphs,
34+
- Substreams package and triggers for substreams-based subgraphs
3435

3536
It compiles the mappings to WebAssembly, builds a ready-to-use version of the subgraph saved to IPFS
3637
or a local directory for debugging, and deploys the subgraph to a
37-
[Graph Node](https://github.com/graphprotocol/graph-node).
38+
[Graph Node](https://github.com/graphprotocol/graph-node) instance or
39+
[Subgraph Studio](https://thegraph.com/studio/). Additionally it allows you to publish your subgraph
40+
to the decentralized network directly, making it available for indexing via
41+
[Graph Explorer](https://thegraph.com/explorer)
3842

3943
## Installation
4044

@@ -73,11 +77,15 @@ Use one of the following commands depending on your distribution:
7377

7478
The Graph CLI can be used with a local or self-hosted
7579
[Graph Node](https://github.com/graphprotocol/graph-node) or with the
76-
[Hosted Service](https://thegraph.com/explorer/). To help you get going, there are
77-
[quick start guides](https://thegraph.com/docs/en/developer/quick-start/) available for both.
80+
[Subgraph Studio](https://thegraph.com/studio/). To help you get going, there are
81+
[quick start guides](https://thegraph.com/docs/en/quick-start/) available for both.
82+
83+
Additionally, you can use Graph CLI to
84+
[publish](https://thegraph.com/docs/en/quick-start/#publishing-from-the-cli) your subgraph to the
85+
decentralized network directly.
7886

7987
If you are ready to dive into the details of building a subgraph from scratch, there is a
80-
[detailed walkthrough](https://thegraph.com/docs/en/developer/create-subgraph-hosted/) for that as
88+
[detailed walkthrough](https://thegraph.com/docs/en/developing/creating-a-subgraph/) for that as
8189
well, along with API documentation for the
8290
[AssemblyScript API](https://thegraph.com/docs/en/developer/assemblyscript-api/).
8391

packages/cli/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@oclif/core": "2.8.6",
3333
"@oclif/plugin-autocomplete": "^2.3.6",
3434
"@oclif/plugin-not-found": "^2.4.0",
35+
"@oclif/plugin-warn-if-update-available": "^3.1.20",
3536
"@whatwg-node/fetch": "^0.8.4",
3637
"assemblyscript": "0.19.23",
3738
"binary-install-raw": "0.0.13",
@@ -85,7 +86,12 @@
8586
],
8687
"plugins": [
8788
"@oclif/plugin-not-found",
88-
"@oclif/plugin-autocomplete"
89-
]
89+
"@oclif/plugin-autocomplete",
90+
"@oclif/plugin-warn-if-update-available"
91+
],
92+
"warn-if-update-available": {
93+
"timeoutInDays": 1,
94+
"message": "<%= config.name %> update available from <%= chalk.yellowBright(config.version) %> to <%= chalk.greenBright(latest) %>."
95+
}
9096
}
9197
}
Lines changed: 3 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,22 @@
11
import { URL } from 'url';
22
import { print } from 'gluegun';
3-
import { GRAPH_CLI_SHARED_HEADERS } from '../constants';
43

54
export const SUBGRAPH_STUDIO_URL = 'https://api.studio.thegraph.com/deploy/';
6-
const HOSTED_SERVICE_URL = 'https://api.thegraph.com/deploy/';
7-
const HOSTED_SERVICE_INDEX_NODE_URL = 'https://api.thegraph.com/index-node/graphql';
85

96
export const validateNodeUrl = (node: string) => new URL(node);
107

118
export const normalizeNodeUrl = (node: string) => new URL(node).toString();
129

13-
export function chooseNodeUrl({
14-
product,
15-
studio,
16-
node,
17-
allowSimpleName,
18-
}: {
19-
product: string | undefined;
20-
studio: boolean | undefined;
21-
node?: string;
22-
allowSimpleName?: boolean;
23-
}) {
10+
export function chooseNodeUrl({ node }: { node?: string }) {
2411
if (node) {
2512
try {
2613
validateNodeUrl(node);
14+
return { node };
2715
} catch (e) {
2816
print.error(`Graph node "${node}" is invalid: ${e.message}`);
2917
process.exit(1);
3018
}
31-
} else {
32-
if (studio) {
33-
product = 'subgraph-studio';
34-
}
35-
switch (product) {
36-
case 'subgraph-studio':
37-
node = SUBGRAPH_STUDIO_URL;
38-
break;
39-
case 'hosted-service':
40-
node = HOSTED_SERVICE_URL;
41-
break;
42-
}
43-
}
44-
if (node?.match(/studio/) || product === 'subgraph-studio') {
45-
allowSimpleName = true;
46-
}
47-
return { node, allowSimpleName };
48-
}
49-
50-
export async function getHostedServiceSubgraphId({
51-
subgraphName,
52-
}: {
53-
subgraphName: string;
54-
}): Promise<{
55-
subgraph: string;
56-
synced: boolean;
57-
health: 'healthy' | 'unhealthy' | 'failed';
58-
}> {
59-
const response = await fetch(HOSTED_SERVICE_INDEX_NODE_URL, {
60-
method: 'POST',
61-
body: JSON.stringify({
62-
query: /* GraphQL */ `
63-
query GraphCli_getSubgraphId($subgraphName: String!) {
64-
indexingStatusForCurrentVersion(subgraphName: $subgraphName) {
65-
subgraph
66-
synced
67-
health
68-
}
69-
}
70-
`,
71-
variables: {
72-
subgraphName,
73-
},
74-
}),
75-
headers: {
76-
'content-type': 'application/json',
77-
...GRAPH_CLI_SHARED_HEADERS,
78-
},
79-
});
80-
81-
const { data, errors } = await response.json();
82-
83-
if (errors) {
84-
throw new Error(errors[0].message);
85-
}
86-
87-
if (!data.indexingStatusForCurrentVersion) {
88-
throw new Error(`Subgraph "${subgraphName}" not found on the hosted service`);
8919
}
9020

91-
return data.indexingStatusForCurrentVersion;
21+
return { node: SUBGRAPH_STUDIO_URL };
9222
}

packages/cli/src/command-helpers/subgraph.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
export const validateSubgraphName = (
2-
name: string,
3-
{ allowSimpleName }: { allowSimpleName?: boolean },
4-
) => {
5-
if (allowSimpleName) {
6-
return name;
7-
}
8-
if (name.split('/').length !== 2) {
9-
throw new Error(`Subgraph name "${name}" needs to have the format "<PREFIX>/${name}".
10-
11-
When using the Hosted Service at https://thegraph.com, <PREFIX> is the
12-
name of your GitHub user or organization.
13-
14-
You can bypass this check with --allow-simple-name.`);
15-
}
16-
};
17-
181
export const getSubgraphBasename = (name: string) => {
192
const segments = name.split('/', 2);
203
return segments[segments.length - 1];

packages/cli/src/commands/add.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ export default class AddCommand extends Command {
7777
const manifest = await Subgraph.load(manifestPath, { protocol });
7878
const network = manifest.result.getIn(['dataSources', 0, 'network']) as any;
7979
const result = manifest.result.asMutable();
80+
const isLocalHost = network === 'localhost'; // This flag prevent Etherscan lookups in case the network selected is `localhost`
81+
82+
if (isLocalHost) this.warn('`localhost` network detected, prompting user for inputs');
8083

8184
let startBlock = startBlockFlag;
8285
let contractName = contractNameFlag;
@@ -96,10 +99,44 @@ export default class AddCommand extends Command {
9699
} else if (network === 'poa-core') {
97100
ethabi = await loadAbiFromBlockScout(EthereumABI, network, address);
98101
} else {
99-
ethabi = await loadAbiFromEtherscan(EthereumABI, network, address);
102+
try {
103+
if (isLocalHost) throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
104+
105+
ethabi = await loadAbiFromEtherscan(EthereumABI, network, address);
106+
} catch (error) {
107+
// we cannot ask user to do prompt in test environment
108+
if (process.env.NODE_ENV !== 'test') {
109+
const { abi: abiFromFile } = await prompt.ask<{ abi: EthereumABI }>([
110+
{
111+
type: 'input',
112+
name: 'abi',
113+
message: 'ABI file (path)',
114+
initial: ethabi,
115+
validate: async (value: string) => {
116+
try {
117+
EthereumABI.load(contractName, value);
118+
return true;
119+
} catch (e) {
120+
this.error(e.message);
121+
}
122+
},
123+
result: async (value: string) => {
124+
try {
125+
return EthereumABI.load(contractName, value);
126+
} catch (e) {
127+
return e.message;
128+
}
129+
},
130+
},
131+
]);
132+
ethabi = abiFromFile;
133+
}
134+
}
100135
}
101136

102137
try {
138+
if (isLocalHost) throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
139+
103140
startBlock ||= Number(await loadStartBlockForContract(network, address)).toString();
104141
} catch (error) {
105142
// we cannot ask user to do prompt in test environment
@@ -122,6 +159,8 @@ export default class AddCommand extends Command {
122159
}
123160

124161
try {
162+
if (isLocalHost) throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
163+
125164
contractName = await loadContractNameForAddress(network, address);
126165
} catch (error) {
127166
// not asking user to do prompt in test environment

packages/cli/src/commands/auth.ts

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,21 @@ export default class AuthCommand extends Command {
77
static description = 'Sets the deploy key to use when deploying to a Graph node.';
88

99
static args = {
10-
node: Args.string(),
1110
'deploy-key': Args.string(),
1211
};
1312

1413
static flags = {
1514
help: Flags.help({
1615
char: 'h',
1716
}),
18-
19-
product: Flags.string({
20-
summary: 'Select a product for which to authenticate.',
21-
options: ['subgraph-studio', 'hosted-service'],
22-
deprecated: {
23-
message:
24-
'In next major version, this flag will be removed. By default we will deploy to the Graph Studio. Learn more about Sunrise of Decentralized Data https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/',
25-
},
26-
}),
27-
studio: Flags.boolean({
28-
summary: 'Shortcut for "--product subgraph-studio".',
29-
exclusive: ['product'],
30-
deprecated: {
31-
message:
32-
'In next major version, this flag will be removed. By default we will deploy to the Graph Studio. Learn more about Sunrise of Decentralized Data https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/',
33-
},
34-
}),
3517
};
3618

3719
async run() {
38-
const {
39-
args: { node: nodeOrDeployKey, 'deploy-key': deployKeyFlag },
40-
flags: { product, studio },
20+
let {
21+
args: { 'deploy-key': deployKey },
4122
} = await this.parse(AuthCommand);
4223

43-
// if user specifies --product or --studio then deployKey is the first parameter
44-
let node: string | undefined;
45-
let deployKey = deployKeyFlag;
46-
if (product || studio) {
47-
({ node } = chooseNodeUrl({ product, studio, node }));
48-
deployKey = nodeOrDeployKey;
49-
} else {
50-
node = nodeOrDeployKey;
51-
}
52-
53-
// eslint-disable-next-line -- prettier has problems with ||=
54-
node =
55-
node ||
56-
(await ux.prompt('Which product to initialize?', {
57-
required: true,
58-
}));
24+
const { node } = chooseNodeUrl({});
5925

6026
// eslint-disable-next-line -- prettier has problems with ||=
6127
deployKey =
@@ -68,10 +34,6 @@ export default class AuthCommand extends Command {
6834
this.error('✖ Deploy key must not exceed 200 characters', { exit: 1 });
6935
}
7036

71-
if (product === 'hosted-service' || node?.match(/api.thegraph.com/)) {
72-
this.error('✖ The hosted service is deprecated', { exit: 1 });
73-
}
74-
7537
try {
7638
await saveDeployKey(node!, deployKey);
7739
print.success(`Deploy key set for ${node}`);

0 commit comments

Comments
 (0)