- Redesign /resources as sectioned hub with category pages - Migrate 645 Squarespace CDN images to local /images/content/ - Create 9 new news/blog posts with event photos - Fix blog post slugs (rename gibberish filenames) - Rename Design Blog to Design Blogs across site - Remove education page, replace with Platform in nav - Redesign rover repair request form with dynamic rover entries - Add school search combobox to contact, store, and repair forms - Extract shared KNOWN_SCHOOLS data - Make /rover-expansion-3d-printing dynamically pull from MDX - Add related resources sections to product pages - Fix homepage broken /quote links to /store - Store page: sample kit cards, inline quote builder, mailing list opt-in
106 lines
2.9 KiB
JavaScript
106 lines
2.9 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import { execSync } from "child_process";
|
|
import crypto from "crypto";
|
|
|
|
const RESOURCES_DIR = "content/resources";
|
|
const IMAGES_DIR = "public/images/resources/content";
|
|
|
|
// Collect all unique Squarespace URLs from MDX files
|
|
const mdxFiles = fs.readdirSync(RESOURCES_DIR).filter((f) => f.endsWith(".mdx"));
|
|
const urlMap = new Map(); // url -> local filename
|
|
|
|
let totalUrls = 0;
|
|
|
|
for (const file of mdxFiles) {
|
|
const content = fs.readFileSync(path.join(RESOURCES_DIR, file), "utf8");
|
|
const urls = content.match(/https:\/\/images\.squarespace-cdn\.com\/[^\s"'\)]+/g);
|
|
if (urls) {
|
|
for (const url of urls) {
|
|
if (!urlMap.has(url)) {
|
|
// Generate a short filename from the URL
|
|
const urlPath = new URL(url).pathname;
|
|
const ext = path.extname(urlPath).toLowerCase() || ".jpg";
|
|
const baseName = path.basename(urlPath, ext).replace(/[^a-zA-Z0-9_-]/g, "-").substring(0, 60);
|
|
const hash = crypto.createHash("md5").update(url).digest("hex").substring(0, 8);
|
|
const localName = `${baseName}-${hash}${ext}`;
|
|
urlMap.set(url, localName);
|
|
totalUrls++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Found ${totalUrls} unique Squarespace URLs across ${mdxFiles.length} MDX files`);
|
|
|
|
// Download all images
|
|
let downloaded = 0;
|
|
let failed = 0;
|
|
const failedUrls = [];
|
|
|
|
for (const [url, localName] of urlMap) {
|
|
const localPath = path.join(IMAGES_DIR, localName);
|
|
|
|
if (fs.existsSync(localPath) && fs.statSync(localPath).size > 1000) {
|
|
downloaded++;
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
execSync(
|
|
`curl -sL -o "${localPath}" --max-time 30 "${url}"`,
|
|
{ timeout: 35000 }
|
|
);
|
|
const stat = fs.statSync(localPath);
|
|
if (stat.size < 500) {
|
|
console.log(`WARN: Small file (${stat.size}b) for ${url}`);
|
|
fs.unlinkSync(localPath);
|
|
failed++;
|
|
failedUrls.push(url);
|
|
} else {
|
|
downloaded++;
|
|
}
|
|
} catch (e) {
|
|
console.log(`FAIL: ${url}`);
|
|
failed++;
|
|
failedUrls.push(url);
|
|
if (fs.existsSync(localPath)) fs.unlinkSync(localPath);
|
|
}
|
|
|
|
if ((downloaded + failed) % 50 === 0) {
|
|
console.log(`Progress: ${downloaded + failed}/${totalUrls} (${failed} failed)`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDownloaded: ${downloaded}, Failed: ${failed}`);
|
|
|
|
// Rewrite MDX files
|
|
let filesUpdated = 0;
|
|
for (const file of mdxFiles) {
|
|
const filePath = path.join(RESOURCES_DIR, file);
|
|
let content = fs.readFileSync(filePath, "utf8");
|
|
let changed = false;
|
|
|
|
for (const [url, localName] of urlMap) {
|
|
if (content.includes(url)) {
|
|
const localRef = `/images/resources/content/${localName}`;
|
|
content = content.replaceAll(url, localRef);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(filePath, content);
|
|
filesUpdated++;
|
|
}
|
|
}
|
|
|
|
console.log(`Updated ${filesUpdated} MDX files`);
|
|
|
|
if (failedUrls.length > 0) {
|
|
console.log(`\nFailed URLs:`);
|
|
for (const u of failedUrls) {
|
|
console.log(` ${u}`);
|
|
}
|
|
}
|