zx-bulk-release 3.1.3 → 3.1.4
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 +6 -0
- package/package.json +1 -1
- package/src/main/js/post/api/index.js +8 -0
- package/src/main/js/post/courier/channels/changelog.js +3 -3
- package/src/main/js/post/courier/channels/gh-pages.js +2 -2
- package/src/main/js/post/courier/channels/gh-release.js +3 -3
- package/src/main/js/post/courier/channels/git-tag.js +4 -3
- package/src/main/js/post/courier/channels/meta.js +2 -2
- package/src/main/js/post/courier/channels/npm.js +2 -2
- package/src/main/js/post/courier/semaphore.js +5 -5
- package/src/main/js/post/depot/generators/meta.js +7 -9
- package/src/main/js/post/depot/generators/notes.js +2 -2
- package/src/main/js/post/depot/reconcile.js +4 -4
- package/src/main/js/post/depot/steps/analyze.js +2 -2
- package/src/main/js/post/depot/steps/build.js +2 -2
- package/src/main/js/post/depot/steps/clean.js +3 -4
- package/src/main/js/post/depot/steps/contextify.js +4 -4
- package/src/main/js/post/depot/steps/pack.js +6 -9
- package/src/main/js/post/modes/pack.js +31 -2
- package/src/main/js/post/modes/receive.js +33 -8
- package/src/main/js/post/release.js +4 -0
- package/src/test/js/utils/mock.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [3.1.4](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.3...v3.1.4) (2026-04-13)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* fix: sync receive/pack via context-driven pack filter ([5b539ff](https://github.com/semrel-extra/zx-bulk-release/commit/5b539ff327b07c171c523b36f28cc2e05c4346d6))
|
|
5
|
+
* refactor: introduce internal di layer ([a32773d](https://github.com/semrel-extra/zx-bulk-release/commit/a32773d37e9454953029f18e0af7de33486c14e3))
|
|
6
|
+
|
|
1
7
|
## [3.1.3](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.2...v3.1.3) (2026-04-13)
|
|
2
8
|
|
|
3
9
|
### Fixes & improvements
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {fs, path} from 'zx-extra'
|
|
2
2
|
import {queuefy} from 'queuefy'
|
|
3
|
-
import {
|
|
3
|
+
import {api} from '../../api/index.js'
|
|
4
4
|
import {log} from '../../log.js'
|
|
5
5
|
import {hasHigherVersion} from '../seniority.js'
|
|
6
6
|
|
|
@@ -14,12 +14,12 @@ const run = queuefy(async (manifest, dir) => {
|
|
|
14
14
|
|
|
15
15
|
log.info('push changelog')
|
|
16
16
|
|
|
17
|
-
const _cwd = await fetchRepo({branch, origin: repoAuthedUrl, basicAuth: ghBasicAuth})
|
|
17
|
+
const _cwd = await api.git.fetchRepo({branch, origin: repoAuthedUrl, basicAuth: ghBasicAuth})
|
|
18
18
|
const filePath = path.resolve(_cwd, file)
|
|
19
19
|
const prev = await fs.readFile(filePath, 'utf8').catch(() => '')
|
|
20
20
|
await fs.outputFile(filePath, releaseNotes + '\n' + prev)
|
|
21
21
|
|
|
22
|
-
await pushCommit({
|
|
22
|
+
await api.git.pushCommit({
|
|
23
23
|
branch,
|
|
24
24
|
msg,
|
|
25
25
|
origin: repoAuthedUrl,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {path} from 'zx-extra'
|
|
2
2
|
import {queuefy} from 'queuefy'
|
|
3
3
|
import {log} from '../../log.js'
|
|
4
|
-
import {
|
|
4
|
+
import {api} from '../../api/index.js'
|
|
5
5
|
import {hasHigherVersion} from '../seniority.js'
|
|
6
6
|
|
|
7
7
|
const run = queuefy(async (manifest, dir) => {
|
|
@@ -15,7 +15,7 @@ const run = queuefy(async (manifest, dir) => {
|
|
|
15
15
|
|
|
16
16
|
log.info(`publish docs to ${branch}`)
|
|
17
17
|
|
|
18
|
-
await pushCommit({
|
|
18
|
+
await api.git.pushCommit({
|
|
19
19
|
cwd: docsDir,
|
|
20
20
|
from: '.',
|
|
21
21
|
to,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {fs, path} from 'zx-extra'
|
|
2
2
|
import {log} from '../../log.js'
|
|
3
|
-
import {
|
|
3
|
+
import {api} from '../../api/index.js'
|
|
4
4
|
|
|
5
5
|
const isDuplicate = (e) =>
|
|
6
6
|
/already_exists|Validation Failed|422/i.test(e?.message || e?.stderr || '')
|
|
@@ -14,7 +14,7 @@ const run = async (manifest, dir) => {
|
|
|
14
14
|
|
|
15
15
|
let res
|
|
16
16
|
try {
|
|
17
|
-
res = await ghCreateRelease({ghApiUrl: apiUrl, ghToken: token, repoName, tag, body: releaseNotes})
|
|
17
|
+
res = await api.gh.ghCreateRelease({ghApiUrl: apiUrl, ghToken: token, repoName, tag, body: releaseNotes})
|
|
18
18
|
} catch (e) {
|
|
19
19
|
if (isDuplicate(e)) return 'duplicate'
|
|
20
20
|
throw e
|
|
@@ -27,7 +27,7 @@ const run = async (manifest, dir) => {
|
|
|
27
27
|
await Promise.all(assets.map(async ({name}) => {
|
|
28
28
|
const url = `${uploadUrl}?name=${name}`
|
|
29
29
|
const body = await fs.readFile(path.join(assetsDir, name))
|
|
30
|
-
const r = await ghFetch(url, {ghToken: token, method: 'POST', headers: {'Content-Type': 'application/octet-stream'}, body})
|
|
30
|
+
const r = await api.gh.ghFetch(url, {ghToken: token, method: 'POST', headers: {'Content-Type': 'application/octet-stream'}, body})
|
|
31
31
|
if (!r.ok) throw new Error(`gh asset upload failed for '${name}': ${r.status}`)
|
|
32
32
|
}))
|
|
33
33
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {api} from '../../api/index.js'
|
|
2
|
+
import {DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL} from '../../api/git.js'
|
|
2
3
|
|
|
3
4
|
export const isTagConflict = (e) =>
|
|
4
5
|
/already exists|updates were rejected|failed to push/i.test(e?.message || e?.stderr || '')
|
|
5
6
|
|
|
6
|
-
const run = async (manifest) => {
|
|
7
|
+
const run = async (manifest, dir) => {
|
|
7
8
|
const {tag, cwd} = manifest
|
|
8
9
|
const gitCommitterName = manifest.gitCommitterName || DEFAULT_GIT_COMMITTER_NAME
|
|
9
10
|
const gitCommitterEmail = manifest.gitCommitterEmail || DEFAULT_GIT_COMMITTER_EMAIL
|
|
10
11
|
try {
|
|
11
|
-
await pushTag({cwd, tag, gitCommitterName, gitCommitterEmail})
|
|
12
|
+
await api.git.pushTag({cwd, tag, gitCommitterName, gitCommitterEmail})
|
|
12
13
|
return 'ok'
|
|
13
14
|
} catch (e) {
|
|
14
15
|
if (isTagConflict(e)) return 'conflict'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {queuefy} from 'queuefy'
|
|
2
2
|
import {log} from '../../log.js'
|
|
3
|
-
import {
|
|
3
|
+
import {api} from '../../api/index.js'
|
|
4
4
|
import {getArtifactPath, isAssetMode, prepareMeta} from '../../depot/generators/meta.js'
|
|
5
5
|
import {hasHigherVersion} from '../seniority.js'
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ const pushMetaBranch = queuefy(async (manifest, dir) => {
|
|
|
18
18
|
const msg = `chore: release meta ${name} ${version}`
|
|
19
19
|
const files = [{relpath: `${getArtifactPath(tag)}.json`, contents: meta}]
|
|
20
20
|
|
|
21
|
-
await pushCommit({
|
|
21
|
+
await api.git.pushCommit({
|
|
22
22
|
to: '.',
|
|
23
23
|
branch: 'meta',
|
|
24
24
|
msg,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {path} from 'zx-extra'
|
|
2
|
-
import {
|
|
2
|
+
import {api} from '../../api/index.js'
|
|
3
3
|
|
|
4
4
|
export const isNpmPublished = (pkg) =>
|
|
5
5
|
!pkg.manifest.private && pkg.config.npmPublish !== false
|
|
@@ -9,7 +9,7 @@ const isDuplicate = (e) =>
|
|
|
9
9
|
|
|
10
10
|
const run = async (manifest, dir) => {
|
|
11
11
|
try {
|
|
12
|
-
await npmPublish({
|
|
12
|
+
await api.npm.npmPublish({
|
|
13
13
|
name: manifest.name,
|
|
14
14
|
version: manifest.version,
|
|
15
15
|
npmTarball: path.join(dir, 'package.tgz'),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {api} from '../api/index.js'
|
|
2
2
|
|
|
3
3
|
const tagName = ({sha}) =>
|
|
4
4
|
`zbr-deliver.${sha.slice(0, 7)}`
|
|
@@ -11,7 +11,7 @@ export const tryLock = async (cwd, directive) => {
|
|
|
11
11
|
packages: directive.queue,
|
|
12
12
|
})
|
|
13
13
|
try {
|
|
14
|
-
await pushAnnotatedTag(cwd, tag, body)
|
|
14
|
+
await api.git.pushAnnotatedTag(cwd, tag, body)
|
|
15
15
|
return true
|
|
16
16
|
} catch {
|
|
17
17
|
return false
|
|
@@ -19,13 +19,13 @@ export const tryLock = async (cwd, directive) => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export const unlock = async (cwd, directive) => {
|
|
22
|
-
await deleteRemoteTag(cwd, tagName(directive))
|
|
22
|
+
await api.git.deleteRemoteTag(cwd, tagName(directive))
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export const signalRebuild = async (cwd, sha) => {
|
|
26
26
|
const tag = `zbr-rebuild.${sha.slice(0, 7)}`
|
|
27
|
-
try { await pushAnnotatedTag(cwd, tag, 'rebuild') } catch { /* already signaled */ }
|
|
27
|
+
try { await api.git.pushAnnotatedTag(cwd, tag, 'rebuild') } catch { /* already signaled */ }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export const consumeRebuildSignal = async (cwd, sha) =>
|
|
31
|
-
deleteRemoteTag(cwd, `zbr-rebuild.${sha.slice(0, 7)}`)
|
|
31
|
+
api.git.deleteRemoteTag(cwd, `zbr-rebuild.${sha.slice(0, 7)}`)
|
|
@@ -1,9 +1,7 @@
|
|
|
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 {
|
|
5
|
-
import {fetchManifest} from '../../api/npm.js'
|
|
6
|
-
import {ghGetAsset} from '../../api/gh.js'
|
|
4
|
+
import {api} from '../../api/index.js'
|
|
7
5
|
import {parseTag} from './tag.js'
|
|
8
6
|
|
|
9
7
|
export const isAssetMode = (type) => type === 'asset' || type === 'assets'
|
|
@@ -16,7 +14,7 @@ export const prepareMeta = async (pkg) => {
|
|
|
16
14
|
if (type === null) return
|
|
17
15
|
|
|
18
16
|
const {absPath: cwd} = pkg
|
|
19
|
-
const hash = await getSha(cwd)
|
|
17
|
+
const hash = await api.git.getSha(cwd)
|
|
20
18
|
pkg.meta = {
|
|
21
19
|
META_VERSION: '1',
|
|
22
20
|
hash,
|
|
@@ -45,7 +43,7 @@ export const getLatest = async (pkg) => {
|
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
export const getTags = async (cwd, ref) =>
|
|
48
|
-
(await
|
|
46
|
+
(await api.git.getTags(cwd, ref))
|
|
49
47
|
.map(tag => parseTag(tag.trim()))
|
|
50
48
|
.filter(Boolean)
|
|
51
49
|
.sort((a, b) => semver.rcompare(a.version, b.version))
|
|
@@ -61,14 +59,14 @@ export const getArtifactPath = (tag) => tag.toLowerCase().replace(/[^a-z0-9-]/g,
|
|
|
61
59
|
export const getLatestMeta = async (pkg, tag) => {
|
|
62
60
|
if (tag) {
|
|
63
61
|
const {absPath: cwd, config: {ghBasicAuth: basicAuth, ghUrl}} = pkg
|
|
64
|
-
const {repoName} = await getRepo(cwd, {basicAuth})
|
|
62
|
+
const {repoName} = await api.git.getRepo(cwd, {basicAuth})
|
|
65
63
|
|
|
66
64
|
try {
|
|
67
|
-
return JSON.parse(await ghGetAsset({repoName, tag, name: 'meta.json', ghUrl}))
|
|
65
|
+
return JSON.parse(await api.gh.ghGetAsset({repoName, tag, name: 'meta.json', ghUrl}))
|
|
68
66
|
} catch {}
|
|
69
67
|
|
|
70
68
|
try {
|
|
71
|
-
const _cwd = await fetchRepo({cwd, branch: 'meta', basicAuth})
|
|
69
|
+
const _cwd = await api.git.fetchRepo({cwd, branch: 'meta', basicAuth})
|
|
72
70
|
return await Promise.any([
|
|
73
71
|
fs.readJson(path.resolve(_cwd, `${getArtifactPath(tag)}.json`)),
|
|
74
72
|
fs.readJson(path.resolve(_cwd, getArtifactPath(tag), 'meta.json'))
|
|
@@ -76,5 +74,5 @@ export const getLatestMeta = async (pkg, tag) => {
|
|
|
76
74
|
} catch {}
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
return fetchManifest(pkg, {nothrow: true})
|
|
77
|
+
return api.npm.fetchManifest(pkg, {nothrow: true})
|
|
80
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Release notes formatting. Pure except for a single getRepo() call to resolve repoPublicUrl.
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {api} from '../../api/index.js'
|
|
4
4
|
import {formatTag} from './tag.js'
|
|
5
5
|
|
|
6
6
|
export const DIFF_TAG_URL = '${repoPublicUrl}/compare/${prevTag}...${newTag}'
|
|
@@ -14,7 +14,7 @@ export const interpolate = (template, vars) => {
|
|
|
14
14
|
|
|
15
15
|
export const formatReleaseNotes = async (pkg) => {
|
|
16
16
|
const {name, version, tag = formatTag({name, version}), absPath: cwd, config: {ghBasicAuth: basicAuth, diffTagUrl = DIFF_TAG_URL, diffCommitUrl = DIFF_COMMIT_URL}} = pkg
|
|
17
|
-
const {repoPublicUrl, repoName} = await getRepo(cwd, {basicAuth})
|
|
17
|
+
const {repoPublicUrl, repoName} = await api.git.getRepo(cwd, {basicAuth})
|
|
18
18
|
const prevTag = pkg.latest.tag?.ref
|
|
19
19
|
const vars = {repoName, repoPublicUrl, prevTag, newTag: tag, name, version}
|
|
20
20
|
const diffUrl = interpolate(diffTagUrl, vars)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {$} from 'zx-extra'
|
|
2
|
+
import {api} from '../api/index.js'
|
|
2
3
|
import {log} from '../log.js'
|
|
3
|
-
import {getRemoteTagSha, clearTagsCache} from '../api/git.js'
|
|
4
4
|
import {formatTag} from './generators/tag.js'
|
|
5
5
|
import {resolvePkgVersion} from './steps/analyze.js'
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ export const preflight = async (pkg, ctx) => {
|
|
|
8
8
|
if (!pkg.tag) return 'ok'
|
|
9
9
|
|
|
10
10
|
const cwd = ctx.git.root
|
|
11
|
-
const remoteSha = await getRemoteTagSha(cwd, pkg.tag)
|
|
11
|
+
const remoteSha = await api.git.getRemoteTagSha(cwd, pkg.tag)
|
|
12
12
|
if (!remoteSha) return 'ok'
|
|
13
13
|
|
|
14
14
|
// tag exists on remote
|
|
@@ -30,7 +30,7 @@ export const preflight = async (pkg, ctx) => {
|
|
|
30
30
|
if (isRemoteOlder.exitCode === 0) {
|
|
31
31
|
log.info(`preflight: ${pkg.tag} — we are newer, re-resolving version`)
|
|
32
32
|
await $({cwd})`git fetch origin --tags --force`
|
|
33
|
-
clearTagsCache()
|
|
33
|
+
api.git.clearTagsCache()
|
|
34
34
|
|
|
35
35
|
const pre = ctx.flags.snapshot ? `-snap.${ctx.git.sha.slice(0, 7)}` : undefined
|
|
36
36
|
// re-resolve: the fetched tags will give us a new latest version
|
|
@@ -38,7 +38,7 @@ export const preflight = async (pkg, ctx) => {
|
|
|
38
38
|
pkg.version = resolvePkgVersion(pkg.releaseType, latestVersion, pkg.manifest.version, pre)
|
|
39
39
|
pkg.manifest.version = pkg.version
|
|
40
40
|
pkg.tag = formatTag({name: pkg.name, version: pkg.version, format: pkg.config.tagFormat})
|
|
41
|
-
return '
|
|
41
|
+
return 'refetch'
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// diverged — anomaly
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {semver} from 'zx-extra'
|
|
2
|
+
import {api} from '../../api/index.js'
|
|
2
3
|
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
|
|
|
@@ -41,7 +41,7 @@ export const semanticRules = [
|
|
|
41
41
|
]
|
|
42
42
|
|
|
43
43
|
export const getSemanticChanges = async (cwd, from, to, rules = semanticRules) => {
|
|
44
|
-
const commits = await getCommits(cwd, from, to)
|
|
44
|
+
const commits = await api.git.getCommits(cwd, from, to)
|
|
45
45
|
|
|
46
46
|
return analyzeCommits(commits, rules)
|
|
47
47
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {memoizeBy} from '../../../util.js'
|
|
2
|
-
import {
|
|
2
|
+
import {api} from '../../api/index.js'
|
|
3
3
|
import {traverseDeps} from '../deps.js'
|
|
4
4
|
import {exec} from '../exec.js'
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ export const build = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
|
8
8
|
await Promise.all([
|
|
9
9
|
traverseDeps({pkg, packages, cb: ({pkg}) => build(pkg, ctx)}),
|
|
10
10
|
pkg.manifest.private !== true && pkg.changes.length === 0 && pkg.config.npmFetch && flags.npmFetch !== false
|
|
11
|
-
? fetchPkg(pkg)
|
|
11
|
+
? api.npm.fetchPkg(pkg)
|
|
12
12
|
: Promise.resolve()
|
|
13
13
|
])
|
|
14
14
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {npmRestore} from '../../api/npm.js'
|
|
1
|
+
import {api} from '../../api/index.js'
|
|
3
2
|
|
|
4
3
|
export const clean = async ({cwd, packages}) => {
|
|
5
|
-
await unsetUserConfig(cwd)
|
|
6
|
-
await Promise.all(Object.values(packages).filter(p => !p.skipped).map(npmRestore))
|
|
4
|
+
await api.git.unsetUserConfig(cwd)
|
|
5
|
+
await Promise.all(Object.values(packages).filter(p => !p.skipped).map(api.npm.npmRestore))
|
|
7
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {getPkgConfig} from '../../../config.js'
|
|
2
|
+
import {api} from '../../api/index.js'
|
|
2
3
|
import {getLatest} from '../generators/meta.js'
|
|
3
|
-
import {getRoot, getSha, getCommitTimestamp} from '../../api/git.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Global release context — one per `run()` invocation.
|
|
@@ -42,9 +42,9 @@ export const contextify = async (pkg, ctx) => {
|
|
|
42
42
|
pkg.ctx = {
|
|
43
43
|
...ctx,
|
|
44
44
|
git: {
|
|
45
|
-
sha: await getSha(ctx.cwd),
|
|
46
|
-
root: await getRoot(ctx.cwd),
|
|
47
|
-
timestamp: await getCommitTimestamp(ctx.cwd),
|
|
45
|
+
sha: await api.git.getSha(ctx.cwd),
|
|
46
|
+
root: await api.git.getRoot(ctx.cwd),
|
|
47
|
+
timestamp: await api.git.getCommitTimestamp(ctx.cwd),
|
|
48
48
|
},
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import crypto from 'node:crypto'
|
|
2
1
|
import {$, tempy, fs, path} from 'zx-extra'
|
|
3
2
|
import {memoizeBy, asTuple} from '../../../util.js'
|
|
3
|
+
import {api} from '../../api/index.js'
|
|
4
4
|
import {prepare, getActiveChannels} from '../../courier/index.js'
|
|
5
5
|
import {buildParcels, PARCELS_DIR} from '../../parcel/index.js'
|
|
6
|
-
import {npmPersist} from '../../api/npm.js'
|
|
7
|
-
import {getRepo} from '../../api/git.js'
|
|
8
6
|
import {formatReleaseNotes} from '../generators/notes.js'
|
|
9
|
-
import {ghPrepareAssets} from '../../api/gh.js'
|
|
10
7
|
import {packTar, hashFile} from '../../tar.js'
|
|
11
8
|
|
|
12
9
|
export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
@@ -15,12 +12,12 @@ export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
|
15
12
|
const active = getActiveChannels(pkg, channelNames, snapshot)
|
|
16
13
|
|
|
17
14
|
await prepare(active, pkg)
|
|
18
|
-
await npmPersist(pkg)
|
|
15
|
+
await api.npm.npmPersist(pkg)
|
|
19
16
|
|
|
20
17
|
const outputDir = flags.pack ? path.resolve(ctx.git.root, typeof flags.pack === 'string' ? flags.pack : PARCELS_DIR) : null
|
|
21
18
|
const stageDir = outputDir || tempy.temporaryDirectory()
|
|
22
19
|
if (outputDir) await fs.ensureDir(outputDir)
|
|
23
|
-
const {repoName, repoHost, originUrl} = await getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
|
|
20
|
+
const {repoName, repoHost, originUrl} = await api.git.getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
|
|
24
21
|
const artifacts = {repoName, repoHost, originUrl}
|
|
25
22
|
|
|
26
23
|
if (active.includes('npm')) {
|
|
@@ -35,19 +32,19 @@ export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
|
35
32
|
await fs.copy(path.join(pkg.absPath, from), artifacts.docsDir)
|
|
36
33
|
}
|
|
37
34
|
if (active.includes('gh-release') && pkg.config.ghAssets?.length)
|
|
38
|
-
artifacts.assetsDir = await ghPrepareAssets(pkg.config.ghAssets, pkg.absPath)
|
|
35
|
+
artifacts.assetsDir = await api.gh.ghPrepareAssets(pkg.config.ghAssets, pkg.absPath)
|
|
39
36
|
|
|
40
37
|
const parcels = buildParcels(pkg, ctx, {channels: active, ...artifacts})
|
|
41
38
|
|
|
42
39
|
const tars = []
|
|
43
40
|
for (const {channel, manifest, files} of parcels) {
|
|
44
41
|
// Two-pass: pack to temp, hash, rename to final name.
|
|
45
|
-
const tmpPath =
|
|
42
|
+
const tmpPath = tempy.temporaryFile({extension: 'tar'})
|
|
46
43
|
await packTar(tmpPath, manifest, files)
|
|
47
44
|
const hash = await hashFile(tmpPath)
|
|
48
45
|
const sha7 = ctx.git.sha.slice(0, 7)
|
|
49
46
|
const finalPath = path.join(stageDir, `parcel.${sha7}.${channel}.${pkg.tag}.${hash}.tar`)
|
|
50
|
-
await fs.
|
|
47
|
+
await fs.move(tmpPath, finalPath)
|
|
51
48
|
tars.push(finalPath)
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -11,10 +11,15 @@ import {clean} from '../depot/steps/clean.js'
|
|
|
11
11
|
import {test} from '../depot/steps/test.js'
|
|
12
12
|
import {preflight} from '../depot/reconcile.js'
|
|
13
13
|
import {buildDirective, PARCELS_DIR} from '../parcel/index.js'
|
|
14
|
+
import {CONTEXT_FILE, readContext} from '../depot/context.js'
|
|
14
15
|
|
|
15
16
|
export const runPack = async ({cwd, env, flags}, ctx) => {
|
|
16
17
|
const {report, packages, queue, prev} = ctx
|
|
17
18
|
|
|
19
|
+
// When running in split pipeline (receive → pack → deliver),
|
|
20
|
+
// the context file is the source of truth for what to release.
|
|
21
|
+
const contextFilter = await loadContextFilter(cwd, flags)
|
|
22
|
+
|
|
18
23
|
const forEachPkg = (cb) => traverseQueue({queue, prev, cb: (name) => within(async () => {
|
|
19
24
|
$.scope = name
|
|
20
25
|
await contextify(packages[name], ctx)
|
|
@@ -42,8 +47,20 @@ export const runPack = async ({cwd, env, flags}, ctx) => {
|
|
|
42
47
|
|
|
43
48
|
const packed = []
|
|
44
49
|
await forEachPkg(async (pkg) => {
|
|
45
|
-
if (
|
|
46
|
-
|
|
50
|
+
if (contextFilter) {
|
|
51
|
+
// Split pipeline: context is the source of truth
|
|
52
|
+
const ctxPkg = contextFilter[pkg.name]
|
|
53
|
+
if (!ctxPkg) { pkg.skipped = true; return report.setStatus('skipped', pkg.name) }
|
|
54
|
+
pkg.version = ctxPkg.version
|
|
55
|
+
pkg.tag = ctxPkg.tag
|
|
56
|
+
pkg.manifest.version = ctxPkg.version
|
|
57
|
+
if (!pkg.releaseType) pkg.releaseType = 'patch'
|
|
58
|
+
} else {
|
|
59
|
+
// Standalone: own analysis decides
|
|
60
|
+
if (!pkg.releaseType) { pkg.skipped = true; return report.setStatus('skipped', pkg.name) }
|
|
61
|
+
if (await preflight(pkg, pkg.ctx) === 'skip') { pkg.skipped = true; return report.setStatus('skipped', pkg.name) }
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
if (flags.build !== false) { report.setStatus('building', pkg.name); await build(pkg) }
|
|
48
65
|
if (flags.test !== false) { report.setStatus('testing', pkg.name); await test(pkg) }
|
|
49
66
|
if (flags.dryRun || flags.publish === false) return report.setStatus('success', pkg.name)
|
|
@@ -67,3 +84,15 @@ export const runPack = async ({cwd, env, flags}, ctx) => {
|
|
|
67
84
|
}
|
|
68
85
|
report.setStatus('success').log('Great success!')
|
|
69
86
|
}
|
|
87
|
+
|
|
88
|
+
const loadContextFilter = async (cwd, flags) => {
|
|
89
|
+
if (!flags.pack) return null
|
|
90
|
+
try {
|
|
91
|
+
const context = await readContext(path.resolve(cwd, CONTEXT_FILE))
|
|
92
|
+
if (context.status === 'proceed') {
|
|
93
|
+
log.info(`using context as package filter (${Object.keys(context.packages).length} package(s))`)
|
|
94
|
+
return context.packages
|
|
95
|
+
}
|
|
96
|
+
} catch { /* no context file — standalone mode */ }
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {$, within} from 'zx-extra'
|
|
2
2
|
|
|
3
|
+
import {api} from '../api/index.js'
|
|
3
4
|
import {log} from '../log.js'
|
|
4
5
|
import {traverseQueue} from '../depot/deps.js'
|
|
5
6
|
import {contextify} from '../depot/steps/contextify.js'
|
|
@@ -9,21 +10,20 @@ import {preflight} from '../depot/reconcile.js'
|
|
|
9
10
|
import {consumeRebuildSignal} from '../courier/semaphore.js'
|
|
10
11
|
import {getActiveChannels} from '../courier/index.js'
|
|
11
12
|
import {writeContext, buildContext} from '../depot/context.js'
|
|
12
|
-
import {
|
|
13
|
-
import {setOutput, isRebuildTrigger} from '../api/gh.js'
|
|
13
|
+
import {getLatest} from '../depot/generators/meta.js'
|
|
14
14
|
|
|
15
15
|
export const runReceive = async ({cwd, env, flags}, ctx) => {
|
|
16
16
|
const {report, packages, queue, prev} = ctx
|
|
17
17
|
|
|
18
|
-
const sha = await getSha(cwd)
|
|
18
|
+
const sha = await api.git.getSha(cwd)
|
|
19
19
|
const sha7 = sha.slice(0, 7)
|
|
20
20
|
|
|
21
|
-
if (isRebuildTrigger(env) && !flags.dryRun) {
|
|
21
|
+
if (api.gh.isRebuildTrigger(env) && !flags.dryRun) {
|
|
22
22
|
const result = await consumeRebuildSignal(cwd, sha)
|
|
23
23
|
if (result?.exitCode !== 0 && result?.stderr?.includes('remote ref does not exist')) {
|
|
24
24
|
log.info(`rebuild signal already consumed by another process`)
|
|
25
25
|
await writeContext(cwd, {status: 'skip', reason: 'rebuild claimed by another process'})
|
|
26
|
-
setOutput('status', 'skip')
|
|
26
|
+
api.gh.setOutput('status', 'skip')
|
|
27
27
|
return report.setStatus('success')
|
|
28
28
|
}
|
|
29
29
|
log.info(`consumed rebuild signal for ${sha7}`)
|
|
@@ -36,16 +36,41 @@ export const runReceive = async ({cwd, env, flags}, ctx) => {
|
|
|
36
36
|
})})
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
+
// Phase 1: analyze all packages
|
|
39
40
|
await forEachPkg(async (pkg) => {
|
|
40
41
|
report.setStatus('analyzing', pkg.name)
|
|
41
42
|
await analyze(pkg)
|
|
42
43
|
})
|
|
43
44
|
|
|
45
|
+
// Phase 2: preflight — may fetch remote tags and re-resolve versions
|
|
46
|
+
let tagsFetched = false
|
|
44
47
|
await forEachPkg(async (pkg) => {
|
|
45
48
|
if (!pkg.releaseType) { pkg.skipped = true; return }
|
|
46
|
-
|
|
49
|
+
const result = await preflight(pkg, pkg.ctx)
|
|
50
|
+
if (result === 'skip') { pkg.skipped = true; return }
|
|
51
|
+
if (result === 'refetch') tagsFetched = true
|
|
47
52
|
})
|
|
48
53
|
|
|
54
|
+
// Phase 3: if preflight fetched tags, dependency cascade may have changed —
|
|
55
|
+
// re-analyze packages that had no changes on the first pass.
|
|
56
|
+
if (tagsFetched) {
|
|
57
|
+
log.info('tags fetched during preflight, re-analyzing for dependency cascades')
|
|
58
|
+
for (const name of queue) {
|
|
59
|
+
const pkg = packages[name]
|
|
60
|
+
if (pkg.releaseType) continue
|
|
61
|
+
pkg.latest = await getLatest(pkg)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await forEachPkg(async (pkg) => {
|
|
65
|
+
if (pkg.releaseType) return
|
|
66
|
+
pkg.skipped = false
|
|
67
|
+
report.setStatus('re-analyzing', pkg.name)
|
|
68
|
+
await analyze(pkg)
|
|
69
|
+
if (!pkg.releaseType) { pkg.skipped = true; return }
|
|
70
|
+
if (await preflight(pkg, pkg.ctx) === 'skip') { pkg.skipped = true }
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
const snapshot = !!flags.snapshot
|
|
50
75
|
const context = buildContext(packages, queue, sha, {
|
|
51
76
|
getChannels: (pkg) => getActiveChannels(pkg, ctx.channels, snapshot),
|
|
@@ -56,10 +81,10 @@ export const runReceive = async ({cwd, env, flags}, ctx) => {
|
|
|
56
81
|
if (count === 0) {
|
|
57
82
|
log.info('nothing to release')
|
|
58
83
|
await writeContext(cwd, {status: 'skip', reason: 'nothing to release'})
|
|
59
|
-
setOutput('status', 'skip')
|
|
84
|
+
api.gh.setOutput('status', 'skip')
|
|
60
85
|
} else {
|
|
61
86
|
log.info(`${count} package(s) to release`)
|
|
62
|
-
setOutput('status', 'proceed')
|
|
87
|
+
api.gh.setOutput('status', 'proceed')
|
|
63
88
|
}
|
|
64
89
|
} catch (e) {
|
|
65
90
|
report.error(e, e.stack).setStatus('failure')
|
|
@@ -38,6 +38,10 @@ export const run = async ({cwd = process.cwd(), env: _env, flags = {}} = {}) =>
|
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
export const createContext = async ({flags, env, cwd}) => {
|
|
41
|
+
// Ensure remote tags are up-to-date before analysis to avoid
|
|
42
|
+
// inconsistencies between receive (context) and pack (parcels).
|
|
43
|
+
try { await $({cwd, quiet: true})`git fetch origin --tags --force` } catch { /* offline / bare */ }
|
|
44
|
+
|
|
41
45
|
const {packages, queue, root, prev, graphs} = await topo({cwd, flags})
|
|
42
46
|
const report = createReport({packages, queue, flags})
|
|
43
47
|
|
|
@@ -111,6 +111,7 @@ export const makeCtx = (overrides = {}) => ({
|
|
|
111
111
|
graphs: overrides.graphs ?? {},
|
|
112
112
|
report: overrides.report ?? makeReport(),
|
|
113
113
|
channels: overrides.channels ?? [],
|
|
114
|
+
|
|
114
115
|
run: overrides.run ?? (async () => {}),
|
|
115
116
|
git: {sha: 'abc1234567890', root: tmpDir, timestamp: '1700000000', ...overrides.git},
|
|
116
117
|
})
|