diff --git a/.eslintrc.js b/.eslintrc.js index a618b4d53e..d83981359c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -173,6 +173,7 @@ module.exports = { 'site/**', 'tests/**', 'scripts/**', + 'scripts/*.ts', '**/*.test.js', '**/__tests__/*', '*.config.js', diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index e2824bd41f..ca3bb37cfa 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -122,4 +122,4 @@ jobs: path: _site - name: run e2e test - run: npm run site:test + run: npm run test:site diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26979f6ad0..9242f3947b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,6 +51,7 @@ jobs: lint: runs-on: ubuntu-latest + needs: setup steps: - name: checkout uses: actions/checkout@v4 @@ -79,10 +80,10 @@ jobs: - name: lint:react-17 run: npm run compile && npm run install-react-17 && npm run tsc:old - needs: setup check_metadata: runs-on: ubuntu-latest + needs: setup steps: - name: checkout uses: actions/checkout@v4 @@ -102,44 +103,6 @@ jobs: with: path: node_modules key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - needs: setup - - ################################ Dist ################################ - dist: - name: dist - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: restore cache from package-lock.json - uses: actions/cache@v4 - with: - path: package-temp-dir - key: lock-${{ github.sha }} - - - name: restore cache from node_modules - uses: actions/cache@v4 - with: - path: node_modules - key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - - - name: cache dist - uses: actions/cache@v4 - with: - path: dist - key: dist-${{ github.sha }} - - - name: dist - run: npm run dist - env: - NODE_OPTIONS: "--max_old_space_size=4096" - CI: 1 - needs: setup ################################ Test ################################ normal-test: @@ -152,6 +115,7 @@ jobs: env: REACT: ${{ matrix.react }} runs-on: ubuntu-latest + needs: build steps: - name: checkout uses: actions/checkout@v4 @@ -222,7 +186,7 @@ jobs: # node test - name: node test if: ${{ matrix.module == 'node' }} - run: npm run test-node + run: npm run test:node # dist test - name: dist test @@ -237,13 +201,12 @@ jobs: run: npm test env: LIB_DIR: dist-min - needs: [setup, dist] ############################ Test Coverage ########################### upload-test-coverage: name: test-coverage runs-on: ubuntu-latest - needs: [normal-test] + needs: normal-test steps: - uses: actions/checkout@v4 @@ -268,8 +231,9 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} ########################### Compile & Test ########################### - compile: + build: runs-on: ubuntu-latest + needs: setup steps: - name: checkout uses: actions/checkout@v4 @@ -311,11 +275,32 @@ jobs: - name: check use client run: node ./tests/dekko/use-client.test.js - needs: setup + - name: cache dist + uses: actions/cache@v4 + with: + path: dist + key: dist-${{ github.sha }} + + - name: dist + run: npm run dist + env: + NODE_OPTIONS: "--max_old_space_size=4096" + CI: 1 + + - uses: actions/upload-artifact@v4 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + with: + name: build artifacts + path: | + dist + locale + es + lib compiled-module-test: name: module test runs-on: ubuntu-latest + needs: build strategy: matrix: react: ['16', '17', '18'] @@ -375,4 +360,3 @@ jobs: run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} env: LIB_DIR: ${{ matrix.module }} - needs: compile diff --git a/.github/workflows/visual-regression-diff-build.yml b/.github/workflows/visual-regression-diff-build.yml index 01212e76ef..5f25bb2815 100644 --- a/.github/workflows/visual-regression-diff-build.yml +++ b/.github/workflows/visual-regression-diff-build.yml @@ -80,7 +80,7 @@ jobs: run: | node node_modules/puppeteer/install.mjs npm run version - npm run test-image + npm run test:image env: NODE_OPTIONS: "--max_old_space_size=4096" @@ -91,7 +91,7 @@ jobs: EVENT_NUMBER: ${{ github.event.number }} BASE_REF: ${{ github.base_ref }} run: | - npm run visual-regression -- --pr-id=$EVENT_NUMBER --base-ref=$BASE_REF + npm run test:visual-regression -- --pr-id=$EVENT_NUMBER --base-ref=$BASE_REF # Upload report in `visualRegressionReport` - name: upload report artifact diff --git a/.github/workflows/visual-regression-persist-start.yml b/.github/workflows/visual-regression-persist-start.yml index 93ba2b87bb..ed83d1aba2 100644 --- a/.github/workflows/visual-regression-persist-start.yml +++ b/.github/workflows/visual-regression-persist-start.yml @@ -70,7 +70,7 @@ jobs: run: | node node_modules/puppeteer/install.mjs npm run version - npm run test-image + npm run test:image tar -czvf imageSnapshots.tar.gz imageSnapshots/* env: NODE_OPTIONS: "--max_old_space_size=4096" diff --git a/.gitignore b/.gitignore index 13e3ef1940..d3f4bd0b50 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ components/**/*.jsx !components/**/__tests__/**/*.js.snap /.history *.tmp +artifacts.zip server/ # Docs templates diff --git a/docs/react/contributing.en-US.md b/docs/react/contributing.en-US.md index 20980de91f..b2538ed334 100644 --- a/docs/react/contributing.en-US.md +++ b/docs/react/contributing.en-US.md @@ -55,7 +55,7 @@ The core team is monitoring for pull requests. We will review your pull request 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (npm run test). Tip: `npm test -- --watch TestName` is helpful in development. 5. Run `npm test -- -u` to update the [jest snapshots](https://jestjs.io/docs/snapshot-testing) and commit these changes as well (if there are any updates). -6. Ensure the UI change passes `npm run test-image`, Run `npm run test-image -- -u` to update UI snapshots and commit these changes as well (if there are any updates), **UI test base on [Docker](https://docs.docker.com/get-docker/), need download the corresponding installation according to the platform** +6. Ensure the UI change passes `npm run test:image`, Run `npm run test:image -- -u` to update UI snapshots and commit these changes as well (if there are any updates), **UI test base on [Docker](https://docs.docker.com/get-docker/), need download the corresponding installation according to the platform** 7. Make sure your code lints (npm run lint). Tip: Lint runs automatically when you `git commit` (Use [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)). 8. Finally, please make sure that all GitHub CI checks pass, if they fail, you can click `detail` to enter the details to view the reason. diff --git a/docs/react/contributing.zh-CN.md b/docs/react/contributing.zh-CN.md index 1cb8f26132..95a32c39fc 100644 --- a/docs/react/contributing.zh-CN.md +++ b/docs/react/contributing.zh-CN.md @@ -55,7 +55,7 @@ Ant Design 团队会关注所有的 pull request,我们会 review 以及合并 3. 如果你修复了一个 bug 或者新增了一个功能,请确保编写了相应的测试,这很重要。 4. 确认所有的测试都通过了 `npm run test`。小贴士:开发过程中可以用 `npm test -- --watch TestName` 来运行指定的测试。 5. 运行 `npm test -- -u` 来更新 [jest snapshot](https://jestjs.io/zh-Hans/docs/snapshot-testing) 并且把这些更新也提交上来(如果有的话)。 -6. 确认所有的 UI 改动通过 `npm run test-image`,可以运行 `npm run test-image -- -u` 更新 UI 快照并且把这些更新也提交上来(如果有的话),**UI 测试基于 [Docker](https://docs.docker.com/get-docker/),根据平台下载对应的安装程序。** +6. 确认所有的 UI 改动通过 `npm run test:image`,可以运行 `npm run test:image -- -u` 更新 UI 快照并且把这些更新也提交上来(如果有的话),**UI 测试基于 [Docker](https://docs.docker.com/get-docker/),根据平台下载对应的安装程序。** 7. 确保你的代码通过了 lint 检查 `npm run lint`。小贴士: Lint 会在你 `git commit` 的时候自动运行(通过[Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks))。 8. 最后请确保所有 GitHub CI 检查通过,如果失败,可点击 `detail` 进入详情查看原因。 diff --git a/package.json b/package.json index 19f6ea747c..4d6e9636f0 100644 --- a/package.json +++ b/package.json @@ -46,63 +46,64 @@ "scripts": { "api-collection": "antd-tools run api-collection", "authors": "tsx scripts/generate-authors.ts", - "biome:format": "biome format --write .", "build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist", - "changelog": "git fetch origin && tsx scripts/print-changelog.ts", + "changelog": "npm run lint:changelog && tsx scripts/print-changelog.ts", "check-commit": "tsx scripts/check-commit.ts", - "clean": "antd-tools run clean && rm -rf es lib coverage dist report.html", - "clean-lockfiles": "rm -rf package-lock.json yarn.lock", - "collect-token-statistic": "tsx scripts/collect-token-statistic.ts", + "clean": "antd-tools run clean && rm -rf es lib coverage locale dist report.html artifacts.zip", + "clean:lockfiles": "rm -rf package-lock.json yarn.lock", "precompile": "npm run prestart", "compile": "npm run clean && antd-tools run compile", - "component-changelog": "tsx scripts/generate-component-changelog.ts", - "predeploy": "antd-tools run clean && npm run site && cp CNAME _site && npm run site:test", + "predeploy": "antd-tools run clean && npm run site && cp CNAME _site && npm run test:site", "deploy": "gh-pages -d _site -b gh-pages -f", "deploy:china-mirror": "git checkout gh-pages && git pull origin gh-pages && git push git@gitee.com:ant-design/ant-design.git gh-pages -f", - "predist": "npm run version", + "predist": "npm run version && npm run token:statistic && npm run token:meta", "dist": "antd-tools run dist", "dist:esbuild": "ESBUILD=true npm run dist", "dist:esbuild-no-dup-check": "ESBUILD=true NO_DUP_CHECK=true npm run dist", + "format": "biome format --write .", "install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16 @testing-library/react@12", "install-react-17": "npm i --no-save --legacy-peer-deps react@17 react-dom@17 @testing-library/react@12", "install-react-18": "npm i --no-save --legacy-peer-deps react@18 react-dom@18", "prelint": "dumi setup", - "lint": "npm run version && npm run tsc && npm run lint:script && npm run lint:demo && npm run lint:md && npm run lint:style", - "lint:demo": "eslint components/*/demo/*.md", - "lint:deps": "antd-tools run deps-lint", - "lint:md": "remark . -f -q", - "lint:script": "npm run component-changelog && eslint . --ext .js,.jsx,.ts,.tsx --cache", - "lint:style": "tsx scripts/check-cssinjs.tsx", + "lint": "npm run version && npm run tsc && npm run lint:script && npm run lint:demo && npm run lint:md && npm run lint:style && npm run lint:changelog", "lint-fix": "npm run lint-fix:script && npm run lint-fix:demo", "lint-fix:demo": "npm run lint:demo -- --fix", "lint-fix:script": "npm run lint:script -- --fix", - "pre-publish": "npm run test-all -- --skip-build && tsx ./scripts/pre-publish-notice.ts", + "lint:changelog": "tsx scripts/generate-component-changelog.ts", + "lint:demo": "eslint components/*/demo/*.md", + "lint:deps": "antd-tools run deps-lint", + "lint:md": "remark . -f -q", + "lint:script": "eslint . --ext .js,.jsx,.ts,.tsx --cache", + "lint:style": "tsx scripts/check-cssinjs.tsx", + "npm-install": "npm install", "prepare": "is-ci || husky && dumi setup", - "prepublishOnly": "antd-tools run guard", + "prepublishOnly": "tsx ./scripts/pre-publish.ts", "prettier": "prettier -c --write **/* --cache", - "pub": "npm run version && npm run collect-token-statistic && npm run token-meta && antd-tools run pub", - "postpublish": "tsx scripts/post-script.ts", + "pub": "echo 'Please use `npm publish` instead.'", + "postpublish": "tsx scripts/post-publish.ts", "presite": "npm run prestart", "site": "dumi build && cp .surgeignore _site", - "site:test": "jest --config .jest.site.js", - "site:test:update": "npm run site && npm run site:test -- -u", "size-limit": "size-limit", - "sort": "npx sort-package-json", - "sort-api": "antd-tools run sort-api-table", - "prestart": "npm run version && npm run collect-token-statistic && npm run token-meta && npm run component-changelog", + "sort:api-table": "antd-tools run sort-api-table", + "sort:package-json": "npx sort-package-json", + "prestart": "npm run version && npm run token:statistic && npm run token:meta && npm run lint:changelog", "start": "cross-env PORT=8001 dumi dev", - "pretest": "npm run version && npm run component-changelog", + "pretest": "npm run version", "test": "jest --config .jest.js --no-cache", - "test-all": "sh -e ./scripts/test-all.sh", - "test-image": "jest --config .jest.image.js --no-cache -i -u --forceExit", - "test-node": "npm run version && jest --config .jest.node.js --no-cache", + "test:all": "sh -e ./scripts/test-all.sh", + "test:dekko": "node ./tests/dekko/index.test.js", + "test:image": "jest --config .jest.image.js --no-cache -i -u --forceExit", + "test:node": "npm run version && jest --config .jest.node.js --no-cache", + "test:package-diff": "antd-tools run package-diff", + "test:site": "jest --config .jest.site.js", + "test:site-update": "npm run site && npm run test:site -- -u", "test:update": "jest --config .jest.js --no-cache -u", - "token-meta": "tsx scripts/generate-token-meta.ts", + "test:visual-regression": "tsx scripts/visual-regression/build.ts", + "token:meta": "tsx scripts/generate-token-meta.ts", + "token:statistic": "tsx scripts/collect-token-statistic.ts", "tsc": "tsc --noEmit", "tsc:old": "tsc --noEmit -p tsconfig-old-react.json", - "version": "tsx scripts/generate-version.ts", - "visual-regression": "tsx scripts/visual-regression/build.ts", - "npm-install": "npm install" + "version": "tsx scripts/generate-version.ts" }, "lint-staged": { "*.{ts,tsx,js,jsx}": "biome format --write", @@ -180,6 +181,8 @@ "@emotion/server": "^11.11.0", "@ianvs/prettier-plugin-sort-imports": "^4.2.1", "@madccc/duplicate-package-checker-webpack-plugin": "^1.0.0", + "@npmcli/run-script": "^7.0.4", + "@octokit/rest": "^20.0.2", "@qixian.cs/github-contributors-list": "^2.0.1", "@size-limit/file": "^11.1.2", "@stackblitz/sdk": "^1.9.0", @@ -187,7 +190,9 @@ "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.2", "@testing-library/user-event": "^14.5.2", + "@types/adm-zip": "^0.5.5", "@types/ali-oss": "^6.16.11", + "@types/cli-progress": "^3.11.5", "@types/fs-extra": "^11.0.4", "@types/gtag.js": "^0.0.19", "@types/http-server": "^0.12.4", @@ -203,6 +208,7 @@ "@types/minimist": "^1.2.5", "@types/node": "^20.12.4", "@types/nprogress": "^0.2.3", + "@types/ora": "^3.2.0", "@types/pixelmatch": "^5.2.6", "@types/pngjs": "^6.0.4", "@types/prismjs": "^1.26.3", @@ -219,13 +225,16 @@ "@types/warning": "^3.0.3", "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", + "adm-zip": "^0.5.12", "ali-oss": "^6.20.0", "antd-img-crop": "^4.21.0", "antd-style": "^3.6.2", "antd-token-previewer": "^2.0.8", + "axios": "^1.6.8", "chalk": "^4.1.2", "cheerio": "1.0.0-rc.12", "circular-dependency-plugin": "^5.2.2", + "cli-progress": "^3.12.0", "cross-env": "^7.0.3", "cross-fetch": "^4.0.0", "crypto": "^1.0.1", @@ -277,9 +286,9 @@ "minimist": "^1.2.8", "mockdate": "^3.0.5", "node-fetch": "^3.3.2", - "node-notifier": "^10.0.1", "nprogress": "^0.2.0", "open": "^10.1.0", + "ora": "^8.0.1", "pixelmatch": "^5.3.0", "pngjs": "^7.0.0", "prettier": "^3.2.5", diff --git a/scripts/check-commit.ts b/scripts/check-commit.ts deleted file mode 100644 index 1e8773ec6b..0000000000 --- a/scripts/check-commit.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable no-console */ -import chalk from 'chalk'; -import fetch from 'isomorphic-fetch'; -import type { StatusResult } from 'simple-git'; -import simpleGit from 'simple-git'; -import localPackage from '../package.json'; - -const { version } = localPackage; - -const cwd = process.cwd(); -const git = simpleGit(cwd); - -function exitProcess(code = 1) { - console.log(''); // Keep an empty line here to make looks good~ - process.exit(code); -} - -async function checkVersion() { - try { - const { versions } = await fetch('http://registry.npmjs.org/antd').then((res: Response) => - res.json(), - ); - if (version in versions) { - console.log(chalk.yellow('😈 Current version already exists. Forget update package.json?')); - console.log(chalk.cyan(' => Current:'), version); - exitProcess(); - } - } catch { - console.log(chalk.red('🚨 Check version failed. Skip...')); - } -} - -async function checkBranch({ current }: StatusResult) { - if ( - version.includes('-alpha.') || - version.includes('-beta.') || - version.includes('-rc.') || - version.includes('-experimental.') - ) { - console.log(chalk.cyan('😃 Alpha version. Skip branch check.')); - } else if (current !== 'master' && current !== '4.0-prepare') { - console.log(chalk.yellow('🤔 You are not in the master branch!')); - exitProcess(); - } -} - -async function checkCommit({ files }: StatusResult) { - if (files.length) { - console.log(chalk.yellow('🙄 You forgot something to commit.')); - files.forEach(({ path: filePath, working_dir: mark }) => { - console.log(' -', chalk.red(mark), filePath); - }); - exitProcess(); - } -} - -async function checkRemote() { - try { - const { remote } = await git.fetch('origin', 'master'); - console.log(chalk.blue('⛳ Checking origin master with `git fetch origin master`')); - if (!remote?.includes('ant-design/ant-design')) { - console.log(chalk.blue('⛳ Checking locally with `git config --get remote.origin.url`')); - const { value } = await git.getConfig('remote.origin.url'); - if (!value?.includes('ant-design/ant-design')) { - console.log( - chalk.yellow('🧐 Your remote origin is not ant-design/ant-design, did you fork it?'), - ); - exitProcess(); - } - } - } catch { - console.log(chalk.red('🚨 Check remote failed. Skip...')); - } -} - -async function checkAll() { - const status = await git.status(); - - await checkVersion(); - - await checkBranch(status); - - await checkCommit(status); - - await checkRemote(); -} - -checkAll(); diff --git a/scripts/check-repo.ts b/scripts/check-repo.ts new file mode 100644 index 0000000000..b0d06aa37e --- /dev/null +++ b/scripts/check-repo.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import fetch from 'isomorphic-fetch'; +import simpleGit, { type StatusResult } from 'simple-git'; +import ora from 'ora'; +import localPackage from '../package.json'; + +const { version } = localPackage; + +const cwd = process.cwd(); +const git = simpleGit(cwd); +const spinner = ora('Loading unicorns').start('开始检查仓库状态'); + +function exitProcess(code = 1) { + console.log(''); // Keep an empty line here to make looks good~ + process.exit(code); +} + +async function checkVersion() { + spinner.start('正在检查当前版本是否已经存在'); + const { versions } = await fetch('http://registry.npmjs.org/antd').then((res: Response) => + res.json(), + ); + if (version in versions) { + spinner.fail(chalk.yellow('😈 Current version already exists. Forget update package.json?')); + spinner.info(`${chalk.cyan(' => Current:')}: version`); + exitProcess(); + } + spinner.succeed('版本检查通过'); +} + +async function checkBranch({ current }: StatusResult) { + spinner.start('正在检查当前分支是否合法'); + if ( + version.includes('-alpha.') || + version.includes('-beta.') || + version.includes('-rc.') || + version.includes('-experimental.') + ) { + spinner.info(chalk.cyan('😃 Alpha version. Skip branch check.')); + } else if (current !== 'master') { + spinner.fail(chalk.red('🤔 You are not in the master branch!')); + exitProcess(); + } + spinner.succeed('分支检查通过'); +} + +async function checkCommit({ files }: StatusResult) { + spinner.start('正在检查当前 git 状态'); + if (files.length) { + spinner.fail(chalk.red('🙄 You forgot something to commit.')); + files.forEach(({ path: filePath, working_dir: mark }) => { + console.log(' -', chalk.red(mark), filePath); + }); + exitProcess(); + } + spinner.succeed('git 状态检查通过'); +} + +async function checkRemote() { + spinner.start('正在检查远程分支'); + const { remote } = await git.fetch('origin', 'master'); + if (!remote?.includes('ant-design/ant-design')) { + const { value } = await git.getConfig('remote.origin.url'); + if (!value?.includes('ant-design/ant-design')) { + spinner.fail( + chalk.red('🧐 Your remote origin is not ant-design/ant-design, did you fork it?'), + ); + exitProcess(); + } + } + spinner.succeed('远程分支检查通过'); +} + +async function checkToken() { + if (!process.env.GITHUB_ACCESS_TOKEN) { + console.log( + spinner.fail( + chalk.red( + '🚨 请先设置 GITHUB_ACCESS_TOKEN 环境变量到本地,请不要泄露给任何在线页面: https://octokit.github.io/rest.js/v20#authentication', + ), + ), + ); + exitProcess(); + } + spinner.succeed('GITHUB_ACCESS_TOKEN 检查通过'); +} + +export default async function checkRepo() { + const status = await git.status(); + await checkVersion(); + await checkBranch(status); + await checkCommit(status); + await checkRemote(); + await checkToken(); +} diff --git a/scripts/post-script.ts b/scripts/post-publish.ts similarity index 100% rename from scripts/post-script.ts rename to scripts/post-publish.ts diff --git a/scripts/pre-publish-notice.ts b/scripts/pre-publish-notice.ts deleted file mode 100644 index 8f113e12d9..0000000000 --- a/scripts/pre-publish-notice.ts +++ /dev/null @@ -1,9 +0,0 @@ -const { Notification: Notifier } = require('node-notifier'); - -new Notifier().notify({ - title: '✅ 准备发布到 npm', - message: '测试用例执行完毕,快回来输入 npm 校验码了!', - sound: 'Crystal', -}); - -process.exit(0); diff --git a/scripts/pre-publish.ts b/scripts/pre-publish.ts new file mode 100644 index 0000000000..e1bbec5eb5 --- /dev/null +++ b/scripts/pre-publish.ts @@ -0,0 +1,167 @@ +/* eslint-disable camelcase */ +import fs from 'node:fs'; +import runScript from '@npmcli/run-script'; +import { Octokit } from '@octokit/rest'; +import ora from 'ora'; +import chalk from 'chalk'; +import AdmZip from 'adm-zip'; +import axios from 'axios'; +import cliProgress from 'cli-progress'; +import checkRepo from './check-repo'; + +const simpleGit = require('simple-git'); + +process.on('SIGINT', () => { + process.exit(1); +}); + +const emojify = (status: string = '') => { + if (!status) { + return ''; + } + const emoji = { + /* status */ + completed: '✅', + queued: '🕒', + in_progress: '⌛', + /* conclusion */ + success: '✅', + failure: '❌', + neutral: '⚪', + cancelled: '❌', + skipped: '⏭️', + timed_out: '⌛', + action_required: '🔴', + }[status]; + return `${emoji || ''} ${(status || '').padEnd(15)}`; +}; + +async function downloadArtifact(url: string, filepath: string) { + const bar = new cliProgress.SingleBar({ + format: ` 下载中 [${chalk.cyan('{bar}')}] {percentage}% | 预计还剩: {eta}s | {value}/{total}`, + }); + bar.start(1, 0); + const response = await axios.get(url, { + headers: { + Authorization: `token ${process.env.GITHUB_ACCESS_TOKEN}`, + }, + responseType: 'arraybuffer', + onDownloadProgress: (progressEvent) => { + bar.setTotal(progressEvent.total || 0); + bar.update(progressEvent.loaded); + }, + }); + fs.writeFileSync(filepath, Buffer.from(response.data)); +} + +const runPrePublish = async () => { + await checkRepo(); + const spinner = ora('Loading unicorns').start(); + spinner.info(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查,远程 CI 通过后方可发布')); + const git = simpleGit(); + const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); + const { current: currentBranch } = await git.branch(); + + spinner.start(`正在拉取远程分支 ${currentBranch}`); + await git.pull('origin', currentBranch); + spinner.succeed(`成功拉取远程分支 ${currentBranch}`); + spinner.start(`正在推送本地分支 ${currentBranch}`); + await git.push('origin', currentBranch); + spinner.succeed(`成功推送远程分支 ${currentBranch}`); + spinner.succeed(`已经和远程分支保持同步 ${currentBranch}`); + + const { latest } = await git.log(); + spinner.succeed(`找到本地最新 commit:`); + spinner.info(chalk.cyan(` hash: ${latest.hash}`)); + spinner.info(chalk.cyan(` date: ${latest.date}`)); + spinner.info(chalk.cyan(` message: ${latest.message}`)); + spinner.info(chalk.cyan(` author_name: ${latest.author_name}`)); + const owner = 'ant-design'; + const repo = 'ant-design'; + spinner.start(`开始检查远程分支 ${currentBranch} 的 CI 状态`); + const { + data: { check_runs }, + } = await octokit.checks.listForRef({ + owner, + repo, + ref: latest.hash, + }); + spinner.succeed(`远程分支 CI 状态:`); + check_runs.forEach((run) => { + spinner.info( + ` ${run.name.padEnd(36)} ${emojify(run.status)} ${emojify(run.conclusion || '')}`, + ); + }); + const conclusions = check_runs.map((run) => run.conclusion); + if ( + conclusions.includes('failure') || + conclusions.includes('cancelled') || + conclusions.includes('timed_out') + ) { + spinner.fail(chalk.bgRedBright('远程分支 CI 执行异常,无法继续发布,请尝试修复或重试')); + spinner.info(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${latest.hash}`); + process.exit(1); + } + const statuses = check_runs.map((run) => run.status); + if (check_runs.length < 1 || statuses.includes('queued') || statuses.includes('in_progress')) { + spinner.fail(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试')); + spinner.info(` 点此查看状态:https://github.com/${owner}/${repo}/commit/${latest.hash}`); + process.exit(1); + } + spinner.succeed(`远程分支 CI 已通过`); + // clean up + await runScript({ event: 'clean', path: '.', stdio: 'inherit' }); + spinner.succeed(`成功清理构建产物目录`); + spinner.start(`开始查找远程分支构建产物`); + const { + data: { workflow_runs }, + } = await octokit.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + head_sha: latest.hash, + per_page: 100, + exclude_pull_requests: true, + event: 'push', + status: 'completed', + conclusion: 'success', + head_branch: currentBranch, + }); + const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test'); + if (!testWorkflowRun) { + spinner.fail(chalk.bgRedBright('找不到远程构建工作流')); + process.exit(1); + } + 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) { + 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 + spinner.start(`正在解压构建产物`); + const zip = new AdmZip('artifacts.zip'); + zip.extractAllTo('./', true); + spinner.succeed(`成功解压构建产物`); + await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' }); + await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' }); + spinner.succeed(`文件检查通过,准备发布!`); + process.exit(0); +}; + +runPrePublish(); diff --git a/scripts/test-all.sh b/scripts/test-all.sh index d82a864a8b..3f0adc8e49 100755 --- a/scripts/test-all.sh +++ b/scripts/test-all.sh @@ -110,7 +110,7 @@ fi if ! has_arg '--skip-node' "$@"; then echo "[TEST ALL] test node" echo "[TEST ALL] test node" > ~test-all.txt - npm run test-node -- --bail + npm run test:node -- --bail else echo "[TEST ALL] test node...skip" fi diff --git a/tests/dekko/dist.test.js b/tests/dekko/dist.test.js index 96a9fef994..f6c7d6ece3 100644 --- a/tests/dekko/dist.test.js +++ b/tests/dekko/dist.test.js @@ -4,9 +4,13 @@ const chalk = require('chalk'); $('dist') .isDirectory() .hasFile('antd-with-locales.js') + .hasFile('antd-with-locales.js.map') .hasFile('antd-with-locales.min.js') + .hasFile('antd-with-locales.min.js.map') .hasFile('antd.js') + .hasFile('antd.js.map') .hasFile('antd.min.js') + .hasFile('antd.min.js.map') .hasFile('reset.css'); // eslint-disable-next-line no-console diff --git a/tests/dekko/index.test.js b/tests/dekko/index.test.js new file mode 100644 index 0000000000..46e8f5a3a6 --- /dev/null +++ b/tests/dekko/index.test.js @@ -0,0 +1,3 @@ +require('./dist.test'); +require('./lib.test'); +require('./use-client.test'); diff --git a/typings/custom-typings.d.ts b/typings/custom-typings.d.ts index 948ae3426c..26a324d4f6 100644 --- a/typings/custom-typings.d.ts +++ b/typings/custom-typings.d.ts @@ -17,3 +17,9 @@ declare module '*.json' { export const version: string; export default value; } + +declare module '@npmcli/run-script' { + export default function runScript(options: { + [key: string]: string | string[] | boolean | NodeJS.ProcessEnv; + }): Promise; +}