zx-bulk-release 1.16.0 → 1.17.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,22 @@
1
+ ## [1.17.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.16.2...v1.17.0) (2022-06-28)
2
+
3
+ ### Fixes & improvements
4
+ * docs: describe CLI options ([e744e97](https://github.com/semrel-extra/zx-bulk-release/commit/e744e97e8e5451ffff217283ad4e552f05073e7c))
5
+ * docs: describe internal release flow ([e29bfc9](https://github.com/semrel-extra/zx-bulk-release/commit/e29bfc968a519d7c48b0475ad30c826bef17fb96))
6
+
7
+ ### Features
8
+ * feat: introduce `--include-private` and `ignore` flags ([d029b62](https://github.com/semrel-extra/zx-bulk-release/commit/d029b6242da2e3c7fabaa5e24d61709b47e42b94))
9
+
10
+ ## [1.16.2](https://github.com/semrel-extra/zx-bulk-release/compare/v1.16.1...v1.16.2) (2022-06-28)
11
+
12
+ ### Fixes & improvements
13
+ * refactor: separate `analyze` helper ([1f76993](https://github.com/semrel-extra/zx-bulk-release/commit/1f76993d3d9539a02d516c0cd757713964f6f523))
14
+
15
+ ## [1.16.1](https://github.com/semrel-extra/zx-bulk-release/compare/v1.16.0...v1.16.1) (2022-06-28)
16
+
17
+ ### Fixes & improvements
18
+ * refactor: extract tag utils from publish.js ([82b3ef0](https://github.com/semrel-extra/zx-bulk-release/commit/82b3ef02affb65bb79eda5f54f32b0378e7083f2))
19
+
1
20
  ## [1.16.0](https://github.com/semrel-extra/zx-bulk-release/compare/v1.15.0...v1.16.0) (2022-06-28)
2
21
 
3
22
  ### Features
package/README.md CHANGED
@@ -26,6 +26,11 @@
26
26
  ```shell
27
27
  GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
28
28
  ```
29
+ | Flag | Description | Default |
30
+ |---------------------|----------------------------|----------|
31
+ | `--dry-run` | Dry run mode | `false` |
32
+ | `--ignore` | Packages to ignore: `a, b` | |
33
+ | `--include-private` | Include private pkgs | `false` |
29
34
 
30
35
  ### JS API
31
36
  ```js
@@ -59,7 +64,7 @@ Any [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) compliant format
59
64
 
60
65
  ### Output
61
66
 
62
- [Readable and clear logs](https://github.com/semrel-extra/demo-zx-bulk-release/runs/7069468916?check_suite_focus=true)
67
+ [Compact and clear logs](https://github.com/semrel-extra/demo-zx-bulk-release/runs/7090161341?check_suite_focus=true#step:6:1)
63
68
 
64
69
  ```shell
65
70
  Run npm_config_yes=true npx zx-bulk-release
@@ -99,6 +104,55 @@ zx-bulk-release
99
104
  ```
100
105
 
101
106
  ## Implementation notes
107
+ ### Flow
108
+ ```js
109
+ try {
110
+ const {packages, queue, root} = await topo({cwd, flags})
111
+ console.log('queue:', queue)
112
+
113
+ for (let name of queue) {
114
+ const pkg = packages[name]
115
+
116
+ await analyze(pkg, packages, root)
117
+
118
+ if (pkg.changes.length === 0) continue
119
+
120
+ await build(pkg, packages)
121
+
122
+ if (flags.dryRun) continue
123
+
124
+ await publish(pkg)
125
+ }
126
+ } catch (e) {
127
+ console.error(e)
128
+ throw e
129
+ }
130
+ ```
131
+
132
+ ### `topo`
133
+ [Toposort](https://github.com/semrel-extra/topo) is used to order the pkgs to be released.
134
+ By default, it omits the packages marked as `private`. You can override this by setting the `--include-private` flag.
135
+
136
+ ### `analyze`
137
+ Determines pkg changes, release type, next version etc.
138
+
139
+ ### `build`
140
+ Building pkg assets: bundles, docs, etc.
141
+ ```js
142
+ export const publish = async (pkg) => {
143
+ await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
144
+ await pushTag(pkg)
145
+ await pushMeta(pkg)
146
+ await pushChangelog(pkg)
147
+ await npmPublish(pkg)
148
+ await ghRelease(pkg)
149
+ await ghPages(pkg)
150
+ }
151
+ ```
152
+
153
+ ### `publish`
154
+ Publish the pkg to git, npm, gh-pages, gh-release, etc.
155
+
102
156
  ### Tags
103
157
  [Lerna](https://github.com/lerna/lerna) tags (like `@pkg/name@v1.0.0-beta.0`) are suitable for monorepos, but they don’t follow [semver spec](https://semver.org/). Therefore, we propose another contract:
104
158
  ```js
@@ -117,14 +171,14 @@ export const parseEnv = (env = process.env) => {
117
171
  const {GH_USER, GH_USERNAME, GITHUB_USER, GITHUB_USERNAME, GH_TOKEN, GITHUB_TOKEN, NPM_TOKEN, NPM_REGISTRY, NPMRC, NPM_USERCONFIG, NPM_CONFIG_USERCONFIG, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL} = env
118
172
 
119
173
  return {
120
- ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME,
121
- ghToken: GH_TOKEN || GITHUB_TOKEN,
122
- npmToken: NPM_TOKEN,
174
+ ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME,
175
+ ghToken: GH_TOKEN || GITHUB_TOKEN,
176
+ npmToken: NPM_TOKEN,
123
177
  // npmConfig suppresses npmToken
124
- npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
125
- npmRegistry: NPM_REGISTRY || 'https://registry.npmjs.org',
126
- gitCommitterName: GIT_COMMITTER_NAME || 'Semrel Extra Bot',
127
- gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
178
+ npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
179
+ npmRegistry: NPM_REGISTRY || 'https://registry.npmjs.org',
180
+ gitCommitterName: GIT_COMMITTER_NAME || 'Semrel Extra Bot',
181
+ gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
128
182
  }
129
183
  }
130
184
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "zx-based alternative for multi-semantic-release",
5
5
  "type": "module",
6
6
  "exports": "./src/main/js/index.js",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@semrel-extra/topo": "^1.4.1",
15
15
  "cosmiconfig": "^7.0.1",
16
- "git-glob-cp": "^1.7.1",
16
+ "git-glob-cp": "^1.7.2",
17
17
  "ini": "^3.0.0",
18
18
  "zx-extra": "^1.7.1"
19
19
  },
@@ -1,4 +1,22 @@
1
1
  import {ctx, semver} from 'zx-extra'
2
+ import {getConfig} from './config.js'
3
+ import {getLatest} from './publish.js'
4
+ import {updateDeps} from './deps.js'
5
+
6
+ export const analyze = async (pkg, packages, root) => {
7
+ pkg.config = await getConfig(pkg.absPath, root.absPath)
8
+ pkg.latest = await getLatest(pkg)
9
+
10
+ const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
11
+ const depsChanges = await updateDeps(pkg, packages)
12
+ const changes = [...semanticChanges, ...depsChanges]
13
+
14
+ pkg.changes = changes
15
+ pkg.version = resolvePkgVersion(changes, pkg.latest.tag?.version || pkg.manifest.version)
16
+ pkg.manifest.version = pkg.version
17
+
18
+ console.log(`[${pkg.name}] semantic changes`, changes)
19
+ }
2
20
 
3
21
  export const releaseSeverityOrder = ['major', 'minor', 'patch']
4
22
  export const semanticRules = [
@@ -2,5 +2,6 @@
2
2
 
3
3
  import {argv} from 'zx-extra'
4
4
  import {run} from './index.js'
5
+ import {normalizeFlags} from './config.js';
5
6
 
6
- run({flags: argv})
7
+ run({flags: normalizeFlags(argv)})
@@ -46,3 +46,6 @@ export const parseEnv = (env = process.env) => {
46
46
  gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
47
47
  }
48
48
  }
49
+
50
+ const camelize = s => s.replace(/-./g, x=>x[1].toUpperCase())
51
+ export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
@@ -1,42 +1,27 @@
1
- import {fs, $} from 'zx-extra'
2
- import {topo} from '@semrel-extra/topo'
3
- import {updateDeps} from './deps.js'
4
- import {getSemanticChanges, resolvePkgVersion} from './analyze.js'
5
- import {publish, getLatest} from './publish.js'
1
+ import {analyze} from './analyze.js'
2
+ import {publish} from './publish.js'
6
3
  import {build} from './build.js'
7
- import {getConfig} from './config.js'
4
+ import {topo} from './topo.js'
8
5
 
9
6
  export const run = async ({cwd = process.cwd(), env = process.env, flags = {}} = {}) => {
10
7
  console.log('zx-bulk-release')
11
8
 
12
9
  try {
13
- const {packages, queue, root} = await topo({cwd})
14
- const dryRun = flags['dry-run'] || flags.dryRun
15
-
10
+ const {packages, queue, root} = await topo({cwd, flags})
16
11
  console.log('queue:', queue)
17
12
 
18
13
  for (let name of queue) {
19
14
  const pkg = packages[name]
20
- pkg.config = await getConfig(pkg.absPath, root.absPath)
21
- pkg.latest = await getLatest(pkg)
22
-
23
- const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
24
- const depsChanges = await updateDeps(pkg, packages)
25
- const changes = [...semanticChanges, ...depsChanges]
26
15
 
27
- pkg.changes = changes
28
- pkg.version = resolvePkgVersion(changes, pkg.latest.tag?.version || pkg.manifest.version)
29
- pkg.manifest.version = pkg.version
16
+ await analyze(pkg, packages, root)
30
17
 
31
- if (changes.length === 0) continue
32
- console.log(`[${name}] semantic changes`, changes)
18
+ if (pkg.changes.length === 0) continue
33
19
 
34
- await build(pkg, packages, cwd)
20
+ await build(pkg, packages)
35
21
 
36
- if (dryRun) continue
22
+ if (flags.dryRun) continue
37
23
 
38
- await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
39
- await publish(pkg, env)
24
+ await publish(pkg)
40
25
  }
41
26
  } catch (e) {
42
27
  console.error(e)
@@ -50,10 +50,6 @@ export const npmPublish = (pkg) => ctx(async ($) => {
50
50
  await $`npm publish --no-git-tag-version --registry=${npmRegistry} --userconfig ${npmrc} --no-workspaces`
51
51
  })
52
52
 
53
- // NOTE registry-auth-token does not work with localhost:4873
54
- export const getAuthToken = (registry, npmrc) =>
55
- (Object.entries(npmrc).find(([reg]) => reg.startsWith(registry.replace(/^https?/, ''))) || [])[1]
56
-
57
53
  // $`npm view ${name}@${version} dist.tarball`
58
54
  export const getTarballUrl = (registry, name, version) => `${registry}/${name}/-/${name.replace(/^.+(%2f|\/)/,'')}-${version}.tgz`
59
55
 
@@ -65,3 +61,7 @@ export const getBearerToken = async (npmRegistry, npmToken, npmConfig) => {
65
61
  : npmToken
66
62
  return `Bearer ${token}`
67
63
  }
64
+
65
+ // NOTE registry-auth-token does not work with localhost:4873
66
+ export const getAuthToken = (registry, npmrc) =>
67
+ (Object.entries(npmrc).find(([reg]) => reg.startsWith(registry.replace(/^https?/, ''))) || [])[1]
@@ -1,10 +1,11 @@
1
- import {formatTag, getLatestTag} from './tag.js'
1
+ import {formatTag, getLatestTag, pushTag} from './tag.js'
2
2
  import {ctx, fs, path, $} from 'zx-extra'
3
3
  import {push, fetch, parseRepo} from './repo.js'
4
4
  import {parseEnv} from './config.js'
5
5
  import {fetchManifest, npmPublish} from './npm.js'
6
6
 
7
7
  export const publish = async (pkg) => {
8
+ await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
8
9
  await pushTag(pkg)
9
10
  await pushMeta(pkg)
10
11
  await pushChangelog(pkg)
@@ -13,25 +14,10 @@ export const publish = async (pkg) => {
13
14
  await ghPages(pkg)
14
15
  }
15
16
 
16
- export const pushTag = (pkg) => ctx(async ($) => {
17
- const {absPath: cwd, name, version} = pkg
18
- const tag = formatTag({name, version})
19
- const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
20
-
21
- console.log(`[${name}] push release tag ${tag}`)
22
-
23
- $.cwd = cwd
24
- await $`git config user.name ${gitCommitterName}`
25
- await $`git config user.email ${gitCommitterEmail}`
26
- await $`git tag -m ${tag} ${tag}`
27
- await $`git push origin ${tag}`
28
- })
29
-
30
17
  export const pushMeta = async (pkg) => {
31
18
  console.log(`[${pkg.name}] push artifact to branch 'meta'`)
32
19
 
33
- const cwd = pkg.absPath
34
- const {name, version} = pkg
20
+ const {name, version, absPath: cwd} = pkg
35
21
  const tag = formatTag({name, version})
36
22
  const to = '.'
37
23
  const branch = 'meta'
@@ -1,7 +1,22 @@
1
- // Semantic tags
1
+ // Semantic tags processing
2
2
 
3
- import {ctx, semver} from 'zx-extra'
3
+ import {ctx, semver, $} from 'zx-extra'
4
4
  import {Buffer} from 'buffer'
5
+ import {parseEnv} from './config.js'
6
+
7
+ export const pushTag = (pkg) => ctx(async ($) => {
8
+ const {absPath: cwd, name, version} = pkg
9
+ const tag = formatTag({name, version})
10
+ const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
11
+
12
+ console.log(`[${name}] push release tag ${tag}`)
13
+
14
+ $.cwd = cwd
15
+ await $`git config user.name ${gitCommitterName}`
16
+ await $`git config user.email ${gitCommitterEmail}`
17
+ await $`git tag -m ${tag} ${tag}`
18
+ await $`git push origin ${tag}`
19
+ })
5
20
 
6
21
  const f0 = {
7
22
  parse(tag) {
@@ -85,15 +100,12 @@ export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(t
85
100
 
86
101
  export const formatTag = (tag) => f0.format(tag) || f1.format(tag) || null
87
102
 
88
- export const getTags = (cwd) => ctx(async ($) => {
89
- $.cwd = cwd
90
-
91
- return (await $`git tag -l`).toString()
103
+ export const getTags = async (cwd) =>
104
+ (await $.o({cwd})`git tag -l`).toString()
92
105
  .split('\n')
93
106
  .map(tag => parseTag(tag.trim()))
94
107
  .filter(Boolean)
95
108
  .sort((a, b) => semver.rcompare(a.version, b.version))
96
- })
97
109
 
98
110
  export const getLatestTag = async (cwd, name) =>
99
111
  (await getTags(cwd)).find(tag => tag.name === name) || null
@@ -0,0 +1,17 @@
1
+ import {topo as _topo} from '@semrel-extra/topo'
2
+
3
+ export const topo = async ({flags = {}, cwd} = {}) => {
4
+ const ignore = typeof flags.ignore === 'string'
5
+ ? flags.ignore.split(/\s*,\s*/)
6
+ : Array.isArray(flags.ignore)
7
+ ? flags.ignore
8
+ : []
9
+
10
+ const filter = flags.includePrivate
11
+ ? () => true
12
+ : ({manifest: {private: _private, name}}) =>
13
+ flags.includePrivate ? true : !_private &&
14
+ !ignore.includes(name)
15
+
16
+ return _topo({cwd, filter})
17
+ }
@@ -2,7 +2,7 @@ import {suite} from 'uvu'
2
2
  import * as assert from 'uvu/assert'
3
3
  import {fileURLToPath} from "node:url"
4
4
  import path from 'node:path'
5
- import {topo} from '@semrel-extra/topo'
5
+ import {topo} from '../../main/js/topo.js'
6
6
 
7
7
  const test = suite('topo')
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -14,4 +14,9 @@ test('topo returns pkg info and release queue', async () => {
14
14
  assert.equal(queue, ['a', 'b', 'd', 'c'])
15
15
  })
16
16
 
17
+ test('topo applies pkg filter', async () => {
18
+ const {nodes, queue} = await topo({cwd: path.resolve(fixtures, 'regular-monorepo'), flags: {ignore: 'b,c'}})
19
+ assert.equal(queue, ['a', 'd'])
20
+ })
21
+
17
22
  test.run()