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 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
- * [ ] Changelogs, docs, bundles go to: release assets and/or meta branch.
15
- * [x] No extra builds. The required deps are fetched from the pkg registry.
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
- ```yaml
46
- buildCmd: 'yarn && yarn build'
47
- testCmd: 'yarn test'
48
- fetch: true
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
- "version": "1.11.2",
3
+ "version": "1.14.0",
4
4
  "description": "zx-based alternative for multi-semantic-release",
5
5
  "type": "module",
6
6
  "exports": "./src/main/js/index.js",
@@ -1,36 +1,29 @@
1
- import {ctx, tempy, fs} from 'zx-extra'
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) => ctx(async ($) => {
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.buildCmd) {
15
- if (pkg.changes.length === 0 && config.fetch) await fetchPkg(pkg)
16
-
14
+ if (config.cmd) {
15
+ if (pkg.changes.length === 0 && config.npmFetch) await fetchPkg(pkg)
17
16
  if (!pkg.fetched) {
18
- $.cwd = pkg.absPath
19
- console.log(`build '${pkg.name}'`)
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 = (pkg) => ctx(async ($) => {
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) =>
@@ -14,13 +14,19 @@ const CONFIG_FILES = [
14
14
  ]
15
15
 
16
16
  export const defaultConfig = {
17
- buildCmd: 'yarn && yarn build',
18
- testCmd: 'yarn test',
19
- fetch: true
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
+ })
@@ -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(`semantic changes of '${name}'`, changes)
30
+ console.log(`[${name}] semantic changes`, changes)
29
31
 
30
32
  await build(pkg, packages, cwd)
31
33
 
@@ -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 createGhRelease(pkg)
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 = (pkg) => ctx(async ($) => {
28
- console.log('push artifact to branch `meta`')
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 = 'meta'
35
- const msg = `chore: release meta ${name} ${version}`
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 = ({absPath: cwd}) => ctx(async ($) => {
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 createGhRelease = (pkg) => ctx(async ($) => {
67
- console.log('create gh release')
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
- const releaseNotes = releaseDiffRef + '\n' + releaseDetails + '\n'
92
- const releaseData = JSON.stringify({
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
- console.log('publish to gh-pages')
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.from, opts.branch, opts.to, opts.msg]
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 = (cwd) => ctx(async ($) => {
207
- $.cwd = cwd
226
+ export const parseRepo = async (cwd) => {
208
227
  const {ghToken, ghUser} = parseEnv($.env)
209
- const originUrl = (await $`git config --get remote.origin.url`).toString().trim()
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: 'docs gh-pages b'
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: [