95eb362bfc
QuantumLab template converted to Next.js 16 + React 19 + TypeScript: - 8 page routes (home, about, blog, contact, careers, team-members, coming-soon, 404) - Dynamic routes for blog posts, career positions, and team members - GSAP animations (marquee, counters, button hovers) - IntersectionObserver-based scroll reveal (blur-to-clear transitions) - Dark mode with next-themes - React Hook Form + Zod contact form - Framer Motion page transitions - Lottie animations via lottie-web Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
3.7 KiB
JavaScript
117 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.resolve(__dirname, "..");
|
|
|
|
const INTERNAL_PREFIXES = ["/", "#"];
|
|
const EXTERNAL_DOMAINS = ["http://", "https://", "mailto:", "tel:"];
|
|
|
|
const TSX_FILES = [];
|
|
function walkDir(dir) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
walkDir(full);
|
|
} else if (entry.name.endsWith(".tsx")) {
|
|
TSX_FILES.push(full);
|
|
}
|
|
}
|
|
}
|
|
walkDir(path.join(ROOT, "components"));
|
|
|
|
let totalConverted = 0;
|
|
|
|
for (const file of TSX_FILES) {
|
|
// Skip Header.tsx - already uses Link
|
|
if (file.endsWith("Header.tsx")) continue;
|
|
|
|
let content = fs.readFileSync(file, "utf8");
|
|
let changed = false;
|
|
|
|
// Convert <a href="..." ...> to <Link href="..." ...> for internal links
|
|
// And add target/rel for external links
|
|
content = content.replace(/<a\s([^>]*?)>/g, (match, attrs) => {
|
|
const hrefMatch = attrs.match(/href="([^"]*)"/);
|
|
if (!hrefMatch) return match;
|
|
const href = hrefMatch[1];
|
|
|
|
const isExternal = EXTERNAL_DOMAINS.some(d => href.startsWith(d));
|
|
const isInternal = INTERNAL_PREFIXES.some(p => href.startsWith(p)) && !isExternal;
|
|
|
|
if (isInternal) {
|
|
changed = true;
|
|
totalConverted++;
|
|
// Remove Webflow-specific attrs
|
|
let cleanAttrs = attrs
|
|
.replace(/data-w-id="[^"]*"/g, "")
|
|
.replace(/data-wf[^=]*="[^"]*"/g, "")
|
|
.replace(/aria-current="page"/g, "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
return `<Link ${cleanAttrs}>`;
|
|
}
|
|
|
|
if (isExternal && !attrs.includes("target=")) {
|
|
changed = true;
|
|
totalConverted++;
|
|
return `<a ${attrs} target="_blank" rel="noopener noreferrer">`;
|
|
}
|
|
|
|
return match;
|
|
});
|
|
|
|
// Convert closing </a> to </Link> where we converted opening tag
|
|
// Simple approach: replace all </a> in files that had changes
|
|
// Actually we need to be smarter - only replace </a> that correspond to <Link>
|
|
// Let's do it line by line
|
|
if (changed) {
|
|
// Count Link opens vs closes to fix
|
|
const lines = content.split("\n");
|
|
let linkDepth = 0;
|
|
const newLines = [];
|
|
|
|
for (const line of lines) {
|
|
let processed = line;
|
|
|
|
// Count Link opens in this line
|
|
const linkOpens = (processed.match(/<Link\s/g) || []).length;
|
|
const selfClosing = (processed.match(/<Link\s[^>]*\/>/g) || []).length;
|
|
linkDepth += linkOpens - selfClosing;
|
|
|
|
// Replace </a> with </Link> when inside a Link context
|
|
if (linkDepth > 0 && processed.includes("</a>")) {
|
|
const closeTags = (processed.match(/<\/a>/g) || []).length;
|
|
for (let i = 0; i < closeTags && linkDepth > 0; i++) {
|
|
processed = processed.replace("</a>", "</Link>");
|
|
linkDepth--;
|
|
}
|
|
}
|
|
|
|
newLines.push(processed);
|
|
}
|
|
|
|
content = newLines.join("\n");
|
|
|
|
// Add Link import
|
|
if (!content.includes('import Link from "next/link"') && !content.includes("import Link from 'next/link'")) {
|
|
if (content.includes('import Image from "next/image"')) {
|
|
content = content.replace(
|
|
'import Image from "next/image"',
|
|
'import Image from "next/image"\nimport Link from "next/link"'
|
|
);
|
|
} else {
|
|
const firstLine = content.indexOf("\n");
|
|
content = content.slice(0, firstLine + 1) + 'import Link from "next/link"\n' + content.slice(firstLine + 1);
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(file, content, "utf8");
|
|
console.log(`Updated: ${path.relative(ROOT, file)}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nTotal links converted: ${totalConverted}`);
|