@loopback/cli 4.0.0-alpha.9 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yo-rc.json +1719 -0
- package/{generators/project/templates/LICENSE → LICENSE} +2 -1
- package/README.md +44 -43
- package/bin/cli-main.js +61 -0
- package/generators/app/index.js +109 -15
- package/generators/app/templates/.dockerignore +5 -0
- package/generators/app/templates/Dockerfile +28 -0
- package/generators/app/templates/README.md.ejs +130 -0
- package/generators/app/templates/public/index.html.ejs +88 -0
- package/generators/app/templates/{test → src/__tests__}/README.md +0 -1
- package/generators/app/templates/src/__tests__/acceptance/home-page.acceptance.ts.ejs +31 -0
- package/generators/app/templates/src/__tests__/acceptance/ping.controller.acceptance.ts.ejs +21 -0
- package/generators/app/templates/src/__tests__/acceptance/test-helper.ts.ejs +32 -0
- package/generators/app/templates/src/application.ts.ejs +70 -0
- package/generators/app/templates/src/controllers/README.md +6 -0
- package/generators/app/templates/src/controllers/index.ts.ejs +1 -0
- package/generators/app/templates/src/controllers/ping.controller.ts.ejs +55 -0
- package/generators/app/templates/src/datasources/README.md +3 -0
- package/generators/app/templates/src/index.ts.ejs +39 -0
- package/generators/app/templates/src/migrate.ts.ejs +20 -0
- package/generators/app/templates/src/models/README.md +3 -0
- package/generators/app/templates/src/openapi-spec.ts.ejs +23 -0
- package/generators/app/templates/src/sequence.ts.ejs +3 -0
- package/generators/controller/index.js +240 -23
- package/generators/controller/templates/src/controllers/controller-rest-template.ts.ejs +150 -0
- package/generators/controller/templates/src/controllers/{controller-template.ts → controller-template.ts.ejs} +2 -2
- package/generators/copyright/fs.js +46 -0
- package/generators/copyright/git.js +78 -0
- package/generators/copyright/header.js +306 -0
- package/generators/copyright/index.js +230 -0
- package/generators/copyright/license.js +105 -0
- package/generators/datasource/index.js +341 -0
- package/generators/datasource/templates/datasource.ts.ejs +22 -0
- package/generators/discover/import-discovered-model.js +70 -0
- package/generators/discover/index.js +411 -0
- package/generators/example/downloader.js +16 -0
- package/generators/example/index.js +176 -0
- package/generators/extension/index.js +34 -5
- package/generators/extension/templates/README.md.ejs +32 -0
- package/generators/extension/templates/{test → src/__tests__}/acceptance/README.md +0 -0
- package/generators/extension/templates/{test → src/__tests__}/integration/README.md +0 -0
- package/generators/extension/templates/{test → src/__tests__}/unit/README.md +0 -0
- package/generators/extension/templates/src/component.ts.ejs +22 -0
- package/generators/extension/templates/src/controllers/README.md +3 -2
- package/generators/extension/templates/src/decorators/README.md +10 -4
- package/generators/extension/templates/src/index.ts.ejs +3 -0
- package/generators/extension/templates/src/keys.ts.ejs +11 -0
- package/generators/extension/templates/src/mixins/README.md +77 -21
- package/generators/extension/templates/src/providers/README.md +51 -25
- package/generators/extension/templates/src/repositories/README.md +1 -1
- package/generators/extension/templates/src/types.ts.ejs +15 -0
- package/generators/import-lb3-models/index.js +197 -0
- package/generators/import-lb3-models/lb3app-loader.js +31 -0
- package/generators/import-lb3-models/migrate-model.js +249 -0
- package/generators/import-lb3-models/model-names.js +32 -0
- package/generators/interceptor/index.js +178 -0
- package/generators/interceptor/templates/interceptor-template.ts.ejs +62 -0
- package/generators/model/index.js +536 -0
- package/generators/model/property-definition.js +85 -0
- package/generators/model/templates/model.ts.ejs +49 -0
- package/generators/observer/index.js +132 -0
- package/generators/observer/templates/observer-template.ts.ejs +40 -0
- package/generators/openapi/README.md +211 -0
- package/generators/openapi/index.js +535 -0
- package/generators/openapi/schema-helper.js +447 -0
- package/generators/openapi/spec-helper.js +484 -0
- package/generators/openapi/spec-loader.js +75 -0
- package/generators/openapi/templates/src/controllers/controller-template.ts.ejs +43 -0
- package/generators/openapi/templates/src/datasources/datasource.ts.ejs +42 -0
- package/generators/openapi/templates/src/models/model-template.ts.ejs +71 -0
- package/generators/openapi/templates/src/models/type-template.ts.ejs +13 -0
- package/generators/openapi/templates/src/services/service-proxy-template.ts.ejs +55 -0
- package/generators/openapi/utils.js +322 -0
- package/generators/project/templates/.eslintignore +4 -0
- package/generators/project/templates/.eslintrc.js.ejs +3 -0
- package/generators/project/templates/.mocharc.json +5 -0
- package/generators/project/templates/.prettierignore +0 -2
- package/generators/project/templates/.prettierrc +2 -1
- package/generators/project/templates/.vscode/launch.json +38 -0
- package/generators/project/templates/.vscode/settings.json +32 -0
- package/generators/project/templates/.vscode/tasks.json +29 -0
- package/generators/project/templates/DEVELOPING.md +36 -0
- package/generators/project/templates/_.gitignore +3 -5
- package/generators/project/templates/package.json.ejs +175 -0
- package/generators/project/templates/package.plain.json.ejs +176 -0
- package/generators/project/templates/tsconfig.json.ejs +39 -0
- package/generators/relation/base-relation.generator.js +220 -0
- package/generators/relation/belongs-to-relation.generator.js +196 -0
- package/generators/relation/has-many-relation.generator.js +200 -0
- package/generators/relation/has-many-through-relation.generator.js +331 -0
- package/generators/relation/has-one-relation.generator.js +200 -0
- package/generators/relation/index.js +795 -0
- package/generators/relation/references-many-relation.generator.js +142 -0
- package/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs +38 -0
- package/generators/relation/templates/controller-relation-template-has-many-through.ts.ejs +110 -0
- package/generators/relation/templates/controller-relation-template-has-many.ts.ejs +110 -0
- package/generators/relation/templates/controller-relation-template-has-one.ts.ejs +110 -0
- package/generators/relation/utils.generator.js +260 -0
- package/generators/repository/index.js +576 -0
- package/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs +21 -0
- package/generators/repository/templates/src/repositories/repository-kv-template.ts.ejs +19 -0
- package/generators/rest-crud/crud-rest-component.js +63 -0
- package/generators/rest-crud/index.js +423 -0
- package/generators/rest-crud/templates/src/model-endpoints/model.rest-config-template.ts.ejs +11 -0
- package/generators/service/index.js +351 -0
- package/generators/service/templates/local-service-class-template.ts.ejs +10 -0
- package/generators/service/templates/local-service-provider-template.ts.ejs +19 -0
- package/generators/service/templates/remote-service-proxy-template.ts.ejs +21 -0
- package/generators/update/index.js +55 -0
- package/intl/cs/messages.json +204 -0
- package/intl/de/messages.json +204 -0
- package/intl/en/messages.json +204 -0
- package/intl/es/messages.json +204 -0
- package/intl/fr/messages.json +204 -0
- package/intl/it/messages.json +204 -0
- package/intl/ja/messages.json +204 -0
- package/intl/ko/messages.json +204 -0
- package/intl/nl/messages.json +204 -0
- package/intl/pl/messages.json +204 -0
- package/intl/pt/messages.json +204 -0
- package/intl/ru/messages.json +204 -0
- package/intl/tr/messages.json +204 -0
- package/intl/zh-Hans/messages.json +204 -0
- package/intl/zh-Hant/messages.json +204 -0
- package/lib/artifact-generator.js +138 -39
- package/lib/ast-helper.js +214 -0
- package/lib/base-generator.js +509 -0
- package/lib/cli.js +233 -0
- package/lib/connectors.json +894 -0
- package/lib/debug.js +16 -0
- package/lib/globalize.js +12 -0
- package/lib/model-discoverer.js +118 -0
- package/lib/project-generator.js +154 -57
- package/lib/tab-completion.js +127 -0
- package/lib/update-index.js +44 -0
- package/lib/utils.js +689 -20
- package/lib/version-helper.js +299 -0
- package/package.json +183 -39
- package/CHANGELOG.md +0 -86
- package/bin/cli.js +0 -66
- package/generators/app/templates/index.js +0 -14
- package/generators/app/templates/src/application.ts +0 -27
- package/generators/app/templates/src/controllers/ping-controller.ts +0 -25
- package/generators/app/templates/src/index.ts +0 -25
- package/generators/app/templates/test/ping-controller.test.ts +0 -46
- package/generators/extension/templates/index.js +0 -8
- package/generators/extension/templates/src/component.ts +0 -14
- package/generators/extension/templates/src/index.ts +0 -6
- package/generators/project/templates/.npmrc +0 -1
- package/generators/project/templates/.yo-rc.json +0 -1
- package/generators/project/templates/README.md +0 -4
- package/generators/project/templates/index.d.ts +0 -6
- package/generators/project/templates/index.ts +0 -11
- package/generators/project/templates/package.json +0 -79
- package/generators/project/templates/package.plain.json +0 -82
- package/generators/project/templates/test/mocha.opts +0 -1
- package/generators/project/templates/tsconfig.json +0 -29
- package/generators/project/templates/tslint.build.json +0 -17
- package/generators/project/templates/tslint.json +0 -33
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/cli
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const debug = require('../../lib/debug')('openapi-generator');
|
|
9
|
+
const {mapSchemaType, registerSchema} = require('./schema-helper');
|
|
10
|
+
const {
|
|
11
|
+
isExtension,
|
|
12
|
+
titleCase,
|
|
13
|
+
debugJson,
|
|
14
|
+
toFileName,
|
|
15
|
+
printSpecObject,
|
|
16
|
+
camelCase,
|
|
17
|
+
escapeIdentifier,
|
|
18
|
+
} = require('./utils');
|
|
19
|
+
|
|
20
|
+
const {validRegex} = require('../../lib/utils');
|
|
21
|
+
|
|
22
|
+
const HTTP_VERBS = [
|
|
23
|
+
'get',
|
|
24
|
+
'put',
|
|
25
|
+
'post',
|
|
26
|
+
'delete',
|
|
27
|
+
'options',
|
|
28
|
+
'head',
|
|
29
|
+
'patch',
|
|
30
|
+
'trace',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a key is an http verb
|
|
35
|
+
* @param {string} key
|
|
36
|
+
*/
|
|
37
|
+
function isHttpVerb(key) {
|
|
38
|
+
return key && HTTP_VERBS.includes(key.toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Find the tag description by name
|
|
43
|
+
* @param {object} apiSpec API spec object
|
|
44
|
+
* @param {string} tag Tag name
|
|
45
|
+
*/
|
|
46
|
+
function getTagDescription(apiSpec, tag) {
|
|
47
|
+
if (Array.isArray(apiSpec.tags)) {
|
|
48
|
+
const tagEntry = apiSpec.tags.find(t => t.name === tag);
|
|
49
|
+
return tagEntry && tagEntry.description;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merge path level parameters into the operation level
|
|
56
|
+
* @param {OperationObject} operationSpec Operation spec
|
|
57
|
+
* @param {ParameterObject[]} pathLevelParams Path level parameters
|
|
58
|
+
*/
|
|
59
|
+
function mergeParameters(operationSpec, pathLevelParams) {
|
|
60
|
+
if (!pathLevelParams || pathLevelParams.length === 0) return;
|
|
61
|
+
for (const p of pathLevelParams) {
|
|
62
|
+
operationSpec.parameters = operationSpec.parameters || [];
|
|
63
|
+
let found = false;
|
|
64
|
+
for (const param of operationSpec.parameters) {
|
|
65
|
+
if (p.name === param.name && p.in === param.in) {
|
|
66
|
+
// The parameter has been overridden at operation level
|
|
67
|
+
found = true;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!found) {
|
|
72
|
+
operationSpec.parameters.push(p);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Group operations by controller class name
|
|
79
|
+
* @param {object} apiSpec OpenAPI 3.x spec
|
|
80
|
+
*/
|
|
81
|
+
function groupOperationsByController(apiSpec) {
|
|
82
|
+
const operationsMapByController = {};
|
|
83
|
+
if (apiSpec.paths == null) return operationsMapByController;
|
|
84
|
+
for (const path in apiSpec.paths) {
|
|
85
|
+
if (isExtension(path)) continue;
|
|
86
|
+
debug('Path: %s', path);
|
|
87
|
+
const pathLevelParams = apiSpec.paths[path].parameters;
|
|
88
|
+
for (const verb in apiSpec.paths[path]) {
|
|
89
|
+
if (isExtension(verb) || !isHttpVerb(verb)) continue;
|
|
90
|
+
debug('Verb: %s', verb);
|
|
91
|
+
const op = apiSpec.paths[path][verb];
|
|
92
|
+
mergeParameters(op, pathLevelParams);
|
|
93
|
+
const operation = {
|
|
94
|
+
path,
|
|
95
|
+
verb,
|
|
96
|
+
spec: op,
|
|
97
|
+
};
|
|
98
|
+
debugJson('Operation', operation);
|
|
99
|
+
// Default to `openapi` if no tags are present
|
|
100
|
+
let controllers = ['OpenApiController'];
|
|
101
|
+
if (op['x-controller-name']) {
|
|
102
|
+
controllers = [op['x-controller-name']];
|
|
103
|
+
} else if (Array.isArray(op.tags) && op.tags.length) {
|
|
104
|
+
// Only add the operation to first tag to avoid duplicate routes
|
|
105
|
+
controllers = [titleCase(op.tags[0] + 'Controller')];
|
|
106
|
+
}
|
|
107
|
+
controllers.forEach((c, index) => {
|
|
108
|
+
/**
|
|
109
|
+
* type ControllerSpec = {
|
|
110
|
+
* tag?: string;
|
|
111
|
+
* description?: string;
|
|
112
|
+
* operations: Operation[]
|
|
113
|
+
* }
|
|
114
|
+
*/
|
|
115
|
+
let controllerSpec = operationsMapByController[c];
|
|
116
|
+
if (!controllerSpec) {
|
|
117
|
+
controllerSpec = {operations: [operation]};
|
|
118
|
+
if (op.tags && op.tags[index]) {
|
|
119
|
+
controllerSpec.tag = op.tags[index];
|
|
120
|
+
controllerSpec.description =
|
|
121
|
+
getTagDescription(apiSpec, controllerSpec.tag) || '';
|
|
122
|
+
}
|
|
123
|
+
const apiSpecJson = printSpecObject({
|
|
124
|
+
components: apiSpec.components,
|
|
125
|
+
paths: {},
|
|
126
|
+
});
|
|
127
|
+
controllerSpec.decoration = `@api(${apiSpecJson})`;
|
|
128
|
+
operationsMapByController[c] = controllerSpec;
|
|
129
|
+
} else {
|
|
130
|
+
controllerSpec.operations.push(operation);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return operationsMapByController;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the method name for an operation spec. If `x-operation-name` is set, use it
|
|
140
|
+
* as-is. Otherwise, derive the name from `operationId`.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} opSpec OpenAPI operation spec
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
function getMethodName(opSpec) {
|
|
146
|
+
let methodName;
|
|
147
|
+
|
|
148
|
+
if (opSpec['x-operation-name']) {
|
|
149
|
+
methodName = opSpec['x-operation-name'];
|
|
150
|
+
} else if (opSpec.operationId) {
|
|
151
|
+
methodName = escapeIdentifier(opSpec.operationId);
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(
|
|
154
|
+
'Could not infer method name from OpenAPI Operation Object. OpenAPI Operation Objects must have either `x-operation-name` or `operationId`.',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return camelCase(methodName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function registerAnonymousSchema(names, schema, typeRegistry) {
|
|
162
|
+
if (!typeRegistry.promoteAnonymousSchemas) {
|
|
163
|
+
// Skip anonymous schemas
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Skip referenced schemas
|
|
168
|
+
if (schema['x-$ref']) return;
|
|
169
|
+
|
|
170
|
+
// Only map object/array types
|
|
171
|
+
if (
|
|
172
|
+
schema.properties ||
|
|
173
|
+
schema.type === 'object' ||
|
|
174
|
+
schema.type === 'array'
|
|
175
|
+
) {
|
|
176
|
+
if (typeRegistry.anonymousSchemaNames == null) {
|
|
177
|
+
typeRegistry.anonymousSchemaNames = new Set();
|
|
178
|
+
}
|
|
179
|
+
// Infer the schema name
|
|
180
|
+
let schemaName;
|
|
181
|
+
if (Array.isArray(names)) {
|
|
182
|
+
schemaName = names.join('-');
|
|
183
|
+
} else if (typeof names === 'string') {
|
|
184
|
+
schemaName = names;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!schemaName && schema.title) {
|
|
188
|
+
schemaName = schema.title;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
schemaName = camelCase(schemaName);
|
|
192
|
+
|
|
193
|
+
// Make sure the schema name is unique
|
|
194
|
+
let index = 1;
|
|
195
|
+
while (typeRegistry.anonymousSchemaNames.has(schemaName)) {
|
|
196
|
+
schemaName = schemaName + index++;
|
|
197
|
+
}
|
|
198
|
+
typeRegistry.anonymousSchemaNames.add(schemaName);
|
|
199
|
+
|
|
200
|
+
registerSchema(schemaName, schema, typeRegistry);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build method spec for an operation
|
|
206
|
+
* @param {object} OpenAPI operation
|
|
207
|
+
*/
|
|
208
|
+
function buildMethodSpec(controllerSpec, op, options) {
|
|
209
|
+
const methodName = getMethodName(op.spec);
|
|
210
|
+
const opName = op.spec['x-operation-name'] || op.spec['operationId'];
|
|
211
|
+
controllerSpec.methodMapping[methodName] = opName;
|
|
212
|
+
const comments = [];
|
|
213
|
+
let args = [];
|
|
214
|
+
let interfaceArgs = [];
|
|
215
|
+
const namedParameters = [];
|
|
216
|
+
const parameters = op.spec.parameters;
|
|
217
|
+
// Keep track of param names to avoid duplicates
|
|
218
|
+
const paramNames = {};
|
|
219
|
+
const interfaceParamNames = {};
|
|
220
|
+
if (parameters) {
|
|
221
|
+
args = parameters.map(p => {
|
|
222
|
+
const {paramType, argName} = buildParameter(p, paramNames, comments);
|
|
223
|
+
const paramJson = printSpecObject(p);
|
|
224
|
+
const optionalType = !p.required ? ' | undefined' : '';
|
|
225
|
+
return `@param(${paramJson}) ${argName}: ${paramType.signature}${optionalType}`;
|
|
226
|
+
});
|
|
227
|
+
interfaceArgs = parameters.map(p => {
|
|
228
|
+
const param = buildParameter(p, interfaceParamNames);
|
|
229
|
+
namedParameters.push(param);
|
|
230
|
+
const {paramType, argName} = param;
|
|
231
|
+
const optionalType = !p.required ? ' | undefined' : '';
|
|
232
|
+
return `${argName}: ${paramType.signature}${optionalType}`;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (op.spec.requestBody) {
|
|
236
|
+
/**
|
|
237
|
+
* requestBody:
|
|
238
|
+
* description: Pet to add to the store
|
|
239
|
+
* required: true
|
|
240
|
+
* content:
|
|
241
|
+
* application/json:
|
|
242
|
+
* schema:
|
|
243
|
+
* $ref: '#/components/schemas/NewPet'
|
|
244
|
+
*/
|
|
245
|
+
let bodyType = {signature: 'unknown'};
|
|
246
|
+
const content = op.spec.requestBody.content;
|
|
247
|
+
const contentType =
|
|
248
|
+
content &&
|
|
249
|
+
(content['application/json'] || content[Object.keys(content)[0]]);
|
|
250
|
+
|
|
251
|
+
let bodyName = 'requestBody';
|
|
252
|
+
if (bodyName in paramNames) {
|
|
253
|
+
bodyName = `${bodyName}${paramNames[bodyName]++}`;
|
|
254
|
+
}
|
|
255
|
+
bodyName = escapeIdentifier(bodyName);
|
|
256
|
+
if (contentType && contentType.schema) {
|
|
257
|
+
registerAnonymousSchema(
|
|
258
|
+
[methodName, bodyName],
|
|
259
|
+
contentType.schema,
|
|
260
|
+
options,
|
|
261
|
+
);
|
|
262
|
+
bodyType = mapSchemaType(contentType.schema, options);
|
|
263
|
+
addImportsForType(bodyType);
|
|
264
|
+
}
|
|
265
|
+
const bodyParam = bodyName; // + (op.spec.requestBody.required ? '' : '?');
|
|
266
|
+
// Add body as the last param
|
|
267
|
+
const bodySpecJson = printSpecObject(op.spec.requestBody);
|
|
268
|
+
args.push(
|
|
269
|
+
`@requestBody(${bodySpecJson}) ${bodyParam}: ${bodyType.signature}`,
|
|
270
|
+
);
|
|
271
|
+
interfaceArgs.push(`${bodyParam}: ${bodyType.signature}`);
|
|
272
|
+
namedParameters.push({
|
|
273
|
+
paramName: 'requestBody',
|
|
274
|
+
paramType: bodyType,
|
|
275
|
+
argName: bodyParam,
|
|
276
|
+
});
|
|
277
|
+
let bodyDescription = op.spec.requestBody.description || '';
|
|
278
|
+
bodyDescription = bodyDescription ? ` ${bodyDescription}` : '';
|
|
279
|
+
comments.push(`@param ${bodyName}${bodyDescription}`);
|
|
280
|
+
}
|
|
281
|
+
let returnType = {signature: 'unknown'};
|
|
282
|
+
const responses = op.spec.responses;
|
|
283
|
+
if (responses) {
|
|
284
|
+
/**
|
|
285
|
+
* responses:
|
|
286
|
+
* '200':
|
|
287
|
+
* description: pet response
|
|
288
|
+
* content:
|
|
289
|
+
* application/json:
|
|
290
|
+
* schema:
|
|
291
|
+
* type: array
|
|
292
|
+
* items:
|
|
293
|
+
* $ref: '#/components/schemas/Pet'
|
|
294
|
+
*/
|
|
295
|
+
for (const code in responses) {
|
|
296
|
+
if (isExtension(code)) continue;
|
|
297
|
+
if (code !== '200' && code !== '201') continue;
|
|
298
|
+
const content = responses[code].content;
|
|
299
|
+
const contentType =
|
|
300
|
+
content &&
|
|
301
|
+
(content['application/json'] || content[Object.keys(content)[0]]);
|
|
302
|
+
if (contentType && contentType.schema) {
|
|
303
|
+
registerAnonymousSchema(
|
|
304
|
+
[methodName, 'responseBody'],
|
|
305
|
+
contentType.schema,
|
|
306
|
+
options,
|
|
307
|
+
);
|
|
308
|
+
returnType = mapSchemaType(contentType.schema, options);
|
|
309
|
+
addImportsForType(returnType);
|
|
310
|
+
comments.push(`@returns ${responses[code].description || ''}`);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const signature = `async ${methodName}(${args.join(', ')}): Promise<${
|
|
316
|
+
returnType.signature
|
|
317
|
+
}>`;
|
|
318
|
+
const signatureForInterface = `${methodName}(${interfaceArgs.join(
|
|
319
|
+
', ',
|
|
320
|
+
)}): Promise<${returnType.signature}>`;
|
|
321
|
+
|
|
322
|
+
const argTypes = namedParameters
|
|
323
|
+
.map(p => {
|
|
324
|
+
if (p.paramName.match(validRegex)) {
|
|
325
|
+
return `${p.paramName}: ${p.paramType.signature}`;
|
|
326
|
+
}
|
|
327
|
+
return `'${p.paramName}': ${p.paramType.signature}`;
|
|
328
|
+
})
|
|
329
|
+
.join('; ');
|
|
330
|
+
const signatureForNamedParams = `${methodName}(params: { ${argTypes} }): Promise<${returnType.signature}>`;
|
|
331
|
+
|
|
332
|
+
comments.unshift(op.spec.description || '', '');
|
|
333
|
+
|
|
334
|
+
// Normalize path variable names to alphanumeric characters including the
|
|
335
|
+
// underscore (Equivalent to [A-Za-z0-9_]). Please note `@loopback/rest`
|
|
336
|
+
// does not allow other characters that don't match `\w`.
|
|
337
|
+
const opPath = op.path.replace(/\{[^\}]+\}/g, varName =>
|
|
338
|
+
varName.replace(/[^\w\{\}]+/g, '_'),
|
|
339
|
+
);
|
|
340
|
+
const opSpecJson = printSpecObject(op.spec);
|
|
341
|
+
const methodSpec = {
|
|
342
|
+
description: op.spec.description,
|
|
343
|
+
comments,
|
|
344
|
+
decoration: `@operation('${op.verb}', '${opPath}', ${opSpecJson})`,
|
|
345
|
+
signature,
|
|
346
|
+
signatureForInterface,
|
|
347
|
+
signatureForNamedParams,
|
|
348
|
+
};
|
|
349
|
+
if (op.spec['x-implementation']) {
|
|
350
|
+
methodSpec.implementation = op.spec['x-implementation'];
|
|
351
|
+
}
|
|
352
|
+
return methodSpec;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Build the parameter
|
|
356
|
+
* @param {object} paramSpec
|
|
357
|
+
* @param {string[]} names
|
|
358
|
+
* @param {string[]} _comments
|
|
359
|
+
*/
|
|
360
|
+
function buildParameter(paramSpec, names, _comments) {
|
|
361
|
+
let argName = escapeIdentifier(paramSpec.name);
|
|
362
|
+
if (argName in names) {
|
|
363
|
+
argName = `${argName}${names[argName]++}`;
|
|
364
|
+
} else {
|
|
365
|
+
names[argName] = 1;
|
|
366
|
+
}
|
|
367
|
+
registerAnonymousSchema([methodName, argName], paramSpec.schema, options);
|
|
368
|
+
let propSchema = paramSpec.schema;
|
|
369
|
+
if (propSchema == null) {
|
|
370
|
+
/*
|
|
371
|
+
{
|
|
372
|
+
name: 'where',
|
|
373
|
+
in: 'query',
|
|
374
|
+
content: { 'application/json': { schema: [Object] } }
|
|
375
|
+
}
|
|
376
|
+
*/
|
|
377
|
+
const content = paramSpec.content;
|
|
378
|
+
const json = content && content['application/json'];
|
|
379
|
+
propSchema = json && json.schema;
|
|
380
|
+
if (propSchema == null && content) {
|
|
381
|
+
for (const m of content) {
|
|
382
|
+
propSchema = content[m].schema;
|
|
383
|
+
if (propSchema) break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
propSchema = propSchema || {type: 'string'};
|
|
388
|
+
const paramType = mapSchemaType(propSchema, options);
|
|
389
|
+
addImportsForType(paramType);
|
|
390
|
+
if (Array.isArray(_comments)) {
|
|
391
|
+
_comments.push(`@param ${argName} ${paramSpec.description || ''}`);
|
|
392
|
+
}
|
|
393
|
+
// Normalize parameter name to match `\w`
|
|
394
|
+
let paramName = paramSpec.name;
|
|
395
|
+
if (paramSpec.in === 'path') {
|
|
396
|
+
paramName = paramName.replace(/[^\w]+/g, '_');
|
|
397
|
+
}
|
|
398
|
+
return {paramName, paramType, argName};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function addImportsForType(typeSpec) {
|
|
402
|
+
if (typeSpec.className && typeSpec.import) {
|
|
403
|
+
const importStmt = typeSpec.import.replace('./', '../models/');
|
|
404
|
+
if (!controllerSpec.imports.includes(importStmt)) {
|
|
405
|
+
controllerSpec.imports.push(importStmt);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (!typeSpec.className && Array.isArray(typeSpec.imports)) {
|
|
409
|
+
typeSpec.imports.forEach(i => {
|
|
410
|
+
i = i.replace('./', '../models/');
|
|
411
|
+
if (!controllerSpec.imports.includes(i)) {
|
|
412
|
+
controllerSpec.imports.push(i);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Build an array of controller specs
|
|
421
|
+
* @param {object} operationsMapByController
|
|
422
|
+
*/
|
|
423
|
+
function buildControllerSpecs(operationsMapByController, options) {
|
|
424
|
+
const controllerSpecs = [];
|
|
425
|
+
for (const controller in operationsMapByController) {
|
|
426
|
+
const entry = operationsMapByController[controller];
|
|
427
|
+
const controllerSpec = {
|
|
428
|
+
tag: entry.tag || '',
|
|
429
|
+
decoration: entry.decoration,
|
|
430
|
+
description: entry.description || '',
|
|
431
|
+
className: controller,
|
|
432
|
+
// Class name for service proxy
|
|
433
|
+
serviceClassName: getBaseName(controller, 'Controller') + 'Service',
|
|
434
|
+
imports: [],
|
|
435
|
+
methodMapping: {},
|
|
436
|
+
};
|
|
437
|
+
controllerSpec.methods = entry.operations.map(op =>
|
|
438
|
+
buildMethodSpec(controllerSpec, op, options),
|
|
439
|
+
);
|
|
440
|
+
controllerSpec.methodMappingObject = printSpecObject(
|
|
441
|
+
controllerSpec.methodMapping,
|
|
442
|
+
);
|
|
443
|
+
controllerSpecs.push(controllerSpec);
|
|
444
|
+
}
|
|
445
|
+
return controllerSpecs;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Generate an array of controller specs for the openapi spec
|
|
450
|
+
* @param {object} apiSpec
|
|
451
|
+
*/
|
|
452
|
+
function generateControllerSpecs(apiSpec, options) {
|
|
453
|
+
const operationsMapByController = groupOperationsByController(apiSpec);
|
|
454
|
+
return buildControllerSpecs(operationsMapByController, options);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getControllerFileName(controllerName) {
|
|
458
|
+
const name = getBaseName(controllerName, 'Controller');
|
|
459
|
+
return toFileName(name) + '.controller.ts';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Get the base name by trimming the suffix
|
|
464
|
+
* @param {string} fullName
|
|
465
|
+
* @param {string} suffix
|
|
466
|
+
*/
|
|
467
|
+
function getBaseName(fullName, suffix) {
|
|
468
|
+
if (fullName.endsWith(suffix)) {
|
|
469
|
+
return fullName.substring(0, fullName.length - suffix.length);
|
|
470
|
+
}
|
|
471
|
+
return fullName;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function getServiceFileName(serviceName) {
|
|
475
|
+
const name = getBaseName(serviceName, 'Service');
|
|
476
|
+
return toFileName(name) + '.service.ts';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
module.exports = {
|
|
480
|
+
getControllerFileName,
|
|
481
|
+
getMethodName,
|
|
482
|
+
getServiceFileName,
|
|
483
|
+
generateControllerSpecs,
|
|
484
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/cli
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const SwaggerParser = require('swagger-parser');
|
|
9
|
+
const swagger2openapi = require('swagger2openapi');
|
|
10
|
+
const {debugJson, cloneSpecObject} = require('./utils');
|
|
11
|
+
const {generateControllerSpecs} = require('./spec-helper');
|
|
12
|
+
const {generateModelSpecs, registerNamedSchemas} = require('./schema-helper');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load swagger specs from the given url or file path; handle yml or json
|
|
16
|
+
* @param {String} specUrlStr The url or file path to the swagger spec
|
|
17
|
+
*/
|
|
18
|
+
async function loadSpec(specUrlStr, {log, validate} = {}) {
|
|
19
|
+
if (typeof log === 'function') {
|
|
20
|
+
log(chalk.blue('Loading ' + specUrlStr + '...'));
|
|
21
|
+
}
|
|
22
|
+
const parser = new SwaggerParser();
|
|
23
|
+
let spec = await parser.parse(specUrlStr);
|
|
24
|
+
if (spec.swagger === '2.0') {
|
|
25
|
+
debugJson('Swagger spec loaded: ', spec);
|
|
26
|
+
spec = (await swagger2openapi.convertObj(spec, {patch: true})).openapi;
|
|
27
|
+
debugJson('OpenAPI spec converted from Swagger: ', spec);
|
|
28
|
+
} else if (spec.openapi) {
|
|
29
|
+
debugJson('OpenAPI spec loaded: ', spec);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
spec = cloneSpecObject(spec);
|
|
33
|
+
|
|
34
|
+
// Validate and deference the spec
|
|
35
|
+
if (validate) {
|
|
36
|
+
spec = await parser.validate(spec, {
|
|
37
|
+
dereference: {
|
|
38
|
+
circular: true, // Allow circular $refs
|
|
39
|
+
},
|
|
40
|
+
validate: {
|
|
41
|
+
spec: true, // Don't validate against the Swagger spec
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
spec = await parser.dereference(spec);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return spec;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function loadAndBuildSpec(
|
|
52
|
+
url,
|
|
53
|
+
{log, validate, promoteAnonymousSchemas} = {},
|
|
54
|
+
) {
|
|
55
|
+
const apiSpec = await loadSpec(url, {log, validate});
|
|
56
|
+
// First populate the type registry for named schemas
|
|
57
|
+
const typeRegistry = {
|
|
58
|
+
objectTypeMapping: new Map(),
|
|
59
|
+
schemaMapping: {},
|
|
60
|
+
promoteAnonymousSchemas,
|
|
61
|
+
};
|
|
62
|
+
registerNamedSchemas(apiSpec, typeRegistry);
|
|
63
|
+
const controllerSpecs = generateControllerSpecs(apiSpec, typeRegistry);
|
|
64
|
+
const modelSpecs = generateModelSpecs(apiSpec, typeRegistry);
|
|
65
|
+
return {
|
|
66
|
+
apiSpec,
|
|
67
|
+
modelSpecs,
|
|
68
|
+
controllerSpecs,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
loadSpec,
|
|
74
|
+
loadAndBuildSpec,
|
|
75
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {api, operation, param, requestBody} from '@loopback/rest';
|
|
2
|
+
<%_
|
|
3
|
+
imports.forEach(i => {
|
|
4
|
+
-%>
|
|
5
|
+
<%- i %>
|
|
6
|
+
<%_
|
|
7
|
+
});
|
|
8
|
+
-%>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The controller class is generated from OpenAPI spec with operations tagged
|
|
12
|
+
* by <%- tag || '<no-tag>' %>.
|
|
13
|
+
*
|
|
14
|
+
<%_ const _comment = escapeComment(description);
|
|
15
|
+
if (_comment) {
|
|
16
|
+
-%>
|
|
17
|
+
* <%- _comment %>
|
|
18
|
+
<%_ } -%>
|
|
19
|
+
*/
|
|
20
|
+
<%- decoration %>
|
|
21
|
+
export class <%- className %> {
|
|
22
|
+
constructor() {}
|
|
23
|
+
|
|
24
|
+
<%_ for (const m of methods) { -%>
|
|
25
|
+
/**
|
|
26
|
+
<%_ for (const c of m.comments) {
|
|
27
|
+
const _comment = escapeComment(c);
|
|
28
|
+
if (_comment) {
|
|
29
|
+
-%>
|
|
30
|
+
* <%- _comment %>
|
|
31
|
+
<%_ } else { -%>
|
|
32
|
+
*
|
|
33
|
+
<%_ }
|
|
34
|
+
} -%>
|
|
35
|
+
*/
|
|
36
|
+
<%- m.decoration %>
|
|
37
|
+
<%- m.signature %> {
|
|
38
|
+
<%- m.implementation || "throw new Error('Not implemented');" %>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
<%_ } -%>
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inject,
|
|
3
|
+
lifeCycleObserver,
|
|
4
|
+
LifeCycleObserver,
|
|
5
|
+
} from '@loopback/core';
|
|
6
|
+
import {juggler} from '@loopback/repository';
|
|
7
|
+
import {HttpErrors} from '@loopback/rest';
|
|
8
|
+
|
|
9
|
+
const config = <%- dsConfigString %>;
|
|
10
|
+
|
|
11
|
+
@lifeCycleObserver('datasource')
|
|
12
|
+
export class <%- className %>DataSource extends juggler.DataSource
|
|
13
|
+
implements LifeCycleObserver {
|
|
14
|
+
static dataSourceName = '<%- name %>';
|
|
15
|
+
static readonly defaultConfig = config;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
@inject('datasources.config.<%- name %>', {optional: true})
|
|
19
|
+
dsConfig: object = config,
|
|
20
|
+
) {
|
|
21
|
+
super({transformResponse, ...dsConfig});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Transform the http response into the return value
|
|
27
|
+
*/
|
|
28
|
+
function transformResponse(response: {
|
|
29
|
+
url: string,
|
|
30
|
+
method: string,
|
|
31
|
+
status: number,
|
|
32
|
+
statusText: string,
|
|
33
|
+
headers: object,
|
|
34
|
+
text: string,
|
|
35
|
+
body: unknown,
|
|
36
|
+
}) {
|
|
37
|
+
if (response.status < 400) {
|
|
38
|
+
return response.body ?? response.text;
|
|
39
|
+
}
|
|
40
|
+
const err = HttpErrors(response.status, response.statusText, response);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|