mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat(release): 给 publish.yml 加 bailian-kb-dsh 通道
复用同一个 Publish workflow 入口(package 下拉多一项 bailian-kb-dsh),路由到 独立的 publish-kb-dsh.mjs 处理: - 版本读自身 package.json(不广播全套 bl 版本) - stable 打 bailian-kb-dsh-v<version> tag(与 bl 的 v<version> 错开命名空间) - channel 临时 bump 到 0.0.0-beta-<sha>-<stamp>(形态与 bl channel 一致), finally 还原 package.json - 走自身的 tsc + tsdown build,无 binary,无 OSS CDN - 复用 lib/git.mjs / lib/npm.mjs / lib/proc.mjs 三个薄工具 - 复用 workflow 入口 UI 与 setup 步骤(checkout / pnpm / node 24 / gitleaks / install),stable 走 environment: production Required Reviewers gate 不复用 publish-stable.mjs / publish-channel.mjs:它们的 loadAndValidatePackages 会广播 core 版本给全套锁步包并强校验一致性,把 kb-dsh 塞进去第一步就 throw。 故意分开是为了保住这个隔离。 本地 --dry-run 端到端跑通:build → 幂等性查重 → pack + publint + gitleaks → pnpm publish --tag latest|<channel> --provenance --dry-run;channel 模式的 finally 还原后 git diff 干净。 文档:dsh-plugin.md 补发布小节 + 已知待办(publint 那条 web bundle CJS/ESM warning);publish.md 加 bailian-kb-dsh 定位;packages.mjs 与 AGENTS.md 的 注释同步指向新的 job 与 script 名。
This commit is contained in:
@@ -10,6 +10,7 @@ on:
|
|||||||
options:
|
options:
|
||||||
- bailian-cli
|
- bailian-cli
|
||||||
- knowledge-studio-cli
|
- knowledge-studio-cli
|
||||||
|
- bailian-kb-dsh
|
||||||
mode:
|
mode:
|
||||||
description: "Publish mode"
|
description: "Publish mode"
|
||||||
required: true
|
required: true
|
||||||
@@ -18,7 +19,7 @@ on:
|
|||||||
- channel
|
- channel
|
||||||
- stable
|
- stable
|
||||||
channel:
|
channel:
|
||||||
description: "Required when mode=channel. npm dist-tag only (lowercase, digits, dashes), e.g. mcp / plugin / sync-release. bailian-cli binary CDN always overwrites sync-release.json; knowledge-studio-cli is npm-only."
|
description: "Required when mode=channel. npm dist-tag only (lowercase, digits, dashes), e.g. mcp / plugin / sync-release. bailian-cli binary CDN always overwrites sync-release.json; knowledge-studio-cli and bailian-kb-dsh are npm-only."
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-stable:
|
publish-stable:
|
||||||
if: inputs.mode == 'stable'
|
if: inputs.mode == 'stable' && inputs.package != 'bailian-kb-dsh'
|
||||||
name: publish stable (${{ inputs.package }}) to npm + binary + tag
|
name: publish stable (${{ inputs.package }}) to npm + binary + tag
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: production # Required Reviewers gate
|
environment: production # Required Reviewers gate
|
||||||
@@ -83,7 +84,7 @@ jobs:
|
|||||||
run: node tools/release/publish-stable.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }}
|
run: node tools/release/publish-stable.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }}
|
||||||
|
|
||||||
publish-channel:
|
publish-channel:
|
||||||
if: inputs.mode == 'channel'
|
if: inputs.mode == 'channel' && inputs.package != 'bailian-kb-dsh'
|
||||||
name: publish channel (${{ inputs.package }}) to npm + binary
|
name: publish channel (${{ inputs.package }}) to npm + binary
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -138,3 +139,49 @@ jobs:
|
|||||||
BAILIAN_RELEASE_PREFIX: ${{ secrets.BAILIAN_RELEASE_PREFIX }}
|
BAILIAN_RELEASE_PREFIX: ${{ secrets.BAILIAN_RELEASE_PREFIX }}
|
||||||
BAILIAN_STATIC_PREFIX: ${{ secrets.BAILIAN_STATIC_PREFIX }}
|
BAILIAN_STATIC_PREFIX: ${{ secrets.BAILIAN_STATIC_PREFIX }}
|
||||||
run: node tools/release/publish-channel.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }} --channel "${{ inputs.channel }}"
|
run: node tools/release/publish-channel.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }} --channel "${{ inputs.channel }}"
|
||||||
|
|
||||||
|
# bailian-kb-dsh is the dsh plugin (downstream host adapter): independent version,
|
||||||
|
# tsc + tsdown build, npm-only. It shares this workflow's entry UI and setup steps
|
||||||
|
# but NOT publish-stable.mjs / publish-channel.mjs — those broadcast one version
|
||||||
|
# across the locked bl package set and produce binary artifacts, neither of which
|
||||||
|
# applies here. See docs/agents/dsh-plugin.md.
|
||||||
|
publish-kb-dsh:
|
||||||
|
if: inputs.package == 'bailian-kb-dsh'
|
||||||
|
name: publish ${{ inputs.mode }} (bailian-kb-dsh) to npm
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# stable goes through the Required Reviewers gate, same as the bl stable job;
|
||||||
|
# channel stays ungated so dist-tag drops need no approval.
|
||||||
|
environment: ${{ inputs.mode == 'stable' && 'production' || '' }}
|
||||||
|
permissions:
|
||||||
|
contents: write # push the bailian-kb-dsh-v<version> tag (stable only)
|
||||||
|
id-token: write # OIDC for npm Trusted Publishing + provenance
|
||||||
|
steps:
|
||||||
|
- name: Require channel input
|
||||||
|
if: ${{ inputs.mode == 'channel' && inputs.channel == '' }}
|
||||||
|
run: |
|
||||||
|
echo "::error::mode=channel requires the workflow input \"channel\" (npm dist-tag, e.g. mcp / plugin). Leave mode=stable if you do not need a dist-tag."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: pnpm
|
||||||
|
registry-url: "https://registry.npmjs.org/"
|
||||||
|
|
||||||
|
- name: Install gitleaks
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
GITLEAKS_VERSION=8.21.2
|
||||||
|
curl -sSfL \
|
||||||
|
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||||
|
| sudo tar -xz -C /usr/local/bin gitleaks
|
||||||
|
gitleaks version
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: publish-kb-dsh
|
||||||
|
run: node tools/release/publish-kb-dsh.mjs ${{ inputs.mode == 'channel' && format('--channel "{0}"', inputs.channel) || '' }}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ Skill / 命令手册随 `skills/bailian-*/` 经 `bl skill init` 安装(装齐
|
|||||||
|
|
||||||
源码包的 `version` 当前保持一致: `packages/core`、`packages/runtime`、`packages/commands`、`packages/cli`、`packages/kscli`。做版本 bump 时一动多动。release 工具当前强校验 / 发布范围以 `tools/release/lib/packages.mjs` 为准;把新包纳入发布前必须同步该清单和 [publish.md](docs/agents/publish.md)。
|
源码包的 `version` 当前保持一致: `packages/core`、`packages/runtime`、`packages/commands`、`packages/cli`、`packages/kscli`。做版本 bump 时一动多动。release 工具当前强校验 / 发布范围以 `tools/release/lib/packages.mjs` 为准;把新包纳入发布前必须同步该清单和 [publish.md](docs/agents/publish.md)。
|
||||||
|
|
||||||
**例外**: `packages/bailian-kb-dsh` 不参与这个锁步(独立 `0.1.x`,跟随 dsh rc 节奏),也不在 release 白名单里,由独立 workflow 发布。
|
**例外**: `packages/bailian-kb-dsh` 不参与这个锁步(独立 `0.1.x`,跟随 dsh rc 节奏),也不在 release 白名单里;它走 `publish.yml` 里 `package=bailian-kb-dsh` 的独立 job(`tools/release/publish-kb-dsh.mjs`)。
|
||||||
|
|
||||||
### 2. 分层边界
|
### 2. 分层边界
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
它是**下游宿主适配层**:依赖方向朝外(消费 `bl` CLI 与百炼 API,装进 DeepSeek Harness 运行),不是 `core → runtime → commands → 产品入口` 这条链上的一环。由此带来四条与 `packages/*` 通行约定的**故意偏离**:
|
它是**下游宿主适配层**:依赖方向朝外(消费 `bl` CLI 与百炼 API,装进 DeepSeek Harness 运行),不是 `core → runtime → commands → 产品入口` 这条链上的一环。由此带来四条与 `packages/*` 通行约定的**故意偏离**:
|
||||||
|
|
||||||
| 项 | 本包 | 其他包 | 原因 |
|
| 项 | 本包 | 其他包 | 原因 |
|
||||||
| -------- | -------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------ |
|
| -------- | ---------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||||
| 版本 | 独立 `0.1.x` | core/runtime/commands/cli/kscli 锁步 | 跟随 dsh 的 rc 节奏,与 `bl` 发版无关;不在 `tools/release/lib/packages.mjs` 白名单里 |
|
| 版本 | 独立 `0.1.x` | core/runtime/commands/cli/kscli 锁步 | 跟随 dsh 的 rc 节奏,与 `bl` 发版无关;不在 `tools/release/lib/packages.mjs` 白名单里 |
|
||||||
| 构建 | `tsc` + `tsdown` | `vp pack` | 浏览器半需要 `__ModuleLoader__` banner/footer 与 lightningcss CSS Modules 内联,`vp pack` 产不出 |
|
| 构建 | `tsc` + `tsdown` | `vp pack` | 浏览器半需要 `__ModuleLoader__` banner/footer 与 lightningcss CSS Modules 内联,`vp pack` 产不出 |
|
||||||
| 发布 | `.github/workflows/publish-kb-dsh.yml` | `publish.yml` | 不在 `bailian-cli` 依赖闭包内,走独立通道 |
|
| 发布 | `publish.yml` 里 `package=bailian-kb-dsh` job,走 `publish-kb-dsh.mjs` | `publish.yml` 里 `publish-stable/channel.mjs` | 不在 `bailian-cli` 依赖闭包内,版本与构建都不同,不能与 `bl` 共用同一条 script |
|
||||||
| tsconfig | 三个 | 一个 | 见下 |
|
| tsconfig | 三个 | 一个 | 见下 |
|
||||||
|
|
||||||
## tsconfig 三件套(改动前先读)
|
## tsconfig 三件套(改动前先读)
|
||||||
|
|
||||||
@@ -78,6 +78,39 @@ dsh plugin --profile dev add <本仓库>/packages/bailian-kb-dsh
|
|||||||
dsh --profile dev --dump-config # 应能看到 tool-bailian-kb row
|
dsh --profile dev --dump-config # 应能看到 tool-bailian-kb row
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 发布
|
||||||
|
|
||||||
|
入口与 `bl` 共用:Actions → **Publish** → `package=bailian-kb-dsh` + `mode=stable|channel`。共享的只有 workflow 入口与 checkout/pnpm/node/gitleaks/install 几步 setup;它走自己的 `tools/release/publish-kb-dsh.mjs`,**不**复用 `publish-stable.mjs` / `publish-channel.mjs`。
|
||||||
|
|
||||||
|
| | stable | channel |
|
||||||
|
| --------- | ------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||||
|
| 版本 | `package.json` 当前值(先手动 bump 并提交) | 临时 `0.0.0-beta-<sha>-<stamp>`,`finally` 还原,不提交 |
|
||||||
|
| npm tag | `latest` | 你传的 `channel`(dist-tag) |
|
||||||
|
| preflight | 工作区干净 + 必须在 `main` | 无 |
|
||||||
|
| git tag | `bailian-kb-dsh-v<version>`(与 `bl` 的 `v<version>` 错开命名空间) | 不打 tag |
|
||||||
|
| 审批 | `environment: production`(Required Reviewers) | 无 |
|
||||||
|
| 产物 | npm only | npm only |
|
||||||
|
|
||||||
|
两种模式都跑:`build`(tsc + web 隔离检查 + tsdown)→ 幂等性查重 → `pnpm pack` → `publint` + `gitleaks` → `pnpm publish --provenance`。
|
||||||
|
|
||||||
|
本地验证(不真发,需本地装 gitleaks):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node tools/release/publish-kb-dsh.mjs --dry-run
|
||||||
|
node tools/release/publish-kb-dsh.mjs --dry-run --channel dsh-beta
|
||||||
|
```
|
||||||
|
|
||||||
|
### 首发前的 npm 侧前置(仓外操作,一次性)
|
||||||
|
|
||||||
|
1. 占住包名 `bailian-kb-dsh`(OIDC Trusted Publishing 无法给不存在的包首发)
|
||||||
|
2. 在 npm 包设置里绑 Trusted Publisher:仓库 `modelstudioai/cli`、workflow `publish.yml`;stable 走 environment `production`,channel 无 environment
|
||||||
|
|
||||||
|
未完成这两步前触发 workflow 会在 `pnpm publish --provenance` 这一步失败(前面的 build / scan 仍会正常跑完)。
|
||||||
|
|
||||||
|
### 已知待办
|
||||||
|
|
||||||
|
- `publint` 会报一条 warning:`exports["./client"]` 是 CJS 但包为 `type: module`,建议改 `.cjs` 扩展名。这是迁移前就存在的状态,warning 不阻断发布;真要改得同时动宿主按 `exports["./client"]` 解析 `/plugins/<id>/client.js` 的 URL 契约,需 dsh 侧一起验证,不要单方面改
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
- 设计与实现计划归档:[docs/kb-dsh/](../kb-dsh/)
|
- 设计与实现计划归档:[docs/kb-dsh/](../kb-dsh/)
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ publish-stable.mjs / publish-channel.mjs ← 唯一发版入口
|
|||||||
|
|
||||||
`tools/release/lib/binary-release.mjs` 等是实现,一般不要单独当发版入口(调试可用)。
|
`tools/release/lib/binary-release.mjs` 等是实现,一般不要单独当发版入口(调试可用)。
|
||||||
|
|
||||||
|
### bailian-kb-dsh(独立版本、npm-only)
|
||||||
|
|
||||||
|
同一个 Publish 入口,`package=bailian-kb-dsh`。它走单独的 `tools/release/publish-kb-dsh.mjs`,不复用 `publish-stable.mjs` / `publish-channel.mjs`(版本独立、无 binary、无 OSS CDN)。详见 [dsh-plugin.md](dsh-plugin.md#发布)。
|
||||||
|
|
||||||
两种模式:
|
两种模式:
|
||||||
|
|
||||||
| 模式 | 用途 | 触发方式 |
|
| 模式 | 用途 | 触发方式 |
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Host-side bridge to the locally installed `bl` CLI (bailian-cli). The CLI's
|
* Host-side bridge to the locally installed `bl` CLI (bailian-cli): reads the
|
||||||
* credential file (`~/.bailian/config.json`) is its documented "output": the
|
* credentials an earlier `bl auth login` already stored, so a user who has
|
||||||
* console browser login (`bl auth login --console`) validates the api key the
|
* already set the CLI up does not have to configure this plugin a second time.
|
||||||
* console issues and persists it there in plain JSON — no CLI command ever
|
|
||||||
* echoes the value back (auth status / config show both mask), so reading the
|
|
||||||
* file is the only way to obtain it programmatically.
|
|
||||||
*
|
*
|
||||||
* Starting a login is NOT done through the CLI: it hard-codes
|
* Reading the CLI's credential file (`~/.bailian/config.json`) is the only way
|
||||||
* `needApiKey: !hasApiKey` and so refuses to have a fresh key issued once any
|
* to obtain the value programmatically — CLI commands mask stored keys on
|
||||||
* key is stored. See `console-login.ts`, which speaks the callback protocol
|
* output, so there is nothing to parse from stdout.
|
||||||
* directly and always asks for one.
|
*
|
||||||
|
* Starting a login is NOT done through the CLI; see `console-login.ts`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* Self-contained Bailian console login: the plugin speaks the console's
|
* Self-contained Bailian console login: the plugin drives the console's browser
|
||||||
* callback protocol itself instead of shelling out to `bl auth login --console`.
|
* login itself and persists what comes back, rather than shelling out to the
|
||||||
|
* `bl` CLI.
|
||||||
*
|
*
|
||||||
* Why not the CLI: it hard-codes `needApiKey: !hasApiKey`, so once any api key
|
* Driving the flow here lets it always request a freshly issued api key, so the
|
||||||
* is stored it never asks the console to issue a fresh one — you end up pairing
|
* key and the workspace id both come from the account that just signed in, and
|
||||||
* an old account's key with a new account's workspace id, and nothing warns
|
* the values land straight in the dsh stores.
|
||||||
* you. Driving the flow here lets us always pass `needapikey=true`, so the key
|
|
||||||
* and the workspace id both come from the account that just signed in, and the
|
|
||||||
* values land straight in the dsh stores without transiting the CLI's
|
|
||||||
* `~/.bailian/config.json`.
|
|
||||||
*
|
*
|
||||||
* Protocol (mirrors the CLI's implementation): bind a loopback-only port, open
|
* Shape of the flow: bind a loopback-only port, open the console login page
|
||||||
* `<console>/console-login?notice=127.0.0.1:<port>?state=<state>&needapikey=true`,
|
* pointed at that port, then accept one callback carrying the credentials as
|
||||||
* then accept one callback carrying the credentials as query parameters or a
|
* query parameters or a JSON / form-encoded body.
|
||||||
* JSON / form-encoded body. Note the URL shape: `state` is part of the `notice`
|
|
||||||
* value (separated by `?`), not a sibling query parameter.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execFile } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
@@ -30,12 +25,12 @@ const CONSOLE_ORIGINS: Record<string, string> = {
|
|||||||
/** How long the loopback listener waits for the browser callback. */
|
/** How long the loopback listener waits for the browser callback. */
|
||||||
const LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
|
const LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
|
||||||
|
|
||||||
/** Upper bound on a callback body, matching the CLI's limit. */
|
/** Upper bound on a callback body. */
|
||||||
const MAX_CALLBACK_BODY = 65536;
|
const MAX_CALLBACK_BODY = 65536;
|
||||||
|
|
||||||
/** Credentials the console callback can carry. */
|
/** Credentials the console callback can carry. */
|
||||||
export interface ConsoleLoginCredentials {
|
export interface ConsoleLoginCredentials {
|
||||||
/** Freshly issued DashScope api key (`needapikey=true` asks for one). */
|
/** Freshly issued DashScope api key. */
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
/** Workspace id of the account that signed in. */
|
/** Workspace id of the account that signed in. */
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
@@ -95,8 +90,8 @@ function readBody(req: http.IncomingMessage): Promise<string> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a callback body as JSON (optionally wrapped in `data`) or as form
|
* Parse a callback body as JSON (optionally wrapped in `data`) or as form
|
||||||
* encoding. Content-type is a hint only — the CLI falls back to trying both,
|
* encoding. Content-type is a hint only: both shapes occur in practice, so both
|
||||||
* and so do we, because the console has shipped both shapes.
|
* are attempted.
|
||||||
* @param raw - the raw request body.
|
* @param raw - the raw request body.
|
||||||
* @returns the flattened fields; an unparseable body yields no fields.
|
* @returns the flattened fields; an unparseable body yields no fields.
|
||||||
*/
|
*/
|
||||||
@@ -278,8 +273,8 @@ export async function startConsoleLogin(opts: {
|
|||||||
return { status: "failed", reason };
|
return { status: "failed", reason };
|
||||||
}
|
}
|
||||||
|
|
||||||
// `state` rides inside the `notice` value, and `needapikey=true` is the whole
|
// Ask for a freshly issued key, so the key and the workspace id cannot end up
|
||||||
// point: it makes the console issue a key for the account signing in.
|
// belonging to two different accounts.
|
||||||
const origin =
|
const origin =
|
||||||
(opts.site !== undefined ? CONSOLE_ORIGINS[opts.site] : undefined) ?? CONSOLE_ORIGINS.domestic!;
|
(opts.site !== undefined ? CONSOLE_ORIGINS[opts.site] : undefined) ?? CONSOLE_ORIGINS.domestic!;
|
||||||
const loginUrl =
|
const loginUrl =
|
||||||
|
|||||||
@@ -561,9 +561,9 @@ export function apply(ctx: Context, config: Config): void {
|
|||||||
sendJson(res, 200, consoleLoginState());
|
sendJson(res, 200, consoleLoginState());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Drive the console flow ourselves with `needapikey=true`, so the key
|
// Drive the console flow ourselves, requesting a freshly issued key,
|
||||||
// and the workspace id both belong to the account signing in now.
|
// so the key and the workspace id both belong to the account signing
|
||||||
// Persisting here keeps the plain key on the host.
|
// in now. Persisting here keeps the plain key on the host.
|
||||||
const started = await startConsoleLogin({
|
const started = await startConsoleLogin({
|
||||||
onComplete: async (credentials) => {
|
onComplete: async (credentials) => {
|
||||||
const written: string[] = [];
|
const written: string[] = [];
|
||||||
|
|||||||
@@ -2,25 +2,21 @@
|
|||||||
* Retrieval-service discovery for the plugin's internal cache. Not a model tool:
|
* Retrieval-service discovery for the plugin's internal cache. Not a model tool:
|
||||||
* see `KB_PATHS.serviceList`.
|
* see `KB_PATHS.serviceList`.
|
||||||
*
|
*
|
||||||
* Two verified server facts shape this module:
|
* Listing is paged, and a workspace can hold far more services than are useful
|
||||||
* - `page_size` is capped at 100 regardless of what is requested, so a workspace
|
* for routing, so this module stops after {@link MAX_PAGES} and reports the
|
||||||
* with hundreds of services needs many round trips. Test/CI workspaces reach
|
* shortfall rather than paging through everything. Only deployed services are
|
||||||
* the high hundreds (913 observed), which is pure noise for routing, so this
|
* requested: anything else is not callable by the default service version, so it
|
||||||
* module stops after {@link MAX_PAGES} and reports the shortfall instead of
|
* would be noise in the catalog the model reads.
|
||||||
* faithfully paging through it.
|
|
||||||
* - `agent_status: 'deployed'` is honored and means "deployed or edited". Only
|
|
||||||
* those are callable by the default agent version, so drafts never reach the
|
|
||||||
* model.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ServiceListResponse, ServiceScene } from "./api-types.js";
|
import type { ServiceListResponse, ServiceScene } from "./api-types.js";
|
||||||
import type { KbClient } from "./client.js";
|
import type { KbClient } from "./client.js";
|
||||||
import { KB_PATHS } from "./endpoints.js";
|
import { KB_PATHS } from "./endpoints.js";
|
||||||
|
|
||||||
/** Server page-size maximum; larger requests are silently clamped to this. */
|
/** Page size requested per listing call. */
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
|
|
||||||
/** Pages fetched per scene before reporting truncation (200 rows is far past the useful range). */
|
/** Pages fetched per scene before reporting truncation; well past the useful routing range. */
|
||||||
const MAX_PAGES = 2;
|
const MAX_PAGES = 2;
|
||||||
|
|
||||||
/** One deployed retrieval or Q&A service, reduced to the fields that inform routing. */
|
/** One deployed retrieval or Q&A service, reduced to the fields that inform routing. */
|
||||||
@@ -28,11 +24,11 @@ export interface ServiceEntry {
|
|||||||
agent_id: string;
|
agent_id: string;
|
||||||
agent_name: string;
|
agent_name: string;
|
||||||
scene: ServiceScene;
|
scene: ServiceScene;
|
||||||
/** `deployed` or `edited` — both are callable by the default version. */
|
/** Deployment status as reported for the service; only callable states are listed. */
|
||||||
status: string;
|
status: string;
|
||||||
/** Last modification timestamp; the only signal for "which of these is in use". */
|
/** Last modification timestamp; the only signal for "which of these is in use". */
|
||||||
modify_time?: string;
|
modify_time?: string;
|
||||||
/** Absent until the backend adds a description to the list response. */
|
/** Service description, when the listing provides one. */
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -427,15 +427,14 @@ export class BailianCardController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch credentials by signing in to the Bailian console. The Host drives
|
* Fetch credentials by signing in to the Bailian console. The Host drives
|
||||||
* the console's callback protocol itself, always asking for a freshly issued
|
* the console browser login itself, always requesting a freshly issued api
|
||||||
* api key, then persists the key into the credential store and the workspace
|
* key, then persists the key into the credential store and the workspace id
|
||||||
* id into the settings section — so both values belong to the account that
|
* into the settings section — so both values belong to the account that just
|
||||||
* just signed in, and the plain key never rides the wire to this page.
|
* signed in, and the plain key never rides the wire to this page.
|
||||||
*
|
*
|
||||||
* Deliberately does NOT adopt the bl CLI's stored login: reusing a key from
|
* Deliberately does NOT adopt the bl CLI's stored login: that path can pair
|
||||||
* `~/.bailian/config.json` can pair one account's key with another account's
|
* one account's key with another account's workspace id, and nothing would
|
||||||
* workspace id (the CLI refuses to re-issue once any key is stored), and
|
* flag the mismatch until a knowledge-base call fails.
|
||||||
* nothing would flag the mismatch until a knowledge-base call fails.
|
|
||||||
*/
|
*/
|
||||||
async autofill(): Promise<void> {
|
async autofill(): Promise<void> {
|
||||||
const phase = this.store.getSnapshot().autofill;
|
const phase = this.store.getSnapshot().autofill;
|
||||||
@@ -452,10 +451,8 @@ export class BailianCardController {
|
|||||||
/**
|
/**
|
||||||
* Ask the Host to open the console login page, then poll for the outcome.
|
* Ask the Host to open the console login page, then poll for the outcome.
|
||||||
* The Host persists the credentials itself when the callback lands, always
|
* The Host persists the credentials itself when the callback lands, always
|
||||||
* asking the console to issue a fresh key — so the key and the workspace id
|
* requesting a freshly issued key — so the key and the workspace id both come
|
||||||
* both come from the account signing in. (The bl CLI's own login refuses to
|
* from the account signing in.
|
||||||
* re-issue once any key is stored, which would otherwise pair an old
|
|
||||||
* account's key with a new account's workspace.)
|
|
||||||
*/
|
*/
|
||||||
private async runConsoleLogin(): Promise<void> {
|
private async runConsoleLogin(): Promise<void> {
|
||||||
let started: { status?: string; loginUrl?: string };
|
let started: { status?: string; loginUrl?: string };
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export const ALL_PACKAGES = [...PACKAGES, KSCLI_PACKAGE];
|
|||||||
// Deliberately absent from every list above: packages/bailian-kb-dsh (bailian-kb-dsh).
|
// Deliberately absent from every list above: packages/bailian-kb-dsh (bailian-kb-dsh).
|
||||||
// It is a dsh plugin — a downstream host adapter, not part of the bl release closure:
|
// It is a dsh plugin — a downstream host adapter, not part of the bl release closure:
|
||||||
// its version tracks the dsh rc cadence instead of the locked core/runtime/commands/cli/kscli
|
// its version tracks the dsh rc cadence instead of the locked core/runtime/commands/cli/kscli
|
||||||
// version, and it ships through .github/workflows/publish-kb-dsh.yml. So it is exempt from
|
// version, and it ships through publish.yml's separate `package=bailian-kb-dsh`
|
||||||
|
// job (tools/release/publish-kb-dsh.mjs). So it is exempt from
|
||||||
// loadAndValidatePackages' version-consistency check and from packAndScan. Not an oversight;
|
// loadAndValidatePackages' version-consistency check and from packAndScan. Not an oversight;
|
||||||
// see docs/agents/dsh-plugin.md before adding it here.
|
// see docs/agents/dsh-plugin.md before adding it here.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Publish bailian-kb-dsh (dsh plugin, downstream host adapter).
|
||||||
|
*
|
||||||
|
* Deliberately independent from publish-stable.mjs / publish-channel.mjs:
|
||||||
|
* - the plugin is NOT in the version-locked bl release set (packages.mjs
|
||||||
|
* PACKAGES / ALL_PACKAGES; see packages.mjs footer comment),
|
||||||
|
* - it uses tsc + tsdown instead of `vp pack`, and has no binary artifact,
|
||||||
|
* - it tags as `bailian-kb-dsh-v<version>` so its lightweight tags never
|
||||||
|
* collide with the bl `v<version>` namespace.
|
||||||
|
*
|
||||||
|
* Shared with the other publish scripts: dry-run gate, CI-only guard,
|
||||||
|
* per-mode preflight, `pnpm publish --provenance`, publint + gitleaks scan.
|
||||||
|
*/
|
||||||
|
import { mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
import { join } from "path";
|
||||||
|
import { parseArgs } from "util";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createTag,
|
||||||
|
currentBranch,
|
||||||
|
headSha7,
|
||||||
|
isWorkingTreeClean,
|
||||||
|
pushTag,
|
||||||
|
tagExists,
|
||||||
|
utcDateStamp,
|
||||||
|
} from "./lib/git.mjs";
|
||||||
|
import { npmViewExists, pnpmPack, pnpmPublish } from "./lib/npm.mjs";
|
||||||
|
import { ROOT } from "./lib/packages.mjs";
|
||||||
|
import { run } from "./lib/proc.mjs";
|
||||||
|
|
||||||
|
const PKG = { key: "kb-dsh", dir: "packages/bailian-kb-dsh", name: "bailian-kb-dsh" };
|
||||||
|
const PKG_JSON_PATH = join(ROOT, PKG.dir, "package.json");
|
||||||
|
|
||||||
|
function log(msg = "") {
|
||||||
|
process.stdout.write(`${msg}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function step(msg) {
|
||||||
|
log(`\n==> ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageJson() {
|
||||||
|
return JSON.parse(readFileSync(PKG_JSON_PATH, "utf-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePackageJson(json) {
|
||||||
|
writeFileSync(PKG_JSON_PATH, `${JSON.stringify(json, null, 2)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { values } = parseArgs({
|
||||||
|
options: {
|
||||||
|
channel: { type: "string" },
|
||||||
|
"dry-run": { type: "boolean", default: false },
|
||||||
|
},
|
||||||
|
allowPositionals: false,
|
||||||
|
});
|
||||||
|
const channel = values.channel;
|
||||||
|
const dryRun = values["dry-run"];
|
||||||
|
const isChannel = channel !== undefined && channel !== "";
|
||||||
|
|
||||||
|
if (!dryRun && !process.env.CI) {
|
||||||
|
process.stderr.write("publish-kb-dsh is CI-only. Pass --dry-run to test locally.\n");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot for channel mode: the temporary version bump must be reverted in
|
||||||
|
// `finally`, even on mid-flight failure. Stable mode does not bump, so the
|
||||||
|
// snapshot is a no-op that keeps the restore path uniform.
|
||||||
|
const originalPackageJson = readFileSync(PKG_JSON_PATH, "utf-8");
|
||||||
|
function restoreOriginal() {
|
||||||
|
writeFileSync(PKG_JSON_PATH, originalPackageJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isChannel) {
|
||||||
|
step(`channel release: ${channel}`);
|
||||||
|
} else {
|
||||||
|
step("stable release");
|
||||||
|
if (!dryRun) {
|
||||||
|
if (!isWorkingTreeClean()) {
|
||||||
|
throw new Error("git working tree is not clean; commit or stash first.");
|
||||||
|
}
|
||||||
|
const branch = currentBranch();
|
||||||
|
if (branch !== "main") {
|
||||||
|
throw new Error(`must publish from main, currently on ${branch}.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log("[dry-run] skipping working-tree + branch preflight");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the version we are about to publish.
|
||||||
|
const originalVersion = readPackageJson().version;
|
||||||
|
let publishVersion = originalVersion;
|
||||||
|
if (isChannel) {
|
||||||
|
// Match the shape used by publish-channel.mjs (bl channel releases) so
|
||||||
|
// consumers see a familiar dist-tag payload; the leading 0.0.0 keeps
|
||||||
|
// semver from ever preferring a beta over a real release.
|
||||||
|
const sha = headSha7();
|
||||||
|
const stamp = utcDateStamp();
|
||||||
|
publishVersion = `0.0.0-beta-${sha}-${stamp}`;
|
||||||
|
step(`temporarily bump ${PKG.name} to ${publishVersion} (not committed)`);
|
||||||
|
const json = readPackageJson();
|
||||||
|
json.version = publishVersion;
|
||||||
|
writePackageJson(json);
|
||||||
|
}
|
||||||
|
log(`${PKG.name}@${publishVersion}`);
|
||||||
|
|
||||||
|
step(`build ${PKG.name}`);
|
||||||
|
run("pnpm", ["--filter", PKG.name, "run", "build"]);
|
||||||
|
|
||||||
|
step(`idempotency: check ${publishVersion} against registry`);
|
||||||
|
const alreadyPublished = npmViewExists(PKG.name, publishVersion);
|
||||||
|
log(`${PKG.name}@${publishVersion}: ${alreadyPublished ? "already published" : "to publish"}`);
|
||||||
|
|
||||||
|
if (alreadyPublished) {
|
||||||
|
if (!isChannel) {
|
||||||
|
throw new Error(
|
||||||
|
`version ${publishVersion} is already published; bump ${PKG.dir}/package.json before retrying.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
log("channel version already published; skipping npm publish");
|
||||||
|
} else {
|
||||||
|
step("pack + scan (publint, gitleaks)");
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "bailian-kb-dsh-release-"));
|
||||||
|
try {
|
||||||
|
const packJson = readPackageJson();
|
||||||
|
const tarball = pnpmPack(PKG, tempDir, packJson);
|
||||||
|
run("tar", ["-xzf", tarball, "-C", tempDir], { stdio: "pipe" });
|
||||||
|
const extractDir = join(tempDir, `extract-${PKG.key}`);
|
||||||
|
renameSync(join(tempDir, "package"), extractDir);
|
||||||
|
run("npx", ["--yes", "publint", extractDir]);
|
||||||
|
run("gitleaks", ["detect", "--source", extractDir, "--no-git", "--redact"]);
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const npmTag = isChannel ? channel : "latest";
|
||||||
|
step(`publish ${PKG.name}@${publishVersion} (tag=${npmTag}, provenance)`);
|
||||||
|
pnpmPublish(PKG, { tag: npmTag, provenance: true, dryRun });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChannel) {
|
||||||
|
log(`\nchannel release complete: ${channel}@${publishVersion} (npm-only, no tag)`);
|
||||||
|
} else {
|
||||||
|
// Namespaced tag: bl uses `v<version>`, so we prefix with the package name
|
||||||
|
// to avoid colliding when a bl release happens to share the same version
|
||||||
|
// fragment.
|
||||||
|
const tag = `${PKG.name}-v${publishVersion}`;
|
||||||
|
if (dryRun) {
|
||||||
|
log("\n[dry-run] skipping git tag");
|
||||||
|
} else if (tagExists(tag)) {
|
||||||
|
log(`tag ${tag} already exists; skipping tag push`);
|
||||||
|
} else {
|
||||||
|
step(`tag ${tag} and push`);
|
||||||
|
createTag(tag);
|
||||||
|
pushTag(tag);
|
||||||
|
}
|
||||||
|
log(`\nstable release complete: ${PKG.name}@${publishVersion} (npm + tag)`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(`\nrelease publish-kb-dsh failed: ${error.message}\n`);
|
||||||
|
// Use exitCode (not process.exit) so `finally` restores any channel bump.
|
||||||
|
process.exitCode = 1;
|
||||||
|
} finally {
|
||||||
|
restoreOriginal();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user