nexus-rpc-gen 0.1.0-alpha0

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.
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import commandLineArgs, {} from "command-line-args";
3
+ import { languageNamed } from "quicktype-core";
4
+ import { CSharpLanguageWithNexus, Generator, GoLanguageWithNexus, JavaLanguageWithNexus, parseFiles, PythonLanguageWithNexus, TypeScriptLanguageWithNexus, } from "@nexus-rpc/gen-core";
5
+ import getUsage, {} from "command-line-usage";
6
+ import { mkdir, writeFile } from "node:fs/promises";
7
+ import path from "node:path";
8
+ const supportedLanguages = [
9
+ new CSharpLanguageWithNexus(),
10
+ new GoLanguageWithNexus(),
11
+ new JavaLanguageWithNexus(),
12
+ new PythonLanguageWithNexus(),
13
+ // TODO(cretz): new RubyTargetLanguage(),
14
+ // TODO(cretz): new RustTargetLanguage(),
15
+ new TypeScriptLanguageWithNexus(),
16
+ ];
17
+ const commonOptionDefs = [
18
+ { name: "help", alias: "h", type: Boolean, description: "Display help." },
19
+ { name: "lang", description: "The target language." },
20
+ {
21
+ name: "out-dir",
22
+ description: "Out directory. Mutually exclusive with --out-file.",
23
+ },
24
+ {
25
+ name: "out-file",
26
+ description: "Out file. Mutually exclusive with --out-dir.",
27
+ },
28
+ {
29
+ name: "dry-run",
30
+ type: Boolean,
31
+ description: "Dump every file that would be written to stdout instead.",
32
+ },
33
+ { name: "files", multiple: true, defaultOption: true },
34
+ ];
35
+ async function main(argv) {
36
+ // Parse args with just lang and files
37
+ const optionDefs = [...commonOptionDefs];
38
+ let rawOptions = commandLineArgs(optionDefs, { argv, partial: true });
39
+ if (rawOptions.help) {
40
+ printUsage();
41
+ return;
42
+ }
43
+ if (rawOptions.lang == null) {
44
+ printUsage();
45
+ throw new Error("--lang required");
46
+ }
47
+ else if (!rawOptions.files || !rawOptions.files.length) {
48
+ printUsage();
49
+ throw new Error("At least one file required");
50
+ }
51
+ else if (rawOptions["out-dir"] && rawOptions["out-file"]) {
52
+ printUsage();
53
+ throw new Error("Cannot provide both --out-dir and --out-file");
54
+ }
55
+ const lang = languageNamed(rawOptions.lang, supportedLanguages);
56
+ // Now parse args with language-specific options
57
+ optionDefs.push(...lang.cliOptionDefinitions.actual);
58
+ rawOptions = commandLineArgs(optionDefs, { argv });
59
+ // Parse/validate YAML files
60
+ const schema = await parseFiles(rawOptions.files);
61
+ // Convert args to generator options structure
62
+ const genOptions = {
63
+ lang,
64
+ schema,
65
+ rendererOptions: {},
66
+ firstFilenameSansExtensions: path
67
+ .basename(rawOptions.files[0])
68
+ .split(".")[0],
69
+ };
70
+ for (const defn of lang.cliOptionDefinitions.actual) {
71
+ const untypedRenderOptions = genOptions.rendererOptions;
72
+ untypedRenderOptions[defn.name] = rawOptions[defn.name];
73
+ }
74
+ // Run generator
75
+ const results = Object.entries(await new Generator(genOptions).generate());
76
+ // Make result filenames absolute
77
+ for (let index = 0; index < results.length; index++) {
78
+ if (rawOptions["out-dir"]) {
79
+ results[index][0] = path.join(rawOptions["out-dir"], results[index][0]);
80
+ }
81
+ else if (!rawOptions["dry-run"] && index > 0) {
82
+ // Cannot use stdout or out-file in multi-file scenarios
83
+ throw new Error(`Generated ${results.length} files, must use --out-dir`);
84
+ }
85
+ else if (rawOptions["out-file"]) {
86
+ // Always overwrite filename, and disallow this arg in multi-file scenarios
87
+ if (index > 0) {
88
+ throw new Error(`Generated ${results.length} files, cannot provide single-file --out-file, use --out-dir`);
89
+ }
90
+ results[index][0] = rawOptions["out-file"];
91
+ }
92
+ }
93
+ // Dump
94
+ for (const [filePath, fileContents] of results) {
95
+ // Write with log for dry-run, stdout if no out, and file otherwise
96
+ if (rawOptions["dry-run"]) {
97
+ console.log(`--- ${filePath} ---\n${fileContents}\n-------`);
98
+ }
99
+ else if (!rawOptions["out-dir"] && !rawOptions["out-file"]) {
100
+ // Note, validation from before means this only happens on single-file results
101
+ process.stdout.write(fileContents);
102
+ }
103
+ else {
104
+ // We make dirs lazily in dir-based scenarios
105
+ if (rawOptions["out-dir"]) {
106
+ await mkdir(path.dirname(filePath), { recursive: true });
107
+ }
108
+ console.log(`Writing ${filePath}`);
109
+ await writeFile(filePath, fileContents);
110
+ }
111
+ }
112
+ }
113
+ function printUsage() {
114
+ // Show shortest lang form
115
+ const langNames = supportedLanguages.flatMap((l) => l.names.reduce((a, b) => (a.length <= b.length ? a : b)));
116
+ // Common sections
117
+ const tableOptions = {
118
+ columns: [
119
+ { name: "option", width: 60 },
120
+ { name: "description", width: 60 },
121
+ ],
122
+ };
123
+ const sections = [
124
+ {
125
+ header: "Synopsis",
126
+ content: [
127
+ `$ nexus-rpc-gen [--lang LANG] [--out FILE/DIR] SCHEMA_FILE|URL ...`,
128
+ "",
129
+ ` LANG ... ${langNames.join("|")}`,
130
+ ],
131
+ },
132
+ {
133
+ header: "Description",
134
+ content: "Generate code from Nexus RPC definition file.",
135
+ },
136
+ {
137
+ header: "Options",
138
+ optionList: commonOptionDefs,
139
+ hide: ["files"],
140
+ tableOptions,
141
+ },
142
+ ];
143
+ // Add per-language sections
144
+ for (const lang of supportedLanguages) {
145
+ sections.push({
146
+ header: `Options for ${lang.displayName}`,
147
+ optionList: lang.cliOptionDefinitions.display,
148
+ tableOptions,
149
+ });
150
+ }
151
+ // Dump
152
+ console.log(getUsage(sections));
153
+ }
154
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
155
+ try {
156
+ await main(process.argv.slice(2));
157
+ }
158
+ catch (error) {
159
+ if (process.env.NEXUS_IDL_DEBUG) {
160
+ console.error(error);
161
+ }
162
+ else {
163
+ console.error(`${error}`);
164
+ }
165
+ process.exit(1);
166
+ }
167
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "nexus-rpc-gen",
3
+ "version": "0.1.0-alpha0",
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
+ "engines": {
11
+ "node": ">= 18.0.0"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/nexus-rpc/nexus-rpc-gen/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/nexus-rpc/nexus-rpc-gen.git",
19
+ "directory": "src/packages/nexus-rpc-gen"
20
+ },
21
+ "homepage": "https://github.com/nexus-rpc/nexus-rpc-gen/tree/main/src/packages/nexus-rpc-gen",
22
+ "dependencies": {
23
+ "arg": "^5.0.2",
24
+ "command-line-args": "^6.0.1",
25
+ "command-line-usage": "^7.0.3",
26
+ "quicktype-core": "^23.2.6",
27
+ "@nexus-rpc/gen-core": "0.1.0-alpha0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/command-line-args": "^5.2.3",
31
+ "@types/command-line-usage": "^5.0.4",
32
+ "@types/node": "^24.7.0",
33
+ "quicktype": "^23.2.6",
34
+ "tsx": "^4.21.0"
35
+ },
36
+ "files": [
37
+ "src",
38
+ "dist"
39
+ ],
40
+ "scripts": {
41
+ "build:schema": "tsx scripts/build-schema.ts"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,189 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import commandLineArgs, { type OptionDefinition } from "command-line-args";
3
+ import { languageNamed, type LanguageName } from "quicktype-core";
4
+ import {
5
+ CSharpLanguageWithNexus,
6
+ Generator,
7
+ GoLanguageWithNexus,
8
+ JavaLanguageWithNexus,
9
+ parseFiles,
10
+ PythonLanguageWithNexus,
11
+ TypeScriptLanguageWithNexus,
12
+ type GeneratorOptions,
13
+ } from "@nexus-rpc/gen-core";
14
+ import getUsage, { type Section } from "command-line-usage";
15
+ import { mkdir, writeFile } from "node:fs/promises";
16
+ import path from "node:path";
17
+
18
+ const supportedLanguages = [
19
+ new CSharpLanguageWithNexus(),
20
+ new GoLanguageWithNexus(),
21
+ new JavaLanguageWithNexus(),
22
+ new PythonLanguageWithNexus(),
23
+ // TODO(cretz): new RubyTargetLanguage(),
24
+ // TODO(cretz): new RustTargetLanguage(),
25
+ new TypeScriptLanguageWithNexus(),
26
+ ];
27
+
28
+ const commonOptionDefs = [
29
+ { name: "help", alias: "h", type: Boolean, description: "Display help." },
30
+ { name: "lang", description: "The target language." },
31
+ {
32
+ name: "out-dir",
33
+ description: "Out directory. Mutually exclusive with --out-file.",
34
+ },
35
+ {
36
+ name: "out-file",
37
+ description: "Out file. Mutually exclusive with --out-dir.",
38
+ },
39
+ {
40
+ name: "dry-run",
41
+ type: Boolean,
42
+ description: "Dump every file that would be written to stdout instead.",
43
+ },
44
+ { name: "files", multiple: true, defaultOption: true },
45
+ ];
46
+
47
+ async function main(argv: string[]) {
48
+ // Parse args with just lang and files
49
+ const optionDefs: OptionDefinition[] = [...commonOptionDefs];
50
+ let rawOptions = commandLineArgs(optionDefs, { argv, partial: true });
51
+ if (rawOptions.help) {
52
+ printUsage();
53
+ return;
54
+ }
55
+ if (rawOptions.lang == null) {
56
+ printUsage();
57
+ throw new Error("--lang required");
58
+ } else if (!rawOptions.files || !rawOptions.files.length) {
59
+ printUsage();
60
+ throw new Error("At least one file required");
61
+ } else if (rawOptions["out-dir"] && rawOptions["out-file"]) {
62
+ printUsage();
63
+ throw new Error("Cannot provide both --out-dir and --out-file");
64
+ }
65
+ const lang = languageNamed(
66
+ rawOptions.lang as LanguageName,
67
+ supportedLanguages,
68
+ );
69
+
70
+ // Now parse args with language-specific options
71
+ optionDefs.push(...lang.cliOptionDefinitions.actual);
72
+ rawOptions = commandLineArgs(optionDefs, { argv });
73
+
74
+ // Parse/validate YAML files
75
+ const schema = await parseFiles(rawOptions.files);
76
+
77
+ // Convert args to generator options structure
78
+ const genOptions: GeneratorOptions = {
79
+ lang,
80
+ schema,
81
+ rendererOptions: {},
82
+ firstFilenameSansExtensions: path
83
+ .basename(rawOptions.files[0])
84
+ .split(".")[0],
85
+ };
86
+ for (const defn of lang.cliOptionDefinitions.actual) {
87
+ const untypedRenderOptions = genOptions.rendererOptions as Record<
88
+ typeof defn.name,
89
+ unknown
90
+ >;
91
+ untypedRenderOptions[defn.name] = rawOptions[defn.name];
92
+ }
93
+
94
+ // Run generator
95
+ const results = Object.entries(await new Generator(genOptions).generate());
96
+
97
+ // Make result filenames absolute
98
+ for (let index = 0; index < results.length; index++) {
99
+ if (rawOptions["out-dir"]) {
100
+ results[index][0] = path.join(rawOptions["out-dir"], results[index][0]);
101
+ } else if (!rawOptions["dry-run"] && index > 0) {
102
+ // Cannot use stdout or out-file in multi-file scenarios
103
+ throw new Error(`Generated ${results.length} files, must use --out-dir`);
104
+ } else if (rawOptions["out-file"]) {
105
+ // Always overwrite filename, and disallow this arg in multi-file scenarios
106
+ if (index > 0) {
107
+ throw new Error(
108
+ `Generated ${results.length} files, cannot provide single-file --out-file, use --out-dir`,
109
+ );
110
+ }
111
+ results[index][0] = rawOptions["out-file"];
112
+ }
113
+ }
114
+
115
+ // Dump
116
+ for (const [filePath, fileContents] of results) {
117
+ // Write with log for dry-run, stdout if no out, and file otherwise
118
+ if (rawOptions["dry-run"]) {
119
+ console.log(`--- ${filePath} ---\n${fileContents}\n-------`);
120
+ } else if (!rawOptions["out-dir"] && !rawOptions["out-file"]) {
121
+ // Note, validation from before means this only happens on single-file results
122
+ process.stdout.write(fileContents);
123
+ } else {
124
+ // We make dirs lazily in dir-based scenarios
125
+ if (rawOptions["out-dir"]) {
126
+ await mkdir(path.dirname(filePath), { recursive: true });
127
+ }
128
+ console.log(`Writing ${filePath}`);
129
+ await writeFile(filePath, fileContents);
130
+ }
131
+ }
132
+ }
133
+
134
+ function printUsage() {
135
+ // Show shortest lang form
136
+ const langNames = supportedLanguages.flatMap((l) =>
137
+ l.names.reduce((a, b) => (a.length <= b.length ? a : b)),
138
+ );
139
+ // Common sections
140
+ const tableOptions = {
141
+ columns: [
142
+ { name: "option", width: 60 },
143
+ { name: "description", width: 60 },
144
+ ],
145
+ };
146
+ const sections: Section[] = [
147
+ {
148
+ header: "Synopsis",
149
+ content: [
150
+ `$ nexus-rpc-gen [--lang LANG] [--out FILE/DIR] SCHEMA_FILE|URL ...`,
151
+ "",
152
+ ` LANG ... ${langNames.join("|")}`,
153
+ ],
154
+ },
155
+ {
156
+ header: "Description",
157
+ content: "Generate code from Nexus RPC definition file.",
158
+ },
159
+ {
160
+ header: "Options",
161
+ optionList: commonOptionDefs,
162
+ hide: ["files"],
163
+ tableOptions,
164
+ },
165
+ ];
166
+ // Add per-language sections
167
+ for (const lang of supportedLanguages) {
168
+ sections.push({
169
+ header: `Options for ${lang.displayName}`,
170
+ optionList: lang.cliOptionDefinitions.display,
171
+ tableOptions,
172
+ });
173
+ }
174
+ // Dump
175
+ console.log(getUsage(sections));
176
+ }
177
+
178
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
179
+ try {
180
+ await main(process.argv.slice(2));
181
+ } catch (error) {
182
+ if (process.env.NEXUS_IDL_DEBUG) {
183
+ console.error(error);
184
+ } else {
185
+ console.error(`${error}`);
186
+ }
187
+ process.exit(1);
188
+ }
189
+ }