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