zx-bulk-release 2.2.6 → 2.2.8

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,14 @@
1
+ ## [2.2.8](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.7...v2.2.8) (2023-03-24)
2
+
3
+ ### Fixes & improvements
4
+ * fix: fix git-push-rebase-retry hook ([30df2f1](https://github.com/semrel-extra/zx-bulk-release/commit/30df2f1d25d43145343e47f2f96d4a00955d6b76))
5
+
6
+ ## [2.2.7](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.6...v2.2.7) (2023-03-24)
7
+
8
+ ### Fixes & improvements
9
+ * docs: describe more CLI flags ([6f7a053](https://github.com/semrel-extra/zx-bulk-release/commit/6f7a053ae2fe2da8fd71858b132ef4ac4d1456ad))
10
+ * refactor: move `topo` to ctx builder ([6fec83c](https://github.com/semrel-extra/zx-bulk-release/commit/6fec83c10dd76feb93a3148eb2c032c6eaa902f1))
11
+
1
12
  ## [2.2.6](https://github.com/semrel-extra/zx-bulk-release/compare/v2.2.5...v2.2.6) (2023-03-24)
2
13
 
3
14
  ### Fixes & improvements
package/README.md CHANGED
@@ -18,10 +18,10 @@
18
18
  * No extra builds. The required deps are fetched from the pkg registry (`npmFetch` config opt).
19
19
 
20
20
  ## Roadmap
21
+ * [x] Store release metrics to `meta`.
22
+ * [ ] ~~Self-repair. Restore broken/missing metadata from external registries (npm, pypi, m2)~~. Tags should be the only source of truth
21
23
  * [ ] Multistack. Add support for java/kt/py.
22
- * [ ] Self-repair. Restore broken/missing metadata from external registries (npm, pypi, m2).
23
24
  * [ ] Semaphore. Let several release agents to serve the monorepo at the same time.
24
- * [ ] Stats. Store release metrics to `meta`.
25
25
 
26
26
  ## Requirements
27
27
  * macOS / linux
@@ -37,11 +37,16 @@ yarn add zx-bulk-release
37
37
  ```shell
38
38
  GH_TOKEN=ghtoken GH_USER=username NPM_TOKEN=npmtoken npx zx-bulk-release [opts]
39
39
  ```
40
- | Flag | Description | Default |
41
- |---------------------|----------------------------|----------|
42
- | `--dry-run` | Dry run mode | `false` |
43
- | `--ignore` | Packages to ignore: `a, b` | |
44
- | `--include-private` | Include private pkgs | `false` |
40
+ | Flag | Description | Default |
41
+ |------------------------------|----------------------------------------------------------------|------------------|
42
+ | `--ignore` | Packages to ignore: `a, b` | |
43
+ | `--include-private` | Include `private` packages | `false` |
44
+ | `--concurrency` | `build/publish` threads limit | `os.cpus.length` |
45
+ | `--no-build` | Skip `buildCmd` invoke | |
46
+ | `--no-npm-fetch` | Disable npm artifacts fetching | |
47
+ | `--dry-run` / `--no-publish` | Disable any publish logic | |
48
+ | `--report` | Persist release state to file | |
49
+ | `--debug` | Enable [zx](https://github.com/google/zx#verbose) verbose mode | |
45
50
 
46
51
  ### JS API
47
52
  ```js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
4
4
  "description": "zx-based alternative for multi-semantic-release",
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,7 +6,7 @@ import {getCommits} from './git.js'
6
6
 
7
7
  export const analyze = async (pkg, packages) => {
8
8
  const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
9
- const depsChanges = await updateDeps(pkg, packages)
9
+ const depsChanges = await updateDeps(pkg, pkg.context.packages)
10
10
  const changes = [...semanticChanges, ...depsChanges]
11
11
  const releaseType = getNextReleaseType(changes)
12
12
 
@@ -15,10 +15,10 @@ const CONFIG_FILES = [
15
15
  ]
16
16
 
17
17
  export const defaultConfig = {
18
- cmd: 'yarn && yarn build && yarn test',
19
- changelog: 'changelog',
20
- npmFetch: true,
21
- ghRelease: true,
18
+ cmd: 'yarn && yarn build && yarn test',
19
+ changelog: 'changelog',
20
+ npmFetch: true,
21
+ ghRelease: true,
22
22
  // npmPublish: true,
23
23
  // ghPages: 'gh-pages'
24
24
  }
@@ -38,10 +38,8 @@ export const normalizePkgConfig = (config, env) => ({
38
38
  }
39
39
  })
40
40
 
41
- export const parseEnv = (env = process.env) => {
42
- 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
43
-
44
- return {
41
+ export const parseEnv = ({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} = process.env) =>
42
+ ({
45
43
  ghUser: GH_USER || GH_USERNAME || GITHUB_USER || GITHUB_USERNAME,
46
44
  ghToken: GH_TOKEN || GITHUB_TOKEN,
47
45
  npmConfig: NPMRC || NPM_USERCONFIG || NPM_CONFIG_USERCONFIG,
@@ -49,7 +47,6 @@ export const parseEnv = (env = process.env) => {
49
47
  npmRegistry: NPM_REGISTRY || 'https://registry.npmjs.org',
50
48
  gitCommitterName: GIT_COMMITTER_NAME || 'Semrel Extra Bot',
51
49
  gitCommitterEmail: GIT_COMMITTER_EMAIL || 'semrel-extra-bot@hotmail.com',
52
- }
53
- }
50
+ })
54
51
 
55
52
  export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
@@ -1,6 +1,7 @@
1
1
  import {$, ctx, fs, path, tempy, copy} from 'zx-extra'
2
2
  import {log} from './log.js'
3
- import {keyByValue, memoizeBy} from './util.js'
3
+ import {memoizeBy} from './util.js'
4
+ import {queuefy} from 'queuefy'
4
5
 
5
6
  export const fetchRepo = memoizeBy(async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => ctx(async ($) => {
6
7
  const origin = _origin || (await getRepo(_cwd, {basicAuth})).repoAuthedUrl
@@ -17,32 +18,30 @@ export const fetchRepo = memoizeBy(async ({cwd: _cwd, branch, origin: _origin, b
17
18
  return cwd
18
19
  }), async ({cwd, branch}) => `${await getRoot(cwd)}:${branch}`)
19
20
 
20
- export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => ctx(async ($) => {
21
+ export const pushCommit = queuefy(async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => ctx(async ($) => {
21
22
  let retries = 3
22
- let _cwd
23
23
 
24
- while (retries > 0) {
25
- try {
26
- _cwd = await fetchRepo({cwd, branch, origin, basicAuth})
24
+ const _cwd = await fetchRepo({cwd, branch, origin, basicAuth})
25
+ $.cwd = _cwd
27
26
 
28
- for (let {relpath, contents} of files) {
29
- const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
30
- await fs.outputFile(path.resolve(_cwd, to, relpath), _contents)
31
- }
32
- if (from) await copy({baseFrom: cwd, from, baseTo: _cwd, to, ignoreFiles, cwd})
33
-
34
- $.cwd = _cwd
35
-
36
- await $`git config user.name ${gitCommitterName}`
37
- await $`git config user.email ${gitCommitterEmail}`
38
- await $`git add .`
39
- try {
40
- await $`git commit -m ${msg}`
41
- } catch {
42
- log({level: 'warn'})(`no changes to commit to ${branch}`)
43
- return
44
- }
27
+ for (let {relpath, contents} of files) {
28
+ const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
29
+ await fs.outputFile(path.resolve(_cwd, to, relpath), _contents)
30
+ }
31
+ if (from) await copy({baseFrom: cwd, from, baseTo: _cwd, to, ignoreFiles, cwd})
32
+
33
+ try {
34
+ await $`git config user.name ${gitCommitterName} &&
35
+ git config user.email ${gitCommitterEmail} &&
36
+ git add . &&
37
+ git commit -m ${msg}`
38
+ } catch {
39
+ log({level: 'warn'})(`no changes to commit to ${branch}`)
40
+ return
41
+ }
45
42
 
43
+ while (retries > 0) {
44
+ try {
46
45
  return await $.raw`git push origin HEAD:refs/heads/${branch}`
47
46
  } catch (e) {
48
47
  retries -= 1
@@ -51,9 +50,11 @@ export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFile
51
50
  if (retries === 0) {
52
51
  throw e
53
52
  }
53
+
54
+ await $`git pull --rebase origin ${branch}`
54
55
  }
55
56
  }
56
- })
57
+ }))
57
58
 
58
59
  export const getRoot = async (cwd) => (await $.o({cwd})`git rev-parse --show-toplevel`).toString().trim()
59
60
 
@@ -1,34 +1,28 @@
1
1
  import {$, fs} from 'zx-extra'
2
2
  import {get, set, tpl} from './util.js'
3
3
 
4
- export const log = (ctx) => {
5
- if ($.report) {
6
- return $.report.log(ctx)
7
- }
8
-
9
- return console.log
10
- }
4
+ export const log = (ctx) =>
5
+ $.report
6
+ ? $.report.log(ctx)
7
+ : console.log
11
8
 
12
- export const createReport = ({logger = console, file = null} = {}) => ({
9
+ export const createReport = ({logger = console, packages = {}, queue = [], flags} = {}) => ({
13
10
  logger,
14
- file,
11
+ flags,
12
+ file: flags.report || flags.file,
15
13
  status: 'initial',
16
- queue: [],
17
- packages: {},
18
14
  events: [],
19
- setPackages(packages) {
20
- this.packages = Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
21
- acc[name] = {
22
- status: 'initial',
23
- name,
24
- version,
25
- path: absPath,
26
- relPath
27
- }
28
- return acc
29
- }, {})
30
- return this
31
- },
15
+ queue,
16
+ packages: Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
17
+ acc[name] = {
18
+ status: 'initial',
19
+ name,
20
+ version,
21
+ path: absPath,
22
+ relPath
23
+ }
24
+ return acc
25
+ }, {}),
32
26
  get(key, pkgName) {
33
27
  return get(
34
28
  pkgName ? this.packages[pkgName] : this,
@@ -13,23 +13,21 @@ import {fetchPkg, npmPublish} from './npm.js'
13
13
  import {memoizeBy, tpl} from './util.js'
14
14
 
15
15
  export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
16
- const {report, build, publish} = createContext(flags, env)
16
+ const context = await createContext({flags, env, cwd})
17
+ const {report, packages, queue, prev, graphs} = context
18
+ const _runCmd = queuefy(runCmd, flags.concurrency || os.cpus().length)
17
19
 
18
- report.log()('zx-bulk-release')
20
+ report
21
+ .log()('zx-bulk-release')
22
+ .log()('queue:', queue)
23
+ .log()('graphs', graphs)
19
24
 
20
25
  try {
21
- const {packages, queue, root, prev, graphs} = await topo({cwd, flags})
22
- report
23
- .log()('queue:', queue)
24
- .log()('graphs', graphs)
25
- .set('queue', queue)
26
- .setPackages(packages)
27
-
28
26
  await traverseQueue({queue, prev, async cb(name) {
29
27
  report.setStatus('analyzing', name)
30
28
  const pkg = packages[name]
31
- await contextify(pkg, packages, root)
32
- await analyze(pkg, packages)
29
+ await contextify(pkg, context)
30
+ await analyze(pkg)
33
31
  report
34
32
  .set('config', pkg.config, name)
35
33
  .set('version', pkg.version, name)
@@ -47,17 +45,14 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
47
45
  report.setStatus('skipped', name)
48
46
  return
49
47
  }
50
-
51
- report.setStatus('building', name)
52
- await build(pkg, packages)
53
-
54
- if (flags.dryRun) {
55
- report.setStatus('success', name)
56
- return
48
+ if (!flags.noBuild) {
49
+ report.setStatus('building', name)
50
+ await build(pkg, _runCmd)
51
+ }
52
+ if (!flags.dryRun && !flags.noPublish) {
53
+ report.setStatus('publishing', name)
54
+ await publish(pkg, _runCmd)
57
55
  }
58
-
59
- report.setStatus('publishing', name)
60
- await publish(pkg)
61
56
 
62
57
  report.setStatus('success', name)
63
58
  }})
@@ -78,30 +73,31 @@ export const runCmd = async (pkg, name) => {
78
73
 
79
74
  if (cmd) {
80
75
  log({pkg})(`run ${name} '${cmd}'`)
81
- await $.o({cwd: pkg.absPath, quote: v => v, preferLocal: true})`${cmd}`
76
+ return $.o({cwd: pkg.absPath, quote: v => v, preferLocal: true})`${cmd}`
82
77
  }
83
78
  }
84
79
 
85
- const createContext = (flags, env) => {
86
- const report = createReport({file: flags.report})
87
- const _runCmd = queuefy(runCmd, flags.concurrency || os.cpus().length)
88
- const _build = memoizeBy((pkg, packages) => build(pkg, packages, _runCmd, _build))
89
- const _publish = memoizeBy((pkg) => publish(pkg, _runCmd))
80
+ const createContext = async ({flags, env, cwd}) => {
81
+ const { packages, queue, root, prev, graphs } = await topo({cwd, flags})
82
+ const report = createReport({packages, queue, flags})
90
83
 
91
- $.report = report
92
- $.env = {...process.env, ...env}
93
- $.verbose = !!(flags.debug || $.env.DEBUG ) || $.verbose
84
+ $.report = report
85
+ $.env = {...process.env, ...env}
86
+ $.verbose = !!(flags.debug || $.env.DEBUG ) || $.verbose
94
87
 
95
88
  return {
96
89
  report,
97
- runCmd: _runCmd,
98
- build: _build,
99
- publish: _publish
90
+ packages,
91
+ root,
92
+ queue,
93
+ prev,
94
+ graphs,
95
+ flags
100
96
  }
101
97
  }
102
98
 
103
99
  // Inspired by https://docs.github.com/en/actions/learn-github-actions/contexts
104
- const contextify = async (pkg, packages, root) => {
100
+ const contextify = async (pkg, {packages, root}) => {
105
101
  pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
106
102
  pkg.latest = await getLatest(pkg)
107
103
  pkg.context = {
@@ -114,13 +110,12 @@ const contextify = async (pkg, packages, root) => {
114
110
  }
115
111
  }
116
112
 
117
- const build = async (pkg, packages, run = runCmd, self = build) => within(async () => {
113
+ const build = memoizeBy(async (pkg, run = runCmd, flags = {}, self = build) => within(async () => {
118
114
  $.scope = pkg.name
119
- if (pkg.built) return
120
115
 
121
116
  await Promise.all([
122
- traverseDeps(pkg, packages, async (_, {pkg}) => self(pkg, packages, run, self)),
123
- pkg.changes.length === 0 && pkg.config.npmFetch
117
+ traverseDeps(pkg, pkg.context.packages, async (_, {pkg}) => self(pkg, run, flags, self)),
118
+ pkg.changes.length === 0 && pkg.config.npmFetch && !flags.noNpmFetch
124
119
  ? fetchPkg(pkg)
125
120
  : Promise.resolve()
126
121
  ])
@@ -131,10 +126,11 @@ const build = async (pkg, packages, run = runCmd, self = build) => within(async
131
126
  }
132
127
 
133
128
  pkg.built = true
134
- })
129
+ }))
135
130
 
136
- const publish = async (pkg, run = runCmd) => within(async () => {
131
+ const publish = memoizeBy(async (pkg, run = runCmd) => within(async () => {
137
132
  $.scope = pkg.name
133
+
138
134
  await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
139
135
  await pushTag(pkg)
140
136
 
@@ -146,4 +142,6 @@ const publish = async (pkg, run = runCmd) => within(async () => {
146
142
  ghPages(pkg),
147
143
  run(pkg, 'publishCmd')
148
144
  ])
149
- })
145
+
146
+ pkg.published = true
147
+ }))
@@ -30,19 +30,6 @@ export const set = (obj, path, value) => {
30
30
 
31
31
  export const msgJoin = (rest, context, def) => tpl(rest.filter(Boolean).join(' ') || def, context)
32
32
 
33
- export const getPromise = () => {
34
- let resolve, reject
35
- const promise = new Promise((...args) => {
36
- [resolve, reject] = args
37
- })
38
-
39
- return {
40
- resolve,
41
- reject,
42
- promise,
43
- }
44
- }
45
-
46
33
  export const keyByValue = (obj, value) => Object.keys(obj).find((key) => obj[key] === value)
47
34
 
48
35
  export const memoizeBy = (fn, getKey = v => v, memo = new Map()) => async (...args) => {