@itcase/config 1.7.8 → 1.7.10
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 +1 -11
- package/package.json +2 -1
- package/semantic-release/common.js +9 -7
- package/semantic-release/releaseNotesFromCommits.js +116 -0
- package/semantic-release/{issuesReleaseNotes.js → releaseNotesFromIssues.js} +15 -7
- package/semantic-release/semanticReleaseApp.js +3 -3
- package/semantic-release/semanticReleaseMonorepo.js +3 -4
- package/semantic-release/semanticReleaseProject.js +2 -2
package/README.md
CHANGED
|
@@ -186,17 +186,7 @@ import { semanticReleaseMonorepo } from '@itcase/config/semantic-release/index.j
|
|
|
186
186
|
export default semanticReleaseMonorepo
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```json
|
|
192
|
-
{
|
|
193
|
-
"overrides": {
|
|
194
|
-
"conventional-changelog-writer": "9.2.1"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
После этого `npm install`. Уже опубликованный релиз сам не перепишется.
|
|
189
|
+
Заметки монорепозитория печатает `conventional-changelog-writer` 9 из этого пакета. Writer 8, который ставит semantic-release, для `conventional-changelog-conventionalcommits` 10 оставляет в `CHANGELOG.md` только заголовок версии. Отдельный `overrides` в репозитории не нужен. Уже опубликованный релиз сам не перепишется.
|
|
200
190
|
|
|
201
191
|
## Подключение Husky
|
|
202
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itcase/config",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10",
|
|
4
4
|
"author": "ITCase",
|
|
5
5
|
"description": "ITCase Config",
|
|
6
6
|
"engines": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"chokidar-cli": "^3.0.0",
|
|
68
68
|
"commitizen": "^4.3.2",
|
|
69
69
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
70
|
+
"conventional-changelog-writer": "9.2.1",
|
|
70
71
|
"cssnano": "^9.0.5",
|
|
71
72
|
"cssnano-preset-default": "^9.0.5",
|
|
72
73
|
"inquirer": "^14.2.2",
|
|
@@ -12,17 +12,19 @@ import {
|
|
|
12
12
|
releaseRules,
|
|
13
13
|
} from './config.js'
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const releaseNotesFromIssuesScript = join(
|
|
16
16
|
dirname(fileURLToPath(import.meta.url)),
|
|
17
|
-
'
|
|
17
|
+
'releaseNotesFromIssues.js',
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
// Ссылки на задачи из PR.
|
|
21
|
-
//
|
|
22
|
-
|
|
20
|
+
// Ссылки на задачи из PR.
|
|
21
|
+
// Pull request'ы текущего репозитория читает GITHUB_TOKEN.
|
|
22
|
+
// Задачи другого репозитория читает RELEASE_NOTES_GH_TOKEN.
|
|
23
|
+
// RELEASE_NOTES_ISSUES_REPO — репозиторий задач.
|
|
24
|
+
const releaseNotesFromIssuesPlugin = [
|
|
23
25
|
'@semantic-release/exec',
|
|
24
26
|
{
|
|
25
|
-
generateNotesCmd: `node ${JSON.stringify(
|
|
27
|
+
generateNotesCmd: `node ${JSON.stringify(releaseNotesFromIssuesScript)} "\${lastRelease.gitTag}" "\${nextRelease.gitTag}"`,
|
|
26
28
|
},
|
|
27
29
|
]
|
|
28
30
|
|
|
@@ -132,6 +134,6 @@ function createSemanticReleaseConfig({
|
|
|
132
134
|
export {
|
|
133
135
|
commonPlugins,
|
|
134
136
|
createSemanticReleaseConfig,
|
|
135
|
-
|
|
137
|
+
releaseNotesFromIssuesPlugin,
|
|
136
138
|
releaseNotesGeneratorConfig,
|
|
137
139
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { format, pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
import { writeChangelogString } from 'conventional-changelog-writer'
|
|
6
|
+
import { filterRevertedCommitsSync } from 'conventional-commits-filter'
|
|
7
|
+
import { CommitParser } from 'conventional-commits-parser'
|
|
8
|
+
|
|
9
|
+
import { releaseNotesGeneratorConfig } from './common.js'
|
|
10
|
+
|
|
11
|
+
// Импорт из этого файла резолвит writer 9, зависимость @itcase/config.
|
|
12
|
+
// Writer 8, который semantic-release поднимает в корень монорепозитория,
|
|
13
|
+
// для preset 10 печатает только заголовок версии.
|
|
14
|
+
|
|
15
|
+
function resolvePackageEntry(cwd, packageName) {
|
|
16
|
+
let directory = cwd
|
|
17
|
+
|
|
18
|
+
while (true) {
|
|
19
|
+
const packageDir = join(directory, 'node_modules', packageName)
|
|
20
|
+
const packageJsonPath = join(packageDir, 'package.json')
|
|
21
|
+
|
|
22
|
+
if (existsSync(packageJsonPath)) {
|
|
23
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
24
|
+
const exportEntry = packageJson.exports
|
|
25
|
+
const relativeEntry =
|
|
26
|
+
exportEntry?.import ||
|
|
27
|
+
exportEntry?.['.']?.import ||
|
|
28
|
+
packageJson.module ||
|
|
29
|
+
packageJson.main
|
|
30
|
+
|
|
31
|
+
return pathToFileURL(join(packageDir, relativeEntry)).href
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const parentDirectory = dirname(directory)
|
|
35
|
+
if (parentDirectory === directory) {
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
directory = parentDirectory
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw new Error(`Cannot resolve "${packageName}" from "${cwd}"`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadPreset(cwd, presetConfig) {
|
|
45
|
+
const presetUrl = resolvePackageEntry(
|
|
46
|
+
cwd,
|
|
47
|
+
'conventional-changelog-conventionalcommits',
|
|
48
|
+
)
|
|
49
|
+
const presetModule = await import(presetUrl)
|
|
50
|
+
|
|
51
|
+
return presetModule.default(presetConfig)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function generateReleaseNotes(pluginConfig, context) {
|
|
55
|
+
const { commits, cwd, lastRelease, nextRelease, options } = context
|
|
56
|
+
const repositoryUrl = options.repositoryUrl.replace(/\.git$/i, '')
|
|
57
|
+
const loadedPreset = await loadPreset(cwd, pluginConfig.presetConfig)
|
|
58
|
+
const parser = new CommitParser({
|
|
59
|
+
referenceActions: [
|
|
60
|
+
'close',
|
|
61
|
+
'closes',
|
|
62
|
+
'closed',
|
|
63
|
+
'fix',
|
|
64
|
+
'fixes',
|
|
65
|
+
'fixed',
|
|
66
|
+
'resolve',
|
|
67
|
+
'resolves',
|
|
68
|
+
'resolved',
|
|
69
|
+
],
|
|
70
|
+
issuePrefixes: ['#', 'gh-'],
|
|
71
|
+
...loadedPreset.parser,
|
|
72
|
+
...pluginConfig.parserOpts,
|
|
73
|
+
})
|
|
74
|
+
const parsedCommits = filterRevertedCommitsSync(
|
|
75
|
+
commits
|
|
76
|
+
.filter((commit) => commit.message?.trim())
|
|
77
|
+
.map((rawCommit) => ({
|
|
78
|
+
...rawCommit,
|
|
79
|
+
...parser.parse(rawCommit.message),
|
|
80
|
+
})),
|
|
81
|
+
)
|
|
82
|
+
const previousTag = lastRelease.gitTag || lastRelease.gitHead
|
|
83
|
+
const currentTag = nextRelease.gitTag || nextRelease.gitHead
|
|
84
|
+
const repository = new URL(repositoryUrl)
|
|
85
|
+
const protocol = repository.protocol === 'http:' ? 'http' : 'https'
|
|
86
|
+
const [, owner, repositoryName] =
|
|
87
|
+
/^\/(?<owner>[^/]+)?\/?(?<repository>.+)?$/.exec(repository.pathname) || []
|
|
88
|
+
|
|
89
|
+
return writeChangelogString(
|
|
90
|
+
parsedCommits,
|
|
91
|
+
{
|
|
92
|
+
commit: 'commit',
|
|
93
|
+
currentTag,
|
|
94
|
+
host: format({
|
|
95
|
+
hostname: repository.hostname,
|
|
96
|
+
port: repository.port,
|
|
97
|
+
protocol: `${protocol}:`,
|
|
98
|
+
}),
|
|
99
|
+
linkCompare: Boolean(currentTag && previousTag),
|
|
100
|
+
owner,
|
|
101
|
+
previousTag,
|
|
102
|
+
repository: repositoryName,
|
|
103
|
+
version: nextRelease.version,
|
|
104
|
+
issue: 'issues',
|
|
105
|
+
},
|
|
106
|
+
{ ...loadedPreset.writer, ...pluginConfig.writerOpts },
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const releaseNotesFromCommitsPlugin = {
|
|
111
|
+
async generateNotes(_pluginConfig, context) {
|
|
112
|
+
return generateReleaseNotes(releaseNotesGeneratorConfig, context)
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { releaseNotesFromCommitsPlugin }
|
|
@@ -14,8 +14,11 @@ import { join } from 'node:path'
|
|
|
14
14
|
*/
|
|
15
15
|
const [lastTagArg, nextTagArg] = process.argv.slice(2)
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
const
|
|
17
|
+
// Pull requests of this repository. In GitHub Actions this is the workflow token.
|
|
18
|
+
const repositoryToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN
|
|
19
|
+
|
|
20
|
+
// Issues live in another repository. The workflow token cannot read them.
|
|
21
|
+
const issuesToken = process.env.RELEASE_NOTES_GH_TOKEN
|
|
19
22
|
|
|
20
23
|
const currentProjectOwner = process.env.GITHUB_REPOSITORY?.split('/')[0]
|
|
21
24
|
const currentProjectRepo = process.env.GITHUB_REPOSITORY?.split('/')[1]
|
|
@@ -27,8 +30,12 @@ const releaseNotesIssuesRepo = process.env.RELEASE_NOTES_ISSUES_REPO
|
|
|
27
30
|
const issuesProjectOwner = releaseNotesIssuesRepo?.split('/')[0]
|
|
28
31
|
const issuesProjectRepo = releaseNotesIssuesRepo?.split('/')[1]
|
|
29
32
|
|
|
30
|
-
if (!currentProjectOwner || !currentProjectRepo || !
|
|
31
|
-
throw new Error('Missing "GITHUB_REPOSITORY" or "
|
|
33
|
+
if (!currentProjectOwner || !currentProjectRepo || !repositoryToken) {
|
|
34
|
+
throw new Error('Missing "GITHUB_REPOSITORY" or "GITHUB_TOKEN"')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!issuesToken) {
|
|
38
|
+
throw new Error('Missing "RELEASE_NOTES_GH_TOKEN"')
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
if (!issuesProjectOwner || !issuesProjectRepo) {
|
|
@@ -269,7 +276,7 @@ async function run() {
|
|
|
269
276
|
}
|
|
270
277
|
|
|
271
278
|
// Small helper for GitHub REST API with common headers and query serialization.
|
|
272
|
-
async function fetchGithubApi(pathname, queryParams = {}) {
|
|
279
|
+
async function fetchGithubApi(pathname, token, queryParams = {}) {
|
|
273
280
|
const apiUrl = new URL(`https://api.github.com${pathname}`)
|
|
274
281
|
|
|
275
282
|
for (const [key, value] of Object.entries(queryParams)) {
|
|
@@ -282,7 +289,7 @@ async function fetchGithubApi(pathname, queryParams = {}) {
|
|
|
282
289
|
const response = await fetch(apiUrl, {
|
|
283
290
|
headers: {
|
|
284
291
|
Accept: 'application/vnd.github+json',
|
|
285
|
-
Authorization: `Bearer ${
|
|
292
|
+
Authorization: `Bearer ${token}`,
|
|
286
293
|
'User-Agent': 'semantic-release-issues-notes',
|
|
287
294
|
// GitHub REST API version header
|
|
288
295
|
'X-GitHub-Api-Version': '2026-03-10',
|
|
@@ -428,6 +435,7 @@ async function getAssociatedPullRequestsForCommit(commitSha) {
|
|
|
428
435
|
// Works for both merge-commit and regular commits associated with PRs.
|
|
429
436
|
const response = await fetchGithubApi(
|
|
430
437
|
`/repos/${currentProjectOwner}/${currentProjectRepo}/commits/${commitSha}/pulls`,
|
|
438
|
+
repositoryToken,
|
|
431
439
|
)
|
|
432
440
|
return Array.isArray(response) ? response : []
|
|
433
441
|
}
|
|
@@ -449,7 +457,7 @@ async function loadIssue(ref) {
|
|
|
449
457
|
const [issueOwner, issueRepo] = repoRef.split('/')
|
|
450
458
|
const issuePath = `/repos/${issueOwner}/${issueRepo}/issues/${issueNumber}`
|
|
451
459
|
try {
|
|
452
|
-
return await fetchGithubApi(issuePath)
|
|
460
|
+
return await fetchGithubApi(issuePath, issuesToken)
|
|
453
461
|
} catch (error) {
|
|
454
462
|
throw new Error(
|
|
455
463
|
`Failed to load issue for ref="${ref}" using path="${issuePath}"`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSemanticReleaseConfig,
|
|
3
|
-
|
|
3
|
+
releaseNotesFromIssuesPlugin,
|
|
4
4
|
} from './common.js'
|
|
5
5
|
|
|
6
6
|
const DEFAULT_VERSION_ASSETS = [
|
|
@@ -18,7 +18,7 @@ const DEFAULT_RELEASE_ASSETS = ['builds/*.apk']
|
|
|
18
18
|
* - Не публикует в npm
|
|
19
19
|
* - Коммитит package + Android version-файлы
|
|
20
20
|
* - Прикрепляет к GitHub release APK из `builds/` (если есть; отсутствующие пропускаются)
|
|
21
|
-
* - Заметки релиза: сначала задачи из
|
|
21
|
+
* - Заметки релиза: сначала задачи из releaseNotesFromIssues.js, если они есть, потом обычные коммиты
|
|
22
22
|
*
|
|
23
23
|
* Сборку APK делайте в prepare проекта или отдельным CI job до publish.
|
|
24
24
|
*
|
|
@@ -39,7 +39,7 @@ function createSemanticReleaseApp({
|
|
|
39
39
|
versionAssets = DEFAULT_VERSION_ASSETS,
|
|
40
40
|
} = {}) {
|
|
41
41
|
return createSemanticReleaseConfig({
|
|
42
|
-
additionalPlugins: [
|
|
42
|
+
additionalPlugins: [releaseNotesFromIssuesPlugin],
|
|
43
43
|
branches: branches,
|
|
44
44
|
gitConfig: {
|
|
45
45
|
message:
|
|
@@ -4,6 +4,7 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
4
4
|
import { resolve } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import { createSemanticReleaseConfig } from './common.js'
|
|
7
|
+
import { releaseNotesFromCommitsPlugin } from './releaseNotesFromCommits.js'
|
|
7
8
|
|
|
8
9
|
const RELEASE_ASSETS = [
|
|
9
10
|
'lerna.json',
|
|
@@ -41,16 +42,14 @@ const semanticReleaseMonorepo = createSemanticReleaseConfig({
|
|
|
41
42
|
[
|
|
42
43
|
'semantic-release-lerna',
|
|
43
44
|
{
|
|
44
|
-
// Заметки пишет @semantic-release/release-notes-generator, не lerna.
|
|
45
|
-
// В корне репозитория, который релизится, нужен override
|
|
46
|
-
// "conventional-changelog-writer": "9.2.1". Writer 8 не печатает
|
|
47
|
-
// коммиты для conventional-changelog-conventionalcommits 10.
|
|
48
45
|
generateNotes: false,
|
|
49
46
|
latch: false,
|
|
50
47
|
},
|
|
51
48
|
],
|
|
49
|
+
releaseNotesFromCommitsPlugin,
|
|
52
50
|
],
|
|
53
51
|
preparePlugins: [keepLernaIndependentPlugin],
|
|
52
|
+
skipReleaseNotesGenerator: true,
|
|
54
53
|
})
|
|
55
54
|
|
|
56
55
|
export { semanticReleaseMonorepo }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSemanticReleaseConfig,
|
|
3
|
-
|
|
3
|
+
releaseNotesFromIssuesPlugin,
|
|
4
4
|
} from './common.js'
|
|
5
5
|
|
|
6
6
|
const semanticReleaseProject = createSemanticReleaseConfig({
|
|
7
|
-
additionalPlugins: [
|
|
7
|
+
additionalPlugins: [releaseNotesFromIssuesPlugin],
|
|
8
8
|
branches: ['docker/master'],
|
|
9
9
|
gitConfig: {
|
|
10
10
|
message: 'release: publishing new version ${nextRelease.version} [skip ci]',
|