zx-bulk-release 2.1.6 → 2.2.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,11 @@
1
+ ## [2.2.0](https://github.com/semrel-extra/zx-bulk-release/compare/v2.1.6...v2.2.0) (2023-03-23)
2
+
3
+ ### Fixes & improvements
4
+ * fix: fix basicAuth tpl ([b6af2f5](https://github.com/semrel-extra/zx-bulk-release/commit/b6af2f51a2e13ddb5a3b6b1ef288595aae61e746))
5
+
6
+ ### Features
7
+ * feat: inherit gh and npm creds from pkg release configs ([f07dd4f](https://github.com/semrel-extra/zx-bulk-release/commit/f07dd4fa0cf9d26942780ccf0c34406b345dae39))
8
+
1
9
  ## [2.1.6](https://github.com/semrel-extra/zx-bulk-release/compare/v2.1.5...v2.1.6) (2023-03-22)
2
10
 
3
11
  ### Fixes & improvements
package/README.md CHANGED
@@ -146,9 +146,10 @@ By default, it omits the packages marked as `private`. You can override this by
146
146
 
147
147
  ### `analyze`
148
148
  Determines pkg changes, release type, next version etc.
149
+
149
150
  ```js
150
151
  export const analyze = async (pkg, packages, root) => {
151
- pkg.config = await getConfig(pkg.absPath, root.absPath)
152
+ pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
152
153
  pkg.latest = await getLatest(pkg)
153
154
 
154
155
  const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-bulk-release",
3
- "version": "2.1.6",
3
+ "version": "2.2.0",
4
4
  "description": "zx-based alternative for multi-semantic-release",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,7 +1,8 @@
1
- import {ctx, semver} from 'zx-extra'
1
+ import {semver} from 'zx-extra'
2
2
  import {updateDeps} from './deps.js'
3
- import {formatTag} from './tag.js';
3
+ import {formatTag} from './meta.js';
4
4
  import {log} from './log.js'
5
+ import {getCommits} from './repo.js'
5
6
 
6
7
  export const analyze = async (pkg, packages) => {
7
8
  const semanticChanges = await getSemanticChanges(pkg.absPath, pkg.latest.tag?.ref)
@@ -25,22 +26,8 @@ export const semanticRules = [
25
26
  {group: 'BREAKING CHANGES', releaseType: 'major', keywords: ['BREAKING CHANGE', 'BREAKING CHANGES']},
26
27
  ]
27
28
 
28
- export const getPkgCommits = async (cwd, since) => ctx(async ($) => {
29
- const range = since ? `${since}..HEAD` : 'HEAD'
30
-
31
- $.cwd = cwd
32
- return (await $.raw`git log ${range} --format=+++%s__%b__%h__%H -- .`)
33
- .toString()
34
- .split('+++')
35
- .filter(Boolean)
36
- .map(msg => {
37
- const [subj, body, short, hash] = msg.split('__').map(raw => raw.trim())
38
- return {subj, body, short, hash}
39
- })
40
- })
41
-
42
- export const getSemanticChanges = async (cwd, since) => {
43
- const commits = await getPkgCommits(cwd, since)
29
+ export const getSemanticChanges = async (cwd, from, to) => {
30
+ const commits = await getCommits(cwd, from, to)
44
31
 
45
32
  return analyzeCommits(commits)
46
33
  }
@@ -0,0 +1,42 @@
1
+ import {$} from 'zx-extra'
2
+ import {log} from './log.js'
3
+ import {fetchRepo, getRepo, pushCommit} from './repo.js'
4
+ import {msgJoin} from './util.js'
5
+ import {formatTag} from './meta.js'
6
+
7
+ export const pushChangelog = async (pkg) => {
8
+ const {absPath: cwd, config: {changelog: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
9
+ if (!opts) return
10
+
11
+ log({pkg})('push changelog')
12
+ const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = typeof opts === 'string'
13
+ ? opts.split(' ')
14
+ : [opts.branch, opts.file, opts.msg]
15
+ const _cwd = await fetchRepo({cwd, branch, basicAuth})
16
+ const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
17
+ const releaseNotes = await formatReleaseNotes(pkg)
18
+
19
+ await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
20
+ await pushCommit({cwd, branch, msg, gitCommitterEmail, gitCommitterName, basicAuth})
21
+ }
22
+
23
+ export const formatReleaseNotes = async (pkg) => {
24
+ const {name, version, absPath: cwd, config: {ghBasicAuth: basicAuth}} = pkg
25
+ const {repoPublicUrl} = await getRepo(cwd, {basicAuth})
26
+ const tag = formatTag({name, version})
27
+ const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
28
+ const releaseDetails = Object.values(pkg.changes
29
+ .reduce((acc, {group, subj, short, hash}) => {
30
+ const {commits} = acc[group] || (acc[group] = {commits: [], group})
31
+ const commitRef = `* ${subj}${short ? ` [${short}](${repoPublicUrl}/commit/${hash})` : ''}`
32
+
33
+ commits.push(commitRef)
34
+
35
+ return acc
36
+ }, {}))
37
+ .map(({group, commits}) => `
38
+ ### ${group}
39
+ ${commits.join('\n')}`).join('\n')
40
+
41
+ return releaseDiffRef + '\n' + releaseDetails + '\n'
42
+ }
@@ -1,4 +1,5 @@
1
1
  import { cosmiconfig } from 'cosmiconfig'
2
+ import { camelize } from './util.js'
2
3
 
3
4
  const CONFIG_NAME = 'release'
4
5
  const CONFIG_FILES = [
@@ -15,22 +16,26 @@ const CONFIG_FILES = [
15
16
 
16
17
  export const defaultConfig = {
17
18
  cmd: 'yarn && yarn build && yarn test',
18
- npmFetch: true,
19
19
  changelog: 'changelog',
20
+ npmFetch: true,
20
21
  ghRelease: true,
21
22
  // npmPublish: true,
22
23
  // ghPages: 'gh-pages'
23
24
  }
24
25
 
25
- export const getConfig = async (...cwds) =>
26
- normalizeConfig((await Promise.all(cwds.map(
26
+ export const getPkgConfig = async (...cwds) =>
27
+ normalizePkgConfig((await Promise.all(cwds.map(
27
28
  cwd => cosmiconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILES }).search(cwd).then(r => r?.config)
28
29
  ))).find(Boolean) || defaultConfig)
29
30
 
30
- export const normalizeConfig = config => ({
31
+ export const normalizePkgConfig = (config, env) => ({
32
+ ...parseEnv(env),
31
33
  ...config,
32
34
  npmFetch: config.npmFetch || config.fetch || config.fetchPkg,
33
- buildCmd: config.buildCmd || config.cmd
35
+ buildCmd: config.buildCmd || config.cmd,
36
+ get ghBasicAuth() {
37
+ return this.ghUser && this.ghToken ? `${this.ghUser}:${this.ghToken}` : false
38
+ }
34
39
  })
35
40
 
36
41
  export const parseEnv = (env = process.env) => {
@@ -47,5 +52,4 @@ export const parseEnv = (env = process.env) => {
47
52
  }
48
53
  }
49
54
 
50
- const camelize = s => s.replace(/-./g, x => x[1].toUpperCase())
51
55
  export const normalizeFlags = (flags = {}) => Object.entries(flags).reduce((acc, [k, v]) => ({...acc, [camelize(k)]: v}), {})
@@ -1,14 +1,14 @@
1
1
  // Semantic tags processing
2
2
 
3
- import {ctx, semver, $} from 'zx-extra'
3
+ import {ctx, semver, $, fs, path} from 'zx-extra'
4
4
  import {Buffer} from 'buffer'
5
- import {parseEnv} from './config.js'
6
5
  import {log} from './log.js'
6
+ import {fetchRepo, pushCommit} from './repo.js'
7
+ import {fetchManifest} from './npm.js'
7
8
 
8
9
  export const pushTag = (pkg) => ctx(async ($) => {
9
- const {absPath: cwd, name, version} = pkg
10
+ const {absPath: cwd, name, version, config: {gitCommitterEmail, gitCommitterName}} = pkg
10
11
  const tag = formatTag({name, version})
11
- const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
12
12
 
13
13
  pkg.context.git.tag = tag
14
14
  log({pkg})(`push release tag ${tag}`)
@@ -20,6 +20,41 @@ export const pushTag = (pkg) => ctx(async ($) => {
20
20
  await $`git push origin ${tag}`
21
21
  })
22
22
 
23
+ export const pushMeta = async (pkg) => {
24
+ log({pkg})('push artifact to branch \'meta\'')
25
+
26
+ const {name, version, absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
27
+ const tag = formatTag({name, version})
28
+ const to = '.'
29
+ const branch = 'meta'
30
+ const msg = `chore: release meta ${name} ${version}`
31
+ const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
32
+ const meta = {
33
+ META_VERSION: '1',
34
+ name: pkg.name,
35
+ hash,
36
+ version: pkg.version,
37
+ dependencies: pkg.dependencies,
38
+ devDependencies: pkg.devDependencies,
39
+ peerDependencies: pkg.peerDependencies,
40
+ optionalDependencies: pkg.optionalDependencies,
41
+ }
42
+ const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
43
+
44
+ await pushCommit({cwd, to, branch, msg, files, gitCommitterEmail, gitCommitterName, basicAuth})
45
+ }
46
+
47
+ export const getLatest = async (pkg) => {
48
+ const {absPath: cwd, name} = pkg
49
+ const tag = await getLatestTag(cwd, name)
50
+ const meta = await getLatestMeta(cwd, tag?.ref) || await fetchManifest(pkg, {nothrow: true})
51
+
52
+ return {
53
+ tag,
54
+ meta
55
+ }
56
+ }
57
+
23
58
  const f0 = {
24
59
  parse(tag) {
25
60
  if (!tag.endsWith('-f0')) return null
@@ -102,8 +137,8 @@ export const parseTag = (tag) => f0.parse(tag) || f1.parse(tag) || lerna.parse(t
102
137
 
103
138
  export const formatTag = (tag) => f0.format(tag) || f1.format(tag) || null
104
139
 
105
- export const getTags = async (cwd) =>
106
- (await $.o({cwd})`git tag -l`).toString()
140
+ export const getTags = async (cwd, ref = '') =>
141
+ (await $.o({cwd})`git tag -l ${ref}`).toString()
107
142
  .split('\n')
108
143
  .map(tag => parseTag(tag.trim()))
109
144
  .filter(Boolean)
@@ -118,3 +153,19 @@ export const getLatestTaggedVersion = async (cwd, name) =>
118
153
  export const formatDateTag = (date = new Date()) => `${date.getUTCFullYear()}.${date.getUTCMonth() + 1}.${date.getUTCDate()}`
119
154
 
120
155
  export const parseDateTag = (date) => new Date(date.replaceAll('.', '-')+'Z')
156
+
157
+ export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
158
+
159
+ export const getLatestMeta = async (cwd, tag) => {
160
+ if (!tag) return null
161
+
162
+ try {
163
+ const _cwd = await fetchRepo({cwd, branch: 'meta'})
164
+ return await Promise.any([
165
+ fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
166
+ fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
167
+ ])
168
+ } catch {}
169
+
170
+ return null
171
+ }
@@ -1,14 +1,13 @@
1
- import {parseEnv} from './config.js'
2
1
  import {log} from './log.js'
3
2
  import {$, ctx, fs, path, INI, fetch} from 'zx-extra'
4
3
 
5
- export const fetchPkg = async (pkg, {env = $.env} = {}) => {
4
+ export const fetchPkg = async (pkg) => {
6
5
  const id = `${pkg.name}@${pkg.version}`
7
6
 
8
7
  try {
9
8
  log({pkg})(`fetching '${id}'`)
10
9
  const cwd = pkg.absPath
11
- const {npmRegistry, npmToken, npmConfig} = parseEnv(env)
10
+ const {npmRegistry, npmToken, npmConfig} = pkg.config
12
11
  const bearerToken = getBearerToken(npmRegistry, npmToken, npmConfig)
13
12
  const tarball = getTarballUrl(npmRegistry, pkg.name, pkg.version)
14
13
  await $.raw`wget --timeout=10 --header='Authorization: ${bearerToken}' -qO- ${tarball} | tar -xvz --strip-components=1 --exclude='package.json' -C ${cwd}`
@@ -19,8 +18,8 @@ export const fetchPkg = async (pkg, {env = $.env} = {}) => {
19
18
  }
20
19
  }
21
20
 
22
- export const fetchManifest = async (pkg, {nothrow, env = $.env} = {}) => {
23
- const {npmRegistry, npmToken, npmConfig} = parseEnv(env)
21
+ export const fetchManifest = async (pkg, {nothrow} = {}) => {
22
+ const {npmRegistry, npmToken, npmConfig} = pkg.config
24
23
  const bearerToken = getBearerToken(npmRegistry, npmToken, npmConfig)
25
24
  const url = getManifestUrl(npmRegistry, pkg.name, pkg.version)
26
25
 
@@ -36,9 +35,9 @@ export const fetchManifest = async (pkg, {nothrow, env = $.env} = {}) => {
36
35
  }
37
36
 
38
37
  export const npmPublish = (pkg) => ctx(async ($) => {
39
- const {absPath: cwd, name, version, manifest} = pkg
40
- if (manifest.private || pkg.config?.npmPublish === false) return
41
- const {npmRegistry, npmToken, npmConfig} = parseEnv($.env)
38
+ const {absPath: cwd, name, version, manifest, config} = pkg
39
+ if (manifest.private || config?.npmPublish === false) return
40
+ const {npmRegistry, npmToken, npmConfig} = config
42
41
  const npmrc = npmConfig ? npmConfig : path.resolve(cwd, '.npmrc')
43
42
 
44
43
  log({pkg})(`publish npm package ${name} ${version} to ${npmRegistry}`)
@@ -1,14 +1,15 @@
1
1
  import os from 'node:os'
2
- import {$, within} from 'zx-extra'
3
- import {log} from './log.js'
4
- import {topo, traverseQueue} from './deps.js'
2
+ import {$, fs, within} from 'zx-extra'
3
+ import {queuefy} from 'queuefy'
5
4
  import {analyze} from './analyze.js'
6
5
  import {build} from './build.js'
7
- import {getLatest, publish} from './publish.js'
8
- import {createState} from './state.js'
9
- import {memoizeBy, tpl} from './util.js'
10
- import {queuefy} from 'queuefy'
11
- import {getConfig} from "./config.js";
6
+ import {getPkgConfig} from './config.js'
7
+ import {topo, traverseQueue} from './deps.js'
8
+ import {log} from './log.js'
9
+ import {getLatest} from './meta.js'
10
+ import {publish} from './publish.js'
11
+ import {getRoot, getSha} from './repo.js'
12
+ import {get, memoizeBy, set, tpl} from './util.js'
12
13
 
13
14
  export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
14
15
  const {state, build, publish} = createContext(flags, env)
@@ -16,21 +17,25 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
16
17
  log()('zx-bulk-release')
17
18
 
18
19
  try {
19
- const {packages, queue, root, sources, next, prev, nodes} = await topo({cwd, flags})
20
+ const {packages, queue, root, sources, next, prev, nodes, graphs, edges} = await topo({cwd, flags})
20
21
  log()('queue:', queue)
22
+ log()('graphs', graphs)
21
23
 
22
- state.setQueue(queue, packages)
24
+ state
25
+ .setQueue(queue)
26
+ .setPackages(packages)
23
27
 
24
28
  await traverseQueue({queue, prev, async cb(name) {
25
29
  state.setStatus('analyzing', name)
26
30
  const pkg = packages[name]
27
31
  await contextify(pkg, packages, root)
28
32
  await analyze(pkg, packages)
29
- state.set('config', pkg.config, name)
30
- state.set('version', pkg.version, name)
31
- state.set('prevVersion', pkg.latest.tag?.version || pkg.manifest.version, name)
32
- state.set('releaseType', pkg.releaseType, name)
33
- state.set('tag', pkg.tag, name)
33
+ state
34
+ .set('config', pkg.config, name)
35
+ .set('version', pkg.version, name)
36
+ .set('prevVersion', pkg.latest.tag?.version || pkg.manifest.version, name)
37
+ .set('releaseType', pkg.releaseType, name)
38
+ .set('tag', pkg.tag, name)
34
39
  }})
35
40
 
36
41
  state.setStatus('pending')
@@ -58,8 +63,9 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
58
63
  }})
59
64
  } catch (e) {
60
65
  log({level: 'error'})(e)
61
- state.set('error', e)
62
- state.setStatus('failure')
66
+ state
67
+ .set('error', e)
68
+ .setStatus('failure')
63
69
  throw e
64
70
  }
65
71
  state.setStatus('success')
@@ -95,14 +101,75 @@ const createContext = (flags, env) => {
95
101
 
96
102
  // Inspired by https://docs.github.com/en/actions/learn-github-actions/contexts
97
103
  export const contextify = async (pkg, packages, root) => {
98
- pkg.config = await getConfig(pkg.absPath, root.absPath)
104
+ pkg.config = await getPkgConfig(pkg.absPath, root.absPath)
99
105
  pkg.latest = await getLatest(pkg)
100
106
  pkg.context = {
101
107
  git: {
102
- sha: (await $`git rev-parse HEAD`).toString().trim(),
103
- root: (await $`git rev-parse --show-toplevel`).toString().trim(),
108
+ sha: await getSha(pkg.absPath),
109
+ root: await getRoot(pkg.absPath)
104
110
  },
105
111
  env: $.env,
106
112
  packages
107
113
  }
108
114
  }
115
+
116
+ export const createState = ({logger = console, file = null} = {}) => ({
117
+ logger,
118
+ file,
119
+ status: 'initial',
120
+ queue: [],
121
+ packages: {},
122
+ events: [],
123
+ setQueue(queue) {
124
+ this.queue = queue
125
+ return this
126
+ },
127
+ setPackages(packages) {
128
+ this.packages = Object.entries(packages).reduce((acc, [name, {manifest: {version}, absPath, relPath}]) => {
129
+ acc[name] = {
130
+ status: 'initial',
131
+ name,
132
+ version,
133
+ path: absPath,
134
+ relPath
135
+ }
136
+ return acc
137
+ }, {})
138
+ return this
139
+ },
140
+ get(key, pkgName) {
141
+ return get(
142
+ pkgName ? this.packages[pkgName] : this,
143
+ key
144
+ )
145
+ },
146
+ set(key, value, pkgName) {
147
+ set(
148
+ pkgName ? this.packages[pkgName] : this,
149
+ key,
150
+ value
151
+ )
152
+ return this
153
+ },
154
+ setStatus(status, name) {
155
+ this.set('status', status, name)
156
+ this.save()
157
+ return this
158
+ },
159
+ getStatus(status, name) {
160
+ return this.get('status', name)
161
+ },
162
+ log(ctx = {}) {
163
+ return function (...chunks) {
164
+ const {pkg, scope = pkg?.name || '~', level = 'info'} = ctx
165
+ const msg = chunks.map(c => typeof c === 'string' ? tpl(c, ctx) : c)
166
+ const event = {msg, scope, date: Date.now(), level}
167
+ this.events.push(event)
168
+ logger[level](`[${scope}]`, ...msg)
169
+ }.bind(this)
170
+ },
171
+ save() {
172
+ this.file && fs.outputJsonSync(this.file, this)
173
+ return this
174
+ }
175
+ })
@@ -1,11 +1,12 @@
1
1
  import {fs, path, $} from 'zx-extra'
2
- import {formatTag, getLatestTag, pushTag} from './tag.js'
3
- import {push, fetch, parseRepo} from './repo.js'
4
- import {parseEnv} from './config.js'
5
- import {fetchManifest, npmPublish} from './npm.js'
2
+ import {formatTag, pushTag} from './meta.js'
3
+ import {pushCommit, getRepo} from './repo.js'
4
+ import {npmPublish} from './npm.js'
5
+ import {pushChangelog, formatReleaseNotes} from './changelog.js'
6
6
  import {msgJoin} from './util.js'
7
7
  import {runCmd} from './processor.js'
8
8
  import {log} from './log.js'
9
+ import {pushMeta} from './meta.js'
9
10
 
10
11
  export const publish = async (pkg, run = runCmd) => {
11
12
  await fs.writeJson(pkg.manifestPath, pkg.manifest, {spaces: 2})
@@ -18,38 +19,14 @@ export const publish = async (pkg, run = runCmd) => {
18
19
  await run(pkg, 'publishCmd')
19
20
  }
20
21
 
21
- export const pushMeta = async (pkg) => {
22
- log({pkg})('push artifact to branch \'meta\'')
23
-
24
- const {name, version, absPath: cwd} = pkg
25
- const tag = formatTag({name, version})
26
- const to = '.'
27
- const branch = 'meta'
28
- const msg = `chore: release meta ${name} ${version}`
29
- const hash = (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
30
- const meta = {
31
- META_VERSION: '1',
32
- name: pkg.name,
33
- hash,
34
- version: pkg.version,
35
- dependencies: pkg.dependencies,
36
- devDependencies: pkg.devDependencies,
37
- peerDependencies: pkg.peerDependencies,
38
- optionalDependencies: pkg.optionalDependencies,
39
- }
40
- const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
41
-
42
- await push({cwd, to, branch, msg, files})
43
- }
44
-
45
22
  export const ghRelease = async (pkg) => {
46
23
  log({pkg})(`create gh release`)
47
24
 
48
- const {ghToken} = parseEnv($.env)
25
+ const {ghBasicAuth: basicAuth, ghToken} = pkg.config
49
26
  if (!ghToken) return null
50
27
 
51
28
  const {name, version, absPath: cwd} = pkg
52
- const {repoName} = await parseRepo(cwd)
29
+ const {repoName} = await getRepo(cwd, {basicAuth})
53
30
  const tag = formatTag({name, version})
54
31
  const releaseNotes = await formatReleaseNotes(pkg)
55
32
  const releaseData = JSON.stringify({
@@ -61,45 +38,8 @@ export const ghRelease = async (pkg) => {
61
38
  await $.o({cwd})`curl -H "Authorization: token ${ghToken}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${repoName}/releases -d ${releaseData}`
62
39
  }
63
40
 
64
- const pushChangelog = async (pkg) => {
65
- const {config: {changelog: opts}} = pkg
66
- if (!opts) return
67
-
68
- log({pkg})('push changelog')
69
- const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = typeof opts === 'string'
70
- ? opts.split(' ')
71
- : [opts.branch, opts.file, opts.msg]
72
- const _cwd = await fetch({cwd: pkg.absPath, branch})
73
- const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
74
- const releaseNotes = await formatReleaseNotes(pkg)
75
-
76
- await $.o({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
77
- await push({cwd: pkg.absPath, branch, msg})
78
- }
79
-
80
- const formatReleaseNotes = async (pkg) => {
81
- const {name, version, absPath: cwd} = pkg
82
- const {repoPublicUrl} = await parseRepo(cwd)
83
- const tag = formatTag({name, version})
84
- const releaseDiffRef = `## [${name}@${version}](${repoPublicUrl}/compare/${pkg.latest.tag?.ref}...${tag}) (${new Date().toISOString().slice(0, 10)})`
85
- const releaseDetails = Object.values(pkg.changes
86
- .reduce((acc, {group, subj, short, hash}) => {
87
- const {commits} = acc[group] || (acc[group] = {commits: [], group})
88
- const commitRef = `* ${subj}${short ? ` [${short}](${repoPublicUrl}/commit/${hash})` : ''}`
89
-
90
- commits.push(commitRef)
91
-
92
- return acc
93
- }, {}))
94
- .map(({group, commits}) => `
95
- ### ${group}
96
- ${commits.join('\n')}`).join('\n')
97
-
98
- return releaseDiffRef + '\n' + releaseDetails + '\n'
99
- }
100
-
101
41
  const ghPages = async (pkg) => {
102
- const {config: {ghPages: opts}} = pkg
42
+ const {config: {ghPages: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
103
43
  if (!opts) return
104
44
 
105
45
  const [branch = 'gh-pages', from = 'docs', to = '.', ..._msg] = typeof opts === 'string'
@@ -109,38 +49,14 @@ const ghPages = async (pkg) => {
109
49
 
110
50
  log({pkg})(`publish docs to ${branch}`)
111
51
 
112
- await push({
52
+ await pushCommit({
113
53
  cwd: path.join(pkg.absPath, from),
114
54
  from: '.',
115
55
  to,
116
56
  branch,
117
- msg
57
+ msg,
58
+ gitCommitterEmail,
59
+ gitCommitterName,
60
+ basicAuth
118
61
  })
119
62
  }
120
-
121
- export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g, '-')
122
-
123
- export const getLatestMeta = async (cwd, tag) => {
124
- if (!tag) return null
125
-
126
- try {
127
- const _cwd = await fetch({cwd, branch: 'meta'})
128
- return await Promise.any([
129
- fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
130
- fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
131
- ])
132
- } catch {}
133
-
134
- return null
135
- }
136
-
137
- export const getLatest = async (pkg) => {
138
- const {absPath: cwd, name} = pkg
139
- const tag = await getLatestTag(cwd, name)
140
- const meta = await getLatestMeta(cwd, tag?.ref) || await fetchManifest(pkg, {nothrow: true})
141
-
142
- return {
143
- tag,
144
- meta
145
- }
146
- }
@@ -1,16 +1,15 @@
1
1
  import {$, ctx, fs, path, tempy, copy} from 'zx-extra'
2
- import {parseEnv} from './config.js'
3
2
  import {log} from './log.js'
4
3
  import {keyByValue} from './util.js'
5
4
 
6
5
  const branches = {}
7
- export const fetch = async ({cwd: _cwd, branch, origin: _origin}) => ctx(async ($) => {
6
+ export const fetchRepo = async ({cwd: _cwd, branch, origin: _origin, basicAuth}) => ctx(async ($) => {
8
7
  const root = await getRoot(_cwd)
9
8
  const id = `${root}:${branch}`
10
9
 
11
10
  if (branches[id]) return branches[id]
12
11
 
13
- const origin = _origin || (await parseRepo(_cwd)).repoAuthedUrl
12
+ const origin = _origin || (await getRepo(_cwd, {basicAuth})).repoAuthedUrl
14
13
  const cwd = tempy.temporaryDirectory()
15
14
  $.cwd = cwd
16
15
  try {
@@ -25,14 +24,14 @@ export const fetch = async ({cwd: _cwd, branch, origin: _origin}) => ctx(async (
25
24
  return branches[id]
26
25
  })
27
26
 
28
- export const push = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = []}) => ctx(async ($) => {
27
+ export const pushCommit = async ({cwd, from, to, branch, origin, msg, ignoreFiles, files = [], basicAuth, gitCommitterEmail, gitCommitterName}) => ctx(async ($) => {
29
28
  let retries = 3
30
29
  let _cwd
31
30
 
32
31
  while (retries > 0) {
33
32
  try {
34
- const {gitCommitterEmail, gitCommitterName} = parseEnv($.env)
35
- _cwd = await fetch({cwd, branch, origin})
33
+
34
+ _cwd = await fetchRepo({cwd, branch, origin, basicAuth})
36
35
 
37
36
  for (let {relpath, contents} of files) {
38
37
  const _contents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2)
@@ -62,16 +61,15 @@ export const push = async ({cwd, from, to, branch, origin, msg, ignoreFiles, fil
62
61
  })
63
62
 
64
63
  const repos = {}
65
- export const parseRepo = async (_cwd) => {
64
+ export const getRepo = async (_cwd, {basicAuth} = {}) => {
66
65
  const cwd = await getRoot(_cwd)
67
66
  if (repos[cwd]) return repos[cwd]
68
67
 
69
- const {ghToken, ghUser} = parseEnv($.env)
70
68
  const originUrl = await getOrigin(cwd)
71
69
  const [, , repoHost, repoName] = originUrl.replace(':', '/').replace(/\.git/, '').match(/.+(@|\/\/)([^/]+)\/(.+)$/) || []
72
70
  const repoPublicUrl = `https://${repoHost}/${repoName}`
73
- const repoAuthedUrl = ghToken && ghUser && repoHost && repoName
74
- ? `https://${ghUser}:${ghToken}@${repoHost}/${repoName}.git`
71
+ const repoAuthedUrl = basicAuth && repoHost && repoName
72
+ ? `https://${basicAuth}@${repoHost}/${repoName}.git`
75
73
  : originUrl
76
74
 
77
75
  repos[cwd] = {
@@ -95,3 +93,19 @@ export const getOrigin = async (cwd) => {
95
93
  }
96
94
 
97
95
  export const getRoot = async (cwd) => (await $.o({cwd})`git rev-parse --show-toplevel`).toString().trim()
96
+
97
+ export const getSha = async (cwd) => (await $.o({cwd})`git rev-parse HEAD`).toString().trim()
98
+
99
+ export const getCommits = async (cwd, from, to = 'HEAD') => ctx(async ($) => {
100
+ const ref = from ? `${from}..${to}` : to
101
+
102
+ $.cwd = cwd
103
+ return (await $.raw`git log ${ref} --format=+++%s__%b__%h__%H -- .`)
104
+ .toString()
105
+ .split('+++')
106
+ .filter(Boolean)
107
+ .map(msg => {
108
+ const [subj, body, short, hash] = msg.split('__').map(raw => raw.trim())
109
+ return {subj, body, short, hash}
110
+ })
111
+ })
@@ -55,3 +55,5 @@ export const memoizeBy = (fn, memo = new Map(), getKey = v => v) => (...args) =>
55
55
  memo.set(key, value)
56
56
  return value
57
57
  }
58
+
59
+ export const camelize = s => s.replace(/-./g, x => x[1].toUpperCase())
@@ -1,56 +0,0 @@
1
- import {get, set, tpl} from './util.js'
2
- import {fs} from 'zx-extra'
3
-
4
- export const createState = ({logger = console, file = null} = {}) => ({
5
- logger,
6
- file,
7
- status: 'initial',
8
- queue: [],
9
- packages: [],
10
- events: [],
11
- setQueue(queue, packages) {
12
- this.queue = queue
13
- this.packages = queue.map(name => {
14
- const {manifest: {version}, absPath, relPath} = packages[name]
15
- return {
16
- status: 'initial',
17
- name,
18
- version,
19
- path: absPath,
20
- relPath
21
- }
22
- })
23
- },
24
- get(key, pkgName) {
25
- return get(
26
- pkgName ? this.packages.find(({name}) => name === pkgName) : this,
27
- key
28
- )
29
- },
30
- set(key, value, pkgName) {
31
- set(
32
- pkgName ? this.packages.find(({name}) => name === pkgName) : this,
33
- key,
34
- value
35
- )
36
- },
37
- setStatus(status, name) {
38
- this.set('status', status, name)
39
- this.save()
40
- },
41
- getStatus(status, name) {
42
- return this.get('status', name)
43
- },
44
- log(ctx = {}) {
45
- return function (...chunks) {
46
- const {pkg, scope = pkg?.name || '~', level = 'info'} = ctx
47
- const msg = chunks.map(c => typeof c === 'string' ? tpl(c, ctx) : c)
48
- const event = {msg, scope, date: Date.now(), level}
49
- this.events.push(event)
50
- logger[level](`[${scope}]`, ...msg)
51
- }.bind(this)
52
- },
53
- save() {
54
- this.file && fs.outputJsonSync(this.file, this)
55
- }
56
- })