-
Notifications
You must be signed in to change notification settings - Fork 11
/
index.js
215 lines (166 loc) · 5.85 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { createRequire } from 'module';
import { EOL } from 'os';
import fs from 'fs';
import which from 'which';
import { Plugin } from 'release-it';
import _ from 'lodash';
import tmp from 'tmp';
import execa from 'execa';
import { fromMarkdown } from 'mdast-util-from-markdown';
const template = _.template;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
import validatePeerDependencies from 'validate-peer-dependencies';
validatePeerDependencies(__dirname);
const require = createRequire(import.meta.url);
const LERNA_PATH = require.resolve('lerna-changelog/bin/cli');
// using a const here, because we may need to change this value in the future
// and this makes it much simpler
const UNRELEASED = 'Unreleased';
function getToday() {
const date = new Date().toISOString();
return date.slice(0, date.indexOf('T'));
}
export default class LernaChangelogGeneratorPlugin extends Plugin {
async init() {
let from = (await this.getTagForHEAD()) || (await this.getFirstCommit());
this.changelog = await this._execLernaChangelog(from);
// this supports release-it < 13.5.3
this.setContext({ changelog: this.changelog });
}
get nextVersion() {
let { version } = this.config.getContext();
let tagName = this.config.getContext('git.tagName');
let nextVersion = tagName ? template(tagName)({ version }) : version;
return nextVersion;
}
// this hook is supported by [email protected]+
getChangelog() {
return this.changelog;
}
async getTagForHEAD() {
try {
return await this.exec('git describe --tags --abbrev=0', { options: { write: false } });
} catch (error) {
return null;
}
}
async getFirstCommit() {
if (this._firstCommit) {
return this._firstCommit;
}
this._firstCommit = await this.exec(`git rev-list --max-parents=0 HEAD`, {
options: { write: false },
});
return this._firstCommit;
}
async _execLernaChangelog(from) {
let changelog = await this.exec(
`${process.execPath} ${LERNA_PATH} --next-version=${UNRELEASED} --from=${from}`,
{
options: { write: false },
}
);
return changelog;
}
async processChangelog() {
// this is populated in `init`
let changelog = this.changelog
? this.changelog.replace(UNRELEASED, this.nextVersion)
: `## ${this.nextVersion} (${getToday()})`;
let finalChangelog = await this.reviewChangelog(changelog);
return finalChangelog;
}
async _launchEditor(tmpFile) {
// do not launch the editor for dry runs
if (this.config.isDryRun) {
return;
}
let editorCommand;
if (typeof this.options.launchEditor === 'boolean') {
let EDITOR = process.env.EDITOR;
if (!EDITOR) {
EDITOR = which.sync('editor', { nothrow: true });
}
if (!EDITOR) {
let error = new Error(
`@release-it-plugins/lerna-changelog configured to launch your editor but no editor was found (tried $EDITOR and searching $PATH for \`editor\`).`
);
this.log.error(error.message);
throw error;
}
// `${file}` is interpolated just below
editorCommand = EDITOR + ' ${file}';
} else {
editorCommand = this.options.launchEditor;
}
editorCommand = editorCommand.replace('${file}', tmpFile);
await execa.command(editorCommand, { stdio: 'inherit' });
}
async reviewChangelog(changelog) {
if (!this.options.launchEditor) {
return changelog;
}
let tmpFile = tmp.fileSync().name;
fs.writeFileSync(tmpFile, changelog, { encoding: 'utf-8' });
await this._launchEditor(tmpFile);
let finalChangelog = fs.readFileSync(tmpFile, { encoding: 'utf-8' });
return finalChangelog;
}
async writeChangelog(changelog) {
const { infile } = this.options;
let hasInfile = false;
try {
fs.accessSync(infile);
hasInfile = true;
} catch (err) {
this.debug(err);
}
if (!hasInfile) {
// generate an initial CHANGELOG.md with all of the versions
let firstCommit = await this.getFirstCommit();
if (firstCommit) {
changelog = await this._execLernaChangelog(firstCommit, this.nextVersion);
changelog = changelog.replace(UNRELEASED, this.nextVersion);
this.debug({ changelog });
} else {
// do something when there is no commit? not sure what our options are...
}
}
if (this.config.isDryRun) {
this.log.log(`! Prepending ${infile} with release notes.`);
} else {
let currentFileData = hasInfile ? fs.readFileSync(infile, { encoding: 'utf8' }) : '';
let newContent = this._insertContent(changelog, currentFileData);
fs.writeFileSync(infile, newContent, { encoding: 'utf8' });
}
if (!hasInfile) {
await this.exec(`git add ${infile}`);
}
}
_insertContent(newContent, oldContent) {
let insertOffset = this._findInsertOffset(oldContent);
let before = oldContent.slice(0, insertOffset);
let after = oldContent.slice(insertOffset);
return before + newContent + EOL + EOL + after;
}
_findInsertOffset(oldContent) {
let ast = fromMarkdown(oldContent);
let firstH2 = ast.children.find((it) => it.type === 'heading' && it.depth === 2);
return firstH2 ? firstH2.position.start.offset : 0;
}
async beforeRelease() {
let processedChangelog = await this.processChangelog();
this.debug({ changelog: processedChangelog });
// remove first two lines to prevent release notes
// from including the version number/date (it looks odd
// in the Github/Gitlab UIs)
let changelogWithoutVersion = processedChangelog.split(EOL).slice(2).join(EOL);
this.config.setContext({ changelog: changelogWithoutVersion });
if (this.options.infile) {
await this.writeChangelog(processedChangelog);
}
}
}