Merge branch 'main' into feat/self-built-framework

This commit is contained in:
若麒
2026-07-08 14:05:48 +08:00
parent d20e037dec
commit b0c48bab6b
172 changed files with 12240 additions and 886 deletions
+15 -5
View File
@@ -4,6 +4,7 @@ import { fileURLToPath } from "url";
import { packAndScan } from "./lib/pack-scan.mjs";
import { run } from "./lib/proc.mjs";
import { assertReadmeSync, loadAndValidatePackages } from "./lib/validate.mjs";
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
function log(msg = "") {
process.stdout.write(`${msg}\n`);
@@ -17,25 +18,29 @@ function step(msg) {
* Pure-validation pipeline. Reusable from publish-stable / publish-channel.
* Returns { coreJson, cliJson } for callers that need the parsed package.jsons.
*
* @param {{ channel?: boolean }} [options]
* @param {{ channel?: boolean, knowledge?: boolean }} [options]
* @param {boolean} [options.channel] — When true (publish-channel): regenerate
* `reference/` and assert it matches git, but do not sync `SKILL.md` from the
* temporary beta `package.json` version (repo skill stays aligned with stable).
* @param {boolean} [options.knowledge] — When true: also build and validate
* knowledge-studio-cli alongside the base packages.
*/
export async function runCheck(options = {}) {
const channel = options.channel === true;
const knowledge = options.knowledge === true;
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
step("pnpm install --frozen-lockfile");
run("pnpm", ["install", "--frozen-lockfile"]);
step("metadata: README sync, version consistency, workspace:* dep");
assertReadmeSync();
const { coreJson, cliJson } = loadAndValidatePackages();
const { coreJson, cliJson } = loadAndValidatePackages({ packages });
log(`bailian-cli-core@${coreJson.version}`);
log(`bailian-cli@${cliJson.version}`);
step("build bailian-cli-core");
run("pnpm", ["--filter", "bailian-cli-core", "run", "build"]);
step("build bailian-cli dependencies (core, commands, runtime)");
run("pnpm", ["--filter", "bailian-cli^...", "run", "build"]);
step(
channel
@@ -63,8 +68,13 @@ export async function runCheck(options = {}) {
step("build bailian-cli");
run("pnpm", ["--filter", "bailian-cli", "run", "build"]);
if (knowledge) {
step("build knowledge-studio-cli");
run("pnpm", ["--filter", "knowledge-studio-cli", "run", "build"]);
}
step("pack + scan (publint, gitleaks)");
packAndScan({ log });
packAndScan({ log, packages });
log("\nrelease check passed.");
return { coreJson, cliJson };
+3 -2
View File
@@ -13,10 +13,11 @@ function extractTarball(tarball, tempDir, key) {
return extractDir;
}
export function packAndScan({ log }) {
export function packAndScan({ log, packages }) {
const pkgs = packages ?? PACKAGES;
const tempDir = mkdtempSync(join(tmpdir(), "bailian-release-"));
try {
for (const pkg of PACKAGES) {
for (const pkg of pkgs) {
const json = readPackageJson(pkg);
log(`packing ${pkg.name}@${json.version}`);
const tarball = pnpmPack(pkg, tempDir, json);
+7
View File
@@ -6,9 +6,16 @@ export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..")
export const PACKAGES = [
{ key: "core", dir: "packages/core", name: "bailian-cli-core" },
{ key: "runtime", dir: "packages/runtime", name: "bailian-cli-runtime" },
{ key: "commands", dir: "packages/commands", name: "bailian-cli-commands" },
{ key: "cli", dir: "packages/cli", name: "bailian-cli" },
];
// knowledge-studio-cli shares the same library deps as bailian-cli.
// Published via publish.yml with package=knowledge-studio-cli (passes --knowledge flag).
export const KSCLI_PACKAGE = { key: "kscli", dir: "packages/kscli", name: "knowledge-studio-cli" };
export const ALL_PACKAGES = [...PACKAGES, KSCLI_PACKAGE];
export function readJson(path) {
return JSON.parse(readFileSync(path, "utf-8"));
}
+20 -13
View File
@@ -18,9 +18,11 @@ export function assertReadmeSync() {
}
}
export function loadAndValidatePackages() {
export function loadAndValidatePackages({ packages } = {}) {
const pkgs = packages ?? PACKAGES;
const internalNames = new Set(pkgs.map((p) => p.name));
const jsonByKey = new Map();
for (const pkg of PACKAGES) {
for (const pkg of pkgs) {
const json = readPackageJson(pkg);
if (json.name !== pkg.name) {
throw new Error(`${pkg.dir} name must be ${pkg.name}, got ${json.name}`);
@@ -30,18 +32,23 @@ export function loadAndValidatePackages() {
const coreJson = jsonByKey.get("core");
const cliJson = jsonByKey.get("cli");
const version = coreJson.version;
if (cliJson.version !== coreJson.version) {
throw new Error(
`core and cli versions must match, got ${coreJson.version} and ${cliJson.version}.`,
);
}
const cliCoreDep = cliJson.dependencies?.["bailian-cli-core"];
if (cliCoreDep !== "workspace:*") {
throw new Error(
`packages/cli source dependency on bailian-cli-core must be "workspace:*", got ${cliCoreDep}.`,
);
for (const pkg of pkgs) {
const json = jsonByKey.get(pkg.key);
if (json.version !== version) {
throw new Error(
`all package versions must match ${version} (bailian-cli-core), ` +
`but ${pkg.name} is ${json.version}.`,
);
}
for (const [dep, range] of Object.entries(json.dependencies ?? {})) {
if (internalNames.has(dep) && range !== "workspace:*") {
throw new Error(
`${pkg.name} dependency on ${dep} must be "workspace:*", got ${String(range)}.`,
);
}
}
}
return { coreJson, cliJson };
+31 -31
View File
@@ -6,7 +6,8 @@ import { runCheck } from "./check.mjs";
import { headSha7, utcDateStamp } from "./lib/git.mjs";
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
import {
findPackage,
ALL_PACKAGES,
PACKAGES,
packageJsonPath,
readPackageJson,
writePackageJson,
@@ -25,11 +26,14 @@ const { values } = parseArgs({
options: {
channel: { type: "string" },
"dry-run": { type: "boolean", default: false },
knowledge: { type: "boolean", default: false },
},
allowPositionals: false,
});
const channel = values.channel;
const dryRun = values["dry-run"];
const knowledge = values.knowledge;
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
assertChannel(channel);
if (!dryRun && !process.env.CI) {
@@ -37,16 +41,15 @@ if (!dryRun && !process.env.CI) {
process.exit(1);
}
const core = findPackage("core");
const cli = findPackage("cli");
const corePath = packageJsonPath(core);
const cliPath = packageJsonPath(cli);
const coreOriginal = readFileSync(corePath, "utf-8");
const cliOriginal = readFileSync(cliPath, "utf-8");
// Snapshot every package.json so the temporary version bump is reverted in
// `finally`, even when the release fails midway.
const originals = packages.map((pkg) => {
const path = packageJsonPath(pkg);
return { pkg, path, content: readFileSync(path, "utf-8") };
});
function restoreOriginals() {
writeFileSync(corePath, coreOriginal);
writeFileSync(cliPath, cliOriginal);
for (const { path, content } of originals) writeFileSync(path, content);
}
try {
@@ -57,32 +60,29 @@ try {
log(`channel=${channel} version=${betaVersion}`);
step("temporarily bump package.json (not committed)");
const coreJson = readPackageJson(core);
const cliJson = readPackageJson(cli);
coreJson.version = betaVersion;
cliJson.version = betaVersion;
writePackageJson(core, coreJson);
writePackageJson(cli, cliJson);
// pnpm pack resolves `workspace:*` to the in-tree version, so CLI tarball
// will depend on bailian-cli-core@<betaVersion> after this bump.
for (const pkg of packages) {
const json = readPackageJson(pkg);
json.version = betaVersion;
writePackageJson(pkg, json);
}
await runCheck({ channel: true });
await runCheck({ channel: true, knowledge });
step(`idempotency: check ${betaVersion} against registry`);
const corePublished = npmViewExists(core.name, betaVersion);
const cliPublished = npmViewExists(cli.name, betaVersion);
log(`${core.name}@${betaVersion}: ${corePublished ? "already published" : "to publish"}`);
log(`${cli.name}@${betaVersion}: ${cliPublished ? "already published" : "to publish"}`);
if (corePublished && cliPublished) {
log("\nboth packages already published; nothing to do.");
const published = new Map();
for (const pkg of packages) {
const exists = npmViewExists(pkg.name, betaVersion);
published.set(pkg.key, exists);
log(`${pkg.name}@${betaVersion}: ${exists ? "already published" : "to publish"}`);
}
if (packages.every((pkg) => published.get(pkg.key))) {
log("\nall packages already published; nothing to do.");
} else {
if (!corePublished) {
step(`publish ${core.name}@${betaVersion} (tag=${channel}, provenance)`);
pnpmPublish(core, { tag: channel, provenance: true, dryRun });
}
if (!cliPublished) {
step(`publish ${cli.name}@${betaVersion} (tag=${channel}, provenance)`);
pnpmPublish(cli, { tag: channel, provenance: true, dryRun });
// Publish in dependency order (core → runtime → commands → cli [→ kscli]).
for (const pkg of packages) {
if (published.get(pkg.key)) continue;
step(`publish ${pkg.name}@${betaVersion} (tag=${channel}, provenance)`);
pnpmPublish(pkg, { tag: channel, provenance: true, dryRun });
}
}
+19 -18
View File
@@ -4,7 +4,7 @@ import { parseArgs } from "util";
import { runCheck } from "./check.mjs";
import { createTag, currentBranch, isWorkingTreeClean, pushTag, tagExists } from "./lib/git.mjs";
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
import { findPackage } from "./lib/packages.mjs";
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
function log(msg = "") {
process.stdout.write(`${msg}\n`);
@@ -17,10 +17,13 @@ function step(msg) {
const { values } = parseArgs({
options: {
"dry-run": { type: "boolean", default: false },
knowledge: { type: "boolean", default: false },
},
allowPositionals: false,
});
const dryRun = values["dry-run"];
const knowledge = values.knowledge;
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
try {
if (!dryRun && !process.env.CI) {
@@ -40,28 +43,26 @@ try {
log("[dry-run] skipping working-tree + branch preflight");
}
const { coreJson } = await runCheck();
const version = coreJson.version; // === cliJson.version, asserted by runCheck
const { coreJson } = await runCheck({ knowledge });
const version = coreJson.version; // all packages share this, asserted by runCheck
step(`idempotency: check ${version} against registry`);
const core = findPackage("core");
const cli = findPackage("cli");
const corePublished = npmViewExists(core.name, version);
const cliPublished = npmViewExists(cli.name, version);
log(`${core.name}@${version}: ${corePublished ? "already published" : "to publish"}`);
log(`${cli.name}@${version}: ${cliPublished ? "already published" : "to publish"}`);
if (corePublished && cliPublished) {
log("\nboth packages already published; nothing to do.");
const published = new Map();
for (const pkg of packages) {
const exists = npmViewExists(pkg.name, version);
published.set(pkg.key, exists);
log(`${pkg.name}@${version}: ${exists ? "already published" : "to publish"}`);
}
if (packages.every((pkg) => published.get(pkg.key))) {
log("\nall packages already published; nothing to do.");
process.exit(0);
}
if (!corePublished) {
step(`publish ${core.name}@${version} (tag=latest, provenance)`);
pnpmPublish(core, { tag: "latest", provenance: true, dryRun });
}
if (!cliPublished) {
step(`publish ${cli.name}@${version} (tag=latest, provenance)`);
pnpmPublish(cli, { tag: "latest", provenance: true, dryRun });
// Publish in dependency order (core → runtime → commands → cli [→ kscli]).
for (const pkg of packages) {
if (published.get(pkg.key)) continue;
step(`publish ${pkg.name}@${version} (tag=latest, provenance)`);
pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun });
}
if (dryRun) {