mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Modernize and fix website build tooling deps and utilize JS type checking (#3348)
* Modernize and fix website build tooling deps and utilize JS type checking * Upgrade to the latest Node.js
This commit is contained in:
120
website/.build-scripts/generate-editor-structure.ts
Normal file
120
website/.build-scripts/generate-editor-structure.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
type Entry = { level: number; text: string; link: string | undefined };
|
||||
|
||||
/// Parses a single line of the input text.
|
||||
function parseLine(line: string) {
|
||||
const linkRegex = /`([^`]+)`$/;
|
||||
const linkMatch = line.match(linkRegex);
|
||||
let link = undefined;
|
||||
|
||||
if (linkMatch) {
|
||||
const filePath = linkMatch[1].replace(/\\/g, "/");
|
||||
link = `https://github.com/GraphiteEditor/Graphite/blob/master/${filePath}`;
|
||||
}
|
||||
|
||||
const textContent = line
|
||||
.replace(/^[\s│├└─]*/, "")
|
||||
.replace(linkRegex, "")
|
||||
.trim();
|
||||
const indentation = line.indexOf(textContent);
|
||||
// Each level of indentation is 4 characters.
|
||||
const level = Math.floor(indentation / 4);
|
||||
|
||||
return { level, text: textContent, link };
|
||||
}
|
||||
|
||||
/// Recursively builds the HTML list from the parsed nodes.
|
||||
function buildHtmlList(nodes: Entry[], currentIndex: number, currentLevel: number) {
|
||||
if (currentIndex >= nodes.length) {
|
||||
return { html: "", nextIndex: currentIndex };
|
||||
}
|
||||
|
||||
let html = "<ul>\n";
|
||||
let i = currentIndex;
|
||||
|
||||
while (i < nodes.length && nodes[i].level >= currentLevel) {
|
||||
const node = nodes[i];
|
||||
|
||||
if (node.level > currentLevel) {
|
||||
// This case handles malformed input, skip to next valid line
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasDirectChildren = i + 1 < nodes.length && nodes[i + 1].level > node.level;
|
||||
const hasDeeperChildren = hasDirectChildren && i + 2 < nodes.length && nodes[i + 2].level > nodes[i + 1].level;
|
||||
|
||||
const linkHtml = node.link ? `<a href="${node.link}" target="_blank">${path.basename(node.link)}</a>` : "";
|
||||
const fieldPieces = node.text.match(/([^:]*):(.*)/);
|
||||
let escapedText;
|
||||
if (fieldPieces && fieldPieces.length === 3) {
|
||||
escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())];
|
||||
} else {
|
||||
escapedText = [escapeHtml(node.text)];
|
||||
}
|
||||
|
||||
let role = "message";
|
||||
if (node.link) role = "subsystem";
|
||||
else if (hasDeeperChildren) role = "submessage";
|
||||
else if (escapedText.length === 2) role = "field";
|
||||
|
||||
const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix));
|
||||
const partOfMessageViolatesNamingConvention = node.link && !partOfMessageFromNamingConvention;
|
||||
const violatesNamingConvention = partOfMessageViolatesNamingConvention
|
||||
? "<span class=\"warn\">(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')</span>"
|
||||
: "";
|
||||
|
||||
if (hasDirectChildren) {
|
||||
html += `<li><span class="tree-node"><span class="${role}">${escapedText}</span>${linkHtml}${violatesNamingConvention}</span>`;
|
||||
const childResult = buildHtmlList(nodes, i + 1, node.level + 1);
|
||||
html += `<div class="nested">${childResult.html}</div></li>\n`;
|
||||
i = childResult.nextIndex;
|
||||
} else if (role === "field") {
|
||||
html += `<li><span class="tree-leaf field">${escapedText[0]}</span>: <span>${escapedText[1]}</span>${linkHtml}</li>\n`;
|
||||
i++;
|
||||
} else {
|
||||
html += `<li><span class="tree-leaf ${role}">${escapedText[0]}</span>${linkHtml}${violatesNamingConvention}</li>\n`;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
html += "</ul>\n";
|
||||
return { html, nextIndex: i };
|
||||
}
|
||||
|
||||
function escapeHtml(text: string) {
|
||||
return text.replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
const inputFile = process.argv[2];
|
||||
const outputFile = process.argv[3];
|
||||
|
||||
if (!inputFile || !outputFile) {
|
||||
console.error("Error: Please provide the input text and output HTML file paths as arguments.");
|
||||
console.log("Usage: node generate-editor-structure.ts <input txt> <output html>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(inputFile)) {
|
||||
console.error(`Error: File not found at "${inputFile}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const fileContent = fs.readFileSync(inputFile, "utf-8");
|
||||
const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "" && !line.startsWith("// filepath:"));
|
||||
const parsedNodes = lines.map(parseLine);
|
||||
|
||||
const { html } = buildHtmlList(parsedNodes, 0, 0);
|
||||
|
||||
fs.writeFileSync(outputFile, html, "utf-8");
|
||||
|
||||
console.log(`Successfully generated HTML outline at: ${outputFile}`);
|
||||
} catch (error) {
|
||||
console.error("An error occurred during processing:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
195
website/.build-scripts/install-fonts.ts
Normal file
195
website/.build-scripts/install-fonts.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import fs from "fs";
|
||||
import https from "https";
|
||||
import path from "path";
|
||||
|
||||
// Define basePath as the directory of the current script
|
||||
const basePath = import.meta.dirname;
|
||||
|
||||
// Define files to copy as [source, destination] pairs
|
||||
// Files with the same destination will be concatenated
|
||||
const FILES_TO_COPY = [
|
||||
["../node_modules/@fontsource-variable/inter/opsz.css", "../static/fonts/common.css"],
|
||||
["../node_modules/@fontsource-variable/inter/opsz-italic.css", "../static/fonts/common.css"],
|
||||
["../node_modules/@fontsource/bona-nova/700.css", "../static/fonts/common.css"],
|
||||
];
|
||||
|
||||
// Define directories to copy recursively as [source, destination] pairs
|
||||
const DIRECTORIES_TO_COPY = [
|
||||
["../node_modules/@fontsource-variable/inter/files", "../static/fonts/files"],
|
||||
["../node_modules/@fontsource/bona-nova/files", "../static/fonts/files"],
|
||||
];
|
||||
|
||||
// Track processed destination files and CSS content
|
||||
const processedDestinations = new Set();
|
||||
const cssDestinations = new Set<string>();
|
||||
const allCopiedFiles = new Set<string>();
|
||||
|
||||
// Process each file
|
||||
FILES_TO_COPY.forEach(([source, dest]) => {
|
||||
// Convert relative paths to absolute paths
|
||||
const sourcePath = path.join(basePath, source);
|
||||
const destPath = path.join(basePath, dest);
|
||||
|
||||
// Track CSS destinations for later analysis
|
||||
if (dest.endsWith(".css")) {
|
||||
cssDestinations.add(destPath);
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = path.dirname(destPath);
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Created directory: ${destDir}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Read source file content
|
||||
const content = fs.readFileSync(sourcePath, "utf8");
|
||||
|
||||
// Check if destination has been processed before
|
||||
if (processedDestinations.has(destPath)) {
|
||||
// Append to existing file
|
||||
fs.appendFileSync(destPath, "\n\n" + content);
|
||||
console.log(`Appended: ${sourcePath} → ${destPath}`);
|
||||
} else {
|
||||
// First time writing to this destination - copy the file
|
||||
fs.writeFileSync(destPath, content);
|
||||
processedDestinations.add(destPath);
|
||||
console.log(`Copied: ${sourcePath} → ${destPath}`);
|
||||
}
|
||||
|
||||
// Replace all occurrences of "./files" with "/fonts" in the destination file
|
||||
let destFileContent = fs.readFileSync(destPath, "utf8");
|
||||
destFileContent = destFileContent.replaceAll("./files/", "/fonts/files/");
|
||||
fs.writeFileSync(destPath, destFileContent);
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${sourcePath} to ${destPath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
function copyDirectoryRecursive(source: string, destination: string) {
|
||||
// Ensure destination directory exists
|
||||
if (!fs.existsSync(destination)) {
|
||||
fs.mkdirSync(destination, { recursive: true });
|
||||
console.log(`Created directory: ${destination}`);
|
||||
}
|
||||
|
||||
// Get all items in the source directory
|
||||
const items = fs.readdirSync(source);
|
||||
|
||||
// Process each item
|
||||
items.forEach((item) => {
|
||||
const sourcePath = path.join(source, item);
|
||||
const destPath = path.join(destination, item);
|
||||
|
||||
// Check if item is a directory or file
|
||||
const stats = fs.statSync(sourcePath);
|
||||
if (stats.isDirectory()) {
|
||||
// Recursively copy subdirectory
|
||||
copyDirectoryRecursive(sourcePath, destPath);
|
||||
} else {
|
||||
// Copy file and track it
|
||||
fs.copyFileSync(sourcePath, destPath);
|
||||
allCopiedFiles.add(destPath);
|
||||
console.log(`Copied: ${sourcePath} → ${destPath}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process each directory
|
||||
DIRECTORIES_TO_COPY.forEach(([source, dest]) => {
|
||||
// Convert relative paths to absolute paths
|
||||
const sourcePath = path.join(basePath, source);
|
||||
const destPath = path.join(basePath, dest);
|
||||
|
||||
try {
|
||||
copyDirectoryRecursive(sourcePath, destPath);
|
||||
console.log(`Copied directory: ${sourcePath} → ${destPath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error copying directory ${sourcePath} to ${destPath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("All files and directories copied successfully!");
|
||||
|
||||
// Now check which of the copied files are actually referenced in CSS
|
||||
console.log("\nChecking for unused font files...");
|
||||
|
||||
// Read all CSS content and join it
|
||||
let allCssContent = "";
|
||||
cssDestinations.forEach((cssPath) => {
|
||||
try {
|
||||
const content = fs.readFileSync(cssPath, "utf8");
|
||||
allCssContent += content;
|
||||
} catch (error) {
|
||||
console.error(`Error reading CSS file ${cssPath}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter files that aren't referenced in CSS
|
||||
const unusedFiles: string[] = [];
|
||||
allCopiedFiles.forEach((filePath) => {
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
// Check if the file name is mentioned in any CSS
|
||||
if (!allCssContent.includes(fileName)) {
|
||||
unusedFiles.push(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
// Delete unused files
|
||||
if (unusedFiles.length > 0) {
|
||||
console.log(`Found ${unusedFiles.length} unused font files to delete:`);
|
||||
unusedFiles.forEach((filePath) => {
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`Deleted unused file: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error deleting file ${filePath}:`, error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("No unused font files found.");
|
||||
}
|
||||
|
||||
console.log("\nFont installation complete!");
|
||||
|
||||
// Fetch and save text-balancer.js, which we don't commit to the repo so we're not version controlling dependency code
|
||||
const textBalancerUrl = "https://static.graphite.rs/text-balancer/text-balancer.js";
|
||||
const textBalancerDest = path.join(basePath, "../static", "text-balancer.js");
|
||||
console.log("\nDownloading text-balancer.js...");
|
||||
https
|
||||
.get(textBalancerUrl, (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`Failed to download text-balancer.js. Status code: ${res.statusCode}`);
|
||||
res.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
try {
|
||||
// Ensure destination directory exists
|
||||
const destDir = path.dirname(textBalancerDest);
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Created directory: ${destDir}`);
|
||||
}
|
||||
fs.writeFileSync(textBalancerDest, data, "utf8");
|
||||
console.log(`Downloaded and saved: ${textBalancerDest}`);
|
||||
} catch (error) {
|
||||
console.error("Error saving text-balancer.js:", error);
|
||||
}
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
console.error("Error downloading text-balancer.js:", err);
|
||||
});
|
||||
Reference in New Issue
Block a user