@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,576 @@
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
+ // no translate: Repository
7
+ 'use strict';
8
+ const _ = require('lodash');
9
+ const ArtifactGenerator = require('../../lib/artifact-generator');
10
+ const fs = require('fs');
11
+ const debug = require('../../lib/debug')('repository-generator');
12
+ const inspect = require('util').inspect;
13
+ const path = require('path');
14
+ const chalk = require('chalk');
15
+ const utils = require('../../lib/utils');
16
+ const tsquery = require('../../lib/ast-helper');
17
+ const g = require('../../lib/globalize');
18
+
19
+ const VALID_CONNECTORS_FOR_REPOSITORY = ['KeyValueModel', 'PersistedModel'];
20
+ const KEY_VALUE_CONNECTOR = ['KeyValueModel'];
21
+
22
+ const DEFAULT_CRUD_REPOSITORY = 'DefaultCrudRepository';
23
+ const KEY_VALUE_REPOSITORY = 'DefaultKeyValueRepository';
24
+ const BASE_REPOSITORIES = [DEFAULT_CRUD_REPOSITORY, KEY_VALUE_REPOSITORY];
25
+ const CLI_BASE_CRUD_REPOSITORIES = [
26
+ {
27
+ name: `${DEFAULT_CRUD_REPOSITORY} ${chalk.gray('(Juggler bridge)')}`,
28
+ value: DEFAULT_CRUD_REPOSITORY,
29
+ },
30
+ ];
31
+ const CLI_BASE_KEY_VALUE_REPOSITORIES = [
32
+ {
33
+ name: `${KEY_VALUE_REPOSITORY} ${chalk.gray(
34
+ '(For access to a key-value store)',
35
+ )}`,
36
+ value: KEY_VALUE_REPOSITORY,
37
+ },
38
+ ];
39
+ const CLI_BASE_SEPARATOR = [
40
+ {
41
+ type: 'separator',
42
+ line: g.f('----- Custom Repositories -----'),
43
+ },
44
+ ];
45
+
46
+ const REPOSITORY_KV_TEMPLATE = 'repository-kv-template.ts.ejs';
47
+ const REPOSITORY_CRUD_TEMPLATE = 'repository-crud-default-template.ts.ejs';
48
+
49
+ const PROMPT_MESSAGE_MODEL = g.f(
50
+ 'Select the model(s) you want to generate a repository for',
51
+ );
52
+ const PROMPT_MESSAGE_DATA_SOURCE = g.f('Select the datasource');
53
+ const PROMPT_BASE_REPOSITORY_CLASS = g.f('Select the repository base class');
54
+ const ERROR_READING_FILE = g.f('Error reading file');
55
+ const ERROR_NO_DATA_SOURCES_FOUND = g.f('No datasources found at');
56
+ const ERROR_NO_MODELS_FOUND = g.f('No models found at');
57
+ const ERROR_NO_MODEL_SELECTED = g.f('You did not select a valid model');
58
+
59
+ module.exports = class RepositoryGenerator extends ArtifactGenerator {
60
+ // Note: arguments and options should be defined in the constructor.
61
+ constructor(args, opts) {
62
+ super(args, opts);
63
+ }
64
+
65
+ /**
66
+ * Find all the base artifacts in the given path whose type matches the
67
+ * provided artifactType.
68
+ * For example, a artifactType of "repository" will search the target path for
69
+ * matches to "*.repository.base.ts"
70
+ * @param {string} dir The target directory from which to load artifacts.
71
+ * @param {string} artifactType The artifact type (ex. "model", "repository")
72
+ */
73
+ async _findBaseClasses(dir, artifactType) {
74
+ const paths = await utils.findArtifactPaths(dir, artifactType + '.base');
75
+ debug(`repository artifact paths: ${paths}`);
76
+
77
+ // get base class and path
78
+ const baseRepositoryList = [];
79
+ for (const p of paths) {
80
+ //get name removing anything from .artifactType.base
81
+ const artifactFile = path.parse(_.last(_.split(p, path.sep))).name;
82
+ const firstWord = _.first(_.split(artifactFile, '.'));
83
+ const artifactName =
84
+ utils.toClassName(firstWord) + utils.toClassName(artifactType);
85
+
86
+ const baseRepository = {name: artifactName, file: artifactFile};
87
+ baseRepositoryList.push(baseRepository);
88
+ }
89
+
90
+ debug(`repository base classes: ${inspect(baseRepositoryList)}`);
91
+ return baseRepositoryList;
92
+ }
93
+
94
+ /**
95
+ * get the property name for the id field
96
+ * @param {string} modelName
97
+ */
98
+ async _getModelIdProperty(modelName) {
99
+ let fileContent = '';
100
+ const modelFile = path.join(
101
+ this.artifactInfo.modelDir,
102
+ utils.getModelFileName(modelName),
103
+ );
104
+ try {
105
+ fileContent = this.fs.read(modelFile, {});
106
+ } catch (err) {
107
+ debug(`${ERROR_READING_FILE} ${modelFile}: ${err.message}`);
108
+ return this.exit(err);
109
+ }
110
+
111
+ return tsquery.getIdFromModel(fileContent);
112
+ }
113
+
114
+ /**
115
+ * helper method to inspect and validate a repository type
116
+ */
117
+ async _inferRepositoryType() {
118
+ if (!this.artifactInfo.dataSourceClass) {
119
+ return;
120
+ }
121
+ const result = utils.isConnectorOfType(
122
+ KEY_VALUE_CONNECTOR,
123
+ this.artifactInfo.datasourcesDir,
124
+ this.artifactInfo.dataSourceClass,
125
+ );
126
+ debug(`KeyValue Connector: ${result}`);
127
+
128
+ if (result) {
129
+ this.artifactInfo.repositoryTypeClass = KEY_VALUE_REPOSITORY;
130
+ this.artifactInfo.defaultTemplate = REPOSITORY_KV_TEMPLATE;
131
+ } else {
132
+ this.artifactInfo.repositoryTypeClass = DEFAULT_CRUD_REPOSITORY;
133
+ this.artifactInfo.defaultTemplate = REPOSITORY_CRUD_TEMPLATE;
134
+ }
135
+
136
+ this.artifactInfo.dataSourceName = utils.getDataSourceName(
137
+ this.artifactInfo.datasourcesDir,
138
+ this.artifactInfo.dataSourceClass,
139
+ );
140
+
141
+ this.artifactInfo.dataSourceClassName =
142
+ utils.toClassName(this.artifactInfo.dataSourceName) + 'DataSource';
143
+ }
144
+
145
+ _setupGenerator() {
146
+ this.artifactInfo = {
147
+ type: 'repository ',
148
+ rootDir: utils.sourceRootDir,
149
+ };
150
+
151
+ this.artifactInfo.outDir = path.resolve(
152
+ this.artifactInfo.rootDir,
153
+ utils.repositoriesDir,
154
+ );
155
+ this.artifactInfo.datasourcesDir = path.resolve(
156
+ this.artifactInfo.rootDir,
157
+ utils.datasourcesDir,
158
+ );
159
+ this.artifactInfo.modelDir = path.resolve(
160
+ this.artifactInfo.rootDir,
161
+ utils.modelsDir,
162
+ );
163
+
164
+ // to be able to write multiple files to the index.ts
165
+ this.artifactInfo.indexesToBeUpdated = [];
166
+
167
+ this.artifactInfo.defaultTemplate = REPOSITORY_CRUD_TEMPLATE;
168
+
169
+ this.option('model', {
170
+ type: String,
171
+ required: false,
172
+ description: g.f('A valid model name'),
173
+ });
174
+
175
+ this.option('id', {
176
+ type: String,
177
+ required: false,
178
+ description: g.f('A valid ID property name for the specified model'),
179
+ });
180
+
181
+ this.option('datasource', {
182
+ type: String,
183
+ required: false,
184
+ description: g.f('A valid datasource name'),
185
+ });
186
+
187
+ this.option('repositoryBaseClass', {
188
+ type: String,
189
+ required: false,
190
+ description: g.f('A valid repository base class'),
191
+ default: 'DefaultCrudRepository',
192
+ });
193
+
194
+ return super._setupGenerator();
195
+ }
196
+
197
+ setOptions() {
198
+ return super.setOptions();
199
+ }
200
+
201
+ checkLoopBackProject() {
202
+ if (this.shouldExit()) return;
203
+ return super.checkLoopBackProject();
204
+ }
205
+
206
+ async checkPaths() {
207
+ if (this.shouldExit()) return;
208
+ // check for datasources
209
+ if (!fs.existsSync(this.artifactInfo.datasourcesDir)) {
210
+ return this.exit(
211
+ new Error(
212
+ `${ERROR_NO_DATA_SOURCES_FOUND} ${this.artifactInfo.datasourcesDir}.
213
+ ${chalk.yellow(
214
+ 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered',
215
+ )}`,
216
+ ),
217
+ );
218
+ }
219
+
220
+ // check for models
221
+ if (!fs.existsSync(this.artifactInfo.modelDir)) {
222
+ return this.exit(
223
+ new Error(
224
+ `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}.
225
+ ${chalk.yellow(
226
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
227
+ )}`,
228
+ ),
229
+ );
230
+ }
231
+ }
232
+
233
+ async promptDataSourceName() {
234
+ if (this.shouldExit()) return false;
235
+
236
+ debug('Prompting for a datasource ');
237
+ let datasourcesList;
238
+
239
+ // grab the datasourcename from the command line
240
+ const cmdDatasourceName = this.options.datasource
241
+ ? utils.toClassName(this.options.datasource) + 'Datasource'
242
+ : '';
243
+
244
+ debug('command line datasource is %j', cmdDatasourceName);
245
+
246
+ try {
247
+ datasourcesList = await utils.getArtifactList(
248
+ this.artifactInfo.datasourcesDir,
249
+ 'datasource',
250
+ true,
251
+ );
252
+ debug(
253
+ 'datasourcesList from %s/%s:',
254
+ utils.sourceRootDir,
255
+ utils.datasourcesDir,
256
+ datasourcesList,
257
+ );
258
+ } catch (err) {
259
+ return this.exit(err);
260
+ }
261
+
262
+ const availableDatasources = datasourcesList.filter(item => {
263
+ const result = utils.isConnectorOfType(
264
+ VALID_CONNECTORS_FOR_REPOSITORY,
265
+ this.artifactInfo.datasourcesDir,
266
+ item,
267
+ );
268
+ debug(
269
+ 'has %s connector of type %o? %s',
270
+ item,
271
+ VALID_CONNECTORS_FOR_REPOSITORY,
272
+ result,
273
+ );
274
+ return result !== false;
275
+ });
276
+
277
+ debug(`artifactInfo.dataSourceClass ${this.artifactInfo.dataSourceClass}`);
278
+
279
+ if (availableDatasources.length === 0) {
280
+ return this.exit(
281
+ new Error(
282
+ `${ERROR_NO_DATA_SOURCES_FOUND} ${this.artifactInfo.datasourcesDir}.
283
+ ${chalk.yellow(
284
+ 'Please visit https://loopback.io/doc/en/lb4/DataSource-generator.html for information on how datasources are discovered',
285
+ )}`,
286
+ ),
287
+ );
288
+ }
289
+
290
+ if (availableDatasources.includes(cmdDatasourceName)) {
291
+ Object.assign(this.artifactInfo, {
292
+ dataSourceClass: cmdDatasourceName,
293
+ });
294
+ }
295
+
296
+ return this.prompt([
297
+ {
298
+ type: 'list',
299
+ name: 'dataSourceClass',
300
+ message: PROMPT_MESSAGE_DATA_SOURCE,
301
+ choices: availableDatasources,
302
+ when: !this.artifactInfo.dataSourceClass,
303
+ default: availableDatasources[0],
304
+ validate: utils.validateClassName,
305
+ },
306
+ ])
307
+ .then(props => {
308
+ Object.assign(this.artifactInfo, props);
309
+ debug(`props after datasource prompt: ${inspect(props)}`);
310
+ return props;
311
+ })
312
+ .catch(err => {
313
+ debug(`Error during datasource prompt: ${err}`);
314
+ return this.exit(err);
315
+ });
316
+ }
317
+
318
+ async promptModels() {
319
+ if (this.shouldExit()) return false;
320
+
321
+ await this._inferRepositoryType();
322
+
323
+ let modelList;
324
+ try {
325
+ debug(`model list dir ${this.artifactInfo.modelDir}`);
326
+ modelList = await utils.getArtifactList(
327
+ this.artifactInfo.modelDir,
328
+ 'model',
329
+ );
330
+ } catch (err) {
331
+ return this.exit(err);
332
+ }
333
+
334
+ if (this.options.model) {
335
+ debug(`Model name received from command line: ${this.options.model}`);
336
+
337
+ this.options.model = utils.toClassName(this.options.model);
338
+ // assign the model name from the command line only if it is valid
339
+ if (
340
+ modelList &&
341
+ modelList.length > 0 &&
342
+ modelList.includes(this.options.model)
343
+ ) {
344
+ Object.assign(this.artifactInfo, {modelNameList: [this.options.model]});
345
+ } else {
346
+ modelList = [];
347
+ }
348
+ }
349
+
350
+ if (modelList.length === 0) {
351
+ return this.exit(
352
+ new Error(
353
+ `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}.
354
+ ${chalk.yellow(
355
+ 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
356
+ )}`,
357
+ ),
358
+ );
359
+ }
360
+
361
+ return this.prompt([
362
+ {
363
+ type: 'checkbox',
364
+ name: 'modelNameList',
365
+ message: PROMPT_MESSAGE_MODEL,
366
+ choices: modelList,
367
+ when: this.artifactInfo.modelNameList === undefined,
368
+ // Require at least one model to be selected
369
+ // This prevents users from accidentally pressing ENTER instead of SPACE
370
+ // to select a model from the list
371
+ validate: result => !!result.length,
372
+ },
373
+ ])
374
+ .then(props => {
375
+ Object.assign(this.artifactInfo, props);
376
+ debug(`props after model list prompt: ${inspect(props)}`);
377
+ return props;
378
+ })
379
+ .catch(err => {
380
+ debug(`Error during model list prompt: ${err}`);
381
+ return this.exit(err);
382
+ });
383
+ }
384
+
385
+ async promptBaseClass() {
386
+ debug('Prompting for repository base');
387
+ if (this.shouldExit()) return;
388
+
389
+ const availableRepositoryList = [];
390
+
391
+ debug(`repositoryTypeClass ${this.artifactInfo.repositoryTypeClass}`);
392
+ // Add base repositories based on datasource type
393
+ if (this.artifactInfo.repositoryTypeClass === KEY_VALUE_REPOSITORY)
394
+ availableRepositoryList.push(...CLI_BASE_KEY_VALUE_REPOSITORIES);
395
+ else availableRepositoryList.push(...CLI_BASE_CRUD_REPOSITORIES);
396
+ availableRepositoryList.push(...CLI_BASE_SEPARATOR);
397
+
398
+ try {
399
+ this.artifactInfo.baseRepositoryList = await this._findBaseClasses(
400
+ this.artifactInfo.outDir,
401
+ 'repository',
402
+ );
403
+ if (
404
+ this.artifactInfo.baseRepositoryList &&
405
+ this.artifactInfo.baseRepositoryList.length > 0
406
+ ) {
407
+ availableRepositoryList.push(...this.artifactInfo.baseRepositoryList);
408
+ debug(`availableRepositoryList ${availableRepositoryList}`);
409
+ }
410
+ } catch (err) {
411
+ return this.exit(err);
412
+ }
413
+
414
+ if (this.options.repositoryBaseClass) {
415
+ debug(
416
+ `Base repository received from command line: ${this.options.repositoryBaseClass}`,
417
+ );
418
+ this.artifactInfo.repositoryBaseClass = this.options.repositoryBaseClass;
419
+ }
420
+
421
+ return this.prompt([
422
+ {
423
+ type: 'list',
424
+ name: 'repositoryBaseClass',
425
+ message: PROMPT_BASE_REPOSITORY_CLASS,
426
+ choices: availableRepositoryList,
427
+ when: this.artifactInfo.repositoryBaseClass === undefined,
428
+ default:
429
+ this.artifactInfo.repositoryBaseClass === undefined
430
+ ? availableRepositoryList[0]
431
+ : this.options.repositoryBaseClass,
432
+ },
433
+ ])
434
+ .then(props => {
435
+ debug(`props after custom repository prompt: ${inspect(props)}`);
436
+ Object.assign(this.artifactInfo, props);
437
+ return props;
438
+ })
439
+ .catch(err => {
440
+ debug(`Error during repository base class prompt: ${err.stack}`);
441
+ return this.exit(err);
442
+ });
443
+ }
444
+
445
+ async promptModelId() {
446
+ if (this.shouldExit()) return false;
447
+ let idProperty;
448
+
449
+ debug(`Model ID property name from command line: ${this.options.id}`);
450
+ debug(`Selected Models: ${this.artifactInfo.modelNameList}`);
451
+
452
+ if (_.isEmpty(this.artifactInfo.modelNameList)) {
453
+ return this.exit(new Error(`${ERROR_NO_MODEL_SELECTED}`));
454
+ } else {
455
+ // iterate thru each selected model, infer or ask for the ID type
456
+ for (const item of this.artifactInfo.modelNameList) {
457
+ this.artifactInfo.modelName = item;
458
+
459
+ const prompts = [
460
+ {
461
+ type: 'input',
462
+ name: 'propertyName',
463
+ message: g.f(
464
+ 'Please enter the name of the ID property for %s:',
465
+ `${item}`,
466
+ ),
467
+ default: 'id',
468
+ },
469
+ ];
470
+
471
+ // user supplied the id from the command line
472
+ if (this.options.id) {
473
+ debug(`passing thru this.options.id with value : ${this.options.id}`);
474
+
475
+ idProperty = this.options.id;
476
+ /** make sure it is only used once, in case user selected more
477
+ * than one model.
478
+ */
479
+ delete this.options.id;
480
+ } else {
481
+ idProperty = await this._getModelIdProperty(item);
482
+ if (idProperty === null) {
483
+ const answer = await this.prompt(prompts);
484
+ idProperty = answer.propertyName;
485
+ }
486
+ }
487
+ this.artifactInfo.idProperty = idProperty;
488
+ // Generate this repository
489
+ await this._scaffold();
490
+ }
491
+ }
492
+ }
493
+
494
+ async _scaffold() {
495
+ if (this.shouldExit()) return false;
496
+
497
+ this.artifactInfo.isRepositoryBaseBuiltin = BASE_REPOSITORIES.includes(
498
+ this.artifactInfo.repositoryBaseClass,
499
+ );
500
+ debug(
501
+ `isRepositoryBaseBuiltin : ${this.artifactInfo.isRepositoryBaseBuiltin}`,
502
+ );
503
+ if (!this.artifactInfo.isRepositoryBaseBuiltin) {
504
+ const baseIndex = _.findIndex(this.artifactInfo.baseRepositoryList, [
505
+ 'name',
506
+ this.artifactInfo.repositoryBaseClass,
507
+ ]);
508
+ this.artifactInfo.repositoryBaseFile =
509
+ this.artifactInfo.baseRepositoryList[baseIndex].file;
510
+ }
511
+
512
+ if (this.options.name) {
513
+ this.artifactInfo.className = utils.toClassName(this.options.name);
514
+ this.artifactInfo.outFile = utils.getRepositoryFileName(
515
+ this.options.name,
516
+ );
517
+
518
+ // make sure the name supplied from cmd line is only used once
519
+ delete this.options.name;
520
+ } else {
521
+ this.artifactInfo.className = utils.toClassName(
522
+ this.artifactInfo.modelName,
523
+ );
524
+
525
+ this.artifactInfo.outFile = utils.getRepositoryFileName(
526
+ this.artifactInfo.modelName,
527
+ );
528
+
529
+ this.artifactInfo.indexesToBeUpdated.push({
530
+ dir: this.artifactInfo.outDir,
531
+ file: this.artifactInfo.outFile,
532
+ });
533
+ }
534
+
535
+ const source = this.templatePath(
536
+ path.join(
537
+ utils.sourceRootDir,
538
+ utils.repositoriesDir,
539
+ this.artifactInfo.defaultTemplate,
540
+ ),
541
+ );
542
+
543
+ const dest = this.destinationPath(
544
+ path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
545
+ );
546
+
547
+ if (debug.enabled) {
548
+ debug(`artifactInfo: ${inspect(this.artifactInfo)}`);
549
+ debug(`Copying artifact to: ${dest}`);
550
+ }
551
+
552
+ this.copyTemplatedFiles(source, dest, this.artifactInfo);
553
+ return;
554
+ }
555
+
556
+ async end() {
557
+ this.artifactInfo.type =
558
+ this.artifactInfo.modelNameList &&
559
+ this.artifactInfo.modelNameList.length > 1
560
+ ? 'Repositories'
561
+ : 'Repository';
562
+
563
+ this.artifactInfo.modelNameList = _.map(
564
+ this.artifactInfo.modelNameList,
565
+ repositoryName => {
566
+ return repositoryName + 'Repository';
567
+ },
568
+ );
569
+
570
+ this.artifactInfo.name = this.artifactInfo.modelNameList
571
+ ? this.artifactInfo.modelNameList.join(this.classNameSeparator)
572
+ : this.artifactInfo.modelName;
573
+
574
+ await super.end();
575
+ }
576
+ };
@@ -0,0 +1,21 @@
1
+ import {inject} from '@loopback/core';
2
+ <%if (isRepositoryBaseBuiltin) { -%>
3
+ import {<%= repositoryTypeClass %>} from '@loopback/repository';
4
+ <% } -%>
5
+ import {<%= dataSourceClassName %>} from '../datasources';
6
+ import {<%= modelName %>, <%= modelName %>Relations} from '../models';
7
+ <%if ( !isRepositoryBaseBuiltin ) { -%>
8
+ import {<%=repositoryBaseClass %>} from './<%=repositoryBaseFile %>';
9
+ <% } -%>
10
+
11
+ export class <%= className %>Repository extends <%= repositoryBaseClass %><
12
+ <%= modelName %>,
13
+ typeof <%= modelName %>.prototype.<%= idProperty %>,
14
+ <%= modelName %>Relations
15
+ > {
16
+ constructor(
17
+ @inject('datasources.<%= dataSourceName %>') dataSource: <%= dataSourceClassName %>,
18
+ ) {
19
+ super(<%= modelName %>, dataSource);
20
+ }
21
+ }
@@ -0,0 +1,19 @@
1
+ import {inject} from '@loopback/core';
2
+ <%if (isRepositoryBaseBuiltin) { -%>
3
+ import {<%= repositoryTypeClass %>, juggler} from '@loopback/repository';
4
+ <% } -%>
5
+ import {<%= dataSourceClassName %>} from '../datasources';
6
+ import {<%= modelName %>} from '../models';
7
+ <%if ( !isRepositoryBaseBuiltin ) { -%>
8
+ import {<%=repositoryBaseClass %>} from './<%=repositoryBaseFile %>';
9
+ <% } -%>
10
+
11
+ export class <%= className %>Repository extends <%= repositoryBaseClass %><
12
+ <%= modelName %>
13
+ > {
14
+ constructor(
15
+ @inject('datasources.<%= dataSourceName %>') dataSource: <%= dataSourceClassName %>,
16
+ ) {
17
+ super(<%= modelName %>, dataSource);
18
+ }
19
+ }
@@ -0,0 +1,63 @@
1
+ // Copyright IBM Corp. and LoopBack contributors 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 ast = require('ts-morph');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Update `src/application.ts`
12
+ * @param projectRoot - Project root directory
13
+ */
14
+ async function updateApplicationTs(projectRoot) {
15
+ const CRUD_REST_COMPONENT = 'CrudRestComponent';
16
+ const applicationTsFile = path.join(projectRoot, 'src/application.ts');
17
+
18
+ // Create an AST project
19
+ const project = new ast.Project({
20
+ manipulationSettings: {
21
+ indentationText: ast.IndentationText.TwoSpaces,
22
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: false,
23
+ newLineKind: ast.NewLineKind.LineFeed,
24
+ quoteKind: ast.QuoteKind.Single,
25
+ },
26
+ });
27
+
28
+ // import {CrudRestComponent} from '@loopback/rest-crud';
29
+ const importDeclaration = {
30
+ kind: ast.StructureKind.ImportDeclaration,
31
+ moduleSpecifier: '@loopback/rest-crud',
32
+ namedImports: [CRUD_REST_COMPONENT],
33
+ };
34
+
35
+ const pFile = project.addSourceFileAtPath(applicationTsFile);
36
+
37
+ // Check if `import {CrudRestComponent} from '@loopback/rest-crud'` exists
38
+ const restCrudImport = pFile.getImportDeclaration('@loopback/rest-crud');
39
+
40
+ if (restCrudImport == null) {
41
+ // Not found
42
+ pFile.addImportDeclaration(importDeclaration);
43
+ } else {
44
+ // Further check named import for CrudRestComponent
45
+ const names = restCrudImport.getNamedImports().map(i => i.getName());
46
+ if (!names.includes(CRUD_REST_COMPONENT)) {
47
+ restCrudImport.addNamedImport(CRUD_REST_COMPONENT);
48
+ }
49
+ }
50
+
51
+ // Find the constructor
52
+ const ctor = pFile.getClasses()[0].getConstructors()[0];
53
+ const body = ctor.getBodyText();
54
+
55
+ // Check if `this.component(CrudRestComponent)` exists
56
+ if (!body.includes(`this.component(${CRUD_REST_COMPONENT}`)) {
57
+ ctor.addStatements(`this.component(${CRUD_REST_COMPONENT});`);
58
+ }
59
+
60
+ await pFile.save();
61
+ }
62
+
63
+ exports.updateApplicationTs = updateApplicationTs;