Skip to content

Commit 08f290f

Browse files
authored
Convert to ESM (#846)
Also drops Node 12 support
1 parent 03bf0ac commit 08f290f

26 files changed

+954
-1897
lines changed
File renamed without changes.

.github/workflows/pull_request.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
test:
1414
strategy:
1515
matrix:
16-
node-version: [12.x, 14.x, 16.x]
16+
node-version: [14.x, 16.x, 17.x]
1717
os: [macos-latest, ubuntu-latest, windows-latest]
1818
runs-on: ${{ matrix.os }}
1919
steps:
@@ -23,10 +23,3 @@ jobs:
2323
node-version: ${{ matrix.node-version }}
2424
- run: npm ci
2525
- run: npm run test:coverage
26-
typecheck:
27-
runs-on: ubuntu-latest
28-
steps:
29-
- uses: actions/checkout@v2
30-
- uses: actions/setup-node@v2
31-
- run: npm ci
32-
- run: npm run typecheck

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ _Thanks to [@psmyrdek](https://github.com/psmyrdek) for the remote spec feature!
6969
Import any top-level item from the generated spec to use it. It works best if you also alias types to save on typing:
7070

7171
```ts
72-
import { components } from './generated-schema.ts';
72+
import { components } from "./generated-schema.ts";
7373

7474
type APIResponse = components["schemas"]["APIResponse"];
7575
```
@@ -79,7 +79,7 @@ Because OpenAPI schemas may have invalid TypeScript characters as names, the squ
7979
Also note that there’s a special `operations` interface that you can import `OperationObjects` by their [operationId][openapi-operationid]:
8080

8181
```ts
82-
import { operations } from './generated-schema.ts';
82+
import { operations } from "./generated-schema.ts";
8383

8484
type getUsersById = operations["getUsersById"];
8585
```
@@ -93,16 +93,16 @@ _Thanks to [@gr2m](https://github.com/gr2m) for the operations feature!_
9393
The generated spec can also be used with [openapi-typescript-fetch](https://www.npmjs.com/package/openapi-typescript-fetch) which implements a typed fetch client for openapi-typescript.
9494

9595
```ts
96-
import { paths } from './petstore'
96+
import { paths } from "./petstore";
9797

98-
import { Fetcher } from 'openapi-typescript-fetch'
98+
import { Fetcher } from "openapi-typescript-fetch";
9999

100100
// declare fetcher for paths
101101
const fetcher = Fetcher.for<paths>()
102102

103103
// global configuration
104104
fetcher.configure({
105-
baseUrl: 'https://petstore.swagger.io/v2',
105+
baseUrl: "https://petstore.swagger.io/v2",
106106
init: {
107107
headers: {
108108
...
@@ -112,19 +112,19 @@ fetcher.configure({
112112
})
113113

114114
// create fetch operations
115-
const findPetsByStatus = fetcher.path('/pet/findByStatus').method('get').create()
116-
const addPet = fetcher.path('/pet').method('post').create()
115+
const findPetsByStatus = fetcher.path("/pet/findByStatus").method("get").create()
116+
const addPet = fetcher.path("/pet").method("post").create()
117117

118118
// fetch
119119
try {
120120
const { status, data: pets } = await findPetsByStatus({
121-
status: ['available', 'pending'],
121+
status: ["available", "pending"],
122122
})
123123
await addPet({ ... })
124124
} catch(e) {
125125
// check which operation threw the exception
126126
if (e instanceof addPet.Error) {
127-
// get discriminated union { status, data }
127+
// get discriminated union { status, data }
128128
const error = e.getActualType()
129129
if (error.status === 400) {
130130
error.data.validationErrors // 400 response data
@@ -167,19 +167,19 @@ npm i --save-dev openapi-typescript
167167
```
168168

169169
```js
170-
const fs = require("fs");
171-
const openapiTS = require("openapi-typescript").default;
170+
import fs from "fs";
171+
import openapiTS from "openapi-typescript";
172172

173173
// example 1: load [object] as schema (JSON only)
174174
const schema = await fs.promises.readFile("spec.json", "utf8") // must be OpenAPI JSON
175175
const output = await openapiTS(JSON.parse(schema));
176176

177177
// example 2: load [string] as local file (YAML or JSON; released in v4.0)
178-
const localPath = path.join(__dirname, 'spec.yaml'); // may be YAML or JSON format
178+
const localPath = new URL("./spec.yaml", import.meta.url); // may be YAML or JSON format
179179
const output = await openapiTS(localPath);
180180

181181
// example 3: load [string] as remote URL (YAML or JSON; released in v4.0)
182-
const output = await openapiTS('https://myurl.com/v1/openapi.yaml');
182+
const output = await openapiTS("https://myurl.com/v1/openapi.yaml");
183183
```
184184
185185
The Node API may be useful if dealing with dynamically-created schemas, or you’re using within context of a larger application. Pass in either a JSON-friendly object to load a schema from memory, or a string to load a schema from a local file or remote URL (it will load the file quickly using built-in Node methods). Note that a YAML string isn’t supported in the Node.js API; either use the CLI or convert to JSON using [js-yaml][js-yaml] first.
@@ -204,7 +204,7 @@ By default, this will generate a type `updated_at?: string;`. But we can overrid
204204
```js
205205
const types = openapiTS(mySchema, {
206206
formatter(node: SchemaObject) {
207-
if (node.format === 'date-time') {
207+
if (node.format === "date-time") {
208208
return "Date"; // return the TypeScript “Date” type, as a string
209209
}
210210
// for all other schema objects, let openapi-typescript decide (return undefined)

bin/cli.js

Lines changed: 64 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
#!/usr/bin/env node
22

3-
const fs = require("fs");
4-
const { bold, green, red } = require("kleur");
5-
const meow = require("meow");
6-
const path = require("path");
7-
const glob = require("tiny-glob");
8-
const { default: openapiTS } = require("../dist/cjs/index.js");
9-
10-
const cli = meow(
11-
`Usage
3+
import fs from "fs";
4+
import path from "path";
5+
import glob from "tiny-glob";
6+
import parser from "yargs-parser";
7+
import openapiTS from "../dist/esm/index.js";
8+
9+
const GREEN = "\u001b[32m";
10+
const BOLD = "\u001b[1m";
11+
const RESET = "\u001b[0m";
12+
13+
const HELP = `Usage
1214
$ openapi-typescript [input] [options]
1315
1416
Options
@@ -24,54 +26,7 @@ Options
2426
--prettier-config, -c (optional) specify path to Prettier config file
2527
--raw-schema (optional) Parse as partial schema (raw components)
2628
--version (optional) Force schema parsing version
27-
`,
28-
{
29-
flags: {
30-
output: {
31-
type: "string",
32-
alias: "o",
33-
},
34-
auth: {
35-
type: "string",
36-
},
37-
headersObject: {
38-
type: "string",
39-
alias: "h",
40-
},
41-
header: {
42-
type: "string",
43-
alias: "x",
44-
isMultiple: true,
45-
},
46-
httpMethod: {
47-
type: "string",
48-
alias: "m",
49-
default: "GET",
50-
},
51-
immutableTypes: {
52-
type: "boolean",
53-
alias: "it",
54-
},
55-
defaultNonNullable: {
56-
type: "boolean",
57-
},
58-
additionalProperties: {
59-
type: "boolean",
60-
alias: "ap",
61-
},
62-
prettierConfig: {
63-
type: "string",
64-
alias: "c",
65-
},
66-
rawSchema: {
67-
type: "boolean",
68-
},
69-
version: {
70-
type: "number",
71-
},
72-
},
73-
}
74-
);
29+
`;
7530

7631
const OUTPUT_FILE = "FILE";
7732
const OUTPUT_STDOUT = "STDOUT";
@@ -80,21 +35,42 @@ const timeStart = process.hrtime();
8035

8136
function errorAndExit(errorMessage) {
8237
process.exitCode = 1; // needed for async functions
83-
throw new Error(red(errorMessage));
38+
throw new Error(errorMessage);
8439
}
8540

41+
const [, , input, ...args] = process.argv;
42+
const flags = parser(args, {
43+
array: ["header"],
44+
boolean: ["defaultNonNullable", "immutableTypes", "rawSchema"],
45+
number: ["version"],
46+
string: ["auth", "header", "headersObject", "httpMethod", "prettierConfig"],
47+
alias: {
48+
additionalProperties: ["ap"],
49+
header: ["x"],
50+
headersObject: ["h"],
51+
httpMethod: ["m"],
52+
immutableTypes: ["it"],
53+
output: ["o"],
54+
prettierConfig: ["c"],
55+
},
56+
default: {
57+
httpMethod: "GET",
58+
},
59+
});
60+
8661
async function generateSchema(pathToSpec) {
87-
const output = cli.flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
62+
const output = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
8863

8964
// Parse incoming headers from CLI flags
9065
let httpHeaders = {};
66+
9167
// prefer --headersObject if specified
92-
if (cli.flags.headersObject) {
93-
httpHeaders = JSON.parse(cli.flags.headersObject); // note: this will generate a recognizable error for the user to act on
68+
if (flags.headersObject) {
69+
httpHeaders = JSON.parse(flags.headersObject); // note: this will generate a recognizable error for the user to act on
9470
}
9571
// otherwise, parse --header
96-
else if (Array.isArray(cli.flags.header)) {
97-
cli.flags.header.forEach((header) => {
72+
else if (Array.isArray(flags.header)) {
73+
flags.header.forEach((header) => {
9874
const firstColon = header.indexOf(":");
9975
const k = header.substring(0, firstColon).trim();
10076
const v = header.substring(firstColon + 1).trim();
@@ -104,21 +80,21 @@ async function generateSchema(pathToSpec) {
10480

10581
// generate schema
10682
const result = await openapiTS(pathToSpec, {
107-
additionalProperties: cli.flags.additionalProperties,
108-
auth: cli.flags.auth,
109-
defaultNonNullable: cli.flags.defaultNonNullable,
110-
immutableTypes: cli.flags.immutableTypes,
111-
prettierConfig: cli.flags.prettierConfig,
112-
rawSchema: cli.flags.rawSchema,
83+
additionalProperties: flags.additionalProperties,
84+
auth: flags.auth,
85+
defaultNonNullable: flags.defaultNonNullable,
86+
immutableTypes: flags.immutableTypes,
87+
prettierConfig: flags.prettierConfig,
88+
rawSchema: flags.rawSchema,
11389
silent: output === OUTPUT_STDOUT,
114-
version: cli.flags.version,
90+
version: flags.version,
11591
httpHeaders,
116-
httpMethod: cli.flags.httpMethod,
92+
httpMethod: flags.httpMethod,
11793
});
11894

11995
// output
12096
if (output === OUTPUT_FILE) {
121-
let outputFilePath = path.resolve(process.cwd(), cli.flags.output); // note: may be directory
97+
let outputFilePath = path.resolve(process.cwd(), flags.output); // note: may be directory
12298
const isDir = fs.existsSync(outputFilePath) && fs.lstatSync(outputFilePath).isDirectory();
12399
if (isDir) {
124100
const filename = pathToSpec.replace(new RegExp(`${path.extname(pathToSpec)}$`), ".ts");
@@ -129,7 +105,7 @@ async function generateSchema(pathToSpec) {
129105

130106
const timeEnd = process.hrtime(timeStart);
131107
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
132-
console.log(green(`🚀 ${pathToSpec} -> ${bold(outputFilePath)} [${time}ms]`));
108+
console.log(`🚀 ${GREEN}${pathToSpec} -> ${BOLD}${outputFilePath}${RESET}${GREEN} [${time}ms]${RESET}`);
133109
} else {
134110
process.stdout.write(result);
135111
// if stdout, (still) don’t log anything to console!
@@ -139,21 +115,27 @@ async function generateSchema(pathToSpec) {
139115
}
140116

141117
async function main() {
142-
let output = cli.flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
143-
const pathToSpec = cli.input[0];
118+
if (flags.help) {
119+
console.info(HELP);
120+
process.exit(0);
121+
}
122+
123+
let output = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
124+
const pathToSpec = input;
144125

145126
if (output === OUTPUT_FILE) {
146-
console.info(bold(`✨ openapi-typescript ${require("../package.json").version}`)); // only log if we’re NOT writing to stdout
127+
const packageJSON = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
128+
console.info(`✨ ${BOLD}openapi-typescript ${packageJSON.version}${RESET}`); // only log if we’re NOT writing to stdout
147129
}
148130

149131
// error: --raw-schema
150-
if (cli.flags.rawSchema && !cli.flags.version) {
132+
if (flags.rawSchema && !flags.version) {
151133
throw new Error(`--raw-schema requires --version flag`);
152134
}
153135

154136
// handle remote schema, exit
155137
if (/^https?:\/\//.test(pathToSpec)) {
156-
if (output !== "." && output === OUTPUT_FILE) fs.mkdirSync(path.dirname(cli.flags.output), { recursive: true });
138+
if (output !== "." && output === OUTPUT_FILE) fs.mkdirSync(path.dirname(flags.output), { recursive: true });
157139
await generateSchema(pathToSpec);
158140
return;
159141
}
@@ -168,15 +150,15 @@ async function main() {
168150
}
169151

170152
// error: tried to glob output to single file
171-
if (isGlob && output === OUTPUT_FILE && fs.existsSync(cli.flags.output) && fs.lstatSync(cli.flags.output).isFile()) {
172-
errorAndExit(`❌ Expected directory for --output if using glob patterns. Received "${cli.flags.output}".`);
153+
if (isGlob && output === OUTPUT_FILE && fs.existsSync(flags.output) && fs.lstatSync(flags.output).isFile()) {
154+
errorAndExit(`❌ Expected directory for --output if using glob patterns. Received "${flags.output}".`);
173155
}
174156

175157
// generate schema(s) in parallel
176158
await Promise.all(
177159
inputSpecPaths.map(async (specPath) => {
178-
if (cli.flags.output !== "." && output === OUTPUT_FILE) {
179-
let outputDir = path.resolve(process.cwd(), cli.flags.output);
160+
if (flags.output !== "." && output === OUTPUT_FILE) {
161+
let outputDir = path.resolve(process.cwd(), flags.output);
180162
if (isGlob) {
181163
outputDir = path.resolve(outputDir, path.dirname(specPath)); // globs: use output dir + spec dir
182164
} else {

0 commit comments

Comments
 (0)