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
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ Then open the local URL printed by Next.js, usually
<http://localhost:3000>. For a production-style preview, run
`npm run build` followed by `npm run start`.

`npm run build` fails if a doc-sync step in `prebuild` fails. When working
offline, set `DOCS_SYNC_OPTIONAL=1` (for example
`DOCS_SYNC_OPTIONAL=1 npm run build`) to keep the committed content instead.
CI and Netlify always use the strict behavior.

## Code review requirements

A PR merges once it passes the CI gates defined in `.github/workflows/` — Build,
Expand Down
1 change: 1 addition & 0 deletions changelog.d/fixed-prebuild-strict-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Make `prebuild` fail the build when a content sync step fails instead of masking it with `|| echo`; set `DOCS_SYNC_OPTIONAL=1` to keep the offline fallback to committed content for local development.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"scripts": {
"dev": "next dev --turbopack",
"prebuild": "(tsx scripts/update-meeting-recordings.ts || echo \"meeting recordings refresh unavailable - using committed data\") && (tsx scripts/sync-hive-docs.ts || echo \"hive docs sync unavailable - using committed content\") && (tsx scripts/sync-sibling-docs.ts || echo \"sibling docs sync unavailable - using committed content\") && tsx scripts/generate-shared-config.ts",
"prebuild": "tsx scripts/run-sync-step.ts scripts/update-meeting-recordings.ts && tsx scripts/run-sync-step.ts scripts/sync-hive-docs.ts && tsx scripts/run-sync-step.ts scripts/sync-sibling-docs.ts && tsx scripts/generate-shared-config.ts",
"build": "next build",
"start": "next start",
"lint": "eslint src/ scripts/ netlify/",
Expand Down
30 changes: 30 additions & 0 deletions scripts/run-sync-step.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it, vi } from "vitest";
import type { spawnSync } from "node:child_process";
import { isSyncOptional, runSyncStep } from "./run-sync-step";

const fake = (status: number | null) =>
vi.fn(() => ({ status })) as unknown as typeof spawnSync;

describe("run-sync-step", () => {
it("is strict unless DOCS_SYNC_OPTIONAL=1", () => {
expect(isSyncOptional({})).toBe(false);
expect(isSyncOptional({ DOCS_SYNC_OPTIONAL: "0" })).toBe(false);
expect(isSyncOptional({ DOCS_SYNC_OPTIONAL: "1" })).toBe(true);
});

it("passes through success", () => {
expect(runSyncStep("a.ts", {}, fake(0))).toBe(0);
});

it("propagates failure by default", () => {
expect(runSyncStep("a.ts", {}, fake(1))).toBe(1);
expect(runSyncStep("a.ts", {}, fake(null))).toBe(1);
});

it("tolerates failure when opted in", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
expect(runSyncStep("a.ts", { DOCS_SYNC_OPTIONAL: "1" }, fake(1))).toBe(0);
expect(warn).toHaveBeenCalled();
warn.mockRestore();
});
});
43 changes: 43 additions & 0 deletions scripts/run-sync-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Runs one prebuild sync step (a tsx script) and propagates its exit code.
* Failures fail the build unless DOCS_SYNC_OPTIONAL=1 is set, in which case the
* committed content is kept and a warning is printed (local/offline use only).
*/
import { spawnSync } from "node:child_process";

// Plain record rather than NodeJS.ProcessEnv: Next.js augments ProcessEnv with
// a required NODE_ENV, which makes object literals in tests unassignable.
export type SyncEnv = Record<string, string | undefined>;

export function isSyncOptional(env: SyncEnv = process.env): boolean {
return env.DOCS_SYNC_OPTIONAL === "1";
}

export function runSyncStep(
script: string,
env: SyncEnv = process.env,
run: typeof spawnSync = spawnSync
): number {
const result = run("tsx", [script], {
stdio: "inherit",
env: env as NodeJS.ProcessEnv,
shell: process.platform === "win32",
});
const status = result.status ?? 1;
if (status !== 0 && isSyncOptional(env)) {
console.warn(
`${script} failed (exit ${status}); DOCS_SYNC_OPTIONAL=1 so using committed content`
);
return 0;
}
return status;
}

if (process.argv[1]?.endsWith("run-sync-step.ts")) {
const script = process.argv[2];
if (!script) {
console.error("usage: tsx scripts/run-sync-step.ts <script>");
process.exit(2);
}
process.exit(runSyncStep(script));
}
Loading