ci: fix Android docs sync (remove stray workflow, prune stale pages) (#5955)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
James Rich
2026-06-25 13:29:40 -05:00
committed by GitHub
parent 33a4ca6731
commit 89f41dea53
2 changed files with 124 additions and 60 deletions

View File

@@ -120,7 +120,7 @@ function rewriteImagePaths(content) {
* e.g., `[text](connections)` → `[text](connections.md)`
* e.g., `[text](../developer/testing)` → `[text](../developer/testing.md)`
*/
function rewriteSiblingLinks(content, section) {
function rewriteSiblingLinks(content, section, isIndex = false) {
const slugs = section === "user" ? KNOWN_USER_SLUGS : KNOWN_DEV_SLUGS;
// Match [text](link) where link is NOT an absolute URL, NOT an anchor, NOT already .md
@@ -132,6 +132,16 @@ function rewriteSiblingLinks(content, section) {
return match;
}
// Section landing page (user/index.md, developer/index.md): the source
// user.md/developer.md sit beside the section dir, so they link to children
// as "user/onboarding". From index.md inside that dir, strip the prefix.
if (isIndex) {
const sameSection = link.match(new RegExp(`^${section}/(.+)`));
if (sameSection && slugs.has(sameSection[1])) {
return `[${text}](${sameSection[1]}.md)`;
}
}
// Check for cross-section links like ../developer/testing
const crossMatch = link.match(/^\.\.\/(\w+)\/(.+)/);
if (crossMatch) {
@@ -213,19 +223,21 @@ function convertCallouts(content) {
// ── Main ─────────────────────────────────────────────────────────────────────
function processMarkdown(srcPath, destPath, section) {
function processMarkdown(srcPath, destPath, section, isIndex = false) {
let content = fs.readFileSync(srcPath, "utf-8");
content = transformFrontmatter(content, section);
content = rewriteImagePaths(content);
content = rewriteSiblingLinks(content, section);
content = rewriteSiblingLinks(content, section, isIndex);
content = convertCallouts(content);
writeFile(destPath, content);
}
/** Sync screenshots, returning the set of destination basenames written. */
function processImages() {
const written = new Set();
if (!fs.existsSync(SRC_SCREENSHOTS_DIR)) {
console.log("No screenshots directory found, skipping image sync.");
return;
return written;
}
const images = fs.readdirSync(SRC_SCREENSHOTS_DIR)
@@ -241,19 +253,70 @@ function processImages() {
if (DRY_RUN) {
console.log(`[dry-run] Would convert: ${srcPath}${destPath}`);
written.add(destName);
} else {
ensureDir(path.dirname(destPath));
try {
execSync(`cwebp -q 80 "${srcPath}" -o "${destPath}"`, { stdio: "pipe" });
console.log(`Converted: ${destPath}`);
written.add(destName);
} catch (err) {
console.error(`Failed to convert ${img}: ${err.message}`);
// Fall back to copying the original
copyFile(srcPath, path.join(DEST_IMAGES_DIR, img));
written.add(img);
}
}
} else {
copyFile(srcPath, path.join(DEST_IMAGES_DIR, img));
written.add(img);
}
}
return written;
}
/** Recursively collect files under a directory, as paths relative to it. */
function collectFiles(dir) {
const results = [];
if (!fs.existsSync(dir)) return results;
(function walk(current) {
for (const entry of fs.readdirSync(current)) {
const full = path.join(current, entry);
if (fs.statSync(full).isDirectory()) walk(full);
else results.push(path.relative(dir, full));
}
})(dir);
return results;
}
/**
* Remove destination files this run did not produce, so renamed or deleted
* source pages (and the screenshots they referenced) don't linger on the site.
* Mirrors the Apple sync's cleanup pass. Scoped to docs pages (.md/.mdx) and
* images under the directories this script owns.
*/
function pruneStale(expectedDocPaths, expectedImageNames) {
for (const file of collectFiles(DEST_DOCS_DIR)) {
const ext = path.extname(file).toLowerCase();
if (ext !== ".md" && ext !== ".mdx") continue;
if (expectedDocPaths.has(file.split(path.sep).join("/"))) continue;
const target = path.join(DEST_DOCS_DIR, file);
if (DRY_RUN) {
console.log(`[dry-run] Would remove stale page: ${target}`);
} else {
fs.unlinkSync(target);
console.log(`Removed stale page: ${target}`);
}
}
for (const file of collectFiles(DEST_IMAGES_DIR)) {
if (!IMAGE_EXTENSIONS.has(path.extname(file).toLowerCase())) continue;
if (expectedImageNames.has(path.basename(file))) continue;
const target = path.join(DEST_IMAGES_DIR, file);
if (DRY_RUN) {
console.log(`[dry-run] Would remove stale image: ${target}`);
} else {
fs.unlinkSync(target);
console.log(`Removed stale image: ${target}`);
}
}
}
@@ -277,12 +340,37 @@ Documentation for the [Meshtastic Android](https://github.com/meshtastic/Meshtas
}
function createCategoryFiles() {
const userCategory = `label: User Guide
// Top-level section category. Uses the synced index.md as the landing page
// (matching the Apple sync), replacing any hand-written generated-index that
// would otherwise collide with index.md.
// Unique `key` per category: the "User Guide" / "Developer Guide" labels also
// exist in the Apple section, and Docusaurus derives sidebar translation keys
// from the label, so without a key the two sections collide at build time.
const sectionCategory = `key: androidApp
label: Android App
collapsible: true
position: 1
link:
type: doc
id: software/android/index
`;
const devCategory = `label: Developer Guide
const userCategory = `key: androidUserGuide
label: User Guide
collapsible: true
position: 1
link:
type: doc
id: software/android/user/index
`;
const devCategory = `key: androidDeveloperGuide
label: Developer Guide
collapsible: true
position: 2
link:
type: doc
id: software/android/developer/index
`;
writeFile(path.join(DEST_DOCS_DIR, "_category_.yml"), sectionCategory);
writeFile(path.join(DEST_DOCS_DIR, "user", "_category_.yml"), userCategory);
writeFile(path.join(DEST_DOCS_DIR, "developer", "_category_.yml"), devCategory);
}
@@ -294,6 +382,30 @@ function main() {
console.log(`Dry run: ${DRY_RUN}`);
console.log("");
// Track everything this run produces so stale files can be pruned afterwards.
const expectedDocPaths = new Set([
"index.md",
"_category_.yml",
"user/_category_.yml",
"developer/_category_.yml",
]);
// Section overview pages: docs/en/user.md → user/index.md and
// docs/en/developer.md → developer/index.md. Docusaurus uses <dir>/index.md
// as the category landing page, so the index.md "User Guide" / "Developer
// Guide" links resolve (matching the Apple sync).
const sections = [
{ src: "user.md", dest: ["user", "index.md"], section: "user", key: "user/index.md" },
{ src: "developer.md", dest: ["developer", "index.md"], section: "developer", key: "developer/index.md" },
];
for (const { src, dest, section, key } of sections) {
const overview = path.join(SRC_DOCS_DIR, src);
if (fs.existsSync(overview)) {
processMarkdown(overview, path.join(DEST_DOCS_DIR, ...dest), section, true);
expectedDocPaths.add(key);
}
}
// Process user guide
const userDir = path.join(SRC_DOCS_DIR, "user");
if (fs.existsSync(userDir)) {
@@ -303,6 +415,7 @@ function main() {
path.join(DEST_DOCS_DIR, "user", file),
"user",
);
expectedDocPaths.add(`user/${file}`);
}
}
@@ -315,6 +428,7 @@ function main() {
path.join(DEST_DOCS_DIR, "developer", file),
"developer",
);
expectedDocPaths.add(`developer/${file}`);
}
}
@@ -323,7 +437,10 @@ function main() {
createCategoryFiles();
// Process images
processImages();
const expectedImageNames = processImages();
// Remove anything left over from a previous sync or the old hand-written docs.
pruneStale(expectedDocPaths, expectedImageNames);
console.log("\nSync complete.");
}