Skip to content
Closed
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
11 changes: 10 additions & 1 deletion website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ $ yarn
$ yarn start
```

This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
This command runs CrocoDocs generation and starts a local development server.
If CrocoDocs generation fails (for example while offline), `yarn start` reuses existing generated files from previous successful runs.

If this is the very first run, keep an internet connection and run:

```
$ yarn crocodocs:generate
```

Most changes are reflected live without having to restart the server.

### Build

Expand Down
2 changes: 1 addition & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"crocodocs:generate": "cd .. && uv --directory ./tools/crocodocs run crocodocs generate",
"start": "yarn crocodocs:generate && docusaurus start",
"start": "node ./scripts/start.js",
"build": "yarn crocodocs:generate && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
Expand Down
72 changes: 72 additions & 0 deletions website/scripts/sanitize-offline-artifacts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const fs = require("fs");
const path = require("path");

/**
* Sanitize offline-generated MDX artifacts that may contain unescaped MDX/JSX.
* This runs as a post-processing step after CrocoDocs generates partials.
*
* When CrocoDocs fails (e.g., offline), the PyPI index partial may contain
* unescaped characters like <, >, [, ] which break MDX compilation.
*
* This script replaces invalid partials with safe fallback versions.
*/

const crocodocs_dir = path.resolve(__dirname, "..", ".crocodocs");

function sanitize_pypi_index() {
const pypi_file = path.join(crocodocs_dir, "pypi-index.mdx");
if (!fs.existsSync(pypi_file)) {
return; // File doesn't exist; nothing to sanitize
}

const content = fs.readFileSync(pypi_file, "utf-8");

// Check for common unescaped MDX/JSX patterns in warning blocks
// (heuristic: if we see <, >, [, ] outside code blocks in a warning, it's likely broken)
const lines = content.split("\n");
let in_warning = false;
let has_unescaped = false;

for (const line of lines) {
if (line.includes(":::warning")) {
in_warning = true;
} else if (line.includes(":::")) {
in_warning = false;
}

if (in_warning) {
// Simple heuristic: bare < > [ ] outside backticks are problematic in MDX
const outside_backticks = line.replace(/`[^`]+`/g, "");
if (/[<>\[\]]/.test(outside_backticks)) {
has_unescaped = true;
break;
}
}
}

if (has_unescaped) {
// Replace with safe fallback
const fallback = `:::info
PyPI index unavailable (offline or network error).

Run \`yarn crocodocs:generate\` from \`website/\` when online to fetch the latest packages.
:::
`;
fs.writeFileSync(pypi_file, fallback, "utf-8");
console.warn(
` [warn] Sanitized ${pypi_file}: replaced invalid offline content`,
);
}
}

// Run sanitization
try {
if (!fs.existsSync(crocodocs_dir)) {
// Artifacts directory doesn't exist yet; nothing to sanitize
process.exit(0);
}
sanitize_pypi_index();
} catch (err) {
console.error("Error sanitizing offline artifacts:", err.message);
process.exit(1);
}
70 changes: 70 additions & 0 deletions website/scripts/start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");

const websiteRoot = path.resolve(__dirname, "..");
const repoRoot = path.resolve(websiteRoot, "..");

const requiredArtifacts = [
path.join(websiteRoot, ".crocodocs", "api-data.json"),
path.join(websiteRoot, ".crocodocs", "docs-manifest.json"),
path.join(websiteRoot, "sidebars.js"),
];

const ALLOWED_COMMANDS = new Set(["uv", "node", "docusaurus"]);

function run(command, args, options = {}) {
if (!ALLOWED_COMMANDS.has(command)) {
throw new Error(`Command not allowed: ${command}`);
}
const result = spawnSync(command, args, {
Comment thread
GrCOTE7 marked this conversation as resolved.
stdio: "inherit",
// shell: true is intentionally avoided to prevent command injection.
// On Windows, .cmd wrappers in node_modules/.bin require a shell.
shell: process.platform === "win32",
...options,
});
return typeof result.status === "number" ? result.status : 1;
}

function hasAllArtifacts() {
return requiredArtifacts.every((file) => fs.existsSync(file));
}

const generateExitCode = run(
"uv",
["--directory", "./tools/crocodocs", "run", "crocodocs", "generate"],
{ cwd: repoRoot },
);

if (generateExitCode !== 0) {
if (!hasAllArtifacts()) {
console.error(
"\nCrocoDocs generation failed and required generated artifacts are missing.",
);
console.error(
"Connect to the internet once and run 'yarn crocodocs:generate' from website/.\n",
);
process.exit(generateExitCode);
}

console.warn(
"\nCrocoDocs generation failed. Reusing existing generated artifacts for offline start.\n",
);
}

// Sanitize offline-generated artifacts (removes unescaped MDX/JSX in error messages)
const sanitizeExitCode = run(
"node",
[path.join("scripts", "sanitize-offline-artifacts.js")],
{
cwd: websiteRoot,
},
);
if (sanitizeExitCode !== 0) {
console.error("Failed to sanitize offline artifacts");
process.exit(sanitizeExitCode);
}

const startExitCode = run("docusaurus", ["start"], { cwd: websiteRoot });
process.exit(startExitCode);
Loading