Config: separate core/plugin baseline entries (#60162)

* Config: separate core/plugin baseline entries

* Config: split config baseline by kind

* Config: split generated baselines by kind

* chore(build): skip generated baseline shards in local tooling

* chore(build): forbid generated docs in npm pack
This commit is contained in:
Vincent Koc
2026-04-03 18:26:23 +09:00
committed by GitHub
parent 9e58a0892b
commit f5c3b409ea
17 changed files with 82094 additions and 19695 deletions

View File

@@ -33,6 +33,8 @@ node_modules
**/.next
coverage
**/coverage
docs/.generated
**/.generated
*.log
tmp
**/tmp

View File

@@ -3,7 +3,9 @@
These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata.
- Do not edit `config-baseline.json` by hand.
- Do not edit `config-baseline.jsonl` by hand.
- Do not edit `config-baseline.core.json` by hand.
- Do not edit `config-baseline.channel.json` by hand.
- Do not edit `config-baseline.plugin.json` by hand.
- Do not edit `plugin-sdk-api-baseline.json` by hand.
- Do not edit `plugin-sdk-api-baseline.jsonl` by hand.
- Regenerate config baseline artifacts with `pnpm config:docs:gen`.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
const DEFAULT_SKIPPED_DIR_NAMES = new Set(["node_modules", "dist", "coverage"]);
const DEFAULT_SKIPPED_DIR_NAMES = new Set(["node_modules", "dist", "coverage", ".generated"]);
export function isCodeFile(filePath: string): boolean {
if (filePath.endsWith(".d.ts")) {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import path from "node:path";
import { fileURLToPath } from "node:url";
import { writeConfigDocBaselineStatefile } from "../src/config/doc-baseline.js";
import { writeConfigDocBaselineArtifacts } from "../src/config/doc-baseline.js";
const args = new Set(process.argv.slice(2));
const checkOnly = args.has("--check");
@@ -12,7 +12,7 @@ if (checkOnly && args.has("--write")) {
}
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const result = await writeConfigDocBaselineStatefile({
const result = await writeConfigDocBaselineArtifacts({
repoRoot,
check: checkOnly,
});
@@ -20,15 +20,22 @@ const result = await writeConfigDocBaselineStatefile({
if (checkOnly) {
if (!result.changed) {
console.log(
`OK ${path.relative(repoRoot, result.jsonPath)} ${path.relative(repoRoot, result.statefilePath)}`,
[
`OK ${path.relative(repoRoot, result.jsonPaths.combined)}`,
`OK ${path.relative(repoRoot, result.jsonPaths.core)}`,
`OK ${path.relative(repoRoot, result.jsonPaths.channel)}`,
`OK ${path.relative(repoRoot, result.jsonPaths.plugin)}`,
].join("\n"),
);
process.exit(0);
}
console.error(
[
"Config baseline drift detected.",
`Expected current: ${path.relative(repoRoot, result.jsonPath)}`,
`Expected current: ${path.relative(repoRoot, result.statefilePath)}`,
`Expected current: ${path.relative(repoRoot, result.jsonPaths.combined)}`,
`Expected current: ${path.relative(repoRoot, result.jsonPaths.core)}`,
`Expected current: ${path.relative(repoRoot, result.jsonPaths.channel)}`,
`Expected current: ${path.relative(repoRoot, result.jsonPaths.plugin)}`,
"If this config-surface change is intentional, run `pnpm config:docs:gen` and commit the updated baseline files.",
"If not intentional, treat this as docs drift or a possible breaking config change and fix the schema/help changes first.",
].join("\n"),
@@ -38,7 +45,9 @@ if (checkOnly) {
console.log(
[
`Wrote ${path.relative(repoRoot, result.jsonPath)}`,
`Wrote ${path.relative(repoRoot, result.statefilePath)}`,
`Wrote ${path.relative(repoRoot, result.jsonPaths.combined)}`,
`Wrote ${path.relative(repoRoot, result.jsonPaths.core)}`,
`Wrote ${path.relative(repoRoot, result.jsonPaths.channel)}`,
`Wrote ${path.relative(repoRoot, result.jsonPaths.plugin)}`,
].join("\n"),
);

View File

@@ -56,6 +56,7 @@ const EXPECTED_REPOSITORY_URL = "https://github.com/openclaw/openclaw";
const MAX_CALVER_DISTANCE_DAYS = 2;
const REQUIRED_PACKED_PATHS = ["dist/control-ui/index.html"];
const CONTROL_UI_ASSET_PREFIX = "dist/control-ui/assets/";
const FORBIDDEN_PACKED_PATH_PREFIXES = ["docs/.generated/"] as const;
const NPM_PACK_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
const skipPackValidationEnv = "OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK";
@@ -437,7 +438,21 @@ function collectPackedTarballErrors(): string[] {
.filter((path): path is string => typeof path === "string" && path.length > 0),
);
return collectControlUiPackErrors(packedPaths);
return [
...collectControlUiPackErrors(packedPaths),
...collectForbiddenPackedPathErrors(packedPaths),
];
}
export function collectForbiddenPackedPathErrors(paths: Iterable<string>): string[] {
const errors: string[] = [];
for (const packedPath of paths) {
if (!FORBIDDEN_PACKED_PATH_PREFIXES.some((prefix) => packedPath.startsWith(prefix))) {
continue;
}
errors.push(`npm package must not include generated docs artifact "${packedPath}".`);
}
return errors.toSorted((left, right) => left.localeCompare(right));
}
function main(): number {

View File

@@ -33,7 +33,7 @@ const requiredPathGroups = [
"dist/channel-catalog.json",
"dist/control-ui/index.html",
];
const forbiddenPrefixes = ["dist-runtime/", "dist/OpenClaw.app/"];
const forbiddenPrefixes = ["dist-runtime/", "dist/OpenClaw.app/", "docs/.generated/"];
// 2026.3.12 ballooned to ~213.6 MiB unpacked and correlated with low-memory
// startup/doctor OOM reports. Keep enough headroom for the current pack with
// restored bundled upgrade surfaces and Control UI assets while still catching

View File

@@ -4,53 +4,51 @@ import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
type ConfigDocBaseline,
renderConfigDocBaselineStatefile,
writeConfigDocBaselineStatefile,
type ConfigDocBaselineEntry,
type ConfigDocBaselineArtifacts,
flattenConfigDocBaselineEntries,
renderConfigDocBaselineArtifacts,
writeConfigDocBaselineArtifacts,
} from "./doc-baseline.js";
describe("config doc baseline integration", () => {
const tempRoots: string[] = [];
const generatedBaselineJsonPath = path.resolve(
process.cwd(),
"docs/.generated/config-baseline.json",
);
const generatedBaselineJsonlPath = path.resolve(
process.cwd(),
"docs/.generated/config-baseline.jsonl",
);
const generatedBaselinePaths = {
combined: path.resolve(process.cwd(), "docs/.generated/config-baseline.json"),
core: path.resolve(process.cwd(), "docs/.generated/config-baseline.core.json"),
channel: path.resolve(process.cwd(), "docs/.generated/config-baseline.channel.json"),
plugin: path.resolve(process.cwd(), "docs/.generated/config-baseline.plugin.json"),
} satisfies Record<keyof ConfigDocBaselineArtifacts, string>;
let sharedBaselinePromise: Promise<ConfigDocBaseline> | null = null;
let sharedRenderedPromise: Promise<
Awaited<ReturnType<typeof renderConfigDocBaselineStatefile>>
Awaited<ReturnType<typeof renderConfigDocBaselineArtifacts>>
> | null = null;
let sharedGeneratedJsonPromise: Promise<string> | null = null;
let sharedGeneratedJsonlPromise: Promise<string> | null = null;
let sharedByPathPromise: Promise<Map<string, ConfigDocBaseline["entries"][number]>> | null = null;
const sharedGeneratedJsonPromises: Partial<
Record<keyof ConfigDocBaselineArtifacts, Promise<string>>
> = {};
let sharedByPathPromise: Promise<Map<string, ConfigDocBaselineEntry>> | null = null;
function getSharedBaseline() {
sharedBaselinePromise ??= fs
.readFile(generatedBaselineJsonPath, "utf8")
.readFile(generatedBaselinePaths.combined, "utf8")
.then((raw) => JSON.parse(raw) as ConfigDocBaseline);
return sharedBaselinePromise;
}
function getSharedRendered() {
sharedRenderedPromise ??= renderConfigDocBaselineStatefile(getSharedBaseline());
sharedRenderedPromise ??= renderConfigDocBaselineArtifacts(getSharedBaseline());
return sharedRenderedPromise;
}
function getGeneratedJson() {
sharedGeneratedJsonPromise ??= fs.readFile(generatedBaselineJsonPath, "utf8");
return sharedGeneratedJsonPromise;
}
function getGeneratedJsonl() {
sharedGeneratedJsonlPromise ??= fs.readFile(generatedBaselineJsonlPath, "utf8");
return sharedGeneratedJsonlPromise;
function getGeneratedJson(kind: keyof ConfigDocBaselineArtifacts) {
sharedGeneratedJsonPromises[kind] ??= fs.readFile(generatedBaselinePaths[kind], "utf8");
return sharedGeneratedJsonPromises[kind];
}
function getSharedByPath() {
sharedByPathPromise ??= getSharedBaseline().then(
(baseline) => new Map(baseline.entries.map((entry) => [entry.path, entry])),
(baseline) =>
new Map(flattenConfigDocBaselineEntries(baseline).map((entry) => [entry.path, entry])),
);
return sharedByPathPromise;
}
@@ -65,22 +63,29 @@ describe("config doc baseline integration", () => {
it("is deterministic across repeated runs", async () => {
const baseline = await getSharedBaseline();
const first = await renderConfigDocBaselineStatefile(baseline);
const second = await renderConfigDocBaselineStatefile(baseline);
const first = await renderConfigDocBaselineArtifacts(baseline);
const second = await renderConfigDocBaselineArtifacts(baseline);
expect(second.json).toBe(first.json);
expect(second.jsonl).toBe(first.jsonl);
expect(second.json.combined).toBe(first.json.combined);
expect(second.json.core).toBe(first.json.core);
expect(second.json.channel).toBe(first.json.channel);
expect(second.json.plugin).toBe(first.json.plugin);
});
it("matches the checked-in generated baseline artifacts", async () => {
const [rendered, generatedJson, generatedJsonl] = await Promise.all([
getSharedRendered(),
getGeneratedJson(),
getGeneratedJsonl(),
]);
const [rendered, generatedCombined, generatedCore, generatedChannel, generatedPlugin] =
await Promise.all([
getSharedRendered(),
getGeneratedJson("combined"),
getGeneratedJson("core"),
getGeneratedJson("channel"),
getGeneratedJson("plugin"),
]);
expect(rendered.json).toBe(generatedJson);
expect(rendered.jsonl).toBe(generatedJsonl);
expect(rendered.json.combined).toBe(generatedCombined);
expect(rendered.json.core).toBe(generatedCore);
expect(rendered.json.channel).toBe(generatedChannel);
expect(rendered.json.plugin).toBe(generatedPlugin);
});
it("includes core, channel, and plugin config metadata", async () => {
@@ -153,18 +158,22 @@ describe("config doc baseline integration", () => {
tempRoots.push(tempRoot);
const rendered = getSharedRendered();
const initial = await writeConfigDocBaselineStatefile({
const initial = await writeConfigDocBaselineArtifacts({
repoRoot: tempRoot,
jsonPath: "docs/.generated/config-baseline.json",
statefilePath: "docs/.generated/config-baseline.jsonl",
combinedPath: "docs/.generated/config-baseline.json",
corePath: "docs/.generated/config-baseline.core.json",
channelPath: "docs/.generated/config-baseline.channel.json",
pluginPath: "docs/.generated/config-baseline.plugin.json",
rendered,
});
expect(initial.wrote).toBe(true);
const current = await writeConfigDocBaselineStatefile({
const current = await writeConfigDocBaselineArtifacts({
repoRoot: tempRoot,
jsonPath: "docs/.generated/config-baseline.json",
statefilePath: "docs/.generated/config-baseline.jsonl",
combinedPath: "docs/.generated/config-baseline.json",
corePath: "docs/.generated/config-baseline.core.json",
channelPath: "docs/.generated/config-baseline.channel.json",
pluginPath: "docs/.generated/config-baseline.plugin.json",
check: true,
rendered,
});
@@ -175,16 +184,13 @@ describe("config doc baseline integration", () => {
'{"generatedBy":"broken","entries":[]}\n',
"utf8",
);
await fs.writeFile(
path.join(tempRoot, "docs/.generated/config-baseline.jsonl"),
'{"recordType":"meta","generatedBy":"broken","totalPaths":0}\n',
"utf8",
);
const stale = await writeConfigDocBaselineStatefile({
const stale = await writeConfigDocBaselineArtifacts({
repoRoot: tempRoot,
jsonPath: "docs/.generated/config-baseline.json",
statefilePath: "docs/.generated/config-baseline.jsonl",
combinedPath: "docs/.generated/config-baseline.json",
corePath: "docs/.generated/config-baseline.core.json",
channelPath: "docs/.generated/config-baseline.channel.json",
pluginPath: "docs/.generated/config-baseline.plugin.json",
check: true,
rendered,
});

View File

@@ -50,25 +50,47 @@ export type ConfigDocBaselineEntry = {
export type ConfigDocBaseline = {
generatedBy: "scripts/generate-config-doc-baseline.ts";
coreEntries: ConfigDocBaselineEntry[];
channelEntries: ConfigDocBaselineEntry[];
pluginEntries: ConfigDocBaselineEntry[];
};
export type ConfigDocBaselineKindBaseline = {
generatedBy: "scripts/generate-config-doc-baseline.ts";
kind: ConfigDocBaselineKind;
entries: ConfigDocBaselineEntry[];
};
export type ConfigDocBaselineStatefileRender = {
json: string;
jsonl: string;
baseline: ConfigDocBaseline;
export type ConfigDocBaselineArtifacts = {
combined: string;
core: string;
channel: string;
plugin: string;
};
export type ConfigDocBaselineStatefileWriteResult = {
export type ConfigDocBaselineArtifactsRender = {
baseline: ConfigDocBaseline;
json: ConfigDocBaselineArtifacts;
};
export type ConfigDocBaselineArtifactPaths = {
combined: string;
core: string;
channel: string;
plugin: string;
};
export type ConfigDocBaselineArtifactsWriteResult = {
changed: boolean;
wrote: boolean;
jsonPath: string;
statefilePath: string;
jsonPaths: ConfigDocBaselineArtifactPaths;
};
const GENERATED_BY = "scripts/generate-config-doc-baseline.ts" as const;
const DEFAULT_JSON_OUTPUT = "docs/.generated/config-baseline.json";
const DEFAULT_STATEFILE_OUTPUT = "docs/.generated/config-baseline.jsonl";
const DEFAULT_COMBINED_OUTPUT = "docs/.generated/config-baseline.json";
const DEFAULT_CORE_OUTPUT = "docs/.generated/config-baseline.core.json";
const DEFAULT_CHANNEL_OUTPUT = "docs/.generated/config-baseline.channel.json";
const DEFAULT_PLUGIN_OUTPUT = "docs/.generated/config-baseline.plugin.json";
let cachedConfigDocBaselinePromise: Promise<ConfigDocBaseline> | null = null;
const uiHintIndexCache = new WeakMap<
ConfigSchemaResponse["uiHints"],
@@ -465,6 +487,36 @@ export function dedupeConfigDocBaselineEntries(
return [...byPath.values()].toSorted((left, right) => left.path.localeCompare(right.path));
}
export function splitConfigDocBaselineEntries(entries: ConfigDocBaselineEntry[]): {
coreEntries: ConfigDocBaselineEntry[];
channelEntries: ConfigDocBaselineEntry[];
pluginEntries: ConfigDocBaselineEntry[];
} {
const coreEntries: ConfigDocBaselineEntry[] = [];
const channelEntries: ConfigDocBaselineEntry[] = [];
const pluginEntries: ConfigDocBaselineEntry[] = [];
for (const entry of entries) {
if (entry.kind === "channel") {
channelEntries.push(entry);
continue;
}
if (entry.kind === "plugin") {
pluginEntries.push(entry);
continue;
}
coreEntries.push(entry);
}
return { coreEntries, channelEntries, pluginEntries };
}
export function flattenConfigDocBaselineEntries(
baseline: ConfigDocBaseline,
): ConfigDocBaselineEntry[] {
return [...baseline.coreEntries, ...baseline.channelEntries, ...baseline.pluginEntries];
}
export async function buildConfigDocBaseline(): Promise<ConfigDocBaseline> {
if (cachedConfigDocBaselinePromise) {
return await cachedConfigDocBaselinePromise;
@@ -482,13 +534,16 @@ export async function buildConfigDocBaseline(): Promise<ConfigDocBaseline> {
const entries = dedupeConfigDocBaselineEntries(
collectConfigDocBaselineEntries(schemaRoot, response.uiHints),
);
const { coreEntries, channelEntries, pluginEntries } = splitConfigDocBaselineEntries(entries);
logConfigDocBaselineDebug(
`collect baseline entries done count=${entries.length} elapsedMs=${Date.now() - collectStart}`,
);
logConfigDocBaselineDebug(`build baseline done elapsedMs=${Date.now() - start}`);
return {
generatedBy: GENERATED_BY,
entries,
coreEntries,
channelEntries,
pluginEntries,
};
})();
try {
@@ -499,28 +554,33 @@ export async function buildConfigDocBaseline(): Promise<ConfigDocBaseline> {
}
}
export async function renderConfigDocBaselineStatefile(
baseline?: ConfigDocBaseline | Promise<ConfigDocBaseline>,
): Promise<ConfigDocBaselineStatefileRender> {
const start = Date.now();
logConfigDocBaselineDebug("render statefile start");
const resolvedBaseline = baseline ? await baseline : await buildConfigDocBaseline();
const json = `${JSON.stringify(resolvedBaseline, null, 2)}\n`;
const metadataLine = JSON.stringify({
function renderKindBaseline(
kind: ConfigDocBaselineKind,
entries: ConfigDocBaselineEntry[],
): string {
const baseline: ConfigDocBaselineKindBaseline = {
generatedBy: GENERATED_BY,
recordType: "meta",
totalPaths: resolvedBaseline.entries.length,
});
const entryLines = resolvedBaseline.entries.map((entry) =>
JSON.stringify({
recordType: "path",
...entry,
}),
);
logConfigDocBaselineDebug(`render statefile done elapsedMs=${Date.now() - start}`);
kind,
entries,
};
return `${JSON.stringify(baseline, null, 2)}\n`;
}
export async function renderConfigDocBaselineArtifacts(
baseline?: ConfigDocBaseline | Promise<ConfigDocBaseline>,
): Promise<ConfigDocBaselineArtifactsRender> {
const start = Date.now();
logConfigDocBaselineDebug("render artifacts start");
const resolvedBaseline = baseline ? await baseline : await buildConfigDocBaseline();
const json: ConfigDocBaselineArtifacts = {
combined: `${JSON.stringify(resolvedBaseline, null, 2)}\n`,
core: renderKindBaseline("core", resolvedBaseline.coreEntries),
channel: renderKindBaseline("channel", resolvedBaseline.channelEntries),
plugin: renderKindBaseline("plugin", resolvedBaseline.pluginEntries),
};
logConfigDocBaselineDebug(`render artifacts done elapsedMs=${Date.now() - start}`);
return {
json,
jsonl: `${[metadataLine, ...entryLines].join("\n")}\n`,
baseline: resolvedBaseline,
};
}
@@ -543,49 +603,72 @@ async function writeIfChanged(filePath: string, next: string): Promise<boolean>
return true;
}
export async function writeConfigDocBaselineStatefile(params?: {
function resolveBaselineArtifactPaths(
repoRoot: string,
params?: {
combinedPath?: string;
corePath?: string;
channelPath?: string;
pluginPath?: string;
},
): ConfigDocBaselineArtifactPaths {
return {
combined: path.resolve(repoRoot, params?.combinedPath ?? DEFAULT_COMBINED_OUTPUT),
core: path.resolve(repoRoot, params?.corePath ?? DEFAULT_CORE_OUTPUT),
channel: path.resolve(repoRoot, params?.channelPath ?? DEFAULT_CHANNEL_OUTPUT),
plugin: path.resolve(repoRoot, params?.pluginPath ?? DEFAULT_PLUGIN_OUTPUT),
};
}
export async function writeConfigDocBaselineArtifacts(params?: {
repoRoot?: string;
check?: boolean;
jsonPath?: string;
statefilePath?: string;
rendered?: ConfigDocBaselineStatefileRender | Promise<ConfigDocBaselineStatefileRender>;
}): Promise<ConfigDocBaselineStatefileWriteResult> {
combinedPath?: string;
corePath?: string;
channelPath?: string;
pluginPath?: string;
rendered?: ConfigDocBaselineArtifactsRender | Promise<ConfigDocBaselineArtifactsRender>;
}): Promise<ConfigDocBaselineArtifactsWriteResult> {
const start = Date.now();
logConfigDocBaselineDebug("write statefile start");
logConfigDocBaselineDebug("write artifacts start");
const repoRoot = params?.repoRoot ?? resolveRepoRoot();
const jsonPath = path.resolve(repoRoot, params?.jsonPath ?? DEFAULT_JSON_OUTPUT);
const statefilePath = path.resolve(repoRoot, params?.statefilePath ?? DEFAULT_STATEFILE_OUTPUT);
const jsonPaths = resolveBaselineArtifactPaths(repoRoot, params);
const rendered = params?.rendered
? await params.rendered
: await renderConfigDocBaselineStatefile();
logConfigDocBaselineDebug(`render statefile done elapsedMs=${Date.now() - start}`);
logConfigDocBaselineDebug(`read current json start ${jsonPath}`);
const currentJson = await readIfExists(jsonPath);
logConfigDocBaselineDebug(`read current json done elapsedMs=${Date.now() - start}`);
logConfigDocBaselineDebug(`read current statefile start ${statefilePath}`);
const currentStatefile = await readIfExists(statefilePath);
logConfigDocBaselineDebug(`read current statefile done elapsedMs=${Date.now() - start}`);
const changed = currentJson !== rendered.json || currentStatefile !== rendered.jsonl;
: await renderConfigDocBaselineArtifacts();
logConfigDocBaselineDebug(`render artifacts done elapsedMs=${Date.now() - start}`);
const current = await Promise.all(
Object.entries(jsonPaths).map(async ([key, filePath]) => [key, await readIfExists(filePath)]),
);
const currentByKey = Object.fromEntries(current) as Record<
keyof ConfigDocBaselineArtifacts,
string | null
>;
const changed = (Object.keys(jsonPaths) as Array<keyof ConfigDocBaselineArtifacts>).some(
(key) => currentByKey[key] !== rendered.json[key],
);
logConfigDocBaselineDebug(
`compare statefile done changed=${changed} elapsedMs=${Date.now() - start}`,
`compare artifacts done changed=${changed} elapsedMs=${Date.now() - start}`,
);
if (params?.check) {
return {
changed,
wrote: false,
jsonPath,
statefilePath,
jsonPaths,
};
}
const wroteJson = await writeIfChanged(jsonPath, rendered.json);
const wroteStatefile = await writeIfChanged(statefilePath, rendered.jsonl);
const wroteResults = await Promise.all(
(Object.keys(jsonPaths) as Array<keyof ConfigDocBaselineArtifacts>).map((key) =>
writeIfChanged(jsonPaths[key], rendered.json[key]),
),
);
return {
changed,
wrote: wroteJson || wroteStatefile,
jsonPath,
statefilePath,
wrote: wroteResults.some(Boolean),
jsonPaths,
};
}

View File

@@ -2,7 +2,11 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { normalizeConfigDocBaselineHelpPath } from "./doc-baseline.js";
import {
type ConfigDocBaseline,
flattenConfigDocBaselineEntries,
normalizeConfigDocBaselineHelpPath,
} from "./doc-baseline.js";
import { FIELD_HELP } from "./schema.help.js";
import {
describeTalkSilenceTimeoutDefaults,
@@ -18,14 +22,11 @@ function readRepoFile(relativePath: string): string {
describe("talk silence timeout defaults", () => {
it("keeps help text and docs aligned with the policy", () => {
const defaultsDescription = describeTalkSilenceTimeoutDefaults();
const baselineLines = readRepoFile("docs/.generated/config-baseline.jsonl")
.trim()
.split("\n")
.map((line) => JSON.parse(line) as { recordType: string; path?: string; help?: string });
const talkEntry = baselineLines.find(
(entry) =>
entry.recordType === "path" &&
entry.path === normalizeConfigDocBaselineHelpPath("talk.silenceTimeoutMs"),
const baseline = JSON.parse(
readRepoFile("docs/.generated/config-baseline.json"),
) as ConfigDocBaseline;
const talkEntry = flattenConfigDocBaselineEntries(baseline).find(
(entry) => entry.path === normalizeConfigDocBaselineHelpPath("talk.silenceTimeoutMs"),
);
expect(FIELD_HELP["talk.silenceTimeoutMs"]).toContain(defaultsDescription);

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
compareReleaseVersions,
collectControlUiPackErrors,
collectForbiddenPackedPathErrors,
collectReleasePackageMetadataErrors,
collectReleaseTagErrors,
parseNpmPackJsonOutput,
@@ -294,6 +295,21 @@ describe("collectControlUiPackErrors", () => {
});
});
describe("collectForbiddenPackedPathErrors", () => {
it("rejects generated docs artifacts in npm pack output", () => {
expect(
collectForbiddenPackedPathErrors([
"dist/index.js",
"docs/.generated/config-baseline.json",
"docs/.generated/config-baseline.plugin.json",
]),
).toEqual([
'npm package must not include generated docs artifact "docs/.generated/config-baseline.json".',
'npm package must not include generated docs artifact "docs/.generated/config-baseline.plugin.json".',
]);
});
});
describe("collectReleaseTagErrors", () => {
it("accepts versions within the two-day CalVer window", () => {
expect(

View File

@@ -243,6 +243,19 @@ describe("collectForbiddenPackPaths", () => {
]),
).toEqual([bundledPluginFile("tlon", "node_modules/.bin/tlon"), "node_modules/.bin/openclaw"]);
});
it("blocks generated docs artifacts from npm pack output", () => {
expect(
collectForbiddenPackPaths([
"dist/index.js",
"docs/.generated/config-baseline.json",
"docs/.generated/config-baseline.core.json",
]),
).toEqual([
"docs/.generated/config-baseline.core.json",
"docs/.generated/config-baseline.json",
]);
});
});
describe("collectMissingPackPaths", () => {

View File

@@ -36,9 +36,11 @@ describe("scripts/check-file-utils collectFilesSync", () => {
const rootDir = makeTempDir();
fs.mkdirSync(path.join(rootDir, "src", "nested"), { recursive: true });
fs.mkdirSync(path.join(rootDir, "dist"), { recursive: true });
fs.mkdirSync(path.join(rootDir, "docs", ".generated"), { recursive: true });
fs.writeFileSync(path.join(rootDir, "src", "keep.ts"), "");
fs.writeFileSync(path.join(rootDir, "src", "nested", "keep.test.ts"), "");
fs.writeFileSync(path.join(rootDir, "dist", "skip.ts"), "");
fs.writeFileSync(path.join(rootDir, "docs", ".generated", "skip.ts"), "");
const files = collectFilesSync(rootDir, {
includeFile: (filePath) => filePath.endsWith(".ts"),