Skip to content

Commit

Permalink
feat: support definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasthiebaud committed Nov 4, 2020
1 parent 15bd726 commit e73f0d1
Show file tree
Hide file tree
Showing 11 changed files with 2,280 additions and 4,803 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
CHANGELOG.md
CHANGELOG.md
coverage
4 changes: 0 additions & 4 deletions jest.config.js

This file was deleted.

9 changes: 9 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Config } from "@jest/types";

const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
forceExit: true,
};

export default config;
6,694 changes: 2,043 additions & 4,651 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Convert json schema for Fastify to typescript interface",
"main": "./dist/index.js",
"bin": {
"fastify-schema-to-typescript": "./bin/fastify-schema-to-typescript.js"
"fastify-schema-to-typescript": "./dist/cli.js"
},
"scripts": {
"clean": "rimraf ./dist",
Expand Down Expand Up @@ -36,13 +36,14 @@
"@types/jest": "^26.0.10",
"@types/js-yaml": "^3.12.5",
"@types/node": "^14.6.0",
"fastify": "^3.2.1",
"fastify": "^3.7.0",
"husky": "^4.2.5",
"jest": "^26.4.0",
"jest": "^26.6.3",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"semantic-release": "^17.1.1",
"ts-jest": "^26.2.0",
"ts-node": "^9.0.0",
"typescript": "^3.9.7"
},
"husky": {
Expand Down
17 changes: 9 additions & 8 deletions bin/fastify-schema-to-typescript.js → src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env node

const { program } = require("commander");
const { convert } = require("../dist/index");
import { program } from "commander";

function parsePrefix(value) {
import { convert, defaultOptions } from "./schema";

function parsePrefix(value: string) {
if (!value.match(/^\w/i)) {
console.error("Prefix needs to start with a letter");
process.exit(-1);
Expand All @@ -12,7 +13,7 @@ function parsePrefix(value) {
return value;
}

function parseExtension(value) {
function parseExtension(value: string) {
if (!value.match(/^\./)) {
console.error('File extension needs to start with a "."');
process.exit(-1);
Expand All @@ -25,24 +26,24 @@ program
.option(
"-g, --glob <value>",
"glob matching JSON schema to convert",
"src/**/schema.{json,yaml,yml}"
defaultOptions.glob
)
.option(
"-p, --prefix <value>",
"prefix to use before interfaces' name",
parsePrefix,
""
defaultOptions.prefix
)
.option(
"-e, --ext <value>",
"file extension to use for generated files",
parseExtension,
".ts"
defaultOptions.ext
)
.option(
"-m, --module <value>",
"module to import the RouteHandler type from",
"fastify"
defaultOptions.module
);

program.parse(process.argv);
Expand Down
137 changes: 2 additions & 135 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,135 +1,2 @@
import fs from "fs";
import glob from "glob";
import yaml from "js-yaml";
import { compile, Options as CompilerOptions } from "json-schema-to-typescript";
import path from "path";
import { promisify } from "util";

const compileOptions: Partial<CompilerOptions> = { bannerComment: "" };
const defaultSchema = { type: "object", additionalProperties: false };

export interface Options {
glob: string;
prefix: string;
ext: string;
module: string;
}

function addDefaultValueToSchema(schema: any) {
return {
...schema,
additionalProperties: schema.additionalProperties || false,
};
}

export async function generateReplyInterfaces(
prefix: string,
replies: Record<any, any> = {}
) {
const generatedInterfaces = [];
const generatedReplyNames = [];
for (const [replyCode, replySchema] of Object.entries(replies)) {
generatedReplyNames.push(prefix + "Reply" + replyCode.toUpperCase());
generatedInterfaces.push(
await compile(
addDefaultValueToSchema(replySchema || defaultSchema),
prefix + "Reply" + replyCode.toUpperCase(),
compileOptions
)
);
}

return `
${generatedInterfaces.join("\n")}
type ${prefix}Reply = ${generatedReplyNames.join(" | ") || "{}"}
`.trim();
}

function writeSchema(schema: any) {
return `\
const schema = ${JSON.stringify(schema, null, 2)}\
`;
}

async function generateInterfaces(schema: any, options: Options) {
return `\
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated. DO NOT MODIFY IT BY HAND.
* Instead, modify the corresponding JSONSchema file and regenerate the types.
*/
import { RouteHandler } from "${options.module}"
${writeSchema(schema)}
${await compile(
addDefaultValueToSchema(schema.params || defaultSchema),
options.prefix + "Params",
compileOptions
)}
${await compile(
addDefaultValueToSchema(schema.querystring || schema.query || defaultSchema),
options.prefix + "Query",
compileOptions
)}
${await compile(
addDefaultValueToSchema(schema.body || defaultSchema),
options.prefix + "Body",
compileOptions
)}
${await compile(
schema.headers || defaultSchema,
options.prefix + "Headers",
compileOptions
)}
${await generateReplyInterfaces(options.prefix, schema.response)}
type ${options.prefix}RouteGeneric = {
Querystring: ${options.prefix}Query;
Body: ${options.prefix}Body;
Params: ${options.prefix}Params;
Headers: ${options.prefix}Headers;
Reply: ${options.prefix}Reply;
}
type ${options.prefix}Handler = RouteHandler<${options.prefix}RouteGeneric>;
export { ${options.prefix}Handler, ${options.prefix}RouteGeneric, schema }\
`;
}

async function writeFile(
parsedPath: path.ParsedPath,
template: string,
options: Options
) {
const write = promisify(fs.writeFile);
return write(
path.join(parsedPath.dir, parsedPath.name + options.ext),
template
);
}

export async function convert(options: Options) {
const filePaths = glob.sync(options.glob);
for (const filePath of filePaths) {
const parsedPath = path.parse(filePath);
try {
if (parsedPath.ext === ".yaml" || parsedPath.ext === ".yml") {
const schema = yaml.safeLoad(fs.readFileSync(filePath, "utf-8"));
const template = await generateInterfaces(schema, options);
await writeFile(parsedPath, template, options);
} else {
const schema = JSON.parse(fs.readFileSync(filePath, "utf-8"));
const template = await generateInterfaces(schema, options);
await writeFile(parsedPath, template, options);
}
} catch (err) {
console.error(
`Failed to process file ${filePath} with error ${JSON.stringify(err)}`
);
}
}
}
export { Options } from "./types";
export { convert, defaultOptions } from "./schema";
Loading

0 comments on commit e73f0d1

Please sign in to comment.