mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 04:05:43 +08:00
[build] add MCP npm publishing workflow
This commit is contained in:
1 parent
b1236534ac
commit
5f526cd70e
8 files changed
+363
-6
No files matched your search
@@ -3,7 +3,7 @@ name: CLI native builds and npm packages
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
tags-ignore: ["cli-v*"]
|
||||
tags-ignore: ["cli-v*", "mcp-v*"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
@@ -2,8 +2,11 @@ name: MCP package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
tags-ignore: ["mcp-v*", "cli-v*"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -10,6 +10,7 @@ on:
|
||||
- "!ios-v*"
|
||||
- "!android-v*"
|
||||
- "!cli-v*"
|
||||
- "!mcp-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
desktop_version:
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Publish MCP to npm
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["mcp-v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Build and validate without publishing (disable only on a matching mcp-v* tag)"
|
||||
type: boolean
|
||||
default: true
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
dry_run: ${{ steps.release.outputs.dry_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
- name: Validate release version and Git tag
|
||||
id: release
|
||||
env:
|
||||
RELEASE_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
||||
run: |
|
||||
python3 scripts/version.py check mcp
|
||||
node apps/mcp/scripts/release-npm.cjs plan
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/build-mcp.yml
|
||||
|
||||
validate:
|
||||
needs: [prepare, build]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: mcp-package
|
||||
path: ${{ runner.temp }}/mcp-release
|
||||
- name: Validate the universal MCP package without publishing
|
||||
env:
|
||||
RELEASE_DRY_RUN: "true"
|
||||
RELEASE_ARTIFACTS: ${{ runner.temp }}/mcp-release
|
||||
run: node apps/mcp/scripts/release-npm.cjs publish "$RELEASE_ARTIFACTS"
|
||||
|
||||
publish:
|
||||
needs: [prepare, validate]
|
||||
if: needs.prepare.outputs.dry_run == 'false'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 25
|
||||
environment: npm
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
concurrency:
|
||||
group: npm-mcp-publish
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false
|
||||
- name: Install npm with Trusted Publishing support
|
||||
run: npm install --global npm@12.2.1 --ignore-scripts --no-audit --no-fund
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: mcp-package
|
||||
path: ${{ runner.temp }}/mcp-release
|
||||
- name: Check native dependencies and publish MCP
|
||||
env:
|
||||
RELEASE_DRY_RUN: "false"
|
||||
RELEASE_ARTIFACTS: ${{ runner.temp }}/mcp-release
|
||||
run: node apps/mcp/scripts/release-npm.cjs publish "$RELEASE_ARTIFACTS"
|
||||
+67
-1
@@ -486,7 +486,7 @@ npm run build
|
||||
npm run pack:npm -- --out-dir /absolute/path/artifacts
|
||||
```
|
||||
|
||||
默认生成通用 tarball,包含 MCP、DSH 插件入口与 bundle 声明、面板、源代码、许可和固定版本的五个平台可选依赖。CI 的 `MCP package` 工作流上传此通用 `mcp-package`,不发布 npm。使用通用包前需要对应版本的原生 npm 依赖已经可用。
|
||||
默认生成通用 tarball,包含 MCP、DSH 插件入口与 bundle 声明、面板、源代码、许可和固定版本的五个平台可选依赖。CI 的 `MCP package` 工作流上传此通用 `mcp-package`,不发布 npm;`Publish MCP to npm` 复用它构建并发布,见下方[自动发布说明](#github-actions-发布到-npm)。使用通用包前需要对应版本的原生 npm 依赖已经可用。
|
||||
|
||||
为 DSH 生成只需安装一次的平台包时,将 CLI 工作流生成的原生 `.tgz` 解压到工程外目录,再传入其 `package` 目录:
|
||||
|
||||
@@ -503,6 +503,72 @@ rm -rf "$native_package_dir"
|
||||
|
||||
从发布 tarball 重建 TypeScript/面板时,将包中的 `build-package.json` 复制为 `package.json`,运行 `npm ci && npm run build`。原生源码和依赖来源随平台包提供,详见 [源码来源](../cli/docs/source-provenance.md)。
|
||||
|
||||
## GitHub Actions 发布到 npm
|
||||
|
||||
[`release-mcp.yml`](../../.github/workflows/release-mcp.yml) 发布 `@chunkdash/crossdesk-mcp` 通用包,同时提供 stdio MCP 和 DSH 插件。发布过程不需要另发一个 DSH 插件包。
|
||||
|
||||
| 操作 | 结果 |
|
||||
| --- | --- |
|
||||
| 普通提交或 PR | `MCP package` 编译并上传 `mcp-package`,不发布 npm |
|
||||
| Actions → Publish MCP to npm → Run workflow,保持 `dry_run` 勾选 | 构建并检查发布包,不使用 npm 发布凭据,不要求 GitHub `npm` environment 审批 |
|
||||
| 推送 `mcp-v0.2.1` | 校验共同源码版本为 `0.2.1`,构建、检查原生依赖并发布到 `latest` |
|
||||
| 推送 `mcp-v0.3.0-rc.1` | 校验共同源码版本为 `0.3.0-rc.1`,发布到 `next` |
|
||||
| 在匹配的 `mcp-v*` 标签上手动运行并取消 `dry_run` | 执行真实发布;分支上不允许真实发布 |
|
||||
|
||||
MCP 标签不触发独立的桌面、CLI 或重复 MCP 构建。手动运行入口需要工作流先进入默认分支;预演可选择分支,标签上的预演也必须与源码版本一致。
|
||||
|
||||
### 配置 npm 发布权限
|
||||
|
||||
在 npm 网站打开 `@chunkdash/crossdesk-mcp` 的 **Settings → Trusted Publisher**,选择 **GitHub Actions**:
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Organization or user | `kunkundi` |
|
||||
| Repository | `crossdesk` |
|
||||
| Workflow filename | `release-mcp.yml` |
|
||||
| Environment name | `npm` |
|
||||
| Allowed actions | 勾选 **Allow npm publish** |
|
||||
|
||||
工作流使用 GitHub-hosted runner、Node.js 24、npm 12.2.1 和 OIDC,无需设置 `NPM_TOKEN`。只有实际发布 job 获得 `id-token: write`,上传时附带 provenance。配置字段需与工作流完全一致,详见 [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/)。
|
||||
|
||||
GitHub 仓库的 **Settings → Environments → npm** 若已限制只允许 `cli-v*`,需同时允许 `mcp-v*`。预演不进入该环境;真实发布会遵循已有的环境审批规则。MCP 与 CLI 的 npm 信任配置分别对应 `release-mcp.yml` 和 `release-cli.yml`,不能互换。
|
||||
|
||||
### 发布一个新版本
|
||||
|
||||
1. 在仓库根目录统一设置 CLI、MCP、DSH 和原生依赖版本。例如准备 `0.2.1`:
|
||||
|
||||
```sh
|
||||
python3 scripts/version.py set mcp 0.2.1
|
||||
python3 scripts/version.py check
|
||||
```
|
||||
|
||||
2. 提交并推送改动,再运行一次 MCP 发布工作流的 `dry_run`。版本脚本同时同步 CLI 构建配置、MCP 锁文件和 DSH 集成元数据,详见[组件版本管理](../../docs/VERSIONING.md)。预演检查 tarball 的名称、版本、SHA-512、可执行入口、页面、DSH bundle 和固定版本的原生依赖声明;它不检查 npm 发布权限或注册表中的原生包是否已经可用。
|
||||
3. 按 [CLI 发布指南](../cli/docs/npm-distribution.md#ci-与发布)从该提交推送 `cli-v0.2.1`,先发布五个平台原生包和 CLI 入口。确认 [`platforms.json`](../cli/npm/platforms.json) 中五个原生包的 `0.2.1` 均可查询后再发布 MCP。即使只改动 MCP,也需要发布同版本 CLI;已发布版本不能用不同内容覆盖。
|
||||
4. 确认 MCP 的 npm 信任配置可用后,从同一提交创建并推送 MCP 标签:
|
||||
|
||||
```sh
|
||||
git tag mcp-v0.2.1
|
||||
git push origin mcp-v0.2.1
|
||||
```
|
||||
|
||||
真实发布前会检查五个平台原生包的确切版本、平台字段和分发信息;任何一个缺失,或注册表返回权限/网络错误,都会停止发布。原生版本不依赖其 `latest` 或 `next` 标签,只需 MCP 引用的确切版本可用。`crossdesk-mcp-<平台>` 安装包会被拒绝;npm 只发布 `mcp-package` 中的通用包。
|
||||
|
||||
### 验证结果与失败恢复
|
||||
|
||||
发布 job 只下载当前工作流的 `mcp-package` artifact。上传后每 15 秒检查版本、SHA-512 和目标 dist-tag,最多等待 15 分钟;npm 尚在处理时不会重复上传。成功后 Actions Summary 显示发布版本、原生依赖版本和 tarball 校验值。
|
||||
|
||||
发布失败后优先 **Re-run failed jobs**,复用原 artifact。同版本、同校验值且 dist-tag 匹配的已发布包会跳过上传;同版本内容不同、dist-tag 已指向更新版本、已有版本的 dist-tag 不匹配或注册表仅有 `0.0.0-stage` 占位版本时,工作流会停止并给出原因。等待处理完成或修复依赖/权限后再重跑,不要为恢复上传而重跑全部构建或覆盖已有版本。本工作流不自动修改已有版本的 dist-tag。
|
||||
|
||||
发布成功后在工程外验证,避免 `npx` 匹配到源码目录中的同名包。以下以 `0.2.1` 为例:
|
||||
|
||||
```sh
|
||||
cd "$(mktemp -d)"
|
||||
npm view @chunkdash/crossdesk-mcp@0.2.1 version optionalDependencies --registry=https://registry.npmjs.org/
|
||||
npx --yes --registry=https://registry.npmjs.org/ @chunkdash/crossdesk-mcp@0.2.1 --help
|
||||
```
|
||||
|
||||
`--help` 仅验证命令入口;完整验收还需按[用户指南](README.md)在 Codex 或 DSH 中连接已授权设备。
|
||||
|
||||
## 本地审阅验证(2026-11-03)
|
||||
|
||||
动作参数校验和输入步骤生成集中在 `apps/cli/automation_protocol.*`,主循环负责调度、取消和权限检查。截图按色度网格对齐后仅转换所需区域,PNG 在容量受限的缓冲区中单次压缩;原生帧仅在没有其他读取者时复用内存。Node 按行累计响应字节,在完整响应到达时解码,避免反复扫描和复制大截图。
|
||||
|
||||
@@ -79,7 +79,7 @@ try {
|
||||
optionalDependencies: Object.fromEntries(
|
||||
Object.values(platforms).map((p) => [p.packageName, nativeVersion]),
|
||||
),
|
||||
publishConfig: { access: "public" },
|
||||
publishConfig: { access: "public", registry: "https://registry.npmjs.org/" },
|
||||
};
|
||||
delete manifest.scripts;
|
||||
delete manifest.devDependencies;
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const { setTimeout: sleep } = require('node:timers/promises');
|
||||
const project = require('../package.json');
|
||||
const lock = require('../package-lock.json');
|
||||
const cli = require('../../cli/package.json');
|
||||
const harness = require('../integrations/deepseek-harness/package.json');
|
||||
const platforms = require('../../cli/npm/platforms.json');
|
||||
const { compareVersions } = require('../../cli/scripts/release-npm.cjs');
|
||||
const registry = 'https://registry.npmjs.org/';
|
||||
const repository = 'kunkundi/crossdesk';
|
||||
|
||||
function releasePlan() {
|
||||
assert(['true', 'false'].includes(process.env.RELEASE_DRY_RUN), 'Set RELEASE_DRY_RUN=true or false explicitly.');
|
||||
const dryRun = process.env.RELEASE_DRY_RUN === 'true';
|
||||
const version = project.version;
|
||||
compareVersions(version, version);
|
||||
assert.equal(project.name, '@chunkdash/crossdesk-mcp', 'Unexpected MCP package name.');
|
||||
for (const manifest of [lock, lock.packages?.['']]) {
|
||||
assert.equal(manifest?.name, project.name, 'package-lock.json name must match package.json.');
|
||||
assert.equal(manifest?.version, version, 'package-lock.json version must match package.json.');
|
||||
}
|
||||
const nativeVersion = fs.readFileSync(path.join(__dirname, '../src/native.ts'), 'utf8')
|
||||
.match(/export const NATIVE_VERSION = "([^"]+)";/)?.[1];
|
||||
assert(nativeVersion, 'MCP source must declare an exact NATIVE_VERSION.');
|
||||
compareVersions(nativeVersion, nativeVersion);
|
||||
assert.equal(cli.version, version, 'CLI and MCP must share the same version.');
|
||||
assert.equal(harness.version, version, 'DSH and MCP must share the same version.');
|
||||
assert.equal(nativeVersion, version, 'MCP must depend on the same native CLI version.');
|
||||
const ref = process.env.GITHUB_REF || '';
|
||||
if (!dryRun) {
|
||||
assert.equal(process.env.GITHUB_ACTIONS, 'true', 'Real publishing must run in GitHub Actions.');
|
||||
assert.equal(process.env.GITHUB_REPOSITORY, repository, 'Unexpected publishing repository.');
|
||||
assert(['push', 'workflow_dispatch'].includes(process.env.GITHUB_EVENT_NAME), 'Unsupported publishing event.');
|
||||
assert.equal(ref, `refs/tags/mcp-v${version}`, 'Publish only from the matching mcp-vVERSION tag.');
|
||||
} else if (ref.startsWith('refs/tags/')) {
|
||||
assert.equal(ref, `refs/tags/mcp-v${version}`, 'Git tag and package version must match, including in a dry run.');
|
||||
}
|
||||
return { version, nativeVersion, tag: version.includes('-') ? 'next' : 'latest', dryRun };
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
return execFileSync(command, args, { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, ...options });
|
||||
}
|
||||
|
||||
function npm(args, options) {
|
||||
return process.env.npm_execpath
|
||||
? run(process.execPath, [process.env.npm_execpath, ...args], options)
|
||||
: run('npm', args, options);
|
||||
}
|
||||
|
||||
function archiveRead(file, member) {
|
||||
return run('tar', ['-xOf', file, `package/${member}`]);
|
||||
}
|
||||
|
||||
function collectPackage(directory, { version, nativeVersion }) {
|
||||
const filename = `chunkdash-crossdesk-mcp-${version}.tgz`;
|
||||
assert.deepEqual(fs.readdirSync(directory), [filename], 'Expected exactly one universal MCP tarball from this workflow run.');
|
||||
const file = path.join(directory, filename);
|
||||
const manifest = JSON.parse(archiveRead(file, 'package.json'));
|
||||
assert.equal(manifest.name, project.name, 'Unexpected MCP package in artifact.');
|
||||
assert.equal(manifest.version, version, 'Artifact version does not match the release tag.');
|
||||
assert(manifest.private === false && !manifest.scripts && !manifest.devDependencies,
|
||||
'Publish only generated packages without lifecycle scripts or development dependencies.');
|
||||
assert(!manifest.os && !manifest.cpu && !manifest.libc, 'Publish the universal MCP package, not a platform bundle.');
|
||||
assert.deepEqual(manifest.publishConfig, { access: 'public', registry }, 'Unexpected publishConfig.');
|
||||
for (const field of ['repository', 'dependencies', 'engines', 'peerDependencies', 'peerDependenciesMeta', 'main', 'exports', 'dsh'])
|
||||
assert.deepEqual(manifest[field], project[field], `Artifact ${field} differs from source metadata.`);
|
||||
assert.deepEqual(manifest.bin, { 'crossdesk-mcp': 'dist/main.js' });
|
||||
assert.deepEqual(manifest.optionalDependencies,
|
||||
Object.fromEntries(Object.values(platforms).map(({ packageName }) => [packageName, nativeVersion])),
|
||||
'MCP must pin all native platform dependencies to NATIVE_VERSION.');
|
||||
const result = JSON.parse(npm(['pack', file, '--dry-run', '--json', '--ignore-scripts']));
|
||||
const entries = Array.isArray(result) ? result : Object.values(result);
|
||||
assert.equal(entries.length, 1, 'Expected one package in the tarball.');
|
||||
const [packed] = entries;
|
||||
const integrity = `sha512-${crypto.createHash('sha512').update(fs.readFileSync(file)).digest('base64')}`;
|
||||
assert.equal(packed.integrity, integrity, 'Tarball integrity mismatch.');
|
||||
const files = new Map(packed.files.map((entry) => [entry.path, entry]));
|
||||
for (const member of ['dist/main.js', 'dist/native.js', 'dist/harness.js', 'dist/harness-client.js',
|
||||
'dist/viewer-browser.html', 'dist/viewer-apps.html', 'cordis.patch.yml', 'README.md', 'DEVELOPMENT.md', 'LICENSE'])
|
||||
assert(files.get(member)?.size > 0, `Missing or empty release file: ${member}`);
|
||||
assert(!packed.files.some((entry) => entry.path.startsWith('native/')), 'Universal package must not embed one platform binary.');
|
||||
assert(files.get('dist/main.js').mode & 0o111, 'MCP command is not executable.');
|
||||
assert(archiveRead(file, 'dist/main.js').startsWith('#!/usr/bin/env node\n'), 'MCP command is missing its Node shebang.');
|
||||
assert.equal(archiveRead(file, 'dist/native.js').match(/export const NATIVE_VERSION = "([^"]+)";/)?.[1],
|
||||
nativeVersion, 'Built MCP native version differs from source. Rebuild before packaging.');
|
||||
assert.equal(archiveRead(file, 'cordis.patch.yml').trim(),
|
||||
fs.readFileSync(path.join(__dirname, '../cordis.patch.yml'), 'utf8').trim(), 'DSH bundle patch differs from source.');
|
||||
return { file, name: manifest.name, version, integrity, manifest };
|
||||
}
|
||||
|
||||
async function readRegistry(name, version) {
|
||||
const response = await fetch(`${registry}${encodeURIComponent(name)}${version ? `/${version}` : ''}`, {
|
||||
headers: { accept: 'application/json', 'cache-control': 'no-cache' },
|
||||
signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
if (response.status === 404) return null;
|
||||
assert(response.ok, `${name}: registry returned HTTP ${response.status}; refusing to treat it as a missing package.`);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function checkNativePackages(version) {
|
||||
const results = await Promise.allSettled(Object.values(platforms).map(async (platform) => {
|
||||
const document = await readRegistry(platform.packageName);
|
||||
const published = document?.versions?.[version];
|
||||
assert(published, `${platform.packageName}@${version} is not available. Publish CLI ${version} first, then retry the MCP publish job.`);
|
||||
assert.equal(published.name, platform.packageName);
|
||||
assert.equal(published.version, version);
|
||||
assert.deepEqual(published.os, [platform.os], `${platform.packageName}: OS mismatch.`);
|
||||
assert.deepEqual(published.cpu, [platform.cpu], `${platform.packageName}: CPU mismatch.`);
|
||||
assert.deepEqual(published.libc, platform.libc ? [platform.libc] : undefined, `${platform.packageName}: libc mismatch.`);
|
||||
assert(published.dist?.tarball && /^sha512-/.test(published.dist?.integrity), `${platform.packageName}: missing distribution metadata.`);
|
||||
return platform.packageName;
|
||||
}));
|
||||
const failures = results.filter((result) => result.status === 'rejected');
|
||||
assert(!failures.length, `Native dependency checks failed:\n${failures.map((result) => result.reason.message).join('\n')}`);
|
||||
console.log(`Verified all ${results.length} native packages at ${version}.`);
|
||||
}
|
||||
|
||||
function checkIntegrity(pkg, published) {
|
||||
assert.equal(published.dist?.integrity, pkg.integrity,
|
||||
`${pkg.name}@${pkg.version} already exists with different content. Bump the version; do not overwrite it.`);
|
||||
}
|
||||
|
||||
async function waitForPackage(pkg, tag) {
|
||||
const deadline = Date.now() + 15 * 60 * 1000;
|
||||
while (true) {
|
||||
const document = await readRegistry(pkg.name);
|
||||
const published = document?.versions?.[pkg.version];
|
||||
if (published) {
|
||||
checkIntegrity(pkg, published);
|
||||
if (document['dist-tags']?.[tag] === pkg.version) return;
|
||||
}
|
||||
assert(Date.now() < deadline, 'npm is still processing. Wait, then re-run the failed job with the same artifact; do not upload again.');
|
||||
console.log(`Waiting for npm to expose ${pkg.name}@${pkg.version} and its ${tag} tag...`);
|
||||
await sleep(15000);
|
||||
}
|
||||
}
|
||||
|
||||
async function publishPackage(pkg, { tag, nativeVersion, dryRun }) {
|
||||
if (dryRun) {
|
||||
// npm publish --dry-run can exchange OIDC credentials and reject existing
|
||||
// versions. The archive has already passed npm pack --dry-run and inspection.
|
||||
console.log('Local validation only: npm permissions and native dependency availability were not checked.');
|
||||
} else {
|
||||
await checkNativePackages(nativeVersion);
|
||||
const document = await readRegistry(pkg.name);
|
||||
const current = document?.['dist-tags']?.[tag];
|
||||
assert(!current || compareVersions(current, pkg.version) <= 0,
|
||||
`${tag} already points to newer ${current}; refusing to roll it back.`);
|
||||
// The version endpoint can become visible before the package document.
|
||||
const published = document?.versions?.[pkg.version] ?? await readRegistry(pkg.name, pkg.version);
|
||||
if (published) {
|
||||
checkIntegrity(pkg, published);
|
||||
assert.equal(current, pkg.version, `The version already exists but ${tag} does not match. Wait for npm processing or review dist-tags before retrying.`);
|
||||
console.log(`Already published with matching integrity: ${pkg.name}@${pkg.version}`);
|
||||
} else {
|
||||
const versions = Object.keys(document?.versions || {});
|
||||
assert(!(versions.length === 1 && versions[0] === '0.0.0-stage'),
|
||||
'npm still has a staging placeholder. Wait for processing before retrying; do not upload again.');
|
||||
console.log(`Publishing ${pkg.name}@${pkg.version} (${tag})`);
|
||||
npm(['publish', pkg.file, '--access', 'public', '--tag', tag, '--ignore-scripts',
|
||||
`--registry=${registry}`, '--fetch-retries=0', '--fetch-timeout=120000', '--provenance'], { stdio: 'inherit' });
|
||||
await waitForPackage(pkg, tag);
|
||||
}
|
||||
}
|
||||
const report = `${dryRun ? 'Validated (no package published)' : 'Published and verified'} MCP ${pkg.version} with npm tag ${tag}.\n\n` +
|
||||
`Native CLI dependency: ${nativeVersion}${dryRun ? ' (registry availability not checked)' : ' (all five platforms verified)'}.\n\n` +
|
||||
`Tarball integrity: \`${pkg.integrity}\`\n`;
|
||||
console.log(report);
|
||||
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, report);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [command, directory, ...extra] = process.argv.slice(2);
|
||||
assert((command === 'plan' && !directory) || (command === 'publish' && directory && !extra.length),
|
||||
'Usage: release-npm.cjs plan | publish ARTIFACT_DIRECTORY (set RELEASE_DRY_RUN=true or false)');
|
||||
const plan = releasePlan();
|
||||
console.log(`MCP ${plan.version}, native CLI ${plan.nativeVersion}, npm tag ${plan.tag}, dry run: ${plan.dryRun}`);
|
||||
if (command === 'plan') {
|
||||
if (process.env.GITHUB_OUTPUT) fs.appendFileSync(process.env.GITHUB_OUTPUT,
|
||||
`version=${plan.version}\nnative_version=${plan.nativeVersion}\ntag=${plan.tag}\ndry_run=${plan.dryRun}\n`);
|
||||
return;
|
||||
}
|
||||
await publishPackage(collectPackage(path.resolve(directory), plan), plan);
|
||||
}
|
||||
|
||||
if (require.main === module) main().catch((error) => {
|
||||
console.error(`release:mcp: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
module.exports = { releasePlan, collectPackage, checkNativePackages, publishPackage };
|
||||
+3
-3
@@ -77,7 +77,7 @@ CLI、MCP、DSH 集成元数据和 MCP 的原生依赖版本必须完全相同
|
||||
|
||||
MCP 通用 npm 包从 `NATIVE_VERSION` 生成五个平台包的精确依赖;MCP 的 `--version` 和协议握手读取包版本。DSH 随 MCP 同包分发,不另发插件版本。
|
||||
|
||||
先通过 CLI 发布工作流发布同版本的五个平台原生包和 CLI 入口,再发布 MCP。即使只修改 MCP,也要为两者准备同一个新版本并按此顺序发布。MCP 平台安装包由 CLI 构建工作流一并生成,通用包使用 `npm run pack:npm` 打包。
|
||||
先通过 CLI 发布工作流发布同版本的五个平台原生包和 CLI 入口,再发布 MCP。即使只修改 MCP,也要为两者准备同一个新版本并按此顺序发布。CLI 与 MCP 仍使用各自的发布工作流和标签前缀;例如同一次版本更新使用 `cli-v0.3.0` 与 `mcp-v0.3.0`,建议指向同一提交。
|
||||
|
||||
CLI 构建工作流为每个平台附带生成 `crossdesk-mcp-<平台>` 安装包。源码版本不一致时,构建、MCP 打包和发布检查会报错;平台安装包还要求传入的原生包版本一致。工具版本不影响客户端版本和 Pi 扩展版本。
|
||||
|
||||
@@ -85,6 +85,6 @@ CLI 构建工作流为每个平台附带生成 `crossdesk-mcp-<平台>` 安装
|
||||
|
||||
客户端版本同时控制本地 Xmake 默认值,以及 GitHub Actions 分支构建未填写 `desktop_version` 时的默认版本。已有 Xmake 配置可能保留缓存,应重新配置;显式 `--CROSSDESK_VERSION`、Actions 的 `desktop_version` 和发布标签继续按原流程覆盖默认值。正式发布时应使标签的基础版本与客户端源码版本一致。日期和热修订后缀仍由发布流程处理,本脚本不修改既有标签格式。各端可分开构建和发布,但共用同一个源码发布版本。
|
||||
|
||||
修改并检查后,审阅、提交和推送源码,再按实际版本创建对应标签:CLI 使用 `cli-v<版本>`。移动端沿用 `android-v<版本>-YYYYMMDD`、`ios-v<版本>-YYYYMMDD`。已发布版本不能用不同内容覆盖;版本修改脚本不会替你判断某个版本是否已在注册表中使用。
|
||||
修改并检查后,审阅、提交和推送源码,再按实际版本创建对应标签:CLI 使用 `cli-v<版本>`,MCP 使用 `mcp-v<版本>`。移动端沿用 `android-v<版本>-YYYYMMDD`、`ios-v<版本>-YYYYMMDD`。已发布版本不能用不同内容覆盖;版本修改脚本不会替你判断某个版本是否已在注册表中使用。
|
||||
|
||||
相关说明:[桌面构建](BUILD.md) · [CLI 发布](../apps/cli/docs/npm-distribution.md#ci-与发布) · [MCP 打包](../apps/mcp/DEVELOPMENT.md#打包)。
|
||||
相关说明:[桌面构建](BUILD.md) · [CLI 发布](../apps/cli/docs/npm-distribution.md#ci-与发布) · [MCP 发布](../apps/mcp/DEVELOPMENT.md#github-actions-发布到-npm)。
|
||||
Reference in new issue
Block a user