build.mjs 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import assert from 'node:assert/strict'
  2. import fs from 'node:fs'
  3. import path from 'node:path'
  4. import { createHash } from 'node:crypto'
  5. import { execFileSync } from 'node:child_process'
  6. import { root, releaseVersion } from './version.mjs'
  7. import { checkTree } from './check-public-tree.mjs'
  8. import { checkEmbeddingPackage, checkMetaPackage } from './tar.mjs'
  9. import { collectDependencySources } from './dependency-sources.mjs'
  10. const git = args => execFileSync('git', args, { cwd: root, encoding: 'utf8', windowsHide: true }).trim()
  11. const info = releaseVersion(root, process.env.RELEASE_TAG)
  12. assert.equal(git(['status', '--porcelain']), '', 'Release source checkout must be clean')
  13. // checkTree validation happens in CI workflow after export; development repo may contain internal tools
  14. const outputIndex = process.argv.indexOf('--out')
  15. const output = outputIndex >= 0 ? path.resolve(process.argv[outputIndex + 1]) : path.join(root, '.tmp', 'release', info.version)
  16. assert.ok(!fs.existsSync(output) || fs.readdirSync(output).length === 0, 'Release output must be empty; never overwrite a version')
  17. fs.mkdirSync(output, { recursive: true })
  18. const pnpm = (args, cwd = root) => execFileSync(process.execPath, [process.env.npm_execpath, ...args], { cwd, stdio: 'inherit', windowsHide: true })
  19. assert.ok(process.env.npm_execpath, 'Run via pnpm release:build')
  20. pnpm(['build'])
  21. assert.equal(git(['status', '--porcelain']), '', 'Build changed tracked notices or metadata; commit them before release')
  22. pnpm(['pack', '--pack-destination', output], path.join(root, 'packages/bundle'))
  23. const main = path.join(output, info.filename)
  24. pnpm(['--filter', info.packageName, 'pack-check', main])
  25. const embedding = JSON.parse(fs.readFileSync(path.join(root, 'packages/embedding-provider/package.json'), 'utf8'))
  26. pnpm(['pack', '--pack-destination', output], path.join(root, 'packages/embedding-provider'))
  27. const embeddingTarball = `webnovel-embedding-provider-${embedding.version}.tgz`
  28. checkEmbeddingPackage(path.join(output, embeddingTarball), embedding.version)
  29. const meta = JSON.parse(fs.readFileSync(path.join(root, 'packages/meta/package.json'), 'utf8'))
  30. pnpm(['pack', '--pack-destination', output], path.join(root, 'packages/meta'))
  31. const metaTarball = `linfengqaqtat-dsh-scriptor-full-${meta.version}.tgz`
  32. checkMetaPackage(path.join(output, metaTarball), info.version, embedding.version)
  33. const source = `dsh-scriptor-${info.version}-source.tar.gz`
  34. git(['archive', '--format=tar.gz', '--prefix=dsh-scriptor-source/', `--output=${path.join(output, source)}`, 'HEAD'])
  35. const dependencies = await collectDependencySources(root, output)
  36. const hash = file => createHash('sha256').update(fs.readFileSync(path.join(output, file))).digest('hex')
  37. const assets = fs.readdirSync(output).filter(file => fs.statSync(path.join(output, file)).isFile()).sort()
  38. const manifest = { schemaVersion: 1, ...info, publicCommit: git(['rev-parse', 'HEAD']), node: process.version,
  39. pnpm: JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).packageManager.split('@').pop(),
  40. dsh: JSON.parse(fs.readFileSync(path.join(root, 'dsh-baseline.json'), 'utf8')).registry.version,
  41. optionalPackages: [
  42. { name: embedding.name, version: embedding.version, tarball: embeddingTarball },
  43. { name: meta.name, version: meta.version, tarball: metaTarball }
  44. ], dependencyCount: dependencies.length,
  45. assets: assets.map(file => ({ file, sha256: hash(file) })) }
  46. fs.writeFileSync(path.join(output, 'release-manifest.json'), JSON.stringify(manifest, null, 2) + '\n')
  47. assets.push('release-manifest.json')
  48. fs.writeFileSync(path.join(output, 'SHA256SUMS'), assets.sort().map(file => `${hash(file)} ${file}`).join('\n') + '\n')
  49. const changelog = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8')
  50. const notes = changelog.slice(changelog.indexOf(`## [${info.version}]`)).split(/\n## \[/)[0]
  51. fs.writeFileSync(path.join(output, 'release-notes.md'), notes + '\n\n校验与安装:请使用本页 SHA256SUMS、release-manifest.json 和对应源码,参阅 v8 分支的安装教程。\n')
  52. console.log(JSON.stringify({ ok: true, output, version: info.version, publicCommit: manifest.publicCommit, assets: assets.length }))