Skip to content

Commit 9378e01

Browse files
authored
Merge pull request #188 from psmyrdek/types-from-url
Fetch specs from remote resources
2 parents 7a34730 + e16f3c0 commit 9378e01

File tree

12 files changed

+404
-64
lines changed

12 files changed

+404
-64
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
example/*.ts
2+
bin

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@
44

55
# 📘️ swagger-to-ts
66

7-
Convert [OpenAPI v2][openapi2] schemas to TypeScript interfaces using Node.js. It can handle large
8-
definition files within milliseconds because it neither validates nor parses; it only transforms the
9-
bare minimum of what it needs to.
7+
🚀 Convert [OpenAPI v2][openapi2] schemas to TypeScript interfaces using Node.js.
108

11-
💅 Prettifies output with [Prettier][prettier].
9+
💅 The output is prettified with [Prettier][prettier].
10+
11+
👉 Works for both local and remote resources (filesystem and http).
1212

1313
To compare actual generated output, see the [example](./example) folder.
1414

15+
(**swagger-to-ts** can handle large definition files within milliseconds because it neither validates nor parses; it only transforms the bare minimum of what it needs to.)
16+
1517
## Usage
1618

1719
### CLI
1820

21+
#### Reading specs from file system
22+
1923
```bash
2024
npx @manifoldco/swagger-to-ts schema.yaml --output schema.d.ts
25+
```
26+
27+
#### Reading specs from remote resource
2128

22-
# 🚀 schema.yaml -> schema.d.ts [500ms]
29+
```bash
30+
npx @manifoldco/swagger-to-ts https://petstore.swagger.io/v2/swagger.json --output petstore.d.ts
2331
```
2432

2533
This will save a `schema.d.ts` file in the current folder under the TypeScript

bin/cli.js

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env node
22

3-
const { readFileSync, existsSync, writeFileSync } = require('fs');
3+
const { writeFileSync } = require('fs');
44
const { mkdirpSync } = require('fs-extra');
55
const chalk = require('chalk');
66
const { dirname, resolve } = require('path');
77
const meow = require('meow');
8-
const yaml = require('js-yaml');
98
const { default: swaggerToTS } = require('../dist-node');
9+
const { loadSpec } = require('./loaders');
1010

1111
const cli = meow(
1212
`Usage
@@ -26,54 +26,32 @@ Options
2626
}
2727
);
2828

29-
let spec = cli.input[0];
30-
29+
const pathToSpec = cli.input[0];
3130
const timeStart = process.hrtime();
3231

33-
// If input is a file, load it
34-
const pathname = resolve(process.cwd(), spec);
35-
if (existsSync(pathname)) {
36-
spec = readFileSync(pathname, 'UTF-8');
37-
}
38-
39-
// Attempt to parse YAML
40-
try {
41-
if (/\.ya?ml$/i.test(spec) || spec[0] !== '{') {
42-
spec = yaml.safeLoad(spec);
32+
(async () => {
33+
let spec = '';
34+
try {
35+
spec = await loadSpec(pathToSpec);
36+
} catch (e) {
37+
console.error(chalk.red(`❌ "${e}"`));
4338
}
44-
} catch (e) {
45-
console.error(
46-
chalk.red(`❌ "${spec}" seems to be YAML, but it couldn’t be parsed.
47-
${e}`)
48-
);
49-
}
5039

51-
// Attempt to parse JSON
52-
try {
53-
if (typeof spec === 'string') {
54-
spec = JSON.parse(spec);
55-
}
56-
} catch (e) {
57-
console.error(
58-
chalk.red(`❌ Could not parse JSON for "${spec}." Is this a valid Swagger spec?
59-
${e}`)
60-
);
61-
}
40+
const result = swaggerToTS(spec);
6241

63-
const result = swaggerToTS(spec);
42+
// Write to file if specifying output
43+
if (cli.flags.output) {
44+
const outputFile = resolve(process.cwd(), cli.flags.output);
45+
const parent = dirname(outputFile);
46+
mkdirpSync(parent);
47+
writeFileSync(outputFile, result);
6448

65-
// Write to file if specifying output
66-
if (cli.flags.output) {
67-
const outputFile = resolve(process.cwd(), cli.flags.output);
68-
const parent = dirname(outputFile);
69-
mkdirpSync(parent);
70-
writeFileSync(outputFile, result);
71-
72-
const timeEnd = process.hrtime(timeStart);
73-
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
74-
console.log(chalk.green(`🚀 ${cli.input[0]} -> ${chalk.bold(cli.flags.output)} [${time}ms]`));
75-
return;
76-
}
49+
const timeEnd = process.hrtime(timeStart);
50+
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
51+
console.log(chalk.green(`🚀 ${cli.input[0]} -> ${chalk.bold(cli.flags.output)} [${time}ms]`));
52+
return;
53+
}
7754

78-
// Otherwise, return result
79-
return result;
55+
// Otherwise, return result
56+
return result;
57+
})();

bin/loaders/index.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const yaml = require('js-yaml');
2+
const chalk = require('chalk');
3+
4+
const loadFromFs = require('./loadFromFs');
5+
const loadFromHttp = require('./loadFromHttp');
6+
7+
async function load(pathToSpec) {
8+
let rawSpec;
9+
if (/^https?:\/\//.test(pathToSpec)) {
10+
rawSpec = await loadFromHttp(pathToSpec);
11+
} else {
12+
rawSpec = await loadFromFs(pathToSpec);
13+
}
14+
return rawSpec;
15+
}
16+
17+
function isYamlSpec(rawSpec, pathToSpec) {
18+
return /\.ya?ml$/i.test(pathToSpec) || rawSpec[0] !== '{';
19+
}
20+
21+
module.exports.loadSpec = async (pathToSpec) => {
22+
console.log(chalk.yellow(`🤞 Loading spec from ${chalk.bold(pathToSpec)}...`));
23+
const rawSpec = await load(pathToSpec);
24+
25+
try {
26+
if (isYamlSpec(rawSpec, pathToSpec)) {
27+
return yaml.safeLoad(rawSpec);
28+
}
29+
} catch {
30+
throw new Error(`The spec under ${pathToSpec} seems to be YAML, but it couldn’t be parsed.`);
31+
}
32+
33+
try {
34+
return JSON.parse(rawSpec);
35+
} catch {
36+
throw new Error(`The spec under ${pathToSpec} couldn’t be parsed neither as YAML nor JSON.`);
37+
}
38+
};

bin/loaders/loadFromFs.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const { resolve } = require('path');
2+
const { existsSync, readFile } = require('fs');
3+
4+
module.exports = (pathToSpec) => {
5+
const pathname = resolve(process.cwd(), pathToSpec);
6+
const pathExists = existsSync(pathname);
7+
8+
return new Promise((resolve, reject) => {
9+
if (pathExists) {
10+
readFile(pathname, 'UTF-8', (err, data) => {
11+
if (err) {
12+
reject(err);
13+
}
14+
resolve(data);
15+
});
16+
} else {
17+
reject(`Cannot find spec under the following path: ${pathname}`);
18+
}
19+
});
20+
};

bin/loaders/loadFromHttp.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const url = require('url');
2+
const adapters = {
3+
'http:': require('http'),
4+
'https:': require('https'),
5+
};
6+
7+
function fetchFrom(inputUrl) {
8+
return adapters[url.parse(inputUrl).protocol];
9+
}
10+
11+
function buildOptions(pathToSpec) {
12+
const requestUrl = url.parse(pathToSpec);
13+
return {
14+
method: 'GET',
15+
hostname: requestUrl.host,
16+
path: requestUrl.path,
17+
};
18+
}
19+
20+
module.exports = (pathToSpec) => {
21+
return new Promise((resolve, reject) => {
22+
const opts = buildOptions(pathToSpec);
23+
const req = fetchFrom(pathToSpec).request(opts, (res) => {
24+
let rawData = '';
25+
res.setEncoding('utf8');
26+
res.on('data', (chunk) => {
27+
rawData += chunk;
28+
});
29+
res.on('end', () => {
30+
resolve(rawData);
31+
});
32+
});
33+
req.on('error', (err) => {
34+
reject(err);
35+
});
36+
req.end();
37+
});
38+
};

0 commit comments

Comments
 (0)