@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.
- package/.yo-rc.json +1719 -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 +411 -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 +49 -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 +423 -0
- package/generators/rest-crud/templates/src/model-endpoints/model.rest-config-template.ts.ejs +11 -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 +137 -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 -75
- package/bin/cli.js +0 -66
- package/generators/.DS_Store +0 -0
- 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
package/lib/utils.js
CHANGED
|
@@ -1,35 +1,68 @@
|
|
|
1
|
-
// Copyright IBM Corp. 2017. All Rights Reserved.
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
|
|
2
2
|
// Node module: @loopback/cli
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const debug = require('../lib/debug')('utils');
|
|
8
10
|
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
9
12
|
const util = require('util');
|
|
13
|
+
const stream = require('stream');
|
|
14
|
+
const {spawnSync} = require('child_process');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const semver = require('semver');
|
|
10
17
|
const regenerate = require('regenerate');
|
|
11
18
|
const _ = require('lodash');
|
|
19
|
+
const pascalCase = require('change-case').pascalCase;
|
|
20
|
+
const lowerCase = require('change-case').lowerCase;
|
|
21
|
+
const promisify = require('util').promisify;
|
|
22
|
+
const toVarName = require('change-case').camelCase;
|
|
23
|
+
const pluralize = require('pluralize');
|
|
24
|
+
const urlSlug = require('url-slug');
|
|
25
|
+
const validate = require('validate-npm-package-name');
|
|
12
26
|
const Conflicter = require('yeoman-generator/lib/util/conflicter');
|
|
27
|
+
const connectors = require('./connectors.json');
|
|
28
|
+
const tsquery = require('./ast-helper');
|
|
29
|
+
const stringifyObject = require('stringify-object');
|
|
30
|
+
const camelCase = _.camelCase;
|
|
31
|
+
const kebabCase = _.kebabCase;
|
|
32
|
+
const untildify = require('untildify');
|
|
33
|
+
const tildify = require('tildify');
|
|
34
|
+
const readdirAsync = promisify(fs.readdir);
|
|
35
|
+
const toFileName = name => {
|
|
36
|
+
return kebabCase(name).replace(/\-(\d+)$/g, '$1');
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const RESERVED_PROPERTY_NAMES = ['constructor'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Either a reference to util.promisify or its polyfill, depending on
|
|
43
|
+
* your version of Node.
|
|
44
|
+
*/
|
|
45
|
+
exports.promisify = promisify;
|
|
13
46
|
|
|
14
47
|
/**
|
|
15
48
|
* Returns a valid variable name regex;
|
|
16
49
|
* taken from https://gist.github.com/mathiasbynens/6334847
|
|
17
50
|
*/
|
|
18
51
|
function generateValidRegex() {
|
|
19
|
-
const get = function(what) {
|
|
52
|
+
const get = function (what) {
|
|
20
53
|
return require('unicode-10.0.0/' + what + '/code-points.js');
|
|
21
54
|
};
|
|
22
|
-
const
|
|
23
|
-
const
|
|
55
|
+
const idStart = get('Binary_Property/ID_Start');
|
|
56
|
+
const idContinue = get('Binary_Property/ID_Continue');
|
|
24
57
|
const compileRegex = _.template(
|
|
25
|
-
'^(?:<%= identifierStart %>)(?:<%= identifierPart %>)*$'
|
|
58
|
+
'^(?:<%= identifierStart %>)(?:<%= identifierPart %>)*$',
|
|
26
59
|
);
|
|
27
|
-
const identifierStart = regenerate(
|
|
28
|
-
const identifierPart = regenerate(
|
|
60
|
+
const identifierStart = regenerate(idStart).add('$', '_');
|
|
61
|
+
const identifierPart = regenerate(idContinue).add(
|
|
29
62
|
'$',
|
|
30
63
|
'_',
|
|
31
64
|
'\u200C',
|
|
32
|
-
'\u200D'
|
|
65
|
+
'\u200D',
|
|
33
66
|
);
|
|
34
67
|
const regex = compileRegex({
|
|
35
68
|
identifierStart: identifierStart.toString(),
|
|
@@ -45,7 +78,7 @@ exports.validRegex = validRegex;
|
|
|
45
78
|
* @param name
|
|
46
79
|
* @returns {String|Boolean}
|
|
47
80
|
*/
|
|
48
|
-
exports.validateClassName = function(name) {
|
|
81
|
+
exports.validateClassName = function (name) {
|
|
49
82
|
if (!name || name === '') {
|
|
50
83
|
return 'Class name cannot be empty';
|
|
51
84
|
}
|
|
@@ -67,18 +100,145 @@ exports.validateClassName = function(name) {
|
|
|
67
100
|
if (name.match(/[\/@\s\+%:]/)) {
|
|
68
101
|
return util.format(
|
|
69
102
|
'Class name cannot contain special characters (/@+%: ): %s',
|
|
70
|
-
name
|
|
103
|
+
name,
|
|
71
104
|
);
|
|
72
105
|
}
|
|
73
106
|
return util.format('Class name is invalid: %s', name);
|
|
74
107
|
};
|
|
75
108
|
|
|
109
|
+
exports.logNamingIssues = function (name, log) {
|
|
110
|
+
if (name.includes('_')) {
|
|
111
|
+
log(
|
|
112
|
+
chalk.red('>>> ') +
|
|
113
|
+
`Underscores _ in the class name will get removed: ${name}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (name.match(/[\u00C0-\u024F\u1E00-\u1EFF]/)) {
|
|
117
|
+
log(
|
|
118
|
+
chalk.red('>>> ') +
|
|
119
|
+
`Accented chars in the class name will get replaced: ${name}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
exports.logClassCreation = function (type, typePlural, name, log) {
|
|
125
|
+
log(
|
|
126
|
+
`${exports.toClassName(type)} ${chalk.yellow(
|
|
127
|
+
name,
|
|
128
|
+
)} will be created in src/${typePlural}/${chalk.yellow(
|
|
129
|
+
exports.toFileName(name) + '.' + `${type}.ts`,
|
|
130
|
+
)}`,
|
|
131
|
+
);
|
|
132
|
+
log();
|
|
133
|
+
};
|
|
134
|
+
|
|
76
135
|
/**
|
|
77
136
|
* Validate project directory to not exist
|
|
78
137
|
*/
|
|
79
|
-
exports.
|
|
80
|
-
if (fs.existsSync(
|
|
81
|
-
return util.format('Directory %s already exists.',
|
|
138
|
+
exports.validateNotExisting = function (projDir) {
|
|
139
|
+
if (fs.existsSync(projDir)) {
|
|
140
|
+
return util.format('Directory %s already exists.', projDir);
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* validate source key or foreign key for relations
|
|
147
|
+
*/
|
|
148
|
+
/* istanbul ignore next */
|
|
149
|
+
exports.validateKeyName = function (name) {
|
|
150
|
+
if (!name || name === '') {
|
|
151
|
+
return 'Key name cannot be empty';
|
|
152
|
+
}
|
|
153
|
+
if (!isNaN(name.charAt(0))) {
|
|
154
|
+
return util.format('Key name cannot start with a number: %s', name);
|
|
155
|
+
}
|
|
156
|
+
if (name.includes('.')) {
|
|
157
|
+
return util.format('Key name cannot contain .: %s', name);
|
|
158
|
+
}
|
|
159
|
+
if (name.includes(' ')) {
|
|
160
|
+
return util.format('Key name cannot contain spaces: %s', name);
|
|
161
|
+
}
|
|
162
|
+
if (name.includes('-')) {
|
|
163
|
+
return util.format('Key name cannot contain hyphens: %s', name);
|
|
164
|
+
}
|
|
165
|
+
if (name.match(/[\/@\s\+%:]/)) {
|
|
166
|
+
return util.format(
|
|
167
|
+
'Key name cannot contain special characters (/@+%: ): %s',
|
|
168
|
+
name,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* validate if the input name is valid. The input name cannot be the same as
|
|
176
|
+
* comparedTo.
|
|
177
|
+
*/
|
|
178
|
+
/* istanbul ignore next */
|
|
179
|
+
exports.validateKeyToKeyFrom = function (input, comparedTo) {
|
|
180
|
+
if (!input || input === '') {
|
|
181
|
+
return 'Key name cannot be empty';
|
|
182
|
+
}
|
|
183
|
+
if (input === comparedTo) {
|
|
184
|
+
return util.format(
|
|
185
|
+
'Through model cannot have two identical foreign keys: %s',
|
|
186
|
+
input,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (!isNaN(input.charAt(0))) {
|
|
190
|
+
return util.format('Key name cannot start with a number: %s', input);
|
|
191
|
+
}
|
|
192
|
+
if (input.includes('.')) {
|
|
193
|
+
return util.format('Key name cannot contain .: %s', input);
|
|
194
|
+
}
|
|
195
|
+
if (input.includes(' ')) {
|
|
196
|
+
return util.format('Key name cannot contain spaces: %s', input);
|
|
197
|
+
}
|
|
198
|
+
if (input.includes('-')) {
|
|
199
|
+
return util.format('Key name cannot contain hyphens: %s', input);
|
|
200
|
+
}
|
|
201
|
+
if (input.match(/[\/@\s\+%:]/)) {
|
|
202
|
+
return util.format(
|
|
203
|
+
'Key name cannot contain special characters (/@+%: ): %s',
|
|
204
|
+
input,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* checks if the belongsTo relation has the same relation name and source key name,
|
|
212
|
+
* which is an invalid case.
|
|
213
|
+
*/
|
|
214
|
+
/* istanbul ignore next */
|
|
215
|
+
exports.validateRelationName = function (name, type, foreignKeyName) {
|
|
216
|
+
if (!name || name === '') {
|
|
217
|
+
return 'Relation name cannot be empty';
|
|
218
|
+
}
|
|
219
|
+
if (type === 'belongsTo' && name === foreignKeyName) {
|
|
220
|
+
return util.format(
|
|
221
|
+
'Relation name cannot be the same as the source key name: %s',
|
|
222
|
+
name,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (!isNaN(name.charAt(0))) {
|
|
226
|
+
return util.format('Relation name cannot start with a number: %s', name);
|
|
227
|
+
}
|
|
228
|
+
if (name.includes('.')) {
|
|
229
|
+
return util.format('Relation name cannot contain .: %s', name);
|
|
230
|
+
}
|
|
231
|
+
if (name.includes(' ')) {
|
|
232
|
+
return util.format('Relation name cannot contain spaces: %s', name);
|
|
233
|
+
}
|
|
234
|
+
if (name.includes('-')) {
|
|
235
|
+
return util.format('Relation name cannot contain hyphens: %s', name);
|
|
236
|
+
}
|
|
237
|
+
if (name.match(/[\/@\s\+%:]/)) {
|
|
238
|
+
return util.format(
|
|
239
|
+
'Relation name cannot contain special characters (/@+%: ): %s',
|
|
240
|
+
name,
|
|
241
|
+
);
|
|
82
242
|
}
|
|
83
243
|
return true;
|
|
84
244
|
};
|
|
@@ -86,13 +246,55 @@ exports.validateyNotExisting = function(path) {
|
|
|
86
246
|
/**
|
|
87
247
|
* Converts a name to class name after validation
|
|
88
248
|
*/
|
|
89
|
-
exports.toClassName = function(name) {
|
|
90
|
-
if (name
|
|
249
|
+
exports.toClassName = function (name) {
|
|
250
|
+
if (name === '') return new Error('no input');
|
|
91
251
|
if (typeof name != 'string' || name == null) return new Error('bad input');
|
|
92
|
-
return
|
|
252
|
+
return pascalCase(camelCase(name));
|
|
93
253
|
};
|
|
94
254
|
|
|
95
|
-
exports.
|
|
255
|
+
exports.lowerCase = lowerCase;
|
|
256
|
+
exports.toFileName = toFileName;
|
|
257
|
+
exports.pascalCase = pascalCase;
|
|
258
|
+
exports.camelCase = camelCase;
|
|
259
|
+
exports.toVarName = toVarName;
|
|
260
|
+
exports.pluralize = pluralize;
|
|
261
|
+
exports.urlSlug = urlSlug;
|
|
262
|
+
exports.untildify = untildify;
|
|
263
|
+
exports.tildify = tildify;
|
|
264
|
+
|
|
265
|
+
exports.validate = function (name) {
|
|
266
|
+
const isValid = validate(name).validForNewPackages;
|
|
267
|
+
if (!isValid) return 'Invalid npm package name: ' + name;
|
|
268
|
+
return isValid;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Adds a backslash to the start of the word if not already present
|
|
273
|
+
* @param {string} httpPath
|
|
274
|
+
*/
|
|
275
|
+
exports.prependBackslash = httpPath => httpPath.replace(/^\/?/, '/');
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Validates whether a given string is a valid url slug or not.
|
|
279
|
+
* Allows slugs with backslash in front of them to be validated as well
|
|
280
|
+
* @param {string} name Slug to validate
|
|
281
|
+
*/
|
|
282
|
+
exports.validateUrlSlug = function (name) {
|
|
283
|
+
const backslashIfNeeded = name.charAt(0) === '/' ? '/' : '';
|
|
284
|
+
if (backslashIfNeeded === '/') {
|
|
285
|
+
name = name.slice(1);
|
|
286
|
+
}
|
|
287
|
+
const separators = ['-', '.', '_', '~', ''];
|
|
288
|
+
const possibleSlugs = separators.map(separator =>
|
|
289
|
+
urlSlug(name, {
|
|
290
|
+
separator,
|
|
291
|
+
transformer: false,
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
if (!possibleSlugs.includes(name))
|
|
295
|
+
return `Invalid url slug. Suggested slug: ${backslashIfNeeded}${possibleSlugs[0]}`;
|
|
296
|
+
return true;
|
|
297
|
+
};
|
|
96
298
|
|
|
97
299
|
/**
|
|
98
300
|
* Extends conflicter so that it keeps track of conflict status
|
|
@@ -104,10 +306,477 @@ exports.StatusConflicter = class StatusConflicter extends Conflicter {
|
|
|
104
306
|
}
|
|
105
307
|
|
|
106
308
|
checkForCollision(filepath, contents, callback) {
|
|
107
|
-
|
|
108
|
-
|
|
309
|
+
// https://github.com/yeoman/generator/pull/1210
|
|
310
|
+
let options = filepath;
|
|
311
|
+
let cb = callback;
|
|
312
|
+
if (typeof contents === 'function') {
|
|
313
|
+
// The signature is `checkForCollision(options, cb)`
|
|
314
|
+
cb = contents;
|
|
315
|
+
} else {
|
|
316
|
+
// The signature is `checkForCollision(filepath, contents, cb)`
|
|
317
|
+
options = {path: filepath, contents};
|
|
318
|
+
}
|
|
319
|
+
super.checkForCollision(options, (err, status) => {
|
|
320
|
+
const filename = options.path.split('/').pop();
|
|
109
321
|
this.generationStatus[filename] = status;
|
|
110
|
-
|
|
322
|
+
cb(err, status);
|
|
111
323
|
});
|
|
112
324
|
}
|
|
113
325
|
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Find all artifacts in the given path whose type matches the provided
|
|
329
|
+
* filetype.
|
|
330
|
+
* For example, a fileType of "model" will search the target path for matches to
|
|
331
|
+
* "*.model.js"
|
|
332
|
+
* @param {string} path The directory path to search. This search is *not*
|
|
333
|
+
* recursive.
|
|
334
|
+
* @param {string} artifactType The type of the artifact in string form.
|
|
335
|
+
* @param {Function=} reader An optional reader function to retrieve the
|
|
336
|
+
* paths. Must return a Promise.
|
|
337
|
+
* @returns {Promise<string[]>} The filtered list of paths.
|
|
338
|
+
*/
|
|
339
|
+
exports.findArtifactPaths = async function (dir, artifactType, reader) {
|
|
340
|
+
const readdir = reader || readdirAsync;
|
|
341
|
+
debug('Finding %j artifact paths at %s', artifactType, dir);
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
// Wrapping readdir in case it's not a promise.
|
|
345
|
+
const files = await readdir(dir);
|
|
346
|
+
return files.filter(
|
|
347
|
+
f =>
|
|
348
|
+
_.endsWith(f, `${artifactType}.js`) ||
|
|
349
|
+
_.endsWith(f, `${artifactType}.ts`),
|
|
350
|
+
);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
if (err.code === 'ENOENT') {
|
|
353
|
+
// Target directory was not found (e.g. "src/models" does not exist yet).
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* Parses the files of the target directory and returns matching JavaScript
|
|
361
|
+
* or TypeScript artifact files. NOTE: This function does not examine the
|
|
362
|
+
* contents of these files!
|
|
363
|
+
* @param {string} dir The target directory from which to load artifacts.
|
|
364
|
+
* @param {string} artifactType The artifact type (ex. "model", "repository")
|
|
365
|
+
* @param {boolean} addSuffix Whether or not to append the artifact type to the
|
|
366
|
+
* results. (ex. [Foo,Bar] -> [FooRepository, BarRepository])
|
|
367
|
+
* @param {Function} reader An alternate function to replace the promisified
|
|
368
|
+
* fs.readdir (useful for testing and for custom overrides).
|
|
369
|
+
*/
|
|
370
|
+
exports.getArtifactList = async function (
|
|
371
|
+
dir,
|
|
372
|
+
artifactType,
|
|
373
|
+
addSuffix,
|
|
374
|
+
reader,
|
|
375
|
+
) {
|
|
376
|
+
const paths = await exports.findArtifactPaths(dir, artifactType, reader);
|
|
377
|
+
debug('Artifacts paths found:', paths);
|
|
378
|
+
return paths.map(p => {
|
|
379
|
+
const firstWord = _.first(_.split(_.last(_.split(p, path.sep)), '.'));
|
|
380
|
+
const result = pascalCase(exports.toClassName(firstWord));
|
|
381
|
+
return addSuffix
|
|
382
|
+
? exports.toClassName(result) + exports.toClassName(artifactType)
|
|
383
|
+
: exports.toClassName(result);
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Check package.json and dependencies.json to find out versions for generated
|
|
389
|
+
* dependencies
|
|
390
|
+
*/
|
|
391
|
+
exports.getDependencies = function () {
|
|
392
|
+
const pkg = require('../package.json');
|
|
393
|
+
let version = pkg.version;
|
|
394
|
+
// First look for config.loopbackVersion
|
|
395
|
+
if (pkg.config && pkg.config.loopbackVersion) {
|
|
396
|
+
version = pkg.config.loopbackVersion;
|
|
397
|
+
}
|
|
398
|
+
// Set it to be `^x.y.0`
|
|
399
|
+
const loopbackVersion =
|
|
400
|
+
'^' + semver.major(version) + '.' + semver.minor(version) + '.0';
|
|
401
|
+
|
|
402
|
+
const deps = {};
|
|
403
|
+
const dependencies = (pkg.config && pkg.config.templateDependencies) || {};
|
|
404
|
+
for (const i in dependencies) {
|
|
405
|
+
// Default to loopback version if the version for a given dependency is ""
|
|
406
|
+
deps[i] = dependencies[i] || loopbackVersion;
|
|
407
|
+
}
|
|
408
|
+
return deps;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Rename EJS files
|
|
413
|
+
*/
|
|
414
|
+
exports.renameEJS = function () {
|
|
415
|
+
const renameStream = new stream.Transform({objectMode: true});
|
|
416
|
+
|
|
417
|
+
renameStream._transform = function (file, enc, callback) {
|
|
418
|
+
const filePath = file.relative;
|
|
419
|
+
const dirname = path.dirname(filePath);
|
|
420
|
+
let extname = path.extname(filePath);
|
|
421
|
+
let basename = path.basename(filePath, extname);
|
|
422
|
+
|
|
423
|
+
// extname already contains a leading '.'
|
|
424
|
+
const fileName = `${basename}${extname}`;
|
|
425
|
+
const result = fileName.match(/(.+)(.ts|.json|.js|.md|.html)\.ejs$/);
|
|
426
|
+
if (result) {
|
|
427
|
+
extname = result[2];
|
|
428
|
+
basename = result[1];
|
|
429
|
+
file.path = path.join(file.base, dirname, basename + extname);
|
|
430
|
+
}
|
|
431
|
+
callback(null, file);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
return renameStream;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get a validate function for object/array type
|
|
439
|
+
* @param {String} type 'object' OR 'array'
|
|
440
|
+
*/
|
|
441
|
+
exports.validateStringObject = function (type) {
|
|
442
|
+
return function validateStringified(val) {
|
|
443
|
+
if (val === null || val === '') {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const err = `The value must be a stringified ${type}`;
|
|
448
|
+
|
|
449
|
+
if (typeof val !== 'string') {
|
|
450
|
+
return err;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
const result = JSON.parse(val);
|
|
455
|
+
if (type === 'array' && !Array.isArray(result)) {
|
|
456
|
+
return err;
|
|
457
|
+
}
|
|
458
|
+
} catch (e) {
|
|
459
|
+
return err;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return true;
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Use readline to read text from stdin
|
|
468
|
+
*/
|
|
469
|
+
exports.readTextFromStdin = function () {
|
|
470
|
+
const rl = readline.createInterface({
|
|
471
|
+
input: process.stdin,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const lines = [];
|
|
475
|
+
let err;
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
rl.on('SIGINT', () => {
|
|
478
|
+
err = new Error('Canceled by user');
|
|
479
|
+
rl.close();
|
|
480
|
+
})
|
|
481
|
+
.on('line', line => {
|
|
482
|
+
if (line === 'EOF') {
|
|
483
|
+
rl.close();
|
|
484
|
+
} else {
|
|
485
|
+
lines.push(line);
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
.on('close', () => {
|
|
489
|
+
if (err) reject(err);
|
|
490
|
+
else resolve(lines.join('\n'));
|
|
491
|
+
})
|
|
492
|
+
.on('error', e => {
|
|
493
|
+
err = e;
|
|
494
|
+
rl.close();
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
/*
|
|
500
|
+
* Validate property name
|
|
501
|
+
* @param {String} name The user input
|
|
502
|
+
* @returns {String|Boolean}
|
|
503
|
+
*/
|
|
504
|
+
exports.checkPropertyName = function (name) {
|
|
505
|
+
const result = exports.validateRequiredName(name);
|
|
506
|
+
if (result !== true) return result;
|
|
507
|
+
if (RESERVED_PROPERTY_NAMES.includes(name)) {
|
|
508
|
+
return `${name} is a reserved keyword. Please use another name`;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Validate required name for properties, data sources, or connectors
|
|
515
|
+
* Empty name could not pass
|
|
516
|
+
* @param {String} name The user input
|
|
517
|
+
* @returns {String|Boolean}
|
|
518
|
+
*/
|
|
519
|
+
exports.validateRequiredName = function (name) {
|
|
520
|
+
if (!name) {
|
|
521
|
+
return 'Name is required';
|
|
522
|
+
}
|
|
523
|
+
return validateValue(name, /[\/@\s\+%:\.]/);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
function validateValue(name, unallowedCharacters) {
|
|
527
|
+
if (!unallowedCharacters) {
|
|
528
|
+
unallowedCharacters = /[\/@\s\+%:\.]/;
|
|
529
|
+
}
|
|
530
|
+
if (name.match(unallowedCharacters)) {
|
|
531
|
+
return `Name cannot contain special characters ${unallowedCharacters}: ${name}`;
|
|
532
|
+
}
|
|
533
|
+
if (name !== encodeURIComponent(name)) {
|
|
534
|
+
return `Name cannot contain special characters escaped by encodeURIComponent: ${name}`;
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Returns the modelName in the directory file format for the model
|
|
540
|
+
* @param {string} modelName
|
|
541
|
+
*/
|
|
542
|
+
exports.getModelFileName = function (modelName) {
|
|
543
|
+
return `${toFileName(modelName)}.model.ts`;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Returns the repositoryName in the directory file format for the repository
|
|
548
|
+
* @param {string} repositoryName
|
|
549
|
+
*/
|
|
550
|
+
exports.getRepositoryFileName = function (repositoryName) {
|
|
551
|
+
return `${toFileName(repositoryName)}.repository.ts`;
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Returns the rest-config in the directory file format for the model endpoint
|
|
556
|
+
* @param {string} modelName
|
|
557
|
+
*/
|
|
558
|
+
exports.getRestConfigFileName = function (modelName) {
|
|
559
|
+
return `${toFileName(modelName)}.rest-config.ts`;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Returns the serviceName in the directory file format for the service
|
|
564
|
+
* @param {string} serviceName
|
|
565
|
+
*/
|
|
566
|
+
exports.getServiceFileName = function (serviceName) {
|
|
567
|
+
return `${toFileName(serviceName)}.service.ts`;
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Returns the observerName in the directory file format for the observer
|
|
572
|
+
* @param {string} observerName
|
|
573
|
+
*/
|
|
574
|
+
exports.getObserverFileName = function (observerName) {
|
|
575
|
+
return `${toFileName(observerName)}.observer.ts`;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Returns the interceptorName in the directory file format for the interceptor
|
|
580
|
+
* @param {string} interceptorName
|
|
581
|
+
*/
|
|
582
|
+
exports.getInterceptorFileName = function (interceptorName) {
|
|
583
|
+
return `${toFileName(interceptorName)}.interceptor.ts`;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
*
|
|
588
|
+
* Returns the connector property for the datasource file
|
|
589
|
+
* @param {string} datasourcesDir path for sources
|
|
590
|
+
* @param {string} dataSourceClass class name for the datasoure
|
|
591
|
+
*/
|
|
592
|
+
exports.getDataSourceConnectorName = function (
|
|
593
|
+
datasourcesDir,
|
|
594
|
+
dataSourceClass,
|
|
595
|
+
) {
|
|
596
|
+
if (!dataSourceClass) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
const config = exports.getDataSourceConfig(datasourcesDir, dataSourceClass);
|
|
600
|
+
return config.connector;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
*
|
|
605
|
+
* load the connectors available and check if the basedModel matches any
|
|
606
|
+
* connectorType supplied for the given connector name
|
|
607
|
+
* @param {string} connectorType single or a comma separated string array
|
|
608
|
+
* @param {string} datasourcesDir path for sources
|
|
609
|
+
* @param {string} dataSourceClass class name for the datasoure
|
|
610
|
+
*/
|
|
611
|
+
exports.isConnectorOfType = function (
|
|
612
|
+
connectorType,
|
|
613
|
+
datasourcesDir,
|
|
614
|
+
dataSourceClass,
|
|
615
|
+
) {
|
|
616
|
+
debug('calling isConnectorType %o for %s', connectorType, dataSourceClass);
|
|
617
|
+
|
|
618
|
+
if (!dataSourceClass) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const config = exports.getDataSourceConfig(datasourcesDir, dataSourceClass);
|
|
623
|
+
|
|
624
|
+
for (const connector of Object.values(connectors)) {
|
|
625
|
+
const matchedConnector =
|
|
626
|
+
config.connector === connector.name ||
|
|
627
|
+
config.connector === `loopback-connector-${connector.name}`;
|
|
628
|
+
|
|
629
|
+
if (matchedConnector) return connectorType.includes(connector.baseModel);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Maybe for other connectors that are not in the supported list
|
|
633
|
+
return null;
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Load the datasource configuration. Supports both the current TypeScript-based
|
|
638
|
+
* flavor and legacy JSON-based configuration.
|
|
639
|
+
* @param {string} datasourcesDir path for sources
|
|
640
|
+
* @param {string} dataSourceClass class name for the datasource
|
|
641
|
+
*/
|
|
642
|
+
exports.getDataSourceConfig = function getDataSourceConfig(
|
|
643
|
+
datasourcesDir,
|
|
644
|
+
dataSourceClass,
|
|
645
|
+
) {
|
|
646
|
+
const config =
|
|
647
|
+
readDataSourceConfigFromTypeScript(datasourcesDir, dataSourceClass) ||
|
|
648
|
+
// Support legacy JSON-based configuration.
|
|
649
|
+
// TODO(semver-major) Print a deprecation warning for JSON-based datasources
|
|
650
|
+
// or stop supporting them entirely.
|
|
651
|
+
readDataSourceConfigFromJSON(datasourcesDir, dataSourceClass);
|
|
652
|
+
|
|
653
|
+
debug('datasource %s has config %o', dataSourceClass, config);
|
|
654
|
+
return config;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
function readDataSourceConfigFromTypeScript(datasourcesDir, dataSourceClass) {
|
|
658
|
+
const srcFile = path.join(
|
|
659
|
+
datasourcesDir,
|
|
660
|
+
exports.dataSourceToArtifactFileName(dataSourceClass),
|
|
661
|
+
);
|
|
662
|
+
debug(
|
|
663
|
+
'Reading datasource config for class %s from %s',
|
|
664
|
+
dataSourceClass,
|
|
665
|
+
srcFile,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
const fileContent = fs.readFileSync(srcFile, 'utf-8');
|
|
669
|
+
return tsquery.getDataSourceConfig(fileContent);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function readDataSourceConfigFromJSON(datasourcesDir, dataSourceClass) {
|
|
673
|
+
const datasourceJSONFile = path.join(
|
|
674
|
+
datasourcesDir,
|
|
675
|
+
exports.dataSourceToJSONFileName(dataSourceClass),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
debug(`Reading datasource config from JSON file ${datasourceJSONFile}`);
|
|
679
|
+
try {
|
|
680
|
+
return JSON.parse(fs.readFileSync(datasourceJSONFile, 'utf8'));
|
|
681
|
+
} catch (err) {
|
|
682
|
+
err.message = `Cannot load ${datasourceJSONFile}: ${err.message}`;
|
|
683
|
+
throw err;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
*
|
|
689
|
+
* returns the name property inside the datasource json file
|
|
690
|
+
* @param {string} datasourcesDir path for sources
|
|
691
|
+
* @param {string} dataSourceClass class name for the datasoure
|
|
692
|
+
*/
|
|
693
|
+
exports.getDataSourceName = function (datasourcesDir, dataSourceClass) {
|
|
694
|
+
if (!dataSourceClass) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
const config = exports.getDataSourceConfig(datasourcesDir, dataSourceClass);
|
|
698
|
+
return config.name;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
exports.dataSourceToJSONFileName = function (dataSourceClass) {
|
|
702
|
+
return path.join(
|
|
703
|
+
toFileName(dataSourceClass.replace('Datasource', '')) +
|
|
704
|
+
'.datasource.config.json',
|
|
705
|
+
);
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
exports.dataSourceToArtifactFileName = function (dataSourceClass) {
|
|
709
|
+
return (
|
|
710
|
+
toFileName(dataSourceClass.replace('Datasource', '')) + '.datasource.ts'
|
|
711
|
+
);
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
exports.stringifyObject = function (data, options = {}) {
|
|
715
|
+
return stringifyObject(data, {
|
|
716
|
+
indent: ' ', // two spaces
|
|
717
|
+
singleQuotes: true,
|
|
718
|
+
inlineCharacterLimit: 80,
|
|
719
|
+
...options,
|
|
720
|
+
});
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
exports.stringifyModelSettings = function (modelSettings) {
|
|
724
|
+
if (!modelSettings || !Object.keys(modelSettings).length) return '';
|
|
725
|
+
return exports.stringifyObject({settings: modelSettings});
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Wrap a single line
|
|
730
|
+
* @param {string} line Text for the a line
|
|
731
|
+
* @param {number} maxLineLength - Maximum line length before wrapping
|
|
732
|
+
*/
|
|
733
|
+
function wrapLine(line, maxLineLength) {
|
|
734
|
+
if (line === '') return line;
|
|
735
|
+
let lineLength = 0;
|
|
736
|
+
const words = line.split(/\s+/g);
|
|
737
|
+
return words.reduce((result, word) => {
|
|
738
|
+
if (lineLength + word.length >= maxLineLength) {
|
|
739
|
+
lineLength = word.length;
|
|
740
|
+
return `${result}\n${word}`;
|
|
741
|
+
} else {
|
|
742
|
+
lineLength += word.length + (result ? 1 : 0);
|
|
743
|
+
return result ? `${result} ${word}` : `${word}`;
|
|
744
|
+
}
|
|
745
|
+
}, '');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Wrap the text into lines respecting the max line length
|
|
750
|
+
* @param {string} text - Text string
|
|
751
|
+
* @param {number} maxLineLength - Maximum line length before wrapping
|
|
752
|
+
*/
|
|
753
|
+
function wrapText(text, maxLineLength = 80) {
|
|
754
|
+
let lines = text.split('\n');
|
|
755
|
+
lines = lines.map(line => wrapLine(line, maxLineLength));
|
|
756
|
+
return lines.join('\n');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
exports.wrapLine = wrapLine;
|
|
760
|
+
exports.wrapText = wrapText;
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Check if `yarn` is installed
|
|
764
|
+
*/
|
|
765
|
+
let yarnInstalled = undefined;
|
|
766
|
+
function isYarnAvailable() {
|
|
767
|
+
if (yarnInstalled == null) {
|
|
768
|
+
yarnInstalled = spawnSync('yarn', ['help'], {stdio: false}).status === 0;
|
|
769
|
+
}
|
|
770
|
+
return yarnInstalled;
|
|
771
|
+
}
|
|
772
|
+
exports.isYarnAvailable = isYarnAvailable;
|
|
773
|
+
|
|
774
|
+
// literal strings with artifacts directory locations
|
|
775
|
+
exports.repositoriesDir = 'repositories';
|
|
776
|
+
exports.datasourcesDir = 'datasources';
|
|
777
|
+
exports.servicesDir = 'services';
|
|
778
|
+
exports.modelsDir = 'models';
|
|
779
|
+
exports.observersDir = 'observers';
|
|
780
|
+
exports.interceptorsDir = 'interceptors';
|
|
781
|
+
exports.modelEndpointsDir = 'model-endpoints';
|
|
782
|
+
exports.sourceRootDir = 'src';
|