wgc 0.84.2 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/commands/grpc-service/commands/generate.js +85 -0
- package/dist/src/commands/grpc-service/commands/generate.js.map +1 -0
- package/dist/src/commands/{router/plugin/commands/test.d.ts → grpc-service/index.d.ts} +1 -1
- package/dist/src/commands/grpc-service/index.js +9 -0
- package/dist/src/commands/grpc-service/index.js.map +1 -0
- package/dist/src/commands/index.js +4 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/router/commands/compose.js +315 -241
- package/dist/src/commands/router/commands/compose.js.map +1 -1
- package/dist/src/commands/router/commands/plugin/commands/build.d.ts +4 -0
- package/dist/src/commands/router/commands/plugin/commands/build.js.map +1 -0
- package/dist/src/commands/router/{plugin → commands/plugin}/commands/init.d.ts +1 -1
- package/dist/src/commands/router/commands/plugin/commands/init.js.map +1 -0
- package/dist/src/commands/router/commands/plugin/commands/test.d.ts +4 -0
- package/dist/src/commands/router/commands/plugin/commands/test.js.map +1 -0
- package/dist/src/commands/router/{plugin → commands/plugin}/helper.js +4 -1
- package/dist/src/commands/router/commands/plugin/helper.js.map +1 -0
- package/dist/src/commands/router/commands/plugin/index.js.map +1 -0
- package/dist/src/commands/router/{plugin → commands/plugin}/templates/plugin.js +4 -4
- package/dist/src/commands/router/commands/plugin/templates/plugin.js.map +1 -0
- package/dist/src/commands/router/{plugin → commands/plugin}/templates/project.js +6 -6
- package/dist/src/commands/router/commands/plugin/templates/project.js.map +1 -0
- package/dist/src/commands/router/{plugin → commands/plugin}/toolchain.js +1 -1
- package/dist/src/commands/router/commands/plugin/toolchain.js.map +1 -0
- package/dist/src/commands/router/index.js +1 -1
- package/dist/src/commands/router/index.js.map +1 -1
- package/dist/src/core/client/client.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/src/commands/router/plugin/commands/build.js.map +0 -1
- package/dist/src/commands/router/plugin/commands/init.js.map +0 -1
- package/dist/src/commands/router/plugin/commands/test.js.map +0 -1
- package/dist/src/commands/router/plugin/helper.js.map +0 -1
- package/dist/src/commands/router/plugin/index.js.map +0 -1
- package/dist/src/commands/router/plugin/templates/plugin.js.map +0 -1
- package/dist/src/commands/router/plugin/templates/project.js.map +0 -1
- package/dist/src/commands/router/plugin/toolchain.js.map +0 -1
- /package/dist/src/commands/{router/plugin/index.d.ts → grpc-service/commands/generate.d.ts} +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/commands/build.js +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/commands/init.js +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/commands/test.js +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/helper.d.ts +0 -0
- /package/dist/src/commands/router/{plugin/commands/build.d.ts → commands/plugin/index.d.ts} +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/index.js +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/templates/plugin.d.ts +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/templates/project.d.ts +0 -0
- /package/dist/src/commands/router/{plugin → commands/plugin}/toolchain.d.ts +0 -0
|
@@ -13,6 +13,53 @@ import { FeatureFlagRouterExecutionConfig, FeatureFlagRouterExecutionConfigs, GR
|
|
|
13
13
|
import Table from 'cli-table3';
|
|
14
14
|
import { ROUTER_COMPATIBILITY_VERSION_ONE } from '@wundergraph/composition';
|
|
15
15
|
import { composeSubgraphs, introspectSubgraph } from '../../../utils.js';
|
|
16
|
+
function constructRouterSubgraph(result, s, index) {
|
|
17
|
+
const subgraphConfig = result.subgraphConfigBySubgraphName.get(s.name);
|
|
18
|
+
const schema = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.schema;
|
|
19
|
+
const configurationDataByTypeName = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.configurationDataByTypeName;
|
|
20
|
+
if (s.kind === SubgraphKind.Standard) {
|
|
21
|
+
const composedSubgraph = {
|
|
22
|
+
kind: SubgraphKind.Standard,
|
|
23
|
+
id: `${index}`,
|
|
24
|
+
name: s.name,
|
|
25
|
+
url: s.routingUrl,
|
|
26
|
+
sdl: s.sdl,
|
|
27
|
+
subscriptionUrl: s.subscriptionUrl,
|
|
28
|
+
subscriptionProtocol: s.subscriptionProtocol,
|
|
29
|
+
websocketSubprotocol: s.websocketSubprotocol,
|
|
30
|
+
schema,
|
|
31
|
+
configurationDataByTypeName,
|
|
32
|
+
};
|
|
33
|
+
return composedSubgraph;
|
|
34
|
+
}
|
|
35
|
+
if (s.kind === SubgraphKind.Plugin) {
|
|
36
|
+
const composedSubgraphPlugin = {
|
|
37
|
+
kind: SubgraphKind.Plugin,
|
|
38
|
+
id: `${index}`,
|
|
39
|
+
name: s.name,
|
|
40
|
+
url: `http://localhost:3000/plugin/${index}`,
|
|
41
|
+
sdl: s.sdl,
|
|
42
|
+
mapping: s.mapping,
|
|
43
|
+
protoSchema: s.protoSchema,
|
|
44
|
+
version: s.version,
|
|
45
|
+
schema,
|
|
46
|
+
configurationDataByTypeName,
|
|
47
|
+
};
|
|
48
|
+
return composedSubgraphPlugin;
|
|
49
|
+
}
|
|
50
|
+
const composedSubgraphGRPC = {
|
|
51
|
+
kind: SubgraphKind.GRPC,
|
|
52
|
+
id: `${index}`,
|
|
53
|
+
name: s.name,
|
|
54
|
+
sdl: s.sdl,
|
|
55
|
+
url: s.routingUrl,
|
|
56
|
+
protoSchema: s.protoSchema,
|
|
57
|
+
mapping: s.mapping,
|
|
58
|
+
schema,
|
|
59
|
+
configurationDataByTypeName,
|
|
60
|
+
};
|
|
61
|
+
return composedSubgraphGRPC;
|
|
62
|
+
}
|
|
16
63
|
export default (opts) => {
|
|
17
64
|
const command = new Command('compose');
|
|
18
65
|
command.description('Generates a router config from a local composition file. This makes it easy to test your router without a control-plane connection. For production, please use the "router fetch" command');
|
|
@@ -20,7 +67,6 @@ export default (opts) => {
|
|
|
20
67
|
command.option('-o, --out [string]', 'Destination file for the router config.');
|
|
21
68
|
command.option('--suppress-warnings', 'This flag suppresses any warnings produced by composition.');
|
|
22
69
|
command.action(async (options) => {
|
|
23
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
24
70
|
const inputFile = resolve(options.input);
|
|
25
71
|
const inputFileLocation = dirname(inputFile);
|
|
26
72
|
if (!existsSync(inputFile)) {
|
|
@@ -29,89 +75,9 @@ export default (opts) => {
|
|
|
29
75
|
const fileContent = (await readFile(inputFile)).toString();
|
|
30
76
|
const config = yaml.load(fileContent);
|
|
31
77
|
const subgraphs = [];
|
|
32
|
-
for (const [index,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!s.plugin.path) {
|
|
36
|
-
program.error(pc.red(pc.bold(`The plugin path is missing in the input file. Please check the path and try again.`)));
|
|
37
|
-
}
|
|
38
|
-
if (!existsSync(s.plugin.path)) {
|
|
39
|
-
program.error(pc.red(pc.bold(`The plugin path '${pc.bold(s.plugin.path)}' does not exist. Please check the path and try again.`)));
|
|
40
|
-
}
|
|
41
|
-
if (!s.plugin.version) {
|
|
42
|
-
program.error(pc.red(pc.bold(`The plugin version is missing in the input file. Please check the version and try again.`)));
|
|
43
|
-
}
|
|
44
|
-
// Check if valid semver
|
|
45
|
-
if (!semver.valid(s.plugin.version)) {
|
|
46
|
-
program.error(pc.red(pc.bold(`The plugin version '${pc.bold(s.plugin.version)}' is not a valid semver. Please check the version and try again.`)));
|
|
47
|
-
}
|
|
48
|
-
const pluginName = basename(s.plugin.path);
|
|
49
|
-
// Check if a plugin with the same name already exists
|
|
50
|
-
if (subgraphs.some((sg) => sg.kind === SubgraphKind.Plugin && sg.name === pluginName)) {
|
|
51
|
-
program.error(pc.red(pc.bold(`A plugin with the name '${pc.bold(pluginName)}' is already registered. Plugin names must be unique.`)));
|
|
52
|
-
}
|
|
53
|
-
const mappingFilePath = resolve(s.plugin.path, 'generated', 'mapping.json');
|
|
54
|
-
const mappingFile = await readFile(mappingFilePath, 'utf8');
|
|
55
|
-
const schemaFilePath = resolve(s.plugin.path, 'src', 'schema.graphql');
|
|
56
|
-
const sdl = await readFile(schemaFilePath, 'utf8');
|
|
57
|
-
const protoSchemaFilePath = resolve(s.plugin.path, 'generated', 'service.proto');
|
|
58
|
-
const protoSchema = await readFile(protoSchemaFilePath, 'utf8');
|
|
59
|
-
subgraphs.push({
|
|
60
|
-
kind: SubgraphKind.Plugin,
|
|
61
|
-
name: pluginName,
|
|
62
|
-
protoSchema,
|
|
63
|
-
version: s.plugin.version,
|
|
64
|
-
sdl,
|
|
65
|
-
mapping: GRPCMapping.fromJsonString(mappingFile),
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// The subgraph name is required
|
|
70
|
-
if (!s.name) {
|
|
71
|
-
program.error(pc.red(pc.bold(`The subgraph name is required for subgraph at index ${index}. Please provide a name and try again.`)));
|
|
72
|
-
}
|
|
73
|
-
const url = (_b = (_a = s.introspection) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : s.routing_url;
|
|
74
|
-
// Check if a subgraph with the same name already exists
|
|
75
|
-
if (subgraphs.some((sg) => sg.name === s.name)) {
|
|
76
|
-
program.error(pc.red(pc.bold(`A subgraph with the name '${pc.bold(s.name)}' is already registered. Subgraph names must be unique.`)));
|
|
77
|
-
}
|
|
78
|
-
let schemaSDL = '';
|
|
79
|
-
// The GraphQL schema is provided in the input file
|
|
80
|
-
if ((_c = s.schema) === null || _c === void 0 ? void 0 : _c.file) {
|
|
81
|
-
const schemaFile = resolve(inputFileLocation, s.schema.file);
|
|
82
|
-
const sdl = (await readFile(schemaFile)).toString();
|
|
83
|
-
schemaSDL = sdl;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// The GraphQL schema is not provided in the input file, so we need to introspect it
|
|
87
|
-
try {
|
|
88
|
-
const result = await introspectSubgraph({
|
|
89
|
-
subgraphURL: url,
|
|
90
|
-
additionalHeaders: Object.entries((_e = (_d = s.introspection) === null || _d === void 0 ? void 0 : _d.headers) !== null && _e !== void 0 ? _e : {}).map(([key, value]) => ({
|
|
91
|
-
key,
|
|
92
|
-
value,
|
|
93
|
-
})),
|
|
94
|
-
rawIntrospection: (_f = s.introspection) === null || _f === void 0 ? void 0 : _f.raw,
|
|
95
|
-
});
|
|
96
|
-
if (!result.success) {
|
|
97
|
-
program.error(`Could not introspect subgraph ${s.name}, URL: ${url}: ${(_g = result.errorMessage) !== null && _g !== void 0 ? _g : 'failed'}`);
|
|
98
|
-
}
|
|
99
|
-
schemaSDL = result.sdl;
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
program.error(`Could not introspect subgraph ${s.name}, URL: ${url}: ${e.message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
subgraphs.push({
|
|
106
|
-
kind: SubgraphKind.Standard,
|
|
107
|
-
name: s.name,
|
|
108
|
-
sdl: schemaSDL,
|
|
109
|
-
subscriptionUrl: ((_h = s.subscription) === null || _h === void 0 ? void 0 : _h.url) || s.routing_url,
|
|
110
|
-
subscriptionProtocol: ((_j = s.subscription) === null || _j === void 0 ? void 0 : _j.protocol) || 'ws',
|
|
111
|
-
websocketSubprotocol: ((_k = s.subscription) === null || _k === void 0 ? void 0 : _k.protocol) === 'ws' ? (_l = s.subscription) === null || _l === void 0 ? void 0 : _l.websocketSubprotocol : 'auto',
|
|
112
|
-
routingUrl: normalizeURL(s.routing_url),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
78
|
+
for (const [index, subgraphConfig] of config.subgraphs.entries()) {
|
|
79
|
+
const metadata = await toSubgraphMetadata(inputFileLocation, index, subgraphConfig, subgraphs);
|
|
80
|
+
subgraphs.push(metadata);
|
|
115
81
|
}
|
|
116
82
|
const result = composeSubgraphs(subgraphs.map((s, index) => {
|
|
117
83
|
var _a;
|
|
@@ -162,166 +128,11 @@ export default (opts) => {
|
|
|
162
128
|
// @TODO get router compatibility version programmatically
|
|
163
129
|
routerCompatibilityVersion: ROUTER_COMPATIBILITY_VERSION_ONE,
|
|
164
130
|
schemaVersionId: 'static',
|
|
165
|
-
subgraphs: subgraphs.map((s, index) =>
|
|
166
|
-
const subgraphConfig = result.subgraphConfigBySubgraphName.get(s.name);
|
|
167
|
-
const schema = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.schema;
|
|
168
|
-
const configurationDataByTypeName = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.configurationDataByTypeName;
|
|
169
|
-
if (s.kind === SubgraphKind.Standard) {
|
|
170
|
-
const composedSubgraph = {
|
|
171
|
-
kind: SubgraphKind.Standard,
|
|
172
|
-
id: `${index}`,
|
|
173
|
-
name: s.name,
|
|
174
|
-
url: s.routingUrl,
|
|
175
|
-
sdl: s.sdl,
|
|
176
|
-
subscriptionUrl: s.subscriptionUrl,
|
|
177
|
-
subscriptionProtocol: s.subscriptionProtocol,
|
|
178
|
-
websocketSubprotocol: s.websocketSubprotocol,
|
|
179
|
-
schema,
|
|
180
|
-
configurationDataByTypeName,
|
|
181
|
-
};
|
|
182
|
-
return composedSubgraph;
|
|
183
|
-
}
|
|
184
|
-
const composedSubgraphPlugin = {
|
|
185
|
-
kind: SubgraphKind.Plugin,
|
|
186
|
-
id: `${index}`,
|
|
187
|
-
name: s.name,
|
|
188
|
-
url: `http://localhost:3000/plugin/${index}`,
|
|
189
|
-
sdl: s.sdl,
|
|
190
|
-
mapping: s.mapping,
|
|
191
|
-
protoSchema: s.protoSchema,
|
|
192
|
-
version: s.version,
|
|
193
|
-
schema,
|
|
194
|
-
configurationDataByTypeName,
|
|
195
|
-
};
|
|
196
|
-
return composedSubgraphPlugin;
|
|
197
|
-
}),
|
|
131
|
+
subgraphs: subgraphs.map((s, index) => constructRouterSubgraph(result, s, index)),
|
|
198
132
|
});
|
|
199
133
|
routerConfig.version = randomUUID();
|
|
200
134
|
if (config.feature_flags && config.feature_flags.length > 0) {
|
|
201
|
-
const ffConfigs =
|
|
202
|
-
// @TODO This logic should exist only once in the shared package and reused across
|
|
203
|
-
// control-plane and cli
|
|
204
|
-
for (const ff of config.feature_flags) {
|
|
205
|
-
const featureSubgraphs = [];
|
|
206
|
-
const standardSubgraphs = config.subgraphs.filter((ss) => !('plugin' in ss));
|
|
207
|
-
// Process each subgraph for this feature flag
|
|
208
|
-
for (const s of standardSubgraphs) {
|
|
209
|
-
// Check if this subgraph is overridden by a feature graph
|
|
210
|
-
const featureSubgraph = ff.feature_graphs.find((ffs) => ffs.subgraph_name === s.name);
|
|
211
|
-
if (featureSubgraph) {
|
|
212
|
-
// This subgraph is overridden by a feature graph
|
|
213
|
-
const url = (_o = (_m = featureSubgraph.introspection) === null || _m === void 0 ? void 0 : _m.url) !== null && _o !== void 0 ? _o : featureSubgraph.routing_url;
|
|
214
|
-
let schemaSDL = '';
|
|
215
|
-
if ((_p = featureSubgraph.schema) === null || _p === void 0 ? void 0 : _p.file) {
|
|
216
|
-
const schemaFile = resolve(inputFileLocation, featureSubgraph.schema.file);
|
|
217
|
-
schemaSDL = await readFile(schemaFile, 'utf8');
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
try {
|
|
221
|
-
const result = await introspectSubgraph({
|
|
222
|
-
subgraphURL: url,
|
|
223
|
-
additionalHeaders: Object.entries((_r = (_q = featureSubgraph.introspection) === null || _q === void 0 ? void 0 : _q.headers) !== null && _r !== void 0 ? _r : {}).map(([key, value]) => ({
|
|
224
|
-
key,
|
|
225
|
-
value,
|
|
226
|
-
})),
|
|
227
|
-
rawIntrospection: (_s = featureSubgraph.introspection) === null || _s === void 0 ? void 0 : _s.raw,
|
|
228
|
-
});
|
|
229
|
-
if (!result.success) {
|
|
230
|
-
program.error(`Could not introspect feature-graph subgraph ${featureSubgraph.name}, URL: ${url}: ${(_t = result.errorMessage) !== null && _t !== void 0 ? _t : 'failed'}`);
|
|
231
|
-
}
|
|
232
|
-
schemaSDL = result.sdl;
|
|
233
|
-
}
|
|
234
|
-
catch (e) {
|
|
235
|
-
program.error(`Could not introspect feature-graph subgraph ${featureSubgraph.name}, URL: ${url}: ${e.message}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
featureSubgraphs.push({
|
|
239
|
-
kind: SubgraphKind.Standard,
|
|
240
|
-
name: featureSubgraph.name,
|
|
241
|
-
sdl: schemaSDL,
|
|
242
|
-
routingUrl: featureSubgraph.routing_url,
|
|
243
|
-
subscriptionUrl: ((_u = featureSubgraph.subscription) === null || _u === void 0 ? void 0 : _u.url) || featureSubgraph.routing_url,
|
|
244
|
-
subscriptionProtocol: ((_v = featureSubgraph.subscription) === null || _v === void 0 ? void 0 : _v.protocol) || 'ws',
|
|
245
|
-
websocketSubprotocol: ((_w = featureSubgraph.subscription) === null || _w === void 0 ? void 0 : _w.protocol) === 'ws'
|
|
246
|
-
? ((_x = featureSubgraph.subscription) === null || _x === void 0 ? void 0 : _x.websocketSubprotocol) || 'auto'
|
|
247
|
-
: 'auto',
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
// Use the base subgraph as is
|
|
252
|
-
// Find the corresponding metadata in the original subgraphs array
|
|
253
|
-
const originalSubgraph = subgraphs.find((sub) => sub.kind === SubgraphKind.Standard && sub.name === s.name);
|
|
254
|
-
if (originalSubgraph) {
|
|
255
|
-
featureSubgraphs.push(originalSubgraph);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const featureResult = composeSubgraphs(featureSubgraphs.map((s) => ({
|
|
260
|
-
name: s.name,
|
|
261
|
-
url: normalizeURL(s.routingUrl),
|
|
262
|
-
definitions: parse(s.sdl),
|
|
263
|
-
})));
|
|
264
|
-
if (!featureResult.success) {
|
|
265
|
-
const compositionErrorsTable = new Table({
|
|
266
|
-
head: [pc.bold(pc.white('ERROR_MESSAGE'))],
|
|
267
|
-
colWidths: [120],
|
|
268
|
-
wordWrap: true,
|
|
269
|
-
});
|
|
270
|
-
console.log(pc.red(`We found composition errors, while composing the feature flag ${pc.italic(ff.name)}.\n${pc.bold('Please check the errors below:')}`));
|
|
271
|
-
for (const compositionError of featureResult.errors) {
|
|
272
|
-
compositionErrorsTable.push([compositionError.message]);
|
|
273
|
-
}
|
|
274
|
-
console.log(compositionErrorsTable.toString());
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
if (!options.suppressWarnings && featureResult.warnings.length > 0) {
|
|
278
|
-
const compositionWarningsTable = new Table({
|
|
279
|
-
head: [pc.bold(pc.white('WARNING_MESSAGE'))],
|
|
280
|
-
colWidths: [120],
|
|
281
|
-
wordWrap: true,
|
|
282
|
-
});
|
|
283
|
-
console.log(pc.yellow(`The following warnings were produced while composing the feature flag ${pc.italic(ff.name)}:`));
|
|
284
|
-
for (const warning of featureResult.warnings) {
|
|
285
|
-
compositionWarningsTable.push([warning.message]);
|
|
286
|
-
}
|
|
287
|
-
console.log(compositionWarningsTable.toString());
|
|
288
|
-
}
|
|
289
|
-
const featureFederatedClientSDL = featureResult.shouldIncludeClientSchema
|
|
290
|
-
? printSchema(featureResult.federatedGraphClientSchema)
|
|
291
|
-
: '';
|
|
292
|
-
const featureRouterConfig = buildRouterConfig({
|
|
293
|
-
federatedClientSDL: featureFederatedClientSDL,
|
|
294
|
-
federatedSDL: printSchemaWithDirectives(featureResult.federatedGraphSchema),
|
|
295
|
-
fieldConfigurations: featureResult.fieldConfigurations,
|
|
296
|
-
// @TODO get router compatibility version programmatically
|
|
297
|
-
routerCompatibilityVersion: ROUTER_COMPATIBILITY_VERSION_ONE,
|
|
298
|
-
schemaVersionId: `static`,
|
|
299
|
-
subgraphs: featureSubgraphs.map((s, index) => {
|
|
300
|
-
const subgraphConfig = featureResult.subgraphConfigBySubgraphName.get(s.name);
|
|
301
|
-
const schema = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.schema;
|
|
302
|
-
const configurationDataByTypeName = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.configurationDataByTypeName;
|
|
303
|
-
const composedSubgraph = {
|
|
304
|
-
kind: SubgraphKind.Standard,
|
|
305
|
-
id: `${index}`,
|
|
306
|
-
name: s.name,
|
|
307
|
-
url: s.routingUrl,
|
|
308
|
-
sdl: s.sdl,
|
|
309
|
-
subscriptionUrl: s.subscriptionUrl,
|
|
310
|
-
subscriptionProtocol: s.subscriptionProtocol,
|
|
311
|
-
websocketSubprotocol: s.websocketSubprotocol,
|
|
312
|
-
schema,
|
|
313
|
-
configurationDataByTypeName,
|
|
314
|
-
};
|
|
315
|
-
return composedSubgraph;
|
|
316
|
-
}),
|
|
317
|
-
});
|
|
318
|
-
ffConfigs.configByFeatureFlagName[ff.name] = new FeatureFlagRouterExecutionConfig({
|
|
319
|
-
version: featureRouterConfig.version,
|
|
320
|
-
subgraphs: featureRouterConfig.subgraphs,
|
|
321
|
-
engineConfig: featureRouterConfig.engineConfig,
|
|
322
|
-
});
|
|
323
|
-
ffConfigs.configByFeatureFlagName[ff.name].version = randomUUID();
|
|
324
|
-
}
|
|
135
|
+
const ffConfigs = await buildFeatureFlagsConfig(config, inputFileLocation, subgraphs, options);
|
|
325
136
|
routerConfig.featureFlagConfigs = ffConfigs;
|
|
326
137
|
}
|
|
327
138
|
if (options.out) {
|
|
@@ -334,4 +145,267 @@ export default (opts) => {
|
|
|
334
145
|
});
|
|
335
146
|
return command;
|
|
336
147
|
};
|
|
148
|
+
function toSubgraphMetadata(inputFileLocation, index, subgraphConfig, subgraphs) {
|
|
149
|
+
if ('plugin' in subgraphConfig) {
|
|
150
|
+
return toSubgraphMetadataPlugin(subgraphConfig, subgraphs);
|
|
151
|
+
}
|
|
152
|
+
if ('grpc' in subgraphConfig) {
|
|
153
|
+
return toSubgraphMetadataGRPC(subgraphConfig);
|
|
154
|
+
}
|
|
155
|
+
return toSubgraphMetadataStandard(inputFileLocation, index, subgraphConfig, subgraphs);
|
|
156
|
+
}
|
|
157
|
+
async function toSubgraphMetadataGRPC(s) {
|
|
158
|
+
validateGRPCSubgraph(s);
|
|
159
|
+
const mappingFileContent = await readFile(s.grpc.mapping_file, 'utf8');
|
|
160
|
+
const mapping = GRPCMapping.fromJsonString(mappingFileContent);
|
|
161
|
+
const protoSchemaFileContent = await readFile(s.grpc.proto_file, 'utf8');
|
|
162
|
+
const sdl = await readFile(s.grpc.schema_file, 'utf8');
|
|
163
|
+
return {
|
|
164
|
+
kind: SubgraphKind.GRPC,
|
|
165
|
+
name: s.name,
|
|
166
|
+
sdl,
|
|
167
|
+
routingUrl: s.routing_url,
|
|
168
|
+
protoSchema: protoSchemaFileContent,
|
|
169
|
+
mapping,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function toSubgraphMetadataPlugin(s, subgraphs) {
|
|
173
|
+
const pluginName = basename(s.plugin.path);
|
|
174
|
+
if (subgraphs.some((sg) => sg.kind === SubgraphKind.Plugin && sg.name === pluginName)) {
|
|
175
|
+
program.error(pc.red(pc.bold(`A plugin with the name '${pc.bold(pluginName)}' is already registered. Plugin names must be unique.`)));
|
|
176
|
+
}
|
|
177
|
+
validateSubgraphPlugin(s);
|
|
178
|
+
// Check if a plugin with the same name already exists
|
|
179
|
+
const mappingFilePath = resolve(s.plugin.path, 'generated', 'mapping.json');
|
|
180
|
+
const mappingFile = await readFile(mappingFilePath, 'utf8');
|
|
181
|
+
const schemaFilePath = resolve(s.plugin.path, 'src', 'schema.graphql');
|
|
182
|
+
const sdl = await readFile(schemaFilePath, 'utf8');
|
|
183
|
+
const protoSchemaFilePath = resolve(s.plugin.path, 'generated', 'service.proto');
|
|
184
|
+
const protoSchema = await readFile(protoSchemaFilePath, 'utf8');
|
|
185
|
+
return {
|
|
186
|
+
kind: SubgraphKind.Plugin,
|
|
187
|
+
name: pluginName,
|
|
188
|
+
protoSchema,
|
|
189
|
+
version: s.plugin.version,
|
|
190
|
+
sdl,
|
|
191
|
+
mapping: GRPCMapping.fromJsonString(mappingFile),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function toSubgraphMetadataStandard(inputFileLocation, index, s, subgraphs) {
|
|
195
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
196
|
+
// The subgraph name is required
|
|
197
|
+
if (!s.name) {
|
|
198
|
+
program.error(pc.red(pc.bold(`The subgraph name is required for subgraph at index ${index}. Please provide a name and try again.`)));
|
|
199
|
+
}
|
|
200
|
+
const url = (_b = (_a = s.introspection) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : s.routing_url;
|
|
201
|
+
// Check if a subgraph with the same name already exists
|
|
202
|
+
if (subgraphs.some((sg) => sg.name === s.name)) {
|
|
203
|
+
program.error(pc.red(pc.bold(`A subgraph with the name '${pc.bold(s.name)}' is already registered. Subgraph names must be unique.`)));
|
|
204
|
+
}
|
|
205
|
+
let schemaSDL = '';
|
|
206
|
+
// The GraphQL schema is provided in the input file
|
|
207
|
+
if ((_c = s.schema) === null || _c === void 0 ? void 0 : _c.file) {
|
|
208
|
+
const schemaFile = resolve(inputFileLocation, s.schema.file);
|
|
209
|
+
const sdl = (await readFile(schemaFile)).toString();
|
|
210
|
+
schemaSDL = sdl;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// The GraphQL schema is not provided in the input file, so we need to introspect it
|
|
214
|
+
try {
|
|
215
|
+
const result = await introspectSubgraph({
|
|
216
|
+
subgraphURL: url,
|
|
217
|
+
additionalHeaders: Object.entries((_e = (_d = s.introspection) === null || _d === void 0 ? void 0 : _d.headers) !== null && _e !== void 0 ? _e : {}).map(([key, value]) => ({
|
|
218
|
+
key,
|
|
219
|
+
value,
|
|
220
|
+
})),
|
|
221
|
+
rawIntrospection: (_f = s.introspection) === null || _f === void 0 ? void 0 : _f.raw,
|
|
222
|
+
});
|
|
223
|
+
if (!result.success) {
|
|
224
|
+
program.error(`Could not introspect subgraph ${s.name}, URL: ${url}: ${(_g = result.errorMessage) !== null && _g !== void 0 ? _g : 'failed'}`);
|
|
225
|
+
}
|
|
226
|
+
schemaSDL = result.sdl;
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
program.error(`Could not introspect subgraph ${s.name}, URL: ${url}: ${e.message}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
kind: SubgraphKind.Standard,
|
|
234
|
+
name: s.name,
|
|
235
|
+
sdl: schemaSDL,
|
|
236
|
+
subscriptionUrl: ((_h = s.subscription) === null || _h === void 0 ? void 0 : _h.url) || s.routing_url,
|
|
237
|
+
subscriptionProtocol: ((_j = s.subscription) === null || _j === void 0 ? void 0 : _j.protocol) || 'ws',
|
|
238
|
+
websocketSubprotocol: ((_k = s.subscription) === null || _k === void 0 ? void 0 : _k.protocol) === 'ws' ? ((_m = (_l = s.subscription) === null || _l === void 0 ? void 0 : _l.websocketSubprotocol) !== null && _m !== void 0 ? _m : 'auto') : 'auto',
|
|
239
|
+
routingUrl: normalizeURL(s.routing_url),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function validateGRPCSubgraph(s) {
|
|
243
|
+
if (!s.name) {
|
|
244
|
+
program.error(pc.red(pc.bold(`The subgraph name is missing in the input file. Please check the name and try again.`)));
|
|
245
|
+
}
|
|
246
|
+
if (!s.routing_url) {
|
|
247
|
+
program.error(pc.red(pc.bold(`The routing URL is missing in the input file. Please check the routing URL and try again.`)));
|
|
248
|
+
}
|
|
249
|
+
if (!s.grpc.schema_file) {
|
|
250
|
+
program.error(pc.red(pc.bold(`The schema file is missing in the input file. Please check the schema file and try again.`)));
|
|
251
|
+
}
|
|
252
|
+
if (!s.grpc.proto_file) {
|
|
253
|
+
program.error(pc.red(pc.bold(`The proto file is missing in the input file. Please check the proto file and try again.`)));
|
|
254
|
+
}
|
|
255
|
+
if (!s.grpc.mapping_file) {
|
|
256
|
+
program.error(pc.red(pc.bold(`The mapping file is missing in the input file. Please check the mapping file and try again.`)));
|
|
257
|
+
}
|
|
258
|
+
if (!existsSync(s.grpc.schema_file)) {
|
|
259
|
+
program.error(pc.red(pc.bold(`The schema file '${pc.bold(s.grpc.schema_file)}' does not exist. Please check the path and try again.`)));
|
|
260
|
+
}
|
|
261
|
+
if (!existsSync(s.grpc.proto_file)) {
|
|
262
|
+
program.error(pc.red(pc.bold(`The proto file '${pc.bold(s.grpc.proto_file)}' does not exist. Please check the path and try again.`)));
|
|
263
|
+
}
|
|
264
|
+
if (!existsSync(s.grpc.mapping_file)) {
|
|
265
|
+
program.error(pc.red(pc.bold(`The mapping file '${pc.bold(s.grpc.mapping_file)}' does not exist. Please check the path and try again.`)));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function validateSubgraphPlugin(s) {
|
|
269
|
+
if (!s.plugin.path) {
|
|
270
|
+
program.error(pc.red(pc.bold(`The plugin path is missing in the input file. Please check the path and try again.`)));
|
|
271
|
+
}
|
|
272
|
+
if (!existsSync(s.plugin.path)) {
|
|
273
|
+
program.error(pc.red(pc.bold(`The plugin path '${pc.bold(s.plugin.path)}' does not exist. Please check the path and try again.`)));
|
|
274
|
+
}
|
|
275
|
+
if (!s.plugin.version) {
|
|
276
|
+
program.error(pc.red(pc.bold(`The plugin version is missing in the input file. Please check the version and try again.`)));
|
|
277
|
+
}
|
|
278
|
+
// Check if valid semver
|
|
279
|
+
if (!semver.valid(s.plugin.version)) {
|
|
280
|
+
program.error(pc.red(pc.bold(`The plugin version '${pc.bold(s.plugin.version)}' is not a valid semver. Please check the version and try again.`)));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function buildFeatureFlagsConfig(config, inputFileLocation, subgraphs, options) {
|
|
284
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
285
|
+
const ffConfigs = new FeatureFlagRouterExecutionConfigs();
|
|
286
|
+
// @TODO This logic should exist only once in the shared package and reused across
|
|
287
|
+
// control-plane and cli
|
|
288
|
+
for (const ff of config.feature_flags) {
|
|
289
|
+
const featureSubgraphs = [];
|
|
290
|
+
const standardSubgraphs = config.subgraphs.filter((ss) => !('plugin' in ss) && !('grpc' in ss));
|
|
291
|
+
// Process each subgraph for this feature flag
|
|
292
|
+
for (const s of standardSubgraphs) {
|
|
293
|
+
// Check if this subgraph is overridden by a feature graph
|
|
294
|
+
const featureSubgraph = ff.feature_graphs.find((ffs) => ffs.subgraph_name === s.name);
|
|
295
|
+
if (featureSubgraph) {
|
|
296
|
+
// This subgraph is overridden by a feature graph
|
|
297
|
+
const url = (_b = (_a = featureSubgraph.introspection) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : featureSubgraph.routing_url;
|
|
298
|
+
let schemaSDL = '';
|
|
299
|
+
if ((_c = featureSubgraph.schema) === null || _c === void 0 ? void 0 : _c.file) {
|
|
300
|
+
const schemaFile = resolve(inputFileLocation, featureSubgraph.schema.file);
|
|
301
|
+
schemaSDL = await readFile(schemaFile, 'utf8');
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
try {
|
|
305
|
+
const result = await introspectSubgraph({
|
|
306
|
+
subgraphURL: url,
|
|
307
|
+
additionalHeaders: Object.entries((_e = (_d = featureSubgraph.introspection) === null || _d === void 0 ? void 0 : _d.headers) !== null && _e !== void 0 ? _e : {}).map(([key, value]) => ({
|
|
308
|
+
key,
|
|
309
|
+
value,
|
|
310
|
+
})),
|
|
311
|
+
rawIntrospection: (_f = featureSubgraph.introspection) === null || _f === void 0 ? void 0 : _f.raw,
|
|
312
|
+
});
|
|
313
|
+
if (!result.success) {
|
|
314
|
+
program.error(`Could not introspect feature-graph subgraph ${featureSubgraph.name}, URL: ${url}: ${(_g = result.errorMessage) !== null && _g !== void 0 ? _g : 'failed'}`);
|
|
315
|
+
}
|
|
316
|
+
schemaSDL = result.sdl;
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
program.error(`Could not introspect feature-graph subgraph ${featureSubgraph.name}, URL: ${url}: ${e.message}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
featureSubgraphs.push({
|
|
323
|
+
kind: SubgraphKind.Standard,
|
|
324
|
+
name: featureSubgraph.name,
|
|
325
|
+
sdl: schemaSDL,
|
|
326
|
+
routingUrl: featureSubgraph.routing_url,
|
|
327
|
+
subscriptionUrl: ((_h = featureSubgraph.subscription) === null || _h === void 0 ? void 0 : _h.url) || featureSubgraph.routing_url,
|
|
328
|
+
subscriptionProtocol: ((_j = featureSubgraph.subscription) === null || _j === void 0 ? void 0 : _j.protocol) || 'ws',
|
|
329
|
+
websocketSubprotocol: ((_k = featureSubgraph.subscription) === null || _k === void 0 ? void 0 : _k.protocol) === 'ws'
|
|
330
|
+
? ((_l = featureSubgraph.subscription) === null || _l === void 0 ? void 0 : _l.websocketSubprotocol) || 'auto'
|
|
331
|
+
: 'auto',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Use the base subgraph as is
|
|
336
|
+
// Find the corresponding metadata in the original subgraphs array
|
|
337
|
+
const originalSubgraph = subgraphs.find((sub) => sub.kind === SubgraphKind.Standard && sub.name === s.name);
|
|
338
|
+
if (originalSubgraph) {
|
|
339
|
+
featureSubgraphs.push(originalSubgraph);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const featureResult = composeSubgraphs(featureSubgraphs.map((s) => ({
|
|
344
|
+
name: s.name,
|
|
345
|
+
url: normalizeURL(s.routingUrl),
|
|
346
|
+
definitions: parse(s.sdl),
|
|
347
|
+
})));
|
|
348
|
+
if (!featureResult.success) {
|
|
349
|
+
const compositionErrorsTable = new Table({
|
|
350
|
+
head: [pc.bold(pc.white('ERROR_MESSAGE'))],
|
|
351
|
+
colWidths: [120],
|
|
352
|
+
wordWrap: true,
|
|
353
|
+
});
|
|
354
|
+
console.log(pc.red(`We found composition errors, while composing the feature flag ${pc.italic(ff.name)}.\n${pc.bold('Please check the errors below:')}`));
|
|
355
|
+
for (const compositionError of featureResult.errors) {
|
|
356
|
+
compositionErrorsTable.push([compositionError.message]);
|
|
357
|
+
}
|
|
358
|
+
console.log(compositionErrorsTable.toString());
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (!options.suppressWarnings && featureResult.warnings.length > 0) {
|
|
362
|
+
const compositionWarningsTable = new Table({
|
|
363
|
+
head: [pc.bold(pc.white('WARNING_MESSAGE'))],
|
|
364
|
+
colWidths: [120],
|
|
365
|
+
wordWrap: true,
|
|
366
|
+
});
|
|
367
|
+
console.log(pc.yellow(`The following warnings were produced while composing the feature flag ${pc.italic(ff.name)}:`));
|
|
368
|
+
for (const warning of featureResult.warnings) {
|
|
369
|
+
compositionWarningsTable.push([warning.message]);
|
|
370
|
+
}
|
|
371
|
+
console.log(compositionWarningsTable.toString());
|
|
372
|
+
}
|
|
373
|
+
const featureFederatedClientSDL = featureResult.shouldIncludeClientSchema
|
|
374
|
+
? printSchema(featureResult.federatedGraphClientSchema)
|
|
375
|
+
: '';
|
|
376
|
+
const featureRouterConfig = buildRouterConfig({
|
|
377
|
+
federatedClientSDL: featureFederatedClientSDL,
|
|
378
|
+
federatedSDL: printSchemaWithDirectives(featureResult.federatedGraphSchema),
|
|
379
|
+
fieldConfigurations: featureResult.fieldConfigurations,
|
|
380
|
+
// @TODO get router compatibility version programmatically
|
|
381
|
+
routerCompatibilityVersion: ROUTER_COMPATIBILITY_VERSION_ONE,
|
|
382
|
+
schemaVersionId: `static`,
|
|
383
|
+
subgraphs: featureSubgraphs.map((s, index) => {
|
|
384
|
+
const subgraphConfig = featureResult.subgraphConfigBySubgraphName.get(s.name);
|
|
385
|
+
const schema = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.schema;
|
|
386
|
+
const configurationDataByTypeName = subgraphConfig === null || subgraphConfig === void 0 ? void 0 : subgraphConfig.configurationDataByTypeName;
|
|
387
|
+
const composedSubgraph = {
|
|
388
|
+
kind: SubgraphKind.Standard,
|
|
389
|
+
id: `${index}`,
|
|
390
|
+
name: s.name,
|
|
391
|
+
url: s.routingUrl,
|
|
392
|
+
sdl: s.sdl,
|
|
393
|
+
subscriptionUrl: s.subscriptionUrl,
|
|
394
|
+
subscriptionProtocol: s.subscriptionProtocol,
|
|
395
|
+
websocketSubprotocol: s.websocketSubprotocol,
|
|
396
|
+
schema,
|
|
397
|
+
configurationDataByTypeName,
|
|
398
|
+
};
|
|
399
|
+
return composedSubgraph;
|
|
400
|
+
}),
|
|
401
|
+
});
|
|
402
|
+
ffConfigs.configByFeatureFlagName[ff.name] = new FeatureFlagRouterExecutionConfig({
|
|
403
|
+
version: featureRouterConfig.version,
|
|
404
|
+
subgraphs: featureRouterConfig.subgraphs,
|
|
405
|
+
engineConfig: featureRouterConfig.engineConfig,
|
|
406
|
+
});
|
|
407
|
+
ffConfigs.configByFeatureFlagName[ff.name].version = randomUUID();
|
|
408
|
+
}
|
|
409
|
+
return ffConfigs;
|
|
410
|
+
}
|
|
337
411
|
//# sourceMappingURL=compose.js.map
|