- models/: GGUFs (Git LFS), manifest.json, model card, Apache-2.0 LICENSE + NOTICE - extensions/vscode/: the Local Autocomplete extension (v0.5.0, publisher harvmaster) - daemon/: acd completion daemon (bundled into the extension; also runs standalone) - shared/: prompt layout + symbol extraction shared with the training pipeline - scripts/: make-model-release.mjs, publish-extension-release.mjs (Gitea releases) - README with quick start, settings, architecture, build and release instructions; MIT license for code; CHANGELOG The default model manifest URL moves to models/manifest.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
4.1 KiB
JavaScript
66 lines
4.1 KiB
JavaScript
// Publishes the built VS Code extension as a Gitea release (tag vscode-v<version>) with the VSIX attached.
|
|
//
|
|
// npm run build # builds extensions/vscode/local-autocomplete-<version>.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 <version>" 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}`)
|