@deot/dev-releaser 1.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/README.md +6 -0
- package/dist/index.cjs.js +559 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +538 -0
- package/package.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chalk = require('chalk');
|
|
4
|
+
var devShared = require('@deot/dev-shared');
|
|
5
|
+
var path = require('node:path');
|
|
6
|
+
var fs = require('fs-extra');
|
|
7
|
+
var node_module = require('node:module');
|
|
8
|
+
var parser = require('conventional-commits-parser');
|
|
9
|
+
var semver = require('semver');
|
|
10
|
+
var inquirer = require('inquirer');
|
|
11
|
+
|
|
12
|
+
function _interopNamespaceDefault(e) {
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n.default = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
30
|
+
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
const require$ = node_module.createRequire(cwd);
|
|
33
|
+
const { prompt } = inquirer;
|
|
34
|
+
const HASH = '-hash-';
|
|
35
|
+
const SUFFIX = '🐒💨🙊';
|
|
36
|
+
const parserOptions = {
|
|
37
|
+
noteKeywords: ['BREAKING CHANGE', 'Breaking Change']
|
|
38
|
+
};
|
|
39
|
+
const reBreaking = new RegExp(`(${parserOptions.noteKeywords.join(')|(')})`);
|
|
40
|
+
class Release {
|
|
41
|
+
packageDir;
|
|
42
|
+
packageName;
|
|
43
|
+
packageFolderName;
|
|
44
|
+
packageOptions;
|
|
45
|
+
packageRelation;
|
|
46
|
+
config;
|
|
47
|
+
changeLog;
|
|
48
|
+
version;
|
|
49
|
+
commits;
|
|
50
|
+
commandOptions;
|
|
51
|
+
constructor(config, commandOptions) {
|
|
52
|
+
const { packageDir, packageRelation } = devShared.Locals.impl();
|
|
53
|
+
if (typeof config === 'string') {
|
|
54
|
+
let packageFolderName = config;
|
|
55
|
+
let packageDir$ = path__namespace.resolve(packageDir, packageFolderName);
|
|
56
|
+
config = {
|
|
57
|
+
dir: packageDir$,
|
|
58
|
+
name: packageFolderName
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
this.packageDir = config.dir;
|
|
62
|
+
this.packageName = devShared.Locals.getPackageName(config.name);
|
|
63
|
+
this.packageFolderName = config.name;
|
|
64
|
+
this.packageOptions = require$(`${this.packageDir}/package.json`);
|
|
65
|
+
this.packageRelation = packageRelation[this.packageName] || [];
|
|
66
|
+
this.config = config;
|
|
67
|
+
this.commits = [];
|
|
68
|
+
this.changeLog = '';
|
|
69
|
+
this.version = '';
|
|
70
|
+
this.commandOptions = commandOptions;
|
|
71
|
+
}
|
|
72
|
+
async parseCommits() {
|
|
73
|
+
const { workspace } = devShared.Locals.impl();
|
|
74
|
+
const { packageFolderName, packageName, commandOptions } = this;
|
|
75
|
+
let params = ['tag', '--list', `'${packageName}@*'`, '--sort', '-v:refname'];
|
|
76
|
+
const { stdout: tags } = await devShared.Shell.exec('git', params);
|
|
77
|
+
const [latestTag] = tags.split('\n');
|
|
78
|
+
devShared.Logger.log(chalk.yellow(`Last Release Tag`) + `: ${latestTag || '<none>'}`);
|
|
79
|
+
params = ['--no-pager', 'log', `${latestTag}..HEAD`, `--format=%B%n${HASH}%n%H${SUFFIX}`];
|
|
80
|
+
let { stdout } = await devShared.Shell.exec('git', params);
|
|
81
|
+
let skipGetLog = false;
|
|
82
|
+
if (latestTag) {
|
|
83
|
+
const log1 = await devShared.Shell.exec('git', ['rev-parse', latestTag]);
|
|
84
|
+
const log2 = await devShared.Shell.exec('git', ['--no-pager', 'log', '-1', '--format=%H']);
|
|
85
|
+
if (log1.stdout === log2.stdout) {
|
|
86
|
+
skipGetLog = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!skipGetLog && !stdout) {
|
|
90
|
+
if (latestTag) {
|
|
91
|
+
params.splice(2, 1, `${latestTag}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
params.splice(2, 1, 'HEAD');
|
|
95
|
+
}
|
|
96
|
+
({ stdout } = await devShared.Shell.exec('git', params));
|
|
97
|
+
}
|
|
98
|
+
const allowTypes = ['feat', `fix`, `break change`, `style`, `perf`, `types`, `refactor`, `chore`];
|
|
99
|
+
const rePlugin = new RegExp(`^(${allowTypes.join('|')})${workspace ? `\\(${packageFolderName}\\)` : '(\\(.+\\))?'}: .*`, 'i');
|
|
100
|
+
const allCommits = stdout.split(SUFFIX);
|
|
101
|
+
const commits = allCommits
|
|
102
|
+
.filter((commit) => {
|
|
103
|
+
const chunk = commit.trim();
|
|
104
|
+
return chunk && rePlugin.test(chunk);
|
|
105
|
+
})
|
|
106
|
+
.map((commit) => {
|
|
107
|
+
const node = parser.sync(commit);
|
|
108
|
+
const body = (node.body || node.footer);
|
|
109
|
+
if (!node.type)
|
|
110
|
+
node.type = parser.sync(node.header?.replace(/\(.+\)!?:/, ':') || '').type;
|
|
111
|
+
if (!node.hash)
|
|
112
|
+
node.hash = commit.split(HASH).pop()?.trim();
|
|
113
|
+
node.breaking = reBreaking.test(body) || /!:/.test(node.header);
|
|
114
|
+
node.effect = false;
|
|
115
|
+
node.custom = false;
|
|
116
|
+
return node;
|
|
117
|
+
});
|
|
118
|
+
if (!commits.length) {
|
|
119
|
+
devShared.Logger.log(chalk.red(`No Commits Found.`));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
devShared.Logger.log(chalk.yellow(`Found `)
|
|
123
|
+
+ chalk.bold(`${allCommits.length}`)
|
|
124
|
+
+ ` Commits, `
|
|
125
|
+
+ chalk.bold(`${commits.length}`)
|
|
126
|
+
+ ' Commits Valid');
|
|
127
|
+
}
|
|
128
|
+
const { skipUpdatePackage } = commandOptions;
|
|
129
|
+
if (commits.length && skipUpdatePackage) {
|
|
130
|
+
let skip = false;
|
|
131
|
+
if (typeof skipUpdatePackage === 'boolean' && skipUpdatePackage) {
|
|
132
|
+
let result = await prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'confirm',
|
|
135
|
+
name: 'skip',
|
|
136
|
+
message: `Skip Update(${this.packageName}@${this.packageOptions.version}):`,
|
|
137
|
+
default: true
|
|
138
|
+
}
|
|
139
|
+
]);
|
|
140
|
+
skip = result.skip;
|
|
141
|
+
}
|
|
142
|
+
else if (typeof skipUpdatePackage === 'string'
|
|
143
|
+
&& (skipUpdatePackage === '**'
|
|
144
|
+
|| skipUpdatePackage.split(',').includes(this.packageName))) {
|
|
145
|
+
skip = true;
|
|
146
|
+
}
|
|
147
|
+
if (skip) {
|
|
148
|
+
devShared.Logger.log(chalk.red(`Skipping Update\n`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
await this.updateVersion();
|
|
153
|
+
await this.updateCommits(commits);
|
|
154
|
+
const { forceUpdatePackage } = commandOptions;
|
|
155
|
+
if (!commits.length && forceUpdatePackage) {
|
|
156
|
+
let force = false;
|
|
157
|
+
if (typeof forceUpdatePackage === 'boolean' && forceUpdatePackage) {
|
|
158
|
+
let result = await prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'confirm',
|
|
161
|
+
name: 'force',
|
|
162
|
+
message: `Force Update(${this.packageName}@${this.packageOptions.version}):`,
|
|
163
|
+
default: true
|
|
164
|
+
}
|
|
165
|
+
]);
|
|
166
|
+
force = result.force;
|
|
167
|
+
}
|
|
168
|
+
else if (typeof forceUpdatePackage === 'string'
|
|
169
|
+
&& (forceUpdatePackage === '**'
|
|
170
|
+
|| forceUpdatePackage.split(',').includes(this.packageName))) {
|
|
171
|
+
force = true;
|
|
172
|
+
}
|
|
173
|
+
if (force) {
|
|
174
|
+
const oldVersion = this.packageOptions.version;
|
|
175
|
+
const versionChanged = `\`${oldVersion}\` -> \`${this.version}\``;
|
|
176
|
+
this.commits = [
|
|
177
|
+
{
|
|
178
|
+
type: 'chore',
|
|
179
|
+
header: `chore(${this.packageFolderName || 'release'}): force-publish ${versionChanged}`,
|
|
180
|
+
hash: '',
|
|
181
|
+
effect: false,
|
|
182
|
+
breaking: false,
|
|
183
|
+
custom: true
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
this.changeLog = `### Force Update Package\n\n- ${versionChanged}`.trim();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
rebuildChangeLog(commits) {
|
|
191
|
+
const { packageDir } = this;
|
|
192
|
+
const { homepage, workspace } = devShared.Locals.impl();
|
|
193
|
+
const logPath = path__namespace.resolve(packageDir, './CHANGELOG.md');
|
|
194
|
+
const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '';
|
|
195
|
+
const notes = {
|
|
196
|
+
breaking: [],
|
|
197
|
+
features: [],
|
|
198
|
+
fixes: [],
|
|
199
|
+
updates: []
|
|
200
|
+
};
|
|
201
|
+
const closeRegxp = /\(?(closes? )\(?#((\d+))\)/ig;
|
|
202
|
+
const pullRegxp = /(?<!closes? )\((#(\d+))\)/ig;
|
|
203
|
+
for (const commit of commits) {
|
|
204
|
+
const { effect, breaking, hash, header, type } = commit;
|
|
205
|
+
const ref = !hash || pullRegxp.test(header)
|
|
206
|
+
? ''
|
|
207
|
+
: ` ([${hash?.substring(0, 7)}](${homepage}/commit/${hash}))`;
|
|
208
|
+
let message = header?.trim();
|
|
209
|
+
if (workspace && !effect) {
|
|
210
|
+
message = message.replace(/\(.+\)!?:/, ':');
|
|
211
|
+
}
|
|
212
|
+
message = message
|
|
213
|
+
.replace(pullRegxp, `[$1](${homepage}/pull/$2)`)
|
|
214
|
+
.replace(closeRegxp, `[$1$2](${homepage}/issues/$2)`) + ref;
|
|
215
|
+
if (breaking) {
|
|
216
|
+
notes.breaking.push(message);
|
|
217
|
+
}
|
|
218
|
+
else if (type === 'fix') {
|
|
219
|
+
notes.fixes.push(message);
|
|
220
|
+
}
|
|
221
|
+
else if (type === 'feat') {
|
|
222
|
+
notes.features.push(message);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
notes.updates.push(message);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
Object.keys(notes).forEach(i => {
|
|
229
|
+
notes[i] = notes[i].filter((j) => {
|
|
230
|
+
return !logFile.includes(j);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
const parts = [
|
|
234
|
+
notes.breaking.length ? `### Breaking Changes\n\n- ${notes.breaking.join('\n- ')}`.trim() : '',
|
|
235
|
+
notes.fixes.length ? `### Bugfixes\n\n- ${notes.fixes.join('\n- ')}`.trim() : '',
|
|
236
|
+
notes.features.length ? `### Features\n\n- ${notes.features.join('\n- ')}`.trim() : '',
|
|
237
|
+
notes.updates.length ? `### Updates\n\n- ${notes.updates.join('\n- ')}`.trim() : ''
|
|
238
|
+
].filter(Boolean);
|
|
239
|
+
const newLog = parts.join('\n\n');
|
|
240
|
+
return !parts.length || logFile.includes(newLog)
|
|
241
|
+
? ''
|
|
242
|
+
: newLog;
|
|
243
|
+
}
|
|
244
|
+
async updateVersion() {
|
|
245
|
+
const { packageOptions, commits, commandOptions } = this;
|
|
246
|
+
const { version } = packageOptions;
|
|
247
|
+
let newVersion = '';
|
|
248
|
+
if (commandOptions.customVersion) {
|
|
249
|
+
newVersion = commandOptions.customVersion;
|
|
250
|
+
if (!(/\d+.\d+.\d+/.test(newVersion)) || version === newVersion) {
|
|
251
|
+
let result = await prompt([
|
|
252
|
+
{
|
|
253
|
+
type: 'input',
|
|
254
|
+
name: 'version',
|
|
255
|
+
message: `Custom Update Version(${this.packageName}@${version}):`,
|
|
256
|
+
default: '',
|
|
257
|
+
validate: (answer) => {
|
|
258
|
+
if (!(/\d+.\d+.\d+/.test(answer))) {
|
|
259
|
+
return 'Version Should Be Like x.x.x';
|
|
260
|
+
}
|
|
261
|
+
if (answer === version) {
|
|
262
|
+
return 'Version Should Be Diff Than Before';
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
]);
|
|
268
|
+
newVersion = result.version;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
const intersection = [
|
|
273
|
+
commandOptions.major && 'major',
|
|
274
|
+
commandOptions.minor && 'minor',
|
|
275
|
+
commandOptions.patch && 'patch'
|
|
276
|
+
].filter(i => !!i);
|
|
277
|
+
if (intersection.length) {
|
|
278
|
+
newVersion = semver.inc(version, intersection[0]) || '';
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const types = new Set(commits.map(({ type }) => type));
|
|
282
|
+
const breaking = commits.some((commit) => !!commit.breaking);
|
|
283
|
+
const level = breaking
|
|
284
|
+
? 'major'
|
|
285
|
+
: types.has('feat')
|
|
286
|
+
? 'minor'
|
|
287
|
+
: 'patch';
|
|
288
|
+
newVersion = semver.inc(version, level) || '';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
this.version = newVersion;
|
|
292
|
+
}
|
|
293
|
+
isChanged() {
|
|
294
|
+
return !!this.commits.length;
|
|
295
|
+
}
|
|
296
|
+
async updateCommits(commits, source) {
|
|
297
|
+
if (!commits.length)
|
|
298
|
+
return;
|
|
299
|
+
const { packageName } = this;
|
|
300
|
+
const olds = this.commits.map(i => JSON.stringify(i));
|
|
301
|
+
const newCommits = commits
|
|
302
|
+
.filter(i => {
|
|
303
|
+
return !olds.includes(JSON.stringify(i));
|
|
304
|
+
})
|
|
305
|
+
.map(j => {
|
|
306
|
+
return {
|
|
307
|
+
...j,
|
|
308
|
+
effect: !!source
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
if (newCommits.length && this.commits.length) {
|
|
312
|
+
this.commits = this.commits.filter(i => !i.custom);
|
|
313
|
+
}
|
|
314
|
+
const commits$ = this.commits.concat(newCommits);
|
|
315
|
+
if (source) {
|
|
316
|
+
devShared.Logger.log(chalk.magenta(`MERGE COMMITS: `)
|
|
317
|
+
+ chalk.bold(`${commits.length}`) + ` Commits. `
|
|
318
|
+
+ 'merge ' + chalk.yellow(source) + ' into ' + chalk.green(packageName));
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
devShared.Logger.log(``);
|
|
322
|
+
}
|
|
323
|
+
const changeLog = this.rebuildChangeLog(commits$);
|
|
324
|
+
if (changeLog) {
|
|
325
|
+
this.commits = commits$;
|
|
326
|
+
this.changeLog = changeLog;
|
|
327
|
+
}
|
|
328
|
+
else if (commits.length) {
|
|
329
|
+
devShared.Logger.log(chalk.red(`${commits.length} Commits Already Exists.`));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async updatePackageOptions(relationVerisons = {}) {
|
|
333
|
+
if (!this.isChanged())
|
|
334
|
+
return;
|
|
335
|
+
const { packageDir, packageOptions, commandOptions } = this;
|
|
336
|
+
const { dependencies, devDependencies } = packageOptions;
|
|
337
|
+
const newVersion = this.version;
|
|
338
|
+
devShared.Logger.log(chalk.yellow(`New Version: `) + `${newVersion}`);
|
|
339
|
+
packageOptions.version = newVersion;
|
|
340
|
+
if (Object.keys(this.packageRelation).length) {
|
|
341
|
+
for (let packageName$ in relationVerisons) {
|
|
342
|
+
let newVersion$ = relationVerisons[packageName$];
|
|
343
|
+
if (dependencies?.[packageName$]) {
|
|
344
|
+
dependencies[packageName$] = newVersion$;
|
|
345
|
+
}
|
|
346
|
+
if (devDependencies?.[packageName$]) {
|
|
347
|
+
devDependencies[packageName$] = newVersion$;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (commandOptions.dryRun) {
|
|
352
|
+
devShared.Logger.log(chalk.yellow(`Skipping package.json Update`));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
devShared.Logger.log(chalk.yellow(`Updating `) + 'package.json');
|
|
356
|
+
fs.outputFileSync(`${packageDir}/package.json`, JSON.stringify(packageOptions, null, 2));
|
|
357
|
+
}
|
|
358
|
+
async updateChangelog() {
|
|
359
|
+
if (!this.isChanged())
|
|
360
|
+
return;
|
|
361
|
+
const { packageName, packageDir, packageOptions, commandOptions } = this;
|
|
362
|
+
const title = `# ${packageName} ChangeLog`;
|
|
363
|
+
const [date] = new Date().toISOString().split('T');
|
|
364
|
+
const logPath = path__namespace.resolve(packageDir, './CHANGELOG.md');
|
|
365
|
+
const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '';
|
|
366
|
+
const oldNotes = logFile.startsWith(title) ? logFile.slice(title.length).trim() : logFile;
|
|
367
|
+
const parts = [
|
|
368
|
+
`## v${packageOptions.version}`,
|
|
369
|
+
`_${date}_`,
|
|
370
|
+
this.changeLog
|
|
371
|
+
].filter(Boolean);
|
|
372
|
+
const newLog = parts.join('\n\n');
|
|
373
|
+
if (commandOptions.dryRun) {
|
|
374
|
+
devShared.Logger.log(chalk.yellow(`New ChangeLog:`) + `\n${newLog}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
devShared.Logger.log(chalk.yellow(`Updating `) + `CHANGELOG.md`);
|
|
378
|
+
let content = [title, newLog, oldNotes].filter(Boolean).join('\n\n');
|
|
379
|
+
if (!content.endsWith('\n'))
|
|
380
|
+
content += '\n';
|
|
381
|
+
fs.writeFileSync(logPath, content, 'utf-8');
|
|
382
|
+
}
|
|
383
|
+
async test() {
|
|
384
|
+
if (!this.isChanged())
|
|
385
|
+
return;
|
|
386
|
+
const { commandOptions } = this;
|
|
387
|
+
if (commandOptions.dryRun) {
|
|
388
|
+
devShared.Logger.log(chalk.yellow('Skipping Test'));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
devShared.Logger.log(chalk.yellow('Test...'));
|
|
393
|
+
}
|
|
394
|
+
await devShared.Shell.exec(`npm run test -- --package-name ${this.packageName}`);
|
|
395
|
+
}
|
|
396
|
+
async build() {
|
|
397
|
+
if (!this.isChanged())
|
|
398
|
+
return;
|
|
399
|
+
const { commandOptions } = this;
|
|
400
|
+
if (commandOptions.dryRun) {
|
|
401
|
+
devShared.Logger.log(chalk.yellow('Skipping Build'));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
devShared.Logger.log(chalk.yellow('Build...'));
|
|
406
|
+
}
|
|
407
|
+
await devShared.Shell.exec(`npm run build -- --package-name ${this.packageName}`);
|
|
408
|
+
}
|
|
409
|
+
async publish() {
|
|
410
|
+
if (!this.isChanged())
|
|
411
|
+
return;
|
|
412
|
+
const { commandOptions, packageDir } = this;
|
|
413
|
+
if (commandOptions.dryRun || !commandOptions.publish) {
|
|
414
|
+
devShared.Logger.log(chalk.yellow(`Skipping Publish`));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
devShared.Logger.log(chalk.cyan(`\n Publishing to NPM`));
|
|
418
|
+
await devShared.Shell.spawn('npm', ['publish', '--no-git-checks', '--access', 'public'], {
|
|
419
|
+
cwd: packageDir
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async tag() {
|
|
423
|
+
if (!this.isChanged())
|
|
424
|
+
return;
|
|
425
|
+
const { commandOptions, packageDir } = this;
|
|
426
|
+
const { packageName, packageOptions } = this;
|
|
427
|
+
if (commandOptions.dryRun || !commandOptions.tag) {
|
|
428
|
+
devShared.Logger.log(chalk.yellow(`Skipping Git Tag`));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const tagName = `${packageName}@${packageOptions.version}`;
|
|
432
|
+
devShared.Logger.log(chalk.blue(`\n Tagging`) + chalk.grey(`${tagName}`));
|
|
433
|
+
await devShared.Shell.spawn('git', ['tag', tagName], {
|
|
434
|
+
cwd: packageDir
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
async process() {
|
|
438
|
+
const { workspace } = devShared.Locals.impl();
|
|
439
|
+
const { packageName, packageDir, packageFolderName } = this;
|
|
440
|
+
if (!packageDir || !fs.pathExists(packageDir)) {
|
|
441
|
+
throw new RangeError(`Could not find directory for package: ${packageFolderName}`);
|
|
442
|
+
}
|
|
443
|
+
devShared.Logger.log(chalk.cyan(`Releasing ${packageName}`) + ' from ' + chalk.grey(`${workspace}/${packageFolderName}`));
|
|
444
|
+
await this.parseCommits();
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const release = (options, commandOptions) => {
|
|
449
|
+
return new Release(options, commandOptions);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const run = (options) => devShared.Utils.autoCatch(async () => {
|
|
453
|
+
options = {
|
|
454
|
+
dryRun: true,
|
|
455
|
+
tag: true,
|
|
456
|
+
publish: true,
|
|
457
|
+
commit: true,
|
|
458
|
+
push: true,
|
|
459
|
+
...options
|
|
460
|
+
};
|
|
461
|
+
const locals = devShared.Locals.impl();
|
|
462
|
+
if (options.dryRun) {
|
|
463
|
+
devShared.Logger.log(chalk.magenta(`DRY RUN: `)
|
|
464
|
+
+ 'No files will be modified.');
|
|
465
|
+
}
|
|
466
|
+
let inputs = [];
|
|
467
|
+
if (locals.workspace) {
|
|
468
|
+
inputs = locals.normalizePackageFolderNames;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
inputs = [''];
|
|
472
|
+
}
|
|
473
|
+
const instances = {};
|
|
474
|
+
await inputs
|
|
475
|
+
.reduce((preProcess, packageFolderName) => {
|
|
476
|
+
preProcess = preProcess
|
|
477
|
+
.then(() => release(packageFolderName, options).process())
|
|
478
|
+
.then((instance) => {
|
|
479
|
+
instances[packageFolderName] = instance;
|
|
480
|
+
});
|
|
481
|
+
return preProcess;
|
|
482
|
+
}, Promise.resolve());
|
|
483
|
+
devShared.Logger.log(chalk.blue(`---------------------\n`));
|
|
484
|
+
let message = `chore(release): publish\n\n`;
|
|
485
|
+
let relationVerisons = {};
|
|
486
|
+
await inputs.reduce((preProcess, packageFolderName) => {
|
|
487
|
+
const instance = instances[packageFolderName];
|
|
488
|
+
instance.packageRelation.forEach(i => {
|
|
489
|
+
let packageFolderName$ = devShared.Locals.getPackageFolderName(i);
|
|
490
|
+
let instance$ = instances[packageFolderName$];
|
|
491
|
+
if (instance$.commits.length > 0) {
|
|
492
|
+
instance.updateCommits(instance$.commits, instance$.packageName);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
if (instance.commits.length) {
|
|
496
|
+
preProcess = preProcess
|
|
497
|
+
.then(() => devShared.Logger.log(chalk.magenta(`CHANGED: `) + instance.packageName))
|
|
498
|
+
.then(() => instance.test())
|
|
499
|
+
.then(() => instance.build())
|
|
500
|
+
.then(() => instance.updatePackageOptions(relationVerisons))
|
|
501
|
+
.then(() => instance.updateChangelog())
|
|
502
|
+
.then(() => {
|
|
503
|
+
message += `- ${instance.packageName}@${instance.packageOptions.version}\n`;
|
|
504
|
+
relationVerisons[instance.packageName] = `^${instance.packageOptions.version}`;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return preProcess;
|
|
508
|
+
}, Promise.resolve());
|
|
509
|
+
devShared.Logger.log(chalk.blue(`\n---------------------\n`));
|
|
510
|
+
const isChanged = Object.keys(relationVerisons).length;
|
|
511
|
+
if (!isChanged) {
|
|
512
|
+
devShared.Logger.log(chalk.magenta(`COMMIT: `) + 'Nothing Chanaged Found.');
|
|
513
|
+
}
|
|
514
|
+
else if (options.dryRun || !options.commit) {
|
|
515
|
+
devShared.Logger.log(chalk.magenta(`COMMIT: `) + chalk.yellow(`Skipping Git Commit`) + `\n${message}`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
devShared.Logger.log(chalk.magenta(`CHANGED: `) + `pnpm-lock.yaml`);
|
|
519
|
+
await devShared.Shell.spawn('npx', ['pnpm', 'install', '--lockfile-only']);
|
|
520
|
+
devShared.Logger.log(chalk.magenta(`COMMIT: `) + `CHANGELOG.md, package.json, pnpm-lock.yaml`);
|
|
521
|
+
await devShared.Shell.spawn('git', ['add', process.cwd()]);
|
|
522
|
+
await devShared.Shell.spawn('git', ['commit', '--m', `'${message}'`]);
|
|
523
|
+
}
|
|
524
|
+
await inputs
|
|
525
|
+
.reduce((preProcess, packageFolderName) => {
|
|
526
|
+
const instance = instances[packageFolderName];
|
|
527
|
+
preProcess = preProcess
|
|
528
|
+
.then(() => instance.publish())
|
|
529
|
+
.then(() => instance.tag());
|
|
530
|
+
return preProcess;
|
|
531
|
+
}, Promise.resolve());
|
|
532
|
+
devShared.Logger.log(chalk.blue(`\n---------------------\n`));
|
|
533
|
+
if (options.dryRun || !options.push) {
|
|
534
|
+
devShared.Logger.log(chalk.magenta(`FINISH: `) + 'Skipping Git Push');
|
|
535
|
+
}
|
|
536
|
+
else if (!isChanged) {
|
|
537
|
+
devShared.Logger.log(chalk.magenta(`FINISH: `) + 'Nothing Chanaged.');
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
await devShared.Shell.spawn('git', ['push']);
|
|
541
|
+
await devShared.Shell.spawn('git', ['push', '--tags']);
|
|
542
|
+
}
|
|
543
|
+
if (options.dryRun) {
|
|
544
|
+
devShared.Logger.log(chalk.green('NO DRY RUN WAY: ')
|
|
545
|
+
+ chalk.grey(`npm run release -- --no-dry-run\n`));
|
|
546
|
+
}
|
|
547
|
+
}, {
|
|
548
|
+
onError: (e) => {
|
|
549
|
+
if (typeof e === 'number' && e === 1) {
|
|
550
|
+
devShared.Logger.error('发布失败');
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
devShared.Logger.error(e);
|
|
554
|
+
}
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
exports.run = run;
|
package/dist/index.d.ts
ADDED
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Locals, Shell, Logger, Utils } from '@deot/dev-shared';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import parser from 'conventional-commits-parser';
|
|
7
|
+
import semver from 'semver';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const require$ = createRequire(cwd);
|
|
12
|
+
const { prompt } = inquirer;
|
|
13
|
+
const HASH = '-hash-';
|
|
14
|
+
const SUFFIX = '🐒💨🙊';
|
|
15
|
+
const parserOptions = {
|
|
16
|
+
noteKeywords: ['BREAKING CHANGE', 'Breaking Change']
|
|
17
|
+
};
|
|
18
|
+
const reBreaking = new RegExp(`(${parserOptions.noteKeywords.join(')|(')})`);
|
|
19
|
+
class Release {
|
|
20
|
+
packageDir;
|
|
21
|
+
packageName;
|
|
22
|
+
packageFolderName;
|
|
23
|
+
packageOptions;
|
|
24
|
+
packageRelation;
|
|
25
|
+
config;
|
|
26
|
+
changeLog;
|
|
27
|
+
version;
|
|
28
|
+
commits;
|
|
29
|
+
commandOptions;
|
|
30
|
+
constructor(config, commandOptions) {
|
|
31
|
+
const { packageDir, packageRelation } = Locals.impl();
|
|
32
|
+
if (typeof config === 'string') {
|
|
33
|
+
let packageFolderName = config;
|
|
34
|
+
let packageDir$ = path.resolve(packageDir, packageFolderName);
|
|
35
|
+
config = {
|
|
36
|
+
dir: packageDir$,
|
|
37
|
+
name: packageFolderName
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
this.packageDir = config.dir;
|
|
41
|
+
this.packageName = Locals.getPackageName(config.name);
|
|
42
|
+
this.packageFolderName = config.name;
|
|
43
|
+
this.packageOptions = require$(`${this.packageDir}/package.json`);
|
|
44
|
+
this.packageRelation = packageRelation[this.packageName] || [];
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.commits = [];
|
|
47
|
+
this.changeLog = '';
|
|
48
|
+
this.version = '';
|
|
49
|
+
this.commandOptions = commandOptions;
|
|
50
|
+
}
|
|
51
|
+
async parseCommits() {
|
|
52
|
+
const { workspace } = Locals.impl();
|
|
53
|
+
const { packageFolderName, packageName, commandOptions } = this;
|
|
54
|
+
let params = ['tag', '--list', `'${packageName}@*'`, '--sort', '-v:refname'];
|
|
55
|
+
const { stdout: tags } = await Shell.exec('git', params);
|
|
56
|
+
const [latestTag] = tags.split('\n');
|
|
57
|
+
Logger.log(chalk.yellow(`Last Release Tag`) + `: ${latestTag || '<none>'}`);
|
|
58
|
+
params = ['--no-pager', 'log', `${latestTag}..HEAD`, `--format=%B%n${HASH}%n%H${SUFFIX}`];
|
|
59
|
+
let { stdout } = await Shell.exec('git', params);
|
|
60
|
+
let skipGetLog = false;
|
|
61
|
+
if (latestTag) {
|
|
62
|
+
const log1 = await Shell.exec('git', ['rev-parse', latestTag]);
|
|
63
|
+
const log2 = await Shell.exec('git', ['--no-pager', 'log', '-1', '--format=%H']);
|
|
64
|
+
if (log1.stdout === log2.stdout) {
|
|
65
|
+
skipGetLog = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!skipGetLog && !stdout) {
|
|
69
|
+
if (latestTag) {
|
|
70
|
+
params.splice(2, 1, `${latestTag}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
params.splice(2, 1, 'HEAD');
|
|
74
|
+
}
|
|
75
|
+
({ stdout } = await Shell.exec('git', params));
|
|
76
|
+
}
|
|
77
|
+
const allowTypes = ['feat', `fix`, `break change`, `style`, `perf`, `types`, `refactor`, `chore`];
|
|
78
|
+
const rePlugin = new RegExp(`^(${allowTypes.join('|')})${workspace ? `\\(${packageFolderName}\\)` : '(\\(.+\\))?'}: .*`, 'i');
|
|
79
|
+
const allCommits = stdout.split(SUFFIX);
|
|
80
|
+
const commits = allCommits
|
|
81
|
+
.filter((commit) => {
|
|
82
|
+
const chunk = commit.trim();
|
|
83
|
+
return chunk && rePlugin.test(chunk);
|
|
84
|
+
})
|
|
85
|
+
.map((commit) => {
|
|
86
|
+
const node = parser.sync(commit);
|
|
87
|
+
const body = (node.body || node.footer);
|
|
88
|
+
if (!node.type)
|
|
89
|
+
node.type = parser.sync(node.header?.replace(/\(.+\)!?:/, ':') || '').type;
|
|
90
|
+
if (!node.hash)
|
|
91
|
+
node.hash = commit.split(HASH).pop()?.trim();
|
|
92
|
+
node.breaking = reBreaking.test(body) || /!:/.test(node.header);
|
|
93
|
+
node.effect = false;
|
|
94
|
+
node.custom = false;
|
|
95
|
+
return node;
|
|
96
|
+
});
|
|
97
|
+
if (!commits.length) {
|
|
98
|
+
Logger.log(chalk.red(`No Commits Found.`));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
Logger.log(chalk.yellow(`Found `)
|
|
102
|
+
+ chalk.bold(`${allCommits.length}`)
|
|
103
|
+
+ ` Commits, `
|
|
104
|
+
+ chalk.bold(`${commits.length}`)
|
|
105
|
+
+ ' Commits Valid');
|
|
106
|
+
}
|
|
107
|
+
const { skipUpdatePackage } = commandOptions;
|
|
108
|
+
if (commits.length && skipUpdatePackage) {
|
|
109
|
+
let skip = false;
|
|
110
|
+
if (typeof skipUpdatePackage === 'boolean' && skipUpdatePackage) {
|
|
111
|
+
let result = await prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'skip',
|
|
115
|
+
message: `Skip Update(${this.packageName}@${this.packageOptions.version}):`,
|
|
116
|
+
default: true
|
|
117
|
+
}
|
|
118
|
+
]);
|
|
119
|
+
skip = result.skip;
|
|
120
|
+
}
|
|
121
|
+
else if (typeof skipUpdatePackage === 'string'
|
|
122
|
+
&& (skipUpdatePackage === '**'
|
|
123
|
+
|| skipUpdatePackage.split(',').includes(this.packageName))) {
|
|
124
|
+
skip = true;
|
|
125
|
+
}
|
|
126
|
+
if (skip) {
|
|
127
|
+
Logger.log(chalk.red(`Skipping Update\n`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await this.updateVersion();
|
|
132
|
+
await this.updateCommits(commits);
|
|
133
|
+
const { forceUpdatePackage } = commandOptions;
|
|
134
|
+
if (!commits.length && forceUpdatePackage) {
|
|
135
|
+
let force = false;
|
|
136
|
+
if (typeof forceUpdatePackage === 'boolean' && forceUpdatePackage) {
|
|
137
|
+
let result = await prompt([
|
|
138
|
+
{
|
|
139
|
+
type: 'confirm',
|
|
140
|
+
name: 'force',
|
|
141
|
+
message: `Force Update(${this.packageName}@${this.packageOptions.version}):`,
|
|
142
|
+
default: true
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
force = result.force;
|
|
146
|
+
}
|
|
147
|
+
else if (typeof forceUpdatePackage === 'string'
|
|
148
|
+
&& (forceUpdatePackage === '**'
|
|
149
|
+
|| forceUpdatePackage.split(',').includes(this.packageName))) {
|
|
150
|
+
force = true;
|
|
151
|
+
}
|
|
152
|
+
if (force) {
|
|
153
|
+
const oldVersion = this.packageOptions.version;
|
|
154
|
+
const versionChanged = `\`${oldVersion}\` -> \`${this.version}\``;
|
|
155
|
+
this.commits = [
|
|
156
|
+
{
|
|
157
|
+
type: 'chore',
|
|
158
|
+
header: `chore(${this.packageFolderName || 'release'}): force-publish ${versionChanged}`,
|
|
159
|
+
hash: '',
|
|
160
|
+
effect: false,
|
|
161
|
+
breaking: false,
|
|
162
|
+
custom: true
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
this.changeLog = `### Force Update Package\n\n- ${versionChanged}`.trim();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
rebuildChangeLog(commits) {
|
|
170
|
+
const { packageDir } = this;
|
|
171
|
+
const { homepage, workspace } = Locals.impl();
|
|
172
|
+
const logPath = path.resolve(packageDir, './CHANGELOG.md');
|
|
173
|
+
const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '';
|
|
174
|
+
const notes = {
|
|
175
|
+
breaking: [],
|
|
176
|
+
features: [],
|
|
177
|
+
fixes: [],
|
|
178
|
+
updates: []
|
|
179
|
+
};
|
|
180
|
+
const closeRegxp = /\(?(closes? )\(?#((\d+))\)/ig;
|
|
181
|
+
const pullRegxp = /(?<!closes? )\((#(\d+))\)/ig;
|
|
182
|
+
for (const commit of commits) {
|
|
183
|
+
const { effect, breaking, hash, header, type } = commit;
|
|
184
|
+
const ref = !hash || pullRegxp.test(header)
|
|
185
|
+
? ''
|
|
186
|
+
: ` ([${hash?.substring(0, 7)}](${homepage}/commit/${hash}))`;
|
|
187
|
+
let message = header?.trim();
|
|
188
|
+
if (workspace && !effect) {
|
|
189
|
+
message = message.replace(/\(.+\)!?:/, ':');
|
|
190
|
+
}
|
|
191
|
+
message = message
|
|
192
|
+
.replace(pullRegxp, `[$1](${homepage}/pull/$2)`)
|
|
193
|
+
.replace(closeRegxp, `[$1$2](${homepage}/issues/$2)`) + ref;
|
|
194
|
+
if (breaking) {
|
|
195
|
+
notes.breaking.push(message);
|
|
196
|
+
}
|
|
197
|
+
else if (type === 'fix') {
|
|
198
|
+
notes.fixes.push(message);
|
|
199
|
+
}
|
|
200
|
+
else if (type === 'feat') {
|
|
201
|
+
notes.features.push(message);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
notes.updates.push(message);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
Object.keys(notes).forEach(i => {
|
|
208
|
+
notes[i] = notes[i].filter((j) => {
|
|
209
|
+
return !logFile.includes(j);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
const parts = [
|
|
213
|
+
notes.breaking.length ? `### Breaking Changes\n\n- ${notes.breaking.join('\n- ')}`.trim() : '',
|
|
214
|
+
notes.fixes.length ? `### Bugfixes\n\n- ${notes.fixes.join('\n- ')}`.trim() : '',
|
|
215
|
+
notes.features.length ? `### Features\n\n- ${notes.features.join('\n- ')}`.trim() : '',
|
|
216
|
+
notes.updates.length ? `### Updates\n\n- ${notes.updates.join('\n- ')}`.trim() : ''
|
|
217
|
+
].filter(Boolean);
|
|
218
|
+
const newLog = parts.join('\n\n');
|
|
219
|
+
return !parts.length || logFile.includes(newLog)
|
|
220
|
+
? ''
|
|
221
|
+
: newLog;
|
|
222
|
+
}
|
|
223
|
+
async updateVersion() {
|
|
224
|
+
const { packageOptions, commits, commandOptions } = this;
|
|
225
|
+
const { version } = packageOptions;
|
|
226
|
+
let newVersion = '';
|
|
227
|
+
if (commandOptions.customVersion) {
|
|
228
|
+
newVersion = commandOptions.customVersion;
|
|
229
|
+
if (!(/\d+.\d+.\d+/.test(newVersion)) || version === newVersion) {
|
|
230
|
+
let result = await prompt([
|
|
231
|
+
{
|
|
232
|
+
type: 'input',
|
|
233
|
+
name: 'version',
|
|
234
|
+
message: `Custom Update Version(${this.packageName}@${version}):`,
|
|
235
|
+
default: '',
|
|
236
|
+
validate: (answer) => {
|
|
237
|
+
if (!(/\d+.\d+.\d+/.test(answer))) {
|
|
238
|
+
return 'Version Should Be Like x.x.x';
|
|
239
|
+
}
|
|
240
|
+
if (answer === version) {
|
|
241
|
+
return 'Version Should Be Diff Than Before';
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]);
|
|
247
|
+
newVersion = result.version;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
const intersection = [
|
|
252
|
+
commandOptions.major && 'major',
|
|
253
|
+
commandOptions.minor && 'minor',
|
|
254
|
+
commandOptions.patch && 'patch'
|
|
255
|
+
].filter(i => !!i);
|
|
256
|
+
if (intersection.length) {
|
|
257
|
+
newVersion = semver.inc(version, intersection[0]) || '';
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const types = new Set(commits.map(({ type }) => type));
|
|
261
|
+
const breaking = commits.some((commit) => !!commit.breaking);
|
|
262
|
+
const level = breaking
|
|
263
|
+
? 'major'
|
|
264
|
+
: types.has('feat')
|
|
265
|
+
? 'minor'
|
|
266
|
+
: 'patch';
|
|
267
|
+
newVersion = semver.inc(version, level) || '';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.version = newVersion;
|
|
271
|
+
}
|
|
272
|
+
isChanged() {
|
|
273
|
+
return !!this.commits.length;
|
|
274
|
+
}
|
|
275
|
+
async updateCommits(commits, source) {
|
|
276
|
+
if (!commits.length)
|
|
277
|
+
return;
|
|
278
|
+
const { packageName } = this;
|
|
279
|
+
const olds = this.commits.map(i => JSON.stringify(i));
|
|
280
|
+
const newCommits = commits
|
|
281
|
+
.filter(i => {
|
|
282
|
+
return !olds.includes(JSON.stringify(i));
|
|
283
|
+
})
|
|
284
|
+
.map(j => {
|
|
285
|
+
return {
|
|
286
|
+
...j,
|
|
287
|
+
effect: !!source
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
if (newCommits.length && this.commits.length) {
|
|
291
|
+
this.commits = this.commits.filter(i => !i.custom);
|
|
292
|
+
}
|
|
293
|
+
const commits$ = this.commits.concat(newCommits);
|
|
294
|
+
if (source) {
|
|
295
|
+
Logger.log(chalk.magenta(`MERGE COMMITS: `)
|
|
296
|
+
+ chalk.bold(`${commits.length}`) + ` Commits. `
|
|
297
|
+
+ 'merge ' + chalk.yellow(source) + ' into ' + chalk.green(packageName));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
Logger.log(``);
|
|
301
|
+
}
|
|
302
|
+
const changeLog = this.rebuildChangeLog(commits$);
|
|
303
|
+
if (changeLog) {
|
|
304
|
+
this.commits = commits$;
|
|
305
|
+
this.changeLog = changeLog;
|
|
306
|
+
}
|
|
307
|
+
else if (commits.length) {
|
|
308
|
+
Logger.log(chalk.red(`${commits.length} Commits Already Exists.`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async updatePackageOptions(relationVerisons = {}) {
|
|
312
|
+
if (!this.isChanged())
|
|
313
|
+
return;
|
|
314
|
+
const { packageDir, packageOptions, commandOptions } = this;
|
|
315
|
+
const { dependencies, devDependencies } = packageOptions;
|
|
316
|
+
const newVersion = this.version;
|
|
317
|
+
Logger.log(chalk.yellow(`New Version: `) + `${newVersion}`);
|
|
318
|
+
packageOptions.version = newVersion;
|
|
319
|
+
if (Object.keys(this.packageRelation).length) {
|
|
320
|
+
for (let packageName$ in relationVerisons) {
|
|
321
|
+
let newVersion$ = relationVerisons[packageName$];
|
|
322
|
+
if (dependencies?.[packageName$]) {
|
|
323
|
+
dependencies[packageName$] = newVersion$;
|
|
324
|
+
}
|
|
325
|
+
if (devDependencies?.[packageName$]) {
|
|
326
|
+
devDependencies[packageName$] = newVersion$;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (commandOptions.dryRun) {
|
|
331
|
+
Logger.log(chalk.yellow(`Skipping package.json Update`));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
Logger.log(chalk.yellow(`Updating `) + 'package.json');
|
|
335
|
+
fs.outputFileSync(`${packageDir}/package.json`, JSON.stringify(packageOptions, null, 2));
|
|
336
|
+
}
|
|
337
|
+
async updateChangelog() {
|
|
338
|
+
if (!this.isChanged())
|
|
339
|
+
return;
|
|
340
|
+
const { packageName, packageDir, packageOptions, commandOptions } = this;
|
|
341
|
+
const title = `# ${packageName} ChangeLog`;
|
|
342
|
+
const [date] = new Date().toISOString().split('T');
|
|
343
|
+
const logPath = path.resolve(packageDir, './CHANGELOG.md');
|
|
344
|
+
const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '';
|
|
345
|
+
const oldNotes = logFile.startsWith(title) ? logFile.slice(title.length).trim() : logFile;
|
|
346
|
+
const parts = [
|
|
347
|
+
`## v${packageOptions.version}`,
|
|
348
|
+
`_${date}_`,
|
|
349
|
+
this.changeLog
|
|
350
|
+
].filter(Boolean);
|
|
351
|
+
const newLog = parts.join('\n\n');
|
|
352
|
+
if (commandOptions.dryRun) {
|
|
353
|
+
Logger.log(chalk.yellow(`New ChangeLog:`) + `\n${newLog}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
Logger.log(chalk.yellow(`Updating `) + `CHANGELOG.md`);
|
|
357
|
+
let content = [title, newLog, oldNotes].filter(Boolean).join('\n\n');
|
|
358
|
+
if (!content.endsWith('\n'))
|
|
359
|
+
content += '\n';
|
|
360
|
+
fs.writeFileSync(logPath, content, 'utf-8');
|
|
361
|
+
}
|
|
362
|
+
async test() {
|
|
363
|
+
if (!this.isChanged())
|
|
364
|
+
return;
|
|
365
|
+
const { commandOptions } = this;
|
|
366
|
+
if (commandOptions.dryRun) {
|
|
367
|
+
Logger.log(chalk.yellow('Skipping Test'));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
Logger.log(chalk.yellow('Test...'));
|
|
372
|
+
}
|
|
373
|
+
await Shell.exec(`npm run test -- --package-name ${this.packageName}`);
|
|
374
|
+
}
|
|
375
|
+
async build() {
|
|
376
|
+
if (!this.isChanged())
|
|
377
|
+
return;
|
|
378
|
+
const { commandOptions } = this;
|
|
379
|
+
if (commandOptions.dryRun) {
|
|
380
|
+
Logger.log(chalk.yellow('Skipping Build'));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
Logger.log(chalk.yellow('Build...'));
|
|
385
|
+
}
|
|
386
|
+
await Shell.exec(`npm run build -- --package-name ${this.packageName}`);
|
|
387
|
+
}
|
|
388
|
+
async publish() {
|
|
389
|
+
if (!this.isChanged())
|
|
390
|
+
return;
|
|
391
|
+
const { commandOptions, packageDir } = this;
|
|
392
|
+
if (commandOptions.dryRun || !commandOptions.publish) {
|
|
393
|
+
Logger.log(chalk.yellow(`Skipping Publish`));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
Logger.log(chalk.cyan(`\n Publishing to NPM`));
|
|
397
|
+
await Shell.spawn('npm', ['publish', '--no-git-checks', '--access', 'public'], {
|
|
398
|
+
cwd: packageDir
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async tag() {
|
|
402
|
+
if (!this.isChanged())
|
|
403
|
+
return;
|
|
404
|
+
const { commandOptions, packageDir } = this;
|
|
405
|
+
const { packageName, packageOptions } = this;
|
|
406
|
+
if (commandOptions.dryRun || !commandOptions.tag) {
|
|
407
|
+
Logger.log(chalk.yellow(`Skipping Git Tag`));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const tagName = `${packageName}@${packageOptions.version}`;
|
|
411
|
+
Logger.log(chalk.blue(`\n Tagging`) + chalk.grey(`${tagName}`));
|
|
412
|
+
await Shell.spawn('git', ['tag', tagName], {
|
|
413
|
+
cwd: packageDir
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
async process() {
|
|
417
|
+
const { workspace } = Locals.impl();
|
|
418
|
+
const { packageName, packageDir, packageFolderName } = this;
|
|
419
|
+
if (!packageDir || !fs.pathExists(packageDir)) {
|
|
420
|
+
throw new RangeError(`Could not find directory for package: ${packageFolderName}`);
|
|
421
|
+
}
|
|
422
|
+
Logger.log(chalk.cyan(`Releasing ${packageName}`) + ' from ' + chalk.grey(`${workspace}/${packageFolderName}`));
|
|
423
|
+
await this.parseCommits();
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const release = (options, commandOptions) => {
|
|
428
|
+
return new Release(options, commandOptions);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const run = (options) => Utils.autoCatch(async () => {
|
|
432
|
+
options = {
|
|
433
|
+
dryRun: true,
|
|
434
|
+
tag: true,
|
|
435
|
+
publish: true,
|
|
436
|
+
commit: true,
|
|
437
|
+
push: true,
|
|
438
|
+
...options
|
|
439
|
+
};
|
|
440
|
+
const locals = Locals.impl();
|
|
441
|
+
if (options.dryRun) {
|
|
442
|
+
Logger.log(chalk.magenta(`DRY RUN: `)
|
|
443
|
+
+ 'No files will be modified.');
|
|
444
|
+
}
|
|
445
|
+
let inputs = [];
|
|
446
|
+
if (locals.workspace) {
|
|
447
|
+
inputs = locals.normalizePackageFolderNames;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
inputs = [''];
|
|
451
|
+
}
|
|
452
|
+
const instances = {};
|
|
453
|
+
await inputs
|
|
454
|
+
.reduce((preProcess, packageFolderName) => {
|
|
455
|
+
preProcess = preProcess
|
|
456
|
+
.then(() => release(packageFolderName, options).process())
|
|
457
|
+
.then((instance) => {
|
|
458
|
+
instances[packageFolderName] = instance;
|
|
459
|
+
});
|
|
460
|
+
return preProcess;
|
|
461
|
+
}, Promise.resolve());
|
|
462
|
+
Logger.log(chalk.blue(`---------------------\n`));
|
|
463
|
+
let message = `chore(release): publish\n\n`;
|
|
464
|
+
let relationVerisons = {};
|
|
465
|
+
await inputs.reduce((preProcess, packageFolderName) => {
|
|
466
|
+
const instance = instances[packageFolderName];
|
|
467
|
+
instance.packageRelation.forEach(i => {
|
|
468
|
+
let packageFolderName$ = Locals.getPackageFolderName(i);
|
|
469
|
+
let instance$ = instances[packageFolderName$];
|
|
470
|
+
if (instance$.commits.length > 0) {
|
|
471
|
+
instance.updateCommits(instance$.commits, instance$.packageName);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
if (instance.commits.length) {
|
|
475
|
+
preProcess = preProcess
|
|
476
|
+
.then(() => Logger.log(chalk.magenta(`CHANGED: `) + instance.packageName))
|
|
477
|
+
.then(() => instance.test())
|
|
478
|
+
.then(() => instance.build())
|
|
479
|
+
.then(() => instance.updatePackageOptions(relationVerisons))
|
|
480
|
+
.then(() => instance.updateChangelog())
|
|
481
|
+
.then(() => {
|
|
482
|
+
message += `- ${instance.packageName}@${instance.packageOptions.version}\n`;
|
|
483
|
+
relationVerisons[instance.packageName] = `^${instance.packageOptions.version}`;
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
return preProcess;
|
|
487
|
+
}, Promise.resolve());
|
|
488
|
+
Logger.log(chalk.blue(`\n---------------------\n`));
|
|
489
|
+
const isChanged = Object.keys(relationVerisons).length;
|
|
490
|
+
if (!isChanged) {
|
|
491
|
+
Logger.log(chalk.magenta(`COMMIT: `) + 'Nothing Chanaged Found.');
|
|
492
|
+
}
|
|
493
|
+
else if (options.dryRun || !options.commit) {
|
|
494
|
+
Logger.log(chalk.magenta(`COMMIT: `) + chalk.yellow(`Skipping Git Commit`) + `\n${message}`);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
Logger.log(chalk.magenta(`CHANGED: `) + `pnpm-lock.yaml`);
|
|
498
|
+
await Shell.spawn('npx', ['pnpm', 'install', '--lockfile-only']);
|
|
499
|
+
Logger.log(chalk.magenta(`COMMIT: `) + `CHANGELOG.md, package.json, pnpm-lock.yaml`);
|
|
500
|
+
await Shell.spawn('git', ['add', process.cwd()]);
|
|
501
|
+
await Shell.spawn('git', ['commit', '--m', `'${message}'`]);
|
|
502
|
+
}
|
|
503
|
+
await inputs
|
|
504
|
+
.reduce((preProcess, packageFolderName) => {
|
|
505
|
+
const instance = instances[packageFolderName];
|
|
506
|
+
preProcess = preProcess
|
|
507
|
+
.then(() => instance.publish())
|
|
508
|
+
.then(() => instance.tag());
|
|
509
|
+
return preProcess;
|
|
510
|
+
}, Promise.resolve());
|
|
511
|
+
Logger.log(chalk.blue(`\n---------------------\n`));
|
|
512
|
+
if (options.dryRun || !options.push) {
|
|
513
|
+
Logger.log(chalk.magenta(`FINISH: `) + 'Skipping Git Push');
|
|
514
|
+
}
|
|
515
|
+
else if (!isChanged) {
|
|
516
|
+
Logger.log(chalk.magenta(`FINISH: `) + 'Nothing Chanaged.');
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
await Shell.spawn('git', ['push']);
|
|
520
|
+
await Shell.spawn('git', ['push', '--tags']);
|
|
521
|
+
}
|
|
522
|
+
if (options.dryRun) {
|
|
523
|
+
Logger.log(chalk.green('NO DRY RUN WAY: ')
|
|
524
|
+
+ chalk.grey(`npm run release -- --no-dry-run\n`));
|
|
525
|
+
}
|
|
526
|
+
}, {
|
|
527
|
+
onError: (e) => {
|
|
528
|
+
if (typeof e === 'number' && e === 1) {
|
|
529
|
+
Logger.error('发布失败');
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
Logger.error(e);
|
|
533
|
+
}
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
export { run };
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deot/dev-releaser",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"main": "dist/index.es.js",
|
|
5
|
+
"module": "dist/index.es.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@deot/dev-extract": "^1.1.0",
|
|
17
|
+
"@deot/dev-shared": "^1.1.0",
|
|
18
|
+
"conventional-commits-parser": "^3.2.4",
|
|
19
|
+
"semver": "^7.3.8"
|
|
20
|
+
}
|
|
21
|
+
}
|