@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,197 @@
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
+ 'use strict';
7
+
8
+ const chalk = require('chalk');
9
+ const path = require('path');
10
+ const BaseGenerator = require('../../lib/base-generator');
11
+ const modelUtils = require('../../lib/model-discoverer');
12
+ const debug = require('../../lib/debug')('import-lb3-models');
13
+ const utils = require('../../lib/utils');
14
+ const {loadLb3App} = require('./lb3app-loader');
15
+ const {importLb3ModelDefinition} = require('./migrate-model');
16
+ const {canImportModelName} = require('./model-names');
17
+ const g = require('../../lib/globalize');
18
+
19
+ module.exports = class Lb3ModelImporter extends BaseGenerator {
20
+ constructor(args, opts) {
21
+ super(args, opts);
22
+
23
+ this.argument('lb3app', {
24
+ type: String,
25
+ required: true,
26
+ description: g.f(
27
+ 'Path to your LoopBack 3.x application. ' +
28
+ 'This can be a project directory (e.g. "my-lb3-app") or ' +
29
+ 'the server file (e.g. "my-lb3-app/server/server.js").',
30
+ ),
31
+ // description:
32
+ // 'Path to your LoopBack 3.x application. ' +
33
+ // 'This can be a project directory (e.g. "my-lb3-app") or ' +
34
+ // 'the server file (e.g. "my-lb3-app/server/server.js").',
35
+ });
36
+
37
+ this.option('outDir', {
38
+ type: String,
39
+ description: g.f('Directory where to write the generated source file'),
40
+ default: 'src/models',
41
+ });
42
+ }
43
+
44
+ async processOptions() {
45
+ this.sourceAppDir = this.args[0];
46
+ this.artifactInfo.outDir = this.options.outDir;
47
+ this.artifactInfo.relPath = path.relative(
48
+ this.destinationPath(),
49
+ this.artifactInfo.outDir,
50
+ );
51
+ this.artifactInfo.modelDir = path.resolve(
52
+ this.artifactInfo.rootDir,
53
+ utils.modelsDir,
54
+ );
55
+ return super.setOptions();
56
+ }
57
+
58
+ async logExperimentalStatus() {
59
+ this.log(
60
+ chalk.red(`
61
+ WARNING: This command is experimental and not feature-complete yet.
62
+ Learn more at https://loopback.io/doc/en/lb4/Importing-LB3-models.html
63
+ `),
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Ensure CLI is being run in a LoopBack 4 project.
69
+ */
70
+ checkLoopBackProject() {
71
+ if (this.shouldExit()) return;
72
+ return super.checkLoopBackProject();
73
+ }
74
+
75
+ async loadTheApp() {
76
+ this.lb3app = await loadLb3App(this.sourceAppDir);
77
+ this.modelRegistry = this.lb3app.registry.modelBuilder.models;
78
+ }
79
+
80
+ async promptModels() {
81
+ const modelNames = Object.keys(this.modelRegistry).filter(
82
+ canImportModelName,
83
+ );
84
+
85
+ debug('Available LB3 models', modelNames);
86
+
87
+ const prompts = [
88
+ {
89
+ name: 'modelNames',
90
+ message: g.f('Select models to import:'),
91
+ type: 'checkbox',
92
+ choices: modelNames,
93
+ // Require at least one model to be selected
94
+ // This prevents users from accidentally pressing ENTER instead of SPACE
95
+ // to select a model from the list
96
+ validate: result => !!result.length,
97
+ // TODO: add a CLI flag to supply these names programmatically
98
+ },
99
+ ];
100
+
101
+ const answers = await this.prompt(prompts);
102
+ debug('Models chosen:', answers.modelNames);
103
+ this.modelNames = answers.modelNames;
104
+ }
105
+
106
+ async loadExistingLb4Models() {
107
+ debug(`model list dir ${this.artifactInfo.modelDir}`);
108
+ this.existingLb4ModelNames = await utils.getArtifactList(
109
+ this.artifactInfo.modelDir,
110
+ 'model',
111
+ );
112
+ }
113
+
114
+ async migrateSelectedModels() {
115
+ if (this.shouldExit()) return;
116
+ this.modelFiles = [];
117
+
118
+ try {
119
+ for (const name of this.modelNames) {
120
+ await this._migrateSingleModel(name);
121
+ }
122
+ } catch (err) {
123
+ if (err.exit) {
124
+ this.exit(err.message);
125
+ } else {
126
+ throw err;
127
+ }
128
+ }
129
+ }
130
+
131
+ async _migrateSingleModel(name) {
132
+ utils.logClassCreation('model', 'models', name, this.log.bind(this));
133
+ const modelCtor = this.modelRegistry[name];
134
+ if (typeof modelCtor !== 'function') {
135
+ const availableModels = Object.keys(this.modelRegistry)
136
+ .filter(canImportModelName)
137
+ .join(', ');
138
+
139
+ this.exit(
140
+ `Unknown model name ${name}. Available models: ${availableModels}.`,
141
+ );
142
+ return;
143
+ }
144
+
145
+ const templateData = importLb3ModelDefinition(
146
+ modelCtor,
147
+ this.log.bind(this),
148
+ );
149
+ debug('LB4 model data', templateData);
150
+
151
+ if (!templateData.isModelBaseBuiltin) {
152
+ const baseName = templateData.modelBaseClass;
153
+ if (
154
+ !this.existingLb4ModelNames.includes(baseName) &&
155
+ !this.modelNames.includes(baseName)
156
+ ) {
157
+ this.log(
158
+ 'Adding %s (base of %s) to the list of imported models.',
159
+ chalk.yellow(baseName),
160
+ chalk.yellow(name),
161
+ );
162
+ this.modelNames.push(baseName);
163
+ }
164
+ }
165
+ const fileName = utils.getModelFileName(name);
166
+ const fullTargetPath = path.resolve(this.artifactInfo.relPath, fileName);
167
+ debug('Model %s output file', name, fullTargetPath);
168
+
169
+ this.copyTemplatedFiles(
170
+ modelUtils.MODEL_TEMPLATE_PATH,
171
+ fullTargetPath,
172
+ templateData,
173
+ );
174
+
175
+ this.modelFiles.push(fileName);
176
+ }
177
+
178
+ /**
179
+ * Iterate through all the models we have discovered and scaffold
180
+ */
181
+ async scaffold() {
182
+ if (this.shouldExit()) return;
183
+ }
184
+
185
+ async end() {
186
+ if (this.shouldExit() || !this._isGenerationSuccessful()) {
187
+ await super.end();
188
+ return;
189
+ }
190
+
191
+ for (const f of this.modelFiles) {
192
+ await this._updateIndexFile(this.artifactInfo.outDir, f);
193
+ }
194
+
195
+ await super.end();
196
+ }
197
+ };
@@ -0,0 +1,31 @@
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
+ 'use strict';
7
+
8
+ const debug = require('../../lib/debug')('import-lb3-models');
9
+ const path = require('path');
10
+ const {once} = require('events');
11
+
12
+ module.exports = {
13
+ loadLb3App,
14
+ };
15
+
16
+ // TODO: do we want to share this code with `Lb3AppBooter.loadAndBootTheApp`?
17
+ async function loadLb3App(dir) {
18
+ debug('Loading LB3 app from', dir);
19
+ const lb3App = require(path.resolve(dir));
20
+
21
+ debug(
22
+ 'If your LB3 app does not boot correctly then make sure it is using loopback-boot version 3.2.1 or higher.',
23
+ );
24
+
25
+ if (lb3App.booting) {
26
+ debug(' waiting for boot process to finish');
27
+ // Wait until the legacy LB3 app boots
28
+ await once(lb3App, 'booted');
29
+ }
30
+ return lb3App;
31
+ }
@@ -0,0 +1,249 @@
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
+ 'use strict';
7
+
8
+ const {
9
+ validateClassName,
10
+ logNamingIssues,
11
+ pascalCase,
12
+ stringifyModelSettings,
13
+ } = require('../../lib/utils');
14
+ const {sanitizeProperty} = require('../../lib/model-discoverer');
15
+ const {
16
+ createPropertyTemplateData,
17
+ findBuiltinType,
18
+ } = require('../model/property-definition');
19
+ const chalk = require('chalk');
20
+ const {isDeepStrictEqual} = require('util');
21
+
22
+ module.exports = {
23
+ importLb3ModelDefinition,
24
+ };
25
+
26
+ /**
27
+ * Convert definition of a LB3 model into template data used by
28
+ * LB4 model generator.
29
+ *
30
+ * @param {Function} modelCtor LB3 model class
31
+ * @param {(msg: string, ...args: any) => void} log Logging function
32
+ * @returns {object} Template data for model source file template
33
+ */
34
+ function importLb3ModelDefinition(modelCtor, log) {
35
+ const modelName = modelCtor.modelName;
36
+ const result = validateClassName(modelName);
37
+ if (!result) {
38
+ const err = new Error(
39
+ `Cannot import model: the name ${modelName} is not valid. ${result}`,
40
+ );
41
+ err.exit = true;
42
+ throw err;
43
+ }
44
+
45
+ logNamingIssues(modelName, log);
46
+
47
+ const baseDefinition = modelCtor.base.definition;
48
+ const baseProps = {...baseDefinition.properties};
49
+
50
+ // Core LB3 models like PersistedModel come with an id property that's
51
+ // injected by juggler. We don't want to inherit that property, because
52
+ // in LB4, we want models to define the id property explicitly.
53
+ if (isCoreModel(modelCtor.base)) {
54
+ delete baseProps.id;
55
+ }
56
+
57
+ const templateData = {
58
+ name: modelName,
59
+ className: pascalCase(modelName),
60
+ ...migrateBaseClass(modelCtor.settings.base),
61
+ properties: migrateModelProperties(
62
+ modelCtor.definition.properties,
63
+ baseProps,
64
+ ),
65
+ settings: migrateModelSettings(
66
+ modelCtor.definition.settings,
67
+ baseDefinition.settings,
68
+ log,
69
+ ),
70
+ };
71
+
72
+ const settings = templateData.settings;
73
+ delete settings.base; // already handled by migrateBaseClass
74
+ templateData.allowAdditionalProperties = !settings.strict;
75
+ templateData.modelSettings = stringifyModelSettings(settings);
76
+
77
+ return templateData;
78
+ }
79
+
80
+ function migrateModelProperties(properties = {}, inherited = {}) {
81
+ const templateData = {};
82
+
83
+ // In LB 3.x, primary keys are typically contributed by connectors later in
84
+ // the startup process, therefore they end up at the end of the property list.
85
+ // Here we create placeholder entries for PKs to get them generated first.
86
+ Object.keys(properties)
87
+ .filter(p => !!properties[p].id)
88
+ .forEach(pk => {
89
+ templateData[pk] = null;
90
+ });
91
+
92
+ for (const prop in properties) {
93
+ const propDef = properties[prop];
94
+
95
+ // Skip the property if it was inherited from the base model (the parent)
96
+ const baseProp = inherited[prop];
97
+ if (baseProp && isDeepStrictEqual(propDef, baseProp)) {
98
+ delete templateData[prop];
99
+ continue;
100
+ }
101
+
102
+ const def = migratePropertyDefinition(propDef);
103
+ templateData[prop] = createPropertyTemplateData(def);
104
+ }
105
+
106
+ return templateData;
107
+ }
108
+
109
+ function migratePropertyDefinition(lb3PropDef) {
110
+ const def = {...lb3PropDef};
111
+
112
+ if (!Array.isArray(def.type)) {
113
+ // scalar type
114
+ def.type = migratePropertyType(def.type);
115
+ } else {
116
+ // array type - conversion is slightly more complex
117
+ def.itemType = migratePropertyType(def.type[0]);
118
+ def.type = 'array';
119
+
120
+ // workaround for a weird behavior of LB3
121
+ if (0 in def && def[0] === def.itemType) {
122
+ delete def[0];
123
+ }
124
+ }
125
+
126
+ if (def.updateOnly === false) {
127
+ // updateOnly is disabled by default, no need to specify "false" value
128
+ delete def.updateOnly;
129
+ }
130
+ sanitizeProperty(def);
131
+ return def;
132
+ }
133
+
134
+ function migratePropertyType(typeDef) {
135
+ if (typeof typeDef === 'function') {
136
+ typeDef = typeDef.name.toString();
137
+ }
138
+
139
+ const builtin = findBuiltinType(typeDef);
140
+ if (builtin) typeDef = builtin;
141
+
142
+ // TODO: handle anonymous object types (nested properties)
143
+
144
+ return typeDef;
145
+ }
146
+
147
+ const BUILTIN_BASE_MODELS = {
148
+ Model: 'Model',
149
+ PersistedModel: 'Entity',
150
+ KeyValueModel: 'KeyValueModel',
151
+ };
152
+
153
+ function migrateBaseClass(base) {
154
+ const baseModelName = base.modelName || base.name || base;
155
+ if (baseModelName in BUILTIN_BASE_MODELS) {
156
+ return {
157
+ modelBaseClass: BUILTIN_BASE_MODELS[baseModelName],
158
+ isModelBaseBuiltin: true,
159
+ };
160
+ }
161
+
162
+ return {
163
+ modelBaseClass: baseModelName,
164
+ isModelBaseBuiltin: false,
165
+ };
166
+ }
167
+
168
+ function migrateModelSettings(settings = {}, inherited = {}, log) {
169
+ // Shallow-clone the object to prevent modification of external data
170
+ settings = {...settings};
171
+
172
+ // "strict" mode is enabled only when explicitly requested
173
+ // LB3 models allow additional properties by default
174
+ settings.strict = settings.strict === true;
175
+
176
+ // Remove settings inherited from the base model
177
+ for (const key in inherited) {
178
+ // Always emit the value of 'strict' setting, make it explicit
179
+ if (key === 'strict') continue;
180
+
181
+ if (isDeepStrictEqual(settings[key], inherited[key])) {
182
+ delete settings[key];
183
+ }
184
+ }
185
+
186
+ if (settings.forceId === 'auto') {
187
+ // The value 'auto' is used when a parent model wants to let the child
188
+ // model make the decision automatically, depending on whether the child
189
+ // model has a database-generated PK.
190
+ // See https://github.com/loopbackio/loopback-datasource-juggler/blob/15251880a1d07ccc2ca6d2dccdd065d00a7375eb/lib/model-builder.js#L347-L355
191
+ //
192
+ // Let's delete the flag from the generated model settings and
193
+ // leave it up to the runtime to decide.
194
+ delete settings.forceId;
195
+ }
196
+
197
+ // Notable settings that are not supported yet:
198
+ // relations, acls, methods, mixins, validations
199
+
200
+ const relationNames = Object.keys(settings.relations || {});
201
+ if (relationNames.length) {
202
+ log(
203
+ chalk.yellow(
204
+ 'Import of model relations is not supported yet. ' +
205
+ 'Skipping the following relations: ' +
206
+ relationNames.join(', '),
207
+ ),
208
+ );
209
+ }
210
+ delete settings.relations;
211
+
212
+ const unsupportedSettings = [];
213
+ if ((settings.acls || []).length) {
214
+ unsupportedSettings.push('acls');
215
+ }
216
+ delete settings.acls;
217
+
218
+ for (const k of ['methods', 'mixins', 'validations']) {
219
+ if (k in settings && Object.keys(settings[k]).length) {
220
+ unsupportedSettings.push(k);
221
+ }
222
+ delete settings[k];
223
+ }
224
+
225
+ if (unsupportedSettings.length) {
226
+ log(
227
+ chalk.yellow(
228
+ 'Ignoring the following unsupported settings: ' +
229
+ unsupportedSettings.join(', '),
230
+ ),
231
+ );
232
+ }
233
+
234
+ if (
235
+ typeof settings.hiddenProperties === 'object' &&
236
+ Object.keys(settings.hiddenProperties).length === 0
237
+ ) {
238
+ delete settings.hiddenProperties;
239
+ }
240
+
241
+ return settings;
242
+ }
243
+
244
+ function isCoreModel(modelCtor) {
245
+ const name = modelCtor.modelName;
246
+ return (
247
+ name === 'Model' || name === 'PersistedModel' || name === 'KeyValueModel'
248
+ );
249
+ }
@@ -0,0 +1,32 @@
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
+ module.exports = {
9
+ canImportModelName,
10
+ };
11
+
12
+ // List of built-in LB models to exclude from the prompt
13
+ const EXCLUDED_MODEL_NAMES = [
14
+ // base models
15
+ 'Model',
16
+ 'PersistedModel',
17
+ 'KeyValueModel',
18
+ // change tracking & replication
19
+ 'Change',
20
+ 'Checkpoint',
21
+ // Email - a dummy model used to attach email-connector methods only
22
+ 'Email',
23
+ ];
24
+
25
+ function canImportModelName(name) {
26
+ if (EXCLUDED_MODEL_NAMES.includes(name)) return false;
27
+ // TODO: find out where are anonymous models coming from
28
+ // (perhaps from object types defined inside property definitions?)
29
+ // and add test cases to verify that we are handling those scenarios correctly
30
+ if (name.startsWith('AnonymousModel_')) return false;
31
+ return true;
32
+ }
@@ -0,0 +1,178 @@
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
+ // no translation: Interceptor
7
+ 'use strict';
8
+ const ArtifactGenerator = require('../../lib/artifact-generator');
9
+ const debug = require('../../lib/debug')('interceptor-generator');
10
+ const inspect = require('util').inspect;
11
+ const path = require('path');
12
+ const utils = require('../../lib/utils');
13
+
14
+ const SCRIPT_TEMPLATE = 'interceptor-template.ts.ejs';
15
+ const g = require('../../lib/globalize');
16
+
17
+ module.exports = class InterceptorGenerator extends ArtifactGenerator {
18
+ // Note: arguments and options should be defined in the constructor.
19
+ constructor(args, opts) {
20
+ super(args, opts);
21
+ }
22
+
23
+ _setupGenerator() {
24
+ this.option('global', {
25
+ description: g.f('Flag to indicate a global interceptor'),
26
+ required: false,
27
+ type: Boolean,
28
+ });
29
+
30
+ this.option('group', {
31
+ description: g.f('Group name for ordering the global interceptor'),
32
+ required: false,
33
+ type: String,
34
+ });
35
+
36
+ this.artifactInfo = {
37
+ type: 'interceptor',
38
+ rootDir: utils.sourceRootDir,
39
+ };
40
+
41
+ this.artifactInfo.outDir = path.resolve(
42
+ this.artifactInfo.rootDir,
43
+ utils.interceptorsDir,
44
+ );
45
+
46
+ this.artifactInfo.defaultTemplate = SCRIPT_TEMPLATE;
47
+
48
+ return super._setupGenerator();
49
+ }
50
+
51
+ setOptions() {
52
+ return super.setOptions();
53
+ }
54
+
55
+ checkLoopBackProject() {
56
+ if (this.shouldExit()) return;
57
+ return super.checkLoopBackProject();
58
+ }
59
+
60
+ /**
61
+ * Ask for Service Name
62
+ */
63
+ async promptArtifactName() {
64
+ debug('Prompting for interceptor name');
65
+ if (this.shouldExit()) return;
66
+
67
+ if (this.options.name) {
68
+ Object.assign(this.artifactInfo, {name: this.options.name});
69
+ }
70
+
71
+ const prompts = [
72
+ {
73
+ type: 'input',
74
+ name: 'name',
75
+ // capitalization
76
+ message: g.f('%s name:', utils.toClassName(this.artifactInfo.type)),
77
+ when: !this.artifactInfo.name,
78
+ validate: utils.validateClassName,
79
+ },
80
+ ];
81
+ return this.prompt(prompts).then(props => {
82
+ Object.assign(this.artifactInfo, props);
83
+ return props;
84
+ });
85
+ }
86
+
87
+ async promptGlobal() {
88
+ debug('Prompting for global interceptor flag');
89
+ if (this.shouldExit()) return;
90
+
91
+ if (this.options.global != null) {
92
+ Object.assign(this.artifactInfo, {isGlobal: !!this.options.global});
93
+ return;
94
+ }
95
+
96
+ // --group hints global
97
+ if (this.options.group != null) {
98
+ Object.assign(this.artifactInfo, {isGlobal: true});
99
+ return;
100
+ }
101
+
102
+ const prompts = [
103
+ {
104
+ type: 'confirm',
105
+ name: 'isGlobal',
106
+ message: g.f('Is it a global interceptor?'),
107
+ default: true,
108
+ },
109
+ ];
110
+ return this.prompt(prompts).then(props => {
111
+ Object.assign(this.artifactInfo, props);
112
+ return props;
113
+ });
114
+ }
115
+
116
+ async promptInterceptorGroup() {
117
+ if (!this.artifactInfo.isGlobal) return;
118
+ debug('Prompting for global interceptor group');
119
+ if (this.shouldExit()) return;
120
+
121
+ if (this.options.group) {
122
+ Object.assign(this.artifactInfo, {group: this.options.group});
123
+ return;
124
+ }
125
+
126
+ const note = g.f(
127
+ 'Global interceptors are sorted by the order of an array of' +
128
+ ' group names bound to ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS.' +
129
+ ' See https://loopback.io/doc/en/lb4/Interceptors.html#order-of-invocation-for-interceptors.',
130
+ );
131
+
132
+ this.log();
133
+ this.log(note);
134
+ this.log();
135
+
136
+ const prompts = [
137
+ {
138
+ type: 'input',
139
+ name: 'group',
140
+ // capitalization
141
+ message: g.f("Group name for the global interceptor: ('')"),
142
+ },
143
+ ];
144
+ return this.prompt(prompts).then(props => {
145
+ Object.assign(this.artifactInfo, props);
146
+ return props;
147
+ });
148
+ }
149
+
150
+ scaffold() {
151
+ if (this.shouldExit()) return false;
152
+
153
+ // Setting up data for templates
154
+ this.artifactInfo.className =
155
+ utils.toClassName(this.artifactInfo.name) + 'Interceptor';
156
+ this.artifactInfo.fileName = utils.toFileName(this.artifactInfo.name);
157
+
158
+ Object.assign(this.artifactInfo, {
159
+ outFile: utils.getInterceptorFileName(this.artifactInfo.name),
160
+ });
161
+
162
+ const source = this.templatePath(this.artifactInfo.defaultTemplate);
163
+
164
+ const dest = this.destinationPath(
165
+ path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
166
+ );
167
+
168
+ debug(`artifactInfo: ${inspect(this.artifactInfo)}`);
169
+ debug(`Copying artifact to: ${dest}`);
170
+
171
+ this.copyTemplatedFiles(source, dest, this.artifactInfo);
172
+ return;
173
+ }
174
+
175
+ async end() {
176
+ await super.end();
177
+ }
178
+ };