// Publishes the built VS Code extension as a Gitea release (tag vscode-v) with the VSIX attached. // // npm run build # builds extensions/vscode/local-autocomplete-.vsix // node scripts/publish-extension-release.mjs [--dry-run] // // Needs a Gitea access token with write:repository scope, from GITEA_TOKEN or the file // ~/.config/local-autocomplete/gitea-token (chmod 600). Create one at // https://git.harvmaster.com/user/settings/applications. // Release notes come from the matching "## vscode " section of CHANGELOG.md. // Re-running for the same version replaces the attached VSIX. import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { execFileSync } from 'node:child_process' const API = 'https://git.harvmaster.com/api/v1/repos/Harvmaster/autocomplete-model' const repo = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..') const dryRun = process.argv.includes('--dry-run') const pkg = JSON.parse(fs.readFileSync(path.join(repo, 'extensions/vscode/package.json'), 'utf8')) const version = pkg.version const tag = `vscode-v${version}` const vsixName = `${pkg.name}-${version}.vsix` const vsix = path.join(repo, 'extensions/vscode', vsixName) if (!fs.existsSync(vsix)) throw new Error(`${vsix} not found: run "npm run build" first`) const changelog = fs.readFileSync(path.join(repo, 'CHANGELOG.md'), 'utf8') const section = new RegExp(`^## vscode ${version.replace(/\./g, '\\.')}\\b[^\\n]*\\n([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, 'm').exec(changelog) if (!section) throw new Error(`CHANGELOG.md has no "## vscode ${version}" section`) const body = `${section[1].trim()}\n\n**Install:** download \`${vsixName}\` below, then \`code --install-extension ${vsixName}\` (or Extensions → … → Install from VSIX). See the [README](https://git.harvmaster.com/Harvmaster/autocomplete-model#quick-start).` // The release should point at a commit that's actually on the server. const git = (...a) => execFileSync('git', ['-C', repo, ...a], { encoding: 'utf8' }).trim() const head = git('rev-parse', 'HEAD') git('fetch', '-q', 'origin', 'main') if (git('merge-base', '--is-ancestor', head, 'origin/main', '--') === '' && !git('branch', '-r', '--contains', head).includes('origin/main')) { throw new Error('HEAD is not on origin/main yet: push first') } const tokenFile = path.join(os.homedir(), '.config/local-autocomplete/gitea-token') const token = process.env.GITEA_TOKEN ?? (fs.existsSync(tokenFile) ? fs.readFileSync(tokenFile, 'utf8').trim() : '') console.log(`release ${tag} @ ${head.slice(0, 8)} with ${vsixName} (${(fs.statSync(vsix).size / 1e6).toFixed(1)} MB)`) if (dryRun) { console.log('--- notes ---\n' + body); process.exit(0) } if (!token) throw new Error(`no token: set GITEA_TOKEN or write it to ${tokenFile}`) async function api(method, route, body, headers = {}) { const res = await fetch(API + route, { method, body, headers: { authorization: `token ${token}`, ...headers } }) const text = await res.text() if (!res.ok && res.status !== 404) throw new Error(`${method} ${route}: HTTP ${res.status} ${text.slice(0, 300)}`) return res.status === 404 ? null : (text ? JSON.parse(text) : {}) } let release = await api('GET', `/releases/tags/${tag}`) if (!release) { release = await api('POST', '/releases', JSON.stringify({ tag_name: tag, target_commitish: head, name: `VS Code extension ${version}`, body, draft: false, prerelease: version.startsWith('0.') }), { 'content-type': 'application/json' }) console.log(`created release ${release.html_url}`) } else { await api('PATCH', `/releases/${release.id}`, JSON.stringify({ body }), { 'content-type': 'application/json' }) for (const a of release.assets ?? []) if (a.name === vsixName) await api('DELETE', `/releases/${release.id}/assets/${a.id}`) console.log(`updating existing release ${release.html_url}`) } const form = new FormData() form.append('attachment', new Blob([fs.readFileSync(vsix)]), vsixName) const asset = await api('POST', `/releases/${release.id}/assets?name=${encodeURIComponent(vsixName)}`, form) console.log(`attached ${asset.name}: ${asset.browser_download_url}`)