@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.
Files changed (159) hide show
  1. package/.yo-rc.json +1719 -0
  2. package/{generators/project/templates/LICENSE → LICENSE} +2 -1
  3. package/README.md +44 -43
  4. package/bin/cli-main.js +61 -0
  5. package/generators/app/index.js +109 -15
  6. package/generators/app/templates/.dockerignore +5 -0
  7. package/generators/app/templates/Dockerfile +28 -0
  8. package/generators/app/templates/README.md.ejs +130 -0
  9. package/generators/app/templates/public/index.html.ejs +88 -0
  10. package/generators/app/templates/{test → src/__tests__}/README.md +0 -1
  11. package/generators/app/templates/src/__tests__/acceptance/home-page.acceptance.ts.ejs +31 -0
  12. package/generators/app/templates/src/__tests__/acceptance/ping.controller.acceptance.ts.ejs +21 -0
  13. package/generators/app/templates/src/__tests__/acceptance/test-helper.ts.ejs +32 -0
  14. package/generators/app/templates/src/application.ts.ejs +70 -0
  15. package/generators/app/templates/src/controllers/README.md +6 -0
  16. package/generators/app/templates/src/controllers/index.ts.ejs +1 -0
  17. package/generators/app/templates/src/controllers/ping.controller.ts.ejs +55 -0
  18. package/generators/app/templates/src/datasources/README.md +3 -0
  19. package/generators/app/templates/src/index.ts.ejs +39 -0
  20. package/generators/app/templates/src/migrate.ts.ejs +20 -0
  21. package/generators/app/templates/src/models/README.md +3 -0
  22. package/generators/app/templates/src/openapi-spec.ts.ejs +23 -0
  23. package/generators/app/templates/src/sequence.ts.ejs +3 -0
  24. package/generators/controller/index.js +240 -23
  25. package/generators/controller/templates/src/controllers/controller-rest-template.ts.ejs +150 -0
  26. package/generators/controller/templates/src/controllers/{controller-template.ts → controller-template.ts.ejs} +2 -2
  27. package/generators/copyright/fs.js +46 -0
  28. package/generators/copyright/git.js +78 -0
  29. package/generators/copyright/header.js +306 -0
  30. package/generators/copyright/index.js +230 -0
  31. package/generators/copyright/license.js +105 -0
  32. package/generators/datasource/index.js +341 -0
  33. package/generators/datasource/templates/datasource.ts.ejs +22 -0
  34. package/generators/discover/import-discovered-model.js +70 -0
  35. package/generators/discover/index.js +411 -0
  36. package/generators/example/downloader.js +16 -0
  37. package/generators/example/index.js +176 -0
  38. package/generators/extension/index.js +34 -5
  39. package/generators/extension/templates/README.md.ejs +32 -0
  40. package/generators/extension/templates/{test → src/__tests__}/acceptance/README.md +0 -0
  41. package/generators/extension/templates/{test → src/__tests__}/integration/README.md +0 -0
  42. package/generators/extension/templates/{test → src/__tests__}/unit/README.md +0 -0
  43. package/generators/extension/templates/src/component.ts.ejs +22 -0
  44. package/generators/extension/templates/src/controllers/README.md +3 -2
  45. package/generators/extension/templates/src/decorators/README.md +10 -4
  46. package/generators/extension/templates/src/index.ts.ejs +3 -0
  47. package/generators/extension/templates/src/keys.ts.ejs +11 -0
  48. package/generators/extension/templates/src/mixins/README.md +77 -21
  49. package/generators/extension/templates/src/providers/README.md +51 -25
  50. package/generators/extension/templates/src/repositories/README.md +1 -1
  51. package/generators/extension/templates/src/types.ts.ejs +15 -0
  52. package/generators/import-lb3-models/index.js +197 -0
  53. package/generators/import-lb3-models/lb3app-loader.js +31 -0
  54. package/generators/import-lb3-models/migrate-model.js +249 -0
  55. package/generators/import-lb3-models/model-names.js +32 -0
  56. package/generators/interceptor/index.js +178 -0
  57. package/generators/interceptor/templates/interceptor-template.ts.ejs +62 -0
  58. package/generators/model/index.js +536 -0
  59. package/generators/model/property-definition.js +85 -0
  60. package/generators/model/templates/model.ts.ejs +49 -0
  61. package/generators/observer/index.js +132 -0
  62. package/generators/observer/templates/observer-template.ts.ejs +40 -0
  63. package/generators/openapi/README.md +211 -0
  64. package/generators/openapi/index.js +535 -0
  65. package/generators/openapi/schema-helper.js +447 -0
  66. package/generators/openapi/spec-helper.js +484 -0
  67. package/generators/openapi/spec-loader.js +75 -0
  68. package/generators/openapi/templates/src/controllers/controller-template.ts.ejs +43 -0
  69. package/generators/openapi/templates/src/datasources/datasource.ts.ejs +42 -0
  70. package/generators/openapi/templates/src/models/model-template.ts.ejs +71 -0
  71. package/generators/openapi/templates/src/models/type-template.ts.ejs +13 -0
  72. package/generators/openapi/templates/src/services/service-proxy-template.ts.ejs +55 -0
  73. package/generators/openapi/utils.js +322 -0
  74. package/generators/project/templates/.eslintignore +4 -0
  75. package/generators/project/templates/.eslintrc.js.ejs +3 -0
  76. package/generators/project/templates/.mocharc.json +5 -0
  77. package/generators/project/templates/.prettierignore +0 -2
  78. package/generators/project/templates/.prettierrc +2 -1
  79. package/generators/project/templates/.vscode/launch.json +38 -0
  80. package/generators/project/templates/.vscode/settings.json +32 -0
  81. package/generators/project/templates/.vscode/tasks.json +29 -0
  82. package/generators/project/templates/DEVELOPING.md +36 -0
  83. package/generators/project/templates/_.gitignore +3 -5
  84. package/generators/project/templates/package.json.ejs +175 -0
  85. package/generators/project/templates/package.plain.json.ejs +176 -0
  86. package/generators/project/templates/tsconfig.json.ejs +39 -0
  87. package/generators/relation/base-relation.generator.js +220 -0
  88. package/generators/relation/belongs-to-relation.generator.js +196 -0
  89. package/generators/relation/has-many-relation.generator.js +200 -0
  90. package/generators/relation/has-many-through-relation.generator.js +331 -0
  91. package/generators/relation/has-one-relation.generator.js +200 -0
  92. package/generators/relation/index.js +795 -0
  93. package/generators/relation/references-many-relation.generator.js +142 -0
  94. package/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs +38 -0
  95. package/generators/relation/templates/controller-relation-template-has-many-through.ts.ejs +110 -0
  96. package/generators/relation/templates/controller-relation-template-has-many.ts.ejs +110 -0
  97. package/generators/relation/templates/controller-relation-template-has-one.ts.ejs +110 -0
  98. package/generators/relation/utils.generator.js +260 -0
  99. package/generators/repository/index.js +576 -0
  100. package/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs +21 -0
  101. package/generators/repository/templates/src/repositories/repository-kv-template.ts.ejs +19 -0
  102. package/generators/rest-crud/crud-rest-component.js +63 -0
  103. package/generators/rest-crud/index.js +423 -0
  104. package/generators/rest-crud/templates/src/model-endpoints/model.rest-config-template.ts.ejs +11 -0
  105. package/generators/service/index.js +351 -0
  106. package/generators/service/templates/local-service-class-template.ts.ejs +10 -0
  107. package/generators/service/templates/local-service-provider-template.ts.ejs +19 -0
  108. package/generators/service/templates/remote-service-proxy-template.ts.ejs +21 -0
  109. package/generators/update/index.js +55 -0
  110. package/intl/cs/messages.json +204 -0
  111. package/intl/de/messages.json +204 -0
  112. package/intl/en/messages.json +204 -0
  113. package/intl/es/messages.json +204 -0
  114. package/intl/fr/messages.json +204 -0
  115. package/intl/it/messages.json +204 -0
  116. package/intl/ja/messages.json +204 -0
  117. package/intl/ko/messages.json +204 -0
  118. package/intl/nl/messages.json +204 -0
  119. package/intl/pl/messages.json +204 -0
  120. package/intl/pt/messages.json +204 -0
  121. package/intl/ru/messages.json +204 -0
  122. package/intl/tr/messages.json +204 -0
  123. package/intl/zh-Hans/messages.json +204 -0
  124. package/intl/zh-Hant/messages.json +204 -0
  125. package/lib/artifact-generator.js +138 -39
  126. package/lib/ast-helper.js +214 -0
  127. package/lib/base-generator.js +509 -0
  128. package/lib/cli.js +233 -0
  129. package/lib/connectors.json +894 -0
  130. package/lib/debug.js +16 -0
  131. package/lib/globalize.js +12 -0
  132. package/lib/model-discoverer.js +118 -0
  133. package/lib/project-generator.js +154 -57
  134. package/lib/tab-completion.js +127 -0
  135. package/lib/update-index.js +44 -0
  136. package/lib/utils.js +689 -20
  137. package/lib/version-helper.js +299 -0
  138. package/package.json +183 -39
  139. package/CHANGELOG.md +0 -86
  140. package/bin/cli.js +0 -66
  141. package/generators/app/templates/index.js +0 -14
  142. package/generators/app/templates/src/application.ts +0 -27
  143. package/generators/app/templates/src/controllers/ping-controller.ts +0 -25
  144. package/generators/app/templates/src/index.ts +0 -25
  145. package/generators/app/templates/test/ping-controller.test.ts +0 -46
  146. package/generators/extension/templates/index.js +0 -8
  147. package/generators/extension/templates/src/component.ts +0 -14
  148. package/generators/extension/templates/src/index.ts +0 -6
  149. package/generators/project/templates/.npmrc +0 -1
  150. package/generators/project/templates/.yo-rc.json +0 -1
  151. package/generators/project/templates/README.md +0 -4
  152. package/generators/project/templates/index.d.ts +0 -6
  153. package/generators/project/templates/index.ts +0 -11
  154. package/generators/project/templates/package.json +0 -79
  155. package/generators/project/templates/package.plain.json +0 -82
  156. package/generators/project/templates/test/mocha.opts +0 -1
  157. package/generators/project/templates/tsconfig.json +0 -29
  158. package/generators/project/templates/tslint.build.json +0 -17
  159. 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/context;
3
+ // import {inject} from '@loopback/core';
4
4
 
5
5
 
6
- export class <%= name %>Controller {
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
+ }