wgc 0.79.5 → 0.80.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.
Files changed (31) hide show
  1. package/dist/package.json +13 -4
  2. package/dist/src/commands/router/commands/compose.js +171 -89
  3. package/dist/src/commands/router/commands/compose.js.map +1 -1
  4. package/dist/src/commands/router/index.js +5 -1
  5. package/dist/src/commands/router/index.js.map +1 -1
  6. package/dist/src/commands/router/plugin/commands/build.d.ts +4 -0
  7. package/dist/src/commands/router/plugin/commands/build.js +76 -0
  8. package/dist/src/commands/router/plugin/commands/build.js.map +1 -0
  9. package/dist/src/commands/router/plugin/commands/init.d.ts +4 -0
  10. package/dist/src/commands/router/plugin/commands/init.js +98 -0
  11. package/dist/src/commands/router/plugin/commands/init.js.map +1 -0
  12. package/dist/src/commands/router/plugin/commands/test.d.ts +4 -0
  13. package/dist/src/commands/router/plugin/commands/test.js +62 -0
  14. package/dist/src/commands/router/plugin/commands/test.js.map +1 -0
  15. package/dist/src/commands/router/plugin/helper.d.ts +10 -0
  16. package/dist/src/commands/router/plugin/helper.js +47 -0
  17. package/dist/src/commands/router/plugin/helper.js.map +1 -0
  18. package/dist/src/commands/router/plugin/index.d.ts +4 -0
  19. package/dist/src/commands/router/plugin/index.js +13 -0
  20. package/dist/src/commands/router/plugin/index.js.map +1 -0
  21. package/dist/src/commands/router/plugin/templates/go-plugin.d.ts +5 -0
  22. package/dist/src/commands/router/plugin/templates/go-plugin.js +342 -0
  23. package/dist/src/commands/router/plugin/templates/go-plugin.js.map +1 -0
  24. package/dist/src/commands/router/plugin/toolchain.d.ts +36 -0
  25. package/dist/src/commands/router/plugin/toolchain.js +413 -0
  26. package/dist/src/commands/router/plugin/toolchain.js.map +1 -0
  27. package/dist/src/core/config.d.ts +1 -0
  28. package/dist/src/core/config.js +1 -0
  29. package/dist/src/core/config.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +16 -7
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import { BaseCommandOptions } from '../../../../core/types/types.js';
3
+ declare const _default: (opts: BaseCommandOptions) => Command;
4
+ export default _default;
@@ -0,0 +1,98 @@
1
+ import { access, mkdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { Command, program } from 'commander';
5
+ import { resolve } from 'pathe';
6
+ import pc from 'picocolors';
7
+ import pupa from 'pupa';
8
+ import Spinner from 'ora';
9
+ import { compileGraphQLToMapping, compileGraphQLToProto } from '@wundergraph/protographic';
10
+ import { camelCase, upperFirst } from 'lodash-es';
11
+ import { goMod, mainGo, mainGoTest, readme, schema } from '../templates/go-plugin.js';
12
+ import { renderResultTree } from '../helper.js';
13
+ export default (opts) => {
14
+ const command = new Command('init');
15
+ command.description('Scaffold a new gRPC router plugin');
16
+ command.argument('name', 'Name of the plugin');
17
+ command.option('-d, --directory <directory>', 'Directory to create the plugin in', '.');
18
+ command.option('-l, --language <language>', 'Programming language to use for the plugin', 'go');
19
+ command.action(async (name, options) => {
20
+ const startTime = performance.now();
21
+ const pluginDir = resolve(process.cwd(), options.directory, name);
22
+ name = upperFirst(camelCase(name));
23
+ const serviceName = name + 'Service';
24
+ // Check if a directory exists
25
+ try {
26
+ await access(pluginDir);
27
+ program.error(pc.red(`Plugin ${name} already exists in ${pluginDir}`));
28
+ }
29
+ catch {
30
+ // Directory doesn't exist, we can proceed
31
+ }
32
+ const spinner = Spinner({ text: 'Creating plugin...' });
33
+ // Create a temporary directory
34
+ const tempDir = resolve(tmpdir(), `cosmo-plugin-${randomUUID()}`);
35
+ spinner.start();
36
+ try {
37
+ spinner.text = 'Creating directories...';
38
+ await mkdir(tempDir, { recursive: true });
39
+ const srcDir = resolve(tempDir, 'src');
40
+ await mkdir(srcDir, { recursive: true });
41
+ const generatedDir = resolve(tempDir, 'generated');
42
+ await mkdir(generatedDir, { recursive: true });
43
+ spinner.text = 'Checkout templates...';
44
+ if (options.language.toLowerCase() !== 'go') {
45
+ spinner.fail(pc.yellow(`Language '${options.language}' is not supported yet. Using 'go' instead.`));
46
+ options.language = 'go';
47
+ }
48
+ await writeFile(resolve(tempDir, 'README.md'), pupa(readme, { name }));
49
+ await writeFile(resolve(srcDir, 'schema.graphql'), pupa(schema, { name }));
50
+ spinner.text = 'Generating mapping and proto files...';
51
+ const mapping = compileGraphQLToMapping(schema, serviceName);
52
+ await writeFile(resolve(generatedDir, 'mapping.json'), JSON.stringify(mapping, null, 2));
53
+ const goModulePath = 'github.com/wundergraph/cosmo/plugin';
54
+ const proto = compileGraphQLToProto(schema, {
55
+ serviceName,
56
+ packageName: 'service',
57
+ goPackage: goModulePath,
58
+ });
59
+ await writeFile(resolve(generatedDir, 'service.proto'), proto.proto);
60
+ await writeFile(resolve(generatedDir, 'service.proto.lock.json'), JSON.stringify(proto.lockData, null, 2));
61
+ await writeFile(resolve(srcDir, 'main.go'), pupa(mainGo, { serviceName }));
62
+ await writeFile(resolve(srcDir, 'main_test.go'), pupa(mainGoTest, { serviceName }));
63
+ // go mod init
64
+ await writeFile(resolve(tempDir, 'go.mod'), pupa(goMod, { modulePath: goModulePath }));
65
+ await mkdir(resolve(process.cwd(), options.directory), { recursive: true });
66
+ await rename(tempDir, pluginDir);
67
+ const endTime = performance.now();
68
+ const elapsedTimeMs = endTime - startTime;
69
+ const formattedTime = elapsedTimeMs > 1000 ? `${(elapsedTimeMs / 1000).toFixed(2)}s` : `${Math.round(elapsedTimeMs)}ms`;
70
+ renderResultTree(spinner, 'Plugin scaffolded!', true, name, {
71
+ language: options.language,
72
+ time: formattedTime,
73
+ location: pluginDir,
74
+ });
75
+ console.log('');
76
+ console.log(` Checkout the ${pc.bold(pc.italic('README.md'))} file for instructions on how to build and run your plugin.`);
77
+ console.log(` Go to https://cosmo-docs.wundergraph.com/router/plugins to learn more about it.`);
78
+ console.log('');
79
+ }
80
+ catch (error) {
81
+ // Clean up the temp directory in case of error
82
+ try {
83
+ await rm(tempDir, { recursive: true, force: true });
84
+ }
85
+ catch {
86
+ // Ignore cleanup errors
87
+ }
88
+ renderResultTree(spinner, 'Plugin scaffolding', false, name, {
89
+ error: error.message,
90
+ });
91
+ }
92
+ finally {
93
+ spinner.stop();
94
+ }
95
+ });
96
+ return command;
97
+ };
98
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../../../src/commands/router/plugin/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAY,OAAO,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAO,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,eAAe,CAAC,IAAwB,EAAE,EAAE;IAC1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,WAAW,CAAC,mCAAmC,CAAC,CAAC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC/C,OAAO,CAAC,MAAM,CAAC,6BAA6B,EAAE,mCAAmC,EAAE,GAAG,CAAC,CAAC;IACxF,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,4CAA4C,EAAE,IAAI,CAAC,CAAC;IAChG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACrC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAElE,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;QAErC,8BAA8B;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,IAAI,sBAAsB,SAAS,EAAE,CAAC,CAAC,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAC;QACxD,+BAA+B;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,gBAAgB,UAAU,EAAE,EAAE,CAAC,CAAC;QAElE,OAAO,CAAC,KAAK,EAAE,CAAC;QAEhB,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,GAAG,yBAAyB,CAAC;YAEzC,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACvC,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACzC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACnD,MAAM,KAAK,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE/C,OAAO,CAAC,IAAI,GAAG,uBAAuB,CAAC;YAEvC,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC5C,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,aAAa,OAAO,CAAC,QAAQ,6CAA6C,CAAC,CAAC,CAAC;gBACpG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YAC1B,CAAC;YAED,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACvE,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAE3E,OAAO,CAAC,IAAI,GAAG,uCAAuC,CAAC;YAEvD,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAC7D,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAEzF,MAAM,YAAY,GAAG,qCAAqC,CAAC;YAE3D,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,EAAE;gBAC1C,WAAW;gBACX,WAAW,EAAE,SAAS;gBACtB,SAAS,EAAE,YAAY;aACxB,CAAC,CAAC;YAEH,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,eAAe,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACrE,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,yBAAyB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAE3G,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAC3E,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAEpF,cAAc;YACd,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;YAEvF,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5E,MAAM,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAEjC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAClC,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;YAC1C,MAAM,aAAa,GACjB,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;YAEpG,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,IAAI,EAAE;gBAC1D,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,IAAI,EAAE,aAAa;gBACnB,QAAQ,EAAE,SAAS;aACpB,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CACT,kBAAkB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,6DAA6D,CAC/G,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAC;YACjG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,+CAA+C;YAC/C,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;YACD,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,IAAI,EAAE;gBAC3D,KAAK,EAAE,KAAK,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import { BaseCommandOptions } from '../../../../core/types/types.js';
3
+ declare const _default: (opts: BaseCommandOptions) => Command;
4
+ export default _default;
@@ -0,0 +1,62 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ import { Command } from 'commander';
4
+ import { resolve } from 'pathe';
5
+ import Spinner from 'ora';
6
+ import { checkAndInstallTools, runGoTests } from '../toolchain.js';
7
+ import { renderResultTree } from '../helper.js';
8
+ export default (opts) => {
9
+ const command = new Command('test');
10
+ command.description('Run tests for a gRPC router plugin');
11
+ command.argument('[directory]', 'Directory of the plugin', '.');
12
+ command.option('--skip-tools-installation', 'Skip tool installation', false);
13
+ command.option('--force-tools-installation', 'Force tools installation regardless of version check or confirmation', false);
14
+ command.action(async (directory, options) => {
15
+ const startTime = performance.now();
16
+ const pluginDir = resolve(directory);
17
+ const spinner = Spinner({ text: 'Running tests...' });
18
+ const pluginName = path.basename(pluginDir);
19
+ try {
20
+ spinner.start();
21
+ // Check and install tools if needed
22
+ if (!options.skipToolsInstallation) {
23
+ await checkAndInstallTools(options.forceToolsInstallation);
24
+ }
25
+ const srcDir = resolve(pluginDir, 'src');
26
+ spinner.text = 'Running tests...';
27
+ try {
28
+ const { failed } = await runGoTests(srcDir, spinner, false);
29
+ // Calculate elapsed time
30
+ const endTime = performance.now();
31
+ const elapsedTimeMs = endTime - startTime;
32
+ const formattedTime = elapsedTimeMs > 1000 ? `${(elapsedTimeMs / 1000).toFixed(2)}s` : `${Math.round(elapsedTimeMs)}ms`;
33
+ // Common details for both success and failure
34
+ const details = {
35
+ source: srcDir,
36
+ env: `${os.platform()} ${os.arch()}`,
37
+ time: formattedTime,
38
+ };
39
+ const title = failed ? 'Tests failed!' : 'Tests completed successfully!';
40
+ renderResultTree(spinner, title, !failed, pluginName, details);
41
+ }
42
+ catch (error) {
43
+ renderResultTree(spinner, 'Tests failed!', false, pluginName, {
44
+ source: srcDir,
45
+ env: `${os.platform()} ${os.arch()}`,
46
+ error: error.message,
47
+ });
48
+ }
49
+ }
50
+ catch (error) {
51
+ renderResultTree(spinner, 'Failed to run tests!', false, pluginName, {
52
+ env: `${os.platform()} ${os.arch()}`,
53
+ error: error.message,
54
+ });
55
+ }
56
+ finally {
57
+ spinner.stop();
58
+ }
59
+ });
60
+ return command;
61
+ };
62
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../../../../src/commands/router/plugin/commands/test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,OAAO,MAAM,KAAK,CAAC;AAE1B,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,eAAe,CAAC,IAAwB,EAAE,EAAE;IAC1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,WAAW,CAAC,oCAAoC,CAAC,CAAC;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAChE,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC7E,OAAO,CAAC,MAAM,CACZ,4BAA4B,EAC5B,sEAAsE,EACtE,KAAK,CACN,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QAC1C,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACtD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,EAAE,CAAC;YAEhB,oCAAoC;YACpC,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;gBACnC,MAAM,oBAAoB,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAEzC,OAAO,CAAC,IAAI,GAAG,kBAAkB,CAAC;YAElC,IAAI,CAAC;gBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;gBAE5D,yBAAyB;gBACzB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;gBAClC,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;gBAC1C,MAAM,aAAa,GACjB,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;gBAEpG,8CAA8C;gBAC9C,MAAM,OAAO,GAAG;oBACd,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;oBACpC,IAAI,EAAE,aAAa;iBACpB,CAAC;gBAEF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,+BAA+B,CAAC;gBACzE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YACjE,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE;oBAC5D,MAAM,EAAE,MAAM;oBACd,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;oBACpC,KAAK,EAAE,KAAK,CAAC,OAAO;iBACrB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,gBAAgB,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,UAAU,EAAE;gBACnE,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;gBACpC,KAAK,EAAE,KAAK,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import Spinner from 'ora';
2
+ /**
3
+ * Renders a tree-formatted result display
4
+ * @param spinner The spinner instance
5
+ * @param title The title to display
6
+ * @param success Whether the operation was successful
7
+ * @param name The name of the item (e.g. plugin name)
8
+ * @param details Key-value pairs of details to display
9
+ */
10
+ export declare function renderResultTree(spinner: ReturnType<typeof Spinner>, title: string, success: boolean, name: string, details: Record<string, string>): void;
@@ -0,0 +1,47 @@
1
+ import pc from 'picocolors';
2
+ /**
3
+ * Renders a tree-formatted result display
4
+ * @param spinner The spinner instance
5
+ * @param title The title to display
6
+ * @param success Whether the operation was successful
7
+ * @param name The name of the item (e.g. plugin name)
8
+ * @param details Key-value pairs of details to display
9
+ */
10
+ export function renderResultTree(spinner, title, success, name, details) {
11
+ const state = success ? pc.green('success') : pc.red('failed');
12
+ const symbol = success ? pc.green('[●]') : pc.red('[●]');
13
+ spinner.stopAndPersist({
14
+ symbol,
15
+ text: pc.bold(title),
16
+ });
17
+ // Build the tree with consistent formatting
18
+ let output = ` ${pc.dim('│')}`;
19
+ // Add the name and state first (these are always present)
20
+ output += `\n ${pc.dim('├────────── name')}: ${name}`;
21
+ output += `\n ${pc.dim('├───────── state')}: ${state}`;
22
+ // Dynamically generate key formatters based on actual keys
23
+ const keys = Object.keys(details);
24
+ const keyFormatters = {};
25
+ // Generate dynamic formatters for each key
26
+ for (const key of [...keys, 'name', 'state']) {
27
+ // Calculate the number of dashes needed to align all values
28
+ const dashCount = 14 - key.length;
29
+ keyFormatters[key] = '─'.repeat(dashCount) + ' ' + key;
30
+ }
31
+ // Apply fixed formatters for the standard keys
32
+ keyFormatters.name = '────────── name';
33
+ keyFormatters.state = '───────── state';
34
+ // Add all the other details except the last one
35
+ for (const key of keys.slice(0, -1)) {
36
+ const formattedKey = keyFormatters[key];
37
+ output += `\n ${pc.dim('├' + formattedKey)}: ${details[key]}`;
38
+ }
39
+ // Add the last detail with the corner character
40
+ if (keys.length > 0) {
41
+ const lastKey = keys.at(-1);
42
+ const formattedKey = keyFormatters[lastKey];
43
+ output += `\n ${pc.dim('└' + formattedKey)}: ${details[lastKey]}`;
44
+ }
45
+ console.log(output);
46
+ }
47
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../../../../src/commands/router/plugin/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAmC,EACnC,KAAa,EACb,OAAgB,EAChB,IAAY,EACZ,OAA+B;IAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAEzD,OAAO,CAAC,cAAc,CAAC;QACrB,MAAM;QACN,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;KACrB,CAAC,CAAC;IAEH,4CAA4C;IAC5C,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;IAE/B,0DAA0D;IAC1D,MAAM,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC;IACtD,MAAM,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,KAAK,EAAE,CAAC;IAEvD,2DAA2D;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,aAAa,GAA2B,EAAE,CAAC;IAEjD,2CAA2C;IAC3C,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QAC7C,4DAA4D;QAC5D,MAAM,SAAS,GAAG,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;QAClC,aAAa,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC;IACzD,CAAC;IAED,+CAA+C;IAC/C,aAAa,CAAC,IAAI,GAAG,iBAAiB,CAAC;IACvC,aAAa,CAAC,KAAK,GAAG,iBAAiB,CAAC;IAExC,gDAAgD;IAChD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,YAAY,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IAChE,CAAC;IAED,gDAAgD;IAChD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,YAAY,GAAG,aAAa,CAAC,OAAiB,CAAC,CAAC;QACtD,MAAM,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,YAAY,CAAC,KAAK,OAAO,CAAC,OAAiB,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import { BaseCommandOptions } from '../../../core/types/types.js';
3
+ declare const _default: (opts: BaseCommandOptions) => Command;
4
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import InitPluginCommand from './commands/init.js';
3
+ import BuildPluginCommand from './commands/build.js';
4
+ import TestPluginCommand from './commands/test.js';
5
+ export default (opts) => {
6
+ const command = new Command('plugin');
7
+ command.description('Provides commands for creating and maintaining router plugins');
8
+ command.addCommand(InitPluginCommand(opts));
9
+ command.addCommand(BuildPluginCommand(opts));
10
+ command.addCommand(TestPluginCommand(opts));
11
+ return command;
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/commands/router/plugin/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,iBAAiB,MAAM,oBAAoB,CAAC;AACnD,OAAO,kBAAkB,MAAM,qBAAqB,CAAC;AACrD,OAAO,iBAAiB,MAAM,oBAAoB,CAAC;AAEnD,eAAe,CAAC,IAAwB,EAAE,EAAE;IAC1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO,CAAC,WAAW,CAAC,+DAA+D,CAAC,CAAC;IACrF,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5C,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const goMod = "\nmodule {modulePath}\n\ngo 1.24.1\n\nrequire (\n github.com/stretchr/testify v1.10.0\n github.com/wundergraph/cosmo/router-plugin v0.0.0-20250519132817-0768029ce9e5\n google.golang.org/grpc v1.68.1\n google.golang.org/protobuf v1.36.5\n)\n";
2
+ export declare const mainGo = "package main\n\nimport (\n \"context\"\n \"log\"\n \"strconv\"\n\n service \"github.com/wundergraph/cosmo/plugin/generated\"\n\n routerplugin \"github.com/wundergraph/cosmo/router-plugin\"\n \"google.golang.org/grpc\"\n)\n\nfunc main() {\n pl, err := routerplugin.NewRouterPlugin(func(s *grpc.Server) {\n s.RegisterService(&service.{serviceName}_ServiceDesc, &{serviceName}{\n nextID: 1,\n })\n })\n\n if err != nil {\n log.Fatalf(\"failed to create router plugin: %v\", err)\n }\n\n pl.Serve()\n}\n\ntype {serviceName} struct {\n service.Unimplemented{serviceName}Server\n nextID int\n}\n\nfunc (s *{serviceName}) QueryHello(ctx context.Context, req *service.QueryHelloRequest) (*service.QueryHelloResponse, error) {\n response := &service.QueryHelloResponse{\n Hello: &service.World{\n Id: strconv.Itoa(s.nextID),\n Name: req.Name,\n },\n }\n s.nextID++\n return response, nil\n}\n";
3
+ export declare const mainGoTest = "package main\n\nimport (\n \"context\"\n \"net\"\n \"testing\"\n\n \"github.com/stretchr/testify/assert\"\n \"github.com/stretchr/testify/require\"\n service \"github.com/wundergraph/cosmo/plugin/generated\"\n \"google.golang.org/grpc\"\n \"google.golang.org/grpc/credentials/insecure\"\n \"google.golang.org/grpc/test/bufconn\"\n)\n\nconst bufSize = 1024 * 1024\n\n// testService is a wrapper that holds the gRPC test components\ntype testService struct {\n grpcConn *grpc.ClientConn\n client service.{serviceName}Client\n cleanup func()\n}\n\n// setupTestService creates a local gRPC server for testing\nfunc setupTestService(t *testing.T) *testService {\n // Create a buffer for gRPC connections\n lis := bufconn.Listen(bufSize)\n\n // Create a new gRPC server\n grpcServer := grpc.NewServer()\n\n // Register our service\n service.Register{serviceName}Server(grpcServer, &{serviceName}{\n nextID: 1,\n })\n\n // Start the server\n go func() {\n if err := grpcServer.Serve(lis); err != nil {\n t.Fatalf(\"failed to serve: %v\", err)\n }\n }()\n\n // Create a client connection\n dialer := func(context.Context, string) (net.Conn, error) {\n return lis.Dial()\n }\n conn, err := grpc.Dial(\n \"passthrough:///bufnet\",\n grpc.WithContextDialer(dialer),\n grpc.WithTransportCredentials(insecure.NewCredentials()),\n )\n require.NoError(t, err)\n\n // Create the service client\n client := service.New{serviceName}Client(conn)\n\n // Return cleanup function\n cleanup := func() {\n conn.Close()\n grpcServer.Stop()\n }\n\n return &testService{\n grpcConn: conn,\n client: client,\n cleanup: cleanup,\n }\n}\n\nfunc TestQueryHello(t *testing.T) {\n // Set up basic service\n svc := setupTestService(t)\n defer svc.cleanup()\n\n tests := []struct {\n name string\n userName string\n wantId string\n wantName string\n wantErr bool\n }{\n {\n name: \"valid hello\",\n userName: \"Alice\",\n wantId: \"1\",\n wantName: \"Alice\",\n wantErr: false,\n },\n {\n name: \"empty name\",\n userName: \"\",\n wantId: \"2\",\n wantName: \"\", // Empty name should be preserved\n wantErr: false,\n },\n {\n name: \"special characters\",\n userName: \"John & Jane\",\n wantId: \"3\",\n wantName: \"John & Jane\",\n wantErr: false,\n },\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n req := &service.QueryHelloRequest{\n Name: tt.userName,\n }\n\n resp, err := svc.client.QueryHello(context.Background(), req)\n if tt.wantErr {\n assert.Error(t, err)\n return\n }\n\n assert.NoError(t, err)\n assert.NotNil(t, resp.Hello)\n assert.Equal(t, tt.wantId, resp.Hello.Id)\n assert.Equal(t, tt.wantName, resp.Hello.Name)\n })\n }\n}\n\nfunc TestSequentialIDs(t *testing.T) {\n // Set up basic service\n svc := setupTestService(t)\n defer svc.cleanup()\n\n // The first request should get ID \"1\"\n firstReq := &service.QueryHelloRequest{Name: \"First\"}\n firstResp, err := svc.client.QueryHello(context.Background(), firstReq)\n require.NoError(t, err)\n assert.Equal(t, \"1\", firstResp.Hello.Id)\n\n // The second request should get ID \"2\"\n secondReq := &service.QueryHelloRequest{Name: \"Second\"}\n secondResp, err := svc.client.QueryHello(context.Background(), secondReq)\n require.NoError(t, err)\n assert.Equal(t, \"2\", secondResp.Hello.Id)\n\n // The third request should get ID \"3\"\n thirdReq := &service.QueryHelloRequest{Name: \"Third\"}\n thirdResp, err := svc.client.QueryHello(context.Background(), thirdReq)\n require.NoError(t, err)\n assert.Equal(t, \"3\", thirdResp.Hello.Id)\n}\n";
4
+ export declare const readme = "# {name} Plugin - Cosmo gRPC Subgraph Example\n\nThis repository contains a simple Cosmo gRPC subgraph plugin that showcases how to design APIs with GraphQL but implement them using gRPC methods instead of traditional resolvers.\n\n## What is this demo about?\n\nThis demo illustrates a key pattern in Cosmo subgraph development:\n- **Design with GraphQL**: Define your API using GraphQL schema\n- **Implement with gRPC**: Instead of writing GraphQL resolvers, implement gRPC service methods\n- **Bridge the gap**: The Cosmo router connects GraphQL operations to your gRPC implementations\n- **Test-Driven Development**: Test your gRPC service implementation with gRPC client and server without external dependencies\n\nThe plugin demonstrates:\n- How GraphQL types and operations map to gRPC service methods\n- Simple \"Hello World\" implementation\n- Proper structure for a Cosmo gRPC subgraph plugin\n- How to test your gRPC service implementation with gRPC client and server without external dependencies\n\n## Plugin Structure\n\n- `src/` - Contains the plugin source code\n - `main.go` - The gRPC service implementation with methods that replace GraphQL resolvers\n - `main_test.go` - The gRPC service implementation with methods that replace GraphQL resolvers\n - `schema.graphql` - The GraphQL schema defining the API contract\n- `generated/` - Contains generated code from the plugin schema\n- `bin/` - Contains compiled binaries of the plugin\n\n## GraphQL to gRPC Mapping\n\nThe plugin shows how GraphQL operations map to gRPC methods:\n\n| GraphQL Operation | gRPC Method |\n|-------------------|-------------|\n| `query { hello }` | `QueryHello()` |\n\n## GraphQL Schema\n\n```graphql\ntype World {\n id: ID!\n name: String!\n}\n\ntype Query {\n hello(name: String!): World!\n}\n```\n\n## Getting Started\n\n1. **Build the plugin**\n\n ```\n wgc router plugin build <plugin-directory>\n ```\n\n2. **Compose your supergraph with your gRPC subgraph**\n\n config.yaml\n ```yaml\n subgraphs:\n - name: <plugin-name>\n plugin:\n version: 0.0.1\n directory: <plugin-directory>/<plugin-name>\n ```\n\n3. **Build the federated graph**\n\n ```bash\n wgc router compose config.yaml\n ```\n\n4. **Test the plugin**\n\n ```bash\n wgc router plugin test <plugin-directory>/<plugin-name>\n ```\n or\n ```bash\n go test src -v\n ```\n if you have the Go toolchain already installed.\n\n5. **Start the router**\n\n ```yaml\n execution_config:\n file:\n path: ./config.yaml\n plugins:\n - <plugin-directory>\n ```\n\n6. **Query the hello endpoint**\n\n Once running, you can perform GraphQL operations like:\n \n ```graphql\n # Hello query\n query {\n hello(name: \"World\") {\n id\n name\n }\n }\n ```\n\n## Further Steps\n\n- Change the plugin code in `src/main.go` and rebuild the plugin\n- Change the GraphQL schema in `src/schema.graphql` and rebuild the plugin\n\n## Learn More\n\nFor more information about Cosmo and building subgraph plugins, visit the [Cosmo documentation](https://cosmo-docs.wundergraph.com).";
5
+ export declare const schema = "type World {\n \"\"\"\n The ID of the world\n \"\"\"\n id: ID!\n \"\"\"\n The name of the world\n \"\"\"\n name: String!\n}\n\ntype Query {\n \"\"\"\n The hello query\n \"\"\"\n hello(name: String!): World!\n}\n";
@@ -0,0 +1,342 @@
1
+ // We store the templates in code to avoid dealing with file system issues when
2
+ // building for bun and transpiling TypeScript.
3
+ export const goMod = `
4
+ module {modulePath}
5
+
6
+ go 1.24.1
7
+
8
+ require (
9
+ github.com/stretchr/testify v1.10.0
10
+ github.com/wundergraph/cosmo/router-plugin v0.0.0-20250519132817-0768029ce9e5
11
+ google.golang.org/grpc v1.68.1
12
+ google.golang.org/protobuf v1.36.5
13
+ )
14
+ `;
15
+ export const mainGo = `package main
16
+
17
+ import (
18
+ "context"
19
+ "log"
20
+ "strconv"
21
+
22
+ service "github.com/wundergraph/cosmo/plugin/generated"
23
+
24
+ routerplugin "github.com/wundergraph/cosmo/router-plugin"
25
+ "google.golang.org/grpc"
26
+ )
27
+
28
+ func main() {
29
+ pl, err := routerplugin.NewRouterPlugin(func(s *grpc.Server) {
30
+ s.RegisterService(&service.{serviceName}_ServiceDesc, &{serviceName}{
31
+ nextID: 1,
32
+ })
33
+ })
34
+
35
+ if err != nil {
36
+ log.Fatalf("failed to create router plugin: %v", err)
37
+ }
38
+
39
+ pl.Serve()
40
+ }
41
+
42
+ type {serviceName} struct {
43
+ service.Unimplemented{serviceName}Server
44
+ nextID int
45
+ }
46
+
47
+ func (s *{serviceName}) QueryHello(ctx context.Context, req *service.QueryHelloRequest) (*service.QueryHelloResponse, error) {
48
+ response := &service.QueryHelloResponse{
49
+ Hello: &service.World{
50
+ Id: strconv.Itoa(s.nextID),
51
+ Name: req.Name,
52
+ },
53
+ }
54
+ s.nextID++
55
+ return response, nil
56
+ }
57
+ `;
58
+ export const mainGoTest = `package main
59
+
60
+ import (
61
+ "context"
62
+ "net"
63
+ "testing"
64
+
65
+ "github.com/stretchr/testify/assert"
66
+ "github.com/stretchr/testify/require"
67
+ service "github.com/wundergraph/cosmo/plugin/generated"
68
+ "google.golang.org/grpc"
69
+ "google.golang.org/grpc/credentials/insecure"
70
+ "google.golang.org/grpc/test/bufconn"
71
+ )
72
+
73
+ const bufSize = 1024 * 1024
74
+
75
+ // testService is a wrapper that holds the gRPC test components
76
+ type testService struct {
77
+ grpcConn *grpc.ClientConn
78
+ client service.{serviceName}Client
79
+ cleanup func()
80
+ }
81
+
82
+ // setupTestService creates a local gRPC server for testing
83
+ func setupTestService(t *testing.T) *testService {
84
+ // Create a buffer for gRPC connections
85
+ lis := bufconn.Listen(bufSize)
86
+
87
+ // Create a new gRPC server
88
+ grpcServer := grpc.NewServer()
89
+
90
+ // Register our service
91
+ service.Register{serviceName}Server(grpcServer, &{serviceName}{
92
+ nextID: 1,
93
+ })
94
+
95
+ // Start the server
96
+ go func() {
97
+ if err := grpcServer.Serve(lis); err != nil {
98
+ t.Fatalf("failed to serve: %v", err)
99
+ }
100
+ }()
101
+
102
+ // Create a client connection
103
+ dialer := func(context.Context, string) (net.Conn, error) {
104
+ return lis.Dial()
105
+ }
106
+ conn, err := grpc.Dial(
107
+ "passthrough:///bufnet",
108
+ grpc.WithContextDialer(dialer),
109
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
110
+ )
111
+ require.NoError(t, err)
112
+
113
+ // Create the service client
114
+ client := service.New{serviceName}Client(conn)
115
+
116
+ // Return cleanup function
117
+ cleanup := func() {
118
+ conn.Close()
119
+ grpcServer.Stop()
120
+ }
121
+
122
+ return &testService{
123
+ grpcConn: conn,
124
+ client: client,
125
+ cleanup: cleanup,
126
+ }
127
+ }
128
+
129
+ func TestQueryHello(t *testing.T) {
130
+ // Set up basic service
131
+ svc := setupTestService(t)
132
+ defer svc.cleanup()
133
+
134
+ tests := []struct {
135
+ name string
136
+ userName string
137
+ wantId string
138
+ wantName string
139
+ wantErr bool
140
+ }{
141
+ {
142
+ name: "valid hello",
143
+ userName: "Alice",
144
+ wantId: "1",
145
+ wantName: "Alice",
146
+ wantErr: false,
147
+ },
148
+ {
149
+ name: "empty name",
150
+ userName: "",
151
+ wantId: "2",
152
+ wantName: "", // Empty name should be preserved
153
+ wantErr: false,
154
+ },
155
+ {
156
+ name: "special characters",
157
+ userName: "John & Jane",
158
+ wantId: "3",
159
+ wantName: "John & Jane",
160
+ wantErr: false,
161
+ },
162
+ }
163
+
164
+ for _, tt := range tests {
165
+ t.Run(tt.name, func(t *testing.T) {
166
+ req := &service.QueryHelloRequest{
167
+ Name: tt.userName,
168
+ }
169
+
170
+ resp, err := svc.client.QueryHello(context.Background(), req)
171
+ if tt.wantErr {
172
+ assert.Error(t, err)
173
+ return
174
+ }
175
+
176
+ assert.NoError(t, err)
177
+ assert.NotNil(t, resp.Hello)
178
+ assert.Equal(t, tt.wantId, resp.Hello.Id)
179
+ assert.Equal(t, tt.wantName, resp.Hello.Name)
180
+ })
181
+ }
182
+ }
183
+
184
+ func TestSequentialIDs(t *testing.T) {
185
+ // Set up basic service
186
+ svc := setupTestService(t)
187
+ defer svc.cleanup()
188
+
189
+ // The first request should get ID "1"
190
+ firstReq := &service.QueryHelloRequest{Name: "First"}
191
+ firstResp, err := svc.client.QueryHello(context.Background(), firstReq)
192
+ require.NoError(t, err)
193
+ assert.Equal(t, "1", firstResp.Hello.Id)
194
+
195
+ // The second request should get ID "2"
196
+ secondReq := &service.QueryHelloRequest{Name: "Second"}
197
+ secondResp, err := svc.client.QueryHello(context.Background(), secondReq)
198
+ require.NoError(t, err)
199
+ assert.Equal(t, "2", secondResp.Hello.Id)
200
+
201
+ // The third request should get ID "3"
202
+ thirdReq := &service.QueryHelloRequest{Name: "Third"}
203
+ thirdResp, err := svc.client.QueryHello(context.Background(), thirdReq)
204
+ require.NoError(t, err)
205
+ assert.Equal(t, "3", thirdResp.Hello.Id)
206
+ }
207
+ `;
208
+ export const readme = `# {name} Plugin - Cosmo gRPC Subgraph Example
209
+
210
+ This repository contains a simple Cosmo gRPC subgraph plugin that showcases how to design APIs with GraphQL but implement them using gRPC methods instead of traditional resolvers.
211
+
212
+ ## What is this demo about?
213
+
214
+ This demo illustrates a key pattern in Cosmo subgraph development:
215
+ - **Design with GraphQL**: Define your API using GraphQL schema
216
+ - **Implement with gRPC**: Instead of writing GraphQL resolvers, implement gRPC service methods
217
+ - **Bridge the gap**: The Cosmo router connects GraphQL operations to your gRPC implementations
218
+ - **Test-Driven Development**: Test your gRPC service implementation with gRPC client and server without external dependencies
219
+
220
+ The plugin demonstrates:
221
+ - How GraphQL types and operations map to gRPC service methods
222
+ - Simple "Hello World" implementation
223
+ - Proper structure for a Cosmo gRPC subgraph plugin
224
+ - How to test your gRPC service implementation with gRPC client and server without external dependencies
225
+
226
+ ## Plugin Structure
227
+
228
+ - \`src/\` - Contains the plugin source code
229
+ - \`main.go\` - The gRPC service implementation with methods that replace GraphQL resolvers
230
+ - \`main_test.go\` - The gRPC service implementation with methods that replace GraphQL resolvers
231
+ - \`schema.graphql\` - The GraphQL schema defining the API contract
232
+ - \`generated/\` - Contains generated code from the plugin schema
233
+ - \`bin/\` - Contains compiled binaries of the plugin
234
+
235
+ ## GraphQL to gRPC Mapping
236
+
237
+ The plugin shows how GraphQL operations map to gRPC methods:
238
+
239
+ | GraphQL Operation | gRPC Method |
240
+ |-------------------|-------------|
241
+ | \`query { hello }\` | \`QueryHello()\` |
242
+
243
+ ## GraphQL Schema
244
+
245
+ \`\`\`graphql
246
+ type World {
247
+ id: ID!
248
+ name: String!
249
+ }
250
+
251
+ type Query {
252
+ hello(name: String!): World!
253
+ }
254
+ \`\`\`
255
+
256
+ ## Getting Started
257
+
258
+ 1. **Build the plugin**
259
+
260
+ \`\`\`
261
+ wgc router plugin build <plugin-directory>
262
+ \`\`\`
263
+
264
+ 2. **Compose your supergraph with your gRPC subgraph**
265
+
266
+ config.yaml
267
+ \`\`\`yaml
268
+ subgraphs:
269
+ - name: <plugin-name>
270
+ plugin:
271
+ version: 0.0.1
272
+ directory: <plugin-directory>/<plugin-name>
273
+ \`\`\`
274
+
275
+ 3. **Build the federated graph**
276
+
277
+ \`\`\`bash
278
+ wgc router compose config.yaml
279
+ \`\`\`
280
+
281
+ 4. **Test the plugin**
282
+
283
+ \`\`\`bash
284
+ wgc router plugin test <plugin-directory>/<plugin-name>
285
+ \`\`\`
286
+ or
287
+ \`\`\`bash
288
+ go test src -v
289
+ \`\`\`
290
+ if you have the Go toolchain already installed.
291
+
292
+ 5. **Start the router**
293
+
294
+ \`\`\`yaml
295
+ execution_config:
296
+ file:
297
+ path: ./config.yaml
298
+ plugins:
299
+ - <plugin-directory>
300
+ \`\`\`
301
+
302
+ 6. **Query the hello endpoint**
303
+
304
+ Once running, you can perform GraphQL operations like:
305
+
306
+ \`\`\`graphql
307
+ # Hello query
308
+ query {
309
+ hello(name: "World") {
310
+ id
311
+ name
312
+ }
313
+ }
314
+ \`\`\`
315
+
316
+ ## Further Steps
317
+
318
+ - Change the plugin code in \`src/main.go\` and rebuild the plugin
319
+ - Change the GraphQL schema in \`src/schema.graphql\` and rebuild the plugin
320
+
321
+ ## Learn More
322
+
323
+ For more information about Cosmo and building subgraph plugins, visit the [Cosmo documentation](https://cosmo-docs.wundergraph.com).`;
324
+ export const schema = `type World {
325
+ """
326
+ The ID of the world
327
+ """
328
+ id: ID!
329
+ """
330
+ The name of the world
331
+ """
332
+ name: String!
333
+ }
334
+
335
+ type Query {
336
+ """
337
+ The hello query
338
+ """
339
+ hello(name: String!): World!
340
+ }
341
+ `;
342
+ //# sourceMappingURL=go-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"go-plugin.js","sourceRoot":"","sources":["../../../../../../src/commands/router/plugin/templates/go-plugin.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+CAA+C;AAE/C,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;;;CAWpB,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CrB,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqJzB,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qIAmH+G,CAAC;AAEtI,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;CAiBrB,CAAC"}