Feature: Generate typescript SDK (#13)

This commit is contained in:
Timothee Guerin 2019-05-22 14:00:35 -07:00 коммит произвёл GitHub
Родитель c1940cc747
Коммит ba2f985669
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 1618 добавлений и 88 удалений

Просмотреть файл

@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
pool: pool:
vmImage: "Ubuntu 16.04" vmImage: "Ubuntu 16.04"

Просмотреть файл

@ -1,35 +0,0 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
trigger:
- master
pr: none
stages:
- stage: Build_Docker
jobs:
- job: Build_Docker
pool:
vmImage: "Ubuntu-16.04"
steps:
- task: Docker@2
displayName: "Build azuredevx/git-rest-api"
inputs:
containerRegistry: "git-rest-api docker"
repository: "azuredevx/git-rest-api"
tags: |
$(Build.BuildId)
latest
- stage: Publish_Docker
dependsOn: Build_Docker
jobs:
- job: Publish_Docker
pool:
vmImage: "Ubuntu-16.04"
steps:
- task: Docker@2
displayName: "Push azuredevx/git-rest-api"
inputs:
containerRegistry: "git-rest-api docker"
repository: "azuredevx/git-rest-api"

58
.ci/publish.yml Normal file
Просмотреть файл

@ -0,0 +1,58 @@
trigger:
- master
pr: none
stages:
- stage: Build_Docker
jobs:
- job: Build_Docker
pool:
vmImage: "Ubuntu-16.04"
steps:
- task: Docker@2
displayName: "Build azuredevx/git-rest-api"
inputs:
containerRegistry: "git-rest-api docker"
repository: "azuredevx/git-rest-api"
tags: |
$(Build.BuildId)
latest
- stage: Publish_Docker
dependsOn: Build_Docker
jobs:
- job: Publish_Docker
pool:
vmImage: "Ubuntu-16.04"
steps:
- task: Docker@2
displayName: "Push azuredevx/git-rest-api"
inputs:
containerRegistry: "git-rest-api docker"
repository: "azuredevx/git-rest-api"
- stage: build_sdks
displayName: Build SDKs
dependsOn: []
jobs:
- job: build
pool:
vmImage: "Ubuntu-16.04"
steps:
- script: npm -s ci
displayName: Install
- script: npm -s run sdk:gen
displayName: Generate SDKs
- script: npm -s pack
workingDirectory: sdk/out/typescript
displayName: Pack
- task: CopyFiles@2
displayName: 'Copy Files to: drop'
inputs:
sourceFolder: ./sdk/out/typescript
contents: '*.tgz'
targetFolder: $(Build.ArtifactStagingDirectory)/drop
- task: PublishPipelineArtifact@0
inputs:
artifactName: "drop"
targetPath: $(Build.ArtifactStagingDirectory)/drop

5
.gitignore поставляемый
Просмотреть файл

@ -61,4 +61,7 @@ bin/
buildcache/ buildcache/
tmp/ tmp/
# Junit # Junit
test-results* test-results*
# Generated sdk folder
sdk/out/

11
.vscode/settings.json поставляемый
Просмотреть файл

@ -6,5 +6,14 @@
"buildcache": true, "buildcache": true,
"coverage": true "coverage": true
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode" "files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"sdk/out/**/node_modules": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.disableAutomaticTypeAcquisition": true // Cause issue with locking folder on windows. Types should be installed anyway in package.json
} }

1339
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"name": "git-rest-api", "name": "git-rest-api",
"version": "1.0.0", "version": "0.2.0",
"description": "This project welcomes contributions and suggestions. Most contributions require you to agree to a\r Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\r the rights to use your contribution. For details, visit https://cla.microsoft.com.", "description": "This project welcomes contributions and suggestions. Most contributions require you to agree to a\r Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\r the rights to use your contribution. For details, visit https://cla.microsoft.com.",
"main": "bin/main.js", "main": "bin/main.js",
"scripts": { "scripts": {
@ -14,7 +14,10 @@
"test:ci": "jest --ci --reporters=default --reporters=jest-junit", "test:ci": "jest --ci --reporters=default --reporters=jest-junit",
"lint": "tslint -p tsconfig.json", "lint": "tslint -p tsconfig.json",
"test:watch": "jest --watch --coverage=false --config ./config/jest.dev.config.js", "test:watch": "jest --watch --coverage=false --config ./config/jest.dev.config.js",
"swagger:gen": "node ./bin/scripts/generate-swagger-specs.js" "swagger:gen": "node ./bin/scripts/generate-swagger-specs.js",
"autorest": "cross-var autorest sdk/config.yaml --package-version=$npm_package_version",
"sdk:gen": "npm run sdk:gen:ts",
"sdk:gen:ts": "cross-var rimraf ./sdk/out/typescript/* && copyfiles -u 3 \"./sdk/src/typescript/**/*\" ./sdk/out/typescript && npm run autorest -- --typescript --enum-types=true && npm run prepack --prefix ./sdk/out/typescript"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -33,7 +36,10 @@
"@types/jest": "^24.0.13", "@types/jest": "^24.0.13",
"@types/node-fetch": "^2.3.4", "@types/node-fetch": "^2.3.4",
"@types/nodegit": "^0.24.6", "@types/nodegit": "^0.24.6",
"autorest": "^2.0.4283",
"concurrently": "^4.1.0", "concurrently": "^4.1.0",
"copyfiles": "^2.1.0",
"cross-var": "^1.1.0",
"jest": "^24.8.0", "jest": "^24.8.0",
"jest-junit": "^6.4.0", "jest-junit": "^6.4.0",
"prettier": "^1.17.1", "prettier": "^1.17.1",

7
sdk/config.yaml Normal file
Просмотреть файл

@ -0,0 +1,7 @@
input-file: ../swagger-spec.json
typescript:
package-name: "git-rest-api"
payload-flattening-threshold: 0
generate-package-json: false
output-folder: "out/typescript"
enum-types: true

Просмотреть файл

@ -0,0 +1,2 @@
export * from "./models";
export * from "./gITRestAPI";

Просмотреть файл

@ -0,0 +1,55 @@
{
"name": "git-rest-api-sdk",
"author": "Microsoft Corporation",
"description": "GITRestAPI Library with typescript type definitions for node.js and browser.",
"version": "0.1.0",
"dependencies": {
"@azure/ms-rest-js": "^1.1.0",
"tslib": "^1.9.3"
},
"keywords": [
"node",
"azure",
"git",
"typescript",
"browser",
"isomorphic"
],
"license": "MIT",
"main": "./dist/bundle.js",
"module": "./esm/index.js",
"types": "./esm/index.d.ts",
"devDependencies": {
"typescript": "^3.1.1",
"rollup": "^0.66.2",
"rollup-plugin-node-resolve": "^3.4.0",
"uglify-js": "^3.4.9"
},
"homepage": "https://github.com/azure/git-rest-api",
"repository": {
"type": "git",
"url": "https://github.com/azure/git-rest-api.git"
},
"bugs": {
"url": "https://github.com/azure/git-rest-api/issues"
},
"files": [
"dist/**/*.js",
"dist/**/*.js.map",
"dist/**/*.d.ts",
"dist/**/*.d.ts.map",
"esm/**/*.js",
"esm/**/*.js.map",
"esm/**/*.d.ts",
"esm/**/*.d.ts.map",
"lib/**/*.ts",
"rollup.config.js",
"tsconfig.json"
],
"scripts": {
"build": "tsc && rollup -c rollup.config.js && npm run minify",
"minify": "uglifyjs -c -m --comments --source-map \"content='./dist/bundle.js.map'\" -o ./dist/bundle.min.js ./dist/bundle.js",
"prepack": "npm install && npm run build"
},
"sideEffects": false
}

Просмотреть файл

@ -0,0 +1,27 @@
import nodeResolve from "rollup-plugin-node-resolve";
/**
* @type {import('rollup').RollupFileOptions}
*/
const config = {
input: './esm/index.js',
external: ["@azure/ms-rest-js", "@azure/ms-rest-azure-js"],
output: {
file: "./dist/bundle.js",
format: "umd",
name: "Bundle",
sourcemap: true,
globals: {
"@azure/ms-rest-js": "msRest",
"@azure/ms-rest-azure-js": "msRestAzure"
},
banner: `/*
* Code generated by Microsoft (R) AutoRest Code Generator.
* Changes may cause incorrect behavior and will be lost if the code is
* regenerated.
*/`
},
plugins: [
nodeResolve({ module: true })
]
};
export default config;

Просмотреть файл

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "es6",
"moduleResolution": "node",
"strict": true,
"target": "es5",
"sourceMap": true,
"declarationMap": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es6"],
"declaration": true,
"outDir": "./esm",
"importHelpers": true
},
"include": ["./lib/**/*.ts"],
"exclude": ["node_modules"]
}

1
sdk/test/typescript/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
package-lock.json

Просмотреть файл

@ -0,0 +1,16 @@
{
"name": "git-rest-api-sdk-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "ts-node test.ts"
},
"dependencies": {
"git-rest-api-sdk": "../../out/typescript",
"ts-node": "^8.1.0",
"typescript": "^3.4.5"
},
"author": "",
"license": "ISC"
}

Просмотреть файл

@ -0,0 +1,18 @@
import { GITRestAPI, GitBranch } from "git-rest-api-sdk";
// tslint:disable: no-console
const sdk = new GITRestAPI({ baseUri: "http://localhost:3009" });
async function run() {
const branches: GitBranch[] = await sdk.branches.list("github.com/Azure/BatchExplorer");
console.log("Branches:");
for (const branch of branches) {
console.log(` - ${branch.name} ${branch.commit.sha}`);
}
}
run().catch(e => {
console.error(e);
process.exit(1);
});

Просмотреть файл

@ -1,13 +1,7 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { Configuration } from "./config"; import { Configuration } from "./config";
import { import { BranchesController, CommitsController, CompareController, HealthCheckController } from "./controllers";
AppController,
BranchesController,
CommitsController,
CompareController,
HealthCheckController,
} from "./controllers";
import { import {
AppService, AppService,
BranchService, BranchService,
@ -23,7 +17,7 @@ import {
@Module({ @Module({
imports: [], imports: [],
controllers: [AppController, HealthCheckController, BranchesController, CommitsController, CompareController], controllers: [HealthCheckController, BranchesController, CommitsController, CompareController],
providers: [ providers: [
AppService, AppService,
CompareService, CompareService,

Просмотреть файл

@ -1,15 +0,0 @@
import { Controller, Get } from "@nestjs/common";
import { AppService } from "../services";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {
this.appService.getHello();
}
@Get()
public getHello(): string {
return this.appService.getHello();
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import { Controller, Get, Param } from "@nestjs/common"; import { Controller, Get, Param } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse } from "@nestjs/swagger"; import { ApiNotFoundResponse, ApiOkResponse, ApiOperation } from "@nestjs/swagger";
import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core"; import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core";
import { GitBranch } from "../../dtos"; import { GitBranch } from "../../dtos";
@ -13,6 +13,7 @@ export class BranchesController {
@ApiHasPassThruAuth() @ApiHasPassThruAuth()
@ApiOkResponse({ type: GitBranch, isArray: true }) @ApiOkResponse({ type: GitBranch, isArray: true })
@ApiNotFoundResponse({}) @ApiNotFoundResponse({})
@ApiOperation({ title: "List branches", operationId: "Branches_List" })
public async list(@Param("remote") remote: string, @Auth() auth: RepoAuth): Promise<GitBranch[]> { public async list(@Param("remote") remote: string, @Auth() auth: RepoAuth): Promise<GitBranch[]> {
const branches = await this.branchService.list(remote, { auth }); const branches = await this.branchService.list(remote, { auth });
return branches; return branches;

Просмотреть файл

@ -1,5 +1,5 @@
import { Controller, Get, NotFoundException, Param } from "@nestjs/common"; import { Controller, Get, NotFoundException, Param } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse } from "@nestjs/swagger"; import { ApiNotFoundResponse, ApiOkResponse, ApiOperation } from "@nestjs/swagger";
import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core"; import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core";
import { GitCommit } from "../../dtos"; import { GitCommit } from "../../dtos";
@ -13,6 +13,7 @@ export class CommitsController {
@ApiHasPassThruAuth() @ApiHasPassThruAuth()
@ApiOkResponse({ type: GitCommit, isArray: true }) @ApiOkResponse({ type: GitCommit, isArray: true })
@ApiNotFoundResponse({}) @ApiNotFoundResponse({})
@ApiOperation({ title: "Get a commit", operationId: "commits_get" })
public async get(@Param("remote") remote: string, @Param("commitSha") commitSha: string, @Auth() auth: RepoAuth) { public async get(@Param("remote") remote: string, @Param("commitSha") commitSha: string, @Auth() auth: RepoAuth) {
const commit = await this.commitService.get(remote, commitSha, { auth }); const commit = await this.commitService.get(remote, commitSha, { auth });

Просмотреть файл

@ -1,5 +1,5 @@
import { Controller, Get, HttpException, Param } from "@nestjs/common"; import { Controller, Get, HttpException, Param } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse } from "@nestjs/swagger"; import { ApiNotFoundResponse, ApiOkResponse, ApiOperation } from "@nestjs/swagger";
import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core"; import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core";
import { GitDiff } from "../../dtos/git-diff"; import { GitDiff } from "../../dtos/git-diff";
@ -13,6 +13,7 @@ export class CompareController {
@ApiHasPassThruAuth() @ApiHasPassThruAuth()
@ApiOkResponse({ type: GitDiff, isArray: true }) @ApiOkResponse({ type: GitDiff, isArray: true })
@ApiNotFoundResponse({}) @ApiNotFoundResponse({})
@ApiOperation({ title: "Compare two commits", operationId: "commits_compare" })
public async compare( public async compare(
@Param("remote") remote: string, @Param("remote") remote: string,
@Param("base") base: string, @Param("base") base: string,

Просмотреть файл

@ -1,8 +1,10 @@
import { Controller, Get } from "@nestjs/common"; import { Controller, Get } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
@Controller("/health") @Controller("/health")
export class HealthCheckController { export class HealthCheckController {
@Get("/alive") @Get("/alive")
@ApiOperation({ title: "Check alive", operationId: "health_checkAlive" })
public async getAlive() { public async getAlive() {
return "alive"; return "alive";
} }

Просмотреть файл

@ -1,4 +1,3 @@
export * from "./app.controller";
export * from "./commits/commits.controller"; export * from "./commits/commits.controller";
export * from "./health-check/health-check.controller"; export * from "./health-check/health-check.controller";
export * from "./branches/branches.controller"; export * from "./branches/branches.controller";

Просмотреть файл

@ -10,6 +10,8 @@ const SWAGGER_FILE_PATH = "./swagger-spec.json";
export async function generateSwagger(): Promise<string> { export async function generateSwagger(): Promise<string> {
const { document } = await createApp(); const { document } = await createApp();
reorderPaths(document.paths);
return JSON.stringify(document, null, 2); return JSON.stringify(document, null, 2);
} }
@ -22,3 +24,34 @@ export async function getSavedSwagger(): Promise<string> {
const response = await readFile(SWAGGER_FILE_PATH); const response = await readFile(SWAGGER_FILE_PATH);
return response.toString(); return response.toString();
} }
/**
* Because of how the nestjs generate the path params they might not be in order which could lead to breaking changes without noticing.
* Force the params to be ordered how they are defined in the path
*/
export function reorderPaths(paths: StringMap<StringMap<any>>) {
for (const [path, methods] of Object.entries(paths)) {
for (const def of Object.values(methods)) {
if (def.parameters) {
def.parameters = getOrderedPathParams(path, def.parameters);
}
}
}
}
interface SwaggerParam {
type: string;
name: string;
required: boolean;
in: "path" | "header";
}
export function getOrderedPathParams(path: string, parameters: SwaggerParam[]) {
const pathParams = parameters.filter(x => x.in === "path");
const otherParams = parameters.filter(x => x.in !== "path");
const sortedPathParams = [...pathParams].sort((a, b) => {
return path.indexOf(`{${a.name}}`) - path.indexOf(`{${b.name}}`);
});
return [...sortedPathParams, ...otherParams];
}

Просмотреть файл

@ -12,23 +12,10 @@
"https" "https"
], ],
"paths": { "paths": {
"/": {
"get": {
"responses": {
"200": {
"description": ""
}
},
"produces": [
"application/json"
],
"consumes": [
"application/json"
]
}
},
"/health/alive": { "/health/alive": {
"get": { "get": {
"summary": "Check alive",
"operationId": "health_checkAlive",
"responses": { "responses": {
"200": { "200": {
"description": "" "description": ""
@ -44,6 +31,8 @@
}, },
"/repos/{remote}/branches": { "/repos/{remote}/branches": {
"get": { "get": {
"summary": "List branches",
"operationId": "Branches_List",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -91,16 +80,18 @@
}, },
"/repos/{remote}/commits/{commitSha}": { "/repos/{remote}/commits/{commitSha}": {
"get": { "get": {
"summary": "Get a commit",
"operationId": "commits_get",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"name": "commitSha", "name": "remote",
"required": true, "required": true,
"in": "path" "in": "path"
}, },
{ {
"type": "string", "type": "string",
"name": "remote", "name": "commitSha",
"required": true, "required": true,
"in": "path" "in": "path"
}, },
@ -144,10 +135,12 @@
}, },
"/repos/{remote}/compare/{base}...{head}": { "/repos/{remote}/compare/{base}...{head}": {
"get": { "get": {
"summary": "Compare two commits",
"operationId": "commits_compare",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"name": "head", "name": "remote",
"required": true, "required": true,
"in": "path" "in": "path"
}, },
@ -159,7 +152,7 @@
}, },
{ {
"type": "string", "type": "string",
"name": "remote", "name": "head",
"required": true, "required": true,
"in": "path" "in": "path"
}, },