mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 10:01:34 +00:00
docs: generate docs map at publish time (#117398)
* docs: generate docs map at publish time * fix: canonicalize Docker pack entry path * fix: preserve package lifecycle ownership
This commit is contained in:
committed by
GitHub
parent
e4d1b7e0d3
commit
385f1dee16
131
test/scripts/package-docs-map.test.ts
Normal file
131
test/scripts/package-docs-map.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { renderDocsHeadingMap } from "../../scripts/docs-list.js";
|
||||
import { restorePrepackArtifacts } from "../../scripts/openclaw-postpack.mjs";
|
||||
import { preparePackageChangelog } from "../../scripts/package-changelog.mjs";
|
||||
import { preparePackageDocsMap, restorePackageDocsMap } from "../../scripts/package-docs-map.mjs";
|
||||
import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const sourceChangelog = `# Changelog
|
||||
|
||||
## 2026.8.1
|
||||
- Current release notes with enough detail for package validation.
|
||||
|
||||
## 2026.7.1
|
||||
- Previous release notes with enough detail for package validation.
|
||||
`;
|
||||
|
||||
function makePackageRoot(): string {
|
||||
const root = makeTempDir(tempDirs, "openclaw-package-docs-map-");
|
||||
mkdirSync(path.join(root, "docs"), { recursive: true });
|
||||
writeFileSync(path.join(root, "docs", "page.md"), "# Package docs\n", "utf8");
|
||||
writeFileSync(path.join(root, "package.json"), '{"name":"openclaw","version":"2026.8.1"}\n');
|
||||
writeFileSync(path.join(root, "CHANGELOG.md"), sourceChangelog);
|
||||
return root;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
describe("package docs map", () => {
|
||||
it("materializes exact generated bytes and removes only its transient copy", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
|
||||
await expect(preparePackageDocsMap(root)).resolves.toBe(true);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(renderDocsHeadingMap(path.join(root, "docs")));
|
||||
await expect(restorePackageDocsMap(root)).resolves.toBe(true);
|
||||
expect(existsSync(mapPath)).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves an identical map that existed before packaging", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
const content = renderDocsHeadingMap(path.join(root, "docs"));
|
||||
writeFileSync(mapPath, content, "utf8");
|
||||
|
||||
await expect(preparePackageDocsMap(root)).resolves.toBe(false);
|
||||
await expect(restorePackageDocsMap(root)).resolves.toBe(false);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(content);
|
||||
});
|
||||
|
||||
it("restores a tracked source stub after packaging", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
const stub = "# Docs map source\n";
|
||||
writeFileSync(mapPath, stub, "utf8");
|
||||
|
||||
await expect(preparePackageDocsMap(root)).resolves.toBe(true);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(renderDocsHeadingMap(path.join(root, "docs")));
|
||||
await expect(restorePackageDocsMap(root)).resolves.toBe(true);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(stub);
|
||||
});
|
||||
|
||||
it("serializes concurrent package preparations with the receipt", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
const stub = "# Docs map source\n";
|
||||
writeFileSync(mapPath, stub, "utf8");
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
preparePackageDocsMap(root),
|
||||
preparePackageDocsMap(root),
|
||||
]);
|
||||
|
||||
expect(results.filter((result) => result.status === "fulfilled")).toHaveLength(1);
|
||||
const rejected = results.find((result) => result.status === "rejected");
|
||||
expect(rejected).toMatchObject({
|
||||
reason: expect.objectContaining({
|
||||
code: "PACKAGE_DOCS_MAP_ACTIVE",
|
||||
message: expect.stringContaining("node scripts/openclaw-postpack.mjs"),
|
||||
}),
|
||||
});
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(renderDocsHeadingMap(path.join(root, "docs")));
|
||||
await expect(restorePackageDocsMap(root)).resolves.toBe(true);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(stub);
|
||||
});
|
||||
|
||||
it("refuses to remove a transient map changed after prepack", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
await preparePackageDocsMap(root);
|
||||
writeFileSync(mapPath, "operator change\n", "utf8");
|
||||
|
||||
await expect(restorePackageDocsMap(root)).rejects.toThrow("changed after prepack");
|
||||
expect(readFileSync(mapPath, "utf8")).toBe("operator change\n");
|
||||
});
|
||||
|
||||
it("keeps the lifecycle lock until interrupted changelog state is restored", async () => {
|
||||
const root = makePackageRoot();
|
||||
const mapPath = path.join(root, "docs", "docs_map.md");
|
||||
const receiptPath = path.join(root, ".artifacts", "package-docs-map", "receipt.json");
|
||||
const changelogBackupPath = path.join(
|
||||
root,
|
||||
".artifacts",
|
||||
"package-changelog",
|
||||
"CHANGELOG.md.prepack-backup",
|
||||
);
|
||||
const stub = "# Docs map source\n";
|
||||
writeFileSync(mapPath, stub);
|
||||
|
||||
await preparePackageDocsMap(root);
|
||||
await preparePackageChangelog(root);
|
||||
const packagedChangelog = readFileSync(path.join(root, "CHANGELOG.md"), "utf8");
|
||||
writeFileSync(path.join(root, "CHANGELOG.md"), "operator change\n");
|
||||
|
||||
await expect(restorePrepackArtifacts(root)).rejects.toThrow("changed since the backup");
|
||||
expect(existsSync(receiptPath)).toBe(true);
|
||||
expect(existsSync(changelogBackupPath)).toBe(true);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(renderDocsHeadingMap(path.join(root, "docs")));
|
||||
|
||||
writeFileSync(path.join(root, "CHANGELOG.md"), packagedChangelog);
|
||||
await expect(restorePrepackArtifacts(root)).resolves.toBeUndefined();
|
||||
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).toBe(sourceChangelog);
|
||||
expect(readFileSync(mapPath, "utf8")).toBe(stub);
|
||||
expect(existsSync(changelogBackupPath)).toBe(false);
|
||||
expect(existsSync(receiptPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user