Skip to content

Commit 4745d70

Browse files
committed
feat(release): 支持 knowledge-studio-cli 的构建与发布流程
- 新增发布工作流 publish-knowledge.yml,支持 stable 和 channel 模式发布含 knowledge 的包 - runCheck 函数增加 knowledge 参数,支持同时构建和验证 knowledge-studio-cli 包 - publish-stable 和 publish-channel 脚本支持传入 knowledge 参数,调整发布的包列表 - packAndScan 函数支持指定发布包列表,增强灵活性 - 扩展 packages 模块,新增 ALL_PACKAGES 常量包含所有包(基础包加 knowledge-studio-cli) - loadAndValidate
1 parent eaa6b07 commit 4745d70

7 files changed

Lines changed: 138 additions & 27 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Publish Knowledge
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
mode:
7+
description: "Publish mode"
8+
required: true
9+
type: choice
10+
options:
11+
- channel
12+
- stable
13+
channel:
14+
description: "dist-tag (channel mode only, e.g. mcp/plugin/advisor)"
15+
required: false
16+
type: string
17+
18+
concurrency:
19+
group: publish-knowledge-${{ inputs.mode }}-${{ inputs.channel }}
20+
cancel-in-progress: false
21+
22+
jobs:
23+
publish-stable:
24+
if: inputs.mode == 'stable'
25+
name: publish stable (with knowledge) to npm + tag
26+
runs-on: ubuntu-latest
27+
environment: production # Required Reviewers gate
28+
permissions:
29+
contents: write # push lightweight tag to origin
30+
id-token: write # OIDC for npm Trusted Publishing + provenance
31+
steps:
32+
- uses: actions/checkout@v6
33+
34+
- uses: pnpm/action-setup@v6
35+
36+
- uses: actions/setup-node@v6
37+
with:
38+
node-version: "24"
39+
cache: pnpm
40+
registry-url: "https://registry.npmjs.org/"
41+
42+
- name: Install gitleaks
43+
run: |
44+
set -euo pipefail
45+
GITLEAKS_VERSION=8.21.2
46+
curl -sSfL \
47+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
48+
| sudo tar -xz -C /usr/local/bin gitleaks
49+
gitleaks version
50+
51+
- run: pnpm install --frozen-lockfile
52+
53+
- name: publish-stable (with knowledge)
54+
run: node tools/release/publish-stable.mjs --knowledge
55+
56+
publish-channel:
57+
if: inputs.mode == 'channel'
58+
name: publish beta (with knowledge) to npm
59+
runs-on: ubuntu-latest
60+
permissions:
61+
contents: read # no tag, no Release; just publish
62+
id-token: write # OIDC for npm Trusted Publishing + provenance
63+
steps:
64+
- uses: actions/checkout@v6
65+
66+
- uses: pnpm/action-setup@v6
67+
68+
- uses: actions/setup-node@v6
69+
with:
70+
node-version: "24"
71+
cache: pnpm
72+
registry-url: "https://registry.npmjs.org/"
73+
74+
- name: Install gitleaks
75+
run: |
76+
set -euo pipefail
77+
GITLEAKS_VERSION=8.21.2
78+
curl -sSfL \
79+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
80+
| sudo tar -xz -C /usr/local/bin gitleaks
81+
gitleaks version
82+
83+
- run: pnpm install --frozen-lockfile
84+
85+
- name: publish-channel (with knowledge)
86+
run: node tools/release/publish-channel.mjs --knowledge --channel "${{ inputs.channel }}"

tools/release/check.mjs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { fileURLToPath } from "url";
44
import { packAndScan } from "./lib/pack-scan.mjs";
55
import { run } from "./lib/proc.mjs";
66
import { assertReadmeSync, loadAndValidatePackages } from "./lib/validate.mjs";
7+
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
78

89
function log(msg = "") {
910
process.stdout.write(`${msg}\n`);
@@ -17,20 +18,24 @@ function step(msg) {
1718
* Pure-validation pipeline. Reusable from publish-stable / publish-channel.
1819
* Returns { coreJson, cliJson } for callers that need the parsed package.jsons.
1920
*
20-
* @param {{ channel?: boolean }} [options]
21+
* @param {{ channel?: boolean, knowledge?: boolean }} [options]
2122
* @param {boolean} [options.channel] — When true (publish-channel): regenerate
2223
* `reference/` and assert it matches git, but do not sync `SKILL.md` from the
2324
* temporary beta `package.json` version (repo skill stays aligned with stable).
25+
* @param {boolean} [options.knowledge] — When true: also build and validate
26+
* knowledge-studio-cli alongside the base packages.
2427
*/
2528
export async function runCheck(options = {}) {
2629
const channel = options.channel === true;
30+
const knowledge = options.knowledge === true;
31+
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
2732

2833
step("pnpm install --frozen-lockfile");
2934
run("pnpm", ["install", "--frozen-lockfile"]);
3035

3136
step("metadata: README sync, version consistency, workspace:* dep");
3237
assertReadmeSync();
33-
const { coreJson, cliJson } = loadAndValidatePackages();
38+
const { coreJson, cliJson } = loadAndValidatePackages({ packages });
3439
log(`bailian-cli-core@${coreJson.version}`);
3540
log(`bailian-cli@${cliJson.version}`);
3641

@@ -65,11 +70,13 @@ export async function runCheck(options = {}) {
6570
step("build bailian-cli");
6671
run("pnpm", ["--filter", "bailian-cli", "run", "build"]);
6772

68-
step("build knowledge-studio-cli");
69-
run("pnpm", ["--filter", "knowledge-studio-cli", "run", "build"]);
73+
if (knowledge) {
74+
step("build knowledge-studio-cli");
75+
run("pnpm", ["--filter", "knowledge-studio-cli", "run", "build"]);
76+
}
7077

7178
step("pack + scan (publint, gitleaks)");
72-
packAndScan({ log });
79+
packAndScan({ log, packages });
7380

7481
log("\nrelease check passed.");
7582
return { coreJson, cliJson };

tools/release/lib/pack-scan.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ function extractTarball(tarball, tempDir, key) {
1313
return extractDir;
1414
}
1515

16-
export function packAndScan({ log }) {
16+
export function packAndScan({ log, packages }) {
17+
const pkgs = packages ?? PACKAGES;
1718
const tempDir = mkdtempSync(join(tmpdir(), "bailian-release-"));
1819
try {
19-
for (const pkg of PACKAGES) {
20+
for (const pkg of pkgs) {
2021
const json = readPackageJson(pkg);
2122
log(`packing ${pkg.name}@${json.version}`);
2223
const tarball = pnpmPack(pkg, tempDir, json);

tools/release/lib/packages.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ import { fileURLToPath } from "url";
44

55
export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
66

7-
// Dependency order: core ← runtime ← commands ← cli / kscli.
7+
// Dependency order: core ← runtime ← commands ← cli.
88
// Consumers rely on this ordering for build/publish (dependencies first).
99
export const PACKAGES = [
1010
{ key: "core", dir: "packages/core", name: "bailian-cli-core" },
1111
{ key: "runtime", dir: "packages/runtime", name: "bailian-cli-runtime" },
1212
{ key: "commands", dir: "packages/commands", name: "bailian-cli-commands" },
1313
{ key: "cli", dir: "packages/cli", name: "bailian-cli" },
14-
{ key: "kscli", dir: "packages/kscli", name: "knowledge-studio-cli" },
1514
];
1615

16+
// knowledge-studio-cli shares the same library deps as bailian-cli.
17+
// Published via a separate workflow (publish-knowledge.yml) with --knowledge flag.
18+
export const KSCLI_PACKAGE = { key: "kscli", dir: "packages/kscli", name: "knowledge-studio-cli" };
19+
export const ALL_PACKAGES = [...PACKAGES, KSCLI_PACKAGE];
20+
1721
export function readJson(path) {
1822
return JSON.parse(readFileSync(path, "utf-8"));
1923
}

tools/release/lib/validate.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ export function assertReadmeSync() {
1818
}
1919
}
2020

21-
export function loadAndValidatePackages() {
22-
const internalNames = new Set(PACKAGES.map((p) => p.name));
21+
export function loadAndValidatePackages({ packages } = {}) {
22+
const pkgs = packages ?? PACKAGES;
23+
const internalNames = new Set(pkgs.map((p) => p.name));
2324
const jsonByKey = new Map();
24-
for (const pkg of PACKAGES) {
25+
for (const pkg of pkgs) {
2526
const json = readPackageJson(pkg);
2627
if (json.name !== pkg.name) {
2728
throw new Error(`${pkg.dir} name must be ${pkg.name}, got ${json.name}`);
@@ -32,7 +33,7 @@ export function loadAndValidatePackages() {
3233
const coreJson = jsonByKey.get("core");
3334
const version = coreJson.version;
3435

35-
for (const pkg of PACKAGES) {
36+
for (const pkg of pkgs) {
3637
const json = jsonByKey.get(pkg.key);
3738
// All packages release in lockstep, so every version must match.
3839
if (json.version !== version) {

tools/release/publish-channel.mjs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { parseArgs } from "util";
55
import { runCheck } from "./check.mjs";
66
import { headSha7, utcDateStamp } from "./lib/git.mjs";
77
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
8-
import { PACKAGES, packageJsonPath, readPackageJson, writePackageJson } from "./lib/packages.mjs";
8+
import {
9+
ALL_PACKAGES,
10+
PACKAGES,
11+
packageJsonPath,
12+
readPackageJson,
13+
writePackageJson,
14+
} from "./lib/packages.mjs";
915
import { assertChannel } from "./lib/validate.mjs";
1016

1117
function log(msg = "") {
@@ -20,11 +26,14 @@ const { values } = parseArgs({
2026
options: {
2127
channel: { type: "string" },
2228
"dry-run": { type: "boolean", default: false },
29+
knowledge: { type: "boolean", default: false },
2330
},
2431
allowPositionals: false,
2532
});
2633
const channel = values.channel;
2734
const dryRun = values["dry-run"];
35+
const knowledge = values.knowledge;
36+
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
2837
assertChannel(channel);
2938

3039
if (!dryRun && !process.env.CI) {
@@ -34,7 +43,7 @@ if (!dryRun && !process.env.CI) {
3443

3544
// Snapshot every package.json so the temporary version bump is reverted in
3645
// `finally`, even when the release fails midway.
37-
const originals = PACKAGES.map((pkg) => {
46+
const originals = packages.map((pkg) => {
3847
const path = packageJsonPath(pkg);
3948
return { pkg, path, content: readFileSync(path, "utf-8") };
4049
});
@@ -51,28 +60,28 @@ try {
5160
log(`channel=${channel} version=${betaVersion}`);
5261

5362
step("temporarily bump package.json (not committed)");
54-
for (const pkg of PACKAGES) {
63+
for (const pkg of packages) {
5564
const json = readPackageJson(pkg);
5665
json.version = betaVersion;
5766
writePackageJson(pkg, json);
5867
}
5968
// pnpm pack resolves `workspace:*` to the in-tree version, so each tarball
6069
// will depend on its siblings at <betaVersion> after this bump.
6170

62-
await runCheck({ channel: true });
71+
await runCheck({ channel: true, knowledge });
6372

6473
step(`idempotency: check ${betaVersion} against registry`);
6574
const published = new Map();
66-
for (const pkg of PACKAGES) {
75+
for (const pkg of packages) {
6776
const exists = npmViewExists(pkg.name, betaVersion);
6877
published.set(pkg.key, exists);
6978
log(`${pkg.name}@${betaVersion}: ${exists ? "already published" : "to publish"}`);
7079
}
71-
if (PACKAGES.every((pkg) => published.get(pkg.key))) {
80+
if (packages.every((pkg) => published.get(pkg.key))) {
7281
log("\nall packages already published; nothing to do.");
7382
} else {
74-
// Publish in dependency order (core → runtime → commands → cli).
75-
for (const pkg of PACKAGES) {
83+
// Publish in dependency order (core → runtime → commands → cli [→ kscli]).
84+
for (const pkg of packages) {
7685
if (published.get(pkg.key)) continue;
7786
step(`publish ${pkg.name}@${betaVersion} (tag=${channel}, provenance)`);
7887
pnpmPublish(pkg, { tag: channel, provenance: true, dryRun });

tools/release/publish-stable.mjs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { parseArgs } from "util";
44
import { runCheck } from "./check.mjs";
55
import { createTag, currentBranch, isWorkingTreeClean, pushTag, tagExists } from "./lib/git.mjs";
66
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
7-
import { PACKAGES } from "./lib/packages.mjs";
7+
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
88

99
function log(msg = "") {
1010
process.stdout.write(`${msg}\n`);
@@ -17,10 +17,13 @@ function step(msg) {
1717
const { values } = parseArgs({
1818
options: {
1919
"dry-run": { type: "boolean", default: false },
20+
knowledge: { type: "boolean", default: false },
2021
},
2122
allowPositionals: false,
2223
});
2324
const dryRun = values["dry-run"];
25+
const knowledge = values.knowledge;
26+
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
2427

2528
try {
2629
if (!dryRun && !process.env.CI) {
@@ -40,23 +43,23 @@ try {
4043
log("[dry-run] skipping working-tree + branch preflight");
4144
}
4245

43-
const { coreJson } = await runCheck();
46+
const { coreJson } = await runCheck({ knowledge });
4447
const version = coreJson.version; // all packages share this, asserted by runCheck
4548

4649
step(`idempotency: check ${version} against registry`);
4750
const published = new Map();
48-
for (const pkg of PACKAGES) {
51+
for (const pkg of packages) {
4952
const exists = npmViewExists(pkg.name, version);
5053
published.set(pkg.key, exists);
5154
log(`${pkg.name}@${version}: ${exists ? "already published" : "to publish"}`);
5255
}
53-
if (PACKAGES.every((pkg) => published.get(pkg.key))) {
56+
if (packages.every((pkg) => published.get(pkg.key))) {
5457
log("\nall packages already published; nothing to do.");
5558
process.exit(0);
5659
}
5760

58-
// Publish in dependency order (core → runtime → commands → cli).
59-
for (const pkg of PACKAGES) {
61+
// Publish in dependency order (core → runtime → commands → cli [→ kscli]).
62+
for (const pkg of packages) {
6063
if (published.get(pkg.key)) continue;
6164
step(`publish ${pkg.name}@${version} (tag=latest, provenance)`);
6265
pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun });

0 commit comments

Comments
 (0)