Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit a848482

Browse files
authored
Merge pull request #24 from pyreon/fix-release-workflow
Fix release: OIDC provenance + custom publish script
2 parents 711899d + 10d39a7 commit a848482

4 files changed

Lines changed: 141 additions & 14 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,53 @@ on:
44
push:
55
branches: [main]
66

7-
concurrency: ${{ github.workflow }}-${{ github.ref }}
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
id-token: write
11+
12+
concurrency:
13+
group: release-${{ github.ref }}
14+
cancel-in-progress: false
15+
16+
env:
17+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
818

919
jobs:
1020
ci:
1121
name: CI
1222
uses: ./.github/workflows/ci.yml
23+
permissions:
24+
contents: read
25+
security-events: write
1326

1427
release:
1528
name: Release
16-
needs: [ci]
29+
needs: ci
1730
runs-on: ubuntu-latest
18-
permissions:
19-
contents: write
20-
pull-requests: write
21-
id-token: write
31+
timeout-minutes: 15
2232
steps:
2333
- uses: actions/checkout@v4
34+
with:
35+
fetch-depth: 0
36+
37+
- uses: actions/setup-node@v4
38+
with:
39+
node-version: 24
40+
registry-url: 'https://registry.npmjs.org'
2441

2542
- uses: oven-sh/setup-bun@v2
2643
with:
2744
bun-version: latest
2845

2946
- run: bun install --frozen-lockfile
3047

31-
- name: Build all packages
32-
run: bun run build
33-
3448
- name: Create Release PR or Publish
3549
uses: changesets/action@v1
3650
with:
37-
version: bunx changeset version
38-
publish: bunx changeset publish
51+
version: bun run version-packages
52+
publish: bun run release
53+
title: "chore: version packages"
54+
commit: "chore: version packages"
3955
env:
4056
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41-
NPM_CONFIG_PROVENANCE: true

.github/workflows/security.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
persist-credentials: false
7474

7575
- name: Run Scorecard
76-
uses: ossf/scorecard-action@v2
76+
uses: ossf/scorecard-action@v2.4.3
7777
with:
7878
results_file: results.sarif
7979
results_format: sarif

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"dev": "bun run --filter='*' dev",
1010
"test": "bun run --filter='*' test",
1111
"typecheck": "bun run --filter='*' typecheck",
12-
"test:template": "bash scripts/test-template.sh"
12+
"test:template": "bash scripts/test-template.sh",
13+
"version-packages": "changeset version",
14+
"release": "bun run build && bun run scripts/publish.ts"
1315
},
1416
"devDependencies": {
1517
"@changesets/changelog-github": "^0.6.0",

scripts/publish.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Publish all @pyreon/* packages via `npm publish --provenance`.
3+
* Resolves workspace:^ → ^X.Y.Z before publish, restores after.
4+
* Skips already-published versions and private packages.
5+
*
6+
* Usage: bun run scripts/publish.ts [--dry-run]
7+
*/
8+
9+
import { readdir, readFile, writeFile } from 'node:fs/promises'
10+
import { join } from 'node:path'
11+
12+
const PACKAGES_DIR = join(import.meta.dirname, '..', 'packages')
13+
const dryRun = process.argv.includes('--dry-run')
14+
const dirs = await readdir(PACKAGES_DIR, { withFileTypes: true })
15+
16+
// Build version map of all workspace packages
17+
const versionMap = new Map<string, string>()
18+
for (const dir of dirs.filter((d) => d.isDirectory())) {
19+
try {
20+
const pkg = JSON.parse(
21+
await readFile(join(PACKAGES_DIR, dir.name, 'package.json'), 'utf-8'),
22+
)
23+
if (pkg.name) versionMap.set(pkg.name, pkg.version)
24+
} catch {
25+
// skip directories without package.json
26+
}
27+
}
28+
29+
/**
30+
* Replace workspace:* and workspace:^ with actual version ranges.
31+
*/
32+
function resolveWorkspaceDeps(
33+
deps: Record<string, string> | undefined,
34+
): Record<string, string> | undefined {
35+
if (!deps) return deps
36+
const resolved = { ...deps }
37+
for (const [name, range] of Object.entries(resolved)) {
38+
if (range.startsWith('workspace:')) {
39+
const version = versionMap.get(name)
40+
if (!version) {
41+
console.error(`Cannot resolve workspace dependency: ${name}`)
42+
process.exit(1)
43+
}
44+
const prefix = range.replace('workspace:', '')
45+
resolved[name] = prefix === '*' ? version : `${prefix}${version}`
46+
}
47+
}
48+
return resolved
49+
}
50+
51+
// Publish each package
52+
for (const dir of dirs.filter((d) => d.isDirectory())) {
53+
const pkgPath = join(PACKAGES_DIR, dir.name, 'package.json')
54+
let raw: string
55+
try {
56+
raw = await readFile(pkgPath, 'utf-8')
57+
} catch {
58+
continue
59+
}
60+
61+
const pkg = JSON.parse(raw)
62+
if (pkg.private || !pkg.name) continue
63+
64+
// Check if already published
65+
const check = Bun.spawnSync(
66+
['npm', 'view', `${pkg.name}@${pkg.version}`, 'version'],
67+
{ stdout: 'pipe', stderr: 'pipe' },
68+
)
69+
if (check.stdout.toString().trim() === pkg.version) {
70+
console.log(`⏭️ ${pkg.name}@${pkg.version} — already published`)
71+
continue
72+
}
73+
74+
console.log(`📦 ${pkg.name}@${pkg.version}`)
75+
76+
// Resolve workspace deps before publishing
77+
const resolved = {
78+
...pkg,
79+
dependencies: resolveWorkspaceDeps(pkg.dependencies),
80+
peerDependencies: resolveWorkspaceDeps(pkg.peerDependencies),
81+
devDependencies: undefined, // strip devDependencies from published package
82+
}
83+
await writeFile(pkgPath, `${JSON.stringify(resolved, null, 2)}\n`)
84+
85+
try {
86+
const args = [
87+
'npm',
88+
'publish',
89+
'--access',
90+
'public',
91+
'--provenance',
92+
'--ignore-scripts',
93+
]
94+
if (dryRun) args.push('--dry-run')
95+
const result = Bun.spawnSync(args, {
96+
cwd: join(PACKAGES_DIR, dir.name),
97+
stdout: 'inherit',
98+
stderr: 'inherit',
99+
})
100+
if (result.exitCode !== 0) {
101+
console.error(`Failed to publish ${pkg.name}`)
102+
process.exit(1)
103+
}
104+
} finally {
105+
// Restore original package.json
106+
await writeFile(pkgPath, raw)
107+
}
108+
}
109+
110+
console.log('\n✅ Done')

0 commit comments

Comments
 (0)