Skip to content

Commit 02ce158

Browse files
authored
Move version manifest out of bundle (#100)
## Summary - move the version manifest to the repo root and fetch it from raw.githubusercontent.com at runtime - generate a separate known-checksums map keyed by version and asset name - update workflows, tests, and bundled output for the new manifest/checksum flow
1 parent 623e124 commit 02ce158

13 files changed

Lines changed: 2024 additions & 5600 deletions

.gitattributes

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
* text=auto eol=lf
22

33
dist/** linguist-generated=true
4-
src/version-manifest.json linguist-generated=true
4+
src/known-checksums.ts linguist-generated=true
5+
version-manifest.json linguist-generated=true
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Update Version Manifest
1+
name: Update Known Versions
22

33
on:
44
schedule:
@@ -14,8 +14,8 @@ concurrency:
1414
cancel-in-progress: false
1515

1616
jobs:
17-
update-version-manifest:
18-
name: Update version manifest
17+
update-known-versions:
18+
name: Update known versions
1919
runs-on: ubuntu-latest
2020
if: ${{ github.repository == 'j178/prek-action' && github.ref_name == github.event.repository.default_branch }}
2121
steps:
@@ -31,44 +31,45 @@ jobs:
3131
- name: Install dependencies
3232
run: npm ci
3333

34-
- name: Refresh bundled prek release manifest
34+
- name: Refresh prek known versions
3535
id: refresh
3636
env:
3737
GITHUB_TOKEN: ${{ github.token }}
38-
run: npm run update-version-manifest
38+
run: npm run update-known-versions
3939

40-
- name: Check whether the manifest changed
40+
- name: Check whether known version files changed
4141
id: changes
4242
run: |
43-
if git diff --quiet -- src/version-manifest.json; then
44-
echo "manifest_changed=false" >> "$GITHUB_OUTPUT"
43+
if git diff --quiet -- version-manifest.json src/known-checksums.ts; then
44+
echo "known_versions_changed=false" >> "$GITHUB_OUTPUT"
4545
else
46-
echo "manifest_changed=true" >> "$GITHUB_OUTPUT"
46+
echo "known_versions_changed=true" >> "$GITHUB_OUTPUT"
4747
fi
4848
4949
- name: Run unit tests
50-
if: steps.changes.outputs.manifest_changed == 'true'
50+
if: steps.changes.outputs.known_versions_changed == 'true'
5151
run: npm test
5252

5353
- name: Rebuild dist
54-
if: steps.changes.outputs.manifest_changed == 'true'
54+
if: steps.changes.outputs.known_versions_changed == 'true'
5555
run: npm run bundle
5656

5757
- name: Create pull request
58-
if: steps.changes.outputs.manifest_changed == 'true'
58+
if: steps.changes.outputs.known_versions_changed == 'true'
5959
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
6060
with:
6161
add-paths: |
62-
src/version-manifest.json
62+
version-manifest.json
63+
src/known-checksums.ts
6364
dist/index.js
6465
dist/post/index.js
65-
branch: automation/update-prek-version-manifest
66-
commit-message: Update prek version manifest
66+
branch: automation/update-prek-known-versions
67+
commit-message: Update prek known versions
6768
delete-branch: true
6869
token: ${{ github.token }}
6970
title: ${{ steps.refresh.outputs.pr_title }}
7071
body: |
71-
Refreshes the bundled prek release manifest and rebuilt action bundles.
72+
Refreshes the prek known version metadata and rebuilt action bundles.
7273
7374
Added releases:
7475
${{ steps.refresh.outputs.added_versions_markdown }}

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ For a stable reference, pin to a specific release tag such as `v1.2.3`, or pin t
3939
| `extra_args` | Deprecated alias for `extra-args` | No | |
4040
| `install-only` | Install `prek` but skip `prek run` | No | `false` |
4141
| `prek-version` | Version or semver range to install, for example `0.2.30`, `0.3.x`, `<=1.0.0`, or `latest` | No | `latest` |
42-
| `show-verbose-logs` | Print the `prek` verbose log after `prek run` completes | No | `true` |
4342
| `working-directory` | Directory where `prek run` is executed | No | `.` |
43+
| `show-verbose-logs` | Print the `prek` verbose log after `prek run` completes | No | `true` |
4444
| `token` | Unused; retained for backward compatibility | No | `${{ github.token }}` |
4545

4646
## Outputs
@@ -79,7 +79,7 @@ steps:
7979
prek-version: '0.2.30'
8080
```
8181

82-
Resolve a semver range from the bundled release manifest:
82+
Resolve a semver range:
8383

8484
```yaml
8585
steps:
@@ -109,7 +109,6 @@ steps:
109109
with:
110110
show-verbose-logs: false
111111
```
112-
113112
## Requirements
114113

115114
The target repository needs a `prek` or pre-commit configuration file:

dist/index.js

Lines changed: 927 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"bundle": "npm run build && ncc build src/main.ts -o dist && ncc build src/post.ts -o dist/post",
1010
"check-format": "prettier --check .",
1111
"test": "npm run build:test && node --test .test-build/test/**/*.test.js",
12-
"update-version-manifest": "node scripts/update-version-manifest.mjs"
12+
"update-known-versions": "node scripts/update-known-versions.mjs"
1313
},
1414
"engines": {
1515
"node": ">=24"
Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import path from 'node:path'
33
import semver from 'semver'
44

55
const releasesApiUrl = 'https://api.github.com/repos/j178/prek/releases'
6-
const manifestPath = path.resolve('src/version-manifest.json')
6+
const manifestPath = path.resolve('version-manifest.json')
7+
const checksumsPath = path.resolve('src/known-checksums.ts')
78
const minimumSupportedVersion = '0.0.23'
89
const installableArchivePattern = /^prek-(.+)\.(tar\.gz|zip)$/
910
const excludedArchiveNames = new Set(['prek-npm-package.tar.gz'])
@@ -12,12 +13,14 @@ async function run() {
1213
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || ''
1314
const previousReleases = await readExistingManifest()
1415
const previousVersions = new Set(previousReleases.map(release => release.version))
15-
const releases = await fetchAllReleases(token)
16+
const {checksumsByAsset, releases} = await fetchAllReleases(token)
1617
const addedVersions = releases.map(release => release.version).filter(version => !previousVersions.has(version))
1718

18-
await mkdir(path.dirname(manifestPath), {recursive: true})
1919
await writeFile(manifestPath, `${JSON.stringify(releases, null, 2)}\n`)
20+
await mkdir(path.dirname(checksumsPath), {recursive: true})
21+
await writeFile(checksumsPath, renderKnownChecksumsFile(checksumsByAsset))
2022
console.log(`Wrote ${releases.length} prek releases to ${manifestPath}`)
23+
console.log(`Wrote ${checksumsByAsset.size} known checksums to ${checksumsPath}`)
2124
if (addedVersions.length === 0) {
2225
console.log('No new prek releases found')
2326
} else {
@@ -28,11 +31,19 @@ async function run() {
2831
}
2932

3033
async function readExistingManifest() {
31-
return JSON.parse(await readFile(manifestPath, 'utf8'))
34+
try {
35+
return JSON.parse(await readFile(manifestPath, 'utf8'))
36+
} catch (error) {
37+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
38+
return []
39+
}
40+
throw error
41+
}
3242
}
3343

3444
async function fetchAllReleases(token) {
3545
const releases = []
46+
const checksumsByAsset = new Map()
3647

3748
for (let page = 1; ; page += 1) {
3849
const url = `${releasesApiUrl}?per_page=100&page=${page}`
@@ -54,17 +65,24 @@ async function fetchAllReleases(token) {
5465
continue
5566
}
5667

57-
releases.push({
58-
assets: Array.isArray(release.assets)
59-
? release.assets
60-
.filter(asset => isInstallableArchive(asset.name))
61-
.map(asset => ({
68+
const assets = Array.isArray(release.assets)
69+
? release.assets
70+
.filter(asset => isInstallableArchive(asset.name))
71+
.map(asset => {
72+
const checksum = normalizeDigest(asset.digest)
73+
if (checksum) {
74+
checksumsByAsset.set(buildChecksumKey(version, asset.name), checksum)
75+
}
76+
77+
return {
6278
downloadUrl: asset.browser_download_url,
63-
name: asset.name,
64-
sha256: normalizeDigest(asset.digest),
65-
size: asset.size
66-
}))
67-
: [],
79+
name: asset.name
80+
}
81+
})
82+
: []
83+
84+
releases.push({
85+
assets,
6886
prerelease: Boolean(release.prerelease),
6987
publishedAt: release.published_at || release.created_at || '',
7088
version
@@ -76,13 +94,16 @@ async function fetchAllReleases(token) {
7694
}
7795
}
7896

79-
return releases.sort(compareReleasesDesc)
97+
return {
98+
checksumsByAsset,
99+
releases: releases.sort(compareReleasesDesc)
100+
}
80101
}
81102

82103
function buildHeaders(token) {
83104
const headers = {
84105
Accept: 'application/vnd.github+json',
85-
'User-Agent': 'prek-action-manifest-updater'
106+
'User-Agent': 'prek-action-known-versions-updater'
86107
}
87108
if (token) {
88109
headers.Authorization = `Bearer ${token}`
@@ -110,6 +131,23 @@ function compareReleasesDesc(left, right) {
110131
return semver.rcompare(left.version, right.version) || right.publishedAt.localeCompare(left.publishedAt)
111132
}
112133

134+
function renderKnownChecksumsFile(checksumsByAsset) {
135+
const entries = [...checksumsByAsset.entries()]
136+
.sort(([left], [right]) => left.localeCompare(right))
137+
.map(([assetKey, checksum]) => ` ['${assetKey}', '${checksum}']`)
138+
.join(',\n')
139+
return (
140+
'// This file is autogenerated by scripts/update-known-versions.mjs. Do not edit manually.\n' +
141+
'export const knownChecksumsByAsset = new Map<string, string>([\n' +
142+
entries +
143+
'\n])\n'
144+
)
145+
}
146+
147+
function buildChecksumKey(version, assetName) {
148+
return `${version}:${assetName}`
149+
}
150+
113151
async function writeOutputs(addedVersions) {
114152
const outputPath = process.env.GITHUB_OUTPUT
115153
if (!outputPath) {
@@ -118,8 +156,8 @@ async function writeOutputs(addedVersions) {
118156

119157
const prTitle =
120158
addedVersions.length === 0
121-
? 'Update prek version manifest'
122-
: `Update prek version manifest for ${formatVersionSummary(addedVersions)}`
159+
? 'Update prek known versions'
160+
: `Update known versions for ${formatVersionSummary(addedVersions)}`
123161
const addedVersionsMarkdown =
124162
addedVersions.length === 0
125163
? '- None'

src/install.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as fs from 'node:fs/promises'
66
import * as path from 'node:path'
77

88
import {getAssetForVersion} from './manifest'
9+
import {knownChecksumsByAsset} from './known-checksums'
910
import type {ManifestAsset, ReleaseAsset, Version} from './types'
1011

1112
// Install a specific bare prek version, preferring the GitHub Actions tool cache when available.
@@ -25,7 +26,7 @@ export async function installPrek(version: Version): Promise<string> {
2526
core.info(
2627
`Selected release asset ${asset.archiveName} for runner ${process.platform}/${process.arch} (tool-cache arch ${toolArch})`
2728
)
28-
const manifestAsset = getAssetForVersion(version, asset.archiveName)
29+
const manifestAsset = await getAssetForVersion(version, asset.archiveName)
2930

3031
core.info(`Downloading prek from ${manifestAsset.downloadUrl}`)
3132
const archivePath = await tc.downloadTool(manifestAsset.downloadUrl)
@@ -164,30 +165,38 @@ async function verifyDownloadChecksum(
164165
asset: ManifestAsset,
165166
version: Version
166167
): Promise<void> {
167-
const result = await validateDownloadedChecksum(archivePath, asset)
168+
const result = await validateDownloadedChecksum(archivePath, asset, version)
168169
if (result === 'missing') {
169-
core.warning(`No SHA-256 checksum recorded for ${asset.name} in the ${version} manifest entry`)
170-
} else {
171-
core.info(`Verified SHA-256 checksum for ${asset.name}`)
170+
core.warning(`Checksum is not known for ${buildChecksumKey(version, asset.name)}; skipping verification for prek ${version}`)
171+
return
172172
}
173+
174+
core.info(`Verified SHA-256 checksum for ${asset.name} from prek ${version}`)
173175
}
174176

175177
export async function validateDownloadedChecksum(
176178
archivePath: string,
177-
asset: ManifestAsset
178-
): Promise<'missing' | 'matched'> {
179-
if (!asset.sha256) {
179+
asset: ManifestAsset,
180+
version: Version,
181+
checksumMap: ReadonlyMap<string, string> = knownChecksumsByAsset
182+
): Promise<'matched' | 'missing'> {
183+
const expectedDigest = checksumMap.get(buildChecksumKey(version, asset.name))
184+
if (!expectedDigest) {
180185
return 'missing'
181186
}
182187

183188
const digest = await hashFile(archivePath)
184-
if (digest !== asset.sha256) {
185-
throw new Error(`Checksum mismatch for ${asset.name}: expected ${asset.sha256}, received ${digest}`)
189+
if (digest !== expectedDigest) {
190+
throw new Error(`Checksum mismatch for ${asset.name}: expected ${expectedDigest}, received ${digest}`)
186191
}
187192

188193
return 'matched'
189194
}
190195

196+
function buildChecksumKey(version: Version, assetName: string): string {
197+
return `${version}:${assetName}`
198+
}
199+
191200
// Stream the archive through SHA-256 hashing to avoid loading the whole file into memory.
192201
export async function hashFile(filePath: string): Promise<string> {
193202
const hash = crypto.createHash('sha256')

0 commit comments

Comments
 (0)