spine连接器和导出修改
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { access, chmod, cp, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { constants } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const run = promisify(execFile)
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
||||
const projectDir = resolve(scriptDir, '..')
|
||||
const releaseRoot = join(projectDir, 'release', 'connector')
|
||||
const platformName = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : process.platform
|
||||
const packageDir = join(releaseRoot, `${platformName}-${process.arch}`)
|
||||
const executableName = process.platform === 'win32' ? 'SpineParticleConnector.exe' : 'SpineParticleConnector'
|
||||
const executablePath = join(packageDir, executableName)
|
||||
const launcherSource = join(projectDir, 'connector', 'launchers', platformName)
|
||||
const nodeTarget = `node${process.versions.node.split('.')[0]}`
|
||||
|
||||
function seaUnavailable(error) {
|
||||
return /single executable application is disabled/i.test(String(error?.stderr || '') + String(error?.stdout || '') + String(error))
|
||||
}
|
||||
|
||||
async function downloadOfficialSeaNode(stagingDir) {
|
||||
const version = process.versions.node
|
||||
const architecture = process.arch === 'arm64' ? 'arm64' : process.arch === 'x64' ? 'x64' : null
|
||||
if (!architecture || !['darwin', 'win32'].includes(process.platform)) {
|
||||
throw new Error('当前系统的 Node 不支持 SEA。请安装 Node 官方发行版,或通过 SEA_NODE_BINARY 指定可用的 Node 可执行文件。')
|
||||
}
|
||||
|
||||
const osName = process.platform === 'darwin' ? 'darwin' : 'win'
|
||||
const extension = process.platform === 'darwin' ? 'tar.gz' : 'zip'
|
||||
const archiveName = `node-v${version}-${osName}-${architecture}.${extension}`
|
||||
const archivePath = join(stagingDir, archiveName)
|
||||
const url = `https://nodejs.org/dist/v${version}/${archiveName}`
|
||||
console.log(`当前 Node 禁用了 SEA,正在临时使用 Node 官方构建器:${url}`)
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(120_000) })
|
||||
if (!response.ok) throw new Error(`无法下载 Node 官方构建器(${response.status}):${url}`)
|
||||
await writeFile(archivePath, Buffer.from(await response.arrayBuffer()))
|
||||
|
||||
if (process.platform === 'darwin') await run('tar', ['-xzf', archivePath, '-C', stagingDir])
|
||||
else await run('powershell.exe', ['-NoProfile', '-Command', `Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${stagingDir.replace(/'/g, "''")}' -Force`])
|
||||
|
||||
const binary = process.platform === 'darwin'
|
||||
? join(stagingDir, `node-v${version}-${osName}-${architecture}`, 'bin', 'node')
|
||||
: join(stagingDir, `node-v${version}-${osName}-${architecture}`, 'node.exe')
|
||||
await access(binary, constants.X_OK)
|
||||
return binary
|
||||
}
|
||||
|
||||
async function buildSea(configPath, stagingDir) {
|
||||
const configuredBinary = process.env.SEA_NODE_BINARY
|
||||
const candidates = configuredBinary ? [configuredBinary, process.execPath] : [process.execPath]
|
||||
let lastError
|
||||
for (const binary of candidates) {
|
||||
try {
|
||||
await run(binary, ['--build-sea', configPath])
|
||||
return
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
if (!seaUnavailable(error)) throw error
|
||||
}
|
||||
}
|
||||
const officialBinary = await downloadOfficialSeaNode(stagingDir)
|
||||
try {
|
||||
await run(officialBinary, ['--build-sea', configPath])
|
||||
} catch (error) {
|
||||
throw new Error(`Node 官方构建器无法生成 SEA:${error instanceof Error ? error.message : error}`, { cause: lastError })
|
||||
}
|
||||
}
|
||||
|
||||
async function createPortableArchive() {
|
||||
const archivePath = join(releaseRoot, `SpineParticleConnector-${platformName}-${process.arch}.zip`)
|
||||
await rm(archivePath, { force: true })
|
||||
if (process.platform === 'darwin') {
|
||||
await run('zip', ['-q', '-r', archivePath, basename(packageDir)], { cwd: releaseRoot })
|
||||
} else if (process.platform === 'win32') {
|
||||
const escapedSource = packageDir.replace(/'/g, "''")
|
||||
const escapedTarget = archivePath.replace(/'/g, "''")
|
||||
await run('powershell.exe', ['-NoProfile', '-Command', `Compress-Archive -LiteralPath '${escapedSource}' -DestinationPath '${escapedTarget}' -Force`])
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return archivePath
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const stagingDir = await mkdtemp(join(tmpdir(), 'spine-particle-sea-'))
|
||||
try {
|
||||
const bundlePath = join(stagingDir, 'spine-connector.cjs')
|
||||
const configPath = join(stagingDir, 'sea-config.json')
|
||||
|
||||
await build({
|
||||
entryPoints: [join(projectDir, 'connector', 'spine-connector.mjs')],
|
||||
outfile: bundlePath,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: nodeTarget,
|
||||
legalComments: 'none',
|
||||
})
|
||||
|
||||
// The release directory is generated output. Recreate this exact package folder
|
||||
// so a rebuild cannot retain files from an older connector version.
|
||||
await rm(packageDir, { recursive: true, force: true })
|
||||
await mkdir(packageDir, { recursive: true })
|
||||
await writeFile(configPath, `${JSON.stringify({
|
||||
main: bundlePath,
|
||||
mainFormat: 'commonjs',
|
||||
output: executablePath,
|
||||
disableExperimentalSEAWarning: true,
|
||||
useCodeCache: false,
|
||||
}, null, 2)}\n`, 'utf8')
|
||||
await buildSea(configPath, stagingDir)
|
||||
|
||||
if (process.platform !== 'win32') await chmod(executablePath, 0o755)
|
||||
if (process.platform === 'darwin') {
|
||||
// SEA modifies a copy of Node. Re-sign it locally so macOS will execute it.
|
||||
await run('codesign', ['--force', '--sign', '-', '--timestamp=none', executablePath])
|
||||
}
|
||||
|
||||
await cp(launcherSource, packageDir, { recursive: true, force: true })
|
||||
if (process.platform !== 'win32') {
|
||||
await Promise.all([
|
||||
chmod(join(packageDir, '启动Spine连接器.command'), 0o755),
|
||||
chmod(join(packageDir, '停止Spine连接器.command'), 0o755),
|
||||
])
|
||||
}
|
||||
await cp(join(projectDir, 'connector', 'DISTRIBUTION.md'), join(packageDir, '使用说明.md'), { force: true })
|
||||
await writeFile(join(packageDir, 'package.json'), `${JSON.stringify({
|
||||
name: 'SpineParticleConnector',
|
||||
connectorVersion: '1.0.0',
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
executable: basename(executablePath),
|
||||
builtWithNode: process.version,
|
||||
}, null, 2)}\n`, 'utf8')
|
||||
|
||||
const archivePath = await createPortableArchive()
|
||||
console.log(`已生成独立连接器:${packageDir}`)
|
||||
console.log(`可执行文件:${executablePath}`)
|
||||
if (archivePath) console.log(`分发压缩包:${archivePath}`)
|
||||
} finally {
|
||||
await rm(stagingDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`构建独立连接器失败:${error instanceof Error ? error.message : error}`)
|
||||
process.exitCode = 1
|
||||
})
|
||||
Reference in New Issue
Block a user