@graphprotocol/graph-cli 0.88.0-alpha-20241031144836-2d9c5cd → 0.88.0-alpha-20241031145713-add2ae3
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/CHANGELOG.md +11 -18
- package/README.md +6 -14
- package/dist/command-helpers/node.d.ts +13 -2
- package/dist/command-helpers/node.js +54 -4
- package/dist/command-helpers/subgraph.d.ts +3 -0
- package/dist/command-helpers/subgraph.js +15 -1
- package/dist/commands/add.js +43 -1
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +35 -2
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.js +164 -5
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +141 -9
- package/oclif.manifest.json +88 -1
- package/package.json +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
# @graphprotocol/graph-cli
|
|
2
2
|
|
|
3
|
-
## 0.88.0-alpha-
|
|
3
|
+
## 0.88.0-alpha-20241031145713-add2ae3
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
- [#
|
|
8
|
-
[`
|
|
9
|
-
Thanks [@
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Removed `--product` flag
|
|
18
|
-
- Removed `--studio` flag
|
|
19
|
-
- Removed `--from-hosted-service` flag
|
|
20
|
-
- `graph init`
|
|
21
|
-
- Removed `--product` flag
|
|
22
|
-
- Removed `--studio` flag
|
|
23
|
-
- Removed `--allow-simple-name` flag
|
|
7
|
+
- [#1749](https://github.com/graphprotocol/graph-tooling/pull/1749)
|
|
8
|
+
[`bf43cbf`](https://github.com/graphprotocol/graph-tooling/commit/bf43cbf8283c53f0005d7cb9dfa681c92f3a6a91)
|
|
9
|
+
Thanks [@0237h](https://github.com/0237h)! - Add warning for available CLI updates
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#1751](https://github.com/graphprotocol/graph-tooling/pull/1751)
|
|
14
|
+
[`0ba99dc`](https://github.com/graphprotocol/graph-tooling/commit/0ba99dcd6783fb20779d4f5a6791fc155969939e)
|
|
15
|
+
Thanks [@0237h](https://github.com/0237h)! - Using `graph add` with `localhost` network now
|
|
16
|
+
prompts the user for input
|
|
24
17
|
|
|
25
18
|
## 0.87.0
|
|
26
19
|
|
package/README.md
CHANGED
|
@@ -29,16 +29,12 @@ As of today, the command line interface supports the following commands:
|
|
|
29
29
|
The Graph CLI takes a subgraph manifest (defaults to `subgraph.yaml`) with references to:
|
|
30
30
|
|
|
31
31
|
- A GraphQL schema,
|
|
32
|
-
- Smart contract ABIs,
|
|
33
|
-
- Mappings written in AssemblyScript
|
|
34
|
-
- Substreams package and triggers for substreams-based subgraphs
|
|
32
|
+
- Smart contract ABIs, and
|
|
33
|
+
- Mappings written in AssemblyScript.
|
|
35
34
|
|
|
36
35
|
It compiles the mappings to WebAssembly, builds a ready-to-use version of the subgraph saved to IPFS
|
|
37
36
|
or a local directory for debugging, and deploys the subgraph to a
|
|
38
|
-
[Graph Node](https://github.com/graphprotocol/graph-node)
|
|
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)
|
|
37
|
+
[Graph Node](https://github.com/graphprotocol/graph-node).
|
|
42
38
|
|
|
43
39
|
## Installation
|
|
44
40
|
|
|
@@ -77,15 +73,11 @@ Use one of the following commands depending on your distribution:
|
|
|
77
73
|
|
|
78
74
|
The Graph CLI can be used with a local or self-hosted
|
|
79
75
|
[Graph Node](https://github.com/graphprotocol/graph-node) or with the
|
|
80
|
-
[
|
|
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.
|
|
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.
|
|
86
78
|
|
|
87
79
|
If you are ready to dive into the details of building a subgraph from scratch, there is a
|
|
88
|
-
[detailed walkthrough](https://thegraph.com/docs/en/
|
|
80
|
+
[detailed walkthrough](https://thegraph.com/docs/en/developer/create-subgraph-hosted/) for that as
|
|
89
81
|
well, along with API documentation for the
|
|
90
82
|
[AssemblyScript API](https://thegraph.com/docs/en/developer/assemblyscript-api/).
|
|
91
83
|
|
|
@@ -3,8 +3,19 @@ import { URL } from 'url';
|
|
|
3
3
|
export declare const SUBGRAPH_STUDIO_URL = "https://api.studio.thegraph.com/deploy/";
|
|
4
4
|
export declare const validateNodeUrl: (node: string) => URL;
|
|
5
5
|
export declare const normalizeNodeUrl: (node: string) => string;
|
|
6
|
-
export declare function chooseNodeUrl({ node }: {
|
|
6
|
+
export declare function chooseNodeUrl({ product, studio, node, allowSimpleName, }: {
|
|
7
|
+
product: string | undefined;
|
|
8
|
+
studio: boolean | undefined;
|
|
7
9
|
node?: string;
|
|
10
|
+
allowSimpleName?: boolean;
|
|
8
11
|
}): {
|
|
9
|
-
node: string;
|
|
12
|
+
node: string | undefined;
|
|
13
|
+
allowSimpleName: boolean | undefined;
|
|
10
14
|
};
|
|
15
|
+
export declare function getHostedServiceSubgraphId({ subgraphName, }: {
|
|
16
|
+
subgraphName: string;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
subgraph: string;
|
|
19
|
+
synced: boolean;
|
|
20
|
+
health: 'healthy' | 'unhealthy' | 'failed';
|
|
21
|
+
}>;
|
|
@@ -1,24 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.chooseNodeUrl = exports.normalizeNodeUrl = exports.validateNodeUrl = exports.SUBGRAPH_STUDIO_URL = void 0;
|
|
3
|
+
exports.getHostedServiceSubgraphId = exports.chooseNodeUrl = exports.normalizeNodeUrl = exports.validateNodeUrl = exports.SUBGRAPH_STUDIO_URL = void 0;
|
|
4
4
|
const url_1 = require("url");
|
|
5
5
|
const gluegun_1 = require("gluegun");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
6
7
|
exports.SUBGRAPH_STUDIO_URL = 'https://api.studio.thegraph.com/deploy/';
|
|
8
|
+
const HOSTED_SERVICE_URL = 'https://api.thegraph.com/deploy/';
|
|
9
|
+
const HOSTED_SERVICE_INDEX_NODE_URL = 'https://api.thegraph.com/index-node/graphql';
|
|
7
10
|
const validateNodeUrl = (node) => new url_1.URL(node);
|
|
8
11
|
exports.validateNodeUrl = validateNodeUrl;
|
|
9
12
|
const normalizeNodeUrl = (node) => new url_1.URL(node).toString();
|
|
10
13
|
exports.normalizeNodeUrl = normalizeNodeUrl;
|
|
11
|
-
function chooseNodeUrl({ node }) {
|
|
14
|
+
function chooseNodeUrl({ product, studio, node, allowSimpleName, }) {
|
|
12
15
|
if (node) {
|
|
13
16
|
try {
|
|
14
17
|
(0, exports.validateNodeUrl)(node);
|
|
15
|
-
return { node };
|
|
16
18
|
}
|
|
17
19
|
catch (e) {
|
|
18
20
|
gluegun_1.print.error(`Graph node "${node}" is invalid: ${e.message}`);
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
else {
|
|
25
|
+
if (studio) {
|
|
26
|
+
product = 'subgraph-studio';
|
|
27
|
+
}
|
|
28
|
+
switch (product) {
|
|
29
|
+
case 'subgraph-studio':
|
|
30
|
+
node = exports.SUBGRAPH_STUDIO_URL;
|
|
31
|
+
break;
|
|
32
|
+
case 'hosted-service':
|
|
33
|
+
node = HOSTED_SERVICE_URL;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (node?.match(/studio/) || product === 'subgraph-studio') {
|
|
38
|
+
allowSimpleName = true;
|
|
39
|
+
}
|
|
40
|
+
return { node, allowSimpleName };
|
|
23
41
|
}
|
|
24
42
|
exports.chooseNodeUrl = chooseNodeUrl;
|
|
43
|
+
async function getHostedServiceSubgraphId({ subgraphName, }) {
|
|
44
|
+
const response = await fetch(HOSTED_SERVICE_INDEX_NODE_URL, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
query: /* GraphQL */ `
|
|
48
|
+
query GraphCli_getSubgraphId($subgraphName: String!) {
|
|
49
|
+
indexingStatusForCurrentVersion(subgraphName: $subgraphName) {
|
|
50
|
+
subgraph
|
|
51
|
+
synced
|
|
52
|
+
health
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
variables: {
|
|
57
|
+
subgraphName,
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
headers: {
|
|
61
|
+
'content-type': 'application/json',
|
|
62
|
+
...constants_1.GRAPH_CLI_SHARED_HEADERS,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const { data, errors } = await response.json();
|
|
66
|
+
if (errors) {
|
|
67
|
+
throw new Error(errors[0].message);
|
|
68
|
+
}
|
|
69
|
+
if (!data.indexingStatusForCurrentVersion) {
|
|
70
|
+
throw new Error(`Subgraph "${subgraphName}" not found on the hosted service`);
|
|
71
|
+
}
|
|
72
|
+
return data.indexingStatusForCurrentVersion;
|
|
73
|
+
}
|
|
74
|
+
exports.getHostedServiceSubgraphId = getHostedServiceSubgraphId;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSubgraphBasename = void 0;
|
|
3
|
+
exports.getSubgraphBasename = exports.validateSubgraphName = void 0;
|
|
4
|
+
const validateSubgraphName = (name, { allowSimpleName }) => {
|
|
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
|
+
exports.validateSubgraphName = validateSubgraphName;
|
|
4
18
|
const getSubgraphBasename = (name) => {
|
|
5
19
|
const segments = name.split('/', 2);
|
|
6
20
|
return segments[segments.length - 1];
|
package/dist/commands/add.js
CHANGED
|
@@ -46,6 +46,9 @@ class AddCommand extends core_1.Command {
|
|
|
46
46
|
const manifest = await subgraph_1.default.load(manifestPath, { protocol });
|
|
47
47
|
const network = manifest.result.getIn(['dataSources', 0, 'network']);
|
|
48
48
|
const result = manifest.result.asMutable();
|
|
49
|
+
const isLocalHost = network === 'localhost'; // This flag prevent Etherscan lookups in case the network selected is `localhost`
|
|
50
|
+
if (isLocalHost)
|
|
51
|
+
this.warn('`localhost` network detected, prompting user for inputs');
|
|
49
52
|
let startBlock = startBlockFlag;
|
|
50
53
|
let contractName = contractNameFlag;
|
|
51
54
|
const entities = getEntities(manifest);
|
|
@@ -61,9 +64,46 @@ class AddCommand extends core_1.Command {
|
|
|
61
64
|
ethabi = await (0, abi_1.loadAbiFromBlockScout)(abi_2.default, network, address);
|
|
62
65
|
}
|
|
63
66
|
else {
|
|
64
|
-
|
|
67
|
+
try {
|
|
68
|
+
if (isLocalHost)
|
|
69
|
+
throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
|
|
70
|
+
ethabi = await (0, abi_1.loadAbiFromEtherscan)(abi_2.default, network, address);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
// we cannot ask user to do prompt in test environment
|
|
74
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
75
|
+
const { abi: abiFromFile } = await gluegun_1.prompt.ask([
|
|
76
|
+
{
|
|
77
|
+
type: 'input',
|
|
78
|
+
name: 'abi',
|
|
79
|
+
message: 'ABI file (path)',
|
|
80
|
+
initial: ethabi,
|
|
81
|
+
validate: async (value) => {
|
|
82
|
+
try {
|
|
83
|
+
abi_2.default.load(contractName, value);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
this.error(e.message);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
result: async (value) => {
|
|
91
|
+
try {
|
|
92
|
+
return abi_2.default.load(contractName, value);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return e.message;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
ethabi = abiFromFile;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
65
103
|
}
|
|
66
104
|
try {
|
|
105
|
+
if (isLocalHost)
|
|
106
|
+
throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
|
|
67
107
|
startBlock || (startBlock = Number(await (0, abi_1.loadStartBlockForContract)(network, address)).toString());
|
|
68
108
|
}
|
|
69
109
|
catch (error) {
|
|
@@ -86,6 +126,8 @@ class AddCommand extends core_1.Command {
|
|
|
86
126
|
}
|
|
87
127
|
}
|
|
88
128
|
try {
|
|
129
|
+
if (isLocalHost)
|
|
130
|
+
throw Error; // Triggers user prompting without waiting for Etherscan lookup to fail
|
|
89
131
|
contractName = await (0, abi_1.loadContractNameForAddress)(network, address);
|
|
90
132
|
}
|
|
91
133
|
catch (error) {
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class AuthCommand extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static args: {
|
|
5
|
+
node: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
5
6
|
'deploy-key': import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
6
7
|
};
|
|
7
8
|
static flags: {
|
|
8
9
|
help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
|
|
10
|
+
product: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
|
+
studio: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
12
|
};
|
|
10
13
|
run(): Promise<void>;
|
|
11
14
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -6,8 +6,23 @@ const auth_1 = require("../command-helpers/auth");
|
|
|
6
6
|
const node_1 = require("../command-helpers/node");
|
|
7
7
|
class AuthCommand extends core_1.Command {
|
|
8
8
|
async run() {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const { args: { node: nodeOrDeployKey, 'deploy-key': deployKeyFlag }, flags: { product, studio }, } = await this.parse(AuthCommand);
|
|
10
|
+
// if user specifies --product or --studio then deployKey is the first parameter
|
|
11
|
+
let node;
|
|
12
|
+
let deployKey = deployKeyFlag;
|
|
13
|
+
if (product || studio) {
|
|
14
|
+
({ node } = (0, node_1.chooseNodeUrl)({ product, studio, node }));
|
|
15
|
+
deployKey = nodeOrDeployKey;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
node = nodeOrDeployKey;
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line -- prettier has problems with ||=
|
|
21
|
+
node =
|
|
22
|
+
node ||
|
|
23
|
+
(await core_1.ux.prompt('Which product to initialize?', {
|
|
24
|
+
required: true,
|
|
25
|
+
}));
|
|
11
26
|
// eslint-disable-next-line -- prettier has problems with ||=
|
|
12
27
|
deployKey =
|
|
13
28
|
deployKey ||
|
|
@@ -18,6 +33,9 @@ class AuthCommand extends core_1.Command {
|
|
|
18
33
|
if (deployKey.length > 200) {
|
|
19
34
|
this.error('✖ Deploy key must not exceed 200 characters', { exit: 1 });
|
|
20
35
|
}
|
|
36
|
+
if (product === 'hosted-service' || node?.match(/api.thegraph.com/)) {
|
|
37
|
+
this.error('✖ The hosted service is deprecated', { exit: 1 });
|
|
38
|
+
}
|
|
21
39
|
try {
|
|
22
40
|
await (0, auth_1.saveDeployKey)(node, deployKey);
|
|
23
41
|
gluegun_1.print.success(`Deploy key set for ${node}`);
|
|
@@ -29,11 +47,26 @@ class AuthCommand extends core_1.Command {
|
|
|
29
47
|
}
|
|
30
48
|
AuthCommand.description = 'Sets the deploy key to use when deploying to a Graph node.';
|
|
31
49
|
AuthCommand.args = {
|
|
50
|
+
node: core_1.Args.string(),
|
|
32
51
|
'deploy-key': core_1.Args.string(),
|
|
33
52
|
};
|
|
34
53
|
AuthCommand.flags = {
|
|
35
54
|
help: core_1.Flags.help({
|
|
36
55
|
char: 'h',
|
|
37
56
|
}),
|
|
57
|
+
product: core_1.Flags.string({
|
|
58
|
+
summary: 'Select a product for which to authenticate.',
|
|
59
|
+
options: ['subgraph-studio', 'hosted-service'],
|
|
60
|
+
deprecated: {
|
|
61
|
+
message: '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/',
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
studio: core_1.Flags.boolean({
|
|
65
|
+
summary: 'Shortcut for "--product subgraph-studio".',
|
|
66
|
+
exclusive: ['product'],
|
|
67
|
+
deprecated: {
|
|
68
|
+
message: '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/',
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
38
71
|
};
|
|
39
72
|
exports.default = AuthCommand;
|
|
@@ -7,6 +7,8 @@ export default class DeployCommand extends Command {
|
|
|
7
7
|
};
|
|
8
8
|
static flags: {
|
|
9
9
|
help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
|
|
10
|
+
product: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
|
+
studio: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
12
|
node: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
13
|
'deploy-key': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
14
|
'access-token': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
@@ -20,6 +22,7 @@ export default class DeployCommand extends Command {
|
|
|
20
22
|
watch: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
23
|
network: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
22
24
|
'network-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
25
|
+
'from-hosted-service': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
23
26
|
};
|
|
24
27
|
run(): Promise<void>;
|
|
25
28
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -48,21 +48,149 @@ const headersFlag = core_1.Flags.custom({
|
|
|
48
48
|
parse: val => JSON.parse(val),
|
|
49
49
|
default: {},
|
|
50
50
|
});
|
|
51
|
+
const productOptions = ['subgraph-studio', 'hosted-service'];
|
|
51
52
|
const deployDebugger = (0, debug_1.default)('graph-cli:deploy');
|
|
52
53
|
class DeployCommand extends core_1.Command {
|
|
53
54
|
async run() {
|
|
54
|
-
const { args: { 'subgraph-name': subgraphNameArg, 'subgraph-manifest': manifest }, flags: { 'deploy-key': deployKeyFlag, 'access-token': accessToken, 'version-label': versionLabelFlag, ipfs, headers, node: nodeFlag, 'output-dir': outputDir, 'skip-migrations': skipMigrations, watch, 'debug-fork': debugFork, network, 'network-file': networkFile, 'ipfs-hash': ipfsHash, }, } = await this.parse(DeployCommand);
|
|
55
|
+
const { args: { 'subgraph-name': subgraphNameArg, 'subgraph-manifest': manifest }, flags: { product: productFlag, studio, 'deploy-key': deployKeyFlag, 'access-token': accessToken, 'version-label': versionLabelFlag, ipfs, headers, node: nodeFlag, 'output-dir': outputDir, 'skip-migrations': skipMigrations, watch, 'debug-fork': debugFork, network, 'network-file': networkFile, 'ipfs-hash': ipfsHash, 'from-hosted-service': hostedServiceSubgraphName, }, } = await this.parse(DeployCommand);
|
|
56
|
+
if (hostedServiceSubgraphName) {
|
|
57
|
+
const { health, subgraph: subgraphIpfsHash } = await (0, node_1.getHostedServiceSubgraphId)({
|
|
58
|
+
subgraphName: hostedServiceSubgraphName,
|
|
59
|
+
});
|
|
60
|
+
const safeToDeployFailedSubgraph = health === 'failed'
|
|
61
|
+
? await core_1.ux.confirm('This subgraph has failed indexing on hosted service. Do you wish to continue the deploy?')
|
|
62
|
+
: true;
|
|
63
|
+
if (!safeToDeployFailedSubgraph)
|
|
64
|
+
return;
|
|
65
|
+
const safeToDeployUnhealthySubgraph = health === 'unhealthy'
|
|
66
|
+
? await core_1.ux.confirm('This subgraph is not healthy on hosted service. Do you wish to continue the deploy?')
|
|
67
|
+
: true;
|
|
68
|
+
if (!safeToDeployUnhealthySubgraph)
|
|
69
|
+
return;
|
|
70
|
+
const { node } = (0, node_1.chooseNodeUrl)({ studio: true, product: undefined });
|
|
71
|
+
// shouldn't happen, but we need to satisfy the compiler
|
|
72
|
+
if (!node) {
|
|
73
|
+
this.error('No node URL available');
|
|
74
|
+
}
|
|
75
|
+
const requestUrl = new url_1.URL(node);
|
|
76
|
+
const client = (0, jsonrpc_1.createJsonRpcClient)(requestUrl);
|
|
77
|
+
// Exit with an error code if the client couldn't be created
|
|
78
|
+
if (!client) {
|
|
79
|
+
this.error('Failed to create RPC client');
|
|
80
|
+
}
|
|
81
|
+
// Use the deploy key, if one is set
|
|
82
|
+
let deployKey = deployKeyFlag;
|
|
83
|
+
if (!deployKey && accessToken) {
|
|
84
|
+
deployKey = accessToken; // backwards compatibility
|
|
85
|
+
}
|
|
86
|
+
deployKey = await (0, auth_1.identifyDeployKey)(node, deployKey);
|
|
87
|
+
if (!deployKey) {
|
|
88
|
+
this.error('No deploy key available');
|
|
89
|
+
}
|
|
90
|
+
// @ts-expect-error options property seems to exist
|
|
91
|
+
client.options.headers = {
|
|
92
|
+
...constants_1.GRAPH_CLI_SHARED_HEADERS,
|
|
93
|
+
Authorization: 'Bearer ' + deployKey,
|
|
94
|
+
};
|
|
95
|
+
const subgraphName = await core_1.ux.prompt('What is the name of the subgraph you want to deploy?', {
|
|
96
|
+
required: true,
|
|
97
|
+
});
|
|
98
|
+
// Ask for label if not on hosted service
|
|
99
|
+
const versionLabel = versionLabelFlag ||
|
|
100
|
+
(await core_1.ux.prompt('Which version label to use? (e.g. "v0.0.1")', {
|
|
101
|
+
required: true,
|
|
102
|
+
}));
|
|
103
|
+
const spinner = gluegun_1.print.spin(`Deploying to Graph node ${requestUrl}`);
|
|
104
|
+
client.request('subgraph_deploy', {
|
|
105
|
+
name: subgraphName,
|
|
106
|
+
ipfs_hash: subgraphIpfsHash,
|
|
107
|
+
version_label: versionLabel,
|
|
108
|
+
debug_fork: debugFork,
|
|
109
|
+
}, async (
|
|
110
|
+
// @ts-expect-error TODO: why are the arguments not typed?
|
|
111
|
+
requestError,
|
|
112
|
+
// @ts-expect-error TODO: why are the arguments not typed?
|
|
113
|
+
jsonRpcError,
|
|
114
|
+
// @ts-expect-error TODO: why are the arguments not typed?
|
|
115
|
+
res) => {
|
|
116
|
+
deployDebugger('requestError: %O', requestError);
|
|
117
|
+
deployDebugger('jsonRpcError: %O', jsonRpcError);
|
|
118
|
+
if (jsonRpcError) {
|
|
119
|
+
const message = jsonRpcError?.message || jsonRpcError?.code?.toString();
|
|
120
|
+
deployDebugger('message: %O', message);
|
|
121
|
+
let errorMessage = `Failed to deploy to Graph node ${requestUrl}: ${message}`;
|
|
122
|
+
// Provide helpful advice when the subgraph has not been created yet
|
|
123
|
+
if (message?.match(/subgraph name not found/)) {
|
|
124
|
+
errorMessage += `
|
|
125
|
+
Make sure to create the subgraph first by running the following command:
|
|
126
|
+
$ graph create --node ${node} ${subgraphName}`;
|
|
127
|
+
}
|
|
128
|
+
if (message?.match(/auth failure/)) {
|
|
129
|
+
errorMessage += '\nYou may need to authenticate first.';
|
|
130
|
+
}
|
|
131
|
+
spinner.fail(errorMessage);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
else if (requestError) {
|
|
135
|
+
spinner.fail(`HTTP error deploying the subgraph ${requestError.code}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
spinner.stop();
|
|
140
|
+
const base = requestUrl.protocol + '//' + requestUrl.hostname;
|
|
141
|
+
let playground = res.playground;
|
|
142
|
+
let queries = res.queries;
|
|
143
|
+
// Add a base URL if graph-node did not return the full URL
|
|
144
|
+
if (playground.charAt(0) === ':') {
|
|
145
|
+
playground = base + playground;
|
|
146
|
+
}
|
|
147
|
+
if (queries.charAt(0) === ':') {
|
|
148
|
+
queries = base + queries;
|
|
149
|
+
}
|
|
150
|
+
gluegun_1.print.success(`Deployed to ${playground}`);
|
|
151
|
+
gluegun_1.print.info('\nSubgraph endpoints:');
|
|
152
|
+
gluegun_1.print.info(`Queries (HTTP): ${queries}`);
|
|
153
|
+
gluegun_1.print.info(``);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
55
159
|
const subgraphName = subgraphNameArg ||
|
|
56
160
|
(await core_1.ux.prompt('What is the subgraph name?', {
|
|
57
161
|
required: true,
|
|
58
162
|
}));
|
|
163
|
+
// We are given a node URL, so we prioritize that over the product flag
|
|
164
|
+
const product = nodeFlag
|
|
165
|
+
? productFlag
|
|
166
|
+
: studio
|
|
167
|
+
? 'subgraph-studio'
|
|
168
|
+
: productFlag ||
|
|
169
|
+
(await gluegun_1.prompt
|
|
170
|
+
.ask([
|
|
171
|
+
{
|
|
172
|
+
name: 'product',
|
|
173
|
+
message: 'Which product to deploy for?',
|
|
174
|
+
required: true,
|
|
175
|
+
type: 'select',
|
|
176
|
+
choices: productOptions,
|
|
177
|
+
},
|
|
178
|
+
])
|
|
179
|
+
.then(({ product }) => product));
|
|
180
|
+
if (product === 'hosted-service') {
|
|
181
|
+
this.error('✖ The hosted service is deprecated', { exit: 1 });
|
|
182
|
+
}
|
|
59
183
|
const { node } = (0, node_1.chooseNodeUrl)({
|
|
184
|
+
product,
|
|
185
|
+
studio,
|
|
60
186
|
node: nodeFlag,
|
|
61
187
|
});
|
|
62
188
|
if (!node) {
|
|
63
189
|
// shouldn't happen, but we do the check to satisfy TS
|
|
64
190
|
this.error('No Graph node provided');
|
|
65
191
|
}
|
|
192
|
+
const isStudio = node.match(/studio/);
|
|
193
|
+
const isHostedService = node.match(/thegraph.com/) && !isStudio;
|
|
66
194
|
const requestUrl = new url_1.URL(node);
|
|
67
195
|
const client = (0, jsonrpc_1.createJsonRpcClient)(requestUrl);
|
|
68
196
|
// Exit with an error code if the client couldn't be created
|
|
@@ -85,9 +213,11 @@ class DeployCommand extends core_1.Command {
|
|
|
85
213
|
}
|
|
86
214
|
// Ask for label if not on hosted service
|
|
87
215
|
let versionLabel = versionLabelFlag;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
216
|
+
if (!versionLabel && !isHostedService) {
|
|
217
|
+
versionLabel = await core_1.ux.prompt('Which version label to use? (e.g. "v0.0.1")', {
|
|
218
|
+
required: true,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
91
221
|
const deploySubgraph = async (ipfsHash) => {
|
|
92
222
|
const spinner = gluegun_1.print.spin(`Deploying to Graph node ${requestUrl}`);
|
|
93
223
|
client.request('subgraph_deploy', {
|
|
@@ -108,6 +238,18 @@ class DeployCommand extends core_1.Command {
|
|
|
108
238
|
const message = jsonRpcError?.message || jsonRpcError?.code?.toString();
|
|
109
239
|
deployDebugger('message: %O', message);
|
|
110
240
|
let errorMessage = `Failed to deploy to Graph node ${requestUrl}: ${message}`;
|
|
241
|
+
// Provide helpful advice when the subgraph has not been created yet
|
|
242
|
+
if (message?.match(/subgraph name not found/)) {
|
|
243
|
+
if (isHostedService) {
|
|
244
|
+
errorMessage +=
|
|
245
|
+
'\nYou may need to create it at https://thegraph.com/explorer/dashboard.';
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
errorMessage += `
|
|
249
|
+
Make sure to create the subgraph first by running the following command:
|
|
250
|
+
$ graph create --node ${node} ${subgraphName}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
111
253
|
if (message?.match(/auth failure/)) {
|
|
112
254
|
errorMessage += '\nYou may need to authenticate first.';
|
|
113
255
|
}
|
|
@@ -130,7 +272,12 @@ class DeployCommand extends core_1.Command {
|
|
|
130
272
|
if (queries.charAt(0) === ':') {
|
|
131
273
|
queries = base + queries;
|
|
132
274
|
}
|
|
133
|
-
|
|
275
|
+
if (isHostedService) {
|
|
276
|
+
gluegun_1.print.success(`Deployed to https://thegraph.com/explorer/subgraph/${subgraphName}`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
gluegun_1.print.success(`Deployed to ${playground}`);
|
|
280
|
+
}
|
|
134
281
|
gluegun_1.print.info('\nSubgraph endpoints:');
|
|
135
282
|
gluegun_1.print.info(`Queries (HTTP): ${queries}`);
|
|
136
283
|
gluegun_1.print.info(``);
|
|
@@ -224,6 +371,14 @@ DeployCommand.flags = {
|
|
|
224
371
|
help: core_1.Flags.help({
|
|
225
372
|
char: 'h',
|
|
226
373
|
}),
|
|
374
|
+
product: core_1.Flags.string({
|
|
375
|
+
summary: 'Select a product for which to authenticate.',
|
|
376
|
+
options: productOptions,
|
|
377
|
+
}),
|
|
378
|
+
studio: core_1.Flags.boolean({
|
|
379
|
+
summary: 'Shortcut for "--product subgraph-studio".',
|
|
380
|
+
exclusive: ['product'],
|
|
381
|
+
}),
|
|
227
382
|
node: core_1.Flags.string({
|
|
228
383
|
summary: 'Graph node for which to initialize.',
|
|
229
384
|
char: 'g',
|
|
@@ -271,9 +426,13 @@ DeployCommand.flags = {
|
|
|
271
426
|
network: core_1.Flags.string({
|
|
272
427
|
summary: 'Network configuration to use from the networks config file.',
|
|
273
428
|
}),
|
|
429
|
+
// TODO: should be networksFile (with an "s"), or?
|
|
274
430
|
'network-file': core_1.Flags.file({
|
|
275
431
|
summary: 'Networks config file path.',
|
|
276
432
|
default: 'networks.json',
|
|
277
433
|
}),
|
|
434
|
+
'from-hosted-service': core_1.Flags.string({
|
|
435
|
+
summary: 'Hosted service Subgraph Name to deploy to studio.',
|
|
436
|
+
}),
|
|
278
437
|
};
|
|
279
438
|
exports.default = DeployCommand;
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -8,7 +8,10 @@ export default class InitCommand extends Command {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
|
|
10
10
|
protocol: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
|
+
product: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
|
+
studio: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
13
|
node: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
|
+
'allow-simple-name': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
15
|
'from-contract': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
13
16
|
'from-example': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
17
|
'contract-name': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
package/dist/commands/init.js
CHANGED
|
@@ -88,10 +88,17 @@ const DEFAULT_EXAMPLE_SUBGRAPH = 'ethereum-gravatar';
|
|
|
88
88
|
class InitCommand extends core_1.Command {
|
|
89
89
|
async run() {
|
|
90
90
|
const { args: { subgraphName, directory }, flags, } = await this.parse(InitCommand);
|
|
91
|
-
const { protocol, node: nodeFlag, 'from-contract': fromContract, 'contract-name': contractName, 'from-example': fromExample, 'index-events': indexEvents, 'skip-install': skipInstall, 'skip-git': skipGit, network, abi: abiPath, 'start-block': startBlock, spkg: spkgPath, } = flags;
|
|
91
|
+
const { protocol, product, studio, node: nodeFlag, 'allow-simple-name': allowSimpleNameFlag, 'from-contract': fromContract, 'contract-name': contractName, 'from-example': fromExample, 'index-events': indexEvents, 'skip-install': skipInstall, 'skip-git': skipGit, network, abi: abiPath, 'start-block': startBlock, spkg: spkgPath, } = flags;
|
|
92
92
|
initDebugger('Flags: %O', flags);
|
|
93
|
-
|
|
93
|
+
if (product === 'hosted-service') {
|
|
94
|
+
this.error('✖ The hosted service is deprecated', { exit: 1 });
|
|
95
|
+
}
|
|
96
|
+
let { node, allowSimpleName } = (0, node_1.chooseNodeUrl)({
|
|
97
|
+
product,
|
|
98
|
+
// if we are loading example, we want to ensure we are using studio
|
|
99
|
+
studio: studio || fromExample !== undefined,
|
|
94
100
|
node: nodeFlag,
|
|
101
|
+
allowSimpleName: allowSimpleNameFlag,
|
|
95
102
|
});
|
|
96
103
|
if (fromContract && fromExample) {
|
|
97
104
|
this.error('Only one of "--from-example" and "--from-contract" can be used at a time.', {
|
|
@@ -124,6 +131,7 @@ class InitCommand extends core_1.Command {
|
|
|
124
131
|
if (fromExample && subgraphName && directory) {
|
|
125
132
|
await initSubgraphFromExample.bind(this)({
|
|
126
133
|
fromExample,
|
|
134
|
+
allowSimpleName,
|
|
127
135
|
directory,
|
|
128
136
|
subgraphName,
|
|
129
137
|
skipInstall,
|
|
@@ -169,6 +177,7 @@ class InitCommand extends core_1.Command {
|
|
|
169
177
|
await initSubgraphFromContract.bind(this)({
|
|
170
178
|
protocolInstance,
|
|
171
179
|
abi,
|
|
180
|
+
allowSimpleName,
|
|
172
181
|
directory,
|
|
173
182
|
contract: fromContract,
|
|
174
183
|
indexEvents,
|
|
@@ -186,6 +195,7 @@ class InitCommand extends core_1.Command {
|
|
|
186
195
|
}
|
|
187
196
|
if (fromExample) {
|
|
188
197
|
const answers = await processFromExampleInitForm.bind(this)({
|
|
198
|
+
allowSimpleName,
|
|
189
199
|
subgraphName,
|
|
190
200
|
directory,
|
|
191
201
|
});
|
|
@@ -194,6 +204,7 @@ class InitCommand extends core_1.Command {
|
|
|
194
204
|
return;
|
|
195
205
|
}
|
|
196
206
|
await initSubgraphFromExample.bind(this)({
|
|
207
|
+
allowSimpleName,
|
|
197
208
|
fromExample,
|
|
198
209
|
subgraphName: answers.subgraphName,
|
|
199
210
|
directory: answers.directory,
|
|
@@ -205,8 +216,12 @@ class InitCommand extends core_1.Command {
|
|
|
205
216
|
// Otherwise, take the user through the interactive form
|
|
206
217
|
const answers = await processInitForm.bind(this)({
|
|
207
218
|
protocol: protocol,
|
|
219
|
+
product,
|
|
220
|
+
studio,
|
|
221
|
+
node,
|
|
208
222
|
abi,
|
|
209
223
|
abiPath,
|
|
224
|
+
allowSimpleName,
|
|
210
225
|
directory,
|
|
211
226
|
contract: fromContract,
|
|
212
227
|
indexEvents,
|
|
@@ -221,11 +236,15 @@ class InitCommand extends core_1.Command {
|
|
|
221
236
|
this.exit(1);
|
|
222
237
|
return;
|
|
223
238
|
}
|
|
224
|
-
({ node } = (0, node_1.chooseNodeUrl)({
|
|
239
|
+
({ node, allowSimpleName } = (0, node_1.chooseNodeUrl)({
|
|
240
|
+
product: answers.product,
|
|
241
|
+
studio: answers.studio,
|
|
225
242
|
node,
|
|
243
|
+
allowSimpleName,
|
|
226
244
|
}));
|
|
227
245
|
await initSubgraphFromContract.bind(this)({
|
|
228
246
|
protocolInstance: answers.protocolInstance,
|
|
247
|
+
allowSimpleName,
|
|
229
248
|
subgraphName: answers.subgraphName,
|
|
230
249
|
directory: answers.directory,
|
|
231
250
|
abi: answers.abi,
|
|
@@ -256,10 +275,31 @@ InitCommand.flags = {
|
|
|
256
275
|
protocol: core_1.Flags.string({
|
|
257
276
|
options: protocolChoices,
|
|
258
277
|
}),
|
|
278
|
+
product: core_1.Flags.string({
|
|
279
|
+
summary: 'Selects the product for which to initialize.',
|
|
280
|
+
options: ['subgraph-studio', 'hosted-service'],
|
|
281
|
+
deprecated: {
|
|
282
|
+
message: '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/',
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
studio: core_1.Flags.boolean({
|
|
286
|
+
summary: 'Shortcut for "--product subgraph-studio".',
|
|
287
|
+
exclusive: ['product'],
|
|
288
|
+
deprecated: {
|
|
289
|
+
message: '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/',
|
|
290
|
+
},
|
|
291
|
+
}),
|
|
259
292
|
node: core_1.Flags.string({
|
|
260
293
|
summary: 'Graph node for which to initialize.',
|
|
261
294
|
char: 'g',
|
|
262
295
|
}),
|
|
296
|
+
'allow-simple-name': core_1.Flags.boolean({
|
|
297
|
+
description: 'Use a subgraph name without a prefix.',
|
|
298
|
+
default: false,
|
|
299
|
+
deprecated: {
|
|
300
|
+
message: '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/',
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
263
303
|
'from-contract': core_1.Flags.string({
|
|
264
304
|
description: 'Creates a scaffold based on an existing contract.',
|
|
265
305
|
exclusive: ['from-example'],
|
|
@@ -314,14 +354,31 @@ InitCommand.flags = {
|
|
|
314
354
|
}),
|
|
315
355
|
};
|
|
316
356
|
exports.default = InitCommand;
|
|
317
|
-
async function processFromExampleInitForm({ directory: initDirectory, subgraphName: initSubgraphName, }) {
|
|
357
|
+
async function processFromExampleInitForm({ directory: initDirectory, subgraphName: initSubgraphName, allowSimpleName: initAllowSimpleName, }) {
|
|
318
358
|
try {
|
|
319
359
|
const { subgraphName } = await gluegun_1.prompt.ask([
|
|
320
360
|
{
|
|
321
361
|
type: 'input',
|
|
322
362
|
name: 'subgraphName',
|
|
363
|
+
// TODO: is defaulting to studio ok?
|
|
323
364
|
message: () => 'Subgraph slug',
|
|
324
365
|
initial: initSubgraphName,
|
|
366
|
+
validate: name => {
|
|
367
|
+
try {
|
|
368
|
+
(0, subgraph_1.validateSubgraphName)(name, {
|
|
369
|
+
allowSimpleName: initAllowSimpleName,
|
|
370
|
+
});
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
return `${e.message}
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
|
|
378
|
+
$ graph init ${os_1.default.userInfo().username}/${name}
|
|
379
|
+
$ graph init ${name} --allow-simple-name`;
|
|
380
|
+
}
|
|
381
|
+
},
|
|
325
382
|
},
|
|
326
383
|
]);
|
|
327
384
|
const { directory } = await gluegun_1.prompt.ask([
|
|
@@ -363,7 +420,7 @@ async function retryWithPrompt(func) {
|
|
|
363
420
|
}
|
|
364
421
|
return undefined;
|
|
365
422
|
}
|
|
366
|
-
async function processInitForm({ protocol: initProtocol, abi: initAbi, abiPath: initAbiPath, directory: initDirectory, contract: initContract, indexEvents: initIndexEvents, fromExample: initFromExample, network: initNetwork, subgraphName: initSubgraphName, contractName: initContractName, startBlock: initStartBlock, spkgPath: initSpkgPath, }) {
|
|
423
|
+
async function processInitForm({ protocol: initProtocol, product: initProduct, studio: initStudio, node: initNode, abi: initAbi, abiPath: initAbiPath, directory: initDirectory, contract: initContract, indexEvents: initIndexEvents, fromExample: initFromExample, network: initNetwork, subgraphName: initSubgraphName, contractName: initContractName, startBlock: initStartBlock, allowSimpleName: initAllowSimpleName, spkgPath: initSpkgPath, }) {
|
|
367
424
|
let abiFromEtherscan = undefined;
|
|
368
425
|
try {
|
|
369
426
|
const { protocol } = await gluegun_1.prompt.ask({
|
|
@@ -384,12 +441,60 @@ async function processInitForm({ protocol: initProtocol, abi: initAbi, abiPath:
|
|
|
384
441
|
const protocolInstance = new protocols_1.default(protocol);
|
|
385
442
|
const isSubstreams = protocol === 'substreams';
|
|
386
443
|
initDebugger.extend('processInitForm')('isSubstreams: %O', isSubstreams);
|
|
444
|
+
const { product } = await gluegun_1.prompt.ask([
|
|
445
|
+
{
|
|
446
|
+
type: 'select',
|
|
447
|
+
name: 'product',
|
|
448
|
+
message: 'Product for which to initialize',
|
|
449
|
+
choices: ['subgraph-studio', 'hosted-service'],
|
|
450
|
+
skip: protocol === 'arweave' ||
|
|
451
|
+
protocol === 'cosmos' ||
|
|
452
|
+
protocol === 'near' ||
|
|
453
|
+
initProduct === 'subgraph-studio' ||
|
|
454
|
+
initProduct === 'hosted-service' ||
|
|
455
|
+
initStudio !== undefined ||
|
|
456
|
+
initNode !== undefined,
|
|
457
|
+
result: value => {
|
|
458
|
+
if (initProduct)
|
|
459
|
+
return initProduct;
|
|
460
|
+
if (initStudio)
|
|
461
|
+
return 'subgraph-studio';
|
|
462
|
+
// For now we only support NEAR subgraphs in the Hosted Service
|
|
463
|
+
if (protocol === 'near') {
|
|
464
|
+
return 'hosted-service';
|
|
465
|
+
}
|
|
466
|
+
if (value == 'subgraph-studio') {
|
|
467
|
+
initAllowSimpleName = true;
|
|
468
|
+
}
|
|
469
|
+
return value;
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
if (product == 'hosted-service') {
|
|
474
|
+
this.error('✖ The hosted service is deprecated', { exit: 1 });
|
|
475
|
+
}
|
|
387
476
|
const { subgraphName } = await gluegun_1.prompt.ask([
|
|
388
477
|
{
|
|
389
478
|
type: 'input',
|
|
390
479
|
name: 'subgraphName',
|
|
391
|
-
message: 'Subgraph slug',
|
|
480
|
+
message: () => (product == 'subgraph-studio' ? 'Subgraph slug' : 'Subgraph name'),
|
|
392
481
|
initial: initSubgraphName,
|
|
482
|
+
validate: name => {
|
|
483
|
+
try {
|
|
484
|
+
(0, subgraph_1.validateSubgraphName)(name, {
|
|
485
|
+
allowSimpleName: initAllowSimpleName,
|
|
486
|
+
});
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
catch (e) {
|
|
490
|
+
return `${e.message}
|
|
491
|
+
|
|
492
|
+
Examples:
|
|
493
|
+
|
|
494
|
+
$ graph init ${os_1.default.userInfo().username}/${name}
|
|
495
|
+
$ graph init ${name} --allow-simple-name`;
|
|
496
|
+
}
|
|
497
|
+
},
|
|
393
498
|
},
|
|
394
499
|
]);
|
|
395
500
|
const { directory } = await gluegun_1.prompt.ask([
|
|
@@ -403,7 +508,7 @@ async function processInitForm({ protocol: initProtocol, abi: initAbi, abiPath:
|
|
|
403
508
|
: true,
|
|
404
509
|
},
|
|
405
510
|
]);
|
|
406
|
-
let choices = (await AVAILABLE_NETWORKS())?.['studio'];
|
|
511
|
+
let choices = (await AVAILABLE_NETWORKS())?.[product === 'subgraph-studio' ? 'studio' : 'hostedService'];
|
|
407
512
|
if (!choices) {
|
|
408
513
|
this.error('Unable to fetch available networks from API. Please report this issue. As a workaround you can pass `--network` flag from the available networks: https://thegraph.com/docs/en/developing/supported-networks', { exit: 1 });
|
|
409
514
|
}
|
|
@@ -591,8 +696,10 @@ async function processInitForm({ protocol: initProtocol, abi: initAbi, abiPath:
|
|
|
591
696
|
protocolInstance,
|
|
592
697
|
subgraphName,
|
|
593
698
|
directory,
|
|
699
|
+
studio: product === 'subgraph-studio',
|
|
594
700
|
startBlock,
|
|
595
701
|
fromExample: !!initFromExample,
|
|
702
|
+
product,
|
|
596
703
|
network,
|
|
597
704
|
contractName,
|
|
598
705
|
contract,
|
|
@@ -619,6 +726,21 @@ const loadAbiFromFile = (ABI, filename) => {
|
|
|
619
726
|
return ABI.load('Contract', filename);
|
|
620
727
|
}
|
|
621
728
|
};
|
|
729
|
+
function revalidateSubgraphName(subgraphName, { allowSimpleName }) {
|
|
730
|
+
// Fail if the subgraph name is invalid
|
|
731
|
+
try {
|
|
732
|
+
(0, subgraph_1.validateSubgraphName)(subgraphName, { allowSimpleName });
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
catch (e) {
|
|
736
|
+
this.error(`${e.message}
|
|
737
|
+
|
|
738
|
+
Examples:
|
|
739
|
+
|
|
740
|
+
$ graph init ${os_1.default.userInfo().username}/${subgraphName}
|
|
741
|
+
$ graph init ${subgraphName} --allow-simple-name`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
622
744
|
// Inspired from: https://github.com/graphprotocol/graph-tooling/issues/1450#issuecomment-1713992618
|
|
623
745
|
async function isInRepo() {
|
|
624
746
|
try {
|
|
@@ -684,7 +806,12 @@ Subgraph ${subgraphName} created in ${relativeDir}
|
|
|
684
806
|
|
|
685
807
|
Make sure to visit the documentation on https://thegraph.com/docs/ for further information.`);
|
|
686
808
|
}
|
|
687
|
-
async function initSubgraphFromExample({ fromExample, subgraphName, directory, skipInstall, skipGit, }, { commands, }) {
|
|
809
|
+
async function initSubgraphFromExample({ fromExample, allowSimpleName, subgraphName, directory, skipInstall, skipGit, }, { commands, }) {
|
|
810
|
+
// Fail if the subgraph name is invalid
|
|
811
|
+
if (!revalidateSubgraphName.bind(this)(subgraphName, { allowSimpleName })) {
|
|
812
|
+
process.exitCode = 1;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
688
815
|
// Fail if the output directory already exists
|
|
689
816
|
if (gluegun_1.filesystem.exists(directory)) {
|
|
690
817
|
this.error(`Directory or file "${directory}" already exists`, { exit: 1 });
|
|
@@ -777,8 +904,13 @@ async function initSubgraphFromExample({ fromExample, subgraphName, directory, s
|
|
|
777
904
|
}
|
|
778
905
|
printNextSteps.bind(this)({ subgraphName, directory }, { commands });
|
|
779
906
|
}
|
|
780
|
-
async function initSubgraphFromContract({ protocolInstance, subgraphName, directory, abi, network, contract, indexEvents, contractName, node, startBlock, spkgPath, skipInstall, skipGit, }, { commands, addContract, }) {
|
|
907
|
+
async function initSubgraphFromContract({ protocolInstance, allowSimpleName, subgraphName, directory, abi, network, contract, indexEvents, contractName, node, startBlock, spkgPath, skipInstall, skipGit, }, { commands, addContract, }) {
|
|
781
908
|
const isSubstreams = protocolInstance.name === 'substreams';
|
|
909
|
+
// Fail if the subgraph name is invalid
|
|
910
|
+
if (!revalidateSubgraphName.bind(this)(subgraphName, { allowSimpleName })) {
|
|
911
|
+
this.exit(1);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
782
914
|
// Fail if the output directory already exists
|
|
783
915
|
if (gluegun_1.filesystem.exists(directory)) {
|
|
784
916
|
this.error(`Directory or file "${directory}" already exists`, { exit: 1 });
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.88.0-alpha-
|
|
2
|
+
"version": "0.88.0-alpha-20241031145713-add2ae3",
|
|
3
3
|
"commands": {
|
|
4
4
|
"add": {
|
|
5
5
|
"id": "add",
|
|
@@ -77,9 +77,37 @@
|
|
|
77
77
|
"char": "h",
|
|
78
78
|
"description": "Show CLI help.",
|
|
79
79
|
"allowNo": false
|
|
80
|
+
},
|
|
81
|
+
"product": {
|
|
82
|
+
"name": "product",
|
|
83
|
+
"type": "option",
|
|
84
|
+
"summary": "Select a product for which to authenticate.",
|
|
85
|
+
"multiple": false,
|
|
86
|
+
"options": [
|
|
87
|
+
"subgraph-studio",
|
|
88
|
+
"hosted-service"
|
|
89
|
+
],
|
|
90
|
+
"deprecated": {
|
|
91
|
+
"message": "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/"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"studio": {
|
|
95
|
+
"name": "studio",
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"summary": "Shortcut for \"--product subgraph-studio\".",
|
|
98
|
+
"allowNo": false,
|
|
99
|
+
"exclusive": [
|
|
100
|
+
"product"
|
|
101
|
+
],
|
|
102
|
+
"deprecated": {
|
|
103
|
+
"message": "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/"
|
|
104
|
+
}
|
|
80
105
|
}
|
|
81
106
|
},
|
|
82
107
|
"args": {
|
|
108
|
+
"node": {
|
|
109
|
+
"name": "node"
|
|
110
|
+
},
|
|
83
111
|
"deploy-key": {
|
|
84
112
|
"name": "deploy-key"
|
|
85
113
|
}
|
|
@@ -317,6 +345,25 @@
|
|
|
317
345
|
"description": "Show CLI help.",
|
|
318
346
|
"allowNo": false
|
|
319
347
|
},
|
|
348
|
+
"product": {
|
|
349
|
+
"name": "product",
|
|
350
|
+
"type": "option",
|
|
351
|
+
"summary": "Select a product for which to authenticate.",
|
|
352
|
+
"multiple": false,
|
|
353
|
+
"options": [
|
|
354
|
+
"subgraph-studio",
|
|
355
|
+
"hosted-service"
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
"studio": {
|
|
359
|
+
"name": "studio",
|
|
360
|
+
"type": "boolean",
|
|
361
|
+
"summary": "Shortcut for \"--product subgraph-studio\".",
|
|
362
|
+
"allowNo": false,
|
|
363
|
+
"exclusive": [
|
|
364
|
+
"product"
|
|
365
|
+
]
|
|
366
|
+
},
|
|
320
367
|
"node": {
|
|
321
368
|
"name": "node",
|
|
322
369
|
"type": "option",
|
|
@@ -416,6 +463,12 @@
|
|
|
416
463
|
"summary": "Networks config file path.",
|
|
417
464
|
"multiple": false,
|
|
418
465
|
"default": "networks.json"
|
|
466
|
+
},
|
|
467
|
+
"from-hosted-service": {
|
|
468
|
+
"name": "from-hosted-service",
|
|
469
|
+
"type": "option",
|
|
470
|
+
"summary": "Hosted service Subgraph Name to deploy to studio.",
|
|
471
|
+
"multiple": false
|
|
419
472
|
}
|
|
420
473
|
},
|
|
421
474
|
"args": {
|
|
@@ -456,6 +509,31 @@
|
|
|
456
509
|
"substreams"
|
|
457
510
|
]
|
|
458
511
|
},
|
|
512
|
+
"product": {
|
|
513
|
+
"name": "product",
|
|
514
|
+
"type": "option",
|
|
515
|
+
"summary": "Selects the product for which to initialize.",
|
|
516
|
+
"multiple": false,
|
|
517
|
+
"options": [
|
|
518
|
+
"subgraph-studio",
|
|
519
|
+
"hosted-service"
|
|
520
|
+
],
|
|
521
|
+
"deprecated": {
|
|
522
|
+
"message": "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/"
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
"studio": {
|
|
526
|
+
"name": "studio",
|
|
527
|
+
"type": "boolean",
|
|
528
|
+
"summary": "Shortcut for \"--product subgraph-studio\".",
|
|
529
|
+
"allowNo": false,
|
|
530
|
+
"exclusive": [
|
|
531
|
+
"product"
|
|
532
|
+
],
|
|
533
|
+
"deprecated": {
|
|
534
|
+
"message": "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/"
|
|
535
|
+
}
|
|
536
|
+
},
|
|
459
537
|
"node": {
|
|
460
538
|
"name": "node",
|
|
461
539
|
"type": "option",
|
|
@@ -463,6 +541,15 @@
|
|
|
463
541
|
"summary": "Graph node for which to initialize.",
|
|
464
542
|
"multiple": false
|
|
465
543
|
},
|
|
544
|
+
"allow-simple-name": {
|
|
545
|
+
"name": "allow-simple-name",
|
|
546
|
+
"type": "boolean",
|
|
547
|
+
"description": "Use a subgraph name without a prefix.",
|
|
548
|
+
"allowNo": false,
|
|
549
|
+
"deprecated": {
|
|
550
|
+
"message": "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/"
|
|
551
|
+
}
|
|
552
|
+
},
|
|
466
553
|
"from-contract": {
|
|
467
554
|
"name": "from-contract",
|
|
468
555
|
"type": "option",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphprotocol/graph-cli",
|
|
3
|
-
"version": "0.88.0-alpha-
|
|
3
|
+
"version": "0.88.0-alpha-20241031145713-add2ae3",
|
|
4
4
|
"description": "CLI for building for and deploying to The Graph",
|
|
5
5
|
"license": "(Apache-2.0 OR MIT)",
|
|
6
6
|
"engines": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@oclif/core": "2.8.6",
|
|
24
24
|
"@oclif/plugin-autocomplete": "^2.3.6",
|
|
25
25
|
"@oclif/plugin-not-found": "^2.4.0",
|
|
26
|
+
"@oclif/plugin-warn-if-update-available": "^3.1.20",
|
|
26
27
|
"@whatwg-node/fetch": "^0.8.4",
|
|
27
28
|
"assemblyscript": "0.19.23",
|
|
28
29
|
"binary-install-raw": "0.0.13",
|
|
@@ -76,8 +77,13 @@
|
|
|
76
77
|
],
|
|
77
78
|
"plugins": [
|
|
78
79
|
"@oclif/plugin-not-found",
|
|
79
|
-
"@oclif/plugin-autocomplete"
|
|
80
|
-
|
|
80
|
+
"@oclif/plugin-autocomplete",
|
|
81
|
+
"@oclif/plugin-warn-if-update-available"
|
|
82
|
+
],
|
|
83
|
+
"warn-if-update-available": {
|
|
84
|
+
"timeoutInDays": 1,
|
|
85
|
+
"message": "<%= config.name %> update available from <%= chalk.yellowBright(config.version) %> to <%= chalk.greenBright(latest) %>."
|
|
86
|
+
}
|
|
81
87
|
},
|
|
82
88
|
"scripts": {
|
|
83
89
|
"build": "tsc -b tsconfig.build.json && oclif manifest && oclif readme && copyfiles -u 1 src/**/*.graphql dist/",
|