Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions .github/workflows/deploy-docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
name: Docs Release

on:
workflow_dispatch:
inputs:
line:
description: 'Docs line (v1 or v2)'
required: true
type: choice
options:
- v1
- v2
version:
description: 'Release tag (e.g. v2.0.0-beta.5)'
required: true
type: string
source_ref:
description: 'Git ref to build (e.g. refs/tags/v2.0.0-beta.5)'
required: true
type: string
source_sha:
description: 'Release commit SHA (optional)'
required: false
type: string
is_prerelease:
description: 'true for prerelease/beta'
required: true
default: 'true'
type: string
switch_current:
description: 'Switch current symlink after upload'
required: true
default: 'false'
type: string
update_root_redirect:
description: 'Update root redirect when release should become default'
required: true
default: 'false'
type: string

concurrency:
group: docs-release-${{ inputs.version }}
cancel-in-progress: true
Comment on lines +41 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reconsider cancel-in-progress: true for release workflows.

Canceling an in-progress release could leave the deployment in a partial state (e.g., files uploaded but symlink not switched). For release workflows, cancel-in-progress: false is safer to ensure each release completes atomically.

Suggested fix
 concurrency:
   group: docs-release-${{ inputs.version }}
-  cancel-in-progress: true
+  cancel-in-progress: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-docs.yaml around lines 41 - 43, The workflow
currently sets concurrency with cancel-in-progress: true which can abort an
ongoing release and leave deployments partial; update the concurrency block (the
group: docs-release-${{ inputs.version }} / cancel-in-progress setting) to use
cancel-in-progress: false (or remove the true value) so that an initiated
release is allowed to finish before a new one starts, ensuring atomic completion
of the release job.


permissions:
contents: read

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Validate line/version
run: |
if [[ "${{ inputs.line }}" == "v1" ]]; then
[[ "${{ inputs.version }}" =~ ^v1\. ]] || { echo "v1 must use v1.* tag"; exit 1; }
else
[[ "${{ inputs.version }}" =~ ^v2\. ]] || { echo "v2 must use v2.* tag"; exit 1; }
fi

- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_ref }}

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: |
npm i -g pnpm
pnpm install

- name: Resolve docs env
id: docs_env
run: |
if [[ "${{ inputs.line }}" == "v2" ]]; then
echo "PUBLIC_ORIGIN=https://v2.element-plus-x.com" >> $GITHUB_OUTPUT
if [[ "${{ inputs.is_prerelease }}" == "true" ]]; then
echo "VERSION_LABEL=v2.x (Beta)" >> $GITHUB_OUTPUT
else
echo "VERSION_LABEL=v2.x" >> $GITHUB_OUTPUT
fi
else
echo "PUBLIC_ORIGIN=https://v1.element-plus-x.com" >> $GITHUB_OUTPUT
echo "VERSION_LABEL=v1.x" >> $GITHUB_OUTPUT
fi

- name: Build docs
env:
DOCS_LINE: ${{ inputs.line }}
DOCS_PUBLIC_ORIGIN: ${{ steps.docs_env.outputs.PUBLIC_ORIGIN }}
DOCS_VERSION_LABEL: ${{ steps.docs_env.outputs.VERSION_LABEL }}
DOCS_V1_ORIGIN: https://v1.element-plus-x.com
DOCS_V2_ORIGIN: https://v2.element-plus-x.com
DOCS_ROOT_ORIGIN: https://element-plus-x.com
DOCS_USE_SOURCE: 'true'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run build:docs

- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DOCS_DEPLOY_SSH_KEY }}

- name: Add deploy host to known_hosts
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
run: |
mkdir -p ~/.ssh
PORT="${DOCS_DEPLOY_PORT:-22}"
ssh-keyscan -p "$PORT" -H "$DOCS_DEPLOY_HOST" >> ~/.ssh/known_hosts

- name: Upload release
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
DOCS_DEPLOY_BASE_DIR: ${{ secrets.DOCS_DEPLOY_BASE_DIR }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
DOCS_BASE="${DOCS_DEPLOY_BASE_DIR}/${{ inputs.line }}"
RELEASE_DIR="${DOCS_BASE}/releases/${{ inputs.version }}"

ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "mkdir -p '$RELEASE_DIR'"
rsync -az --delete -e "ssh -p $PORT" apps/docs/.vitepress/dist/ "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST:$RELEASE_DIR/"
echo "Uploaded release to $RELEASE_DIR"

- name: Switch current symlink
if: inputs.switch_current == 'true'
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
DOCS_DEPLOY_BASE_DIR: ${{ secrets.DOCS_DEPLOY_BASE_DIR }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
DOCS_BASE="${DOCS_DEPLOY_BASE_DIR}/${{ inputs.line }}"
RELEASE_DIR="${DOCS_BASE}/releases/${{ inputs.version }}"

ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "ln -sfn '$RELEASE_DIR' '$DOCS_BASE/current'"

- name: Update root redirect
if: inputs.update_root_redirect == 'true'
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
DOCS_DEPLOY_BASE_DIR: ${{ secrets.DOCS_DEPLOY_BASE_DIR }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
ROOT_REDIRECT="${DOCS_DEPLOY_BASE_DIR}/root/redirect.conf"

if [[ "${{ inputs.line }}" == "v1" ]]; then
ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "mkdir -p '${DOCS_DEPLOY_BASE_DIR}/root' && echo 'set \\$root_target https://v1.element-plus-x.com;' > '$ROOT_REDIRECT'"
elif [[ "${{ inputs.line }}" == "v2" && "${{ inputs.is_prerelease }}" != "true" ]]; then
ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "mkdir -p '${DOCS_DEPLOY_BASE_DIR}/root' && echo 'set \\$root_target https://v2.element-plus-x.com;' > '$ROOT_REDIRECT'"
else
echo "Skipping root redirect update for prerelease line=${{ inputs.line }}"
fi

- name: Reload nginx
if: inputs.switch_current == 'true' || inputs.update_root_redirect == 'true'
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "nginx -t && systemctl reload nginx"
Comment on lines +171 to +173
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

nginx -t and systemctl reload nginx likely require sudo.

These commands typically require root privileges. Unless the deploy user has passwordless sudo configured (which should be documented), this step will fail.

Suggested fix
          PORT="${DOCS_DEPLOY_PORT:-22}"
-         ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "nginx -t && systemctl reload nginx"
+         ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "sudo nginx -t && sudo systemctl reload nginx"

Also consider documenting in scripts/docs/README.md that the deploy user needs passwordless sudo for nginx commands:

echo "deploy_user ALL=(ALL) NOPASSWD: /usr/sbin/nginx, /bin/systemctl reload nginx" | sudo tee /etc/sudoers.d/docs-deploy
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-docs.yaml around lines 171 - 173, The deploy step
runs "nginx -t" and "systemctl reload nginx" as the deploy user which normally
requires root; update the workflow to invoke these commands with sudo (e.g., use
"sudo nginx -t" and "sudo systemctl reload nginx") or ensure the
DOCS_DEPLOY_USER has passwordless sudo for those binaries, and add a note to
scripts/docs/README.md documenting the required sudoers entry and environment
variables (DOCS_DEPLOY_USER, DOCS_DEPLOY_HOST, DOCS_DEPLOY_PORT) so the
deployment will succeed without interactive password prompts.

130 changes: 130 additions & 0 deletions .github/workflows/docs-deploy-verify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Docs Deploy Verify

on:
workflow_dispatch:
inputs:
line:
description: 'Docs line (v1 or v2)'
required: true
default: 'v2'
type: choice
options:
- v1
- v2
source_ref:
description: 'Git ref to build (branch or tag)'
required: true
default: 'refs/heads/updata-2602'
type: string
Comment on lines +14 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in default source_ref value.

The default value 'refs/heads/updata-2602' appears to contain a typo—likely intended to be 'refs/heads/update-2602' or another valid branch name. Consider using a more stable default like 'refs/heads/main' or removing the default entirely since source_ref is required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docs-deploy-verify.yaml around lines 14 - 18, The workflow
input `source_ref` has a typo in its default value ('refs/heads/updata-2602');
update the default for the `source_ref` input to a correct branch name (e.g.,
'refs/heads/update-2602' or a stable branch like 'refs/heads/main') or remove
the default since `source_ref` is required; locate the `source_ref` block in the
docs-deploy-verify workflow and change the `default` field accordingly to the
intended ref.

verify_id:
description: 'Verify directory suffix (defaults to run id)'
required: false
type: string

concurrency:
group: docs-deploy-verify-${{ github.ref }}-${{ inputs.line }}
cancel-in-progress: false

permissions:
contents: read

jobs:
build-and-upload-verify:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_ref }}

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: |
npm i -g pnpm
pnpm install

- name: Resolve docs env
id: docs_env
run: |
if [[ "${{ inputs.line }}" == "v2" ]]; then
echo "PUBLIC_ORIGIN=https://v2.element-plus-x.com" >> $GITHUB_OUTPUT
echo "VERSION_LABEL=v2.x (Verify)" >> $GITHUB_OUTPUT
else
echo "PUBLIC_ORIGIN=https://v1.element-plus-x.com" >> $GITHUB_OUTPUT
echo "VERSION_LABEL=v1.x (Verify)" >> $GITHUB_OUTPUT
fi

- name: Build docs
env:
DOCS_LINE: ${{ inputs.line }}
DOCS_PUBLIC_ORIGIN: ${{ steps.docs_env.outputs.PUBLIC_ORIGIN }}
DOCS_VERSION_LABEL: ${{ steps.docs_env.outputs.VERSION_LABEL }}
DOCS_V1_ORIGIN: https://v1.element-plus-x.com
DOCS_V2_ORIGIN: https://v2.element-plus-x.com
DOCS_ROOT_ORIGIN: https://element-plus-x.com
DOCS_USE_SOURCE: 'true'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run build:docs

- name: Resolve verify path
id: verify_path
env:
DOCS_DEPLOY_BASE_DIR: ${{ secrets.DOCS_DEPLOY_BASE_DIR }}
run: |
VERIFY_ID="${{ inputs.verify_id }}"
if [[ -z "$VERIFY_ID" ]]; then
VERIFY_ID="${{ github.run_id }}"
fi
REMOTE_DIR="${DOCS_DEPLOY_BASE_DIR}/verify/${{ inputs.line }}/$VERIFY_ID"
echo "VERIFY_ID=$VERIFY_ID" >> $GITHUB_OUTPUT
echo "REMOTE_DIR=$REMOTE_DIR" >> $GITHUB_OUTPUT

- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DOCS_DEPLOY_SSH_KEY }}

- name: Add deploy host to known_hosts
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
run: |
mkdir -p ~/.ssh
PORT="${DOCS_DEPLOY_PORT:-22}"
ssh-keyscan -p "$PORT" -H "$DOCS_DEPLOY_HOST" >> ~/.ssh/known_hosts

- name: Upload verify build
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
REMOTE_DIR="${{ steps.verify_path.outputs.REMOTE_DIR }}"

ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "mkdir -p '$REMOTE_DIR'"
rsync -az --delete -e "ssh -p $PORT" apps/docs/.vitepress/dist/ "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST:$REMOTE_DIR/"

- name: Verify remote artifacts
env:
DOCS_DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DOCS_DEPLOY_PORT: ${{ secrets.DOCS_DEPLOY_PORT }}
DOCS_DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
run: |
PORT="${DOCS_DEPLOY_PORT:-22}"
REMOTE_DIR="${{ steps.verify_path.outputs.REMOTE_DIR }}"

ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "\
test -d '$REMOTE_DIR' && \
test -f '$REMOTE_DIR/index.html' && \
(test -d '$REMOTE_DIR/assets' || test -d '$REMOTE_DIR/.vitepress')"
Comment on lines +122 to +125
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remote artifact check for .vitepress directory will always fail.

The rsync uploads from apps/docs/.vitepress/dist/ to $REMOTE_DIR/, so the remote directory contains the contents of dist/ (e.g., index.html, assets/), not a .vitepress folder. The check test -d '$REMOTE_DIR/.vitepress' will never succeed.

Suggested fix
          ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "\
            test -d '$REMOTE_DIR' && \
            test -f '$REMOTE_DIR/index.html' && \
-           (test -d '$REMOTE_DIR/assets' || test -d '$REMOTE_DIR/.vitepress')"
+           test -d '$REMOTE_DIR/assets'"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "\
test -d '$REMOTE_DIR' && \
test -f '$REMOTE_DIR/index.html' && \
(test -d '$REMOTE_DIR/assets' || test -d '$REMOTE_DIR/.vitepress')"
ssh -p "$PORT" "$DOCS_DEPLOY_USER@$DOCS_DEPLOY_HOST" "\
test -d '$REMOTE_DIR' && \
test -f '$REMOTE_DIR/index.html' && \
test -d '$REMOTE_DIR/assets'"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docs-deploy-verify.yaml around lines 122 - 125, The
workflow's remote verification uses a check for '$REMOTE_DIR/.vitepress' which
will never exist because rsync uploads the contents of
apps/docs/.vitepress/dist/ into $REMOTE_DIR; update the ssh test block in
.github/workflows/docs-deploy-verify.yaml to remove the non-existent "test -d
'$REMOTE_DIR/.vitepress'" check and instead verify presence of generated
artifacts (e.g., keep "test -d '$REMOTE_DIR/assets'" and "test -f
'$REMOTE_DIR/index.html'" or add checks for other dist files) so the SSH command
tests the actual uploaded files under $REMOTE_DIR.


- name: Print verify target
run: |
echo "Docs verify upload completed."
echo "Remote path: ${{ steps.verify_path.outputs.REMOTE_DIR }}"
72 changes: 72 additions & 0 deletions scripts/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Docs Deployment on Tencent Cloud (Lite Server)

## DNS Records

Create A records to your Tencent Cloud public IP:

1. `element-plus-x.com`
2. `v1.element-plus-x.com`
3. `v2.element-plus-x.com`

## Nginx Config

1. Copy `scripts/docs/nginx/element-plus-x.conf` to `/etc/nginx/conf.d/element-plus-x.conf`.
2. Create the redirect target file:

```bash
sudo mkdir -p /var/www/element-plus-x/root
echo 'set $root_target https://v1.element-plus-x.com;' | sudo tee /var/www/element-plus-x/root/redirect.conf
```

3. Create base directories:

```bash
sudo mkdir -p /var/www/element-plus-x/v1/releases /var/www/element-plus-x/v2/releases
sudo mkdir -p /var/www/element-plus-x/v1/current /var/www/element-plus-x/v2/current
```
Comment on lines +24 to +26
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid pre-creating current as a directory—workflows expect it to be a symlink.

Line 25 creates /var/www/element-plus-x/v1/current and v2/current as directories. However, the deploy-docs.yaml workflow (line 144) uses ln -sfn to atomically replace current with a symlink pointing to a release directory. If current exists as a non-empty directory, ln -sfn will create a symlink inside that directory rather than replacing it.

Suggested fix
 sudo mkdir -p /var/www/element-plus-x/v1/releases /var/www/element-plus-x/v2/releases
-sudo mkdir -p /var/www/element-plus-x/v1/current /var/www/element-plus-x/v2/current
+# Note: 'current' will be created as a symlink by the first release workflow run
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sudo mkdir -p /var/www/element-plus-x/v1/releases /var/www/element-plus-x/v2/releases
sudo mkdir -p /var/www/element-plus-x/v1/current /var/www/element-plus-x/v2/current
```
sudo mkdir -p /var/www/element-plus-x/v1/releases /var/www/element-plus-x/v2/releases
# Note: 'current' will be created as a symlink by the first release workflow run
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/docs/README.md` around lines 24 - 26, Remove the mkdir that
pre-creates the "current" directories so workflows can atomically replace
"current" with a symlink; specifically, update scripts/docs/README.md to stop
calling "sudo mkdir -p /var/www/element-plus-x/v1/current
/var/www/element-plus-x/v2/current" and only create the "releases" paths, and
add a short note that deploy-docs.yaml uses "ln -sfn" to set "current" as a
symlink so it must not exist as a directory.


4. Test and reload:

```bash
sudo nginx -t
sudo systemctl reload nginx
```

## HTTPS (Recommended)

Use Certbot to issue TLS certificates for the three domains. Example:

```bash
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d element-plus-x.com -d v1.element-plus-x.com -d v2.element-plus-x.com
```

## GitHub Secrets Required

Configure these secrets in the repo settings:

1. `DOCS_DEPLOY_HOST`
2. `DOCS_DEPLOY_PORT`
3. `DOCS_DEPLOY_USER`
4. `DOCS_DEPLOY_SSH_KEY`
5. `DOCS_DEPLOY_BASE_DIR` (suggested: `/var/www/element-plus-x`)

## Workflows

### Docs Deploy Verify

Use the `Docs Deploy Verify` workflow for the first end-to-end server test.

- Trigger it manually from the default branch.
- Point `source_ref` to the branch or tag you want to build.
- It uploads to `${DOCS_DEPLOY_BASE_DIR}/verify/<line>/<verify_id>`.
- It does not switch `current`, does not change `root/redirect.conf`, and does not reload nginx.

### Docs Release

Use the `Docs Release` workflow for release uploads and cutover.

- `switch_current=false` uploads the release without switching live traffic.
- `switch_current=true` updates the `<line>/current` symlink.
- `update_root_redirect=true` updates the root domain redirect when the release should become the default docs line.