UNPKG

11.6 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 var desc = Object.getOwnPropertyDescriptor(m, k);
5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 desc = { enumerable: true, get: function() { return m[k]; } };
7 }
8 Object.defineProperty(o, k2, desc);
9}) : (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 o[k2] = m[k];
12}));
13var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 Object.defineProperty(o, "default", { enumerable: true, value: v });
15}) : function(o, v) {
16 o["default"] = v;
17});
18var __importStar = (this && this.__importStar) || function (mod) {
19 if (mod && mod.__esModule) return mod;
20 var result = {};
21 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 __setModuleDefault(result, mod);
23 return result;
24};
25var __importDefault = (this && this.__importDefault) || function (mod) {
26 return (mod && mod.__esModule) ? mod : { "default": mod };
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29const path_1 = __importDefault(require("path"));
30const fs_extra_1 = __importDefault(require("fs-extra"));
31const graphql = __importStar(require("graphql/language"));
32const immutable_1 = __importDefault(require("immutable"));
33const yaml_1 = __importDefault(require("yaml"));
34const types_1 = require("yaml/types");
35const debug_1 = __importDefault(require("./debug"));
36const validation = __importStar(require("./validation"));
37const subgraphDebug = (0, debug_1.default)('graph-cli:subgraph');
38const throwCombinedError = (filename, errors) => {
39 throw new Error(errors.reduce((msg, e) => `${msg}
40
41 Path: ${e.get('path').size === 0 ? '/' : e.get('path').join(' > ')}
42 ${e.get('message').split('\n').join('\n ')}`, `Error in ${path_1.default.relative(process.cwd(), filename)}:`));
43};
44const buildCombinedWarning = (filename, warnings) => warnings.size > 0
45 ? warnings.reduce((msg, w) => `${msg}
46
47 Path: ${w.get('path').size === 0 ? '/' : w.get('path').join(' > ')}
48 ${w.get('message').split('\n').join('\n ')}`, `Warnings in ${path_1.default.relative(process.cwd(), filename)}:`) + '\n'
49 : null;
50class Subgraph {
51 static async validate(data, protocol, { resolveFile }) {
52 subgraphDebug(`Validating Subgraph with protocol "%s"`, protocol);
53 if (protocol.name == null) {
54 return immutable_1.default.fromJS([
55 {
56 path: [],
57 message: `Unable to determine for which protocol manifest file is built for. Ensure you have at least one 'dataSources' and/or 'templates' elements defined in your subgraph.`,
58 },
59 ]);
60 }
61 // Parse the default subgraph schema
62 const schema = graphql.parse(await fs_extra_1.default.readFile(path_1.default.join(__dirname, 'protocols', protocol.name, `manifest.graphql`), 'utf-8'));
63 // Obtain the root `SubgraphManifest` type from the schema
64 const rootType = schema.definitions.find(definition => {
65 // @ts-expect-error TODO: name field does not exist on definition, really?
66 return definition.name.value === 'SubgraphManifest';
67 });
68 // Validate the subgraph manifest using this schema
69 return validation.validateManifest(data, rootType, schema, protocol, { resolveFile });
70 }
71 static validateSchema(manifest, { resolveFile }) {
72 const filename = resolveFile(manifest.getIn(['schema', 'file']));
73 const validationErrors = validation.validateSchema(filename);
74 let errors;
75 if (validationErrors.size > 0) {
76 errors = validationErrors.groupBy(error => error.get('entity')).sort();
77 const msg = errors.reduce((msg, errors, entity) => {
78 errors = errors.groupBy((error) => error.get('directive'));
79 const inner_msgs = errors.reduce((msg, errors, directive) => {
80 return `${msg}${directive
81 ? `
82 ${directive}:`
83 : ''}
84 ${errors
85 .map(error => error.get('message').split('\n').join('\n '))
86 .map(msg => `${directive ? ' ' : ''}- ${msg}`)
87 .join('\n ')}`;
88 }, ``);
89 return `${msg}
90
91 ${entity}:${inner_msgs}`;
92 }, `Error in ${path_1.default.relative(process.cwd(), filename)}:`);
93 throw new Error(msg);
94 }
95 }
96 static validateRepository(manifest) {
97 const repository = manifest.get('repository');
98 return /^https:\/\/github\.com\/graphprotocol\/graph-tooling?$/.test(repository) ||
99 // For legacy reasons, we should error on example subgraphs
100 /^https:\/\/github\.com\/graphprotocol\/example-subgraphs?$/.test(repository)
101 ? immutable_1.default.List().push(immutable_1.default.fromJS({
102 path: ['repository'],
103 message: `\
104The repository is still set to ${repository}.
105Please replace it with a link to your subgraph source code.`,
106 }))
107 : immutable_1.default.List();
108 }
109 static validateDescription(manifest) {
110 // TODO: Maybe implement this in the future for each protocol example description
111 return manifest.get('description', '').startsWith('Gravatar for ')
112 ? immutable_1.default.List().push(immutable_1.default.fromJS({
113 path: ['description'],
114 message: `\
115The description is still the one from the example subgraph.
116Please update it to tell users more about your subgraph.`,
117 }))
118 : immutable_1.default.List();
119 }
120 static validateHandlers(manifest, protocol, protocolSubgraph) {
121 return manifest
122 .get('dataSources')
123 .filter((dataSource) => protocol.isValidKindName(dataSource.get('kind')))
124 .reduce((errors, dataSource, dataSourceIndex) => {
125 const path = ['dataSources', dataSourceIndex, 'mapping'];
126 const mapping = dataSource.get('mapping');
127 const handlerTypes = protocolSubgraph.handlerTypes();
128 subgraphDebug('Validating dataSource "%s" handlers with %d handlers types defined for protocol', dataSource.get('name'), handlerTypes.size);
129 if (handlerTypes.size == 0) {
130 return errors;
131 }
132 const areAllHandlersEmpty = handlerTypes
133 .map((handlerType) => mapping.get(handlerType, immutable_1.default.List()))
134 .every((handlers) => handlers.isEmpty());
135 const handlerNamesWithoutLast = handlerTypes.pop().join(', ');
136 return areAllHandlersEmpty
137 ? errors.push(immutable_1.default.fromJS({
138 path,
139 message: `\
140Mapping has no ${handlerNamesWithoutLast} or ${handlerTypes.get(-1)}.
141At least one such handler must be defined.`,
142 }))
143 : errors;
144 }, immutable_1.default.List());
145 }
146 static validateContractValues(manifest, protocol) {
147 if (!protocol.hasContract()) {
148 return immutable_1.default.List();
149 }
150 return validation.validateContractValues(manifest, protocol);
151 }
152 // Validate that data source names are unique, so they don't overwrite each other.
153 static validateUniqueDataSourceNames(manifest) {
154 const names = [];
155 return manifest
156 .get('dataSources')
157 .reduce((errors, dataSource, dataSourceIndex) => {
158 const path = ['dataSources', dataSourceIndex, 'name'];
159 const name = dataSource.get('name');
160 if (names.includes(name)) {
161 errors = errors.push(immutable_1.default.fromJS({
162 path,
163 message: `\
164More than one data source named '${name}', data source names must be unique.`,
165 }));
166 }
167 names.push(name);
168 return errors;
169 }, immutable_1.default.List());
170 }
171 static validateUniqueTemplateNames(manifest) {
172 const names = [];
173 return manifest
174 .get('templates', immutable_1.default.List())
175 .reduce((errors, template, templateIndex) => {
176 const path = ['templates', templateIndex, 'name'];
177 const name = template.get('name');
178 if (names.includes(name)) {
179 errors = errors.push(immutable_1.default.fromJS({
180 path,
181 message: `\
182More than one template named '${name}', template names must be unique.`,
183 }));
184 }
185 names.push(name);
186 return errors;
187 }, immutable_1.default.List());
188 }
189 static dump(manifest) {
190 types_1.strOptions.fold.lineWidth = 90;
191 // @ts-expect-error TODO: plain is the value behind the TS constant
192 types_1.strOptions.defaultType = 'PLAIN';
193 return yaml_1.default.stringify(manifest.toJS());
194 }
195 static async load(filename, { protocol, skipValidation } = {
196 skipValidation: false,
197 }) {
198 // Load and validate the manifest
199 let data = null;
200 let has_file_data_sources = false;
201 if (filename.match(/.js$/)) {
202 data = require(path_1.default.resolve(filename));
203 }
204 else {
205 const raw_data = await fs_extra_1.default.readFile(filename, 'utf-8');
206 has_file_data_sources = raw_data.includes('kind: file');
207 data = yaml_1.default.parse(raw_data);
208 }
209 // Helper to resolve files relative to the subgraph manifest
210 const resolveFile = maybeRelativeFile => path_1.default.resolve(path_1.default.dirname(filename), maybeRelativeFile);
211 // TODO: Validation for file data sources
212 if (!has_file_data_sources) {
213 const manifestErrors = await Subgraph.validate(data, protocol, { resolveFile });
214 if (manifestErrors.size > 0) {
215 throwCombinedError(filename, manifestErrors);
216 }
217 }
218 const manifest = immutable_1.default.fromJS(data);
219 // Validate the schema
220 Subgraph.validateSchema(manifest, { resolveFile });
221 // Perform other validations
222 const protocolSubgraph = protocol.getSubgraph({
223 manifest,
224 resolveFile,
225 });
226 const errors = skipValidation
227 ? immutable_1.default.List()
228 : immutable_1.default.List.of(...protocolSubgraph.validateManifest(), ...Subgraph.validateContractValues(manifest, protocol), ...Subgraph.validateUniqueDataSourceNames(manifest), ...Subgraph.validateUniqueTemplateNames(manifest), ...Subgraph.validateHandlers(manifest, protocol, protocolSubgraph));
229 if (errors.size > 0) {
230 throwCombinedError(filename, errors);
231 }
232 // Perform warning validations
233 const warnings = skipValidation
234 ? immutable_1.default.List()
235 : immutable_1.default.List.of(...Subgraph.validateRepository(manifest), ...Subgraph.validateDescription(manifest));
236 return {
237 result: manifest,
238 warning: warnings.size > 0 ? buildCombinedWarning(filename, warnings) : null,
239 };
240 }
241 static async write(manifest, filename) {
242 await fs_extra_1.default.writeFile(filename, Subgraph.dump(manifest));
243 }
244}
245exports.default = Subgraph;