Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm desktop:release:check
- run: pnpm test:desktop-release
- run: pnpm --dir apps/desktop exec electron-forge package --platform ${{ matrix.platform }} --arch ${{ matrix.arch }}
- uses: actions/upload-artifact@v4
with:
Expand Down
209 changes: 104 additions & 105 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ name: Desktop Release
on:
push:
tags:
- "desktop-v*"
- "v*"
workflow_dispatch:
inputs:
version:
description: "版本号或 desktop-v 标签,例如 0.28.0-beta.1"
description: "版本号或 v 标签,例如 v0.28.0、0.28.0-beta.1"
required: true
source_ref:
description: "构建来源 ref;留空使用 main"
description: "构建来源 ref;留空使用对应 v 标签"
required: false
default: ""
channel:
description: "更新通道;稳定版只能使用已签名产物"
required: true
type: choice
options:
- beta
- stable
default: beta
- beta
default: stable
publish:
description: "是否创建或更新 GitHub Release;手动运行默认只上传 CI artifact"
required: true
Expand All @@ -31,10 +31,6 @@ permissions:
contents: write
actions: read

concurrency:
group: desktop-release-${{ github.ref }}
cancel-in-progress: false

env:
NODE_VERSION: "22"
PNPM_VERSION: "10.8.1"
Expand All @@ -51,61 +47,20 @@ jobs:
source_ref: ${{ steps.identity.outputs.source_ref }}
publish: ${{ steps.identity.outputs.publish }}
steps:
- name: Checkout release automation
uses: actions/checkout@v4

- name: Resolve tag, version and channel
id: identity
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_TAG: ${{ github.ref_name }}
GITHUB_SHA: ${{ github.sha }}
INPUT_VERSION: ${{ github.event.inputs.version || '' }}
INPUT_CHANNEL: ${{ github.event.inputs.channel || 'beta' }}
INPUT_CHANNEL: ${{ github.event.inputs.channel || 'stable' }}
INPUT_SOURCE_REF: ${{ github.event.inputs.source_ref || '' }}
INPUT_PUBLISH: ${{ github.event.inputs.publish || 'false' }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "push" ]]; then
tag="$EVENT_TAG"
source_ref="${GITHUB_SHA}"
publish=true
else
raw_version="${INPUT_VERSION#desktop-v}"
tag="desktop-v${raw_version}"
source_ref="${INPUT_SOURCE_REF:-main}"
publish="${INPUT_PUBLISH}"
fi

if [[ ! "$tag" =~ ^desktop-v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::invalid desktop release tag: $tag"
exit 1
fi
version="${tag#desktop-v}"
if [[ "$version" == *-* ]]; then
channel=beta
elif [[ "$EVENT_NAME" == push ]]; then
channel=stable
else
channel="${INPUT_CHANNEL:-stable}"
fi
if [[ "$channel" == stable && "$version" == *-* ]]; then
echo "::error::a prerelease version cannot enter stable"
exit 1
fi
if [[ "$publish" == true && "$channel" == beta && "$version" != *-* ]]; then
echo "::error::a published beta requires a prerelease version"
exit 1
fi
if [[ "$publish" != true && "$publish" != false ]]; then
echo "::error::publish must be true or false"
exit 1
fi

{
echo "tag=$tag"
echo "version=$version"
echo "channel=$channel"
echo "source_ref=$source_ref"
echo "publish=$publish"
} >> "$GITHUB_OUTPUT"
run: node scripts/resolve-desktop-release.mjs

build:
name: Build ${{ matrix.name }}
Expand Down Expand Up @@ -164,28 +119,7 @@ jobs:
cache-dependency-path: pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build macOS DMG native dependency
if: matrix.platform == 'darwin'
shell: bash
run: |
set -euo pipefail
for package_name in macos-alias fs-xattr; do
if [[ "$package_name" == macos-alias ]]; then
expected_output=volume.node
else
expected_output=xattr.node
fi
package_json="$(find node_modules/.pnpm -path "*/node_modules/$package_name/package.json" -print -quit)"
if [[ -z "$package_json" ]]; then
echo "::error::$package_name package is missing"
exit 1
fi
package_dir="$(dirname "$package_json")"
pnpm exec node-gyp rebuild --directory "$package_dir"
test -f "$package_dir/build/Release/$expected_output"
done
run: pnpm install --frozen-lockfile --config.node-linker=hoisted

- name: Validate desktop release contract
run: pnpm desktop:release:check
Expand All @@ -202,8 +136,22 @@ jobs:
exit 1
}

- name: Verify published source matches release tag
if: needs.resolve.outputs.publish == 'true'
shell: bash
env:
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
run: |
set -euo pipefail
tag_commit="$(git rev-list -n 1 "$RELEASE_TAG")"
source_commit="$(git rev-parse 'HEAD^{commit}')"
test "$source_commit" = "$tag_commit" || {
echo "::error::published desktop source $source_commit does not match $RELEASE_TAG at $tag_commit"
exit 1
}

- name: Validate macOS signing secrets
if: matrix.platform == 'darwin'
if: matrix.platform == 'darwin' && needs.resolve.outputs.publish == 'true'
shell: bash
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
Expand All @@ -225,7 +173,7 @@ jobs:
fi

- name: Import macOS signing certificate
if: matrix.platform == 'darwin'
if: matrix.platform == 'darwin' && needs.resolve.outputs.publish == 'true'
shell: bash
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
Expand All @@ -245,7 +193,7 @@ jobs:
echo "CONTENTCLOUD_DESKTOP_MAC_KEYCHAIN=$keychain" >> "$GITHUB_ENV"

- name: Validate Windows signing secrets
if: matrix.platform == 'win32'
if: matrix.platform == 'win32' && needs.resolve.outputs.publish == 'true'
shell: bash
env:
WINDOWS_SIGNING_CERTIFICATE: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE }}
Expand All @@ -256,7 +204,7 @@ jobs:
[[ -n "${WINDOWS_SIGNING_CERTIFICATE_PASSWORD:-}" ]] || { echo "::error::missing WINDOWS_SIGNING_CERTIFICATE_PASSWORD"; exit 1; }

- name: Prepare Windows signing certificate
if: matrix.platform == 'win32'
if: matrix.platform == 'win32' && needs.resolve.outputs.publish == 'true'
shell: bash
env:
WINDOWS_SIGNING_CERTIFICATE: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE }}
Expand All @@ -274,20 +222,42 @@ jobs:
- name: Build Electron packages
shell: bash
env:
CONTENTCLOUD_DESKTOP_SIGN: ${{ matrix.signing == 'required' && '1' || '' }}
CONTENTCLOUD_DESKTOP_SIGN: ${{ needs.resolve.outputs.publish == 'true' && matrix.signing == 'required' && '1' || '' }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
pnpm --dir apps/desktop exec electron-forge make \
pnpm --dir apps/desktop exec electron-forge package \
--platform "${{ matrix.platform }}" \
--arch "${{ matrix.arch }}" \
--targets "${{ matrix.forge_targets }}"
--arch "${{ matrix.arch }}"

FORGE_MAKE_LOG="$RUNNER_TEMP/electron-forge-make-${{ matrix.target }}.log"
run_forge_make() {
set +e
pnpm --dir apps/desktop exec electron-forge make \
--skip-package \
--platform "${{ matrix.platform }}" \
--arch "${{ matrix.arch }}" \
--targets "${{ matrix.forge_targets }}" 2>&1 | tee "$FORGE_MAKE_LOG"
forge_status=${PIPESTATUS[0]}
set -e
}

run_forge_make
if [[ "$forge_status" -ne 0 && "${{ matrix.platform }}" == darwin ]] \
&& grep -q "hdiutil detach /Volumes/Content Work OS" "$FORGE_MAKE_LOG" \
&& grep -q "No such file or directory" "$FORGE_MAKE_LOG"; then
echo "DMG volume cleanup raced with hdiutil; retrying Forge make once."
hdiutil detach "/Volumes/Content Work OS" -force || true
sleep 5
run_forge_make
fi
exit "$forge_status"

- name: Verify macOS signatures and notarization
if: matrix.platform == 'darwin'
if: matrix.platform == 'darwin' && needs.resolve.outputs.publish == 'true'
shell: bash
run: |
set -euo pipefail
Expand All @@ -303,7 +273,7 @@ jobs:
done

- name: Verify Windows Authenticode signature
if: matrix.platform == 'win32'
if: matrix.platform == 'win32' && needs.resolve.outputs.publish == 'true'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
Expand All @@ -324,26 +294,40 @@ jobs:
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
RELEASE_VERSION: ${{ needs.resolve.outputs.version }}
RELEASE_CHANNEL: ${{ needs.resolve.outputs.channel }}
RELEASE_REPOSITORY: ${{ github.repository }}
RELEASE_REPOSITORY: ${{ needs.resolve.outputs.publish == 'true' && github.repository || '' }}
run: |
set -euo pipefail
signing_flag=()
if [[ "${{ matrix.signing }}" == required ]]; then signing_flag+=(--signed); fi
node scripts/stage-desktop-release.mjs stage \
--forge-dir apps/desktop/out \
--out-dir "desktop-staged/${{ matrix.target }}" \
--target "${{ matrix.target }}" \
--version "$RELEASE_VERSION" \
--channel "$RELEASE_CHANNEL" \
--tag "$RELEASE_TAG" \
--repository "$RELEASE_REPOSITORY" \
"${signing_flag[@]}"
stage_args=(
--forge-dir apps/desktop/out
--out-dir "desktop-staged/${{ matrix.target }}"
--target "${{ matrix.target }}"
--version "$RELEASE_VERSION"
--channel "$RELEASE_CHANNEL"
--tag "$RELEASE_TAG"
)
if [[ -n "$RELEASE_REPOSITORY" ]]; then
stage_args+=(--repository "$RELEASE_REPOSITORY")
fi
if [[ "${{ matrix.signing }}" == required ]]; then
if [[ "${{ needs.resolve.outputs.publish }}" == true ]]; then
stage_args+=(--signed)
else
stage_args+=(--preview)
fi
fi
node scripts/stage-desktop-release.mjs stage "${stage_args[@]}"
staged_dir="desktop-staged/${{ matrix.target }}"
if [[ -z "$(find "$staged_dir" -maxdepth 1 -type f -print -quit)" ]]; then
echo "::error::desktop release staging produced no files under $staged_dir"
exit 1
fi
find "$staged_dir" -maxdepth 1 -type f -print | sort

- name: Upload staged desktop assets
uses: actions/upload-artifact@v4
with:
name: desktop-release-${{ matrix.target }}
path: desktop-staged/${{ matrix.target }}/*
path: desktop-staged/${{ matrix.target }}
if-no-files-found: error
retention-days: 14

Expand All @@ -352,6 +336,9 @@ jobs:
needs: [resolve, build]
if: needs.resolve.outputs.publish == 'true' && needs.build.result == 'success'
runs-on: ubuntu-22.04
concurrency:
group: desktop-release-publish-${{ needs.resolve.outputs.tag }}
cancel-in-progress: false
steps:
- name: Checkout source
uses: actions/checkout@v4
Expand Down Expand Up @@ -387,10 +374,16 @@ jobs:
--tag "$RELEASE_TAG" \
--repository "$RELEASE_REPOSITORY" \
--require-all-targets
if [[ -f github-release-assets/checksums.txt ]]; then
test ! -e github-release-assets/desktop-checksums.txt
mv github-release-assets/checksums.txt github-release-assets/desktop-checksums.txt
fi
test -f github-release-assets/desktop-checksums.txt
cp desktop-staged/* github-release-assets/
find github-release-assets -maxdepth 1 -type f -print | sort

- name: Create draft GitHub Release
- name: Ensure GitHub Release exists
id: release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
Expand All @@ -400,14 +393,19 @@ jobs:
run: |
set -euo pipefail
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --target "$SOURCE_REF" --draft=true
is_draft="$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft')"
echo "created=false" >> "$GITHUB_OUTPUT"
echo "publish_after_upload=$is_draft" >> "$GITHUB_OUTPUT"
echo "Reusing existing GitHub Release $RELEASE_TAG"
else
gh release create "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--target "$SOURCE_REF" \
--title "Content Work OS Desktop $RELEASE_TAG" \
--title "ContentCloud $RELEASE_TAG" \
--generate-notes \
--draft
echo "created=true" >> "$GITHUB_OUTPUT"
echo "publish_after_upload=true" >> "$GITHUB_OUTPUT"
fi

- name: Upload desktop release assets
Expand All @@ -419,7 +417,8 @@ jobs:
set -euo pipefail
gh release upload "$RELEASE_TAG" github-release-assets/* --repo "$GITHUB_REPOSITORY" --clobber

- name: Publish release
- name: Publish draft release
if: steps.release.outputs.publish_after_upload == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

ContentCloud 的重要变更记录在此文件中。

## [0.29.0] - 2026-08-24

### Added

- 增加按业务定制的视频、文章、电商和连载小说工作台,并通过 Workbench Registry 管理版本、摘要、模板绑定和租户启用。
- 打通工作台 Action Contract 到 WorkTask、SOP、Gate、Runtime、Review、ApprovedSnapshot、Artifact、Delivery 和 Performance 的统一平台主链。
- 增加 Claude Code 宿主 bootstrap、preflight、plan、apply、resume 与 Web 连接宿主选择能力。
- 增加 Runtime 清理诊断、媒体合成、剪映导出和多业务完整链路验证能力。

### Changed

- 更新分层平台架构、代码组织、插件边界、业务工作台 UI 原型、时序图和流程图文档。
- 管理后台增加工作台、Runtime 恢复、清理和运营诊断视图;客户任务展示统一的流程、产物和效果摘要。
- Server、Worker、Web、Desktop、CLI 和 npm 启动包统一升级到 `0.29.0`;视频生产场景插件继续固定在已验证的 `0.27.0`。

### Fixed

- 修复客户连接客户端目录在开放 Claude Code 后仍只允许 Codex 的 HTTP 测试断言。

## [0.28.0] - 2026-08-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.28.0
0.29.0
Loading
Loading