mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-10 07:53:57 +00:00
Recordly 2.0 local review stack 6/6
Dependency-ordered review preparation. Not pushed or remotely reviewed.
This commit is contained in:
1 parent
08f72ef6aa
commit
0f8d11acd8
297 files changed
+25705
-600
No files matched your search
@@ -0,0 +1,24 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, readdir, access } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const blog = resolve(root, "dist/blog");
|
||||
const contents = await readdir(blog, {withFileTypes:true});
|
||||
assert.equal(contents.filter((entry) => entry.isDirectory()).length, 0, "Generated article routes must be removed");
|
||||
const html = await readFile(resolve(blog, "index.html"), "utf8");
|
||||
assert.match(html, /id="root" data-prerendered/);
|
||||
assert.match(html, /<h1>Blog<\/h1>/);
|
||||
assert.match(html, /No posts yet\./);
|
||||
assert.doesNotMatch(html, /class="blog-card"|class="blog-filters"|BlogPosting|\/tutorials|assets\/blog\//);
|
||||
assert.match(html, /<a href="\/blog">Blog<\/a>/);
|
||||
assert.match(html, /recordly-mark\.svg/);
|
||||
assert.equal((html.match(/<link rel="canonical"/g)||[]).length, 1);
|
||||
assert.match(html, /<link rel="canonical" href="[^"]+\/blog"/);
|
||||
const schema = JSON.parse(html.match(/<script type="application\/ld\+json" data-blog-schema>(.*?)<\/script>/s)[1]);
|
||||
assert.equal(schema["@type"], "CollectionPage");
|
||||
assert.deepEqual(schema.mainEntity.itemListElement, []);
|
||||
const sitemap = await readFile(resolve(root,"dist/sitemap.xml"), "utf8");
|
||||
assert.doesNotMatch(sitemap, /\/tutorials|\/blog\//);
|
||||
await assert.rejects(access(resolve(root, "dist/tutorials")));
|
||||
console.log("Passed: empty Blog, direct navigation, blue logo, clean metadata, and no generated article or tutorial routes.");
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {readFile} from "node:fs/promises";
|
||||
import {fileURLToPath} from "node:url";
|
||||
import {resolve,dirname} from "node:path";
|
||||
import {parseCatalog,dollars,gigabytes} from "../src/pricing/catalog.js";
|
||||
const valid = {prices:[{interval:"monthly",currency:"usd",unitAmount:900,regularUnitAmount:1500,promotion:"early_bird"}],limits:{freeRecordingLimit:5,freeMaxRecordingBytes:1e9,plusMaxRecordingBytes:1e10,storageLimitBytes:null}};
|
||||
const parsed = parseCatalog(valid);
|
||||
assert.equal(dollars(parsed.amount), "$9");
|
||||
assert.equal(dollars(parsed.regular), "$15");
|
||||
assert.equal(gigabytes(parsed.cloudBytes), "10 GB");
|
||||
assert.equal(parseCatalog({...valid,prices:[{...valid.prices[0],promotion:undefined}]}).regular,null);
|
||||
for (const unitAmount of [0,-900,9.5,"900",NaN]) assert.throws(()=>parseCatalog({...valid,prices:[{...valid.prices[0],unitAmount}]}));
|
||||
assert.throws(()=>parseCatalog({...valid,prices:[{...valid.prices[0],currency:"eur"}]}));
|
||||
assert.throws(()=>parseCatalog({...valid,limits:{...valid.limits,plusMaxRecordingBytes:1}}));
|
||||
assert.throws(()=>parseCatalog(null));
|
||||
const root=resolve(dirname(fileURLToPath(import.meta.url)),"..");
|
||||
const html=await readFile(resolve(root,"dist/pricing/index.html"),"utf8");
|
||||
assert.match(html, /<title>Pricing \| Recordly<\/title>/);
|
||||
assert.match(html, /<a href="\/pricing">Pricing<\/a>/);
|
||||
assert.match(html, /id="free-plan"/);
|
||||
assert.match(html, /id="cloud-plan"/);
|
||||
assert.doesNotMatch(html, /id="get-cloud"|href="#get-cloud"|Your plan lives in Recordly/);
|
||||
assert.match(html, /href="\/billing">Billing & refunds/);
|
||||
assert.doesNotMatch(html, /<a href="\/billing">Pricing/);
|
||||
const blog=await readFile(resolve(root,"dist/blog/index.html"),"utf8");
|
||||
assert.match(blog, /<a href="\/pricing">Pricing<\/a>/);
|
||||
assert.match(blog, /No posts yet/);
|
||||
console.log("Passed: validated prices/limits, safe rejection of invalid catalogs, pricing route, separate policy link, and empty blog preserved.");
|
||||
@@ -0,0 +1,44 @@
|
||||
import { createServer, loadEnv } from "vite";
|
||||
import { readFile, writeFile, mkdir, mkdtemp, rm } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const env = loadEnv("production", root, "VITE_");
|
||||
const origin = (process.env.VITE_SITE_URL || env.VITE_SITE_URL || "https://recordly-beta-landing.youngchen3442.workers.dev").replace(/\/+$/, "");
|
||||
const parsedOrigin = new URL(origin);
|
||||
if (!["https:", "http:"].includes(parsedOrigin.protocol) || parsedOrigin.origin !== origin) throw new Error("VITE_SITE_URL must be an absolute http(s) origin without a path.");
|
||||
const cacheDir = await mkdtemp(resolve(tmpdir(), "recordly-blog-prerender-"));
|
||||
const server = await createServer({root, cacheDir, mode:"production", server:{middlewareMode:true}, appType:"custom", optimizeDeps:{noDiscovery:true}});
|
||||
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => ({"&":"&", "<":"<", ">":">", '"':""", "'":"'"}[char]));
|
||||
const metaTag = (kind, name, content) => `<meta ${kind}="${name}" content="${escapeHtml(content)}">`;
|
||||
try {
|
||||
const {blogPaths, renderBlog} = await server.ssrLoadModule("/src/blog/render.jsx");
|
||||
const template = await readFile(resolve(root, "dist/index.html"), "utf8");
|
||||
if (!template.includes('<div id="root"></div>')) throw new Error("Built HTML is missing the expected React root.");
|
||||
for (const path of blogPaths) {
|
||||
const {body, meta} = renderBlog(path, origin);
|
||||
const tags = [
|
||||
metaTag("name", "description", meta.description), metaTag("name", "robots", meta.robots),
|
||||
`<link rel="canonical" href="${escapeHtml(meta.canonical)}">`,
|
||||
...Object.entries({"og:title":meta.title, "og:description":meta.description, "og:type":meta.post ? "article" : "website", "og:url":meta.canonical, "og:image":meta.image, "og:image:width":meta.image ? "1200" : null, "og:image:height":meta.image ? "630" : null, "og:site_name":"Recordly"}).filter(([,value]) => value).map(([key,value]) => metaTag("property", key, value)),
|
||||
...Object.entries({"twitter:card":meta.image ? "summary_large_image" : "summary", "twitter:title":meta.title, "twitter:description":meta.description, "twitter:image":meta.image}).filter(([,value]) => value).map(([key,value]) => metaTag("name", key, value)),
|
||||
...(meta.post ? [metaTag("property", "article:published_time", meta.post.datePublished), metaTag("property", "article:modified_time", meta.post.dateModified)] : []),
|
||||
`<script type="application/ld+json" data-blog-schema>${JSON.stringify(meta.structuredData).replace(/</g, "\\u003c")}</script>`
|
||||
].join("\n");
|
||||
const html = template.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(meta.title)}</title>`)
|
||||
.replace(/<meta\s+name="description"[^>]*>/, "")
|
||||
.replace("</head>", `${tags}\n</head>`)
|
||||
.replace('<div id="root"></div>', `<div id="root" data-prerendered>${body}</div>`);
|
||||
const folder = resolve(root, "dist", path.slice(1));
|
||||
await mkdir(folder, {recursive:true});
|
||||
await writeFile(resolve(folder, "index.html"), html);
|
||||
}
|
||||
const entries = ["/", ...blogPaths].map((path) => `<url><loc>${escapeHtml(`${origin}${path}`)}</loc></url>`).join("\n");
|
||||
await writeFile(resolve(root, "dist/sitemap.xml"), `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${entries}</urlset>\n`);
|
||||
console.log(`Prerendered ${blogPaths.length} blog pages with metadata, article content, and sitemap.`);
|
||||
} finally {
|
||||
await server.close();
|
||||
await rm(cacheDir, {recursive:true, force:true});
|
||||
}
|
||||
Reference in new issue
Block a user