Skip to content

Commit 121f05d

Browse files
committed
Merge remote-tracking branch 'origin/wilson/eng-8027-allow-subgraph-check-with-read-permission' into wilson/eng-8027-allow-subgraph-check-with-read-permission
2 parents 021918a + f993aef commit 121f05d

39 files changed

+8060
-77
lines changed

cli/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Binaries are attached to the github release otherwise all images can be found [h
44
All notable changes to this project will be documented in this file.
55
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
66

7+
# [0.93.0](https://github.com/wundergraph/cosmo/compare/wgc@0.92.0...wgc@0.93.0) (2025-09-12)
8+
9+
### Features
10+
11+
* add cli commands to create,publish and delete grpc subgraphs ([#2179](https://github.com/wundergraph/cosmo/issues/2179)) ([2254925](https://github.com/wundergraph/cosmo/commit/2254925681aa644119e11b8958911bf490a2e885)) (@JivusAyrus)
12+
713
# [0.92.0](https://github.com/wundergraph/cosmo/compare/wgc@0.91.1...wgc@0.92.0) (2025-09-10)
814

915
### Features

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wgc",
3-
"version": "0.92.0",
3+
"version": "0.93.0",
44
"description": "The official CLI tool to manage the GraphQL Federation Platform Cosmo",
55
"type": "module",
66
"main": "dist/src/index.js",

cli/src/commands/feature-subgraph/commands/publish.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { websocketSubprotocolDescription } from '../../../constants.js';
1010
import { BaseCommandOptions } from '../../../core/types/types.js';
1111
import { handleCompositionResult } from '../../../handle-composition-result.js';
1212
import { validateSubscriptionProtocols } from '../../../utils.js';
13+
import { getBaseHeaders } from '../../../core/config.js';
1314

1415
export default (opts: BaseCommandOptions) => {
1516
const command = new Command('publish');
@@ -94,27 +95,32 @@ export default (opts: BaseCommandOptions) => {
9495
spinner.start();
9596
}
9697

97-
const resp = await opts.client.platform.publishFederatedSubgraph({
98-
baseSubgraphName: options.subgraph,
99-
disableResolvabilityValidation: options.disableResolvabilityValidation,
100-
isFeatureSubgraph: true,
101-
labels: [],
102-
name,
103-
namespace: options.namespace,
104-
// Publish schema only
105-
// Optional when feature subgraph does not exist yet
106-
routingUrl: options.routingUrl,
107-
schema,
108-
subscriptionProtocol: options.subscriptionProtocol
109-
? parseGraphQLSubscriptionProtocol(options.subscriptionProtocol)
110-
: undefined,
111-
subscriptionUrl: options.subscriptionUrl,
112-
websocketSubprotocol: options.websocketSubprotocol
113-
? parseGraphQLWebsocketSubprotocol(options.websocketSubprotocol)
114-
: undefined,
115-
// passing Standard type to the backend, because the users have to use the 'wgc router plugin publish' command to publish the plugin
116-
type: SubgraphType.STANDARD,
117-
});
98+
const resp = await opts.client.platform.publishFederatedSubgraph(
99+
{
100+
baseSubgraphName: options.subgraph,
101+
disableResolvabilityValidation: options.disableResolvabilityValidation,
102+
isFeatureSubgraph: true,
103+
labels: [],
104+
name,
105+
namespace: options.namespace,
106+
// Publish schema only
107+
// Optional when feature subgraph does not exist yet
108+
routingUrl: options.routingUrl,
109+
schema,
110+
subscriptionProtocol: options.subscriptionProtocol
111+
? parseGraphQLSubscriptionProtocol(options.subscriptionProtocol)
112+
: undefined,
113+
subscriptionUrl: options.subscriptionUrl,
114+
websocketSubprotocol: options.websocketSubprotocol
115+
? parseGraphQLWebsocketSubprotocol(options.websocketSubprotocol)
116+
: undefined,
117+
// passing Standard type to the backend, because the users have to use the 'wgc router plugin publish' command to publish the plugin
118+
type: SubgraphType.STANDARD,
119+
},
120+
{
121+
headers: getBaseHeaders(),
122+
},
123+
);
118124

119125
try {
120126
handleCompositionResult({
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { existsSync } from 'node:fs';
2+
import { readFile } from 'node:fs/promises';
3+
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
4+
import { SubgraphType } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
5+
import { splitLabel } from '@wundergraph/cosmo-shared';
6+
import { Command, program } from 'commander';
7+
import ora from 'ora';
8+
import { resolve } from 'pathe';
9+
import pc from 'picocolors';
10+
import { getBaseHeaders } from '../../../core/config.js';
11+
import { BaseCommandOptions } from '../../../core/types/types.js';
12+
13+
export default (opts: BaseCommandOptions) => {
14+
const command = new Command('create');
15+
command.description('Creates a federated grpc subgraph on the control plane.');
16+
command.argument(
17+
'<name>',
18+
'The name of the grpc subgraph to create. It is used to uniquely identify your grpc subgraph.',
19+
);
20+
command.option('-n, --namespace [string]', 'The namespace of the grpc subgraph.');
21+
command.requiredOption(
22+
'-r, --routing-url <url>',
23+
'The routing URL of your subgraph. This is the url at which the subgraph will be accessible.',
24+
);
25+
command.option(
26+
'--label [labels...]',
27+
'The labels to apply to the subgraph. The labels are passed in the format <key>=<value> <key>=<value>.',
28+
);
29+
command.option('--readme <path-to-readme>', 'The markdown file which describes the subgraph.');
30+
31+
command.action(async (name, options) => {
32+
let readmeFile;
33+
if (options.readme) {
34+
readmeFile = resolve(options.readme);
35+
if (!existsSync(readmeFile)) {
36+
program.error(
37+
pc.red(
38+
pc.bold(`The readme file '${pc.bold(readmeFile)}' does not exist. Please check the path and try again.`),
39+
),
40+
);
41+
}
42+
}
43+
44+
const spinner = ora('GRPC Subgraph is being created...').start();
45+
const resp = await opts.client.platform.createFederatedSubgraph(
46+
{
47+
name,
48+
namespace: options.namespace,
49+
labels: options.label ? options.label.map((label: string) => splitLabel(label)) : [],
50+
routingUrl: options.routingUrl,
51+
readme: readmeFile ? await readFile(readmeFile, 'utf8') : undefined,
52+
type: SubgraphType.GRPC_SERVICE,
53+
},
54+
{
55+
headers: getBaseHeaders(),
56+
},
57+
);
58+
59+
if (resp.response?.code === EnumStatusCode.OK) {
60+
spinner.succeed('GRPC subgraph was created successfully.');
61+
} else {
62+
spinner.fail('Failed to create grpc subgraph.');
63+
if (resp.response?.details) {
64+
console.log(pc.red(pc.bold(resp.response?.details)));
65+
}
66+
process.exitCode = 1;
67+
// eslint-disable-next-line no-useless-return
68+
return;
69+
}
70+
});
71+
72+
return command;
73+
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { Command } from 'commander';
2+
import pc from 'picocolors';
3+
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
4+
import inquirer from 'inquirer';
5+
import Table from 'cli-table3';
6+
import ora from 'ora';
7+
import { BaseCommandOptions } from '../../../core/types/types.js';
8+
import { getBaseHeaders } from '../../../core/config.js';
9+
10+
export default (opts: BaseCommandOptions) => {
11+
const command = new Command('delete');
12+
command.description('Deletes a gRPC subgraph on the control plane.');
13+
command.argument('<name>', 'The name of the gRPC subgraph to delete.');
14+
command.option('-n, --namespace [string]', 'The namespace of the gRPC subgraph.');
15+
command.option('-f, --force', 'Flag to force the deletion (skip confirmation).');
16+
command.option('--suppress-warnings', 'This flag suppresses any warnings produced by composition.');
17+
command.action(async (name, options) => {
18+
if (!options.force) {
19+
const deletionConfirmed = await inquirer.prompt({
20+
name: 'confirmDeletion',
21+
type: 'confirm',
22+
message: `Are you sure you want to delete the gRPC subgraph "${name}"?`,
23+
});
24+
if (!deletionConfirmed.confirmDeletion) {
25+
process.exitCode = 1;
26+
return;
27+
}
28+
}
29+
30+
const spinner = ora(`The gRPC subgraph "${name}" is being deleted...`).start();
31+
32+
const resp = await opts.client.platform.deleteFederatedSubgraph(
33+
{
34+
subgraphName: name,
35+
namespace: options.namespace,
36+
},
37+
{
38+
headers: getBaseHeaders(),
39+
},
40+
);
41+
42+
switch (resp.response?.code) {
43+
case EnumStatusCode.OK: {
44+
spinner.succeed(`The gRPC subgraph "${name}" was deleted successfully.`);
45+
if (resp.proposalMatchMessage) {
46+
console.log(pc.yellow(`Warning: Proposal match failed`));
47+
console.log(pc.yellow(resp.proposalMatchMessage));
48+
}
49+
break;
50+
}
51+
case EnumStatusCode.ERR_SCHEMA_MISMATCH_WITH_APPROVED_PROPOSAL: {
52+
spinner.fail(`Failed to delete gRPC subgraph "${name}".`);
53+
console.log(pc.red(`Error: Proposal match failed`));
54+
console.log(pc.red(resp.proposalMatchMessage));
55+
break;
56+
}
57+
case EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED: {
58+
spinner.fail(`The gRPC subgraph "${name}" was deleted but with composition errors.`);
59+
60+
const compositionErrorsTable = new Table({
61+
head: [
62+
pc.bold(pc.white('FEDERATED_GRAPH_NAME')),
63+
pc.bold(pc.white('NAMESPACE')),
64+
pc.bold(pc.white('FEATURE_FLAG')),
65+
pc.bold(pc.white('ERROR_MESSAGE')),
66+
],
67+
colWidths: [30, 30, 30, 120],
68+
wordWrap: true,
69+
});
70+
71+
console.log(
72+
pc.red(
73+
`There were composition errors when composing at least one federated graph related to the` +
74+
` gRPC subgraph "${name}".\nThe router will continue to work with the latest valid schema.` +
75+
`\n${pc.bold('Please check the errors below:')}`,
76+
),
77+
);
78+
for (const compositionError of resp.compositionErrors) {
79+
compositionErrorsTable.push([
80+
compositionError.federatedGraphName,
81+
compositionError.namespace,
82+
compositionError.featureFlag || '-',
83+
compositionError.message,
84+
]);
85+
}
86+
// Don't exit here with 1 because the change was still applied
87+
console.log(compositionErrorsTable.toString());
88+
89+
break;
90+
}
91+
case EnumStatusCode.ERR_DEPLOYMENT_FAILED: {
92+
spinner.warn(
93+
`The gRPC subgraph "${name}" was deleted, but the updated composition could not be deployed.` +
94+
`\nThis means the updated composition is not accessible to the router.` +
95+
`\n${pc.bold('Please check the errors below:')}`,
96+
);
97+
98+
const deploymentErrorsTable = new Table({
99+
head: [
100+
pc.bold(pc.white('FEDERATED_GRAPH_NAME')),
101+
pc.bold(pc.white('NAMESPACE')),
102+
pc.bold(pc.white('ERROR_MESSAGE')),
103+
],
104+
colWidths: [30, 30, 120],
105+
wordWrap: true,
106+
});
107+
108+
for (const deploymentError of resp.deploymentErrors) {
109+
deploymentErrorsTable.push([
110+
deploymentError.federatedGraphName,
111+
deploymentError.namespace,
112+
deploymentError.message,
113+
]);
114+
}
115+
// Don't exit here with 1 because the change was still applied
116+
console.log(deploymentErrorsTable.toString());
117+
118+
break;
119+
}
120+
default: {
121+
spinner.fail(`Failed to delete the gRPC subgraph "${name}".`);
122+
if (resp.response?.details) {
123+
console.log(pc.red(pc.bold(resp.response?.details)));
124+
}
125+
process.exitCode = 1;
126+
return;
127+
}
128+
}
129+
130+
if (!options.suppressWarnings && resp.compositionWarnings.length > 0) {
131+
const compositionWarningsTable = new Table({
132+
head: [
133+
pc.bold(pc.white('FEDERATED_GRAPH_NAME')),
134+
pc.bold(pc.white('NAMESPACE')),
135+
pc.bold(pc.white('FEATURE_FLAG')),
136+
pc.bold(pc.white('WARNING_MESSAGE')),
137+
],
138+
colWidths: [30, 30, 30, 120],
139+
wordWrap: true,
140+
});
141+
142+
console.log(pc.yellow(`The following warnings were produced while composing the federated graph:`));
143+
for (const compositionWarning of resp.compositionWarnings) {
144+
compositionWarningsTable.push([
145+
compositionWarning.federatedGraphName,
146+
compositionWarning.namespace,
147+
compositionWarning.featureFlag || '-',
148+
compositionWarning.message,
149+
]);
150+
}
151+
console.log(compositionWarningsTable.toString());
152+
}
153+
});
154+
155+
return command;
156+
};

0 commit comments

Comments
 (0)