diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 19001011aa..57ffea1333 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -1,4 +1,4 @@ -name: auto-merge +name: Auto merge main -> next concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -13,17 +13,29 @@ jobs: push-commit: runs-on: ubuntu-latest steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Checkout uses: actions/checkout@v4 with: repository: nocobase/nocobase - ssh-key: ${{ secrets.NOCOBASE_DEPLOY_KEY }} + token: ${{ steps.app-token.outputs.token }} persist-credentials: true fetch-depth: 0 - name: main -> next(nocobase) run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions Bot" + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' git checkout main git pull origin main git checkout next diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 80eced7754..faa8574b39 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -1,4 +1,4 @@ -name: Build Docker Image +name: Build docker image concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/build-pro-image.yml b/.github/workflows/build-pro-image.yml index 0600375667..964a26dd84 100644 --- a/.github/workflows/build-pro-image.yml +++ b/.github/workflows/build-pro-image.yml @@ -1,4 +1,4 @@ -name: Build Pro Image +name: Build pro image concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/changelog-and-release.yml b/.github/workflows/changelog-and-release.yml index 7b9b6f2f40..ed7d78c231 100644 --- a/.github/workflows/changelog-and-release.yml +++ b/.github/workflows/changelog-and-release.yml @@ -70,7 +70,7 @@ jobs: - name: Run script shell: bash run: | - node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} + node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }} env: PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/deploy-client-docs.yml b/.github/workflows/deploy-client-docs.yml index 8918589bf0..780b48031d 100644 --- a/.github/workflows/deploy-client-docs.yml +++ b/.github/workflows/deploy-client-docs.yml @@ -1,4 +1,4 @@ -name: deploy client docs +name: Deploy client docs concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,30 +25,30 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: 'yarn' - - run: yarn install - - name: Build zh-CN - run: yarn doc build core/client --lang=zh-CN - - name: Build en-US - run: yarn doc build core/client --lang=en-US - - name: Set tags - id: set-tags - run: | - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "::set-output name=tags::${{ github.ref_name }}" - else - echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}" - fi - - name: copy files via ssh - ${{ steps.set-tags.outputs.tags }} - uses: appleboy/scp-action@v0.1.4 - with: - host: ${{ secrets.CN_CLIENT_HOST }} - username: ${{ secrets.CN_CLIENT_USERNAME }} - key: ${{ secrets.CN_CLIENT_KEY }} - port: ${{ secrets.CN_CLIENT_PORT }} - source: "packages/core/client/dist/*" - target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }} + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'yarn' + - run: yarn install + - name: Build zh-CN + run: yarn doc build core/client --lang=zh-CN + - name: Build en-US + run: yarn doc build core/client --lang=en-US + - name: Set tags + id: set-tags + run: | + if [[ "${{ github.ref_name }}" == "main" ]]; then + echo "::set-output name=tags::${{ github.ref_name }}" + else + echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}" + fi + - name: copy files via ssh - ${{ steps.set-tags.outputs.tags }} + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{ secrets.CN_CLIENT_HOST }} + username: ${{ secrets.CN_CLIENT_USERNAME }} + key: ${{ secrets.CN_CLIENT_KEY }} + port: ${{ secrets.CN_CLIENT_PORT }} + source: 'packages/core/client/dist/*' + target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }} diff --git a/.github/workflows/get-nocobase-app-token.yml b/.github/workflows/get-nocobase-app-token.yml deleted file mode 100644 index 1c3b51dc97..0000000000 --- a/.github/workflows/get-nocobase-app-token.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Get nocobase app github token - -on: - workflow_call: - outputs: - token: - value: ${{ jobs.get-app-token.outputs.token }} - user-id: - value: ${{ jobs.get-app-token.outputs.user-id }} - app-slug: - value: ${{ jobs.get-app-token.outputs.app-slug }} - -jobs: - get-app-token: - runs-on: ubuntu-latest - outputs: - token: ${{ steps.encrypt-token.outputs.token }} - app-slug: ${{ steps.app-token.outputs.app-slug }} - user-id: ${{ steps.get-user-id.outputs.user-id }} - steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ vars.NOCOBASE_APP_ID }} - private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} - repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }} - skip-token-revoke: true - - name: Encrypt token - id: encrypt-token - shell: bash - run: | - APP_TOKEN=${{ steps.app-token.outputs.token }}; - ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}" | base64 -w 0); - echo "token=$ENCRYPTED_SECRET" >> $GITHUB_OUTPUT - - name: Get GitHub App User ID - id: get-user-id - run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/manual-build-pr-docker-image.yml b/.github/workflows/manual-build-pr-docker-image.yml index 1548b0a6db..c5bf6ba271 100644 --- a/.github/workflows/manual-build-pr-docker-image.yml +++ b/.github/workflows/manual-build-pr-docker-image.yml @@ -1,4 +1,4 @@ -name: manual-build-pr-docker-image +name: Manual build pr docker image concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/manual-build-pro-image.yml b/.github/workflows/manual-build-pro-image.yml index a66790d515..1192c443d6 100644 --- a/.github/workflows/manual-build-pro-image.yml +++ b/.github/workflows/manual-build-pro-image.yml @@ -1,4 +1,4 @@ -name: manual-build-pro-image +name: Manual build pro image concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/manual-build-pro-plugin-image.yml b/.github/workflows/manual-build-pro-plugin-image.yml index 89b55d2cfc..3b87bcaf75 100644 --- a/.github/workflows/manual-build-pro-plugin-image.yml +++ b/.github/workflows/manual-build-pro-plugin-image.yml @@ -1,4 +1,4 @@ -name: Build Pro Plugin Docker Image +name: Build pro plugin docker image concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/manual-e2e.yml b/.github/workflows/manual-e2e.yml index f72aee3db4..61a5bb4d39 100644 --- a/.github/workflows/manual-e2e.yml +++ b/.github/workflows/manual-e2e.yml @@ -1,4 +1,4 @@ -name: manual-e2e +name: Manual e2e on: workflow_dispatch: diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 51cf9efa6b..218c17f083 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -1,4 +1,4 @@ -name: manual-release +name: Manual release concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/nocobase-test-backend.yml b/.github/workflows/nocobase-test-backend.yml index 7bd6f7915e..d9e1ceee4e 100644 --- a/.github/workflows/nocobase-test-backend.yml +++ b/.github/workflows/nocobase-test-backend.yml @@ -1,4 +1,4 @@ -name: NocoBase Backend Test +name: NocoBase backend test concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/nocobase-test-frontend.yml b/.github/workflows/nocobase-test-frontend.yml index 0f74eee596..d08413e015 100644 --- a/.github/workflows/nocobase-test-frontend.yml +++ b/.github/workflows/nocobase-test-frontend.yml @@ -1,4 +1,4 @@ -name: NocoBase FrontEnd Test +name: NocoBase frontEnd test concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -33,7 +33,7 @@ jobs: frontend-test: strategy: matrix: - node_version: [ '18' ] + node_version: ['18'] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} steps: diff --git a/.github/workflows/release-next.yml b/.github/workflows/release-next.yml index da0cf14f7f..240075a57f 100644 --- a/.github/workflows/release-next.yml +++ b/.github/workflows/release-next.yml @@ -1,4 +1,4 @@ -name: Release Next +name: Release next concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -148,7 +148,7 @@ jobs: shell: bash run: | git fetch - node scripts/release/changelogAndRelease.js --ver alpha + node scripts/release/changelogAndRelease.js --ver alpha --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }} env: PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }} GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/scripts/release/changelogAndRelease.js b/scripts/release/changelogAndRelease.js index 74bf98a8e0..1552d99e51 100644 --- a/scripts/release/changelogAndRelease.js +++ b/scripts/release/changelogAndRelease.js @@ -3,8 +3,15 @@ const fs = require('fs/promises'); const path = require('path'); const { Command } = require('commander'); const program = new Command(); +const axios = require('axios'); -program.option('-f, --from [from]').option('-t, --to [to]').option('-v, --ver [ver]', '', 'beta').option('--test'); +program + .option('-f, --from [from]') + .option('-t, --to [to]') + .option('-v, --ver [ver]', '', 'beta') + .option('--test') + .option('--cmsURL [url]') + .option('--cmsToken [token]'); program.parse(process.argv); const header = { @@ -99,7 +106,9 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) { // gh pr view 5112 --json author,body,files let res; try { - const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName'], { cwd }); + const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName,url'], { + cwd, + }); res = stdout; } catch (error) { console.error(`Get PR #${number} failed, error: ${error.message}`); @@ -110,7 +119,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) { } return { number }; } - const { author, body, files, baseRefName } = JSON.parse(res); + const { author, body, files, baseRefName, url } = JSON.parse(res); if (ver === 'alpha' && baseRefName !== 'next') { return { number }; } @@ -130,6 +139,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) { author: author.login, moduleType: name?.includes('plugin-') ? 'plugin' : 'core', module: name, + url, en: { module: displayName || pkgName, description, @@ -187,17 +197,7 @@ function arrangeChangelogs(changelogs) { return result; } -async function collect() { - let { from, to, ver = 'beta' } = program.opts(); - if (!from || !to) { - // git tag -l --sort=version:refname | grep "v*-ver" | tail -2 - const tagPattern = `v*-${ver}`; - const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, { - shell: true, - }); - [from, to] = tags.split('\n'); - } - console.log(`From: ${from}, To: ${to}`); +async function collect(from, to) { const changelogs = []; const get = async (changelogs, pkgType, cwd, pkg) => { const prs = await getPRList(from, to, cwd); @@ -231,11 +231,11 @@ async function collect() { } } } - return { changelogs: arrangeChangelogs(changelogs), from, to }; + return { changelogs: arrangeChangelogs(changelogs) }; } -async function generateChangelog() { - const { changelogs, from, to } = await collect(); +async function generateChangelog(changelogs) { + const { test } = program.opts(); const prTypeLocale = { 'New feature': { en: '🎉 New Features', @@ -275,13 +275,13 @@ async function generateChangelog() { const moduleResults = []; const lists = []; for (const changelog of moduleChangelogs) { - const { number, author, pro } = changelog; + const { number, author, pro, url } = changelog; const { description, docTitle, docLink } = changelog[lang]; if (!description) { console.warn(`PR #${number} has no ${lang} changelog`); continue; } - const pr = pro ? '' : ` ([#${number}](https://github.com/nocobase/nocobase/pull/${number}))`; + const pr = pro && !test ? '' : ` ([#${number}](${url}))`; const doc = docTitle && docLink ? `${referenceLocale[lang]}[${docTitle}](${docLink})` : ''; lists.push(`${description}${pr} by @${author}\n${doc}`); } @@ -314,7 +314,7 @@ async function generateChangelog() { const cn = generate(changelogs, 'cn'); const en = generate(changelogs, 'en'); - return { cn, en, from, to }; + return { cn, en }; } async function writeChangelog(cn, en, from, to) { @@ -335,6 +335,13 @@ async function writeChangelog(cn, en, from, to) { } async function createRelease(cn, en, to) { + const { stdout } = await execa('gh', ['release', 'list', '--json', 'tagName']); + const releases = JSON.parse(stdout); + const tags = releases.map((release) => release.tagName); + if (tags.includes(to)) { + console.log(`Release ${to} already exists`); + return; + } let { ver = 'beta' } = program.opts(); // gh release create -t title -n note if (ver === 'alpha') { @@ -344,21 +351,91 @@ async function createRelease(cn, en, to) { await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`]); } -async function writeChangelogAndCreateRelease() { - let { ver = 'beta', test } = program.opts(); - const { cn, en, from, to } = await generateChangelog(); - if (!cn && !en) { - throw new Error('No changelog generated'); +async function getExistsChangelog(from, to) { + const get = async (lang) => { + const file = lang === 'cn' ? 'CHANGELOG.zh-CN.md' : 'CHANGELOG.md'; + const oldChangelog = await fs.readFile(path.join(__dirname, `../../${file}`), 'utf8'); + if (!oldChangelog.includes(`## [${to}]`)) { + return null; + } + const fromIndex = oldChangelog.indexOf(`## [${from}]`); + const toIndex = oldChangelog.indexOf(`## [${to}]`); + return oldChangelog.slice(toIndex, fromIndex); + }; + const cn = await get('cn'); + const en = await get('en'); + return { cn, en }; +} + +async function getVersion() { + let { from, to, ver = 'beta' } = program.opts(); + if (!from || !to) { + // git tag -l --sort=version:refname | grep "v*-ver" | tail -2 + const tagPattern = `v*-${ver}`; + const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, { + shell: true, + }); + [from, to] = tags.split('\n'); } - if (test) { - console.log(en); - console.log(cn); + console.log(`From: ${from}, To: ${to}`); + return { from, to }; +} + +async function postCMS(title, content, contentCN) { + const { cmsToken, cmsURL } = program.opts(); + if (!cmsToken || !cmsURL) { + console.error('No cmsToken or cmsURL provided'); return; } - if (ver === 'beta') { + await axios.request({ + method: 'post', + url: `${cmsURL}/api/articles:updateOrCreate`, + headers: { + Authorization: `Bearer ${cmsToken}`, + }, + params: { + filterKeys: ['title'], + }, + data: { + title, + title_cn: title, + content, + content_cn: contentCN, + tags: [4], + status: 'drafted', + author: 'nocobase [bot]', + }, + }); +} + +async function writeChangelogAndCreateRelease() { + let { ver = 'beta', test } = program.opts(); + const { from, to } = await getVersion(); + let { cn, en } = await getExistsChangelog(from, to); + let exists = false; + if (cn || en) { + exists = true; + console.log('Changelog already exists'); + } else { + const { changelogs } = await collect(from, to); + const c = await generateChangelog(changelogs); + cn = c.cn; + en = c.en; + if (!cn && !en) { + console.error('No changelog generated'); + return; + } + } + console.log(en); + console.log(cn); + if (test) { + return; + } + if (ver === 'beta' && !exists) { await writeChangelog(cn, en, from, to); } await createRelease(cn, en, to); + await postCMS(to, en, cn); } writeChangelogAndCreateRelease();