Merge branch 'main' into next

This commit is contained in:
nocobase[bot] 2024-10-13 04:04:16 +00:00
commit e862b5a95e
15 changed files with 163 additions and 113 deletions

View File

@ -1,4 +1,4 @@
name: auto-merge name: Auto merge main -> next
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -13,17 +13,29 @@ jobs:
push-commit: push-commit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: nocobase/nocobase repository: nocobase/nocobase
ssh-key: ${{ secrets.NOCOBASE_DEPLOY_KEY }} token: ${{ steps.app-token.outputs.token }}
persist-credentials: true persist-credentials: true
fetch-depth: 0 fetch-depth: 0
- name: main -> next(nocobase) - name: main -> next(nocobase)
run: | run: |
git config --global user.email "actions@github.com" git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.name "GitHub Actions 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 checkout main
git pull origin main git pull origin main
git checkout next git checkout next

View File

@ -1,4 +1,4 @@
name: Build Docker Image name: Build docker image
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: Build Pro Image name: Build pro image
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -70,7 +70,7 @@ jobs:
- name: Run script - name: Run script
shell: bash shell: bash
run: | 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: env:
PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }}
GH_TOKEN: ${{ steps.app-token.outputs.token }} GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -1,4 +1,4 @@
name: deploy client docs name: Deploy client docs
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -25,30 +25,30 @@ jobs:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install
- name: Build zh-CN - name: Build zh-CN
run: yarn doc build core/client --lang=zh-CN run: yarn doc build core/client --lang=zh-CN
- name: Build en-US - name: Build en-US
run: yarn doc build core/client --lang=en-US run: yarn doc build core/client --lang=en-US
- name: Set tags - name: Set tags
id: set-tags id: set-tags
run: | run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "::set-output name=tags::${{ github.ref_name }}" echo "::set-output name=tags::${{ github.ref_name }}"
else else
echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}" echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}"
fi fi
- name: copy files via ssh - ${{ steps.set-tags.outputs.tags }} - name: copy files via ssh - ${{ steps.set-tags.outputs.tags }}
uses: appleboy/scp-action@v0.1.4 uses: appleboy/scp-action@v0.1.4
with: with:
host: ${{ secrets.CN_CLIENT_HOST }} host: ${{ secrets.CN_CLIENT_HOST }}
username: ${{ secrets.CN_CLIENT_USERNAME }} username: ${{ secrets.CN_CLIENT_USERNAME }}
key: ${{ secrets.CN_CLIENT_KEY }} key: ${{ secrets.CN_CLIENT_KEY }}
port: ${{ secrets.CN_CLIENT_PORT }} port: ${{ secrets.CN_CLIENT_PORT }}
source: "packages/core/client/dist/*" source: 'packages/core/client/dist/*'
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }} target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }}

View File

@ -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 }}

View File

@ -1,4 +1,4 @@
name: manual-build-pr-docker-image name: Manual build pr docker image
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: manual-build-pro-image name: Manual build pro image
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: Build Pro Plugin Docker Image name: Build pro plugin docker image
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: manual-e2e name: Manual e2e
on: on:
workflow_dispatch: workflow_dispatch:

View File

@ -1,4 +1,4 @@
name: manual-release name: Manual release
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: NocoBase Backend Test name: NocoBase backend test
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: NocoBase FrontEnd Test name: NocoBase frontEnd test
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -33,7 +33,7 @@ jobs:
frontend-test: frontend-test:
strategy: strategy:
matrix: matrix:
node_version: [ '18' ] node_version: ['18']
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: node:${{ matrix.node_version }} container: node:${{ matrix.node_version }}
steps: steps:

View File

@ -1,4 +1,4 @@
name: Release Next name: Release next
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -148,7 +148,7 @@ jobs:
shell: bash shell: bash
run: | run: |
git fetch 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: env:
PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }} PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }}
GH_TOKEN: ${{ steps.app-token.outputs.token }} GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -3,8 +3,15 @@ const fs = require('fs/promises');
const path = require('path'); const path = require('path');
const { Command } = require('commander'); const { Command } = require('commander');
const program = new Command(); 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); program.parse(process.argv);
const header = { const header = {
@ -99,7 +106,9 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
// gh pr view 5112 --json author,body,files // gh pr view 5112 --json author,body,files
let res; let res;
try { 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; res = stdout;
} catch (error) { } catch (error) {
console.error(`Get PR #${number} failed, error: ${error.message}`); console.error(`Get PR #${number} failed, error: ${error.message}`);
@ -110,7 +119,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
} }
return { number }; return { number };
} }
const { author, body, files, baseRefName } = JSON.parse(res); const { author, body, files, baseRefName, url } = JSON.parse(res);
if (ver === 'alpha' && baseRefName !== 'next') { if (ver === 'alpha' && baseRefName !== 'next') {
return { number }; return { number };
} }
@ -130,6 +139,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
author: author.login, author: author.login,
moduleType: name?.includes('plugin-') ? 'plugin' : 'core', moduleType: name?.includes('plugin-') ? 'plugin' : 'core',
module: name, module: name,
url,
en: { en: {
module: displayName || pkgName, module: displayName || pkgName,
description, description,
@ -187,17 +197,7 @@ function arrangeChangelogs(changelogs) {
return result; return result;
} }
async function collect() { async function collect(from, to) {
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}`);
const changelogs = []; const changelogs = [];
const get = async (changelogs, pkgType, cwd, pkg) => { const get = async (changelogs, pkgType, cwd, pkg) => {
const prs = await getPRList(from, to, cwd); 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() { async function generateChangelog(changelogs) {
const { changelogs, from, to } = await collect(); const { test } = program.opts();
const prTypeLocale = { const prTypeLocale = {
'New feature': { 'New feature': {
en: '🎉 New Features', en: '🎉 New Features',
@ -275,13 +275,13 @@ async function generateChangelog() {
const moduleResults = []; const moduleResults = [];
const lists = []; const lists = [];
for (const changelog of moduleChangelogs) { for (const changelog of moduleChangelogs) {
const { number, author, pro } = changelog; const { number, author, pro, url } = changelog;
const { description, docTitle, docLink } = changelog[lang]; const { description, docTitle, docLink } = changelog[lang];
if (!description) { if (!description) {
console.warn(`PR #${number} has no ${lang} changelog`); console.warn(`PR #${number} has no ${lang} changelog`);
continue; 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})` : ''; const doc = docTitle && docLink ? `${referenceLocale[lang]}[${docTitle}](${docLink})` : '';
lists.push(`${description}${pr} by @${author}\n${doc}`); lists.push(`${description}${pr} by @${author}\n${doc}`);
} }
@ -314,7 +314,7 @@ async function generateChangelog() {
const cn = generate(changelogs, 'cn'); const cn = generate(changelogs, 'cn');
const en = generate(changelogs, 'en'); const en = generate(changelogs, 'en');
return { cn, en, from, to }; return { cn, en };
} }
async function writeChangelog(cn, en, from, to) { async function writeChangelog(cn, en, from, to) {
@ -335,6 +335,13 @@ async function writeChangelog(cn, en, from, to) {
} }
async function createRelease(cn, en, 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(); let { ver = 'beta' } = program.opts();
// gh release create -t title -n note // gh release create -t title -n note
if (ver === 'alpha') { 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}`]); await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`]);
} }
async function writeChangelogAndCreateRelease() { async function getExistsChangelog(from, to) {
let { ver = 'beta', test } = program.opts(); const get = async (lang) => {
const { cn, en, from, to } = await generateChangelog(); const file = lang === 'cn' ? 'CHANGELOG.zh-CN.md' : 'CHANGELOG.md';
if (!cn && !en) { const oldChangelog = await fs.readFile(path.join(__dirname, `../../${file}`), 'utf8');
throw new Error('No changelog generated'); 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(`From: ${from}, To: ${to}`);
console.log(en); return { from, to };
console.log(cn); }
async function postCMS(title, content, contentCN) {
const { cmsToken, cmsURL } = program.opts();
if (!cmsToken || !cmsURL) {
console.error('No cmsToken or cmsURL provided');
return; 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 writeChangelog(cn, en, from, to);
} }
await createRelease(cn, en, to); await createRelease(cn, en, to);
await postCMS(to, en, cn);
} }
writeChangelogAndCreateRelease(); writeChangelogAndCreateRelease();