@loopback/cli 4.0.0-alpha.7 → 4.0.0

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.
Files changed (159) hide show
  1. package/.yo-rc.json +1697 -0
  2. package/{generators/project/templates/LICENSE → LICENSE} +2 -1
  3. package/README.md +44 -43
  4. package/bin/cli-main.js +61 -0
  5. package/generators/app/index.js +109 -15
  6. package/generators/app/templates/.dockerignore +5 -0
  7. package/generators/app/templates/Dockerfile +28 -0
  8. package/generators/app/templates/README.md.ejs +130 -0
  9. package/generators/app/templates/public/index.html.ejs +88 -0
  10. package/generators/app/templates/{test → src/__tests__}/README.md +0 -1
  11. package/generators/app/templates/src/__tests__/acceptance/home-page.acceptance.ts.ejs +31 -0
  12. package/generators/app/templates/src/__tests__/acceptance/ping.controller.acceptance.ts.ejs +21 -0
  13. package/generators/app/templates/src/__tests__/acceptance/test-helper.ts.ejs +32 -0
  14. package/generators/app/templates/src/application.ts.ejs +70 -0
  15. package/generators/app/templates/src/controllers/README.md +6 -0
  16. package/generators/app/templates/src/controllers/index.ts.ejs +1 -0
  17. package/generators/app/templates/src/controllers/ping.controller.ts.ejs +55 -0
  18. package/generators/app/templates/src/datasources/README.md +3 -0
  19. package/generators/app/templates/src/index.ts.ejs +39 -0
  20. package/generators/app/templates/src/migrate.ts.ejs +20 -0
  21. package/generators/app/templates/src/models/README.md +3 -0
  22. package/generators/app/templates/src/openapi-spec.ts.ejs +23 -0
  23. package/generators/app/templates/src/sequence.ts.ejs +3 -0
  24. package/generators/controller/index.js +279 -0
  25. package/generators/controller/templates/src/controllers/controller-rest-template.ts.ejs +150 -0
  26. package/generators/controller/templates/src/controllers/controller-template.ts.ejs +8 -0
  27. package/generators/copyright/fs.js +46 -0
  28. package/generators/copyright/git.js +78 -0
  29. package/generators/copyright/header.js +306 -0
  30. package/generators/copyright/index.js +230 -0
  31. package/generators/copyright/license.js +105 -0
  32. package/generators/datasource/index.js +341 -0
  33. package/generators/datasource/templates/datasource.ts.ejs +22 -0
  34. package/generators/discover/import-discovered-model.js +70 -0
  35. package/generators/discover/index.js +349 -0
  36. package/generators/example/downloader.js +16 -0
  37. package/generators/example/index.js +176 -0
  38. package/generators/extension/index.js +34 -5
  39. package/generators/extension/templates/README.md.ejs +32 -0
  40. package/generators/extension/templates/{test → src/__tests__}/acceptance/README.md +0 -0
  41. package/generators/extension/templates/{test → src/__tests__}/integration/README.md +0 -0
  42. package/generators/extension/templates/{test → src/__tests__}/unit/README.md +0 -0
  43. package/generators/extension/templates/src/component.ts.ejs +22 -0
  44. package/generators/extension/templates/src/controllers/README.md +3 -2
  45. package/generators/extension/templates/src/decorators/README.md +10 -4
  46. package/generators/extension/templates/src/index.ts.ejs +3 -0
  47. package/generators/extension/templates/src/keys.ts.ejs +11 -0
  48. package/generators/extension/templates/src/mixins/README.md +77 -21
  49. package/generators/extension/templates/src/providers/README.md +51 -25
  50. package/generators/extension/templates/src/repositories/README.md +1 -1
  51. package/generators/extension/templates/src/types.ts.ejs +15 -0
  52. package/generators/import-lb3-models/index.js +197 -0
  53. package/generators/import-lb3-models/lb3app-loader.js +31 -0
  54. package/generators/import-lb3-models/migrate-model.js +249 -0
  55. package/generators/import-lb3-models/model-names.js +32 -0
  56. package/generators/interceptor/index.js +178 -0
  57. package/generators/interceptor/templates/interceptor-template.ts.ejs +62 -0
  58. package/generators/model/index.js +536 -0
  59. package/generators/model/property-definition.js +85 -0
  60. package/generators/model/templates/model.ts.ejs +42 -0
  61. package/generators/observer/index.js +132 -0
  62. package/generators/observer/templates/observer-template.ts.ejs +40 -0
  63. package/generators/openapi/README.md +211 -0
  64. package/generators/openapi/index.js +535 -0
  65. package/generators/openapi/schema-helper.js +447 -0
  66. package/generators/openapi/spec-helper.js +484 -0
  67. package/generators/openapi/spec-loader.js +75 -0
  68. package/generators/openapi/templates/src/controllers/controller-template.ts.ejs +43 -0
  69. package/generators/openapi/templates/src/datasources/datasource.ts.ejs +42 -0
  70. package/generators/openapi/templates/src/models/model-template.ts.ejs +71 -0
  71. package/generators/openapi/templates/src/models/type-template.ts.ejs +13 -0
  72. package/generators/openapi/templates/src/services/service-proxy-template.ts.ejs +55 -0
  73. package/generators/openapi/utils.js +322 -0
  74. package/generators/project/templates/.eslintignore +4 -0
  75. package/generators/project/templates/.eslintrc.js.ejs +3 -0
  76. package/generators/project/templates/.mocharc.json +5 -0
  77. package/generators/project/templates/.prettierignore +0 -2
  78. package/generators/project/templates/.prettierrc +2 -1
  79. package/generators/project/templates/.vscode/launch.json +38 -0
  80. package/generators/project/templates/.vscode/settings.json +32 -0
  81. package/generators/project/templates/.vscode/tasks.json +29 -0
  82. package/generators/project/templates/DEVELOPING.md +36 -0
  83. package/generators/project/templates/_.gitignore +3 -5
  84. package/generators/project/templates/package.json.ejs +175 -0
  85. package/generators/project/templates/package.plain.json.ejs +176 -0
  86. package/generators/project/templates/tsconfig.json.ejs +39 -0
  87. package/generators/relation/base-relation.generator.js +220 -0
  88. package/generators/relation/belongs-to-relation.generator.js +196 -0
  89. package/generators/relation/has-many-relation.generator.js +200 -0
  90. package/generators/relation/has-many-through-relation.generator.js +331 -0
  91. package/generators/relation/has-one-relation.generator.js +200 -0
  92. package/generators/relation/index.js +795 -0
  93. package/generators/relation/references-many-relation.generator.js +142 -0
  94. package/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs +38 -0
  95. package/generators/relation/templates/controller-relation-template-has-many-through.ts.ejs +110 -0
  96. package/generators/relation/templates/controller-relation-template-has-many.ts.ejs +110 -0
  97. package/generators/relation/templates/controller-relation-template-has-one.ts.ejs +110 -0
  98. package/generators/relation/utils.generator.js +260 -0
  99. package/generators/repository/index.js +576 -0
  100. package/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs +21 -0
  101. package/generators/repository/templates/src/repositories/repository-kv-template.ts.ejs +19 -0
  102. package/generators/rest-crud/crud-rest-component.js +63 -0
  103. package/generators/rest-crud/index.js +401 -0
  104. package/generators/rest-crud/templates/src/model-endpoints/model.rest-config-template.ts.ejs +10 -0
  105. package/generators/service/index.js +351 -0
  106. package/generators/service/templates/local-service-class-template.ts.ejs +10 -0
  107. package/generators/service/templates/local-service-provider-template.ts.ejs +19 -0
  108. package/generators/service/templates/remote-service-proxy-template.ts.ejs +21 -0
  109. package/generators/update/index.js +55 -0
  110. package/intl/cs/messages.json +204 -0
  111. package/intl/de/messages.json +204 -0
  112. package/intl/en/messages.json +204 -0
  113. package/intl/es/messages.json +204 -0
  114. package/intl/fr/messages.json +204 -0
  115. package/intl/it/messages.json +204 -0
  116. package/intl/ja/messages.json +204 -0
  117. package/intl/ko/messages.json +204 -0
  118. package/intl/nl/messages.json +204 -0
  119. package/intl/pl/messages.json +204 -0
  120. package/intl/pt/messages.json +204 -0
  121. package/intl/ru/messages.json +204 -0
  122. package/intl/tr/messages.json +204 -0
  123. package/intl/zh-Hans/messages.json +204 -0
  124. package/intl/zh-Hant/messages.json +204 -0
  125. package/lib/artifact-generator.js +189 -0
  126. package/lib/ast-helper.js +214 -0
  127. package/lib/base-generator.js +509 -0
  128. package/lib/cli.js +233 -0
  129. package/lib/connectors.json +894 -0
  130. package/lib/debug.js +16 -0
  131. package/lib/globalize.js +12 -0
  132. package/lib/model-discoverer.js +118 -0
  133. package/lib/project-generator.js +154 -57
  134. package/lib/tab-completion.js +127 -0
  135. package/lib/update-index.js +44 -0
  136. package/lib/utils.js +746 -22
  137. package/lib/version-helper.js +299 -0
  138. package/package.json +183 -32
  139. package/CHANGELOG.md +0 -64
  140. package/bin/cli.js +0 -65
  141. package/generators/app/templates/index.js +0 -14
  142. package/generators/app/templates/src/application.ts +0 -27
  143. package/generators/app/templates/src/controllers/ping-controller.ts +0 -25
  144. package/generators/app/templates/src/index.ts +0 -25
  145. package/generators/app/templates/test/ping-controller.test.ts +0 -46
  146. package/generators/extension/templates/index.js +0 -8
  147. package/generators/extension/templates/src/component.ts +0 -14
  148. package/generators/extension/templates/src/index.ts +0 -6
  149. package/generators/project/templates/.npmrc +0 -1
  150. package/generators/project/templates/.yo-rc.json +0 -1
  151. package/generators/project/templates/README.md +0 -4
  152. package/generators/project/templates/index.d.ts +0 -6
  153. package/generators/project/templates/index.ts +0 -11
  154. package/generators/project/templates/package.json +0 -79
  155. package/generators/project/templates/package.plain.json +0 -82
  156. package/generators/project/templates/test/mocha.opts +0 -1
  157. package/generators/project/templates/tsconfig.json +0 -29
  158. package/generators/project/templates/tslint.build.json +0 -17
  159. package/generators/project/templates/tslint.json +0 -33
@@ -0,0 +1,484 @@
1
+ // Copyright IBM Corp. 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. 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
+ }