diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4005d6e..8802b78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,15 +10,13 @@ jobs: strategy: fail-fast: false matrix: - # 默认两矩阵(发布负责人可按支持面增删): - # - Node 22 + 最低宿主:目标宿主 ref 需在受支持分支上稳定可拉取; - # 宿主契约合并后,把 HOST_REF_22 填成含该契约的完整 SHA。 - # - Node 24 + main:前向集成(拉当前 main 的 HEAD)。 + # latest 必须继续兼容已发布 rc;next 才能依赖 alpha 契约。 + # 宿主发布新版本时显式更新这里,禁止跟随可变的 master 冒充验收。 include: - node: 22 - host-ref: "master" # OSS 宿主默认分支 + host-ref: "dsh-v0.1.1-rc.2" - node: 24 - host-ref: "master" + host-ref: "dsh-v0.1.2-alpha.1" runs-on: ubuntu-latest timeout-minutes: 40 steps: @@ -35,12 +33,11 @@ jobs: # 宿主 checkout 统一落在 $GITHUB_WORKSPACE/../../.dsh/source/current # (= $GITHUB_WORKSPACE 的规范化父级 .dsh)——这样 vitest alias 与 # tsconfig paths 里 `../../.dsh/...` 的相对解析在 GitHub 上同样成立。 - - name: Clone dsh main repo at DSH_ROOT + - name: Clone pinned DSH host at DSH_ROOT run: | DSH_ROOT="$GITHUB_WORKSPACE/../../.dsh/source/current" mkdir -p "$(dirname "$DSH_ROOT")" - git clone --depth 1 "https://github.com/deepseek-ai/deepseek-harness.git" "$DSH_ROOT" - git -C "$DSH_ROOT" checkout "${{ matrix.host-ref }}" + git clone --depth 1 --branch "${{ matrix.host-ref }}" "https://github.com/deepseek-ai/deepseek-harness.git" "$DSH_ROOT" test -f "$DSH_ROOT/packages/client/ui-primitives/src/index.ts" || { echo "::error::宿主预检失败:ui-primitives 源码缺失"; exit 1; } echo "HOST_SHA=$(git -C "$DSH_ROOT" rev-parse HEAD)" >> "$GITHUB_ENV" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b978b68..4afc9e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,6 +19,7 @@ jobs: env: RELEASE_TAG: ${{ github.event.release.tag_name }} PRERELEASE: ${{ github.event.release.prerelease }} + HOST_REF: ${{ github.event.release.prerelease && 'dsh-v0.1.2-alpha.1' || 'dsh-v0.1.1-rc.2' }} steps: # actions/checkout@v4 @@ -53,6 +54,12 @@ jobs: exit 1 fi + VERSION_PRERELEASE="$(node -e 'console.log(process.argv[1].split("+")[0].includes("-") ? "true" : "false")' "$VERSION")" + if [ "$PRERELEASE" != "$VERSION_PRERELEASE" ]; then + echo "::error::package version prerelease state ($VERSION_PRERELEASE) does not match GitHub release prerelease state ($PRERELEASE)" + exit 1 + fi + if [ "$PRERELEASE" = "true" ]; then echo "npm_tag=next" >> "$GITHUB_OUTPUT" else @@ -87,8 +94,7 @@ jobs: run: | DSH_ROOT="$GITHUB_WORKSPACE/../../.dsh/source/current" mkdir -p "$(dirname "$DSH_ROOT")" - git clone --depth 1 https://github.com/deepseek-ai/deepseek-harness.git "$DSH_ROOT" - git -C "$DSH_ROOT" checkout master + git clone --depth 1 --branch "$HOST_REF" https://github.com/deepseek-ai/deepseek-harness.git "$DSH_ROOT" test -f "$DSH_ROOT/packages/client/ui-primitives/src/index.ts" || { echo "::error::DSH host preflight failed: ui-primitives source is missing" exit 1 @@ -110,5 +116,11 @@ jobs: - name: Verify package run: node scripts/verify-pack.mjs + - name: Smoke selected release channel + run: node scripts/e2e.mjs --smoke --install link --port 3190 + env: + DSH_ROOT: ${{ github.workspace }}/../../.dsh/source/current + DSH_BIN: ${{ github.workspace }}/../../.dsh/source/current/apps/cli/lib/bin.js + - name: Publish to npm run: npm publish --access public --tag "${{ steps.release.outputs.npm_tag }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1103874..13cf69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ ## [Unreleased] ### 发布 - npm 发布作用域改为个人账号 `@changfenhuang/dsh-genui`;GitHub 仓库继续保留在 `omdsh-dev` 组织。同步更新运行时模块标识、资源路由、安装脚本、文档和测试,不保留旧 npm 名称兼容层。 +- **正式版与测试版分流**:发布版本带预发布后缀时必须勾选 GitHub Pre-release,并只进入 npm `next`;无后缀版本只能作为正式 Release 进入 `latest`。CI 与发布验收固定覆盖 `dsh-v0.1.1-rc.2` / `dsh-v0.1.2-alpha.1`,不再跟随可变的宿主开发分支。 ### 兼容性 - **dsh 0.1.0-rc.8**:对齐全部宿主 peer 依赖并补齐实际使用的 conversation、input-trigger、session 直接声明;改用 ui-tool 的公开客户端入口,测试和构建不再读取本机旧源码快照。`tsc`、`tsdown`、Vitest 全通过(316 passed / 104 skipped,0 失败)。 diff --git a/scripts/e2e.mjs b/scripts/e2e.mjs index fac9660..52ab0c0 100644 --- a/scripts/e2e.mjs +++ b/scripts/e2e.mjs @@ -42,7 +42,7 @@ const arg = (name) => { return index === -1 ? undefined : process.argv[index + 1] } function findDshWebUrl(output) { - return output.match(/dsh web: (http:\/\/127\.0\.0\.1:\d+\/\?token=[A-Za-z0-9_-]+)/u)?.[1] + return output.match(/dsh web: (http:\/\/127\.0\.0\.1:\d+(?:\/\?token=[A-Za-z0-9_-]+)?)/u)?.[1] } const PORT = Number(arg('--port') ?? 3088) const KEEP = process.argv.includes('--keep') @@ -190,7 +190,7 @@ try { await page.goto(readyUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }) await page.waitForTimeout(5000) - // 0.1.2 起只服务启动图公告的不可变组合 URL,不再开放旧版单包裸路径。 + // 0.1.2 起使用启动图公告的不可变组合 URL;更早的受支持宿主使用裸插件地址。 const boot = await page.evaluate(() => { const boot = window.__DSH_BOOT__ if (boot === null || typeof boot !== 'object' || !Array.isArray(boot.entries)) return undefined @@ -200,14 +200,14 @@ try { await page.screenshot({ path: join(artifactsDir, 'e2e-fail-empty-host-boot.png') }) fail('dsh 宿主启动图为空(连内置浏览器插件都未注册)') } - if (boot?.clientUrl === undefined) fail('启动图缺少 @changfenhuang/dsh-genui(插件未注册)') - const clientRes = await fetch(`${BASE}${boot.clientUrl}`) + const clientUrl = boot?.clientUrl ?? '/plugins/@changfenhuang/dsh-genui/client.js' + const clientRes = await fetch(`${BASE}${clientUrl}`) if (!clientRes.ok) { await page.screenshot({ path: join(artifactsDir, 'e2e-fail-client404.png') }) await logTail() - fail(`启动图公告的 GenUI bundle 返回 ${clientRes.status}`) + fail(`GenUI bundle 返回 ${clientRes.status}`) } - log(`✓ 启动图 GenUI bundle ${clientRes.status}`) + log(`✓ GenUI bundle ${clientRes.status}`) if (pageErrors.length > 0) { await page.screenshot({ path: join(artifactsDir, 'e2e-fail-pageerror.png') }) diff --git a/tests/e2e-auth.spec.ts b/tests/e2e-auth.spec.ts index 9e95b25..2790024 100644 --- a/tests/e2e-auth.spec.ts +++ b/tests/e2e-auth.spec.ts @@ -8,9 +8,10 @@ if (match === null) throw new Error('e2e script must define findDshWebUrl') const findDshWebUrl = Function(`${match[0]}; return findDshWebUrl`)() as (output: string) => string | undefined describe('dsh web authentication', () => { - it('extracts only the tokenized local startup URL', () => { + it('extracts tokenized alpha and bare rc startup URLs', () => { expect(findDshWebUrl('booting\ndsh web: http://127.0.0.1:3190/?token=abc_DEF-123\n')) .toBe('http://127.0.0.1:3190/?token=abc_DEF-123') - expect(findDshWebUrl('dsh web: http://127.0.0.1:3190/')).toBeUndefined() + expect(findDshWebUrl('dsh web: http://127.0.0.1:3190')) + .toBe('http://127.0.0.1:3190') }) })