Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions scripts/generate-docs-content.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,28 @@
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { join, relative, sep } from "node:path";

import { globbySync } from "globby";

import { renderHookEventsMatrix } from "./hook-events-table.js";
import { repoRoot, runOxfmt } from "./run-oxfmt.js";

/**
* Embed the canonical `docs/**\/*.md` hierarchy into a generated TypeScript
* module so the `rulesync docs` command can serve it from every distribution
* (npm dist bundles and bun-compiled binaries alike). The generated file is
* committed; CI fails on drift via `pnpm run check:docs-content`.
*/
const repoRoot = join(import.meta.dirname, "..");
const docsRoot = join(repoRoot, "docs");
const outputPath = join(repoRoot, "src", "generated", "docs-content.ts");

// Normalize to the repo's formatter so the drift check compares stable output.
// npx is npx.cmd on Windows; a shell resolves it. stderr is inherited so a
// formatter failure stays diagnosable.
const runOxfmt = (path: string): void => {
execFileSync("npx", ["oxfmt", relative(repoRoot, path)], {
cwd: repoRoot,
stdio: ["ignore", "ignore", "inherit"],
shell: process.platform === "win32",
});
};

// Regenerate the derived hook-event matrix inside file-formats.md before
// embedding, so a stale committed table fails the docs-content drift check
// (`check:docs-content` diffs this file as well as the embed). Written
// unconditionally: the renderer emits cells without column padding and oxfmt
// owns the final column layout, mirroring generate-supported-tools-tables.ts.
const hookMatrixPath = join(docsRoot, "reference", "file-formats.md");
writeFileSync(hookMatrixPath, renderHookEventsMatrix(readFileSync(hookMatrixPath, "utf8")), "utf8");
runOxfmt(hookMatrixPath);
runOxfmt([hookMatrixPath]);

const filePaths = globbySync("**/*.md", {
cwd: docsRoot,
Expand Down Expand Up @@ -68,6 +56,6 @@ const lines: string[] = [
];

writeFileSync(outputPath, lines.join("\n"), "utf8");
runOxfmt(outputPath);
runOxfmt([outputPath]);
// oxlint-disable-next-line no-console
console.log(`Embedded ${entries.length} docs into ${relative(repoRoot, outputPath)}`);
4 changes: 2 additions & 2 deletions scripts/generate-json-schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { execFileSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import { join } from "node:path";

Expand All @@ -13,6 +12,7 @@ import {
} from "../src/constants/rulesync-paths.js";
import { RulesyncMcpFileSchema } from "../src/features/mcp/rulesync-mcp.js";
import { RulesyncPermissionsFileSchema } from "../src/types/permissions.js";
import { runOxfmt } from "./run-oxfmt.js";

type SchemaMeta = {
$id: string;
Expand Down Expand Up @@ -70,4 +70,4 @@ generateSchema(
);

// Format generated schema files with oxfmt for consistent formatting
execFileSync("npx", ["oxfmt", outputPath, mcpOutputPath, permissionsOutputPath]);
runOxfmt([outputPath, mcpOutputPath, permissionsOutputPath]);
4 changes: 2 additions & 2 deletions scripts/generate-supported-tools-tables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";

Expand All @@ -7,6 +6,7 @@ import { TOOL_DISPLAY, type ToolDisplayEntry } from "../src/types/tool-display.j
import { ALL_TOOL_TARGETS, type ToolTarget } from "../src/types/tool-targets.js";
import { formatError } from "../src/utils/error.js";
import { replaceBetweenMarkers } from "./markdown-markers.js";
import { runOxfmt } from "./run-oxfmt.js";

const FEATURES = [
"rules",
Expand Down Expand Up @@ -145,7 +145,7 @@ const main = (): void => {
// run it here. Freshness is checked in CI by running this script then
// `git diff` (see the `check:supported-tools` package script) — the same
// approach as the gitignore generator, avoiding an oxfmt-vs-generator conflict.
execFileSync("pnpm", ["exec", "oxfmt", ...targets.map((t) => t.path)], { stdio: "inherit" });
runOxfmt(targets.map((t) => t.path));
};

main();
58 changes: 58 additions & 0 deletions scripts/run-oxfmt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { execFileSync } from "node:child_process";
import { join } from "node:path";

import { afterEach, describe, expect, it, vi } from "vitest";

import { repoRoot, runOxfmt } from "./run-oxfmt.js";

vi.mock("node:child_process", () => ({
execFileSync: vi.fn(),
}));

const execFileSyncMock = vi.mocked(execFileSync);

describe("runOxfmt", () => {
afterEach(() => {
execFileSyncMock.mockClear();
vi.unstubAllGlobals();
});

it("does not spawn oxfmt when no paths are given, so it cannot format the whole repo", () => {
runOxfmt([]);

expect(execFileSyncMock).not.toHaveBeenCalled();
});

it("passes repo-root-relative paths and runs from the repo root", () => {
runOxfmt([
join(repoRoot, "README.md"),
join(repoRoot, "docs", "reference", "supported-tools.md"),
]);

expect(execFileSyncMock).toHaveBeenCalledTimes(1);
const [command, args, options] = execFileSyncMock.mock.calls[0]!;
expect(command).toBe("npx");
expect(args).toEqual([
"--no-install",
"oxfmt",
"README.md",
join("docs", "reference", "supported-tools.md"),
]);
expect(options?.cwd).toBe(repoRoot);
});

it("resolves npx through a shell only on Windows", () => {
const platform = process.platform;

Object.defineProperty(process, "platform", { value: "win32", configurable: true });
try {
runOxfmt([join(repoRoot, "README.md")]);
} finally {
Object.defineProperty(process, "platform", { value: platform, configurable: true });
}
expect(execFileSyncMock.mock.calls[0]?.[2]?.shell).toBe(true);

runOxfmt([join(repoRoot, "README.md")]);
expect(execFileSyncMock.mock.calls[1]?.[2]?.shell).toBe(false);
});
});
25 changes: 25 additions & 0 deletions scripts/run-oxfmt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { execFileSync } from "node:child_process";
import { join, relative } from "node:path";

export const repoRoot = join(import.meta.dirname, "..");

/**
* Format generated files with the repo's formatter so drift checks compare
* stable output. Shared by the generator scripts (docs content, supported-tools
* tables, JSON schemas) so a fix here reaches every call site.
*
* npx is npx.cmd on Windows; a shell resolves it. `--no-install` keeps npx from
* silently fetching a different oxfmt version when the pinned devDependency is
* missing, which would surface as an unexplained drift-check failure. stderr is
* inherited so a formatter failure stays diagnosable.
*/
export const runOxfmt = (paths: string[]): void => {
if (paths.length === 0) {
return;
}
execFileSync("npx", ["--no-install", "oxfmt", ...paths.map((path) => relative(repoRoot, path))], {
cwd: repoRoot,
stdio: ["ignore", "ignore", "inherit"],
shell: process.platform === "win32",
});
};
Loading