bulk-release 2.21.1 → 3.0.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +94 -26
  3. package/package.json +1 -1
  4. package/src/main/js/index.js +1 -1
  5. package/src/main/js/{processor → post}/api/gh.js +0 -27
  6. package/src/main/js/{processor → post}/api/git.js +2 -10
  7. package/src/main/js/{processor → post}/api/npm.js +4 -2
  8. package/src/main/js/post/courier/channels/changelog.js +29 -0
  9. package/src/main/js/{processor/publishers → post/courier/channels}/cmd.js +1 -0
  10. package/src/main/js/post/courier/channels/gh-pages.js +30 -0
  11. package/src/main/js/post/courier/channels/gh-release.js +35 -0
  12. package/src/main/js/post/courier/channels/meta.js +34 -0
  13. package/src/main/js/post/courier/channels/npm.js +26 -0
  14. package/src/main/js/post/courier/index.js +113 -0
  15. package/src/main/js/post/courier/parcel.js +77 -0
  16. package/src/main/js/{processor → post/depot}/exec.js +2 -2
  17. package/src/main/js/{processor → post/depot}/generators/meta.js +3 -3
  18. package/src/main/js/{processor → post/depot}/generators/notes.js +1 -1
  19. package/src/main/js/{processor → post/depot}/steps/analyze.js +2 -2
  20. package/src/main/js/{processor → post/depot}/steps/build.js +2 -2
  21. package/src/main/js/{processor → post/depot}/steps/clean.js +2 -2
  22. package/src/main/js/{processor → post/depot}/steps/contextify.js +3 -3
  23. package/src/main/js/post/depot/steps/pack.js +59 -0
  24. package/src/main/js/post/depot/steps/publish.js +29 -0
  25. package/src/main/js/{processor → post/depot}/steps/test.js +1 -1
  26. package/src/main/js/{processor → post}/release.js +44 -37
  27. package/src/main/js/post/tar.js +73 -0
  28. package/src/test/js/utils/mock.js +1 -1
  29. package/src/main/js/processor/publishers/changelog.js +0 -26
  30. package/src/main/js/processor/publishers/gh-pages.js +0 -32
  31. package/src/main/js/processor/publishers/gh-release.js +0 -41
  32. package/src/main/js/processor/publishers/meta.js +0 -58
  33. package/src/main/js/processor/publishers/npm.js +0 -15
  34. package/src/main/js/processor/steps/publish.js +0 -39
  35. package/src/main/js/processor/steps/teardown.js +0 -58
  36. /package/src/main/js/{processor → post/depot}/deps.js +0 -0
  37. /package/src/main/js/{processor → post/depot}/generators/tag.js +0 -0
  38. /package/src/main/js/{processor → post}/log.js +0 -0
@@ -0,0 +1,77 @@
1
+ import {asTuple, msgJoin} from '../../util.js'
2
+
3
+ const gitFields = (a, pkg) => ({
4
+ repoHost: a.repoHost,
5
+ repoName: a.repoName,
6
+ originUrl: a.originUrl,
7
+ gitCommitterEmail: pkg.config.gitCommitterEmail,
8
+ gitCommitterName: pkg.config.gitCommitterName,
9
+ })
10
+
11
+ const entry = {
12
+ npm: (pkg, ctx, a) => ({
13
+ channel: 'npm',
14
+ manifest: {
15
+ channel: 'npm',
16
+ name: pkg.name, version: pkg.version, preversion: pkg.preversion,
17
+ registry: '${{NPM_REGISTRY}}', token: '${{NPM_TOKEN}}',
18
+ config: '${{NPM_CONFIG}}', provenance: '${{NPM_PROVENANCE}}', oidc: '${{NPM_OIDC}}',
19
+ },
20
+ files: a.npmTarball ? [{name: 'package.tgz', source: a.npmTarball}] : [],
21
+ }),
22
+
23
+ 'gh-release': (pkg, ctx, a) => ({
24
+ channel: 'gh-release',
25
+ manifest: {
26
+ channel: 'gh-release',
27
+ tag: pkg.tag, repoName: a.repoName, releaseNotes: a.releaseNotes,
28
+ token: '${{GH_TOKEN}}', apiUrl: '${{GH_API_URL}}',
29
+ assets: pkg.config.ghAssets ? [...pkg.config.ghAssets] : undefined,
30
+ },
31
+ files: a.assetsDir ? [{name: 'assets', source: a.assetsDir}] : [],
32
+ }),
33
+
34
+ 'gh-pages': (pkg, ctx, a) => {
35
+ const [branch = 'gh-pages', , to = '.', ..._msg] = asTuple(pkg.config.ghPages, ['branch', 'from', 'to', 'msg'])
36
+ return {
37
+ channel: 'gh-pages',
38
+ manifest: {channel: 'gh-pages', branch, to, msg: msgJoin(_msg, pkg, 'docs: update docs ${{name}} ${{version}}'), ...gitFields(a, pkg)},
39
+ files: a.docsDir ? [{name: 'docs', source: a.docsDir}] : [],
40
+ }
41
+ },
42
+
43
+ changelog: (pkg, ctx, a) => {
44
+ const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = asTuple(pkg.config.changelog, ['branch', 'file', 'msg'])
45
+ return {
46
+ channel: 'changelog',
47
+ manifest: {channel: 'changelog', releaseNotes: a.releaseNotes, branch, file, msg: msgJoin(_msg, pkg, 'chore: update changelog ${{name}}'), ...gitFields(a, pkg)},
48
+ files: [],
49
+ }
50
+ },
51
+
52
+ meta: (pkg, ctx, a) => ({
53
+ channel: 'meta',
54
+ manifest: {
55
+ channel: 'meta',
56
+ name: pkg.name, version: pkg.version, tag: pkg.tag,
57
+ type: pkg.config.meta?.type ?? null, data: pkg.meta,
58
+ ...gitFields(a, pkg),
59
+ },
60
+ files: [],
61
+ }),
62
+ }
63
+
64
+ const defaultEntry = (channel) => (pkg, ctx, a) => ({
65
+ channel,
66
+ manifest: {channel, name: pkg.name, version: pkg.version, tag: pkg.tag},
67
+ files: [],
68
+ })
69
+
70
+ export const buildParcels = (pkg, ctx, {
71
+ channels: channelNames = [],
72
+ npmTarball, releaseNotes, docsDir, assetsDir,
73
+ repoName, repoHost, originUrl,
74
+ } = {}) => {
75
+ const a = {npmTarball, releaseNotes, docsDir, assetsDir, repoName, repoHost, originUrl}
76
+ return channelNames.map(n => (entry[n] || defaultEntry(n))(pkg, ctx, a))
77
+ }
@@ -1,5 +1,5 @@
1
- import {tpl} from '../util.js'
2
- import {log} from './log.js'
1
+ import {tpl} from '../../util.js'
2
+ import {log} from '../log.js'
3
3
  import {$} from 'zx-extra'
4
4
 
5
5
  export const exec = async (pkg, name) => {
@@ -1,9 +1,9 @@
1
1
  // Meta generator: builds pkg.meta payload and resolves latest-release meta from git tags / gh assets / meta branch.
2
2
 
3
3
  import {semver, $, fs, path} from 'zx-extra'
4
- import {fetchRepo, getTags as getGitTags, getRepo} from '../api/git.js'
5
- import {fetchManifest} from '../api/npm.js'
6
- import {ghGetAsset} from '../api/gh.js'
4
+ import {fetchRepo, getTags as getGitTags, getRepo} from '../../api/git.js'
5
+ import {fetchManifest} from '../../api/npm.js'
6
+ import {ghGetAsset} from '../../api/gh.js'
7
7
  import {parseTag} from './tag.js'
8
8
 
9
9
  export const isAssetMode = (type) => type === 'asset' || type === 'assets'
@@ -1,6 +1,6 @@
1
1
  // Release notes formatting. Pure except for a single getRepo() call to resolve repoPublicUrl.
2
2
 
3
- import {getRepo} from '../api/git.js'
3
+ import {getRepo} from '../../api/git.js'
4
4
  import {formatTag} from './tag.js'
5
5
 
6
6
  export const DIFF_TAG_URL = '${repoPublicUrl}/compare/${prevTag}...${newTag}'
@@ -1,6 +1,6 @@
1
1
  import {semver} from 'zx-extra'
2
- import {log} from '../log.js'
3
- import {getCommits} from '../api/git.js'
2
+ import {log} from '../../log.js'
3
+ import {getCommits} from '../../api/git.js'
4
4
  import {updateDeps} from '../deps.js'
5
5
  import {formatTag} from '../generators/tag.js'
6
6
 
@@ -1,5 +1,5 @@
1
- import {memoizeBy} from '../../util.js'
2
- import {fetchPkg} from '../api/npm.js'
1
+ import {memoizeBy} from '../../../util.js'
2
+ import {fetchPkg} from '../../api/npm.js'
3
3
  import {traverseDeps} from '../deps.js'
4
4
  import {exec} from '../exec.js'
5
5
 
@@ -1,5 +1,5 @@
1
- import {unsetUserConfig} from '../api/git.js'
2
- import {npmRestore} from '../api/npm.js'
1
+ import {unsetUserConfig} from '../../api/git.js'
2
+ import {npmRestore} from '../../api/npm.js'
3
3
 
4
4
  export const clean = async ({cwd, packages}) => {
5
5
  await unsetUserConfig(cwd)
@@ -1,13 +1,13 @@
1
- import {getPkgConfig} from '../../config.js'
1
+ import {getPkgConfig} from '../../../config.js'
2
2
  import {getLatest} from '../generators/meta.js'
3
- import {getRoot, getSha} from '../api/git.js'
3
+ import {getRoot, getSha} from '../../api/git.js'
4
4
 
5
5
  /**
6
6
  * Global release context — one per `run()` invocation.
7
7
  * Built by `createContext()` in release.js and extended with runtime bits (run, publishers).
8
8
  *
9
9
  * @typedef {object} ReleaseContext
10
- * @property {object} flags CLI flags (build, test, publish, snapshot, dryRun, recover, ...).
10
+ * @property {object} flags CLI flags (build, test, publish, snapshot, dryRun, pack, deliver, ...).
11
11
  * @property {Record<string,string>} env Resolved process env (process.env merged with per-run overrides).
12
12
  * @property {string} cwd Repo working directory.
13
13
  * @property {object} root Root package descriptor (from topo()).
@@ -0,0 +1,59 @@
1
+ import {$, tempy, fs, path} from 'zx-extra'
2
+ import {memoizeBy, asTuple} from '../../../util.js'
3
+ import {channels, prepare, buildParcels} from '../../courier/index.js'
4
+ import {npmPersist} from '../../api/npm.js'
5
+ import {getRepo} from '../../api/git.js'
6
+ import {formatReleaseNotes} from '../generators/notes.js'
7
+ import {ghPrepareAssets} from '../../api/gh.js'
8
+ import {packTar, hashFile} from '../../tar.js'
9
+
10
+ const filterActive = (names, pkg, {snapshot = false} = {}) =>
11
+ names.filter(n => {
12
+ const ch = channels[n]
13
+ return ch && ch.transport !== false && (!snapshot || ch.snapshot) && ch.when(pkg)
14
+ })
15
+
16
+ export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
17
+ const {channels: channelNames = [], flags} = ctx
18
+ const snapshot = !!flags.snapshot
19
+ const active = filterActive(channelNames, pkg, {snapshot})
20
+
21
+ await prepare(active, pkg)
22
+ await npmPersist(pkg)
23
+
24
+ const outputDir = flags.pack ? (typeof flags.pack === 'string' ? flags.pack : 'parcels') : null
25
+ const stageDir = outputDir || tempy.temporaryDirectory()
26
+ if (outputDir) await fs.ensureDir(outputDir)
27
+ const {repoName, repoHost, originUrl} = await getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
28
+ const artifacts = {repoName, repoHost, originUrl}
29
+
30
+ if (active.includes('npm')) {
31
+ const out = await $({cwd: pkg.absPath})`npm pack --pack-destination ${stageDir}`
32
+ artifacts.npmTarball = path.join(stageDir, out.toString().trim())
33
+ }
34
+ if (active.includes('gh-release') || active.includes('changelog'))
35
+ artifacts.releaseNotes = await formatReleaseNotes(pkg)
36
+ if (active.includes('gh-pages')) {
37
+ const [, from = 'docs'] = asTuple(pkg.config.ghPages, ['branch', 'from'])
38
+ artifacts.docsDir = path.join(stageDir, 'docs')
39
+ await fs.copy(path.join(pkg.absPath, from), artifacts.docsDir)
40
+ }
41
+ if (active.includes('gh-release') && pkg.config.ghAssets?.length)
42
+ artifacts.assetsDir = await ghPrepareAssets(pkg.config.ghAssets, pkg.absPath)
43
+
44
+ const parcels = buildParcels(pkg, ctx, {channels: active, ...artifacts})
45
+
46
+ const tars = []
47
+ for (const {channel, manifest, files} of parcels) {
48
+ // Two-pass: pack to temp, hash, rename to final name.
49
+ const tmpPath = path.join(stageDir, `_tmp.${channel}.tar`)
50
+ await packTar(tmpPath, manifest, files)
51
+ const hash = await hashFile(tmpPath)
52
+ const finalPath = path.join(stageDir, `parcel.${pkg.tag}.${channel}.${hash}.tar`)
53
+ await fs.rename(tmpPath, finalPath)
54
+ tars.push(finalPath)
55
+ }
56
+
57
+ pkg.tars = tars
58
+ pkg.activeTransport = active
59
+ })
@@ -0,0 +1,29 @@
1
+ import {memoizeBy} from '../../../util.js'
2
+ import {exec} from '../exec.js'
3
+ import {log} from '../../log.js'
4
+ import {deliver, channels, runChannel} from '../../courier/index.js'
5
+ import {pushTag} from '../../api/git.js'
6
+
7
+ export const publish = memoizeBy(async (pkg, ctx = pkg.ctx) => {
8
+ if (pkg.version !== pkg.manifest.version)
9
+ throw new Error('package.json version not synced')
10
+
11
+ const {run = exec, channels: channelNames = [], flags} = ctx
12
+ const snapshot = !!flags.snapshot
13
+ const {tars = []} = pkg
14
+
15
+ if (!snapshot) {
16
+ const {tag, config: {gitCommitterEmail, gitCommitterName}} = pkg
17
+ ctx.git.tag = tag
18
+ log.info(`push release tag ${tag}`)
19
+ await pushTag({cwd: ctx.git.root, tag, gitCommitterEmail, gitCommitterName})
20
+ }
21
+
22
+ await deliver(tars, ctx.env)
23
+
24
+ const cmd = channels.cmd
25
+ if (channelNames.includes('cmd') && cmd?.when(pkg) && (!snapshot || cmd.snapshot))
26
+ await runChannel('cmd', pkg, run)
27
+
28
+ pkg.published = true
29
+ })
@@ -1,4 +1,4 @@
1
- import {memoizeBy} from '../../util.js'
1
+ import {memoizeBy} from '../../../util.js'
2
2
  import {exec} from '../exec.js'
3
3
 
4
4
  export const test = memoizeBy(async (pkg, ctx = pkg.ctx) => {
@@ -1,29 +1,22 @@
1
1
  import os from 'node:os'
2
2
  import {createRequire} from 'node:module'
3
- import {$, within} from 'zx-extra'
3
+ import {$, within, fs, glob, path} from 'zx-extra'
4
4
  import {queuefy} from 'queuefy'
5
- import {topo, traverseQueue} from './deps.js'
5
+ import {topo, traverseQueue} from './depot/deps.js'
6
6
  import {createReport, log} from './log.js'
7
- import {exec} from './exec.js'
8
-
9
- import {contextify} from './steps/contextify.js'
10
- import {recover} from './steps/teardown.js'
11
- import {fetchTags} from './api/git.js'
12
- import {analyze} from './steps/analyze.js'
13
- import {build} from './steps/build.js'
14
- import {publish} from './steps/publish.js'
15
- import {clean} from './steps/clean.js'
16
- import {test} from './steps/test.js'
17
-
18
- import meta from './publishers/meta.js'
19
- import npm from './publishers/npm.js'
20
- import ghRelease from './publishers/gh-release.js'
21
- import ghPages from './publishers/gh-pages.js'
22
- import changelog from './publishers/changelog.js'
23
- import cmd from './publishers/cmd.js'
24
-
25
- // Publisher registry. Order = publish order; teardown walks it in reverse.
26
- const publishers = [meta, npm, ghRelease, ghPages, changelog, cmd]
7
+ import {exec} from './depot/exec.js'
8
+
9
+ import {contextify} from './depot/steps/contextify.js'
10
+ import {analyze} from './depot/steps/analyze.js'
11
+ import {build} from './depot/steps/build.js'
12
+ import {pack} from './depot/steps/pack.js'
13
+ import {publish} from './depot/steps/publish.js'
14
+ import {clean} from './depot/steps/clean.js'
15
+ import {test} from './depot/steps/test.js'
16
+
17
+ import {deliver, defaultOrder as channels} from './courier/index.js'
18
+
19
+ const PARCELS_DIR = 'parcels'
27
20
 
28
21
  export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within(async () => {
29
22
  $.memo = new Map()
@@ -34,10 +27,22 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
34
27
  return
35
28
  }
36
29
 
30
+ // --deliver [dir]: standalone delivery from pre-packed tars.
31
+ if (flags.deliver) {
32
+ const dir = typeof flags.deliver === 'string' ? flags.deliver : PARCELS_DIR
33
+ const tars = await glob(path.join(dir, 'parcel.*.tar'))
34
+ if (!tars.length) throw new Error(`no tars found in ${dir}`)
35
+ const _env = {...process.env, ...env}
36
+ log.secret(_env.GH_TOKEN, _env.GITHUB_TOKEN, _env.NPM_TOKEN)
37
+ log.info(`deliver: ${tars.length} tar(s) from ${dir}`)
38
+ const delivered = await deliver(tars, _env)
39
+ log.info(`deliver: done, ${delivered} delivered`)
40
+ return
41
+ }
42
+
37
43
  const ctx = await createContext({flags, env, cwd})
38
44
  const {report, packages, queue, prev} = ctx
39
45
 
40
- // Per-package scope: $.scope, packages[name] lookup, contextify on first touch.
41
46
  const forEachPkg = (cb) => traverseQueue({queue, prev, cb: (name) => within(async () => {
42
47
  $.scope = name
43
48
  const pkg = packages[name]
@@ -50,15 +55,6 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
50
55
  .log('queue:', queue)
51
56
  .log('graphs', ctx.graphs)
52
57
 
53
- // --recover: standalone mode — clean orphan tags and exit.
54
- if (flags.recover) {
55
- await fetchTags(cwd)
56
- let recovered = 0
57
- await forEachPkg(async (pkg) => { if (await recover(pkg)) recovered++ })
58
- report.log(`recover: cleaned ${recovered} orphan tag(s)`)
59
- return
60
- }
61
-
62
58
  try {
63
59
  await forEachPkg(async (pkg) => {
64
60
  report.setStatus('analyzing', pkg.name)
@@ -88,10 +84,22 @@ export const run = async ({cwd = process.cwd(), env, flags = {}} = {}) => within
88
84
  report.setStatus('testing', pkg.name)
89
85
  await test(pkg)
90
86
  }
91
- if (!flags.dryRun && flags.publish !== false) {
92
- report.setStatus('publishing', pkg.name)
93
- await publish(pkg)
87
+ if (flags.dryRun || flags.publish === false) {
88
+ report.setStatus('success', pkg.name)
89
+ return
94
90
  }
91
+
92
+ report.setStatus('packing', pkg.name)
93
+ await pack(pkg)
94
+
95
+ // --pack <dir>: pack only, skip delivery.
96
+ if (flags.pack) {
97
+ report.setStatus('packed', pkg.name)
98
+ return
99
+ }
100
+
101
+ report.setStatus('publishing', pkg.name)
102
+ await publish(pkg)
95
103
  report.setStatus('success', pkg.name)
96
104
  })
97
105
  } catch (e) {
@@ -108,7 +116,6 @@ export const createContext = async ({flags, env: _env, cwd}) => {
108
116
  const report = createReport({packages, queue, flags})
109
117
  const env = {...process.env, ..._env}
110
118
 
111
- // Register known secrets so the logger redacts them from all output.
112
119
  log.secret(env.GH_TOKEN, env.GITHUB_TOKEN, env.NPM_TOKEN)
113
120
 
114
121
  $.report = report
@@ -118,7 +125,7 @@ export const createContext = async ({flags, env: _env, cwd}) => {
118
125
 
119
126
  return {
120
127
  cwd, env, flags, root, packages, queue, prev, graphs, report,
121
- publishers,
128
+ channels,
122
129
  run: queuefy(exec, flags.concurrency || os.cpus().length),
123
130
  }
124
131
  }
@@ -0,0 +1,73 @@
1
+ import tar from 'tar-stream'
2
+ import crypto from 'node:crypto'
3
+ import {fs, path} from 'zx-extra'
4
+ import {pipeline} from 'node:stream/promises'
5
+ import {createWriteStream, createReadStream} from 'node:fs'
6
+
7
+ export const packTar = async (tarPath, manifest, files = []) => {
8
+ const pack = tar.pack()
9
+ const json = JSON.stringify(manifest, null, 2)
10
+ pack.entry({name: 'manifest.json', size: Buffer.byteLength(json)}, json)
11
+
12
+ for (const {name, source} of files) {
13
+ const stat = await fs.stat(source)
14
+ if (stat.isDirectory()) await addDir(pack, name, source)
15
+ else {
16
+ const buf = await fs.readFile(source)
17
+ pack.entry({name, size: buf.length}, buf)
18
+ }
19
+ }
20
+
21
+ pack.finalize()
22
+ await pipeline(pack, createWriteStream(tarPath))
23
+ return tarPath
24
+ }
25
+
26
+ export const hashFile = async (filePath) => {
27
+ const hash = crypto.createHash('sha1')
28
+ await pipeline(createReadStream(filePath), hash)
29
+ return hash.digest('hex').slice(0, 8)
30
+ }
31
+
32
+ const addDir = async (pack, prefix, dirPath) => {
33
+ for (const e of await fs.readdir(dirPath, {withFileTypes: true})) {
34
+ const full = path.join(dirPath, e.name)
35
+ const rel = path.join(prefix, e.name)
36
+ if (e.isDirectory()) await addDir(pack, rel, full)
37
+ else {
38
+ const buf = await fs.readFile(full)
39
+ pack.entry({name: rel, size: buf.length}, buf)
40
+ }
41
+ }
42
+ }
43
+
44
+ export const unpackTar = async (tarPath, destDir) => {
45
+ await fs.ensureDir(destDir)
46
+ const extract = tar.extract()
47
+ let manifest = null
48
+
49
+ const done = new Promise((resolve, reject) => {
50
+ extract.on('entry', (header, stream, cb) => {
51
+ const chunks = []
52
+ stream.on('data', d => chunks.push(d))
53
+ stream.on('end', async () => {
54
+ const buf = Buffer.concat(chunks)
55
+ if (header.name === 'manifest.json') {
56
+ manifest = JSON.parse(buf.toString('utf8'))
57
+ } else {
58
+ const dest = path.join(destDir, header.name)
59
+ await fs.ensureDir(path.dirname(dest))
60
+ await fs.writeFile(dest, buf)
61
+ }
62
+ cb()
63
+ })
64
+ stream.resume()
65
+ })
66
+ extract.on('finish', resolve)
67
+ extract.on('error', reject)
68
+ })
69
+
70
+ await pipeline(createReadStream(tarPath), extract)
71
+ await done
72
+ return {manifest, dir: destDir}
73
+ }
@@ -109,7 +109,7 @@ export const makeCtx = (overrides = {}) => ({
109
109
  prev: overrides.prev ?? {},
110
110
  graphs: overrides.graphs ?? {},
111
111
  report: overrides.report ?? makeReport(),
112
- publishers: overrides.publishers ?? [],
112
+ channels: overrides.channels ?? [],
113
113
  run: overrides.run ?? (async () => {}),
114
114
  git: {sha: 'abc1234567890', root: tmpDir, ...overrides.git},
115
115
  })
@@ -1,26 +0,0 @@
1
- import {$} from 'zx-extra'
2
- import {queuefy} from 'queuefy'
3
- import {fetchRepo, pushCommit} from '../api/git.js'
4
- import {formatReleaseNotes} from '../generators/notes.js'
5
- import {log} from '../log.js'
6
- import {asTuple, msgJoin} from '../../util.js'
7
-
8
- const run = queuefy(async (pkg) => {
9
- const {absPath: cwd, config: {changelog: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
10
- if (!opts) return
11
-
12
- log.info('push changelog')
13
- const [branch = 'changelog', file = `${pkg.name.replace(/[^a-z0-9-]/ig, '')}-changelog.md`, ..._msg] = asTuple(opts, ['branch', 'file', 'msg'])
14
- const _cwd = await fetchRepo({cwd, branch, basicAuth})
15
- const msg = msgJoin(_msg, pkg, 'chore: update changelog ${{name}}')
16
- const releaseNotes = await formatReleaseNotes(pkg)
17
-
18
- await $({cwd: _cwd})`echo ${releaseNotes}"\n$(cat ./${file})" > ./${file}`
19
- await pushCommit({cwd, branch, msg, gitCommitterEmail, gitCommitterName, basicAuth})
20
- })
21
-
22
- export default {
23
- name: 'changelog',
24
- when: (pkg) => !!pkg.config.changelog,
25
- run,
26
- }
@@ -1,32 +0,0 @@
1
- import {queuefy} from 'queuefy'
2
- import {path} from 'zx-extra'
3
- import {log} from '../log.js'
4
- import {pushCommit} from '../api/git.js'
5
- import {asTuple, msgJoin} from '../../util.js'
6
-
7
- const run = queuefy(async (pkg) => {
8
- const {config: {ghPages: opts, gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
9
- if (!opts) return
10
-
11
- const [branch = 'gh-pages', from = 'docs', to = '.', ..._msg] = asTuple(opts, ['branch', 'from', 'to', 'msg'])
12
- const msg = msgJoin(_msg, pkg, 'docs: update docs ${{name}} ${{version}}')
13
-
14
- log.info(`publish docs to ${branch}`)
15
-
16
- await pushCommit({
17
- cwd: path.join(pkg.absPath, from),
18
- from: '.',
19
- to,
20
- branch,
21
- msg,
22
- gitCommitterEmail,
23
- gitCommitterName,
24
- basicAuth,
25
- })
26
- })
27
-
28
- export default {
29
- name: 'gh-pages',
30
- when: (pkg) => !!pkg.config.ghPages,
31
- run,
32
- }
@@ -1,41 +0,0 @@
1
- import {log} from '../log.js'
2
- import {getRepo, getRoot} from '../api/git.js'
3
- import {ghCreateRelease, ghDeleteReleaseByTag, ghUploadAssets} from '../api/gh.js'
4
- import {formatTag} from '../generators/tag.js'
5
- import {formatReleaseNotes} from '../generators/notes.js'
6
-
7
- const run = async (pkg) => {
8
- const {ghBasicAuth: basicAuth, ghToken, ghAssets, ghApiUrl} = pkg.config
9
- if (!ghToken) return null
10
-
11
- log.info('create gh release')
12
-
13
- const now = Date.now()
14
- const {name, version, absPath: cwd, tag = formatTag({name, version})} = pkg
15
- const {repoName} = await getRepo(cwd, {basicAuth})
16
- const body = await formatReleaseNotes(pkg)
17
- const res = await ghCreateRelease({ghApiUrl, ghToken, repoName, tag, body})
18
-
19
- if (ghAssets?.length) {
20
- // GH API returns a pseudo-url `...releases/110103594/assets{?name,label}` — strip the template.
21
- const uploadUrl = res.upload_url.slice(0, res.upload_url.indexOf('{'))
22
- await ghUploadAssets({ghToken, ghAssets, uploadUrl, cwd})
23
- }
24
-
25
- log.info(`duration gh release: ${Date.now() - now}`)
26
- }
27
-
28
- const undo = async (pkg, {tag}) => {
29
- const {ghBasicAuth: basicAuth, ghToken, ghApiUrl} = pkg.config
30
- if (!ghToken) return
31
- const cwd = await getRoot(pkg.absPath)
32
- const {repoName} = await getRepo(cwd, {basicAuth})
33
- await ghDeleteReleaseByTag({ghApiUrl, ghToken, repoName, tag})
34
- }
35
-
36
- export default {
37
- name: 'gh-release',
38
- when: (pkg) => !!pkg.config.ghToken,
39
- run,
40
- undo,
41
- }
@@ -1,58 +0,0 @@
1
- // Meta publisher: pushes meta artifacts to the `meta` branch and undoes them on rollback.
2
- // The meta payload itself is built by the generator (../generators/meta.js).
3
-
4
- import {queuefy} from 'queuefy'
5
- import {fs, path} from 'zx-extra'
6
- import {log} from '../log.js'
7
- import {fetchRepo, pushCommit} from '../api/git.js'
8
- import {formatTag} from '../generators/tag.js'
9
- import {prepareMeta, getArtifactPath, isAssetMode} from '../generators/meta.js'
10
-
11
- // Push the meta artifact to the `meta` branch. No-op in asset mode (handled by gh-release).
12
- const pushMetaBranch = queuefy(async (pkg) => {
13
- const {type} = pkg.config.meta
14
- if (type === null || isAssetMode(type)) return
15
-
16
- log.info('push artifact to branch \'meta\'')
17
-
18
- const {name, version, meta, tag = formatTag({name, version}), absPath: cwd, config: {gitCommitterEmail, gitCommitterName, ghBasicAuth: basicAuth}} = pkg
19
- const msg = `chore: release meta ${name} ${version}`
20
- const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
21
-
22
- await pushCommit({cwd, to: '.', branch: 'meta', msg, files, gitCommitterEmail, gitCommitterName, basicAuth})
23
- })
24
-
25
- // Remove a meta artifact for a given tag from the meta branch.
26
- // Returns true if something was removed and pushed.
27
- const removeMetaArtifact = async (pkg, {tag, version, reason}) => {
28
- const {config: {meta: {type}, ghBasicAuth: basicAuth, gitCommitterName, gitCommitterEmail}} = pkg
29
- // Asset-mode meta lives inside the GH release; deleting the release already removes it.
30
- if (type === null || isAssetMode(type)) return false
31
-
32
- const cwd = pkg.ctx?.git?.root || pkg.absPath
33
- const metaCwd = await fetchRepo({cwd, branch: 'meta', basicAuth})
34
- const artifactPath = getArtifactPath(tag)
35
- const candidates = [
36
- path.resolve(metaCwd, `${artifactPath}.json`),
37
- path.resolve(metaCwd, artifactPath),
38
- ]
39
- let removed = false
40
- for (const p of candidates) {
41
- if (fs.existsSync(p)) {
42
- await fs.remove(p)
43
- removed = true
44
- }
45
- }
46
- if (removed) {
47
- await pushCommit({cwd, branch: 'meta', msg: `chore: ${reason} ${pkg.name} ${version}`, gitCommitterName, gitCommitterEmail, basicAuth})
48
- }
49
- return removed
50
- }
51
-
52
- export default {
53
- name: 'meta',
54
- when: (pkg) => pkg.config.meta.type !== null,
55
- prepare: prepareMeta,
56
- run: pushMetaBranch,
57
- undo: removeMetaArtifact,
58
- }
@@ -1,15 +0,0 @@
1
- import {npmPublish} from '../api/npm.js'
2
-
3
- // Domain predicate: does this package get published to npm?
4
- // Private packages and those with `npmPublish: false` are git-tag-only —
5
- // the tag itself IS the release.
6
- export const isNpmPublished = (pkg) =>
7
- !pkg.manifest.private && pkg.config.npmPublish !== false
8
-
9
- export default {
10
- name: 'npm',
11
- when: isNpmPublished,
12
- run: (pkg) => npmPublish(pkg),
13
- snapshot: true,
14
- // No undo: npm unpublish is unreliable (time-limited, cached by mirrors).
15
- }