Skip to content

Commit

Permalink
feat(bot): auto update links of moved files (mdn#31091)
Browse files Browse the repository at this point in the history
* feat(bot): auto update links of moved files

* Update scripts/update-moved-file-links.js

Co-authored-by: Jean-Yves Perrier <[email protected]>

---------

Co-authored-by: Jean-Yves Perrier <[email protected]>
  • Loading branch information
2 people authored and dipikabh committed Jan 17, 2024
1 parent 3b11a96 commit 696b097
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Create Markdownlint auto-fix PR
name: Create content auto-fix PR

on:
schedule:
Expand Down Expand Up @@ -31,14 +31,15 @@ jobs:
yarn content fix-flaws
yarn fix:md
yarn fix:fm
node scripts/update-moved-file-links.js
- name: Create PR with only fixable issues
if: success()
uses: peter-evans/create-pull-request@v5
with:
commit-message: "chore: auto-fix Markdownlint issues"
commit-message: "chore: auto-fix Markdownlint, Prettier, front-matter, redirects issues"
branch: markdownlint-auto-cleanup
title: "Markdownlint auto-cleanup"
title: "fix: auto-cleanup by bot"
author: mdn-bot <[email protected]>
body: |
All issues auto-fixed
Expand All @@ -50,7 +51,7 @@ jobs:
with:
commit-message: "chore: auto-fix Markdownlint issues"
branch: markdownlint-auto-cleanup
title: "Markdownlint auto-cleanup"
title: "fix: auto-cleanup by bot"
author: mdn-bot <[email protected]>
body: |
Auto-fix was run, but additional issues found.
Expand Down
138 changes: 138 additions & 0 deletions scripts/update-moved-file-links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import fs from "node:fs/promises";
import path from "node:path";
import { execGit, getRootDir, walkSync, isImagePath } from "./utils.js";

const SLUG_RX = /(?<=\nslug: ).*?$/gm;
const HELP_MSG =
"Usage:\n\t" +
"node scripts/update-moved-file-links.js\n\t" +
"node scripts/update-moved-file-links.js [movedFromPath] [movedToPath]\n";

/**
* Try to get slug for an image from file path
*/
export async function getImageSlug(imagePath, root) {
const nodePath = path.parse(imagePath);
const absolutePath = `${root}/files/en-us/${nodePath.dir}/index.md`;
let content;
try {
content = await fs.readFile(absolutePath, "utf-8");
} catch (e) {}

if (content) {
return `/en-US/docs/${(content.match(SLUG_RX) || [])[0]}/${nodePath.base}`;
} else {
return `/en-US/docs/${imagePath}`;
}
}

let movedFiles = [];
const rootDir = getRootDir();
const argLength = process.argv.length;

if (process.argv[2] === "--help" || process.argv[2] === "-h") {
console.error(HELP_MSG);
process.exit(0);
} else if (argLength === 2 && argLength > 3) {
console.error(HELP_MSG);
process.exit(1);
} else if (argLength === 3) {
movedFiles.push({ from: process.argv[2], to: process.argv[3] });
} else {
// git log --name-status --pretty=format:"" --since "1 day ago" --diff-filter=R
let result = execGit(
[
"log",
"--name-status",
"--pretty=format:",
'--since="1 day ago"',
"--diff-filter=R",
],
{ cwd: "." },
);

if (result.trim()) {
movedFiles.push(
...result
.split("\n")
.filter((line) => line.trim() !== "" && line.includes("files/en-us"))
.map((line) => line.replaceAll(/files\/en-us\/|\/index.md/gm, ""))
.map((line) => line.split(/\s/))
.map((tuple) => {
return { from: tuple[1], to: tuple[2] };
}),
);
}
}

if (movedFiles.length < 1) {
console.log("No content files were moved. Nothing to update! 🎉");
process.exit(0);
}

const redirectsText = await fs.readFile(
`${rootDir}/files/en-us/_redirects.txt`,
"utf-8",
);

// convert file paths to slugs
movedFiles = (
await Promise.all(
movedFiles.map(async (tuple) => {
const movedLineRg = new RegExp(`\n.*?${tuple.from}\\s+.*?\n`, "gmi");
const redirectLine = (redirectsText.match(movedLineRg) || [])[0];

if (redirectLine) {
const urls = redirectLine.trim().split(/\s+/);
return { from: urls[0], to: urls[1] };
}

if (isImagePath(tuple.from)) {
return {
from: await getImageSlug(tuple.from, rootDir),
to: await getImageSlug(tuple.to, rootDir),
};
}

console.warn("No redirect entry found for: ", tuple.from);
}),
)
).filter((e) => !!e);

console.log(`Number of moved files to consider: ${movedFiles.length}`);

let totalNo = 0;
let updatedNo = 0;
for await (const filePath of walkSync(getRootDir())) {
if (filePath.endsWith("index.md")) {
try {
totalNo++;
const content = await fs.readFile(filePath, "utf-8");
let updated = new String(content);
for (const moved of movedFiles) {
// [text](link)
updated = updated.replaceAll(`${moved.from})`, `${moved.to})`);
// <link>
updated = updated.replaceAll(`${moved.from}>`, `${moved.to}>`);
// [text](link#)
updated = updated.replaceAll(`${moved.from}#`, `${moved.to}#`);
// [text](link "tool tip")
updated = updated.replaceAll(`${moved.from} `, `${moved.to} `);
// <a href="link">
updated = updated.replaceAll(`${moved.from}"`, `${moved.to}"`);
// <a href='link'>
updated = updated.replaceAll(`${moved.from}'`, `${moved.to}'`);
}

if (content !== updated) {
updatedNo++;
await fs.writeFile(filePath, updated);
}
} catch (e) {
console.error(`Error processing ${filePath}: ${e.message}`);
throw e;
}
}
}

console.log(`Updated moved file links in ${updatedNo}/${totalNo} files.`);
51 changes: 51 additions & 0 deletions scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fs from "node:fs/promises";
import path from "node:path";
import childProcess from "node:child_process";

const IMG_RX = /(\.png|\.jpg|\.svg|\.gif)$/gim;

export async function* walkSync(dir) {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name));
} else {
yield path.join(dir, file.name);
}
}
}

export function execGit(args, opts = {}, root = null) {
const gitRoot = root || getRootDir();
const { status, error, stdout, stderr } = childProcess.spawnSync(
"git",
args,
{
cwd: gitRoot,
// Default is 1MB
maxBuffer: 1024 * 1024 * 100, // 100MB
},
);
if (error || status !== 0) {
if (stderr) {
console.log(args);
console.log(`Error running git ${args}`);
console.error(stderr);
}
if (error) {
throw error;
}
throw new Error(
`git command failed: ${stderr.toString() || stdout.toString()}`,
);
}
return stdout.toString().trim();
}

export function getRootDir() {
return execGit(["rev-parse", "--show-toplevel"], {}, process.cwd());
}

export function isImagePath(path) {
return IMG_RX.test(path);
}

0 comments on commit 696b097

Please sign in to comment.