@loopback/cli 4.0.0-alpha.9 → 4.1.1
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 +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,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Count,
|
|
3
|
+
CountSchema,
|
|
4
|
+
Filter,
|
|
5
|
+
FilterExcludingWhere,
|
|
6
|
+
repository,
|
|
7
|
+
Where,
|
|
8
|
+
} from '@loopback/repository';
|
|
9
|
+
import {
|
|
10
|
+
post,
|
|
11
|
+
param,
|
|
12
|
+
get,
|
|
13
|
+
getModelSchemaRef,
|
|
14
|
+
patch,
|
|
15
|
+
put,
|
|
16
|
+
del,
|
|
17
|
+
requestBody,
|
|
18
|
+
response,
|
|
19
|
+
} from '@loopback/rest';
|
|
20
|
+
import {<%= modelName %>} from '../models';
|
|
21
|
+
import {<%= repositoryName %>} from '../repositories';
|
|
22
|
+
|
|
23
|
+
export class <%= className %>Controller {
|
|
24
|
+
constructor(
|
|
25
|
+
@repository(<%= repositoryName %>)
|
|
26
|
+
public <%= repositoryNameCamel %> : <%= repositoryName %>,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
@post('<%= httpPathName %>')
|
|
30
|
+
@response(200, {
|
|
31
|
+
description: '<%= modelName %> model instance',
|
|
32
|
+
content: {'application/json': {schema: getModelSchemaRef(<%= modelName %>)}},
|
|
33
|
+
})
|
|
34
|
+
async create(
|
|
35
|
+
@requestBody({
|
|
36
|
+
content: {
|
|
37
|
+
'application/json': {
|
|
38
|
+
schema: getModelSchemaRef(<%= modelName %>, {
|
|
39
|
+
title: 'New<%= modelName %>',
|
|
40
|
+
<%if (idOmitted) {%>exclude: ['<%= id %>'],<% } %>
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
<%= modelVariableName %>: <% if (!idOmitted) { -%><%= modelName %><% } else { -%>Omit<<%= modelName %>, '<%= id %>'><% } -%>,
|
|
46
|
+
): Promise<<%= modelName %>> {
|
|
47
|
+
return this.<%= repositoryNameCamel %>.create(<%= modelVariableName %>);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@get('<%= httpPathName %>/count')
|
|
51
|
+
@response(200, {
|
|
52
|
+
description: '<%= modelName %> model count',
|
|
53
|
+
content: {'application/json': {schema: CountSchema}},
|
|
54
|
+
})
|
|
55
|
+
async count(
|
|
56
|
+
@param.where(<%= modelName %>) where?: Where<<%= modelName %>>,
|
|
57
|
+
): Promise<Count> {
|
|
58
|
+
return this.<%= repositoryNameCamel %>.count(where);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@get('<%= httpPathName %>')
|
|
62
|
+
@response(200, {
|
|
63
|
+
description: 'Array of <%= modelName %> model instances',
|
|
64
|
+
content: {
|
|
65
|
+
'application/json': {
|
|
66
|
+
schema: {
|
|
67
|
+
type: 'array',
|
|
68
|
+
items: getModelSchemaRef(<%= modelName %>, {includeRelations: true}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
async find(
|
|
74
|
+
@param.filter(<%= modelName %>) filter?: Filter<<%= modelName %>>,
|
|
75
|
+
): Promise<<%= modelName %>[]> {
|
|
76
|
+
return this.<%= repositoryNameCamel %>.find(filter);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@patch('<%= httpPathName %>')
|
|
80
|
+
@response(200, {
|
|
81
|
+
description: '<%= modelName %> PATCH success count',
|
|
82
|
+
content: {'application/json': {schema: CountSchema}},
|
|
83
|
+
})
|
|
84
|
+
async updateAll(
|
|
85
|
+
@requestBody({
|
|
86
|
+
content: {
|
|
87
|
+
'application/json': {
|
|
88
|
+
schema: getModelSchemaRef(<%= modelName %>, {partial: true}),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
<%= modelVariableName %>: <%= modelName %>,
|
|
93
|
+
@param.where(<%= modelName %>) where?: Where<<%= modelName %>>,
|
|
94
|
+
): Promise<Count> {
|
|
95
|
+
return this.<%= repositoryNameCamel %>.updateAll(<%= modelVariableName %>, where);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@get('<%= httpPathName %>/{id}')
|
|
99
|
+
@response(200, {
|
|
100
|
+
description: '<%= modelName %> model instance',
|
|
101
|
+
content: {
|
|
102
|
+
'application/json': {
|
|
103
|
+
schema: getModelSchemaRef(<%= modelName %>, {includeRelations: true}),
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
async findById(
|
|
108
|
+
@param.path.<%= idType %>('id') id: <%= idType %>,
|
|
109
|
+
@param.filter(<%= modelName %>, {exclude: 'where'}) filter?: FilterExcludingWhere<<%= modelName %>>
|
|
110
|
+
): Promise<<%= modelName %>> {
|
|
111
|
+
return this.<%= repositoryNameCamel %>.findById(id, filter);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@patch('<%= httpPathName %>/{id}')
|
|
115
|
+
@response(204, {
|
|
116
|
+
description: '<%= modelName %> PATCH success',
|
|
117
|
+
})
|
|
118
|
+
async updateById(
|
|
119
|
+
@param.path.<%= idType %>('id') id: <%= idType %>,
|
|
120
|
+
@requestBody({
|
|
121
|
+
content: {
|
|
122
|
+
'application/json': {
|
|
123
|
+
schema: getModelSchemaRef(<%= modelName %>, {partial: true}),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
<%= modelVariableName %>: <%= modelName %>,
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
await this.<%= repositoryNameCamel %>.updateById(id, <%= modelVariableName %>);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@put('<%= httpPathName %>/{id}')
|
|
133
|
+
@response(204, {
|
|
134
|
+
description: '<%= modelName %> PUT success',
|
|
135
|
+
})
|
|
136
|
+
async replaceById(
|
|
137
|
+
@param.path.<%= idType %>('id') id: <%= idType %>,
|
|
138
|
+
@requestBody() <%= modelVariableName %>: <%= modelName %>,
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
await this.<%= repositoryNameCamel %>.replaceById(id, <%= modelVariableName %>);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@del('<%= httpPathName %>/{id}')
|
|
144
|
+
@response(204, {
|
|
145
|
+
description: '<%= modelName %> DELETE success',
|
|
146
|
+
})
|
|
147
|
+
async deleteById(@param.path.<%= idType %>('id') id: <%= idType %>): Promise<void> {
|
|
148
|
+
await this.<%= repositoryNameCamel %>.deleteById(id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Uncomment these imports to begin using these cool features!
|
|
2
2
|
|
|
3
|
-
// import {inject} from @loopback/
|
|
3
|
+
// import {inject} from '@loopback/core';
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
export class <%=
|
|
6
|
+
export class <%= className %>Controller {
|
|
7
7
|
constructor() {}
|
|
8
8
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/cli
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fse = require('fs-extra');
|
|
9
|
+
const _ = require('lodash');
|
|
10
|
+
const {promisify} = require('util');
|
|
11
|
+
const glob = promisify(require('glob'));
|
|
12
|
+
|
|
13
|
+
const defaultFS = {
|
|
14
|
+
write: fse.writeFile,
|
|
15
|
+
read: fse.readFile,
|
|
16
|
+
writeJSON: fse.writeJson,
|
|
17
|
+
readJSON: fse.readJson,
|
|
18
|
+
exists: fse.exists,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List all JS/TS files
|
|
23
|
+
* @param {string[]} paths - Paths to search
|
|
24
|
+
*/
|
|
25
|
+
async function jsOrTsFiles(cwd, paths = []) {
|
|
26
|
+
paths = [].concat(paths);
|
|
27
|
+
let globs;
|
|
28
|
+
if (paths.length === 0) {
|
|
29
|
+
globs = [glob('**/*.{js,ts}', {nodir: true, follow: false, cwd})];
|
|
30
|
+
} else {
|
|
31
|
+
globs = paths.map(p => {
|
|
32
|
+
if (/\/$/.test(p)) {
|
|
33
|
+
p += '**/*.{js,ts}';
|
|
34
|
+
} else if (!/[^*]\.(js|ts)$/.test(p)) {
|
|
35
|
+
p += '/**/*.{js,ts}';
|
|
36
|
+
}
|
|
37
|
+
return glob(p, {nodir: true, follow: false, cwd});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
paths = await Promise.all(globs);
|
|
41
|
+
paths = _.flatten(paths);
|
|
42
|
+
return _.filter(paths, /\.(js|ts)$/);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.FSE = defaultFS;
|
|
46
|
+
exports.jsOrTsFiles = jsOrTsFiles;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/cli
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
const cp = require('child_process');
|
|
10
|
+
const util = require('util');
|
|
11
|
+
const debug = require('debug')('loopback:cli:copyright:git');
|
|
12
|
+
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run a git command
|
|
17
|
+
* @param {string} cwd - Current directory to run the command
|
|
18
|
+
* @param {...any} args - Args for the git command
|
|
19
|
+
*/
|
|
20
|
+
async function git(cwd, ...args) {
|
|
21
|
+
const cmd = 'git ' + util.format(...args);
|
|
22
|
+
const key = `${cwd}:${cmd}`;
|
|
23
|
+
debug('Running %s in directory', cmd, cwd);
|
|
24
|
+
if (cache.has(key)) {
|
|
25
|
+
return cache.get(key);
|
|
26
|
+
}
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
cp.exec(cmd, {maxBuffer: 1024 * 1024, cwd}, (err, stdout) => {
|
|
29
|
+
stdout = _(stdout || '')
|
|
30
|
+
.split(/[\r\n]+/g)
|
|
31
|
+
.map(_.trim)
|
|
32
|
+
.filter()
|
|
33
|
+
.value();
|
|
34
|
+
if (err) {
|
|
35
|
+
// reject(err);
|
|
36
|
+
resolve([]);
|
|
37
|
+
} else {
|
|
38
|
+
cache.set(key, stdout);
|
|
39
|
+
debug('Stdout', stdout);
|
|
40
|
+
resolve(stdout);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Inspect years for a given file based on git history
|
|
48
|
+
* @param {string} file - JS/TS file
|
|
49
|
+
*/
|
|
50
|
+
async function getYears(file) {
|
|
51
|
+
file = file || '.';
|
|
52
|
+
const gitDates = await git(
|
|
53
|
+
process.cwd(),
|
|
54
|
+
'--no-pager log --pretty=%%ai --all -- %s',
|
|
55
|
+
file,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const currentYear = new Date().getFullYear();
|
|
59
|
+
|
|
60
|
+
if (!gitDates.length) return [currentYear];
|
|
61
|
+
|
|
62
|
+
const latestGitYear = getYear(gitDates[0]);
|
|
63
|
+
const oldestGitYear = getYear(gitDates.slice(-1)[0]);
|
|
64
|
+
const latestYear = currentYear > latestGitYear ? currentYear : latestGitYear;
|
|
65
|
+
const yearRange = [oldestGitYear];
|
|
66
|
+
|
|
67
|
+
if (latestYear !== yearRange[0]) yearRange.push(latestYear);
|
|
68
|
+
|
|
69
|
+
return yearRange;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// assumes ISO-8601 (or similar) format
|
|
73
|
+
function getYear(str) {
|
|
74
|
+
return str.slice(0, 4);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.git = git;
|
|
78
|
+
exports.getYears = getYears;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 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
|
+
const {Project} = require('@lerna/project');
|
|
7
|
+
const {Minimatch} = require('minimatch');
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const {git, getYears} = require('./git');
|
|
12
|
+
const {FSE, jsOrTsFiles} = require('./fs');
|
|
13
|
+
const {spdxLicenseList} = require('./license');
|
|
14
|
+
|
|
15
|
+
const debug = require('debug')('loopback:cli:copyright');
|
|
16
|
+
|
|
17
|
+
// Components of the copyright header.
|
|
18
|
+
const COPYRIGHT = [
|
|
19
|
+
' Copyright <%= owner %> <%= years %>. All Rights Reserved.',
|
|
20
|
+
' Node module: <%= name %>',
|
|
21
|
+
];
|
|
22
|
+
const LICENSE = [
|
|
23
|
+
' This file is licensed under the <%= license %>.',
|
|
24
|
+
' License text available at <%= url %>',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Compiled templates for generating copyright headers
|
|
28
|
+
const LICENSED = _.template(COPYRIGHT.concat(LICENSE).join('\n'));
|
|
29
|
+
|
|
30
|
+
function getCustomTemplate(customLicenseLines = []) {
|
|
31
|
+
if (typeof customLicenseLines === 'string') {
|
|
32
|
+
customLicenseLines = [customLicenseLines];
|
|
33
|
+
}
|
|
34
|
+
const UNLICENSED = _.template(COPYRIGHT.join('\n'));
|
|
35
|
+
let CUSTOM = UNLICENSED;
|
|
36
|
+
if (customLicenseLines.length) {
|
|
37
|
+
let copyrightLines = COPYRIGHT;
|
|
38
|
+
if (customLicenseLines.some(line => line.includes('Copyright'))) {
|
|
39
|
+
copyrightLines = [];
|
|
40
|
+
}
|
|
41
|
+
CUSTOM = _.template(copyrightLines.concat(customLicenseLines).join('\n'));
|
|
42
|
+
}
|
|
43
|
+
return CUSTOM;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Patterns for matching previously generated copyright headers
|
|
47
|
+
const BLANK = /^\s*$/;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Preserve characters that have special meaning for RegExp
|
|
51
|
+
* @param {string} text - Text
|
|
52
|
+
*/
|
|
53
|
+
function escapeRegExp(text) {
|
|
54
|
+
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getHeaderRegEx(customLicenseLines) {
|
|
58
|
+
const lines =
|
|
59
|
+
customLicenseLines != null && customLicenseLines.length
|
|
60
|
+
? customLicenseLines
|
|
61
|
+
: COPYRIGHT.concat(LICENSE, customLicenseLines);
|
|
62
|
+
const regExp = lines.map(
|
|
63
|
+
l => new RegExp(escapeRegExp(l).replace(/<%[^>]+%>/g, '.*')),
|
|
64
|
+
);
|
|
65
|
+
return regExp;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build header for a file
|
|
70
|
+
* @param {string} file - JS/TS file
|
|
71
|
+
* @param {object} pkg - Package json object
|
|
72
|
+
* @param {object} options - Options
|
|
73
|
+
*/
|
|
74
|
+
async function buildHeader(file, pkg, options) {
|
|
75
|
+
const license =
|
|
76
|
+
options.license || _.get(pkg, 'license') || options.defaultLicense;
|
|
77
|
+
const years = await getYears(file);
|
|
78
|
+
const params = expandLicense(license, options.customLicenseLines);
|
|
79
|
+
params.years = years.join(',');
|
|
80
|
+
const owner = getCopyrightOwner(pkg, options);
|
|
81
|
+
|
|
82
|
+
const name =
|
|
83
|
+
options.copyrightIdentifer ||
|
|
84
|
+
_.get(pkg, 'copyright.identifier') ||
|
|
85
|
+
_.get(pkg, 'name') ||
|
|
86
|
+
options.defaultCopyrightIdentifer;
|
|
87
|
+
|
|
88
|
+
debug(owner, name, license);
|
|
89
|
+
|
|
90
|
+
_.defaults(params, {
|
|
91
|
+
owner,
|
|
92
|
+
name,
|
|
93
|
+
license,
|
|
94
|
+
});
|
|
95
|
+
debug('Params', params);
|
|
96
|
+
return params.template(params);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getCopyrightOwner(pkg, options) {
|
|
100
|
+
return (
|
|
101
|
+
options.copyrightOwner ||
|
|
102
|
+
_.get(pkg, 'copyright.owner') ||
|
|
103
|
+
_.get(pkg, 'author.name') ||
|
|
104
|
+
options.defaultCopyrightOwner ||
|
|
105
|
+
'Owner'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build the license template params
|
|
111
|
+
* @param {string|object} spdxLicense - SPDX license id or object
|
|
112
|
+
*/
|
|
113
|
+
function expandLicense(spdxLicense, customLicenseLines = []) {
|
|
114
|
+
if (typeof spdxLicense === 'string') {
|
|
115
|
+
spdxLicense = spdxLicenseList[spdxLicense.toLowerCase()];
|
|
116
|
+
}
|
|
117
|
+
if (spdxLicense && spdxLicense.id !== 'CUSTOM') {
|
|
118
|
+
return {
|
|
119
|
+
template: LICENSED,
|
|
120
|
+
license: spdxLicense.name,
|
|
121
|
+
url: spdxLicense.url,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
template: getCustomTemplate(customLicenseLines),
|
|
126
|
+
license: spdxLicense,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Format the header for a file
|
|
132
|
+
* @param {string} file - JS/TS file
|
|
133
|
+
* @param {string} pkg - Package json object
|
|
134
|
+
* @param {object} options - Options
|
|
135
|
+
*/
|
|
136
|
+
async function formatHeader(file, pkg, options) {
|
|
137
|
+
const header = await buildHeader(file, pkg, options);
|
|
138
|
+
return header.split('\n').map(line => `//${line}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Ensure the file is updated with the correct header
|
|
143
|
+
* @param {string} file - JS/TS file
|
|
144
|
+
* @param {object} pkg - Package json object
|
|
145
|
+
* @param {object} options - Options
|
|
146
|
+
*/
|
|
147
|
+
async function ensureHeader(file, pkg, options = {}) {
|
|
148
|
+
const fs = options.fs || FSE;
|
|
149
|
+
const header = await formatHeader(file, pkg, options);
|
|
150
|
+
debug('Header: %s', header);
|
|
151
|
+
const current = await fs.read(file, 'utf8');
|
|
152
|
+
const content = mergeHeaderWithContent(
|
|
153
|
+
header,
|
|
154
|
+
current,
|
|
155
|
+
options.customLicenseLines,
|
|
156
|
+
);
|
|
157
|
+
if (!options.dryRun) {
|
|
158
|
+
await fs.write(file, content, 'utf8');
|
|
159
|
+
} else {
|
|
160
|
+
const log = options.log || console.log;
|
|
161
|
+
log(file, header);
|
|
162
|
+
}
|
|
163
|
+
return content;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merge header with file content
|
|
168
|
+
* @param {string} header - Copyright header
|
|
169
|
+
* @param {string} content - File content
|
|
170
|
+
* @param {string[]} customLicenseLines - Custom license lines
|
|
171
|
+
*/
|
|
172
|
+
function mergeHeaderWithContent(header, content, customLicenseLines) {
|
|
173
|
+
const lineEnding = /\r\n/gm.test(content) ? '\r\n' : '\n';
|
|
174
|
+
const preamble = [];
|
|
175
|
+
content = content.split(lineEnding);
|
|
176
|
+
if (/^#!/.test(content[0])) {
|
|
177
|
+
preamble.push(content.shift());
|
|
178
|
+
}
|
|
179
|
+
const patterns = getHeaderRegEx(customLicenseLines);
|
|
180
|
+
// replace any existing copyright header lines and collapse blank lines down
|
|
181
|
+
// to just one.
|
|
182
|
+
while (headerOrBlankLine(content[0], patterns)) {
|
|
183
|
+
content.shift();
|
|
184
|
+
}
|
|
185
|
+
return [].concat(preamble, header, '', content).join(lineEnding);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function headerOrBlankLine(line, patterns) {
|
|
189
|
+
return BLANK.test(line) || patterns.some(pat => pat.test(line));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Update file headers for the given project
|
|
194
|
+
* @param {string} projectRoot - Root directory of a package or a lerna monorepo
|
|
195
|
+
* @param {object} options - Options
|
|
196
|
+
*/
|
|
197
|
+
async function updateFileHeaders(projectRoot, options = {}) {
|
|
198
|
+
debug('Starting project root: %s', projectRoot);
|
|
199
|
+
options = {
|
|
200
|
+
dryRun: false,
|
|
201
|
+
gitOnly: true,
|
|
202
|
+
...options,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const fs = options.fs || FSE;
|
|
206
|
+
const isMonorepo = await fs.exists(path.join(projectRoot, 'lerna.json'));
|
|
207
|
+
if (isMonorepo) {
|
|
208
|
+
// List all packages for the monorepo
|
|
209
|
+
const project = new Project(projectRoot);
|
|
210
|
+
debug('Lerna monorepo', project);
|
|
211
|
+
const packages = await project.getPackages();
|
|
212
|
+
|
|
213
|
+
// Update file headers for each package
|
|
214
|
+
const visited = [];
|
|
215
|
+
for (const p of packages) {
|
|
216
|
+
visited.push(p.location);
|
|
217
|
+
const pkgOptions = {...options};
|
|
218
|
+
// Set default copyright owner and id so that package-level settings
|
|
219
|
+
// take precedence
|
|
220
|
+
pkgOptions.defaultCopyrightIdentifer = options.copyrightIdentifer;
|
|
221
|
+
pkgOptions.defaultCopyrightOwner = options.copyrightOwner;
|
|
222
|
+
pkgOptions.defaultLicense = options.license;
|
|
223
|
+
delete pkgOptions.copyrightOwner;
|
|
224
|
+
delete pkgOptions.copyrightIdentifer;
|
|
225
|
+
delete pkgOptions.license;
|
|
226
|
+
await updateFileHeaders(p.location, pkgOptions);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Now handle the root level package
|
|
230
|
+
// Exclude files that have been processed
|
|
231
|
+
const filter = f => {
|
|
232
|
+
const included = !visited.some(dir => {
|
|
233
|
+
dir = path.relative(projectRoot, dir);
|
|
234
|
+
// glob return files with `/`
|
|
235
|
+
return f.startsWith(path.join(dir, '/').replace(/\\/g, '/'));
|
|
236
|
+
});
|
|
237
|
+
if (included) {
|
|
238
|
+
debug('File %s is included for monorepo package', f);
|
|
239
|
+
}
|
|
240
|
+
return included;
|
|
241
|
+
};
|
|
242
|
+
const pkgOptions = {filter, ...options};
|
|
243
|
+
await updateFileHeadersForSinglePackage(projectRoot, pkgOptions);
|
|
244
|
+
} else {
|
|
245
|
+
await updateFileHeadersForSinglePackage(projectRoot, options);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Update file headers for the given project
|
|
251
|
+
* @param {string} projectRoot - Root directory of a package
|
|
252
|
+
* @param {object} options - Options
|
|
253
|
+
*/
|
|
254
|
+
async function updateFileHeadersForSinglePackage(projectRoot, options) {
|
|
255
|
+
debug('Options', options);
|
|
256
|
+
debug('Project root: %s', projectRoot);
|
|
257
|
+
const log = options.log || console.log;
|
|
258
|
+
const pkgFile = path.join(projectRoot, 'package.json');
|
|
259
|
+
const fs = options.fs || FSE;
|
|
260
|
+
const exists = await fs.exists(pkgFile);
|
|
261
|
+
if (!exists) {
|
|
262
|
+
log(chalk.red(`No package.json exists at ${projectRoot}`));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const pkg = await fs.readJSON(pkgFile);
|
|
266
|
+
|
|
267
|
+
log(
|
|
268
|
+
'Updating project %s (%s)',
|
|
269
|
+
pkg.name,
|
|
270
|
+
path.relative(process.cwd(), projectRoot) || '.',
|
|
271
|
+
);
|
|
272
|
+
debug('Package', pkg);
|
|
273
|
+
let files = options.gitOnly ? await git(projectRoot, 'ls-files') : [];
|
|
274
|
+
debug('Paths', files);
|
|
275
|
+
files = await jsOrTsFiles(projectRoot, files);
|
|
276
|
+
if (typeof options.filter === 'function') {
|
|
277
|
+
files = files.filter(options.filter);
|
|
278
|
+
}
|
|
279
|
+
debug('JS/TS files', files);
|
|
280
|
+
if (Array.isArray(options.excludePatterns)) {
|
|
281
|
+
debug('Exclude', options.excludePatterns);
|
|
282
|
+
files = files.filter(
|
|
283
|
+
f =>
|
|
284
|
+
!options.excludePatterns.some(p => {
|
|
285
|
+
const minimatch = new Minimatch(p, {dot: true});
|
|
286
|
+
const result = minimatch.match(f);
|
|
287
|
+
debug('Matching %s against %s:', f, p, result);
|
|
288
|
+
return result;
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
debug('JS/TS files excluding %s:', options.excludePatterns, files);
|
|
293
|
+
for (const file of files) {
|
|
294
|
+
await ensureHeader(path.resolve(projectRoot, file), pkg, options);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
exports.updateFileHeaders = updateFileHeaders;
|
|
299
|
+
exports.getYears = getYears;
|
|
300
|
+
|
|
301
|
+
if (require.main === module) {
|
|
302
|
+
updateFileHeaders(process.cwd()).catch(err => {
|
|
303
|
+
console.error(err);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
});
|
|
306
|
+
}
|