chore: build with upload (#48456)

* chore: build with upload

* chore: use zip instead

* chore: tmp of it

* chore: done of logic

* chore: clean up

* chore: rm test logic

* chore: udpate log

* chore: tmp of it

* chore: tmp of it

* chore: back

* chore: clean up

* chore: more print

* chore: xx.xx%

---------

Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
二货爱吃白萝卜 2024-04-16 16:41:02 +08:00 committed by GitHub
parent acbc46d798
commit 717272daa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 209 additions and 90 deletions

View File

@ -287,6 +287,7 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=4096" NODE_OPTIONS: "--max_old_space_size=4096"
CI: 1 CI: 1
# Artifact build files
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: github.event_name == 'push' && github.ref == 'refs/heads/master' if: github.event_name == 'push' && github.ref == 'refs/heads/master'
with: with:
@ -297,6 +298,17 @@ jobs:
es es
lib lib
- name: zip builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
ALI_OSS_AK_ID: ${{ secrets.ALI_OSS_AK_ID }}
ALI_OSS_AK_SECRET: ${{ secrets.ALI_OSS_AK_SECRET }}
HEAD_SHA: ${{ github.sha }}
run: |
zip -r oss-artifacts.zip dist locale es lib
echo "🤖 Uploading"
node scripts/visual-regression/upload.js ./oss-artifacts.zip --ref=$HEAD_SHA
compiled-module-test: compiled-module-test:
name: module test name: module test
runs-on: ubuntu-latest runs-on: ubuntu-latest

1
.gitignore vendored
View File

@ -45,6 +45,7 @@ components/**/*.jsx
/.history /.history
*.tmp *.tmp
artifacts.zip artifacts.zip
oss-artifacts.zip
server/ server/
# Docs templates # Docs templates

View File

@ -49,7 +49,7 @@
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist", "build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
"changelog": "npm run lint:changelog && tsx scripts/print-changelog.ts", "changelog": "npm run lint:changelog && tsx scripts/print-changelog.ts",
"check-commit": "tsx scripts/check-commit.ts", "check-commit": "tsx scripts/check-commit.ts",
"clean": "antd-tools run clean && rm -rf es lib coverage locale dist report.html artifacts.zip", "clean": "antd-tools run clean && rm -rf es lib coverage locale dist report.html artifacts.zip oss-artifacts.zip",
"clean:lockfiles": "rm -rf package-lock.json yarn.lock", "clean:lockfiles": "rm -rf package-lock.json yarn.lock",
"precompile": "npm run prestart", "precompile": "npm run prestart",
"compile": "npm run clean && antd-tools run compile", "compile": "npm run clean && antd-tools run compile",
@ -221,6 +221,7 @@
"@types/react-highlight-words": "^0.16.7", "@types/react-highlight-words": "^0.16.7",
"@types/react-resizable": "^3.0.7", "@types/react-resizable": "^3.0.7",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@types/spinnies": "^0.5.3",
"@types/tar": "^6.1.12", "@types/tar": "^6.1.12",
"@types/throttle-debounce": "^5.0.2", "@types/throttle-debounce": "^5.0.2",
"@types/warning": "^3.0.3", "@types/warning": "^3.0.3",
@ -328,6 +329,7 @@
"sharp": "^0.33.3", "sharp": "^0.33.3",
"simple-git": "^3.24.0", "simple-git": "^3.24.0",
"size-limit": "^11.1.2", "size-limit": "^11.1.2",
"spinnies": "^0.5.1",
"stylelint": "^16.3.1", "stylelint": "^16.3.1",
"stylelint-config-rational-order": "^0.1.2", "stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^36.0.0", "stylelint-config-standard": "^36.0.0",

View File

@ -1,18 +1,65 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase, no-async-promise-executor */
import fs from 'node:fs'; import fs from 'node:fs';
import runScript from '@npmcli/run-script'; import runScript from '@npmcli/run-script';
import { Octokit } from '@octokit/rest'; import { Octokit } from '@octokit/rest';
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
import axios from 'axios'; import axios from 'axios';
import chalk from 'chalk'; import chalk from 'chalk';
import cliProgress from 'cli-progress'; import Spinnies from 'spinnies';
import ora from 'ora';
import checkRepo from './check-repo'; import checkRepo from './check-repo';
const { Notification: Notifier } = require('node-notifier'); const { Notification: Notifier } = require('node-notifier');
const simpleGit = require('simple-git'); const simpleGit = require('simple-git');
const blockStatus = ['failure', 'cancelled', 'timed_out'] as const;
const spinner = { interval: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] };
const spinnies = new Spinnies({ spinner });
let spinniesId = 0;
// `spinnies` 为按条目进度,需要做简单的封装变成接近 `ora` 的形态
const showMessage = (
message: string,
status?: 'succeed' | 'fail' | 'spinning' | 'non-spinnable' | 'stopped' | true,
uniqueTitle?: string,
) => {
if (!status) {
spinnies.add(`info-${spinniesId}`, {
text: message,
status: 'non-spinnable',
});
spinniesId += 1;
} else {
const mergedId = uniqueTitle || `msg-${spinniesId}`;
let mergedMessage = uniqueTitle ? `${uniqueTitle} ${message}` : message;
// `spinnies` 对中文支持有 bug长度会按中文一半计算。我们翻个倍修复一下。
mergedMessage = `${mergedMessage}${' '.repeat(mergedMessage.length)}`;
const existSpinner = spinnies.pick(mergedId);
if (!existSpinner) {
spinnies.add(mergedId, {
text: '',
});
}
if (status === 'succeed' || status === 'fail' || status === 'stopped') {
spinnies.update(mergedId, {
text: mergedMessage,
status,
});
spinniesId += 1;
} else {
spinnies.update(mergedId, {
text: mergedMessage,
status: status === true ? 'spinning' : status,
});
}
}
};
process.on('SIGINT', () => { process.on('SIGINT', () => {
process.exit(1); process.exit(1);
}); });
@ -38,136 +85,193 @@ const emojify = (status: string = '') => {
return `${emoji || ''} ${(status || '').padEnd(15)}`; return `${emoji || ''} ${(status || '').padEnd(15)}`;
}; };
async function downloadArtifact(url: string, filepath: string) { const toMB = (bytes: number) => (bytes / 1024 / 1024).toFixed(2);
const bar = new cliProgress.SingleBar(
{ async function downloadArtifact(msgKey: string, url: string, filepath: string, token?: string) {
format: ` 下载中 [${chalk.cyan( const headers: Record<string, string> = {};
'{bar}', if (token) {
)}] {percentage}% | : {eta}s | {value}/{total}`, headers.Authorization = `token ${token}`;
}, }
cliProgress.Presets.rect,
);
bar.start(1, 0);
const response = await axios.get(url, { const response = await axios.get(url, {
headers: { headers,
Authorization: `token ${process.env.GITHUB_ACCESS_TOKEN}`,
},
responseType: 'arraybuffer', responseType: 'arraybuffer',
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
bar.setTotal(progressEvent.total || 0); const { loaded, total = 0 } = progressEvent;
bar.update(progressEvent.loaded);
showMessage(
`下载进度 ${toMB(loaded)}MB/${toMB(total)}MB (${((loaded / total) * 100).toFixed(2)}%)`,
true,
msgKey,
);
}, },
}); });
fs.writeFileSync(filepath, Buffer.from(response.data)); fs.writeFileSync(filepath, Buffer.from(response.data));
return filepath;
} }
const runPrePublish = async () => { const runPrePublish = async () => {
await checkRepo(); await checkRepo();
const spinner = ora(); showMessage(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
spinner.info(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布'));
const git = simpleGit(); const git = simpleGit();
const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
const { current: currentBranch } = await git.branch(); const { current: currentBranch } = await git.branch();
spinner.start(`正在拉取远程分支 ${currentBranch}`); showMessage(`正在拉取远程分支 ${currentBranch}`, true);
await git.pull('origin', currentBranch); await git.pull('origin', currentBranch);
spinner.succeed(`成功拉取远程分支 ${currentBranch}`); showMessage(`成功拉取远程分支 ${currentBranch}`, 'succeed');
spinner.start(`正在推送本地分支 ${currentBranch}`); showMessage(`正在推送本地分支 ${currentBranch}`, true);
await git.push('origin', currentBranch); await git.push('origin', currentBranch);
spinner.succeed(`成功推送远程分支 ${currentBranch}`); showMessage(`成功推送远程分支 ${currentBranch}`, 'succeed');
spinner.succeed(`已经和远程分支保持同步 ${currentBranch}`); showMessage(`已经和远程分支保持同步 ${currentBranch}`, 'succeed');
const { latest } = await git.log(); const { latest } = await git.log();
spinner.succeed(`找到本地最新 commit:`); const sha = process.env.TARGET_SHA || latest.hash;
spinner.info(chalk.cyan(` hash: ${latest.hash}`));
spinner.info(chalk.cyan(` date: ${latest.date}`)); showMessage(`找到本地最新 commit:`, 'succeed');
spinner.info(chalk.cyan(` message: ${latest.message}`)); showMessage(chalk.cyan(` hash: ${sha}`));
spinner.info(chalk.cyan(` author_name: ${latest.author_name}`)); showMessage(chalk.cyan(` date: ${latest.date}`));
showMessage(chalk.cyan(` message: ${latest.message}`));
showMessage(chalk.cyan(` author_name: ${latest.author_name}`));
const owner = 'ant-design'; const owner = 'ant-design';
const repo = 'ant-design'; const repo = 'ant-design';
spinner.start(`开始检查远程分支 ${currentBranch} 的 CI 状态`); showMessage(`开始检查远程分支 ${currentBranch} 的 CI 状态`, true);
const failureUrlList: string[] = [];
const { const {
data: { check_runs }, data: { check_runs },
} = await octokit.checks.listForRef({ } = await octokit.checks.listForRef({
owner, owner,
repo, repo,
ref: latest.hash, ref: sha,
}); });
spinner.succeed(`远程分支 CI 状态:`); showMessage(`远程分支 CI 状态(${check_runs.length})`, 'succeed');
check_runs.forEach((run) => { check_runs.forEach((run) => {
spinner.info( showMessage(` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`);
` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`, if (blockStatus.some((status) => run.conclusion === status)) {
); failureUrlList.push(run.html_url!);
}
}); });
const conclusions = check_runs.map((run) => run.conclusion); const conclusions = check_runs.map((run) => run.conclusion);
if ( if (blockStatus.some((status) => conclusions.includes(status))) {
conclusions.includes('failure') || showMessage(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试'), 'fail');
conclusions.includes('cancelled') || showMessage(` 点此查看状态https://github.com/${owner}/${repo}/commit/${sha}`);
conclusions.includes('timed_out')
) { failureUrlList.forEach((url) => {
spinner.fail(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试')); showMessage(` - ${url}`);
spinner.info(` 点此查看状态https://github.com/${owner}/${repo}/commit/${latest.hash}`); });
process.exit(1); process.exit(1);
} }
const statuses = check_runs.map((run) => run.status); const statuses = check_runs.map((run) => run.status);
if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) { if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) {
spinner.fail(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试')); showMessage(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试'), 'fail');
spinner.info(` 点此查看状态https://github.com/${owner}/${repo}/commit/${latest.hash}`); showMessage(` 点此查看状态https://github.com/${owner}/${repo}/commit/${sha}`);
process.exit(1); process.exit(1);
} }
spinner.succeed(`远程分支 CI 已通过`); showMessage(`远程分支 CI 已通过`, 'succeed');
// clean up // clean up
await runScript({ event: 'clean', path: '.', stdio: 'inherit' }); await runScript({ event: 'clean', path: '.', stdio: 'inherit' });
spinner.succeed(`成功清理构建产物目录`); showMessage(`成功清理构建产物目录`, 'succeed');
spinner.start(`开始查找远程分支构建产物`);
const { // 从 github artifact 中下载产物
data: { workflow_runs }, const downloadArtifactPromise = Promise.resolve().then(async () => {
} = await octokit.rest.actions.listWorkflowRunsForRepo({ showMessage('开始查找远程分支构建产物', true, '[Github]');
owner,
repo, const {
head_sha: latest.hash, data: { workflow_runs },
per_page: 100, } = await octokit.rest.actions.listWorkflowRunsForRepo({
exclude_pull_requests: true, owner,
event: 'push', repo,
status: 'completed', head_sha: sha,
conclusion: 'success', per_page: 100,
head_branch: currentBranch, exclude_pull_requests: true,
event: 'push',
status: 'completed',
conclusion: 'success',
head_branch: currentBranch,
});
const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test');
if (!testWorkflowRun) {
throw new Error('找不到远程构建工作流');
}
const {
data: { artifacts },
} = await octokit.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: testWorkflowRun?.id || 0,
});
const artifact = artifacts.find((item) => item.name === 'build artifacts');
if (!artifact) {
throw new Error('找不到远程构建产物');
}
showMessage(`准备从远程分支下载构建产物`, true, '[Github]');
const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
owner,
repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
// 返回下载后的文件路径
return downloadArtifact('[Github]', url, 'artifacts.zip', process.env.GITHUB_ACCESS_TOKEN);
}); });
const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test'); downloadArtifactPromise
if (!testWorkflowRun) { .then(() => {
spinner.fail(chalk.bgRedBright('找不到远程构建工作流')); showMessage(`成功下载构建产物`, 'succeed', '[Github]');
})
.catch((e: Error) => {
showMessage(chalk.bgRedBright(e.message), 'fail', '[Github]');
});
// 从 OSS 下载产物
const downloadOSSPromise = Promise.resolve().then(async () => {
const url = `https://antd-visual-diff.oss-cn-shanghai.aliyuncs.com/${sha}/oss-artifacts.zip`;
showMessage(`准备从远程 OSS 下载构建产物`, true, '[OSS]');
// 返回下载后的文件路径
return downloadArtifact('[OSS]', url, 'oss-artifacts.zip');
});
downloadOSSPromise
.then(() => {
showMessage(`成功下载构建产物`, 'succeed', '[OSS]');
})
.catch((e: Error) => {
showMessage(chalk.bgRedBright(e.message), 'fail', '[OSS]');
});
// 任意一个完成,则完成
let firstArtifactFile: string;
try {
// @ts-ignore
firstArtifactFile = await Promise.any([downloadArtifactPromise, downloadOSSPromise]);
} catch (error) {
showMessage(
chalk.bgRedBright(`下载失败,请确认你当前 ${sha.slice(0, 6)} 位于 master 分支中`),
'fail',
);
process.exit(1); process.exit(1);
} }
const {
data: { artifacts }, showMessage(`成功从远程分支下载构建产物`, 'succeed');
} = await octokit.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: testWorkflowRun?.id || 0,
});
const artifact = artifacts.find((item) => item.name === 'build artifacts');
if (!artifact) {
spinner.fail(chalk.bgRedBright('找不到远程构建产物'));
process.exit(1);
}
spinner.info(`准备从远程分支下载构建产物`);
const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
owner,
repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await downloadArtifact(url, 'artifacts.zip');
spinner.info();
spinner.succeed(`成功从远程分支下载构建产物`);
// unzip // unzip
spinner.start(`正在解压构建产物`); showMessage(`正在解压构建产物`, true);
const zip = new AdmZip('artifacts.zip'); const zip = new AdmZip(firstArtifactFile);
zip.extractAllTo('./', true); zip.extractAllTo('./', true);
spinner.succeed(`成功解压构建产物`); showMessage(`成功解压构建产物`, 'succeed');
await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' }); await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' });
await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' }); await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' });
spinner.succeed(`文件检查通过,准备发布!`); showMessage(`文件检查通过,准备发布!`, 'succeed');
new Notifier().notify({ new Notifier().notify({
title: '✅ 准备发布到 npm', title: '✅ 准备发布到 npm',