Skip to content

Commit

Permalink
Enhance, debug, and clean up scripts for CSS migration (BL-12857)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenMcConnel committed Dec 6, 2023
1 parent d20da5c commit 6908abb
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 96 deletions.
26 changes: 14 additions & 12 deletions custom-css-analysis-and-migration/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
In Bloom 5.7, we introduce a modernized, parameterized page layout CSS system, known as "Appearance". This new system conflicts with the arbitrary custom css that was used in prior Bloom versions, mostly in the area of margins.

As of this writing, we are only looking at `customBookStyles.css`, but `customCollectionsStyles.css` have the same problem.
As of this writing, we are looking at both `customBookStyles.css` and `customCollectionsStyles.css`, but not handling having both files with content, and not really dealing with `customCollectionStyles.css` being a collection wide settings file.

When Bloom encounters a pre-5.7 book with a `customBookStyles.css` that would conflict with the new system, it goes into a kind of "safe mode" that keeps things working by using a legacy `basePage.css`. Better, though, is to migrate the old css to the new system. Conceivably, a reliable program could be written to do this automatically. However at the moment what we do is to write the migrations using the utilities here, and then have a dev evaluate individual migrations and then copy them over to the shipping Bloom. So when Bloom encounters one of these books, we may already have a tested migration for it.

# How to use this system

⚠️ Be careful what you commit to an open source repo. We do not want to expose emails or other private data.

1. As of this writing, bun only works on linux. If you are on windows, just install Windows Subsystem for Linux (not as big of a deal as it sounds), then run in a WSL terminal in VSCODE.
1. As of this writing, bun works only on linux. If you are on windows, just install Windows Subsystem for Linux (not as big of a deal as it sounds), then run in a WSL terminal in VSCODE.

1. Get [bun](https://bun.sh/) installed
2. Get [bun](https://bun.sh/) installed

1. Install dependencies: `bun install`

1. Download all the `customBookStyles.css` from blorg
3. Choose (or create) a folder for working with CSS files and download all the `customBookStyles.css` and `customCollectionStyles.css` files from bloomlibrary.org. This will take some time.

`./some-path/BloomBulkDownloader.exe --include "*/*/custom*Styles.css" --syncfolder ./output/downloads --bucket production customBookStyles-files`

4. Download all the `meta.json` files from bloomlibrary.org. Again, this will take some time.

`./some-path/BloomBulkDownloader.exe --include "*/*/meta.json" --syncfolder ./output/downloads --bucket production customBookStyles-files`

Each of the following take an optional argument that will limit the number of records processed.

5. Process those, grouping them into bins of duplicate stylesheets
5. Process those, grouping them into bins of duplicate stylesheets

`bun run group-stylesheets.ts 13`
`bun run /path-to/group-stylesheets.ts 13`

1. Process those groups, discarding ones that don't need migration
6. Process those groups, discarding ones that don't need migration

`bun run filter-stylesheets.ts 7`
`bun run /path-to/filter-stylesheets.ts 7`

1. Create draft migration files for each one
7. Create draft migration files for each one

`bun run create-migrations.ts 3`
`bun run /path-to/create-migrations.ts 3`
66 changes: 52 additions & 14 deletions custom-css-analysis-and-migration/create-migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,47 @@ for (const record of records) {
if (record.css === undefined) continue;

++count;
if (Bun.argv.length > 2 && count >= Number.parseInt(Bun.argv[2])) break;
if (Bun.argv.length > 2 && count > Number.parseInt(Bun.argv[2])) break;
console.log(`css = ${record.css}`);
const checksum = crypto.createHash("md5").update(record.css).digest("hex");
const folder = `./output/migration/${count} ${checksum}`;
console.log(`output folder=${folder}`);
fs.mkdirSync(folder);

let cssPath = `${folder}/customBookStyles.css`;
if (fs.existsSync(cssPath)) {
cssPath = `${folder}/customBookStyles2.css`;
console.log(`WARNING: multiple migrations in ${folder}`);
}

const migration = migrateCssToAppearance(record.css);

fs.writeFileSync(
`${folder}/customBookStyles.css`,
migrateCssToAppearance(record.css) +
`\n\n /* ----- ORIGINAL ---- */\n/*${record.css.replaceAll(
"*/",
"#/"
)}*/`
cssPath,
migration.modifiedCss +
`
/* ----- ORIGINAL ---- */
/*${record.css.replaceAll("*/", "#/")}*/
`
);

const brandingSet = new Set();
for (let i = 0; i < record.paths.length; ++i) {
const metaPath = record.paths[i] + "/meta.json";
try {
const metaString: string = fs.readFileSync(metaPath, "utf8");
const metadata = JSON.parse(metaString);
const brandingProject = metadata.brandingProjectName as string;
if (brandingProject &&
brandingProject.toLowerCase() !== "default" &&
brandingProject.toLowerCase() !== "local-community")
brandingSet.add(brandingProject);
} catch (e) {
console.log("Could not extract brandingProject from " + metaPath);
}
}

const uniqueUploaders = [
...new Set(
record.paths.map((path: string) => {
Expand All @@ -63,15 +89,27 @@ for (const record of records) {
),
].slice(0, 3); // first 3 are enough to give a sense of who uploaded these books

fs.writeFileSync(
`${folder}/appearance.json`,
const brandings = [...brandingSet];
const date = new Date();
const outputString: string =
`// Matches customBookStyles.css with checksum ${checksum}
// This was used by ${record.book_count} books (${record.unique_named_books} unique).` +
// enhance: +`// Affected branding projects include "Mali-ACR-2020-Soninke".`
`// Uploaders included ${JSON.stringify(uniqueUploaders)}.
// On ${date.toDateString()} this was used by ${record.book_count} books (${record.unique_named_books} unique).
` +
(brandings && brandings.length > 0
? `// Affected branding projects included ${JSON.stringify(brandings)}.
`
: ``) +
`// Uploaders included ${JSON.stringify(uniqueUploaders)}.
// Example Book: ${record.first_book}
// A replacement customBookStyles.css has been generated.
{
}`
);
"cssThemeName": "default",
"coverShowTitleL2": ${migration.hideL2TitleOnCover ? "false" : "true"},
"coverShowTitleL3": false,
}
`;
let appearancePath = `${folder}/appearance.json`;
if (fs.existsSync(appearancePath))
appearancePath = `${folder}/appearance2.json`;
fs.writeFileSync(appearancePath, outputString);
}
9 changes: 5 additions & 4 deletions custom-css-analysis-and-migration/filter-stylesheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const recordsWithUniqueifiedPaths = filteredRecords.map((record) => {
const uniquePaths = uniqueFilenames.map((filename) => {
return paths.find((path) => path.endsWith(filename));
});
const instanceId = paths[0].split("/")[2];
const instanceId = paths[0].split("/")[3];
return {
book_count: paths.length,
unique_named_books: uniquePaths.length,
Expand Down Expand Up @@ -75,12 +75,13 @@ console.write(
`, covering ${filteredRecords.reduce(
(acc, record) => acc + record.paths.length,
0
)} books, `
)} books`
);

// console.write(
// `${recordsWithUniqueifiedPaths.reduce(
// `, ${recordsWithUniqueifiedPaths.reduce(
// (acc, record) => acc + record.uniqueified_paths.length,
// 0
// )} of which have unique names (should remove most rebrands).\r\n`
// );
console.write(`
`);
13 changes: 8 additions & 5 deletions custom-css-analysis-and-migration/group-stylesheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,38 @@ interface FileData {
}

const boilerplate =
'/* Some books may need control over aspects of layout that cannot yet be adjusted\r\n from the Bloom interface. In those cases, Bloom provides this "under the hood" method\r\n of creating style rules using the underlying "Cascading Stylesheets" system.\r\n These rules are then applied to all books in this collection. EDIT THIS FILE ONLY\r\n IN THE COLLECTION FOLDER: changes made to a copy found in the book folder will be\r\n lost the next time the book is edited with Bloom!\r\n\r\n Note: you can also add a file named "customBookStyles.css" in the book folder,\r\n to limit the effects of the rules to just that one book.\r\n\r\n You can learn about CSS from hundreds of books, or online. However chances are, if\r\n you need this customization, you will need an expert to create a version of this file\r\n for you, or give you rules that you can paste in below this line. */';
/\/\* Some books may need control over aspects of layout that cannot yet be adjusted(.|[\r\n])*?\*\//;

const boilerplate2 =
'/* Some books may need control over aspects of layout that cannot yet be adjusted\r\n from the Bloom interface. In those cases, Bloom provides this "under the hood" method\r\n of creating style rules using the underlying "Cascading Stylesheets" system. \r\n These rules are then applied to all books in this collection.\r\n\r\n Note: you can also add a file named "customBookStyles.css" in the book folder,\r\n to limit the effects of the rules to just that one book.\r\n \r\n You can learn about CSS from hundreds of books, or online. However chances are, if\r\n you need this customization, you will need an expert to create a version of this file\r\n for you, or give you rules that you can paste in below this line. */';
let count = 0;
function readFilesRecursively(
dir: string,
fileMap: Map<string, FileData>
): void {
const files = fs.readdirSync(dir);
let cssCount = 0;
for (const file of files) {
const filePath = path.join(dir, file);
if (Bun.argv.length > 2 && count >= Number.parseInt(Bun.argv[2])) return;
if (fs.statSync(filePath).isDirectory()) {
readFilesRecursively(filePath, fileMap);
} else {
if (file === "meta.json") continue;
const content = fs
.readFileSync(filePath, "utf8")
.replace(boilerplate, "")
.replace(boilerplate2, "")
.trim();
if (content === "") continue;
++cssCount;

const fileData: FileData = fileMap.get(content) || { content, paths: [] };
fileData.paths.push(dir.replace("./output/downloads", ""));
fileMap.set(content, fileData);
console.log(++count + " " + dir);
console.log(++count + " " + filePath);
}
}
if (cssCount > 1)
console.log(`WARNING: multiple CSS files with content in ${dir}`);
cssCount = 0;
}

const sourceDir = "./output/downloads";
Expand Down
Loading

0 comments on commit 6908abb

Please sign in to comment.