nexus-rpc-gen 0.1.0-alpha.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Temporal Technologies Inc. All Rights Reserved
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # nexus-rpc-gen
2
+
3
+ CLI tool for generating code from NexusRPC definition files. This package provides the command-line interface that uses `@nexus-rpc/gen-core` to generate code in multiple languages.
4
+
5
+ For installation, usage, and detailed documentation, see the [main repository README](https://github.com/nexus-rpc/nexus-rpc-gen/blob/main/README.md).
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from "node:url";
3
+ import commandLineArgs, {} from "command-line-args";
4
+ import { languageNamed } from "quicktype-core";
5
+ import esMain from "es-main";
6
+ import { CSharpLanguageWithNexus, Generator, GoLanguageWithNexus, JavaLanguageWithNexus, parseFiles, PythonLanguageWithNexus, TypeScriptLanguageWithNexus, } from "@nexus-rpc/gen-core";
7
+ import getUsage, {} from "command-line-usage";
8
+ import { mkdir, writeFile } from "node:fs/promises";
9
+ import path from "node:path";
10
+ const supportedLanguages = [
11
+ new CSharpLanguageWithNexus(),
12
+ new GoLanguageWithNexus(),
13
+ new JavaLanguageWithNexus(),
14
+ new PythonLanguageWithNexus(),
15
+ // TODO(cretz): new RubyTargetLanguage(),
16
+ // TODO(cretz): new RustTargetLanguage(),
17
+ new TypeScriptLanguageWithNexus(),
18
+ ];
19
+ const commonOptionDefs = [
20
+ { name: "help", alias: "h", type: Boolean, description: "Display help." },
21
+ { name: "lang", description: "The target language." },
22
+ {
23
+ name: "out-dir",
24
+ description: "Out directory. Mutually exclusive with --out-file.",
25
+ },
26
+ {
27
+ name: "out-file",
28
+ description: "Out file. Mutually exclusive with --out-dir.",
29
+ },
30
+ {
31
+ name: "dry-run",
32
+ type: Boolean,
33
+ description: "Dump every file that would be written to stdout instead.",
34
+ },
35
+ { name: "files", multiple: true, defaultOption: true },
36
+ ];
37
+ async function main(argv) {
38
+ // Parse args with just lang and files
39
+ const optionDefs = [...commonOptionDefs];
40
+ let rawOptions = commandLineArgs(optionDefs, { argv, partial: true });
41
+ if (rawOptions.help) {
42
+ printUsage();
43
+ return;
44
+ }
45
+ if (rawOptions.lang == null) {
46
+ printUsage();
47
+ throw new Error("--lang required");
48
+ }
49
+ else if (!rawOptions.files || !rawOptions.files.length) {
50
+ printUsage();
51
+ throw new Error("At least one file required");
52
+ }
53
+ else if (rawOptions["out-dir"] && rawOptions["out-file"]) {
54
+ printUsage();
55
+ throw new Error("Cannot provide both --out-dir and --out-file");
56
+ }
57
+ const lang = languageNamed(rawOptions.lang, supportedLanguages);
58
+ // Now parse args with language-specific options
59
+ optionDefs.push(...lang.cliOptionDefinitions.actual);
60
+ rawOptions = commandLineArgs(optionDefs, { argv });
61
+ // Parse/validate YAML files
62
+ const schema = await parseFiles(rawOptions.files);
63
+ // Convert args to generator options structure
64
+ const genOptions = {
65
+ lang,
66
+ schema,
67
+ rendererOptions: {},
68
+ firstFilenameSansExtensions: path
69
+ .basename(rawOptions.files[0])
70
+ .split(".")[0],
71
+ };
72
+ for (const defn of lang.cliOptionDefinitions.actual) {
73
+ const untypedRenderOptions = genOptions.rendererOptions;
74
+ untypedRenderOptions[defn.name] = rawOptions[defn.name];
75
+ }
76
+ // Run generator
77
+ const results = Object.entries(await new Generator(genOptions).generate());
78
+ // Make result filenames absolute
79
+ for (let index = 0; index < results.length; index++) {
80
+ if (rawOptions["out-dir"]) {
81
+ results[index][0] = path.join(rawOptions["out-dir"], results[index][0]);
82
+ }
83
+ else if (!rawOptions["dry-run"] && index > 0) {
84
+ // Cannot use stdout or out-file in multi-file scenarios
85
+ throw new Error(`Generated ${results.length} files, must use --out-dir`);
86
+ }
87
+ else if (rawOptions["out-file"]) {
88
+ // Always overwrite filename, and disallow this arg in multi-file scenarios
89
+ if (index > 0) {
90
+ throw new Error(`Generated ${results.length} files, cannot provide single-file --out-file, use --out-dir`);
91
+ }
92
+ results[index][0] = rawOptions["out-file"];
93
+ }
94
+ }
95
+ // Dump
96
+ for (const [filePath, fileContents] of results) {
97
+ // Write with log for dry-run, stdout if no out, and file otherwise
98
+ if (rawOptions["dry-run"]) {
99
+ console.log(`--- ${filePath} ---\n${fileContents}\n-------`);
100
+ }
101
+ else if (!rawOptions["out-dir"] && !rawOptions["out-file"]) {
102
+ // Note, validation from before means this only happens on single-file results
103
+ process.stdout.write(fileContents);
104
+ }
105
+ else {
106
+ // We make dirs lazily in dir-based scenarios
107
+ if (rawOptions["out-dir"]) {
108
+ await mkdir(path.dirname(filePath), { recursive: true });
109
+ }
110
+ console.log(`Writing ${filePath}`);
111
+ await writeFile(filePath, fileContents);
112
+ }
113
+ }
114
+ }
115
+ function printUsage() {
116
+ // Show shortest lang form
117
+ const langNames = supportedLanguages.flatMap((l) => l.names.reduce((a, b) => (a.length <= b.length ? a : b)));
118
+ // Common sections
119
+ const tableOptions = {
120
+ columns: [
121
+ { name: "option", width: 60 },
122
+ { name: "description", width: 60 },
123
+ ],
124
+ };
125
+ const sections = [
126
+ {
127
+ header: "Synopsis",
128
+ content: [
129
+ `$ nexus-rpc-gen [--lang LANG] [--out FILE/DIR] SCHEMA_FILE|URL ...`,
130
+ "",
131
+ ` LANG ... ${langNames.join("|")}`,
132
+ ],
133
+ },
134
+ {
135
+ header: "Description",
136
+ content: "Generate code from Nexus RPC definition file.",
137
+ },
138
+ {
139
+ header: "Options",
140
+ optionList: commonOptionDefs,
141
+ hide: ["files"],
142
+ tableOptions,
143
+ },
144
+ ];
145
+ // Add per-language sections
146
+ for (const lang of supportedLanguages) {
147
+ sections.push({
148
+ header: `Options for ${lang.displayName}`,
149
+ optionList: lang.cliOptionDefinitions.display,
150
+ tableOptions,
151
+ });
152
+ }
153
+ // Dump
154
+ console.log(getUsage(sections));
155
+ }
156
+ // Node 22.18+ and 24.2+ provides `import.meta.main`, which deprecates the need for
157
+ // this npm library. Bun and Deno have had that API for a long time already. But until
158
+ // we're ready to drop older versions of Node, we need this library to support it.
159
+ if (import.meta.main || esMain(import.meta)) {
160
+ try {
161
+ await main(process.argv.slice(2));
162
+ }
163
+ catch (error) {
164
+ if (process.env.NEXUS_IDL_DEBUG) {
165
+ console.error(error);
166
+ }
167
+ else {
168
+ console.error(`${error}`);
169
+ }
170
+ process.exit(1);
171
+ }
172
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "nexus-rpc-gen",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "Nexus code generation",
5
+ "author": "Temporal Technologies Inc. <sdk@temporal.io>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "bin": "dist/index.js",
11
+ "engines": {
12
+ "node": ">= 18.0.0"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/nexus-rpc/nexus-rpc-gen/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/nexus-rpc/nexus-rpc-gen.git",
20
+ "directory": "src/packages/nexus-rpc-gen"
21
+ },
22
+ "homepage": "https://github.com/nexus-rpc/nexus-rpc-gen/tree/main/src/packages/nexus-rpc-gen",
23
+ "dependencies": {
24
+ "arg": "^5.0.2",
25
+ "command-line-args": "^6.0.1",
26
+ "command-line-usage": "^7.0.3",
27
+ "es-main": "^1.4.0",
28
+ "quicktype-core": "^23.2.6",
29
+ "@nexus-rpc/gen-core": "0.1.0-alpha.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/command-line-args": "^5.2.3",
33
+ "@types/command-line-usage": "^5.0.4",
34
+ "@types/node": "^24.7.0",
35
+ "quicktype": "^23.2.6",
36
+ "tsx": "^4.21.0"
37
+ },
38
+ "files": [
39
+ "README.md",
40
+ "src",
41
+ "dist"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build:schema": "tsx scripts/build-schema.ts"
48
+ }
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { pathToFileURL } from "node:url";
4
+ import commandLineArgs, { type OptionDefinition } from "command-line-args";
5
+ import { languageNamed, type LanguageName } from "quicktype-core";
6
+ import esMain from "es-main";
7
+ import {
8
+ CSharpLanguageWithNexus,
9
+ Generator,
10
+ GoLanguageWithNexus,
11
+ JavaLanguageWithNexus,
12
+ parseFiles,
13
+ PythonLanguageWithNexus,
14
+ TypeScriptLanguageWithNexus,
15
+ type GeneratorOptions,
16
+ } from "@nexus-rpc/gen-core";
17
+ import getUsage, { type Section } from "command-line-usage";
18
+ import { mkdir, writeFile } from "node:fs/promises";
19
+ import path from "node:path";
20
+
21
+ const supportedLanguages = [
22
+ new CSharpLanguageWithNexus(),
23
+ new GoLanguageWithNexus(),
24
+ new JavaLanguageWithNexus(),
25
+ new PythonLanguageWithNexus(),
26
+ // TODO(cretz): new RubyTargetLanguage(),
27
+ // TODO(cretz): new RustTargetLanguage(),
28
+ new TypeScriptLanguageWithNexus(),
29
+ ];
30
+
31
+ const commonOptionDefs = [
32
+ { name: "help", alias: "h", type: Boolean, description: "Display help." },
33
+ { name: "lang", description: "The target language." },
34
+ {
35
+ name: "out-dir",
36
+ description: "Out directory. Mutually exclusive with --out-file.",
37
+ },
38
+ {
39
+ name: "out-file",
40
+ description: "Out file. Mutually exclusive with --out-dir.",
41
+ },
42
+ {
43
+ name: "dry-run",
44
+ type: Boolean,
45
+ description: "Dump every file that would be written to stdout instead.",
46
+ },
47
+ { name: "files", multiple: true, defaultOption: true },
48
+ ];
49
+
50
+ async function main(argv: string[]) {
51
+ // Parse args with just lang and files
52
+ const optionDefs: OptionDefinition[] = [...commonOptionDefs];
53
+ let rawOptions = commandLineArgs(optionDefs, { argv, partial: true });
54
+ if (rawOptions.help) {
55
+ printUsage();
56
+ return;
57
+ }
58
+ if (rawOptions.lang == null) {
59
+ printUsage();
60
+ throw new Error("--lang required");
61
+ } else if (!rawOptions.files || !rawOptions.files.length) {
62
+ printUsage();
63
+ throw new Error("At least one file required");
64
+ } else if (rawOptions["out-dir"] && rawOptions["out-file"]) {
65
+ printUsage();
66
+ throw new Error("Cannot provide both --out-dir and --out-file");
67
+ }
68
+ const lang = languageNamed(
69
+ rawOptions.lang as LanguageName,
70
+ supportedLanguages,
71
+ );
72
+
73
+ // Now parse args with language-specific options
74
+ optionDefs.push(...lang.cliOptionDefinitions.actual);
75
+ rawOptions = commandLineArgs(optionDefs, { argv });
76
+
77
+ // Parse/validate YAML files
78
+ const schema = await parseFiles(rawOptions.files);
79
+
80
+ // Convert args to generator options structure
81
+ const genOptions: GeneratorOptions = {
82
+ lang,
83
+ schema,
84
+ rendererOptions: {},
85
+ firstFilenameSansExtensions: path
86
+ .basename(rawOptions.files[0])
87
+ .split(".")[0],
88
+ };
89
+ for (const defn of lang.cliOptionDefinitions.actual) {
90
+ const untypedRenderOptions = genOptions.rendererOptions as Record<
91
+ typeof defn.name,
92
+ unknown
93
+ >;
94
+ untypedRenderOptions[defn.name] = rawOptions[defn.name];
95
+ }
96
+
97
+ // Run generator
98
+ const results = Object.entries(await new Generator(genOptions).generate());
99
+
100
+ // Make result filenames absolute
101
+ for (let index = 0; index < results.length; index++) {
102
+ if (rawOptions["out-dir"]) {
103
+ results[index][0] = path.join(rawOptions["out-dir"], results[index][0]);
104
+ } else if (!rawOptions["dry-run"] && index > 0) {
105
+ // Cannot use stdout or out-file in multi-file scenarios
106
+ throw new Error(`Generated ${results.length} files, must use --out-dir`);
107
+ } else if (rawOptions["out-file"]) {
108
+ // Always overwrite filename, and disallow this arg in multi-file scenarios
109
+ if (index > 0) {
110
+ throw new Error(
111
+ `Generated ${results.length} files, cannot provide single-file --out-file, use --out-dir`,
112
+ );
113
+ }
114
+ results[index][0] = rawOptions["out-file"];
115
+ }
116
+ }
117
+
118
+ // Dump
119
+ for (const [filePath, fileContents] of results) {
120
+ // Write with log for dry-run, stdout if no out, and file otherwise
121
+ if (rawOptions["dry-run"]) {
122
+ console.log(`--- ${filePath} ---\n${fileContents}\n-------`);
123
+ } else if (!rawOptions["out-dir"] && !rawOptions["out-file"]) {
124
+ // Note, validation from before means this only happens on single-file results
125
+ process.stdout.write(fileContents);
126
+ } else {
127
+ // We make dirs lazily in dir-based scenarios
128
+ if (rawOptions["out-dir"]) {
129
+ await mkdir(path.dirname(filePath), { recursive: true });
130
+ }
131
+ console.log(`Writing ${filePath}`);
132
+ await writeFile(filePath, fileContents);
133
+ }
134
+ }
135
+ }
136
+
137
+ function printUsage() {
138
+ // Show shortest lang form
139
+ const langNames = supportedLanguages.flatMap((l) =>
140
+ l.names.reduce((a, b) => (a.length <= b.length ? a : b)),
141
+ );
142
+ // Common sections
143
+ const tableOptions = {
144
+ columns: [
145
+ { name: "option", width: 60 },
146
+ { name: "description", width: 60 },
147
+ ],
148
+ };
149
+ const sections: Section[] = [
150
+ {
151
+ header: "Synopsis",
152
+ content: [
153
+ `$ nexus-rpc-gen [--lang LANG] [--out FILE/DIR] SCHEMA_FILE|URL ...`,
154
+ "",
155
+ ` LANG ... ${langNames.join("|")}`,
156
+ ],
157
+ },
158
+ {
159
+ header: "Description",
160
+ content: "Generate code from Nexus RPC definition file.",
161
+ },
162
+ {
163
+ header: "Options",
164
+ optionList: commonOptionDefs,
165
+ hide: ["files"],
166
+ tableOptions,
167
+ },
168
+ ];
169
+ // Add per-language sections
170
+ for (const lang of supportedLanguages) {
171
+ sections.push({
172
+ header: `Options for ${lang.displayName}`,
173
+ optionList: lang.cliOptionDefinitions.display,
174
+ tableOptions,
175
+ });
176
+ }
177
+ // Dump
178
+ console.log(getUsage(sections));
179
+ }
180
+
181
+ // Node 22.18+ and 24.2+ provides `import.meta.main`, which deprecates the need for
182
+ // this npm library. Bun and Deno have had that API for a long time already. But until
183
+ // we're ready to drop older versions of Node, we need this library to support it.
184
+ if (import.meta.main || esMain(import.meta)) {
185
+ try {
186
+ await main(process.argv.slice(2));
187
+ } catch (error) {
188
+ if (process.env.NEXUS_IDL_DEBUG) {
189
+ console.error(error);
190
+ } else {
191
+ console.error(`${error}`);
192
+ }
193
+ process.exit(1);
194
+ }
195
+ }