@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.
Files changed (159) hide show
  1. package/.yo-rc.json +1719 -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 +240 -23
  25. package/generators/controller/templates/src/controllers/controller-rest-template.ts.ejs +150 -0
  26. package/generators/controller/templates/src/controllers/{controller-template.ts → controller-template.ts.ejs} +2 -2
  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 +411 -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 +49 -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 +423 -0
  104. package/generators/rest-crud/templates/src/model-endpoints/model.rest-config-template.ts.ejs +11 -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 +138 -39
  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 +689 -20
  137. package/lib/version-helper.js +299 -0
  138. package/package.json +183 -39
  139. package/CHANGELOG.md +0 -86
  140. package/bin/cli.js +0 -66
  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,535 @@
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 BaseGenerator = require('../../lib/base-generator');
9
+ const {debug, debugJson, validateUrlOrFile, escapeComment} = require('./utils');
10
+ const {loadAndBuildSpec} = require('./spec-loader');
11
+ const utils = require('../../lib/utils');
12
+ const {parse} = require('url');
13
+ const path = require('path');
14
+ const semver = require('semver');
15
+ const slash = require('slash');
16
+ const {getControllerFileName, getServiceFileName} = require('./spec-helper');
17
+
18
+ const updateIndex = require('../../lib/update-index');
19
+ const MODEL = 'models';
20
+ const CONTROLLER = 'controllers';
21
+ const DATASOURCE = 'datasources';
22
+ const SERVICE = 'services';
23
+ const g = require('../../lib/globalize');
24
+ const json5 = require('json5');
25
+
26
+ const isWindows = process.platform === 'win32';
27
+
28
+ module.exports = class OpenApiGenerator extends BaseGenerator {
29
+ // Note: arguments and options should be defined in the constructor.
30
+ constructor(args, opts) {
31
+ super(args, opts);
32
+ }
33
+
34
+ _setupGenerator() {
35
+ this.argument('url', {
36
+ description: g.f('URL or file path of the OpenAPI spec'),
37
+ required: false,
38
+ type: String,
39
+ });
40
+
41
+ this.option('url', {
42
+ description: g.f('URL or file path of the OpenAPI spec'),
43
+ required: false,
44
+ type: String,
45
+ });
46
+
47
+ this.option('validate', {
48
+ description: g.f('Validate the OpenAPI spec'),
49
+ required: false,
50
+ default: false,
51
+ type: Boolean,
52
+ });
53
+
54
+ this.option('server', {
55
+ description: g.f('Generate server-side controllers for the OpenAPI spec'),
56
+ required: false,
57
+ default: true,
58
+ type: Boolean,
59
+ });
60
+
61
+ this.option('client', {
62
+ description: g.f(
63
+ 'Generate client-side service proxies for the OpenAPI spec',
64
+ ),
65
+ required: false,
66
+ default: false,
67
+ type: Boolean,
68
+ });
69
+
70
+ this.option('datasource', {
71
+ type: String,
72
+ required: false,
73
+ description: g.f('A valid datasource name for the OpenAPI endpoint'),
74
+ });
75
+
76
+ this.option('baseModel', {
77
+ description: g.f('Base model class'),
78
+ required: false,
79
+ default: '',
80
+ type: String,
81
+ });
82
+
83
+ this.option('promote-anonymous-schemas', {
84
+ description: g.f('Promote anonymous schemas as models'),
85
+ required: false,
86
+ default: false,
87
+ type: Boolean,
88
+ });
89
+
90
+ return super._setupGenerator();
91
+ }
92
+
93
+ setOptions() {
94
+ return super.setOptions();
95
+ }
96
+
97
+ checkLoopBackProject() {
98
+ return super.checkLoopBackProject();
99
+ }
100
+
101
+ async promptDataSourceName() {
102
+ if (this.shouldExit()) return false;
103
+ if (
104
+ this.options.baseModel &&
105
+ this.options.baseModel !== 'Model' &&
106
+ this.options.baseModel !== 'Entity'
107
+ ) {
108
+ this.exit(
109
+ `Invalid baseModel: ${this.options.baseModel}. Valid values: Model, Entity.`,
110
+ );
111
+ return;
112
+ }
113
+ if (this.options.client !== true) return;
114
+ if (this.options.url) return;
115
+
116
+ debug('Prompting for a datasource');
117
+
118
+ // grab the datasource from the command line
119
+ const dsClass = this.options.datasource
120
+ ? utils.toClassName(this.options.datasource) + 'DataSource'
121
+ : '';
122
+
123
+ debug(`command line datasource is ${dsClass}`);
124
+
125
+ const dataSourcesDir = this.destinationPath(
126
+ `${utils.sourceRootDir}/${utils.datasourcesDir}`,
127
+ );
128
+
129
+ const dataSourceClasses = await utils.getArtifactList(
130
+ dataSourcesDir,
131
+ 'datasource',
132
+ true,
133
+ );
134
+
135
+ // List all data sources
136
+ const dataSourceList = dataSourceClasses.map(ds => ({
137
+ className: ds,
138
+ }));
139
+ debug('datasourceList from %s', dataSourcesDir, dataSourceList);
140
+
141
+ // Find openapi data sources
142
+ const openApiDataSources = dataSourceList.filter(ds => {
143
+ debug(`data source inspecting item: ${ds.className}`);
144
+ const dsObj = utils.getDataSourceConfig(dataSourcesDir, ds.className);
145
+
146
+ if (
147
+ dsObj.connector === 'openapi' ||
148
+ dsObj.connector === 'loopback-connector-openapi'
149
+ ) {
150
+ ds.usePositionalParams = dsObj.positional;
151
+ ds.name = dsObj.name;
152
+ ds.className = utils.toClassName(dsObj.name) + 'DataSource';
153
+ ds.specPath = dsObj.spec;
154
+ return true;
155
+ }
156
+ });
157
+
158
+ debug('Data sources using openapi connector', openApiDataSources);
159
+
160
+ if (openApiDataSources.length === 0) {
161
+ return;
162
+ }
163
+
164
+ const matchedDs = openApiDataSources.find(ds => ds.className === dsClass);
165
+ if (matchedDs) {
166
+ this.dataSourceInfo = {
167
+ name: matchedDs.name,
168
+ className: matchedDs.className,
169
+ usePositionalParams: matchedDs.usePositionalParams,
170
+ specPath: matchedDs.specPath,
171
+ };
172
+ this.log(
173
+ g.f('Datasource %s - %s found for OpenAPI: %s'),
174
+ this.options.datasource,
175
+ this.dataSourceInfo.className,
176
+ this.dataSourceInfo.specPath,
177
+ );
178
+ return;
179
+ }
180
+
181
+ if (dsClass) return;
182
+
183
+ const answers = await this.prompt([
184
+ {
185
+ type: 'list',
186
+ name: 'dataSource',
187
+ message: g.f('Please select the datasource'),
188
+ choices: openApiDataSources.map(ds => ({
189
+ name: `${ds.name} - ${ds.className}`,
190
+ value: ds,
191
+ })),
192
+ default: `${openApiDataSources[0].name} - ${openApiDataSources[0].className}`,
193
+ validate: utils.validateClassName,
194
+ },
195
+ ]);
196
+
197
+ debug('Datasource selected', answers);
198
+ if (answers && answers.dataSource) {
199
+ this.dataSourceInfo = {
200
+ name: answers.dataSource.name,
201
+ className: answers.dataSource.className,
202
+ usePositionalParams: answers.dataSource.usePositionalParams,
203
+ specPath: answers.dataSource.specPath,
204
+ };
205
+ this.log(
206
+ g.f('Datasource %s - %s selected: %s'),
207
+ this.dataSourceInfo.name,
208
+ this.dataSourceInfo.className,
209
+ this.dataSourceInfo.specPath,
210
+ );
211
+ }
212
+ }
213
+
214
+ async askForSpecUrlOrPath() {
215
+ if (this.shouldExit()) return;
216
+ if (this.dataSourceInfo && this.dataSourceInfo.specPath) {
217
+ this.url = this.dataSourceInfo.specPath;
218
+ return;
219
+ }
220
+ const prompts = [
221
+ {
222
+ name: 'url',
223
+ message: g.f('Enter the OpenAPI spec url or file path:'),
224
+ default: this.options.url,
225
+ validate: validateUrlOrFile,
226
+ when: this.options.url == null,
227
+ },
228
+ ];
229
+ const answers = await this.prompt(prompts);
230
+ if (answers.url) {
231
+ this.url = answers.url.trim();
232
+ } else {
233
+ this.url = this.options.url;
234
+ }
235
+ }
236
+
237
+ async loadAndBuildApiSpec() {
238
+ if (this.shouldExit()) return;
239
+ try {
240
+ const result = await loadAndBuildSpec(this.url, {
241
+ log: this.log,
242
+ validate: this.options.validate,
243
+ promoteAnonymousSchemas: this.options['promote-anonymous-schemas'],
244
+ });
245
+ debugJson('OpenAPI spec', result.apiSpec);
246
+ Object.assign(this, result);
247
+ } catch (e) {
248
+ this.exit(e);
249
+ }
250
+ }
251
+
252
+ async selectControllers() {
253
+ if (this.shouldExit()) return;
254
+ const choices = this.controllerSpecs.map(c => {
255
+ const names = [];
256
+ if (this.options.server !== false) {
257
+ names.push(c.className);
258
+ }
259
+ if (this.options.client === true) {
260
+ names.push(c.serviceClassName);
261
+ }
262
+ const name = c.tag ? `[${c.tag}] ${names.join(' ')}` : names.join(' ');
263
+ return {
264
+ name,
265
+ value: c.className,
266
+ checked: true,
267
+ };
268
+ });
269
+ const prompts = [
270
+ {
271
+ name: 'controllerSelections',
272
+ message: g.f(
273
+ 'Select controllers and/or service proxies to be generated:',
274
+ ),
275
+ type: 'checkbox',
276
+ choices: choices,
277
+ default: choices.map(c => c.value),
278
+ // Require at least one item to be selected
279
+ // This prevents users from accidentally pressing ENTER instead of SPACE
280
+ // to select an item from the list
281
+ validate: result => !!result.length,
282
+ },
283
+ ];
284
+ const selections = (await this.prompt(prompts)).controllerSelections;
285
+ this.selectedControllers = this.controllerSpecs.filter(c =>
286
+ selections.some(a => a === c.className),
287
+ );
288
+ this.selectedServices = this.selectedControllers;
289
+ this.selectedControllers.forEach(c => {
290
+ c.fileName = getControllerFileName(c.tag || c.className);
291
+ c.serviceFileName = getServiceFileName(c.tag || c.serviceClassName);
292
+ });
293
+ }
294
+
295
+ _generateControllers() {
296
+ const source = this.templatePath(
297
+ 'src/controllers/controller-template.ts.ejs',
298
+ );
299
+ for (const c of this.selectedControllers) {
300
+ const controllerFile = c.fileName;
301
+ if (debug.enabled) {
302
+ debug(`Artifact output filename set to: ${controllerFile}`);
303
+ }
304
+ const dest = this.destinationPath(`src/controllers/${controllerFile}`);
305
+ if (debug.enabled) {
306
+ debug('Copying artifact to: %s', dest);
307
+ }
308
+ this.copyTemplatedFiles(source, dest, mixinEscapeComment(c));
309
+ }
310
+ }
311
+
312
+ async _generateDataSource() {
313
+ let specPath = this.url;
314
+ const parsed = parse(this.url);
315
+ if (
316
+ // Relative paths and UNIX paths don't have any protocol set
317
+ parsed.protocol == null ||
318
+ // Support absolute Windows paths, e.g. "C:\some\dir\api.yaml"
319
+ // When such path is parsed as a URL, we end up with the drive ("C:")
320
+ // recognized as the protocol.
321
+ (isWindows && parsed.protocol.match(/^[a-zA-Z]:$/))
322
+ ) {
323
+ specPath = path.relative(this.destinationRoot(), this.url);
324
+ if (isWindows && !path.parse(specPath).root) {
325
+ // On Windows, convert the relative path to use Unix-style separator
326
+ // We need this behavior for our snapshot-based tests, but @bajtos
327
+ // thinks it is also nicer for users - at the end of the day,
328
+ // this is a spec URL and URLs always use forward-slash characters
329
+ specPath = slash(specPath);
330
+ }
331
+ }
332
+ this.dataSourceInfo.specPath = specPath;
333
+ const dsConfig = {
334
+ name: this.dataSourceInfo.name,
335
+ connector: 'openapi',
336
+ spec: this.dataSourceInfo.specPath,
337
+ validate: false,
338
+ positional:
339
+ this.dataSourceInfo.usePositionalParams !== false ? 'bodyLast' : false,
340
+ };
341
+
342
+ this.dataSourceInfo.dsConfigString = json5.stringify(dsConfig, null, 2);
343
+
344
+ const classTemplatePath = this.templatePath(
345
+ 'src/datasources/datasource.ts.ejs',
346
+ );
347
+ const dataSourceFile = this.dataSourceInfo.outFile;
348
+ if (debug.enabled) {
349
+ debug(`Artifact output filename set to: ${dataSourceFile}`);
350
+ }
351
+ const dest = this.destinationPath(`src/datasources/${dataSourceFile}`);
352
+ if (debug.enabled) {
353
+ debug('Copying artifact to: %s', dest);
354
+ }
355
+ const context = {...this.dataSourceInfo};
356
+ let dsClass = context.className;
357
+ dsClass = dsClass.endsWith('DataSource')
358
+ ? dsClass.substring(0, dsClass.length - 'DataSource'.length)
359
+ : dsClass;
360
+ context.className = dsClass;
361
+ this.copyTemplatedFiles(classTemplatePath, dest, context);
362
+ this.dataSources = [{fileName: dataSourceFile}];
363
+ }
364
+
365
+ _generateServiceProxies() {
366
+ const source = this.templatePath(
367
+ 'src/services/service-proxy-template.ts.ejs',
368
+ );
369
+ for (const c of this.selectedControllers) {
370
+ const file = c.serviceFileName;
371
+ if (debug.enabled) {
372
+ debug(`Artifact output filename set to: ${file}`);
373
+ }
374
+ const dest = this.destinationPath(`src/services/${file}`);
375
+ if (debug.enabled) {
376
+ debug('Copying artifact to: %s', dest);
377
+ }
378
+ const context = {
379
+ ...mixinEscapeComment(c),
380
+ usePositionalParams: this.dataSourceInfo.usePositionalParams,
381
+ dataSourceName: this.dataSourceInfo.name,
382
+ dataSourceClassName: this.dataSourceInfo.className,
383
+ };
384
+ this.copyTemplatedFiles(source, dest, context);
385
+ }
386
+ }
387
+
388
+ _generateModels() {
389
+ const modelSource = this.templatePath('src/models/model-template.ts.ejs');
390
+ const typeSource = this.templatePath('src/models/type-template.ts.ejs');
391
+ for (const m of this.modelSpecs) {
392
+ if (!m.fileName) continue;
393
+ m.baseModel = this.options.baseModel;
394
+ const modelFile = m.fileName;
395
+ if (debug.enabled) {
396
+ debug(`Artifact output filename set to: ${modelFile}`);
397
+ }
398
+ const dest = this.destinationPath(`src/models/${modelFile}`);
399
+ if (debug.enabled) {
400
+ debug('Copying artifact to: %s', dest);
401
+ }
402
+ const source = m.kind === 'class' ? modelSource : typeSource;
403
+ this.copyTemplatedFiles(source, dest, mixinEscapeComment(m));
404
+ }
405
+ }
406
+
407
+ // update index file for models and controllers
408
+ async _updateIndex(dir) {
409
+ const update = async files => {
410
+ const targetDir = this.destinationPath(`src/${dir}`);
411
+ for (const f of files) {
412
+ // Check all files being generated to ensure they succeeded
413
+ const status = this.conflicter.generationStatus[f];
414
+ if (status !== 'skip' && status !== 'identical') {
415
+ await updateIndex(targetDir, f, this.fs);
416
+ }
417
+ }
418
+ };
419
+ let files = undefined;
420
+ switch (dir) {
421
+ case MODEL:
422
+ files = this.modelSpecs.map(m => m.fileName);
423
+ break;
424
+ case CONTROLLER:
425
+ files = this.selectedControllers.map(m => m.fileName);
426
+ break;
427
+ case SERVICE:
428
+ files = this.selectedServices.map(m => m.serviceFileName);
429
+ break;
430
+ case DATASOURCE:
431
+ files = this.dataSources.map(m => m.fileName);
432
+ break;
433
+ }
434
+
435
+ if (files != null) {
436
+ await update(files);
437
+ }
438
+ }
439
+
440
+ async promptNewDataSourceName() {
441
+ if (this.shouldExit()) return false;
442
+ if (this.options.client !== true) return;
443
+ // skip if the data source already exists
444
+ if (this.dataSourceInfo && this.dataSourceInfo.name) return;
445
+
446
+ this.dataSourceInfo = {
447
+ name: this.options.datasource || 'openapi',
448
+ usePositionalParams: this.options.positional !== false,
449
+ };
450
+
451
+ debug('Prompting for artifact name');
452
+ const prompts = [
453
+ {
454
+ type: 'input',
455
+ name: 'dataSourceName',
456
+ // capitalization
457
+ message: g.f('DataSource name:'),
458
+ default: 'openapi',
459
+ validate: utils.validateClassName,
460
+ when: !this.options.datasource,
461
+ },
462
+ ];
463
+
464
+ const answers = await this.prompt(prompts);
465
+ if (answers != null && answers.dataSourceName) {
466
+ this.dataSourceInfo.name = answers.dataSourceName;
467
+ }
468
+
469
+ // Setting up data for templates
470
+ this.dataSourceInfo.className =
471
+ utils.toClassName(this.dataSourceInfo.name) + 'DataSource';
472
+ this.dataSourceInfo.fileName = utils.toFileName(this.dataSourceInfo.name);
473
+ this.dataSourceInfo.outFile = `${this.dataSourceInfo.fileName}.datasource.ts`;
474
+ }
475
+
476
+ async scaffold() {
477
+ if (this.shouldExit()) return false;
478
+ this._generateModels();
479
+ await this._updateIndex(MODEL);
480
+ if (this.options.server !== false) {
481
+ this._generateControllers();
482
+ await this._updateIndex(CONTROLLER);
483
+ }
484
+ if (this.options.client === true) {
485
+ if (this.dataSourceInfo.outFile) {
486
+ await this._generateDataSource();
487
+ await this._updateIndex(DATASOURCE);
488
+ }
489
+ this._generateServiceProxies();
490
+ await this._updateIndex(SERVICE);
491
+ }
492
+ }
493
+
494
+ install() {
495
+ if (this.shouldExit()) return false;
496
+ debug('install npm dependencies');
497
+ const pkgJson = this.packageJson || {};
498
+ const deps = pkgJson.dependencies || {};
499
+ const pkgs = [];
500
+ const connectorVersionRange = deps['loopback-connector-openapi'];
501
+ if (!connectorVersionRange) {
502
+ // No dependency found for loopback-connector-openapi
503
+ pkgs.push('loopback-connector-openapi');
504
+ } else {
505
+ // `loopback-connector-openapi` exists - make sure its version range
506
+ // is >= 6.0.0
507
+ try {
508
+ const minVersion = semver.minVersion(connectorVersionRange);
509
+ if (semver.lt(minVersion, '6.0.0')) {
510
+ pkgs.push('loopback-connector-openapi@^6.0.0');
511
+ }
512
+ } catch (err) {
513
+ // The version can be a tarball
514
+ this.log(err);
515
+ }
516
+ }
517
+
518
+ if (pkgs.length === 0) return;
519
+
520
+ this.pkgManagerInstall(pkgs, {
521
+ npm: {
522
+ save: true,
523
+ },
524
+ });
525
+ }
526
+
527
+ async end() {
528
+ await super.end();
529
+ if (this.shouldExit()) return;
530
+ }
531
+ };
532
+
533
+ function mixinEscapeComment(context) {
534
+ return Object.assign(context, {escapeComment});
535
+ }