@loopback/cli 4.0.0-alpha.8 → 4.1.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 (160) 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 +137 -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 -75
  140. package/bin/cli.js +0 -66
  141. package/generators/.DS_Store +0 -0
  142. package/generators/app/templates/index.js +0 -14
  143. package/generators/app/templates/src/application.ts +0 -27
  144. package/generators/app/templates/src/controllers/ping-controller.ts +0 -25
  145. package/generators/app/templates/src/index.ts +0 -25
  146. package/generators/app/templates/test/ping-controller.test.ts +0 -46
  147. package/generators/extension/templates/index.js +0 -8
  148. package/generators/extension/templates/src/component.ts +0 -14
  149. package/generators/extension/templates/src/index.ts +0 -6
  150. package/generators/project/templates/.npmrc +0 -1
  151. package/generators/project/templates/.yo-rc.json +0 -1
  152. package/generators/project/templates/README.md +0 -4
  153. package/generators/project/templates/index.d.ts +0 -6
  154. package/generators/project/templates/index.ts +0 -11
  155. package/generators/project/templates/package.json +0 -79
  156. package/generators/project/templates/package.plain.json +0 -82
  157. package/generators/project/templates/test/mocha.opts +0 -1
  158. package/generators/project/templates/tsconfig.json +0 -29
  159. package/generators/project/templates/tslint.build.json +0 -17
  160. package/generators/project/templates/tslint.json +0 -33
@@ -0,0 +1,70 @@
1
+ // Copyright IBM Corp. and LoopBack contributors 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 {pascalCase, stringifyModelSettings} = require('../../lib/utils');
9
+ const {sanitizeProperty} = require('../../lib/model-discoverer');
10
+ const {
11
+ createPropertyTemplateData,
12
+ findBuiltinType,
13
+ } = require('../model/property-definition');
14
+
15
+ module.exports = {
16
+ importDiscoveredModel,
17
+ };
18
+
19
+ /**
20
+ * Convert model definition created by loopback-datasource-juggler discovery
21
+ * into template data used by LB4 model generator.
22
+ *
23
+ * @param {object} discoveredDefinition Model definition as discovered from DB
24
+ * @returns {object} Template data for model source file template
25
+ */
26
+ function importDiscoveredModel(discoveredDefinition) {
27
+ const modelName = discoveredDefinition.name;
28
+ const templateData = {
29
+ name: modelName,
30
+ className: pascalCase(modelName),
31
+ modelBaseClass: 'Entity',
32
+ isModelBaseBuiltin: true,
33
+ settings: importModelSettings(discoveredDefinition.settings),
34
+ properties: importModelProperties(discoveredDefinition.properties),
35
+ allowAdditionalProperties: true,
36
+ };
37
+
38
+ templateData.modelSettings = stringifyModelSettings(templateData.settings);
39
+
40
+ return templateData;
41
+ }
42
+
43
+ function importModelSettings(discoveredSettings = {}) {
44
+ // Currently a no-op, we may want to apply transformation in the future
45
+ // See migrateModelSettings in ../import-lb3-models/migrate-model.js
46
+ return {
47
+ // Shallow-clone to avoid accidental modification of input data
48
+ ...discoveredSettings,
49
+ };
50
+ }
51
+
52
+ function importModelProperties(discoveredProps) {
53
+ const templateData = {};
54
+ for (const prop in discoveredProps) {
55
+ templateData[prop] = importPropertyDefinition(discoveredProps[prop]);
56
+ }
57
+ return templateData;
58
+ }
59
+
60
+ function importPropertyDefinition(discoveredDefinition) {
61
+ const propDef = {
62
+ ...discoveredDefinition,
63
+ };
64
+
65
+ const builtinType = findBuiltinType(propDef.type);
66
+ if (builtinType) propDef.type = builtinType;
67
+
68
+ sanitizeProperty(propDef);
69
+ return createPropertyTemplateData(propDef);
70
+ }
@@ -0,0 +1,411 @@
1
+ // Copyright IBM Corp. and LoopBack contributors 2019,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
+ const path = require('path');
7
+ const ArtifactGenerator = require('../../lib/artifact-generator');
8
+ const modelMaker = require('../../lib/model-discoverer');
9
+ const debug = require('../../lib/debug')('discover-generator');
10
+ const chalk = require('chalk');
11
+ const utils = require('../../lib/utils');
12
+ const modelDiscoverer = require('../../lib/model-discoverer');
13
+ const {importDiscoveredModel} = require('./import-discovered-model');
14
+ const g = require('../../lib/globalize');
15
+
16
+ const rootDir = 'src';
17
+
18
+ module.exports = class DiscoveryGenerator extends ArtifactGenerator {
19
+ constructor(args, opts) {
20
+ super(args, opts);
21
+
22
+ this.option('dataSource', {
23
+ type: String,
24
+ alias: 'ds',
25
+ description: g.f('The name of the datasource to discover'),
26
+ });
27
+
28
+ this.option('views', {
29
+ type: Boolean,
30
+ description: g.f('Boolean to discover views'),
31
+ default: true,
32
+ });
33
+
34
+ this.option('relations', {
35
+ type: Boolean,
36
+ description: g.f('Discover and create relations'),
37
+ default: false,
38
+ });
39
+
40
+ this.option('schema', {
41
+ type: String,
42
+ description: g.f('Schema to discover'),
43
+ default: '',
44
+ });
45
+
46
+ this.option('all', {
47
+ type: Boolean,
48
+ description: g.f('Discover all models without prompting users to select'),
49
+ default: false,
50
+ });
51
+
52
+ this.option('outDir', {
53
+ type: String,
54
+ description: g.f(
55
+ 'Specify the directory into which the `model.model.ts` files will be placed',
56
+ ),
57
+ default: undefined,
58
+ });
59
+
60
+ this.option('optionalId', {
61
+ type: Boolean,
62
+ description: g.f('Boolean to mark id property as optional field'),
63
+ default: false,
64
+ });
65
+ }
66
+
67
+ _setupGenerator() {
68
+ this.artifactInfo = {
69
+ type: 'discover',
70
+ rootDir,
71
+ outDir: path.resolve(rootDir, 'models'),
72
+ };
73
+
74
+ return super._setupGenerator();
75
+ }
76
+
77
+ /**
78
+ * If we have a dataSource, attempt to load it
79
+ * @returns {*}
80
+ */
81
+ setOptions() {
82
+ /* istanbul ignore next */
83
+ if (this.options.dataSource) {
84
+ debug(`Data source specified: ${this.options.dataSource}`);
85
+ this.artifactInfo.dataSource = modelMaker.loadDataSourceByName(
86
+ this.options.dataSource,
87
+ );
88
+ }
89
+
90
+ return super.setOptions();
91
+ }
92
+
93
+ /**
94
+ * Ensure CLI is being run in a LoopBack 4 project.
95
+ */
96
+ checkLoopBackProject() {
97
+ /* istanbul ignore next */
98
+ if (this.shouldExit()) return;
99
+ return super.checkLoopBackProject();
100
+ }
101
+
102
+ /**
103
+ * Loads all datasources to choose if the dataSource option isn't set
104
+ */
105
+ async loadAllDatasources() {
106
+ // If we have a dataSourcePath then it is already loaded for us, we don't need load any
107
+ /* istanbul ignore next */
108
+ if (this.artifactInfo.dataSource) {
109
+ return;
110
+ }
111
+ const dsDir = modelMaker.DEFAULT_DATASOURCE_DIRECTORY;
112
+ const datasourcesList = await utils.getArtifactList(
113
+ dsDir,
114
+ 'datasource',
115
+ false,
116
+ );
117
+ debug(datasourcesList);
118
+
119
+ this.dataSourceChoices = datasourcesList.map(s =>
120
+ modelDiscoverer.loadDataSource(
121
+ path.resolve(dsDir, `${utils.toFileName(s)}.datasource.js`),
122
+ ),
123
+ );
124
+
125
+ if (this.options.dataSource) {
126
+ if (
127
+ this.dataSourceChoices
128
+ .map(d => d.name)
129
+ .includes(this.options.dataSource)
130
+ ) {
131
+ Object.assign(this.artifactInfo, {
132
+ dataSource: this.dataSourceChoices.find(
133
+ d => d.name === this.options.dataSource,
134
+ ),
135
+ });
136
+ }
137
+ }
138
+ debug(`Done importing datasources`);
139
+ }
140
+
141
+ /**
142
+ * Ask the user to select the data source from which to discover
143
+ */
144
+ promptDataSource() {
145
+ /* istanbul ignore next */
146
+ if (this.shouldExit()) return;
147
+ const prompts = [
148
+ {
149
+ name: 'dataSource',
150
+ message: g.f('Select the connector to discover'),
151
+ type: 'list',
152
+ choices: this.dataSourceChoices,
153
+ when:
154
+ this.artifactInfo.dataSource === undefined &&
155
+ !this.artifactInfo.modelDefinitions,
156
+ },
157
+ ];
158
+
159
+ return this.prompt(prompts).then(answer => {
160
+ /* istanbul ignore next */
161
+ if (!answer.dataSource) return;
162
+ debug(`Datasource answer: ${JSON.stringify(answer)}`);
163
+
164
+ this.artifactInfo.dataSource = this.dataSourceChoices.find(
165
+ d => d.name === answer.dataSource,
166
+ );
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Puts all discoverable models in this.modelChoices
172
+ */
173
+ async discoverModelInfos() {
174
+ /* istanbul ignore if */
175
+ if (this.artifactInfo.modelDefinitions) return;
176
+ debug(`Getting all models from ${this.artifactInfo.dataSource.name}`);
177
+
178
+ this.modelChoices = await modelMaker.discoverModelNames(
179
+ this.artifactInfo.dataSource,
180
+ {
181
+ views: this.options.views,
182
+ schema: this.options.schema,
183
+ },
184
+ );
185
+ debug(
186
+ `Got ${this.modelChoices.length} models from ${this.artifactInfo.dataSource.name}`,
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Now that we have a list of all models for a datasource,
192
+ * ask which models to discover
193
+ */
194
+ promptModelChoices() {
195
+ // If we are discovering all we don't need to prompt
196
+ /* istanbul ignore next */
197
+ if (this.options.all) {
198
+ this.discoveringModels = this.modelChoices;
199
+ }
200
+
201
+ const prompts = [
202
+ {
203
+ name: 'discoveringModels',
204
+ message: g.f('Select the models which to discover'),
205
+ type: 'checkbox',
206
+ choices: this.modelChoices,
207
+ when:
208
+ this.discoveringModels === undefined &&
209
+ !this.artifactInfo.modelDefinitions,
210
+ // Require at least one model to be selected
211
+ // This prevents users from accidentally pressing ENTER instead of SPACE
212
+ // to select a model from the list
213
+ validate: result => !!result.length,
214
+ },
215
+ ];
216
+
217
+ return this.prompt(prompts).then(answers => {
218
+ /* istanbul ignore next */
219
+ if (!answers.discoveringModels) return;
220
+ debug(`Models chosen: ${JSON.stringify(answers)}`);
221
+ this.discoveringModels = [];
222
+ answers.discoveringModels.forEach(m => {
223
+ this.discoveringModels.push(this.modelChoices.find(c => c.name === m));
224
+ });
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Prompts what naming convention they would like to have for column names.
230
+ */
231
+ promptColNamingConvention() {
232
+ this.namingConvention = [
233
+ {
234
+ name: g.f('Camel case (exampleColumn) (Recommended)'),
235
+ value: 'camelCase',
236
+ },
237
+ {
238
+ name: g.f('No conversion (EXAMPLE_COLUMN)'),
239
+ value: 'noCase',
240
+ },
241
+ ];
242
+ return this.prompt([
243
+ {
244
+ name: 'disableCamelCase',
245
+ message: g.f(
246
+ 'Select a convention to convert db column names(EXAMPLE_COLUMN) to model property names:',
247
+ ),
248
+ type: 'list',
249
+ choices: this.namingConvention,
250
+ default: false,
251
+ },
252
+ ]).then(props => {
253
+ /* istanbul ignore next */
254
+ if (!props.disableCamelCase) return;
255
+ props.disableCamelCase = props.disableCamelCase !== 'camelCase';
256
+
257
+ Object.assign(this.artifactInfo, props);
258
+ /* istanbul ignore next */
259
+ if (props.disableCamelCase) {
260
+ this.log(
261
+ chalk.red(
262
+ g.f(
263
+ 'By disabling Camel case, you might need to specify these customized names in relation definition.',
264
+ ),
265
+ ),
266
+ );
267
+ }
268
+ debug(`props after naming convention prompt: ${props.disableCamelCase}`);
269
+ return props;
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Using artifactInfo.dataSource,
275
+ * artifactInfo.modelNameOptions
276
+ *
277
+ * this will discover every model
278
+ * and put it in artifactInfo.modelDefinitions
279
+ * @returns {Promise<void>}
280
+ */
281
+ async getAllModelDefs() {
282
+ /* istanbul ignore next */
283
+ if (this.shouldExit()) {
284
+ await this.artifactInfo.dataSource.disconnect();
285
+ return false;
286
+ }
287
+ this.artifactInfo.modelDefinitions = [];
288
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
289
+ for (let i = 0; i < this.discoveringModels.length; i++) {
290
+ const modelInfo = this.discoveringModels[i];
291
+ debug(`Discovering: ${modelInfo.name}...`);
292
+ const modelDefinition = await modelMaker.discoverSingleModel(
293
+ this.artifactInfo.dataSource,
294
+ modelInfo.name,
295
+ {
296
+ schema: modelInfo.owner,
297
+ disableCamelCase: this.artifactInfo.disableCamelCase,
298
+ associations: this.options.relations,
299
+ },
300
+ );
301
+ if (this.options.optionalId) {
302
+ // Find id properties (can be multiple ids if using composite key)
303
+ const idProperties = Object.values(modelDefinition.properties).filter(
304
+ property => property.id,
305
+ );
306
+ // Mark as not required
307
+ idProperties.forEach(property => {
308
+ property.required = false;
309
+ });
310
+ }
311
+ this.artifactInfo.modelDefinitions.push(modelDefinition);
312
+ debug(`Discovered: ${modelInfo.name}`);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Iterate through all the models we have discovered and scaffold
318
+ */
319
+ async scaffold() {
320
+ // Exit if needed
321
+ /* istanbul ignore next */
322
+ if (this.shouldExit()) {
323
+ await this.artifactInfo.dataSource.disconnect();
324
+ return;
325
+ }
326
+ this.artifactInfo.indexesToBeUpdated =
327
+ this.artifactInfo.indexesToBeUpdated || [];
328
+
329
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
330
+ for (let i = 0; i < this.artifactInfo.modelDefinitions.length; i++) {
331
+ const modelDefinition = this.artifactInfo.modelDefinitions[i];
332
+ const templateData = importDiscoveredModel(modelDefinition);
333
+
334
+ debug(
335
+ 'Generating model %s from template data',
336
+ modelDefinition.name,
337
+ templateData,
338
+ );
339
+
340
+ const fullPath = path.resolve(
341
+ this.options.outDir || this.artifactInfo.outDir,
342
+ utils.getModelFileName(modelDefinition.name),
343
+ );
344
+ debug(`Writing: ${fullPath}`);
345
+
346
+ if (this.options.relations) {
347
+ const relationImports = [];
348
+ const relationDestinationImports = [];
349
+ const foreignKeys = {};
350
+ for (const relationName in templateData.settings.relations) {
351
+ const relation = templateData.settings.relations[relationName];
352
+ const targetModel = this.artifactInfo.modelDefinitions.find(
353
+ model => model.name === relation.model,
354
+ );
355
+ // If targetModel is not in discovered models, skip creating relation
356
+ if (targetModel) {
357
+ Object.assign(templateData.properties[relation.foreignKey], {
358
+ relation,
359
+ });
360
+ relationImports.push(relation.type);
361
+ relationDestinationImports.push(relation.model);
362
+
363
+ foreignKeys[relationName] = {};
364
+ Object.assign(foreignKeys[relationName], {
365
+ name: relationName,
366
+ entity: relation.model,
367
+ entityKey: Object.entries(targetModel.properties).find(
368
+ x => x?.[1].id === 1,
369
+ )?.[0],
370
+ foreignKey: relation.foreignKey,
371
+ });
372
+ }
373
+ }
374
+ templateData.relationImports = relationImports;
375
+ templateData.relationDestinationImports = relationDestinationImports;
376
+ // Delete relation from modelSettings
377
+ delete templateData.settings.relations;
378
+ if (Object.keys(foreignKeys)?.length > 0) {
379
+ Object.assign(templateData.settings, {foreignKeys});
380
+ }
381
+ templateData.modelSettings = utils.stringifyModelSettings(
382
+ templateData.settings,
383
+ );
384
+ }
385
+
386
+ this.copyTemplatedFiles(
387
+ modelDiscoverer.MODEL_TEMPLATE_PATH,
388
+ fullPath,
389
+ templateData,
390
+ );
391
+
392
+ this.artifactInfo.indexesToBeUpdated.push({
393
+ dir: this.options.outDir || this.artifactInfo.outDir,
394
+ file: utils.getModelFileName(modelDefinition.name),
395
+ });
396
+
397
+ await this.artifactInfo.dataSource.disconnect();
398
+ }
399
+
400
+ // This part at the end is just for the ArtifactGenerator
401
+ // end message to output something nice, before it was "Discover undefined was created in src/models/"
402
+ this.artifactInfo.type = 'Models';
403
+ this.artifactInfo.name = this.artifactInfo.modelDefinitions
404
+ .map(d => d.name)
405
+ .join(',');
406
+ }
407
+
408
+ async end() {
409
+ await super.end();
410
+ }
411
+ };
@@ -0,0 +1,16 @@
1
+ // Copyright IBM Corp. and LoopBack contributors 2018. 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 path = require('path');
9
+ const pacote = require('pacote');
10
+
11
+ module.exports = async function downloadAndExtractExample(exampleName, cwd) {
12
+ const packageSpec = `@loopback/example-${exampleName}`;
13
+ const outDir = path.join(cwd, `loopback4-example-${exampleName}`);
14
+ await pacote.extract(packageSpec, outDir);
15
+ return outDir;
16
+ };
@@ -0,0 +1,176 @@
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 chalk = require('chalk');
10
+ const downloadAndExtractExample = require('./downloader');
11
+ const path = require('path');
12
+ const fs = require('fs-extra');
13
+ const g = require('../../lib/globalize');
14
+
15
+ const EXAMPLES = {
16
+ todo: g.f('Tutorial example on how to build an application with LoopBack 4.'),
17
+ 'todo-list': g.f(
18
+ 'Continuation of the todo example using relations in LoopBack 4.',
19
+ ),
20
+ 'hello-world': g.f('A simple hello-world application using LoopBack 4.'),
21
+ 'log-extension': g.f('An example extension project for LoopBack 4.'),
22
+ 'rpc-server': g.f('A basic RPC server using a made-up protocol.'),
23
+ 'soap-calculator': g.f('An example on how to integrate SOAP web services.'),
24
+ 'express-composition': g.f(
25
+ 'A simple Express application that uses LoopBack 4 REST API.',
26
+ ),
27
+ context: g.f('Standalone examples showing how to use @loopback/context.'),
28
+ 'greeter-extension': g.f(
29
+ 'An example showing how to implement the extension point/extension pattern.',
30
+ ),
31
+ 'greeting-app': g.f(
32
+ 'An example showing how to compose an application from component and ' +
33
+ 'controllers, interceptors, and observers.',
34
+ ),
35
+ 'lb3-application': g.f(
36
+ 'An example LoopBack 3 application mounted in a LoopBack 4 project.',
37
+ ),
38
+ 'rest-crud': g.f(
39
+ 'A simplified version of the Todo example that only requires a model and ' +
40
+ 'a datasource.',
41
+ ),
42
+ 'file-transfer': g.f(
43
+ 'An example showing how to expose APIs to upload/download files.',
44
+ ),
45
+ 'access-control-migration': g.f(
46
+ 'An access control example migrated from the LoopBack 3 repository ' +
47
+ 'loopback-example-access-control.',
48
+ ),
49
+ 'metrics-prometheus': g.f(
50
+ 'An example illustrating metrics using Prometheus.',
51
+ ),
52
+ 'validation-app': g.f('An example demonstrating how to add validations.'),
53
+ 'multi-tenancy': g.f(
54
+ 'An example application to demonstrate how to implement multi-tenancy with LoopBack 4.',
55
+ ),
56
+ 'passport-login': g.f(
57
+ 'An example implmenting authentication in a LoopBack application using Passport modules.',
58
+ ),
59
+ 'todo-jwt': g.f('A modified Todo application with JWT authentication.'),
60
+ webpack: g.f('An example to bundle @loopback/core using webpack.'),
61
+ graphql: g.f('An example to demonstrate GraphQL integration.'),
62
+ socketio: g.f('A basic implementation of Socket.IO'),
63
+ };
64
+ Object.freeze(EXAMPLES);
65
+
66
+ module.exports = class extends BaseGenerator {
67
+ static getAllExamples() {
68
+ return EXAMPLES;
69
+ }
70
+
71
+ // Note: arguments and options should be defined in the constructor.
72
+ constructor(args, opts) {
73
+ super(args, opts);
74
+ }
75
+
76
+ _setupGenerator() {
77
+ this.projectType = 'example';
78
+ this.argument('example-name', {
79
+ type: String,
80
+ description: g.f('Name of the example to clone'),
81
+ required: false,
82
+ });
83
+
84
+ return super._setupGenerator();
85
+ }
86
+
87
+ setOptions() {
88
+ return super.setOptions();
89
+ }
90
+
91
+ help() {
92
+ const examplesHelp = Object.keys(EXAMPLES)
93
+ .map(name => ` ${name}: ${EXAMPLES[name]}`)
94
+ .join('\n');
95
+
96
+ return super.help() + `\nAvailable examples:\n${examplesHelp}\n`;
97
+ }
98
+
99
+ _describeExamples() {}
100
+
101
+ promptExampleName() {
102
+ if (this.shouldExit()) return;
103
+ if (this.options['example-name']) {
104
+ this.exampleName = this.options['example-name'];
105
+ return;
106
+ }
107
+
108
+ const choices = Object.keys(EXAMPLES).map(k => {
109
+ return {
110
+ name: `${k}: ${EXAMPLES[k]}`,
111
+ value: `${k}`,
112
+ short: `${k}`,
113
+ };
114
+ });
115
+ const prompts = [
116
+ {
117
+ name: 'name',
118
+ message: g.f('What example would you like to clone?'),
119
+ type: 'list',
120
+ choices,
121
+ },
122
+ ];
123
+ return this.prompt(prompts).then(
124
+ answers => (this.exampleName = answers.name),
125
+ );
126
+ }
127
+
128
+ validateExampleName() {
129
+ if (this.shouldExit()) return;
130
+ if (this.exampleName in EXAMPLES) return;
131
+ this.exit(
132
+ g.f(
133
+ 'Invalid example name: %s\n' +
134
+ 'Run "lb4 example --help" to print the list of available example names.',
135
+ this.exampleName,
136
+ ),
137
+ );
138
+ }
139
+
140
+ async downloadAndExtract() {
141
+ if (this.shouldExit()) return false;
142
+ const cwd = process.cwd();
143
+ const absOutDir = await downloadAndExtractExample(this.exampleName, cwd);
144
+ this.outDir = path.relative(cwd, absOutDir);
145
+ const tsconfig = path.join(absOutDir, 'tsconfig.json');
146
+
147
+ // Support older versions of examples that are using `tsconfig.build.json`
148
+ const tsBuildConfig = path.join(absOutDir, 'tsconfig.build.json');
149
+ const exists = await fs.pathExists(tsconfig);
150
+ if (!exists) {
151
+ return fs.rename(tsBuildConfig, tsconfig);
152
+ }
153
+
154
+ // Recent versions of examples are using project references inside monorepo,
155
+ // see https://github.com/loopbackio/loopback-next/pull/5155
156
+ // We must switch to standalone mode (no project references) when the code
157
+ // was checked out outside of our monorepo.
158
+ const tsconfigContent = await fs.readJson(tsconfig);
159
+ delete tsconfigContent.references;
160
+ tsconfigContent.compilerOptions.composite = false;
161
+ await fs.writeJson(tsconfig, tsconfigContent);
162
+ }
163
+
164
+ install() {
165
+ if (this.shouldExit()) return false;
166
+ this.destinationRoot(this.outDir);
167
+ return super.install();
168
+ }
169
+
170
+ async end() {
171
+ await super.end();
172
+ this.log();
173
+ this.log(g.f('The example was cloned to %s.', chalk.green(this.outDir)));
174
+ this.log();
175
+ }
176
+ };