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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +167 -0
- package/package.json +43 -0
- package/src/index.ts +189 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|