import crypto from 'node:crypto' import path from 'node:path' import fs from 'node:fs' import { fileURLToPath } from 'node:url' import { options, packageRelease, inspectPackage, deploymentTarget, shellQuote, frontendRoot, frontendImage, validTag, run, fail } from './release.mjs' export const installScript = `set -euo pipefail BASE_DIR=$(cd -- "$1" && pwd -P) ARCHIVE=$2 EXPECTED=$3 TAG=$4 MODE=$5 RELEASE="$BASE_DIR/frontend-releases/$TAG" STAGING='' cleanup() { rm -f -- "$ARCHIVE" if [[ -n "$STAGING" && "$STAGING" == "$BASE_DIR/frontend-releases/.incoming-"* ]]; then rm -rf -- "$STAGING"; fi } trap cleanup EXIT exec 8>"$BASE_DIR/frontend-releases/.install-$TAG.lock" flock -x 8 printf '%s %s\\n' "$EXPECTED" "$ARCHIVE" | sha256sum -c - if [[ -e "$RELEASE" ]]; then [[ -f "$RELEASE/package.sha256" && "$(cat "$RELEASE/package.sha256")" == "$EXPECTED" ]] || { echo 'Existing release has a different checksum' >&2; exit 1; } else STAGING=$(mktemp -d "$BASE_DIR/frontend-releases/.incoming-$TAG.XXXXXX") tar --no-same-owner -xzf "$ARCHIVE" -C "$STAGING" printf '%s\\n' "$EXPECTED" > "$STAGING/package.sha256" mv -T -- "$STAGING" "$RELEASE" STAGING='' fi SW_DEPLOY_BASE="$BASE_DIR" bash "$RELEASE/deploy.sh" "$TAG" "$MODE" ` function main() { const args = options({ archive: { type: 'string' }, tag: { type: 'string' }, 'source-ref': { type: 'string' }, 'skip-install': { type: 'boolean' }, 'image-tag': { type: 'string' }, host: { type: 'string' }, user: { type: 'string' }, port: { type: 'string' }, identity: { type: 'string' }, directory: { type: 'string' }, 'build-only': { type: 'boolean' } }) if (args.help) { console.log('pnpm deploy:test [--archive FILE.tar.gz | --image-tag VERSION] [--tag VERSION] [--build-only] [--host 192.168.2.226] [--user root] [--port 22] [--identity SSH_KEY] [--directory /www/wwwroot/sw2.fuhua91.com]\n默认打包并在服务器构建、推送 Harbor 后部署;--image-tag 直接拉取已有 Harbor 镜像;--build-only 仅预检,不替换当前容器。') return } if (args.archive && (args.tag || args['source-ref'] || args['skip-install'])) throw new Error('--archive 复用现有包,不能再指定构建参数') if (args['image-tag'] && (args.archive || args.tag || args['source-ref'] || args['skip-install'])) throw new Error('--image-tag 直接使用 Harbor 镜像,不能同时指定发布包或构建参数') const target = deploymentTarget(args) const identity = args.identity ? ['-i', path.resolve(args.identity)] : [] const ssh = ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', '-p', target.port, ...identity, target.remote] if (args['image-tag']) { const tag = validTag(args['image-tag']) const command = `SW_DEPLOY_BASE=${shellQuote(target.base)} bash -s -- ${shellQuote(tag)} --pull-only${args['build-only'] ? ' --build-only' : ''}` run('ssh', [...ssh, command], { stdio: ['pipe', 'inherit', 'inherit'], input: fs.readFileSync(path.join(frontendRoot, 'deploy/deploy.sh'), 'utf8').replaceAll('\r\n', '\n') }) console.log(`${args['build-only'] ? 'Harbor 镜像预检通过' : 'Harbor 镜像部署完成'}:${frontendImage(tag)}`) return } const release = args.archive ? inspectPackage(args.archive) : inspectPackage(packageRelease(args).archive) const uploads = `${target.base}/frontend-releases` const uploaded = `${uploads}/.upload-${release.info.version}-${crypto.randomBytes(6).toString('hex')}.tar.gz` run('ssh', [...ssh, `test -d ${shellQuote(target.base)} && test -w ${shellQuote(target.base)} && mkdir -p ${shellQuote(uploads)}`]) run('scp', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', '-P', target.port, ...identity, release.archive, `${target.remote}:${uploaded}`]) const parameters = [target.base, uploaded, release.checksum, release.info.version, args['build-only'] ? '--build-only' : ''] run('ssh', [...ssh, `bash -s -- ${parameters.map(shellQuote).join(' ')}`], { stdio: ['pipe', 'inherit', 'inherit'], input: installScript }) console.log(args['build-only'] ? `预检通过:${release.info.version},当前前端未替换` : `前端发布完成:${release.info.version};http://sw2.fuhua91.com`) } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { try { main() } catch (error) { fail(error) } }