Skip to content

Commit e73f0d1

Browse files
feat: support definitions
1 parent 15bd726 commit e73f0d1

11 files changed

+2280
-4803
lines changed

.prettierignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
dist
3-
CHANGELOG.md
3+
CHANGELOG.md
4+
coverage

jest.config.js

-4
This file was deleted.

jest.config.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Config } from "@jest/types";
2+
3+
const config: Config.InitialOptions = {
4+
preset: "ts-jest",
5+
testEnvironment: "node",
6+
forceExit: true,
7+
};
8+
9+
export default config;

package-lock.json

+2,043-4,651
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Convert json schema for Fastify to typescript interface",
55
"main": "./dist/index.js",
66
"bin": {
7-
"fastify-schema-to-typescript": "./bin/fastify-schema-to-typescript.js"
7+
"fastify-schema-to-typescript": "./dist/cli.js"
88
},
99
"scripts": {
1010
"clean": "rimraf ./dist",
@@ -36,13 +36,14 @@
3636
"@types/jest": "^26.0.10",
3737
"@types/js-yaml": "^3.12.5",
3838
"@types/node": "^14.6.0",
39-
"fastify": "^3.2.1",
39+
"fastify": "^3.7.0",
4040
"husky": "^4.2.5",
41-
"jest": "^26.4.0",
41+
"jest": "^26.6.3",
4242
"prettier": "^2.0.5",
4343
"rimraf": "^3.0.2",
4444
"semantic-release": "^17.1.1",
4545
"ts-jest": "^26.2.0",
46+
"ts-node": "^9.0.0",
4647
"typescript": "^3.9.7"
4748
},
4849
"husky": {

bin/fastify-schema-to-typescript.js renamed to src/cli.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env node
22

3-
const { program } = require("commander");
4-
const { convert } = require("../dist/index");
3+
import { program } from "commander";
54

6-
function parsePrefix(value) {
5+
import { convert, defaultOptions } from "./schema";
6+
7+
function parsePrefix(value: string) {
78
if (!value.match(/^\w/i)) {
89
console.error("Prefix needs to start with a letter");
910
process.exit(-1);
@@ -12,7 +13,7 @@ function parsePrefix(value) {
1213
return value;
1314
}
1415

15-
function parseExtension(value) {
16+
function parseExtension(value: string) {
1617
if (!value.match(/^\./)) {
1718
console.error('File extension needs to start with a "."');
1819
process.exit(-1);
@@ -25,24 +26,24 @@ program
2526
.option(
2627
"-g, --glob <value>",
2728
"glob matching JSON schema to convert",
28-
"src/**/schema.{json,yaml,yml}"
29+
defaultOptions.glob
2930
)
3031
.option(
3132
"-p, --prefix <value>",
3233
"prefix to use before interfaces' name",
3334
parsePrefix,
34-
""
35+
defaultOptions.prefix
3536
)
3637
.option(
3738
"-e, --ext <value>",
3839
"file extension to use for generated files",
3940
parseExtension,
40-
".ts"
41+
defaultOptions.ext
4142
)
4243
.option(
4344
"-m, --module <value>",
4445
"module to import the RouteHandler type from",
45-
"fastify"
46+
defaultOptions.module
4647
);
4748

4849
program.parse(process.argv);

src/index.ts

+2-135
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,2 @@
1-
import fs from "fs";
2-
import glob from "glob";
3-
import yaml from "js-yaml";
4-
import { compile, Options as CompilerOptions } from "json-schema-to-typescript";
5-
import path from "path";
6-
import { promisify } from "util";
7-
8-
const compileOptions: Partial<CompilerOptions> = { bannerComment: "" };
9-
const defaultSchema = { type: "object", additionalProperties: false };
10-
11-
export interface Options {
12-
glob: string;
13-
prefix: string;
14-
ext: string;
15-
module: string;
16-
}
17-
18-
function addDefaultValueToSchema(schema: any) {
19-
return {
20-
...schema,
21-
additionalProperties: schema.additionalProperties || false,
22-
};
23-
}
24-
25-
export async function generateReplyInterfaces(
26-
prefix: string,
27-
replies: Record<any, any> = {}
28-
) {
29-
const generatedInterfaces = [];
30-
const generatedReplyNames = [];
31-
for (const [replyCode, replySchema] of Object.entries(replies)) {
32-
generatedReplyNames.push(prefix + "Reply" + replyCode.toUpperCase());
33-
generatedInterfaces.push(
34-
await compile(
35-
addDefaultValueToSchema(replySchema || defaultSchema),
36-
prefix + "Reply" + replyCode.toUpperCase(),
37-
compileOptions
38-
)
39-
);
40-
}
41-
42-
return `
43-
${generatedInterfaces.join("\n")}
44-
type ${prefix}Reply = ${generatedReplyNames.join(" | ") || "{}"}
45-
`.trim();
46-
}
47-
48-
function writeSchema(schema: any) {
49-
return `\
50-
const schema = ${JSON.stringify(schema, null, 2)}\
51-
`;
52-
}
53-
54-
async function generateInterfaces(schema: any, options: Options) {
55-
return `\
56-
/* tslint:disable */
57-
/* eslint-disable */
58-
/**
59-
* This file was automatically generated. DO NOT MODIFY IT BY HAND.
60-
* Instead, modify the corresponding JSONSchema file and regenerate the types.
61-
*/
62-
63-
import { RouteHandler } from "${options.module}"
64-
65-
${writeSchema(schema)}
66-
67-
${await compile(
68-
addDefaultValueToSchema(schema.params || defaultSchema),
69-
options.prefix + "Params",
70-
compileOptions
71-
)}
72-
${await compile(
73-
addDefaultValueToSchema(schema.querystring || schema.query || defaultSchema),
74-
options.prefix + "Query",
75-
compileOptions
76-
)}
77-
${await compile(
78-
addDefaultValueToSchema(schema.body || defaultSchema),
79-
options.prefix + "Body",
80-
compileOptions
81-
)}
82-
${await compile(
83-
schema.headers || defaultSchema,
84-
options.prefix + "Headers",
85-
compileOptions
86-
)}
87-
${await generateReplyInterfaces(options.prefix, schema.response)}
88-
89-
type ${options.prefix}RouteGeneric = {
90-
Querystring: ${options.prefix}Query;
91-
Body: ${options.prefix}Body;
92-
Params: ${options.prefix}Params;
93-
Headers: ${options.prefix}Headers;
94-
Reply: ${options.prefix}Reply;
95-
}
96-
97-
type ${options.prefix}Handler = RouteHandler<${options.prefix}RouteGeneric>;
98-
99-
export { ${options.prefix}Handler, ${options.prefix}RouteGeneric, schema }\
100-
`;
101-
}
102-
103-
async function writeFile(
104-
parsedPath: path.ParsedPath,
105-
template: string,
106-
options: Options
107-
) {
108-
const write = promisify(fs.writeFile);
109-
return write(
110-
path.join(parsedPath.dir, parsedPath.name + options.ext),
111-
template
112-
);
113-
}
114-
115-
export async function convert(options: Options) {
116-
const filePaths = glob.sync(options.glob);
117-
for (const filePath of filePaths) {
118-
const parsedPath = path.parse(filePath);
119-
try {
120-
if (parsedPath.ext === ".yaml" || parsedPath.ext === ".yml") {
121-
const schema = yaml.safeLoad(fs.readFileSync(filePath, "utf-8"));
122-
const template = await generateInterfaces(schema, options);
123-
await writeFile(parsedPath, template, options);
124-
} else {
125-
const schema = JSON.parse(fs.readFileSync(filePath, "utf-8"));
126-
const template = await generateInterfaces(schema, options);
127-
await writeFile(parsedPath, template, options);
128-
}
129-
} catch (err) {
130-
console.error(
131-
`Failed to process file ${filePath} with error ${JSON.stringify(err)}`
132-
);
133-
}
134-
}
135-
}
1+
export { Options } from "./types";
2+
export { convert, defaultOptions } from "./schema";

0 commit comments

Comments
 (0)