zx-bulk-release 1.11.2 → 1.14.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/CHANGELOG.md +16 -0
- package/README.md +11 -7
- package/package.json +1 -1
- package/src/main/js/build.js +11 -18
- package/src/main/js/config.js +11 -5
- package/src/main/js/index.js +3 -1
- package/src/main/js/publish.js +56 -37
- package/src/test/js/integration.test.js +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [1.14.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.13.0...v1.14.0) (2022-06-27)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
* feat: add config normalizer ([80afee7](https://github.com/semrel-extra/zx-bulk-release/commit/80afee796e71aef6288943b2c6276b6f172192c5))
|
|
5
|
+
|
|
6
|
+
## [1.13.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.12.0...v1.13.0) (2022-06-27)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
* feat: add log labels ([a0e80b6](https://github.com/semrel-extra/zx-bulk-release/commit/a0e80b64215c3f69206713cd6073c0240b0930c9))
|
|
10
|
+
* feat: add changelog generator ([4c0914f](https://github.com/semrel-extra/zx-bulk-release/commit/4c0914f24a91e2297f8c6ff8410949c2a0220fb5))
|
|
11
|
+
|
|
12
|
+
## [1.12.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.11.2...v1.12.0) (2022-06-26)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
* feat: enhance default gh-pages commit msg ([81ecd7f](https://github.com/semrel-extra/zx-bulk-release/commit/81ecd7f73478c4e1f62d4287b81931e7a8120d58))
|
|
16
|
+
|
|
1
17
|
## [1.11.2](https://github.com/semrel-extra/zx-bulk-release/compare/v1.11.1...v1.11.2) (2022-06-26)
|
|
2
18
|
|
|
3
19
|
### Fixes & improvements
|
package/README.md
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
## Roadmap
|
|
11
11
|
* [x] [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) trigger semantic releases.
|
|
12
12
|
* [x] Predictable [toposort](https://githib.com/semrel-extra/topo)-driven flow.
|
|
13
|
-
* [x] No blocking (no release commits).
|
|
14
|
-
* [
|
|
15
|
-
* [x]
|
|
13
|
+
* [x] No default branch blocking (no release commits).
|
|
14
|
+
* [x] Pkg changelogs go to `changelog` branch (configurable).
|
|
15
|
+
* [x] Docs are published to `gh-pages` branch (configurable).
|
|
16
|
+
* [x] No extra builds. The required deps are fetched from the pkg registry (`npmFetch` flag).
|
|
16
17
|
|
|
17
18
|
## Requirements
|
|
18
19
|
* macOS / linux
|
|
@@ -42,10 +43,13 @@ await run({
|
|
|
42
43
|
|
|
43
44
|
### Config
|
|
44
45
|
Any [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) compliant format: `.releaserc`, `.release.json`, `.release.yaml`, etc.
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"cmd": "yarn && yarn build && yarn test",
|
|
49
|
+
"npmFetch": true,
|
|
50
|
+
"changelog": "changelog",
|
|
51
|
+
"ghPages": "gh-pages"
|
|
52
|
+
}
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
## Implementation notes
|
package/package.json
CHANGED
package/src/main/js/build.js
CHANGED
|
@@ -1,36 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {tempy, fs, $} from 'zx-extra'
|
|
2
2
|
import {copy} from 'git-glob-cp'
|
|
3
3
|
import ini from 'ini'
|
|
4
4
|
import {traverseDeps} from './deps.js'
|
|
5
5
|
import {parseEnv} from './publish.js'
|
|
6
6
|
|
|
7
|
-
export const build = (pkg, packages) =>
|
|
7
|
+
export const build = async (pkg, packages) => {
|
|
8
8
|
if (pkg.built) return true
|
|
9
9
|
|
|
10
10
|
await traverseDeps(pkg, packages, async (_, {pkg}) => build(pkg, packages))
|
|
11
11
|
|
|
12
12
|
const {config} = pkg
|
|
13
13
|
|
|
14
|
-
if (config.
|
|
15
|
-
if (pkg.changes.length === 0 && config.
|
|
16
|
-
|
|
14
|
+
if (config.cmd) {
|
|
15
|
+
if (pkg.changes.length === 0 && config.npmFetch) await fetchPkg(pkg)
|
|
17
16
|
if (!pkg.fetched) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
await $.raw`${config.buildCmd}`
|
|
21
|
-
|
|
22
|
-
if (config.testCmd) {
|
|
23
|
-
console.log(`test '${pkg.name}'`)
|
|
24
|
-
await $.raw`${config.testCmd}`
|
|
25
|
-
}
|
|
17
|
+
console.log(`[${pkg.name}] run cmd '${config.cmd}'`)
|
|
18
|
+
await $.o({cwd: pkg.absPath, quote: v => v})`${config.cmd}`
|
|
26
19
|
}
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
pkg.built = true
|
|
30
23
|
return true
|
|
31
|
-
}
|
|
24
|
+
}
|
|
32
25
|
|
|
33
|
-
const fetchPkg =
|
|
26
|
+
const fetchPkg = async (pkg) => {
|
|
34
27
|
try {
|
|
35
28
|
const cwd = pkg.absPath
|
|
36
29
|
const {npmRegistry, npmToken, npmConfig} = parseEnv($.env)
|
|
@@ -45,11 +38,11 @@ const fetchPkg = (pkg) => ctx(async ($) => {
|
|
|
45
38
|
await copy({from: ['**/*', '!package.json'], to: cwd, cwd: `${temp}/package`})
|
|
46
39
|
|
|
47
40
|
pkg.fetched = true
|
|
48
|
-
console.log(`fetched '${pkg.name}@${pkg.version}'`)
|
|
41
|
+
console.log(`[${pkg.name}] fetched '${pkg.name}@${pkg.version}'`)
|
|
49
42
|
} catch (e) {
|
|
50
|
-
console.log(`fetching '${pkg.name}@${pkg.version}' failed`, e)
|
|
43
|
+
console.log(`[${pkg.name}] fetching '${pkg.name}@${pkg.version}' failed`, e)
|
|
51
44
|
}
|
|
52
|
-
}
|
|
45
|
+
}
|
|
53
46
|
|
|
54
47
|
// NOTE registry-auth-token does not work with localhost:4873
|
|
55
48
|
const getAuthToken = (registry, npmrc) =>
|
package/src/main/js/config.js
CHANGED
|
@@ -14,13 +14,19 @@ const CONFIG_FILES = [
|
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
export const defaultConfig = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
cmd: 'yarn && yarn build && yarn test',
|
|
18
|
+
npmFetch: true,
|
|
19
|
+
changelog: 'changelog',
|
|
20
|
+
ghPages: 'gh-pages'
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const getConfig = async (...cwds) =>
|
|
23
|
-
(await Promise.all(cwds.map(
|
|
24
|
+
normalizeConfig((await Promise.all(cwds.map(
|
|
24
25
|
cwd => cosmiconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILES }).search(cwd).then(r => r?.config)
|
|
25
|
-
))).find(Boolean) || defaultConfig
|
|
26
|
+
))).find(Boolean) || defaultConfig)
|
|
26
27
|
|
|
28
|
+
export const normalizeConfig = config => ({
|
|
29
|
+
...config,
|
|
30
|
+
npmFetch: config.npmFetch || config.fetch || config.fetchPkg,
|
|
31
|
+
cmd: config.cmd || (config.buildCmd ? `${config.buildCmd}${config.testCmd ? ` && ${config.testCmd}` : ''}` : '')
|
|
32
|
+
})
|
package/src/main/js/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import {build} from './build.js'
|
|
|
7
7
|
import {getConfig} from './config.js'
|
|
8
8
|
|
|
9
9
|
export const run = async ({cwd = process.cwd(), env = process.env, flags = {}} = {}) => {
|
|
10
|
+
console.log('zx-bulk-release')
|
|
11
|
+
|
|
10
12
|
try {
|
|
11
13
|
const {packages, queue, root} = await topo({cwd})
|
|
12
14
|
const dryRun = flags['dry-run'] || flags.dryRun
|
|
@@ -25,7 +27,7 @@ export const run = async ({cwd = process.cwd(), env = process.env, flags = {}} =
|
|
|
25
27
|
pkg.manifest.version = pkg.version
|
|
26
28
|
|
|
27
29
|
if (changes.length === 0) continue
|
|
28
|
-
console.log(`
|
|
30
|
+
console.log(`[${name}] semantic changes`, changes)
|
|
29
31
|
|
|
30
32
|
await build(pkg, packages, cwd)
|
|
31
33
|
|
package/src/main/js/publish.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {formatTag, getLatestTag} from './tag.js'
|
|
2
|
-
import {tempy, ctx, fs, path} from 'zx-extra'
|
|
2
|
+
import {tempy, ctx, fs, path, $} from 'zx-extra'
|
|
3
3
|
import {copydir} from 'git-glob-cp'
|
|
4
4
|
|
|
5
5
|
export const publish = async (pkg) => {
|
|
6
6
|
await pushTag(pkg)
|
|
7
7
|
await pushMeta(pkg)
|
|
8
|
+
await pushChangelog(pkg)
|
|
8
9
|
await npmPublish(pkg)
|
|
9
|
-
await
|
|
10
|
+
await ghRelease(pkg)
|
|
10
11
|
await ghPages(pkg)
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -14,28 +15,26 @@ export const pushTag = (pkg) => ctx(async ($) => {
|
|
|
14
15
|
const {absPath: cwd, name, version} = pkg
|
|
15
16
|
const tag = formatTag({name, version})
|
|
16
17
|
const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
|
|
17
|
-
$.cwd = cwd
|
|
18
18
|
|
|
19
|
-
console.log(`push release tag ${tag}`)
|
|
19
|
+
console.log(`[${name}] push release tag ${tag}`)
|
|
20
20
|
|
|
21
|
+
$.cwd = cwd
|
|
21
22
|
await $`git config user.name ${gitCommitterName}`
|
|
22
23
|
await $`git config user.email ${gitCommitterEmail}`
|
|
23
24
|
await $`git tag -m ${tag} ${tag}`
|
|
24
25
|
await $`git push origin ${tag}`
|
|
25
26
|
})
|
|
26
27
|
|
|
27
|
-
export const pushMeta =
|
|
28
|
-
console.log(
|
|
28
|
+
export const pushMeta = async (pkg) => {
|
|
29
|
+
console.log(`[${pkg.name}] push artifact to branch 'meta'`)
|
|
29
30
|
|
|
30
31
|
const cwd = pkg.absPath
|
|
31
32
|
const {name, version} = pkg
|
|
32
33
|
const tag = formatTag({name, version})
|
|
33
34
|
const to = '.'
|
|
34
|
-
const branch =
|
|
35
|
-
const msg =
|
|
36
|
-
|
|
37
|
-
$.cwd = cwd
|
|
38
|
-
const hash = (await $`git rev-parse HEAD`).toString().trim()
|
|
35
|
+
const branch = 'meta'
|
|
36
|
+
const msg = `chore: release meta ${name} ${version}`
|
|
37
|
+
const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
|
|
39
38
|
const meta = {
|
|
40
39
|
META_VERSION: '1',
|
|
41
40
|
name: pkg.name,
|
|
@@ -49,13 +48,14 @@ export const pushMeta = (pkg) => ctx(async ($) => {
|
|
|
49
48
|
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
50
49
|
|
|
51
50
|
await push({cwd, to, branch, msg, files})
|
|
52
|
-
}
|
|
51
|
+
}
|
|
53
52
|
|
|
54
|
-
export const npmPublish = (
|
|
53
|
+
export const npmPublish = (pkg) => ctx(async ($) => {
|
|
54
|
+
const {absPath: cwd, name, version} = pkg
|
|
55
55
|
const {npmRegistry, npmToken, npmConfig} = parseEnv($.env)
|
|
56
56
|
const npmrc = npmConfig ? npmConfig : path.resolve(cwd, '.npmrc')
|
|
57
57
|
|
|
58
|
-
console.log(`publish npm package to ${npmRegistry}`)
|
|
58
|
+
console.log(`[${name}] publish npm package ${name} ${version} to ${npmRegistry}`)
|
|
59
59
|
$.cwd = cwd
|
|
60
60
|
if (!npmConfig) {
|
|
61
61
|
await $.raw`echo ${npmRegistry.replace(/https?:/, '')}/:_authToken=${npmToken} >> ${npmrc}`
|
|
@@ -63,16 +63,43 @@ export const npmPublish = ({absPath: cwd}) => ctx(async ($) => {
|
|
|
63
63
|
await $`npm publish --no-git-tag-version --registry=${npmRegistry} --userconfig ${npmrc} --no-workspaces`
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
export const
|
|
67
|
-
console.log(
|
|
66
|
+
export const ghRelease = async (pkg) => {
|
|
67
|
+
console.log(`[${pkg.name}] create gh release`)
|
|
68
68
|
|
|
69
|
-
const cwd = pkg.absPath
|
|
70
|
-
const {name, version} = pkg
|
|
71
69
|
const {ghUser, ghToken} = parseEnv($.env)
|
|
72
|
-
const {repoName, repoPublicUrl} = await parseRepo(cwd)
|
|
73
|
-
|
|
74
70
|
if (!ghToken || !ghUser) return null
|
|
75
71
|
|
|
72
|
+
const {name, version, absPath: cwd} = pkg
|
|
73
|
+
const {repoName} = await parseRepo(cwd)
|
|
74
|
+
const tag = formatTag({name, version})
|
|
75
|
+
const releaseNotes = await formatReleaseNotes(pkg)
|
|
76
|
+
const releaseData = JSON.stringify({
|
|
77
|
+
name: tag,
|
|
78
|
+
tag_name: tag,
|
|
79
|
+
body: releaseNotes
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await $.o({cwd})`curl -u ${ghUser}:${ghToken} -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${repoName}/releases -d ${releaseData}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const pushChangelog = async (pkg) => {
|
|
86
|
+
const {config: {changelog: opts}} = pkg
|
|
87
|
+
if (!opts) return
|
|
88
|
+
|
|
89
|
+
console.log(`[${pkg.name}] push changelog`)
|
|
90
|
+
const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, msg = `chore: update changelog ${pkg.name}`] = typeof opts === 'string'
|
|
91
|
+
? opts.split(' ')
|
|
92
|
+
: [opts.branch, opts.file, opts.msg]
|
|
93
|
+
const _cwd = await fetch({cwd: pkg.absPath, branch})
|
|
94
|
+
const releaseNotes = await formatReleaseNotes(pkg)
|
|
95
|
+
|
|
96
|
+
await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
|
|
97
|
+
await push({cwd: _cwd, branch, msg})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const formatReleaseNotes = async (pkg) => {
|
|
101
|
+
const {name, version, absPath: cwd} = pkg
|
|
102
|
+
const {repoPublicUrl} = await parseRepo(cwd)
|
|
76
103
|
const tag = formatTag({name, version})
|
|
77
104
|
const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
|
|
78
105
|
const releaseDetails = Object.values(pkg.changes
|
|
@@ -88,25 +115,18 @@ export const createGhRelease = (pkg) => ctx(async ($) => {
|
|
|
88
115
|
### ${group}
|
|
89
116
|
${commits.join('\n')}`).join('\n')
|
|
90
117
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
name: tag,
|
|
94
|
-
tag_name: tag,
|
|
95
|
-
body: releaseNotes
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
$.cwd = cwd
|
|
99
|
-
await $`curl -u ${ghUser}:${ghToken} -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${repoName}/releases -d ${releaseData}`
|
|
100
|
-
})
|
|
118
|
+
return releaseDiffRef + '\n' + releaseDetails + '\n'
|
|
119
|
+
}
|
|
101
120
|
|
|
102
121
|
const ghPages = async (pkg) => {
|
|
103
122
|
const {config: {ghPages: opts}} = pkg
|
|
104
123
|
if (!opts) return
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
const [from, branch = 'gh-pages', to = '.', msg = 'docs updated'] = typeof opts === 'string'
|
|
125
|
+
const [branch = 'gh-pages', from = 'docs', to = '.', msg = `docs: update docs ${pkg.name} ${pkg.version}`] = typeof opts === 'string'
|
|
108
126
|
? opts.split(' ')
|
|
109
|
-
: [opts.
|
|
127
|
+
: [opts.branch, opts.from, opts.to, opts.msg]
|
|
128
|
+
|
|
129
|
+
console.log(`[${pkg.name}] publish docs to ${branch}`)
|
|
110
130
|
|
|
111
131
|
await push({
|
|
112
132
|
cwd: path.resolve(pkg.absPath, from),
|
|
@@ -203,10 +223,9 @@ export const parseEnv = (env = process.env) => {
|
|
|
203
223
|
}
|
|
204
224
|
}
|
|
205
225
|
|
|
206
|
-
export const parseRepo =
|
|
207
|
-
$.cwd = cwd
|
|
226
|
+
export const parseRepo = async (cwd) => {
|
|
208
227
|
const {ghToken, ghUser} = parseEnv($.env)
|
|
209
|
-
const originUrl = (await
|
|
228
|
+
const originUrl = (await $.o({cwd})`git config --get remote.origin.url`).toString().trim()
|
|
210
229
|
const [,,repoHost, repoName] = originUrl.replace(':', '/').replace(/\.git/, '').match(/.+(@|\/\/)([^/]+)\/(.+)$/) || []
|
|
211
230
|
const repoPublicUrl = `https://${repoHost}/${repoName}`
|
|
212
231
|
const repoAuthedUrl = ghToken && ghUser && repoHost && repoName?
|
|
@@ -220,4 +239,4 @@ export const parseRepo = (cwd) => ctx(async ($) => {
|
|
|
220
239
|
repoAuthedUrl,
|
|
221
240
|
originUrl,
|
|
222
241
|
}
|
|
223
|
-
}
|
|
242
|
+
}
|
|
@@ -51,7 +51,8 @@ const cwd = await createFakeRepo({
|
|
|
51
51
|
release: {
|
|
52
52
|
buildCmd: 'yarn && yarn build',
|
|
53
53
|
testCmd: 'yarn test',
|
|
54
|
-
fetch: true
|
|
54
|
+
fetch: true,
|
|
55
|
+
changelog: 'changelog'
|
|
55
56
|
},
|
|
56
57
|
exports: {
|
|
57
58
|
'.': {
|
|
@@ -114,7 +115,7 @@ const cwd = await createFakeRepo({
|
|
|
114
115
|
buildCmd: 'yarn && yarn build',
|
|
115
116
|
testCmd: 'yarn test',
|
|
116
117
|
fetch: true,
|
|
117
|
-
ghPages: '
|
|
118
|
+
ghPages: 'gh-pages docs b'
|
|
118
119
|
},
|
|
119
120
|
exports: {
|
|
120
121
|
'.': {
|
|
@@ -174,9 +175,13 @@ test('run()', async () => {
|
|
|
174
175
|
|
|
175
176
|
const origin = (await $`git remote get-url origin`).toString().trim()
|
|
176
177
|
const meta = tempy.temporaryDirectory()
|
|
178
|
+
const chlog = tempy.temporaryDirectory()
|
|
177
179
|
|
|
178
180
|
await $`git clone --single-branch --branch meta --depth 1 ${origin} ${meta}`
|
|
179
181
|
assert.is((await fs.readJson(`${meta}/${tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')}.json`)).version, '1.0.1')
|
|
182
|
+
|
|
183
|
+
await $`git clone --single-branch --branch changelog --depth 1 ${origin} ${chlog}`
|
|
184
|
+
assert.ok((await fs.readFile(`${chlog}/a-changelog.md`, 'utf-8')).includes('### Fixes & improvements'))
|
|
180
185
|
})
|
|
181
186
|
|
|
182
187
|
await addCommits({cwd, commits: [
|