ci: add release workflow + rcedit devdep, fix patch_icon3 for CI
This commit is contained in:
parent
ee59ba8247
commit
023ebae786
4 changed files with 390 additions and 1 deletions
41
.github/workflows/release.yml
vendored
Normal file
41
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
name: Release EXE
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build EXE
|
||||||
|
run: npx pkg bm.js --target node18-win-x64 --output dist/bm.exe
|
||||||
|
|
||||||
|
- name: Patch icon
|
||||||
|
run: node patch_icon3.js
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bm-downloader-windows
|
||||||
|
path: dist/bm.exe
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: dist/bm.exe
|
||||||
|
name: BurmaldaMusic CLI ${{ github.ref_name }}
|
||||||
|
generate_release_notes: true
|
||||||
|
prerelease: false
|
||||||
237
package-lock.json
generated
Normal file
237
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
{
|
||||||
|
"name": "bm-downloader",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "bm-downloader",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"bm": "bm.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"png-to-ico": "^3.0.2",
|
||||||
|
"rcedit": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@malept/cross-spawn-promise": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/malept"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz",
|
||||||
|
"integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-spawn": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-spawn-windows-exe": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/malept"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/subscription/pkg/npm-cross-spawn-windows-exe?utm_medium=referral&utm_source=npm_fund"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@malept/cross-spawn-promise": "^1.1.0",
|
||||||
|
"is-wsl": "^2.2.0",
|
||||||
|
"which": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-docker": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"is-docker": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-wsl": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/isexe": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/png-to-ico": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/png-to-ico/-/png-to-ico-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-36vvp3/YF7LPYkUEOj08/WgB1wI63qW391YfOhOWckgOtGkarr9+EhPBimcCmRP7fJaG4EaXQhTTaQ8qqdj8aA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"pngjs": "^7.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"png-to-ico": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rcedit": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg==",
|
||||||
|
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn-windows-exe": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/node-which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"assets": []
|
"assets": []
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"png-to-ico": "^3.0.2"
|
"png-to-ico": "^3.0.2",
|
||||||
|
"rcedit": "^4.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
patch_icon3.js
Normal file
110
patch_icon3.js
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
'use strict'
|
||||||
|
const { spawnSync } = require('child_process')
|
||||||
|
const fs = require('fs')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const EXE = path.resolve(__dirname, 'dist', 'bm.exe')
|
||||||
|
const CLEAN = fs.existsSync(path.resolve(__dirname, 'dist', 'bm_clean.exe'))
|
||||||
|
? path.resolve(__dirname, 'dist', 'bm_clean.exe')
|
||||||
|
: EXE
|
||||||
|
const ICON = path.resolve(__dirname, 'icon.ico')
|
||||||
|
|
||||||
|
function findRcedit() {
|
||||||
|
const candidates = [
|
||||||
|
path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'node_modules', 'rcedit', 'bin', 'rcedit-x64.exe'),
|
||||||
|
]
|
||||||
|
try {
|
||||||
|
const pkgDir = path.dirname(require.resolve('rcedit/package.json'))
|
||||||
|
candidates.unshift(path.join(pkgDir, 'bin', 'rcedit-x64.exe'))
|
||||||
|
} catch {}
|
||||||
|
const fromPath = spawnSync('where', ['rcedit-x64.exe'], { encoding: 'utf8' })
|
||||||
|
if (fromPath.stdout) candidates.unshift(fromPath.stdout.trim().split('\n')[0])
|
||||||
|
return candidates.find(p => { try { return fs.statSync(p).isFile() } catch { return false } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const RCEDIT = findRcedit()
|
||||||
|
if (!RCEDIT) { console.error('rcedit not found'); process.exit(1) }
|
||||||
|
console.log('rcedit:', RCEDIT)
|
||||||
|
|
||||||
|
if (CLEAN === EXE) {
|
||||||
|
const tmp = EXE.replace('.exe', '_clean.exe')
|
||||||
|
fs.copyFileSync(EXE, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanPath = CLEAN === EXE ? EXE.replace('.exe', '_clean.exe') : CLEAN
|
||||||
|
const origBuf = fs.readFileSync(cleanPath)
|
||||||
|
|
||||||
|
// Find pkg position values
|
||||||
|
const kbuf = Buffer.from("var PAYLOAD_POSITION = '", 'ascii')
|
||||||
|
let pos = {}
|
||||||
|
for (let i = 0; i < origBuf.length - 200; i++) {
|
||||||
|
if (origBuf.slice(i, i + kbuf.length).equals(kbuf)) {
|
||||||
|
const block = origBuf.slice(i, i + 400).toString('ascii')
|
||||||
|
const pp = block.match(/PAYLOAD_POSITION = '(\d+)\s*'/)
|
||||||
|
const ps = block.match(/PAYLOAD_SIZE = '(\d+)\s*'/)
|
||||||
|
const pp2 = block.match(/PRELUDE_POSITION = '(\d+)\s*'/)
|
||||||
|
const ps2 = block.match(/PRELUDE_SIZE = '(\d+)\s*'/)
|
||||||
|
if (pp) {
|
||||||
|
pos = {
|
||||||
|
PAYLOAD_POSITION: +pp[1],
|
||||||
|
PAYLOAD_SIZE: +ps[1],
|
||||||
|
PRELUDE_POSITION: +pp2[1],
|
||||||
|
PRELUDE_SIZE: +ps2[1],
|
||||||
|
}
|
||||||
|
console.log('pkg vars:', pos)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pos.PAYLOAD_POSITION) { console.error('vars not found'); process.exit(1) }
|
||||||
|
|
||||||
|
const cleanBuf = origBuf
|
||||||
|
const overlay = cleanBuf.slice(pos.PAYLOAD_POSITION)
|
||||||
|
const peOnly = cleanBuf.slice(0, pos.PAYLOAD_POSITION)
|
||||||
|
console.log(`overlay: ${overlay.length}b, PE: ${peOnly.length}b`)
|
||||||
|
|
||||||
|
fs.writeFileSync(EXE, peOnly)
|
||||||
|
|
||||||
|
console.log('rcedit...')
|
||||||
|
const r = spawnSync(RCEDIT, [EXE, '--set-icon', ICON], { stdio: 'inherit' })
|
||||||
|
if (r.status !== 0) { process.exit(1) }
|
||||||
|
|
||||||
|
const patchedPE = fs.readFileSync(EXE)
|
||||||
|
const newPP = patchedPE.length
|
||||||
|
const newPRP = newPP + pos.PAYLOAD_SIZE
|
||||||
|
console.log(`new PE: ${patchedPE.length} new PP: ${newPP} new PRP: ${newPRP}`)
|
||||||
|
|
||||||
|
const full = Buffer.from(Buffer.concat([patchedPE, overlay]))
|
||||||
|
|
||||||
|
function replaceNum(buf, oldVal, newVal) {
|
||||||
|
const oldStr = String(oldVal)
|
||||||
|
const newStr = String(newVal)
|
||||||
|
const needle = Buffer.from(`'${oldStr}`, 'ascii')
|
||||||
|
let count = 0
|
||||||
|
let i = 0
|
||||||
|
while ((i = buf.indexOf(needle, i)) !== -1) {
|
||||||
|
let end = i + 1
|
||||||
|
while (buf[end] !== 0x27 && end < i + 30) end++
|
||||||
|
const fieldLen = end - i - 1
|
||||||
|
if (fieldLen < oldStr.length) { i++; continue }
|
||||||
|
const padded = newStr + ' '.repeat(fieldLen - newStr.length)
|
||||||
|
buf.write(padded, i + 1, 'ascii')
|
||||||
|
console.log(` patched '${oldStr}' → '${newStr}' at offset ${i}`)
|
||||||
|
count++
|
||||||
|
i = end
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
let total = 0
|
||||||
|
total += replaceNum(full, pos.PAYLOAD_POSITION, newPP)
|
||||||
|
total += replaceNum(full, pos.PRELUDE_POSITION, newPRP)
|
||||||
|
console.log(`total patches: ${total}`)
|
||||||
|
|
||||||
|
fs.writeFileSync(EXE, full)
|
||||||
|
fs.unlinkSync(EXE + '.notail').catch?.(() => {})
|
||||||
|
try { fs.unlinkSync(CLEAN + '.pe_only') } catch {}
|
||||||
|
|
||||||
|
console.log(`done: ${EXE} = ${full.length} bytes`)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue