Skip to content

Commit 20b1003

Browse files
author
gitlayzer
committed
ci: allow npm publish from draft release assets
1 parent 1edc2c9 commit 20b1003

2 files changed

Lines changed: 84 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ jobs:
177177
178178
- name: Publish npm packages
179179
env:
180+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
180181
NPM_VERSION: ${{ github.ref_name }}
181182
NPM_RELEASE_TAG: ${{ github.ref_name }}
182183
NPM_DIST_TAG: ${{ needs.goreleaser.outputs.npm_dist_tag }}

scripts/build-npm-packages.mjs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,90 @@ function writeJson(file, value) {
191191
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
192192
}
193193

194-
async function download(url, destination) {
194+
function githubHeaders(accept) {
195195
const headers = {
196196
'user-agent': 'sealtun-npm-packager'
197197
};
198+
if (accept) {
199+
headers.accept = accept;
200+
}
198201
if (process.env.GITHUB_TOKEN) {
199202
headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
200203
}
204+
if (urlUsesGitHubApi(accept)) {
205+
headers['x-github-api-version'] = '2022-11-28';
206+
}
207+
return headers;
208+
}
209+
210+
function urlUsesGitHubApi(accept) {
211+
return accept === 'application/vnd.github+json' || accept === 'application/octet-stream';
212+
}
213+
214+
async function githubJson(url) {
215+
const response = await fetch(url, {
216+
headers: githubHeaders('application/vnd.github+json')
217+
});
218+
if (!response.ok) {
219+
throw new Error(`Failed to query ${url}: ${response.status} ${response.statusText}`);
220+
}
221+
return response.json();
222+
}
223+
224+
async function resolveReleaseAssetUrls(args) {
225+
if (!process.env.GITHUB_TOKEN) {
226+
return new Map();
227+
}
228+
229+
const releaseApiUrl = `https://api.github.com/repos/${args.repo}/releases/tags/${encodeURIComponent(args.tag)}`;
230+
try {
231+
const release = await githubJson(releaseApiUrl);
232+
return assetUrlsFromRelease(release);
233+
} catch (error) {
234+
console.warn(`${error.message}. Trying draft release lookup.`);
235+
}
236+
237+
const releasesApiUrl = `https://api.github.com/repos/${args.repo}/releases?per_page=100`;
238+
try {
239+
const releases = await githubJson(releasesApiUrl);
240+
const release = releases.find((candidate) => candidate.tag_name === args.tag);
241+
if (!release) {
242+
throw new Error(`Release ${args.tag} was not found in ${releasesApiUrl}`);
243+
}
244+
return assetUrlsFromRelease(release);
245+
} catch (error) {
246+
console.warn(`${error.message}. Falling back to public release asset URLs.`);
247+
return new Map();
248+
}
249+
}
250+
251+
function assetUrlsFromRelease(release) {
252+
const assets = new Map();
253+
for (const asset of release.assets || []) {
254+
if (asset.name && asset.url) {
255+
assets.set(asset.name, asset.url);
256+
}
257+
}
258+
return assets;
259+
}
260+
261+
function releaseAssetSource(args, releaseAssetUrls, assetName) {
262+
const apiUrl = releaseAssetUrls.get(assetName);
263+
if (apiUrl) {
264+
return {
265+
url: apiUrl,
266+
accept: 'application/octet-stream'
267+
};
268+
}
269+
270+
return {
271+
url: `https://github.com/${args.repo}/releases/download/${args.tag}/${assetName}`,
272+
accept: ''
273+
};
274+
}
275+
276+
async function download(url, destination, options = {}) {
277+
const headers = githubHeaders(options.accept);
201278

202279
const attempts = Number(process.env.NPM_DOWNLOAD_RETRIES || 3) + 1;
203280
let lastError = null;
@@ -417,10 +494,11 @@ This package installs the Sealtun CLI by selecting one of the optional platform-
417494

418495
const downloadDir = mkdtempSync(path.join(tmpdir(), 'sealtun-npm-assets-'));
419496
try {
497+
const releaseAssetUrls = await resolveReleaseAssetUrls(args);
420498
const checksumsPath = path.join(downloadDir, 'checksums.txt');
421-
const checksumsUrl = `https://github.com/${args.repo}/releases/download/${args.tag}/checksums.txt`;
499+
const checksumsSource = releaseAssetSource(args, releaseAssetUrls, 'checksums.txt');
422500
console.log('Downloading checksums.txt');
423-
await download(checksumsUrl, checksumsPath);
501+
await download(checksumsSource.url, checksumsPath, { accept: checksumsSource.accept });
424502
const checksums = parseChecksums(readFileSync(checksumsPath, 'utf8'));
425503
if (checksums.size === 0) {
426504
throw new Error('checksums.txt did not contain any SHA-256 entries');
@@ -431,11 +509,11 @@ This package installs the Sealtun CLI by selecting one of the optional platform-
431509
const packageDir = path.join(args.outDir, target.id);
432510
const packageBinDir = path.join(packageDir, 'bin');
433511
const assetName = assetNameFor(target);
434-
const assetUrl = `https://github.com/${args.repo}/releases/download/${args.tag}/${assetName}`;
512+
const assetSource = releaseAssetSource(args, releaseAssetUrls, assetName);
435513
const archivePath = path.join(downloadDir, assetName);
436514

437515
console.log(`Downloading ${assetName}`);
438-
await download(assetUrl, archivePath);
516+
await download(assetSource.url, archivePath, { accept: assetSource.accept });
439517
verifyChecksum(archivePath, assetName, checksums);
440518

441519
mkdirSync(packageBinDir, { recursive: true });

0 commit comments

Comments
 (0)