Skip to content

Commit 875d528

Browse files
Initial commit
0 parents  commit 875d528

24 files changed

+2332
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
dist
3+
doc
4+
node_modules
5+
.vscode
6+
.nyc_output
7+
coverage
8+
*.log

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
!dist/**
2+
!doc/**
3+
.travis.yml

.travis.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
language: node_js
2+
node_js:
3+
- '6'
4+
script: nyc npm test && nyc report --reporter=text-lcov | coveralls
5+
deploy:
6+
provider: npm
7+
8+
api_key:
9+
secure: h36KkxAMuHGXktDL1FB3rgl8s3cPLdSBpbBMN7A5XpXF3E7mMIPEEEfSQ5nfRc5wxmlseBZjyjhcXbEavluAGmBB+d3bBRz3ifqrwqT/DTPRdTMkMhqVKC7ywAzh+pU8qkmcX9TVMmYYJQhFIj7fWy0yr2DRoEEa6Ei4ZEDFH/hHOSzHmfJNZAoXuI7rZ1nJoQZ1UJsGERMNp+xdJdVY1jng33karymkl+zNrqxPPf+enfazxqo4+s5fQM/pAU64IEyj+JCpzYrXG9uu+1CDOVyp+G1bdVYohCJj1nUFqSpaKyYHTjO963ogVnMjMFlDlzy2C6jD6/q6f4dqWiG6W1EAau2VdJdfEVfOqiZ7djPYR8DP/qdLIBUdpcWJKOchL3+Y/S6vz25s/aGWmZrqBp1Lwmuod9vh+Wdfm96kWA9HQO6JIt+bsxa8RNstNnWlb8FCf9O48S/Jhjl5UTcgsSjxvXJe/5V0bqXSW6MNGwP89Y7mnDgV+r2zd/twuYOc020YBNHqdvywQJrMk1m+rP3MLJPgnzjfFrXUKmoL2JI9Nr73rce1U6E3D24KXdE43NmvulXve6DQ23Zc57U2DKNxlxvac+Jx6ksoQ3rYkWa9d3R3bYYD5e3HAn9D+QsJz+kTo8uVlH/9PjAAE4TyhJkQY+XikIVfa36dFmAXeos=
10+
on:
11+
tags: true
12+
repo: thiagobustamante/typescript-rest-swagger

README.MD

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[![npm version](https://badge.fury.io/js/typescript-rest-swagger.svg)](https://badge.fury.io/js/typescript-rest-swagger)
2+
[![Build Status](https://travis-ci.org/thiagobustamante/typescript-rest-swagger.png?branch=master)](https://travis-ci.org/thiagobustamante/typescript-rest-swagger)
3+
4+
# Swagger for Typescript-rest
5+
This is a tool to generate swagger files from a [typescript-rest](https://github.com/thiagobustamante/typescript-rest) project.
6+
7+
## Installation
8+
9+
```bash
10+
npm install typescript-rest-swagger -g
11+
```
12+
13+
## Usage
14+
15+
```bash
16+
swaggerGen -c ./swaggerConfig.json
17+
```
18+
19+
Where the swaggerConfig.json file, contains settings about the swagger generation. For example:
20+
21+
```json
22+
{
23+
"swagger": {
24+
"outputDirectory": "./dist",
25+
"entryFile": "./tests/data/apis.ts",
26+
"host": "localhost:3000",
27+
"version": "1.0",
28+
"name": "Typescript-rest Test API",
29+
"description": "a description",
30+
"license": "MIT",
31+
"basePath": "/v1",
32+
"securityDefinitions": {
33+
"api_key": {
34+
"type": "apiKey",
35+
"name": "access_token",
36+
"in": "query"
37+
}
38+
}
39+
}
40+
}
41+
```

package.json

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{
2+
"name": "typescript-rest-swagger",
3+
"version": "0.0.1",
4+
"description": "Generate Swagger files from a typescript-rest project",
5+
"keywords": [
6+
"typescript",
7+
"typescript-rest",
8+
"swagger",
9+
"open api",
10+
"rest",
11+
"microservice",
12+
"codegen",
13+
"generation"
14+
],
15+
"main": "./dist/index.js",
16+
"typings": "./dist/index.d.ts",
17+
"scripts": {
18+
"start": "tsc -w",
19+
"build": "npm run clean && tsc",
20+
"clean": "rimraf dist",
21+
"lint": "tslint --exclude=./node_modules/** ./src/**/*.ts ./test/**/*.ts",
22+
"format": "tsfmt -r",
23+
"prepublish": "npm run build",
24+
"deploy": "npm version patch && npm publish",
25+
"postversion": "git push origin master",
26+
"swagger-gen": "node ./dist/cli.js -c ./swagger.json",
27+
"pretest": "cross-env NODE_ENV=test npm build && npm run swagger-gen && npm run lint",
28+
"test": "cross-env NODE_ENV=test mocha",
29+
"test:coverage": "nyc npm test",
30+
"tsc": "tsc"
31+
},
32+
"nyc": {
33+
"include": [
34+
"src/**/*.ts"
35+
],
36+
"extension": [
37+
".ts"
38+
],
39+
"require": [
40+
"ts-node/register"
41+
],
42+
"reporter": [
43+
"text-summary",
44+
"html"
45+
],
46+
"sourceMap": true,
47+
"instrument": true
48+
},
49+
"author": "Thiago da Rosa de Bustamante <[email protected]>",
50+
"license": "MIT",
51+
"dependencies": {
52+
"argparse": "^1.0.9",
53+
"lodash": "^4.17.4",
54+
"merge": "^1.2.0",
55+
"mkdirp": "^0.5.1",
56+
"path": "^0.12.7",
57+
"typescript": "^2.2.2",
58+
"typescript-rest": "^0.4.0",
59+
"yamljs": "^0.2.10"
60+
},
61+
"devDependencies": {
62+
"@types/argparse": "^1.0.30",
63+
"@types/chai": "^3.5.1",
64+
"@types/lodash": "^4.14.63",
65+
"@types/mkdirp": "^0.3.29",
66+
"@types/mocha": "^2.2.41",
67+
"@types/yamljs": "^0.2.30",
68+
"chai": "^3.5.0",
69+
"coveralls": "^2.13.1",
70+
"cross-env": "^4.0.0",
71+
"istanbul": "^0.4.5",
72+
"mocha": "^3.3.0",
73+
"nyc": "^10.2.0",
74+
"rimraf": "^2.6.1",
75+
"source-map-support": "^0.4.14",
76+
"ts-node": "^3.0.2",
77+
"tslint": "^5.1.0",
78+
"typescript-formatter": "^5.1.3"
79+
},
80+
"repository": {
81+
"type": "git",
82+
"url": "https://github.com/thiagobustamante/typescript-rest-swagger.git"
83+
},
84+
"bin": {
85+
"swaggerGen": "dist/cli.js"
86+
},
87+
"engines": {
88+
"node": ">=6.0.0"
89+
},
90+
"engineStrict": true
91+
}

src/cli.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"use strict";
2+
3+
import { ArgumentParser } from "argparse";
4+
import { Config, SwaggerConfig } from './config';
5+
import { MetadataGenerator } from './metadata/metadataGenerator';
6+
import { SpecGenerator } from './swagger/generator';
7+
8+
let parser = new ArgumentParser({
9+
version: '0.0.1',
10+
addHelp: true,
11+
description: 'Tree-Gateway'
12+
});
13+
14+
parser.addArgument(
15+
['-c', '--config'],
16+
{
17+
help: 'The swagger config file (swagger.json).'
18+
}
19+
);
20+
21+
const getPackageJsonValue = (key: string): string => {
22+
try {
23+
const packageJson = require(`${workingDir}/package.json`);
24+
return packageJson[key] || '';
25+
}
26+
catch (err) {
27+
return '';
28+
}
29+
};
30+
31+
const versionDefault = getPackageJsonValue('version');
32+
const nameDefault = getPackageJsonValue('name');
33+
const descriptionDefault = getPackageJsonValue('description');
34+
const licenseDefault = getPackageJsonValue('license');
35+
36+
const getConfig = (configPath = 'swagger.json'): Config => {
37+
let config: Config;
38+
try {
39+
config = require(`${workingDir}/${configPath}`);
40+
} catch (err) {
41+
if (err.code === 'MODULE_NOT_FOUND') {
42+
throw Error(`No config file found at '${configPath}'`);
43+
} else if (err.name === 'SyntaxError') {
44+
throw Error(`Invalid JSON syntax in config at '${configPath}': ${err.message}`);
45+
} else {
46+
throw Error(`Unhandled error encountered loading '${configPath}': ${err.message}`);
47+
}
48+
}
49+
50+
return config;
51+
};
52+
53+
const validateSwaggerConfig = (config: SwaggerConfig): SwaggerConfig => {
54+
if (!config.outputDirectory) { throw new Error('Missing outputDirectory: onfiguration most contain output directory'); }
55+
if (!config.entryFile) { throw new Error('Missing entryFile: Configuration must contain an entry point file.'); }
56+
config.version = config.version || versionDefault;
57+
config.name = config.name || nameDefault;
58+
config.description = config.description || descriptionDefault;
59+
config.license = config.license || licenseDefault;
60+
config.basePath = config.basePath || '/';
61+
config.yaml = config.yaml || true;
62+
63+
return config;
64+
};
65+
66+
const workingDir: string = process.cwd();
67+
68+
let parameters = parser.parseArgs();
69+
const config = getConfig(parameters.config);
70+
71+
const swaggerConfig = validateSwaggerConfig(config.swagger);
72+
const metadata = new MetadataGenerator(swaggerConfig.entryFile).generate();
73+
new SpecGenerator(metadata, config.swagger).generate(swaggerConfig.outputDirectory, swaggerConfig.yaml)
74+
.then(() => {
75+
console.log ('Generation completed.')
76+
})
77+
.catch((err: any) => {
78+
console.error(`Error generating swagger. ${err}`);
79+
});

src/config.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"use strict";
2+
3+
export interface Config {
4+
/**
5+
* Swagger generation configuration object
6+
*/
7+
swagger: SwaggerConfig;
8+
}
9+
10+
export interface SwaggerConfig {
11+
/**
12+
* Support the output to be an yaml file
13+
*/
14+
yaml: boolean;
15+
16+
/**
17+
* Generated SwaggerConfig.json will output here
18+
*/
19+
outputDirectory: string;
20+
21+
/**
22+
* The entry point to your API
23+
*/
24+
entryFile: string;
25+
26+
/**
27+
* API host, expressTemplate.g. localhost:3000 or https://myapi.com
28+
*/
29+
host?: string;
30+
31+
/**
32+
* API version number; defaults to npm package version
33+
*/
34+
version?: string;
35+
36+
/**
37+
* API name; defaults to npm package name
38+
*/
39+
name?: string;
40+
41+
/**
42+
* 'API description; defaults to npm package description
43+
*/
44+
description?: string;
45+
46+
/**
47+
* API license; defaults to npm package license
48+
*/
49+
license?: string;
50+
51+
/**
52+
* Base API path; e.g. the 'v1' in https://myapi.com/v1
53+
*/
54+
basePath?: string;
55+
56+
/**
57+
* Extend generated swagger spec with this object
58+
* Note that generated properties will always take precedence over what get specified here
59+
*/
60+
spec?: any;
61+
62+
/**
63+
* Alter how the spec is merged to generated swagger spec.
64+
* Possible values:
65+
* - 'immediate' is overriding top level elements only thus you can not append a new path or alter an existing value without erasing same level elements.
66+
* - 'recursive' proceed to a deep merge and will concat every branches or override or create new values if needed. @see https://www.npmjs.com/package/merge
67+
* The default is set to immediate so it is not breaking previous versions.
68+
* @default 'immediate'
69+
*/
70+
specMerging?: 'immediate' | 'recursive';
71+
72+
/**
73+
* Security Definitions Object
74+
* A declaration of the security schemes available to be used in the
75+
* specification. This does not enforce the security schemes on the operations
76+
* and only serves to provide the relevant details for each scheme.
77+
*/
78+
securityDefinitions?: {
79+
[name: string]: {
80+
type: string;
81+
name?: string;
82+
authorizationUrl?: string;
83+
tokenUrl?: string;
84+
flow?: string;
85+
in?: string;
86+
scopes?: { [scopeName: string]: string; }
87+
}
88+
};
89+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as ts from 'typescript';
2+
import { Controller } from './metadataGenerator';
3+
import { MethodGenerator } from './methodGenerator';
4+
import { getDecoratorTextValue } from '../utils/decoratorUtils';
5+
6+
export class ControllerGenerator {
7+
private readonly pathValue: string | undefined;
8+
9+
constructor(private readonly node: ts.ClassDeclaration) {
10+
this.pathValue = getDecoratorTextValue(node, decorator => decorator.text === 'Path');
11+
}
12+
13+
public isValid() {
14+
return !!this.pathValue || this.pathValue === '';
15+
}
16+
17+
public generate(): Controller {
18+
if (!this.node.parent) { throw new Error('Controller node doesn\'t have a valid parent source file.'); }
19+
if (!this.node.name) { throw new Error('Controller node doesn\'t have a valid name.'); }
20+
21+
const sourceFile = this.node.parent.getSourceFile();
22+
23+
return {
24+
location: sourceFile.fileName,
25+
methods: this.buildMethods(),
26+
name: this.node.name.text,
27+
path: this.pathValue || ''
28+
};
29+
}
30+
31+
private buildMethods() {
32+
return this.node.members
33+
.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration)
34+
.map((m: ts.MethodDeclaration) => new MethodGenerator(m))
35+
.filter(generator => generator.IsValid())
36+
.map(generator => generator.Generate());
37+
}
38+
}

0 commit comments

Comments
 (0)