@loopback/cli 4.0.0-alpha.9 → 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 +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 +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 +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,62 @@
1
+ import {
2
+ /* inject, */
3
+ <% if (isGlobal) { -%>
4
+ globalInterceptor,
5
+ <% } else { -%>
6
+ injectable,
7
+ <% } -%>
8
+ Interceptor,
9
+ InvocationContext,
10
+ InvocationResult,
11
+ Provider,
12
+ ValueOrPromise,
13
+ } from '@loopback/core';
14
+
15
+ /**
16
+ * This class will be bound to the application as an `Interceptor` during
17
+ * `boot`
18
+ */
19
+ <% if (isGlobal) { -%>
20
+ @globalInterceptor('<%= group %>', {tags: {name: '<%= name %>'}})
21
+ <% } else { -%>
22
+ @injectable({tags: {key: <%= className %>.BINDING_KEY}})
23
+ <% } -%>
24
+ export class <%= className %> implements Provider<Interceptor> {
25
+ <% if (!isGlobal) { -%>
26
+ static readonly BINDING_KEY = `interceptors.${<%= className %>.name}`;
27
+
28
+ <% } -%>
29
+ /*
30
+ constructor() {}
31
+ */
32
+
33
+ /**
34
+ * This method is used by LoopBack context to produce an interceptor function
35
+ * for the binding.
36
+ *
37
+ * @returns An interceptor function
38
+ */
39
+ value() {
40
+ return this.intercept.bind(this);
41
+ }
42
+
43
+ /**
44
+ * The logic to intercept an invocation
45
+ * @param invocationCtx - Invocation context
46
+ * @param next - A function to invoke next interceptor or the target method
47
+ */
48
+ async intercept(
49
+ invocationCtx: InvocationContext,
50
+ next: () => ValueOrPromise<InvocationResult>,
51
+ ) {
52
+ try {
53
+ // Add pre-invocation logic here
54
+ const result = await next();
55
+ // Add post-invocation logic here
56
+ return result;
57
+ } catch (err) {
58
+ // Add error handling logic here
59
+ throw err;
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,536 @@
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
+ // no translation: Model
7
+ 'use strict';
8
+
9
+ const modelDiscoverer = require('../../lib/model-discoverer');
10
+
11
+ const ArtifactGenerator = require('../../lib/artifact-generator');
12
+ const debug = require('../../lib/debug')('model-generator');
13
+ const inspect = require('util').inspect;
14
+ const utils = require('../../lib/utils');
15
+ const chalk = require('chalk');
16
+ const path = require('path');
17
+ const g = require('../../lib/globalize');
18
+
19
+ const {
20
+ createPropertyTemplateData,
21
+ findBuiltinType,
22
+ } = require('./property-definition');
23
+
24
+ const PROMPT_BASE_MODEL_CLASS = g.f('Please select the model base class');
25
+ const ERROR_NO_MODELS_FOUND = g.f('Model was not found in');
26
+
27
+ const BASE_MODELS = ['Entity', 'Model'];
28
+ const CLI_BASE_MODELS = [
29
+ {
30
+ name: `Entity ${chalk.gray('(A persisted model with an ID)')}`,
31
+ value: 'Entity',
32
+ },
33
+ {name: `Model ${chalk.gray('(A business domain object)')}`, value: 'Model'},
34
+ {type: 'separator', line: '----- Custom Models -----'},
35
+ ];
36
+ const MODEL_TEMPLATE_PATH = 'model.ts.ejs';
37
+
38
+ /**
39
+ * Model Generator
40
+ *
41
+ * Prompts for a Model name and model properties and creates the model class.
42
+ * Currently properties can only be added once to each model using the CLI (at
43
+ * creation).
44
+ *
45
+ * Will prompt for properties to add to the Model till a blank property name is
46
+ * entered. Will also ask if a property is required, the default value for the
47
+ * property, if it's the ID (unless one has been selected), etc.
48
+ */
49
+ module.exports = class ModelGenerator extends ArtifactGenerator {
50
+ constructor(args, opts) {
51
+ super(args, opts);
52
+ }
53
+
54
+ _setupGenerator() {
55
+ this.artifactInfo = {
56
+ type: 'model',
57
+ rootDir: utils.sourceRootDir,
58
+ };
59
+
60
+ this.artifactInfo.outDir = path.resolve(
61
+ this.artifactInfo.rootDir,
62
+ utils.modelsDir,
63
+ );
64
+
65
+ // Model Property Types
66
+ this.typeChoices = [
67
+ 'string',
68
+ 'number',
69
+ 'boolean',
70
+ 'object',
71
+ 'array',
72
+ 'date',
73
+ 'buffer',
74
+ 'geopoint',
75
+ 'any',
76
+ ];
77
+
78
+ this.artifactInfo.properties = {};
79
+ this.artifactInfo.modelSettings = {};
80
+
81
+ this.artifactInfo.modelDir = path.resolve(
82
+ this.artifactInfo.rootDir,
83
+ utils.modelsDir,
84
+ );
85
+
86
+ this.option('base', {
87
+ type: String,
88
+ required: false,
89
+ description: g.f('A valid based model'),
90
+ });
91
+
92
+ // The base class can be specified:
93
+ // 1. From the prompt
94
+ // 2. using the --base flag
95
+ // 3. in the json when using the --config flag
96
+ // This flag is to indicate whether the base class has been validated.
97
+ this.isBaseClassChecked = false;
98
+
99
+ this.option('dataSource', {
100
+ type: String,
101
+ required: false,
102
+ description: g.f(
103
+ 'The name of the dataSource which contains this model and suppots model discovery',
104
+ ),
105
+ });
106
+
107
+ this.option('table', {
108
+ type: String,
109
+ required: false,
110
+ description: g.f(
111
+ 'If discovering a model from a dataSource, specify the name of its table/view',
112
+ ),
113
+ });
114
+
115
+ this.option('schema', {
116
+ type: String,
117
+ required: false,
118
+ description: g.f(
119
+ 'If discovering a model from a dataSource, specify the schema which contains it',
120
+ ),
121
+ });
122
+
123
+ return super._setupGenerator();
124
+ }
125
+
126
+ setOptions() {
127
+ return super.setOptions();
128
+ }
129
+
130
+ checkLoopBackProject() {
131
+ if (this.shouldExit()) return;
132
+ return super.checkLoopBackProject();
133
+ }
134
+
135
+ async getDataSource() {
136
+ if (!this.options.dataSource) {
137
+ debug('Not loading any dataSources because none specified');
138
+ return;
139
+ }
140
+
141
+ this.artifactInfo.dataSource = modelDiscoverer.loadDataSourceByName(
142
+ this.options.dataSource,
143
+ );
144
+
145
+ if (!this.artifactInfo.dataSource) {
146
+ const s = `Could not find dataSource ${this.options.dataSource}`;
147
+ debug(s);
148
+ return this.exit(
149
+ new Error(
150
+ `${s}.${chalk.yellow(
151
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
152
+ )}`,
153
+ ),
154
+ );
155
+ }
156
+ }
157
+
158
+ // Use the dataSource to discover model properties
159
+ async discoverModelPropertiesWithDatasource() {
160
+ if (this.shouldExit()) return false;
161
+ if (!this.options.dataSource) return;
162
+ if (!this.artifactInfo.dataSource) return;
163
+
164
+ const schemaDef = await modelDiscoverer.discoverSingleModel(
165
+ this.artifactInfo.dataSource,
166
+ this.options.table,
167
+ {
168
+ schema: this.options.schema,
169
+ views: true,
170
+ },
171
+ );
172
+ await this.artifactInfo.dataSource.disconnect();
173
+
174
+ if (!schemaDef) {
175
+ this.exit(
176
+ new Error(
177
+ `Could not locate table: ${this.options.table} in schema: ${
178
+ this.options.schema
179
+ }
180
+ ${chalk.yellow(
181
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
182
+ )}`,
183
+ ),
184
+ );
185
+ }
186
+
187
+ Object.assign(this.artifactInfo, schemaDef);
188
+ this.artifactInfo.defaultName = this.artifactInfo.name;
189
+ delete this.artifactInfo.name;
190
+ }
191
+
192
+ // Prompt a user for Model Name
193
+ async promptArtifactName() {
194
+ if (this.shouldExit()) return;
195
+ await super.promptArtifactName();
196
+ this.artifactInfo.className = utils.toClassName(this.artifactInfo.name);
197
+ // Prompt warning msg for the name
198
+ super.promptWarningMsgForName();
199
+ }
200
+
201
+ // Ask for Model base class
202
+ async promptModelBaseClassName() {
203
+ if (this.shouldExit()) return;
204
+ const availableModelBaseClasses = [];
205
+
206
+ availableModelBaseClasses.push(...CLI_BASE_MODELS);
207
+
208
+ try {
209
+ debug(`model list dir ${this.artifactInfo.modelDir}`);
210
+ const modelList = await utils.getArtifactList(
211
+ this.artifactInfo.modelDir,
212
+ 'model',
213
+ );
214
+ debug(`modelist ${modelList}`);
215
+
216
+ if (modelList && modelList.length > 0) {
217
+ availableModelBaseClasses.push(...modelList);
218
+ debug(`availableModelBaseClasses ${availableModelBaseClasses}`);
219
+ }
220
+ } catch (err) {
221
+ debug(`error ${err}`);
222
+ return this.exit(err);
223
+ }
224
+
225
+ if (this.options.base) {
226
+ this.isBaseClassChecked = true;
227
+ if (
228
+ this.isValidBaseClass(
229
+ availableModelBaseClasses,
230
+ this.options.base,
231
+ true,
232
+ )
233
+ ) {
234
+ this.artifactInfo.modelBaseClass = utils.toClassName(this.options.base);
235
+ } else {
236
+ return this.exit(
237
+ new Error(
238
+ `${ERROR_NO_MODELS_FOUND} ${
239
+ this.artifactInfo.modelDir
240
+ }.${chalk.yellow(
241
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
242
+ )}`,
243
+ ),
244
+ );
245
+ }
246
+ }
247
+
248
+ return this.prompt([
249
+ {
250
+ type: 'list',
251
+ name: 'modelBaseClass',
252
+ message: PROMPT_BASE_MODEL_CLASS,
253
+ choices: availableModelBaseClasses,
254
+ when: !this.artifactInfo.modelBaseClass,
255
+ default: availableModelBaseClasses[0],
256
+ validate: utils.validateClassName,
257
+ },
258
+ ])
259
+ .then(props => {
260
+ if (this.isBaseClassChecked) return;
261
+ if (typeof props.modelBaseClass === 'object')
262
+ props.modelBaseClass = props.modelBaseClass.value;
263
+ // Find whether the specified base class is one of the available base
264
+ // class list
265
+ const isValidBase = this.isValidBaseClass(
266
+ availableModelBaseClasses,
267
+ props.modelBaseClass,
268
+ false,
269
+ );
270
+ if (!props.modelBaseClass && !isValidBase) {
271
+ this.exit(
272
+ new Error(
273
+ `${ERROR_NO_MODELS_FOUND} ${
274
+ this.artifactInfo.modelDir
275
+ }.${chalk.yellow(
276
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
277
+ )}`,
278
+ ),
279
+ );
280
+ }
281
+
282
+ Object.assign(this.artifactInfo, props);
283
+ debug(`props after model base class prompt: ${inspect(props)}`);
284
+ return props;
285
+ })
286
+ .catch(err => {
287
+ debug(`Error during model base class prompt: ${err}`);
288
+ return this.exit(err);
289
+ });
290
+ }
291
+
292
+ async promptStrictMode() {
293
+ if (this.shouldExit()) return false;
294
+ return this.prompt([
295
+ {
296
+ name: 'allowAdditionalProperties',
297
+ message: g.f('Allow additional (free-form) properties?'),
298
+ type: 'confirm',
299
+ default: false,
300
+ when: !this.artifactInfo.allowAdditionalProperties,
301
+ },
302
+ ])
303
+ .then(setting => {
304
+ Object.assign(this.artifactInfo, setting);
305
+
306
+ if (this.artifactInfo.allowAdditionalProperties) {
307
+ Object.assign(this.artifactInfo.modelSettings, {strict: false});
308
+ }
309
+ // inform user what model/file names will be created
310
+ super.promptClassFileName(
311
+ 'model',
312
+ 'models',
313
+ this.artifactInfo.className,
314
+ );
315
+
316
+ this.log(
317
+ g.f(
318
+ "Let's add a property to %s",
319
+ `${chalk.yellow(this.artifactInfo.className)}`,
320
+ ),
321
+ );
322
+ })
323
+ .catch(err => {
324
+ debug(`Error during model strict mode prompt: ${err}`);
325
+ return this.exit(err);
326
+ });
327
+ }
328
+
329
+ // Check whether the base class name is a valid one.
330
+ // It is either one of the predefined base classes,
331
+ // or an existing user defined class
332
+ // @isClassNameNullable - true if it is valid to have classname as null
333
+ isValidBaseClass(availableModelBaseClasses, classname, isClassNameNullable) {
334
+ if (!classname && !isClassNameNullable) return false;
335
+
336
+ for (const i in availableModelBaseClasses) {
337
+ let baseClass = '';
338
+ if (typeof availableModelBaseClasses[i] == 'object')
339
+ baseClass = availableModelBaseClasses[i].value;
340
+ else baseClass = availableModelBaseClasses[i];
341
+
342
+ if (classname === baseClass) {
343
+ return true;
344
+ }
345
+ }
346
+ return false;
347
+ }
348
+
349
+ // Prompt for a Property Name
350
+ async promptPropertyName() {
351
+ if (this.shouldExit()) return false;
352
+
353
+ // If properties are provided from config file
354
+ if (this.options.properties) {
355
+ Object.assign(this.artifactInfo.properties, this.options.properties);
356
+ return;
357
+ }
358
+
359
+ this.log(g.f('Enter an empty property name when done'));
360
+ this.log();
361
+
362
+ // This function can be called repeatedly so this deletes the previous
363
+ // property name if one was set.
364
+ delete this.propName;
365
+
366
+ const prompts = [
367
+ {
368
+ name: 'propName',
369
+ message: g.f('Enter the property name:'),
370
+ validate: function (val) {
371
+ if (val) {
372
+ return utils.checkPropertyName(val);
373
+ } else {
374
+ return true;
375
+ }
376
+ },
377
+ },
378
+ ];
379
+
380
+ const answers = await this.prompt(prompts);
381
+ // debug(`propName => ${JSON.stringify(answers)}`);
382
+ if (answers.propName) {
383
+ this.artifactInfo.properties[answers.propName] = {};
384
+ this.propName = answers.propName;
385
+ }
386
+ return this._promptPropertyInfo();
387
+ }
388
+
389
+ // Internal Method. Called when a new property is entered.
390
+ // Prompts the user for more information about the property to be added.
391
+ async _promptPropertyInfo() {
392
+ if (!this.propName) return true;
393
+
394
+ const prompts = [
395
+ {
396
+ name: 'type',
397
+ message: g.f('Property type:'),
398
+ type: 'list',
399
+ choices: this.typeChoices,
400
+ },
401
+ {
402
+ name: 'itemType',
403
+ message: g.f('Type of array items:'),
404
+ type: 'list',
405
+ choices: this.typeChoices.filter(choice => {
406
+ return choice !== 'array';
407
+ }),
408
+ when: answers => {
409
+ return answers.type === 'array';
410
+ },
411
+ },
412
+ {
413
+ name: 'id',
414
+ message: g.f(
415
+ 'Is %s the ID property?',
416
+ `${chalk.yellow(this.propName)}`,
417
+ ),
418
+ type: 'confirm',
419
+ default: false,
420
+ when: answers => {
421
+ return (
422
+ !this.idFieldSet &&
423
+ !['array', 'object', 'buffer'].includes(answers.type)
424
+ );
425
+ },
426
+ },
427
+ {
428
+ name: 'generated',
429
+ message: g.f(
430
+ 'Is %s generated automatically?',
431
+ `${chalk.yellow(this.propName)}`,
432
+ ),
433
+ type: 'confirm',
434
+ default: true,
435
+ when: answers => answers.id,
436
+ },
437
+ {
438
+ name: 'required',
439
+ message: g.f('Is it required?:'),
440
+ type: 'confirm',
441
+ default: false,
442
+ when: answers => !answers.generated,
443
+ },
444
+ {
445
+ name: 'default',
446
+ message: g.f(
447
+ 'Default value %s:',
448
+ `${chalk.yellow(g.f('[leave blank for none]'))}`,
449
+ ),
450
+ when: answers => {
451
+ return (
452
+ ![null, 'buffer', 'any'].includes(answers.type) &&
453
+ !answers.generated &&
454
+ answers.required !== true
455
+ );
456
+ },
457
+ },
458
+ ];
459
+
460
+ const answers = await this.prompt(prompts);
461
+ debug(`propertyInfo => ${JSON.stringify(answers)}`);
462
+
463
+ // Yeoman sets the default to `''` so we remove it unless the user entered
464
+ // a different value
465
+ if (answers.default === '') {
466
+ delete answers.default;
467
+ }
468
+
469
+ Object.assign(this.artifactInfo.properties[this.propName], answers);
470
+
471
+ // We prompt for `id` only once per model using idFieldSet flag.
472
+ // and 'generated' flag makes sure id is defined, especially for database like MySQL
473
+ // Skipped the test for `generated` for now.
474
+ if (answers.id) {
475
+ this.idFieldSet = true;
476
+ }
477
+
478
+ this.log();
479
+ this.log(
480
+ g.f(
481
+ "Let's add another property to %s",
482
+ `${chalk.yellow(this.artifactInfo.className)}`,
483
+ ),
484
+ );
485
+ return this.promptPropertyName();
486
+ }
487
+
488
+ scaffold() {
489
+ if (this.shouldExit()) return false;
490
+
491
+ debug('scaffolding');
492
+
493
+ Object.entries(this.artifactInfo.properties).forEach(([k, v]) => {
494
+ const builtinType = findBuiltinType(v.type);
495
+ if (builtinType) v.type = builtinType;
496
+ modelDiscoverer.sanitizeProperty(v);
497
+ });
498
+
499
+ // Data for templates
500
+ this.artifactInfo.outFile = utils.getModelFileName(this.artifactInfo.name);
501
+
502
+ // Resolved Output Path
503
+ const tsPath = this.destinationPath(
504
+ this.artifactInfo.outDir,
505
+ this.artifactInfo.outFile,
506
+ );
507
+
508
+ this.artifactInfo.isModelBaseBuiltin = BASE_MODELS.includes(
509
+ this.artifactInfo.modelBaseClass,
510
+ );
511
+
512
+ const propDefs = this.artifactInfo.properties;
513
+ this.artifactInfo.properties = {};
514
+ for (const key in propDefs) {
515
+ this.artifactInfo.properties[key] = createPropertyTemplateData(
516
+ propDefs[key],
517
+ );
518
+ }
519
+
520
+ if (this.artifactInfo.modelSettings) {
521
+ this.artifactInfo.modelSettings = utils.stringifyModelSettings(
522
+ this.artifactInfo.modelSettings,
523
+ );
524
+ }
525
+
526
+ this.copyTemplatedFiles(
527
+ this.templatePath(MODEL_TEMPLATE_PATH),
528
+ tsPath,
529
+ this.artifactInfo,
530
+ );
531
+ }
532
+
533
+ async end() {
534
+ await super.end();
535
+ }
536
+ };
@@ -0,0 +1,85 @@
1
+ // Copyright IBM Corp. 2019. 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 TS_TYPES = ['string', 'number', 'object', 'boolean', 'any'];
9
+ const NON_TS_TYPES = ['geopoint', 'date'];
10
+ const BUILTIN_TYPES = [...TS_TYPES, ...NON_TS_TYPES];
11
+
12
+ module.exports = {
13
+ createPropertyTemplateData,
14
+ findBuiltinType,
15
+ BUILTIN_TYPES,
16
+ };
17
+
18
+ /**
19
+ * Convert property definition in LB4 style to data needed by model template
20
+ * @param {object} val The property definition
21
+ * @returns {object} Data for model-property template
22
+ */
23
+ function createPropertyTemplateData(val) {
24
+ // shallow clone the object - don't modify original data!
25
+ val = {...val};
26
+
27
+ // Default tsType is the type property
28
+ val.tsType = val.type;
29
+
30
+ // Override tsType based on certain type values
31
+ if (val.type === 'array') {
32
+ if (TS_TYPES.includes(val.itemType)) {
33
+ val.tsType = `${val.itemType}[]`;
34
+ } else if (val.type === 'buffer') {
35
+ val.tsType = 'Buffer[]';
36
+ } else {
37
+ val.tsType = 'string[]';
38
+ }
39
+ } else if (val.type === 'buffer') {
40
+ val.tsType = 'Buffer';
41
+ }
42
+
43
+ if (NON_TS_TYPES.includes(val.tsType)) {
44
+ val.tsType = 'string';
45
+ }
46
+
47
+ if (
48
+ val.defaultValue &&
49
+ NON_TS_TYPES.concat(['string', 'any']).includes(val.type)
50
+ ) {
51
+ val.defaultValue = `'${val.defaultValue}'`;
52
+ }
53
+
54
+ // Convert Type to include '' for template
55
+ val.type = `'${val.type}'`;
56
+ if (val.itemType) {
57
+ val.itemType = `'${val.itemType}'`;
58
+ }
59
+
60
+ // If required is false, we can delete it as that's the default assumption
61
+ // for this field if not present. This helps to avoid polluting the
62
+ // decorator with redundant properties.
63
+ if (!val.required) {
64
+ delete val.required;
65
+ }
66
+
67
+ // We only care about marking the `id` field as `id` and not fields that
68
+ // are not the id so if this is false we delete it similar to `required`.
69
+ if (!val.id) {
70
+ delete val.id;
71
+ }
72
+
73
+ return val;
74
+ }
75
+
76
+ /**
77
+ * Check if the type is a built-in type, return the canonical type name in
78
+ * such case (e.g. convert 'String' to 'string').
79
+ *
80
+ * @param {string} typeName Property type name, e.g. 'String' or 'Address'
81
+ * @returns {string|undefined} Built-in type name (e.g. 'string') or undefined
82
+ */
83
+ function findBuiltinType(typeName) {
84
+ return BUILTIN_TYPES.find(t => t === typeName.toLowerCase());
85
+ }