Skip to content

Commit 0397c04

Browse files
feat(cli): add lb4 repository feature
close #1588
1 parent 5607b8e commit 0397c04

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright IBM Corp. 2017,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+
const _ = require('lodash');
8+
const ArtifactGenerator = require('../../lib/artifact-generator');
9+
const debug = require('../../lib/debug')('repository-generator');
10+
const inspect = require('util').inspect;
11+
const path = require('path');
12+
const chalk = require('chalk');
13+
const utils = require('../../lib/utils');
14+
const util = require('util');
15+
const fs = require('fs');
16+
const exists = util.promisify(fs.exists);
17+
18+
const SERVICE_VALUE_CONNECTOR = 'soap,rest';
19+
const KEY_VALUE_CONNECTOR = 'kv-';
20+
21+
const KEY_VALUE_REPOSITORY = 'KeyValueRepository';
22+
const DEFAULT_CRUD_REPOSITORY = 'DefaultCrudRepository';
23+
24+
const REPOSITORY_KV_TEMPLATE = 'repository-kv-template.ts.ejs';
25+
const REPOSITORY_CRUD_TEMPLATE = 'repository-crud-default-template.ts.ejs';
26+
27+
const PROMPT_MESSAGE_MODEL =
28+
'Select the model you want to generate a repository';
29+
const PROMPT_MESSAGE_DATA_SOURCE = 'Please select the datasource';
30+
const PROMPT_MESSAGE_ID_TYPE = 'What is the type of your ID?';
31+
32+
const ERROR_READING_FILE = 'Error reading file';
33+
const ERROR_NO_DATA_SOURCES_FOUND = 'No datasources found in';
34+
const ERROR_NO_MODELS_FOUND = 'No models found in';
35+
36+
module.exports = class RepositoryGenerator extends ArtifactGenerator {
37+
// Note: arguments and options should be defined in the constructor.
38+
constructor(args, opts) {
39+
super(args, opts);
40+
41+
/** instance helper method isolated from the execution loop
42+
* @connectorType: can be a single or a comma separated list
43+
*/
44+
this.isConnectorType = async function(connectorType, dataSourceClassName) {
45+
debug(`callling isConnectorType ${connectorType}`);
46+
let jsonFileContent = '';
47+
let result = false;
48+
49+
let datasourceJSONFile = path.join(
50+
'src',
51+
'datasources',
52+
dataSourceClassName
53+
.replace('Datasource', '.datasource.json')
54+
.toLowerCase(),
55+
);
56+
57+
try {
58+
const jsonFileExists = await exists(datasourceJSONFile);
59+
if (jsonFileExists) {
60+
jsonFileContent = this.fs.readJSON(datasourceJSONFile, {});
61+
}
62+
} catch (err) {
63+
debug(`${ERROR_READING_FILE} ${datasourceJSONFile}: ${err}`);
64+
return this.exit(err);
65+
}
66+
67+
let keyWordsToSearch = connectorType.split(',');
68+
for (let keyWord of keyWordsToSearch) {
69+
debug(`asking for keyword ${keyWord}`);
70+
if (jsonFileContent.connector.includes(keyWord)) {
71+
result = true;
72+
break;
73+
}
74+
}
75+
76+
return result;
77+
};
78+
}
79+
80+
_setupGenerator() {
81+
super._setupGenerator();
82+
83+
this.artifactInfo = {
84+
type: 'repository',
85+
rootDir: 'src',
86+
};
87+
this.artifactInfo.outDir = path.resolve(
88+
this.artifactInfo.rootDir,
89+
'repositories',
90+
);
91+
this.artifactInfo.datasourcesDir = path.resolve(
92+
this.artifactInfo.rootDir,
93+
'datasources',
94+
);
95+
this.artifactInfo.modelDir = path.resolve(
96+
this.artifactInfo.rootDir,
97+
'models',
98+
);
99+
}
100+
101+
setOptions() {
102+
return super.setOptions();
103+
}
104+
105+
checkLoopBackProject() {
106+
return super.checkLoopBackProject();
107+
}
108+
109+
async promptDataSource() {
110+
debug('Prompting for a datasource ');
111+
let datasourcesList;
112+
113+
try {
114+
datasourcesList = await utils.getArtifactList(
115+
this.artifactInfo.datasourcesDir,
116+
'datasource',
117+
true,
118+
);
119+
} catch (err) {
120+
return this.exit(err);
121+
}
122+
123+
// iterate over it to exclude service oriented data sources
124+
let tempDataSourceList = Object.assign(datasourcesList, {});
125+
for (let item of tempDataSourceList) {
126+
let result = await this.isConnectorType(SERVICE_VALUE_CONNECTOR, item);
127+
debug(`${item} has keyword ${SERVICE_VALUE_CONNECTOR} is ${result}`);
128+
if (result) {
129+
// remove from original list
130+
_.remove(datasourcesList, e => e == item);
131+
}
132+
}
133+
134+
if (_.isEmpty(datasourcesList)) {
135+
return this.exit(
136+
`${ERROR_NO_DATA_SOURCES_FOUND} ${this.artifactInfo.datasourcesDir}.
137+
${chalk.yellow(
138+
'Please visit http://loopback.io/doc/en/lb4/Controller-generator.html for information on how repositories are discovered',
139+
)}`,
140+
);
141+
}
142+
143+
return this.prompt([
144+
{
145+
type: 'list',
146+
name: 'dataSourceClassName',
147+
message: PROMPT_MESSAGE_DATA_SOURCE,
148+
choices: datasourcesList,
149+
when: this.artifactInfo.dataSourceClassName === undefined,
150+
default: datasourcesList[0],
151+
validate: utils.validateClassName,
152+
},
153+
])
154+
.then(props => {
155+
debug(`props: ${inspect(props)}`);
156+
Object.assign(this.artifactInfo, props);
157+
return props;
158+
})
159+
.catch(err => {
160+
debug(`Error during prompt for datasource name: ${err}`);
161+
return this.exit(err);
162+
});
163+
}
164+
165+
async inferRepositoryType() {
166+
let result = await this.isConnectorType(
167+
KEY_VALUE_CONNECTOR,
168+
this.artifactInfo.dataSourceClassName,
169+
);
170+
171+
if (result) {
172+
this.artifactInfo.repositoryTypeClass = KEY_VALUE_REPOSITORY;
173+
} else {
174+
this.artifactInfo.repositoryTypeClass = DEFAULT_CRUD_REPOSITORY;
175+
}
176+
177+
// assign the data source name to the information artifact
178+
let dataSourceName = this.artifactInfo.dataSourceClassName
179+
.replace('Datasource', '')
180+
.toLowerCase();
181+
182+
Object.assign(this.artifactInfo, {dataSourceName: dataSourceName});
183+
// parent async end() checks for name property, albeit we don't use it here
184+
Object.assign(this.artifactInfo, {name: dataSourceName});
185+
}
186+
187+
async promptModels() {
188+
let modelList;
189+
try {
190+
modelList = await utils.getArtifactList(
191+
this.artifactInfo.modelDir,
192+
'model',
193+
);
194+
} catch (err) {
195+
return this.exit(err);
196+
}
197+
198+
if (_.isEmpty(modelList)) {
199+
return this.exit(
200+
`${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}.
201+
${chalk.yellow(
202+
'Please visit http://loopback.io/doc/en/lb4/Repository-generator.html for information on how models are discovered',
203+
)}`,
204+
);
205+
}
206+
207+
return this.prompt([
208+
{
209+
type: 'list',
210+
name: 'modelName',
211+
message: PROMPT_MESSAGE_MODEL,
212+
choices: modelList,
213+
when: this.artifactInfo.modelName === undefined,
214+
default: modelList[0],
215+
validate: utils.validateClassName,
216+
},
217+
{
218+
type: 'list',
219+
name: 'idType',
220+
message: PROMPT_MESSAGE_ID_TYPE,
221+
choices: ['number', 'string', 'object'],
222+
when: this.artifactInfo.idType === undefined,
223+
default: 'number',
224+
},
225+
])
226+
.then(props => {
227+
debug(`props: ${inspect(props)}`);
228+
Object.assign(this.artifactInfo, props);
229+
return props;
230+
})
231+
.catch(err => {
232+
debug(`Error during prompt for repository variables: ${err}`);
233+
return this.exit(err);
234+
});
235+
}
236+
237+
scaffold() {
238+
// We don't want to call the base scaffold function since it copies
239+
// all of the templates!
240+
if (this.shouldExit()) return false;
241+
242+
this.artifactInfo.className = utils.toClassName(this.artifactInfo.name);
243+
244+
this.artifactInfo.outFile =
245+
utils.kebabCase(this.artifactInfo.modelName) + '.repository.ts';
246+
if (debug.enabled) {
247+
debug(`Artifact output filename set to: ${this.artifactInfo.outFile}`);
248+
}
249+
250+
let template = '';
251+
252+
/* place a switch statement for future repository types */
253+
switch (this.artifactInfo.repositoryTypeClass) {
254+
case KEY_VALUE_REPOSITORY:
255+
template = REPOSITORY_KV_TEMPLATE;
256+
break;
257+
default:
258+
template = REPOSITORY_CRUD_TEMPLATE;
259+
}
260+
261+
const source = this.templatePath(
262+
path.join('src', 'repositories', template),
263+
);
264+
if (debug.enabled) {
265+
debug(`Using template at: ${source}`);
266+
}
267+
const dest = this.destinationPath(
268+
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
269+
);
270+
271+
if (debug.enabled) {
272+
debug(`artifactInfo: ${inspect(this.artifactInfo)}`);
273+
debug(`Copying artifact to: ${dest}`);
274+
}
275+
this.fs.copyTpl(
276+
source,
277+
dest,
278+
this.artifactInfo,
279+
{},
280+
{globOptions: {dot: true}},
281+
);
282+
return;
283+
}
284+
285+
async end() {
286+
await super.end();
287+
}
288+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {<%= repositoryTypeClass %>}, juggler} from '@loopback/repository';
2+
import {<%= modelName %>} from '../models';
3+
import {inject} from '@loopback/core';
4+
5+
export class <%= className %>Repository extends <%= repositoryTypeClass %><
6+
<%= modelName %>, <%= idType %>> {
7+
constructor(
8+
@inject('datasources.<%= dataSourceName %>') protected datasource: juggler.DataSource,
9+
) {
10+
super(<%= modelName %>, datasource);
11+
}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {<%= repositoryTypeClass %>}} from '@loopback/repository';
2+
import {<%= modelName %>} from '../models';
3+
import {inject} from '@loopback/core';
4+
5+
export class <%= className %>Repository extends <%= repositoryTypeClass %><<%= modelName %>> {
6+
constructor(
7+
@inject('datasources.<%= dataSourceName %>') protected datasource: juggler.DataSource,
8+
) {
9+
super(<%= modelName %>,datasource);
10+
}
11+
}

packages/cli/lib/cli.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ function setupGenerators() {
6363
PREFIX + 'datasource',
6464
);
6565
env.register(path.join(__dirname, '../generators/model'), PREFIX + 'model');
66+
env.register(
67+
path.join(__dirname, '../generators/repository'),
68+
PREFIX + 'repository',
69+
);
6670
env.register(
6771
path.join(__dirname, '../generators/example'),
6872
PREFIX + 'example',

0 commit comments

Comments
 (0)