mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-26 06:25:24 -04:00
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:
@@ -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.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user