zx-bulk-release 3.1.3 → 3.1.5
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 +13 -0
- package/package.json +1 -1
- package/src/main/js/post/api/gh.js +1 -2
- package/src/main/js/post/api/git.js +4 -4
- 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 +15 -4
- 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/index.js +12 -11
- package/src/main/js/post/courier/semaphore.js +15 -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 +9 -10
- package/src/main/js/post/modes/deliver.js +26 -2
- 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/parcel/build.js +3 -1
- package/src/main/js/post/parcel/index.js +1 -1
- package/src/main/js/post/parcel/verify.js +3 -2
- 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,16 @@
|
|
|
1
|
+
## [3.1.5](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.4...v3.1.5) (2026-04-13)
|
|
2
|
+
|
|
3
|
+
### Fixes & improvements
|
|
4
|
+
* fix: make git-tag channel work with both local and remote repos ([0102cdd](https://github.com/semrel-extra/zx-bulk-release/commit/0102cddaf6c091e8cc68533be8034f95776c64b4))
|
|
5
|
+
* fix: use package name and version in parcel names ([6b5f1e7](https://github.com/semrel-extra/zx-bulk-release/commit/6b5f1e761c9821460267c1464db01af1029dbaa7))
|
|
6
|
+
* fix: enable git-tag and semaphore in bare deliver repo ([abe82d9](https://github.com/semrel-extra/zx-bulk-release/commit/abe82d9b299a489ce16c0a9f3867894657b4e2a4))
|
|
7
|
+
|
|
8
|
+
## [3.1.4](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.3...v3.1.4) (2026-04-13)
|
|
9
|
+
|
|
10
|
+
### Fixes & improvements
|
|
11
|
+
* fix: sync receive/pack via context-driven pack filter ([5b539ff](https://github.com/semrel-extra/zx-bulk-release/commit/5b539ff327b07c171c523b36f28cc2e05c4346d6))
|
|
12
|
+
* refactor: introduce internal di layer ([a32773d](https://github.com/semrel-extra/zx-bulk-release/commit/a32773d37e9454953029f18e0af7de33486c14e3))
|
|
13
|
+
|
|
1
14
|
## [3.1.3](https://github.com/semrel-extra/zx-bulk-release/compare/v3.1.2...v3.1.3) (2026-04-13)
|
|
2
15
|
|
|
3
16
|
### Fixes & improvements
|
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Low-level GitHub API primitives. No domain knowledge, no imports from processor/ or steps/.
|
|
2
2
|
|
|
3
|
-
import nodeFs from 'node:fs'
|
|
4
3
|
import {$, path, tempy, glob, fs, fetch} from 'zx-extra'
|
|
5
4
|
import {asArray, attempt2} from '../../util.js'
|
|
6
5
|
|
|
@@ -93,7 +92,7 @@ export const ghGetAsset = async ({repoName, tag, name, ghUrl}) => {
|
|
|
93
92
|
export const setOutput = (name, value) => {
|
|
94
93
|
const outputFile = process.env.GITHUB_OUTPUT
|
|
95
94
|
if (outputFile) {
|
|
96
|
-
try {
|
|
95
|
+
try { fs.appendFileSync(outputFile, `${name}=${value}\n`) } catch { /* not in CI */ }
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
|
|
@@ -104,12 +104,12 @@ export const getTags = memoizeBy(
|
|
|
104
104
|
async (cwd, ref = '*') => `${await getRoot(cwd)}:${ref}`,
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
export const pushTag = async ({cwd, tag, gitCommitterName, gitCommitterEmail}) => {
|
|
107
|
+
export const pushTag = async ({cwd, tag, sha, gitCommitterName, gitCommitterEmail}) => {
|
|
108
108
|
await setUserConfig(cwd, gitCommitterName, gitCommitterEmail)
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
const target = sha ? [sha] : []
|
|
111
|
+
await $({cwd})`git tag -m ${tag} ${tag} ${target}`
|
|
112
|
+
await $({cwd})`git push origin ${tag}`
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Memoize prevents .git/config lock
|
|
@@ -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,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {$} from 'zx-extra'
|
|
2
|
+
import {api} from '../../api/index.js'
|
|
3
|
+
import {DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL} from '../../api/git.js'
|
|
2
4
|
|
|
3
5
|
export const isTagConflict = (e) =>
|
|
4
6
|
/already exists|updates were rejected|failed to push/i.test(e?.message || e?.stderr || '')
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
+
const ensureSha = async (cwd, sha) => {
|
|
9
|
+
if (!sha) return
|
|
10
|
+
const check = await $({cwd, nothrow: true, quiet: true})`git cat-file -e ${sha}`
|
|
11
|
+
if (check.exitCode !== 0) {
|
|
12
|
+
await $({cwd, quiet: true})`git fetch origin --depth 1`
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const run = async (manifest, dir) => {
|
|
17
|
+
const {tag, sha, cwd} = manifest
|
|
8
18
|
const gitCommitterName = manifest.gitCommitterName || DEFAULT_GIT_COMMITTER_NAME
|
|
9
19
|
const gitCommitterEmail = manifest.gitCommitterEmail || DEFAULT_GIT_COMMITTER_EMAIL
|
|
10
20
|
try {
|
|
11
|
-
await
|
|
21
|
+
await ensureSha(cwd, sha)
|
|
22
|
+
await api.git.pushTag({cwd, tag, sha, gitCommitterName, gitCommitterEmail})
|
|
12
23
|
return 'ok'
|
|
13
24
|
} catch (e) {
|
|
14
25
|
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'),
|
|
@@ -49,13 +49,14 @@ export const resolveManifest = (manifest, env = process.env) => {
|
|
|
49
49
|
|
|
50
50
|
const MARKERS = new Set(['released', 'skip', 'conflict', 'orphan'])
|
|
51
51
|
|
|
52
|
-
const openParcel = async (tarPath, env) => {
|
|
52
|
+
const openParcel = async (tarPath, env, {cwd} = {}) => {
|
|
53
53
|
const content = await fs.readFile(tarPath, 'utf8').catch(() => null)
|
|
54
54
|
if (MARKERS.has(content)) return null
|
|
55
55
|
|
|
56
56
|
const destDir = tempy.temporaryDirectory()
|
|
57
57
|
const {manifest} = await unpackTar(tarPath, destDir)
|
|
58
58
|
const resolved = resolveManifest(manifest, env)
|
|
59
|
+
if (cwd) resolved.cwd = cwd
|
|
59
60
|
const ch = channels[resolved.channel]
|
|
60
61
|
|
|
61
62
|
if (!ch) return {warn: `unknown channel '${resolved.channel || '<none>'}'`}
|
|
@@ -67,14 +68,14 @@ const openParcel = async (tarPath, env) => {
|
|
|
67
68
|
return {ch, resolved, destDir, tarPath}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export const inspect = async (tars, env = process.env) => {
|
|
71
|
+
export const inspect = async (tars, env = process.env, {cwd} = {}) => {
|
|
71
72
|
const parcels = []
|
|
72
73
|
const skipped = []
|
|
73
74
|
|
|
74
75
|
for (const tarPath of tars) {
|
|
75
76
|
const fname = path.basename(tarPath)
|
|
76
77
|
try {
|
|
77
|
-
const p = await openParcel(tarPath, env)
|
|
78
|
+
const p = await openParcel(tarPath, env, {cwd})
|
|
78
79
|
if (!p) continue
|
|
79
80
|
if (p.warn) {
|
|
80
81
|
skipped.push({file: fname, reason: p.warn, tarPath: p.tarPath})
|
|
@@ -100,8 +101,8 @@ export const inspect = async (tars, env = process.env) => {
|
|
|
100
101
|
|
|
101
102
|
// --- Legacy deliver (no directive) ---
|
|
102
103
|
|
|
103
|
-
const deliverLegacy = async (tars, env, {concurrency, dryRun}) => {
|
|
104
|
-
const {groups, skipped, total, pending} = await inspect(tars, env)
|
|
104
|
+
const deliverLegacy = async (tars, env, {concurrency, dryRun, cwd}) => {
|
|
105
|
+
const {groups, skipped, total, pending} = await inspect(tars, env, {cwd})
|
|
105
106
|
|
|
106
107
|
for (const {file, reason, tarPath} of skipped) {
|
|
107
108
|
log.warn(`skipping ${file}: ${reason}`)
|
|
@@ -135,8 +136,8 @@ const deliverLegacy = async (tars, env, {concurrency, dryRun}) => {
|
|
|
135
136
|
|
|
136
137
|
// --- Directive-aware deliver ---
|
|
137
138
|
|
|
138
|
-
const deliverParcel = async (tarPath, channelName, pkgName, version, env, {dryRun}) => {
|
|
139
|
-
const p = await openParcel(tarPath, env)
|
|
139
|
+
const deliverParcel = async (tarPath, channelName, pkgName, version, env, {dryRun, cwd}) => {
|
|
140
|
+
const p = await openParcel(tarPath, env, {cwd})
|
|
140
141
|
if (!p) return 'already'
|
|
141
142
|
if (p.warn) {
|
|
142
143
|
log.warn(`skipping ${p.warn}`)
|
|
@@ -159,7 +160,7 @@ const deliverParcel = async (tarPath, channelName, pkgName, version, env, {dryRu
|
|
|
159
160
|
return res === 'duplicate' ? 'duplicate' : 'ok'
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
const deliverDirective = async (directive, tarMap, env, {dryRun}) => {
|
|
163
|
+
const deliverDirective = async (directive, tarMap, env, {dryRun, cwd}) => {
|
|
163
164
|
const entries = []
|
|
164
165
|
const conflicts = []
|
|
165
166
|
const skipped = []
|
|
@@ -178,7 +179,7 @@ const deliverDirective = async (directive, tarMap, env, {dryRun}) => {
|
|
|
178
179
|
const tarPath = parcelName && tarMap.get(parcelName)
|
|
179
180
|
if (!tarPath) return 'missing'
|
|
180
181
|
|
|
181
|
-
return deliverParcel(tarPath, channelName, pkgName, pkg.version, env, {dryRun})
|
|
182
|
+
return deliverParcel(tarPath, channelName, pkgName, pkg.version, env, {dryRun, cwd})
|
|
182
183
|
}))
|
|
183
184
|
|
|
184
185
|
for (let i = 0; i < step.length; i++) {
|
|
@@ -209,7 +210,7 @@ export const deliver = async (tars, env = process.env, {concurrency = 4, dryRun
|
|
|
209
210
|
const dir = tars.length ? path.dirname(tars[0]) : null
|
|
210
211
|
const directives = dir ? await scanDirectives(dir) : []
|
|
211
212
|
|
|
212
|
-
if (!directives.length) return deliverLegacy(tars, env, {concurrency, dryRun})
|
|
213
|
+
if (!directives.length) return deliverLegacy(tars, env, {concurrency, dryRun, cwd})
|
|
213
214
|
|
|
214
215
|
const tarMap = new Map(tars.map(t => [path.basename(t), t]))
|
|
215
216
|
const allEntries = []
|
|
@@ -225,7 +226,7 @@ export const deliver = async (tars, env = process.env, {concurrency = 4, dryRun
|
|
|
225
226
|
|
|
226
227
|
try {
|
|
227
228
|
await invalidateOrphans(dir, directive)
|
|
228
|
-
const {entries, conflicts, skipped} = await deliverDirective(directive, tarMap, env, {dryRun})
|
|
229
|
+
const {entries, conflicts, skipped} = await deliverDirective(directive, tarMap, env, {dryRun, cwd: gitRoot})
|
|
229
230
|
allEntries.push(...entries)
|
|
230
231
|
allConflicts.push(...conflicts)
|
|
231
232
|
allSkipped.push(...skipped)
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {$} from 'zx-extra'
|
|
2
|
+
import {api} from '../api/index.js'
|
|
3
|
+
import {DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL} from '../api/git.js'
|
|
2
4
|
|
|
3
5
|
const tagName = ({sha}) =>
|
|
4
6
|
`zbr-deliver.${sha.slice(0, 7)}`
|
|
5
7
|
|
|
8
|
+
const ensureHead = async (cwd) => {
|
|
9
|
+
try { await $({cwd, quiet: true})`git rev-parse HEAD` } catch {
|
|
10
|
+
await api.git.setUserConfig(cwd, DEFAULT_GIT_COMMITTER_NAME, DEFAULT_GIT_COMMITTER_EMAIL)
|
|
11
|
+
await $({cwd, quiet: true})`git commit --allow-empty -m init`
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
export const tryLock = async (cwd, directive) => {
|
|
7
16
|
const tag = tagName(directive)
|
|
8
17
|
const body = JSON.stringify({
|
|
@@ -11,7 +20,8 @@ export const tryLock = async (cwd, directive) => {
|
|
|
11
20
|
packages: directive.queue,
|
|
12
21
|
})
|
|
13
22
|
try {
|
|
14
|
-
await
|
|
23
|
+
await ensureHead(cwd)
|
|
24
|
+
await api.git.pushAnnotatedTag(cwd, tag, body)
|
|
15
25
|
return true
|
|
16
26
|
} catch {
|
|
17
27
|
return false
|
|
@@ -19,13 +29,13 @@ export const tryLock = async (cwd, directive) => {
|
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
export const unlock = async (cwd, directive) => {
|
|
22
|
-
await deleteRemoteTag(cwd, tagName(directive))
|
|
32
|
+
await api.git.deleteRemoteTag(cwd, tagName(directive))
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
export const signalRebuild = async (cwd, sha) => {
|
|
26
36
|
const tag = `zbr-rebuild.${sha.slice(0, 7)}`
|
|
27
|
-
try { await pushAnnotatedTag(cwd, tag, 'rebuild') } catch { /* already signaled */ }
|
|
37
|
+
try { await api.git.pushAnnotatedTag(cwd, tag, 'rebuild') } catch { /* already signaled */ }
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
export const consumeRebuildSignal = async (cwd, sha) =>
|
|
31
|
-
deleteRemoteTag(cwd, `zbr-rebuild.${sha.slice(0, 7)}`)
|
|
41
|
+
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,10 @@
|
|
|
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 {
|
|
7
|
-
import {getRepo} from '../../api/git.js'
|
|
6
|
+
import {sanitizePkgName} from '../../parcel/build.js'
|
|
8
7
|
import {formatReleaseNotes} from '../generators/notes.js'
|
|
9
|
-
import {ghPrepareAssets} from '../../api/gh.js'
|
|
10
8
|
import {packTar, hashFile} from '../../tar.js'
|
|
11
9
|
|
|
12
10
|
export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
@@ -15,12 +13,12 @@ export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
|
15
13
|
const active = getActiveChannels(pkg, channelNames, snapshot)
|
|
16
14
|
|
|
17
15
|
await prepare(active, pkg)
|
|
18
|
-
await npmPersist(pkg)
|
|
16
|
+
await api.npm.npmPersist(pkg)
|
|
19
17
|
|
|
20
18
|
const outputDir = flags.pack ? path.resolve(ctx.git.root, typeof flags.pack === 'string' ? flags.pack : PARCELS_DIR) : null
|
|
21
19
|
const stageDir = outputDir || tempy.temporaryDirectory()
|
|
22
20
|
if (outputDir) await fs.ensureDir(outputDir)
|
|
23
|
-
const {repoName, repoHost, originUrl} = await getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
|
|
21
|
+
const {repoName, repoHost, originUrl} = await api.git.getRepo(pkg.absPath, {basicAuth: pkg.config.ghBasicAuth})
|
|
24
22
|
const artifacts = {repoName, repoHost, originUrl}
|
|
25
23
|
|
|
26
24
|
if (active.includes('npm')) {
|
|
@@ -35,19 +33,20 @@ export const pack = memoizeBy(async (pkg, ctx = pkg.ctx) => {
|
|
|
35
33
|
await fs.copy(path.join(pkg.absPath, from), artifacts.docsDir)
|
|
36
34
|
}
|
|
37
35
|
if (active.includes('gh-release') && pkg.config.ghAssets?.length)
|
|
38
|
-
artifacts.assetsDir = await ghPrepareAssets(pkg.config.ghAssets, pkg.absPath)
|
|
36
|
+
artifacts.assetsDir = await api.gh.ghPrepareAssets(pkg.config.ghAssets, pkg.absPath)
|
|
39
37
|
|
|
40
38
|
const parcels = buildParcels(pkg, ctx, {channels: active, ...artifacts})
|
|
41
39
|
|
|
42
40
|
const tars = []
|
|
43
41
|
for (const {channel, manifest, files} of parcels) {
|
|
44
42
|
// Two-pass: pack to temp, hash, rename to final name.
|
|
45
|
-
const tmpPath =
|
|
43
|
+
const tmpPath = tempy.temporaryFile({extension: 'tar'})
|
|
46
44
|
await packTar(tmpPath, manifest, files)
|
|
47
45
|
const hash = await hashFile(tmpPath)
|
|
48
46
|
const sha7 = ctx.git.sha.slice(0, 7)
|
|
49
|
-
const
|
|
50
|
-
|
|
47
|
+
const safeName = sanitizePkgName(pkg.name)
|
|
48
|
+
const finalPath = path.join(stageDir, `parcel.${sha7}.${channel}.${safeName}.${pkg.version}.${hash}.tar`)
|
|
49
|
+
await fs.move(tmpPath, finalPath)
|
|
51
50
|
tars.push(finalPath)
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {$, glob, path} from 'zx-extra'
|
|
1
|
+
import {$, glob, path, tempy} from 'zx-extra'
|
|
2
2
|
import {createReport, log} from '../log.js'
|
|
3
3
|
import {deliver} from '../courier/index.js'
|
|
4
4
|
import {PARCELS_DIR} from '../parcel/index.js'
|
|
@@ -15,9 +15,33 @@ export const runDeliver = async ({env, flags}) => {
|
|
|
15
15
|
const tars = await glob(path.join(dir, 'parcel.*.tar'))
|
|
16
16
|
if (!tars.length) return report.setStatus('success').log(`no parcels in ${dir}`)
|
|
17
17
|
|
|
18
|
+
const cwd = await ensureGitRepo(env)
|
|
19
|
+
|
|
18
20
|
report.setStatus('delivering').log(`parcels: ${tars.length}`)
|
|
19
|
-
const result = await deliver(tars, env, {dryRun: flags.dryRun})
|
|
21
|
+
const result = await deliver(tars, env, {dryRun: flags.dryRun, cwd})
|
|
20
22
|
report.set('delivery', result).setStatus('success')
|
|
21
23
|
|
|
22
24
|
log.info(`done: ${result.delivered} delivered, ${result.skipped} skipped`)
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
const ensureGitRepo = async (env) => {
|
|
28
|
+
try {
|
|
29
|
+
return (await $({quiet: true})`git rev-parse --show-toplevel`).toString().trim()
|
|
30
|
+
} catch {
|
|
31
|
+
// Deliver runs in a clean runner with no checkout — init a temp repo for git push operations.
|
|
32
|
+
const tmp = tempy.temporaryDirectory()
|
|
33
|
+
const repo = env.GITHUB_REPOSITORY || ''
|
|
34
|
+
const token = env.GH_TOKEN || env.GITHUB_TOKEN || ''
|
|
35
|
+
const user = env.GH_USER || env.GH_USERNAME || 'x-access-token'
|
|
36
|
+
const server = env.GITHUB_SERVER_URL || 'https://github.com'
|
|
37
|
+
const host = new URL(server).host
|
|
38
|
+
const origin = token
|
|
39
|
+
? `https://${user}:${token}@${host}/${repo}.git`
|
|
40
|
+
: `${server}/${repo}.git`
|
|
41
|
+
|
|
42
|
+
await $({quiet: true})`git init ${tmp}`
|
|
43
|
+
await $({quiet: true, cwd: tmp})`git remote add origin ${origin}`
|
|
44
|
+
log.info(`initialized temp git repo for delivery: ${repo}`)
|
|
45
|
+
return tmp
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -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')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {asTuple, msgJoin} from '../../util.js'
|
|
2
2
|
|
|
3
|
+
export const sanitizePkgName = (name) => name.replace(/[^a-z0-9-]/ig, '-').replace(/^-+|-+$/g, '')
|
|
4
|
+
|
|
3
5
|
const gitFields = (a, pkg) => ({
|
|
4
6
|
repoHost: a.repoHost,
|
|
5
7
|
repoName: a.repoName,
|
|
@@ -14,7 +16,7 @@ const entry = {
|
|
|
14
16
|
manifest: {
|
|
15
17
|
channel: 'git-tag',
|
|
16
18
|
name: pkg.name, version: pkg.version, tag: pkg.tag,
|
|
17
|
-
cwd: ctx.git.root,
|
|
19
|
+
cwd: ctx.git.root, sha: ctx.git.sha,
|
|
18
20
|
gitCommitterName: '${{GIT_COMMITTER_NAME}}',
|
|
19
21
|
gitCommitterEmail: '${{GIT_COMMITTER_EMAIL}}',
|
|
20
22
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {buildParcels} from './build.js'
|
|
1
|
+
export {buildParcels, sanitizePkgName} from './build.js'
|
|
2
2
|
export {buildDirective, parseDirective, scanDirectives, invalidateOrphans, parcelChannel} from './directive.js'
|
|
3
3
|
export {verifyParcels} from './verify.js'
|
|
4
4
|
export const PARCELS_DIR = 'parcels'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {path} from 'zx-extra'
|
|
2
2
|
import {parcelChannel} from './directive.js'
|
|
3
|
+
import {sanitizePkgName} from './build.js'
|
|
3
4
|
|
|
4
5
|
export const verifyParcels = (tars, context) => {
|
|
5
6
|
const {sha7, packages: expected} = context
|
|
@@ -25,8 +26,8 @@ export const verifyParcels = (tars, context) => {
|
|
|
25
26
|
continue
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const belongsTo = Object.entries(expected).find(([, pkg]) =>
|
|
29
|
-
|
|
29
|
+
const belongsTo = Object.entries(expected).find(([pkgName, pkg]) =>
|
|
30
|
+
name.includes(`.${sanitizePkgName(pkgName)}.${pkg.version}.`)
|
|
30
31
|
)
|
|
31
32
|
if (!belongsTo) {
|
|
32
33
|
errors.push(`unexpected parcel (no matching package): ${name}`)
|
|
@@ -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
|
})
|