Skip to content

Commit 4c0ce80

Browse files
feat(cli): add lb4 model option to select base model class
close #1698
1 parent 1fe2a6e commit 4c0ce80

File tree

3 files changed

+169
-9
lines changed

3 files changed

+169
-9
lines changed

packages/cli/generators/model/index.js

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77

88
const ArtifactGenerator = require('../../lib/artifact-generator');
99
const debug = require('../../lib/debug')('model-generator');
10+
const inspect = require('util').inspect;
1011
const utils = require('../../lib/utils');
1112
const chalk = require('chalk');
1213
const path = require('path');
1314

15+
const PROMPT_BASE_MODEL_CLASS = 'Please select the model base class';
16+
const ERROR_NO_MODELS_FOUND = 'Model was not found in';
17+
const BASE_MODELS = ['Entity', 'Model'];
18+
const MODEL_TEMPLATE_PATH = 'model.ts.ejs';
19+
1420
/**
1521
* Model Generator
1622
*
@@ -54,6 +60,17 @@ module.exports = class ModelGenerator extends ArtifactGenerator {
5460
this.artifactInfo.properties = {};
5561
this.propCounter = 0;
5662

63+
this.artifactInfo.modelDir = path.resolve(
64+
this.artifactInfo.rootDir,
65+
utils.modelsDir,
66+
);
67+
68+
this.option('base', {
69+
type: String,
70+
required: false,
71+
description: 'A valid based model',
72+
});
73+
5774
return super._setupGenerator();
5875
}
5976

@@ -70,15 +87,82 @@ module.exports = class ModelGenerator extends ArtifactGenerator {
7087
if (this.shouldExit()) return;
7188
await super.promptArtifactName();
7289
this.artifactInfo.className = utils.toClassName(this.artifactInfo.name);
73-
this.log();
74-
this.log(
75-
`Let's add a property to ${chalk.yellow(this.artifactInfo.className)}`,
76-
);
90+
}
91+
92+
// Ask for Model base class
93+
async promptModelBaseClassName() {
94+
const availableModelBaseClasses = [];
95+
96+
availableModelBaseClasses.push(...BASE_MODELS);
97+
98+
try {
99+
debug(`model list dir ${this.artifactInfo.modelDir}`);
100+
const modelList = await utils.getArtifactList(
101+
this.artifactInfo.modelDir,
102+
'model',
103+
);
104+
debug(`modelist ${modelList}`);
105+
106+
if (modelList && modelList.length > 0) {
107+
availableModelBaseClasses.push(...modelList);
108+
debug(`availableModelBaseClasses ${availableModelBaseClasses}`);
109+
}
110+
} catch (err) {
111+
debug(`error ${err}`);
112+
return this.exit(err);
113+
}
114+
115+
if (
116+
this.options.base &&
117+
availableModelBaseClasses.includes(this.options.base)
118+
) {
119+
this.artifactInfo.modelBaseClass = utils.toClassName(this.options.base);
120+
} else {
121+
if (this.options.base) {
122+
// the model specified in the command line does not exists
123+
return this.exit(
124+
new Error(
125+
`${ERROR_NO_MODELS_FOUND} ${
126+
this.artifactInfo.modelDir
127+
}.${chalk.yellow(
128+
'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered',
129+
)}`,
130+
),
131+
);
132+
}
133+
}
134+
135+
return this.prompt([
136+
{
137+
type: 'list',
138+
name: 'modelBaseClass',
139+
message: PROMPT_BASE_MODEL_CLASS,
140+
choices: availableModelBaseClasses,
141+
when: !this.artifactInfo.modelBaseClass,
142+
default: availableModelBaseClasses[0],
143+
validate: utils.validateClassName,
144+
},
145+
])
146+
.then(props => {
147+
Object.assign(this.artifactInfo, props);
148+
debug(`props after model base class prompt: ${inspect(props)}`);
149+
this.log(
150+
`Let's add a property to ${chalk.yellow(
151+
this.artifactInfo.className,
152+
)}`,
153+
);
154+
return props;
155+
})
156+
.catch(err => {
157+
debug(`Error during model base class prompt: ${err}`);
158+
return this.exit(err);
159+
});
77160
}
78161

79162
// Prompt for a Property Name
80163
async promptPropertyName() {
81-
if (this.shouldExit()) return;
164+
if (this.shouldExit()) return false;
165+
82166
this.log(`Enter an empty property name when done`);
83167
this.log();
84168

@@ -200,7 +284,11 @@ module.exports = class ModelGenerator extends ArtifactGenerator {
200284
this.artifactInfo.outFile,
201285
);
202286

203-
const modelTemplatePath = this.templatePath('model.ts.ejs');
287+
this.artifactInfo.isModelBaseBuiltin = BASE_MODELS.includes(
288+
this.artifactInfo.modelBaseClass,
289+
)
290+
? true
291+
: false;
204292

205293
// Set up types for Templating
206294
const TS_TYPES = ['string', 'number', 'object', 'boolean', 'any'];
@@ -253,7 +341,11 @@ module.exports = class ModelGenerator extends ArtifactGenerator {
253341
}
254342
});
255343

256-
this.fs.copyTpl(modelTemplatePath, tsPath, this.artifactInfo);
344+
this.fs.copyTpl(
345+
this.templatePath(MODEL_TEMPLATE_PATH),
346+
tsPath,
347+
this.artifactInfo,
348+
);
257349
}
258350

259351
async end() {

packages/cli/generators/model/templates/model.ts.ejs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import {Entity, model, property} from '@loopback/repository';
1+
<% if (isModelBaseBuiltin) { -%>
2+
import {<%= modelBaseClass %>, model, property} from '@loopback/repository';
3+
<% } else { -%>
4+
import {model, property} from '@loopback/repository';
5+
import {<%= modelBaseClass %>} from '.';
6+
<% } -%>
27

38
@model()
4-
export class <%= className %> extends Entity {
9+
export class <%= className %> extends <%= modelBaseClass %> {
510
<% Object.entries(properties).forEach(([key, val]) => { -%>
611
@property({
712
<%_ Object.entries(val).forEach(([propKey, propVal]) => { -%>

packages/cli/test/integration/generators/model.integration.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ describe('lb4 model integration', () => {
6060
).to.be.rejectedWith(/No `loopback` keyword found in/);
6161
});
6262

63+
it('does not run if passed an invalid model from command line', () => {
64+
return expect(
65+
testUtils
66+
.executeGenerator(generator)
67+
.inDir(SANDBOX_PATH, () =>
68+
testUtils.givenLBProject(SANDBOX_PATH, {excludeKeyword: true}),
69+
)
70+
.withArguments('myNewModel --base InvalidModel'),
71+
).to.be.rejectedWith(/Model was not found in/);
72+
});
73+
6374
describe('model generator', () => {
6475
it('scaffolds correct files with input', async () => {
6576
await testUtils
@@ -73,6 +84,58 @@ describe('lb4 model integration', () => {
7384
basicModelFileChecks();
7485
});
7586

87+
it('scaffolds correct files with model base class', async () => {
88+
await testUtils
89+
.executeGenerator(generator)
90+
.inDir(SANDBOX_PATH, () => testUtils.givenLBProject(SANDBOX_PATH))
91+
.withPrompts({
92+
name: 'test',
93+
propName: null,
94+
modelBaseClass: 'Model',
95+
});
96+
97+
assert.file(expectedModelFile);
98+
assert.file(expectedIndexFile);
99+
100+
// Actual Model File
101+
assert.fileContent(
102+
expectedModelFile,
103+
/import {Model, model, property} from '@loopback\/repository';/,
104+
);
105+
assert.fileContent(expectedModelFile, /@model()/);
106+
assert.fileContent(
107+
expectedModelFile,
108+
/export class Test extends Model {/,
109+
);
110+
});
111+
112+
it('scaffolds correct files with model custom class', async () => {
113+
await testUtils
114+
.executeGenerator(generator)
115+
.inDir(SANDBOX_PATH, () =>
116+
testUtils.givenLBProject(SANDBOX_PATH, {includeDummyModel: true}),
117+
)
118+
.withPrompts({
119+
name: 'test',
120+
propName: null,
121+
modelBaseClass: 'ProductReview',
122+
});
123+
124+
assert.file(expectedModelFile);
125+
assert.file(expectedIndexFile);
126+
127+
// Actual Model File
128+
assert.fileContent(
129+
expectedModelFile,
130+
/import {model, property} from '@loopback\/repository';/,
131+
);
132+
assert.fileContent(expectedModelFile, /@model()/);
133+
assert.fileContent(
134+
expectedModelFile,
135+
/export class Test extends ProductReview {/,
136+
);
137+
});
138+
76139
it('scaffolds correct files with args', async () => {
77140
await testUtils
78141
.executeGenerator(generator)

0 commit comments

Comments
 (0)