219 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { load } from "cheerio";
 | 
						|
import { readFile, writeFileSync, mkdirSync, cpSync, rmSync } from "fs";
 | 
						|
import process from "child_process";
 | 
						|
import { simpleSitemapAndIndex } from "sitemap";
 | 
						|
 | 
						|
import stores from "./stores.json" with { type: "json" };
 | 
						|
 | 
						|
function mkDir(path) {
 | 
						|
  try {
 | 
						|
    return mkdirSync(path);
 | 
						|
  } catch (err) {
 | 
						|
    if (err.code !== "EEXIST") throw err;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function writeFile(path, content) {
 | 
						|
  try {
 | 
						|
    writeFileSync(path, content);
 | 
						|
  } catch (err) {
 | 
						|
    if (err) throw err;
 | 
						|
  }
 | 
						|
  console.log(`${path} updated.`);
 | 
						|
}
 | 
						|
 | 
						|
function slugify(str) {
 | 
						|
  return str
 | 
						|
    .toLowerCase()
 | 
						|
    .replace(/é/g, "e")
 | 
						|
    .replace(/&/g, " and ")
 | 
						|
    .replace(/ /g, "-")
 | 
						|
    .replace(/[']+/g, "")
 | 
						|
    .replace(/[^\w-]+/g, "-")
 | 
						|
    .replace(/-+/g, "-")
 | 
						|
    .replace(/^-|-$/g, "");
 | 
						|
}
 | 
						|
 | 
						|
function cleanWebsite(str) {
 | 
						|
  return str
 | 
						|
    .toLowerCase()
 | 
						|
    .replace(/^https?:\/\//g, "")
 | 
						|
    .replace(/^www./g, "")
 | 
						|
    .replace(/\/$/g, "");
 | 
						|
}
 | 
						|
 | 
						|
function metaDescription({ name, meta, description }) {
 | 
						|
  if (meta && (meta.length > 155 || (meta.length < 145 && meta.length !== 0))) {
 | 
						|
    console.log(
 | 
						|
      `warning: meta tag for ${name} is invalid: 145/${meta.length}/155`
 | 
						|
    );
 | 
						|
  }
 | 
						|
  return meta || description.length > 155
 | 
						|
    ? description.slice(0, 153) + "..."
 | 
						|
    : description ||
 | 
						|
        "A guide to and map of every independent bookstore in New York City. We have a complete list of community bookstores in NYC with locations and descriptions.";
 | 
						|
}
 | 
						|
 | 
						|
function GetRecentChanges() {
 | 
						|
  const res = process
 | 
						|
    .execSync('git log -15 --pretty=format:"%ct %s"')
 | 
						|
    .toString();
 | 
						|
  return res.split("\n");
 | 
						|
}
 | 
						|
 | 
						|
function ChangeLog(logs) {
 | 
						|
  let res = "\n";
 | 
						|
  let i = 0;
 | 
						|
  logs.forEach((l) => {
 | 
						|
    if (
 | 
						|
      i > 3 ||
 | 
						|
      l.includes("[skip]") ||
 | 
						|
      l.includes("[ignore]") ||
 | 
						|
      l.includes("caddy") ||
 | 
						|
      l.includes("renovate")
 | 
						|
    ) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    i++;
 | 
						|
    const s = l.split(" ");
 | 
						|
    const date = new Date(s[0] * 1000).toLocaleDateString("en-US", {
 | 
						|
      year: "numeric",
 | 
						|
      month: "long",
 | 
						|
      day: "numeric",
 | 
						|
    });
 | 
						|
    res = res + `<li>${date} - ${s.slice(1).join(" ")}</li>\n`;
 | 
						|
  });
 | 
						|
  return res;
 | 
						|
}
 | 
						|
 | 
						|
function TableViewTemplate(rows) {
 | 
						|
  let table = "<table>";
 | 
						|
  rows.forEach((row, key) => {
 | 
						|
    row.rowNumber = key;
 | 
						|
    table = table + TableRowTemplate(row);
 | 
						|
  });
 | 
						|
  return table + "</table>";
 | 
						|
}
 | 
						|
 | 
						|
function TableRowTemplate({ rowNumber, name, slug, address, city }) {
 | 
						|
  return `
 | 
						|
  <tr id="${rowNumber}" class="spotRow">
 | 
						|
    <td class="name"><a href="/${slugify(name)}/">${name}</a></td>
 | 
						|
    <td><a href="/${slugify(name)}/">${address}, ${city}</a></td>
 | 
						|
  </tr>`;
 | 
						|
}
 | 
						|
 | 
						|
function TitleTemplate({ name }) {
 | 
						|
  return `${name} | Independent Bookstores in New York City - Best Community Bookstores in NYC`;
 | 
						|
}
 | 
						|
 | 
						|
function SelectedStoreTemplate({
 | 
						|
  name,
 | 
						|
  address,
 | 
						|
  city,
 | 
						|
  postcode,
 | 
						|
  website,
 | 
						|
  events,
 | 
						|
  cafe,
 | 
						|
  description,
 | 
						|
}) {
 | 
						|
  return `
 | 
						|
    <h2>${name}</h2>
 | 
						|
    <p class="address">${address}</p>
 | 
						|
    <p></p>
 | 
						|
    <p class="address">
 | 
						|
      ${city}, NY ${postcode}
 | 
						|
    </p>
 | 
						|
    <p>
 | 
						|
      View in:
 | 
						|
      <a
 | 
						|
        href="https://maps.google.com/maps?q=${encodeURIComponent(
 | 
						|
          name
 | 
						|
        )}+${address},${city},NY"
 | 
						|
        target="_blank"
 | 
						|
        >Google Maps</a
 | 
						|
      >
 | 
						|
      <a
 | 
						|
        href="http://maps.apple.com/?q=${encodeURIComponent(
 | 
						|
          name
 | 
						|
        )}&address=${address},${city},NY"
 | 
						|
        target="_blank"
 | 
						|
        >Apple Maps</a
 | 
						|
      >
 | 
						|
    </p>
 | 
						|
    <ul>
 | 
						|
      ${
 | 
						|
        website
 | 
						|
          ? `<li><a href="${website}" target="_blank">${cleanWebsite(
 | 
						|
              website
 | 
						|
            )}</a></li>`
 | 
						|
          : ""
 | 
						|
      }
 | 
						|
      <li class="storeDetails">Events: ${events}</li>
 | 
						|
      <li class="storeDetails">Café: ${cafe}</li>
 | 
						|
    </ul>
 | 
						|
    ${description ? `<p class="description">${description}</p>` : ""}`;
 | 
						|
}
 | 
						|
 | 
						|
readFile("./index.tmpl.html", function (err, data) {
 | 
						|
  const changeList = GetRecentChanges();
 | 
						|
  if (err) {
 | 
						|
    throw err;
 | 
						|
  }
 | 
						|
  const $ = load(data);
 | 
						|
 | 
						|
  stores.sort(function (a, b) {
 | 
						|
    var aname = a.name.toLowerCase();
 | 
						|
    var bname = b.name.toLowerCase();
 | 
						|
    return aname === bname ? 0 : +(aname > bname) || -1;
 | 
						|
  });
 | 
						|
 | 
						|
  $("#Stores").html(TableViewTemplate(stores));
 | 
						|
  $("#storeCount").html(stores.length);
 | 
						|
  $("#updatedOn").html(
 | 
						|
    new Date().toLocaleDateString("en-US", {
 | 
						|
      year: "numeric",
 | 
						|
      month: "long",
 | 
						|
      day: "numeric",
 | 
						|
    })
 | 
						|
  );
 | 
						|
  $("#changesList").html(ChangeLog(changeList));
 | 
						|
  const cssurl = $("link[type='text/css']").attr("href").split("?")[0];
 | 
						|
  $("link[type='text/css']").attr("href", cssurl + "?" + new Date().getTime());
 | 
						|
 | 
						|
  rmSync("./build", { recursive: true, force: true });
 | 
						|
 | 
						|
  mkDir("./build");
 | 
						|
 | 
						|
  writeFile("./build/index.html", $.html());
 | 
						|
 | 
						|
  cpSync("./site.css", "./build/site.css");
 | 
						|
  cpSync("./robots.txt", "./build/robots.txt");
 | 
						|
  cpSync("./img", "./build/img", { recursive: true });
 | 
						|
  cpSync("./stores.json", "./build/stores.json");
 | 
						|
 | 
						|
  let pages = [{ url: `/` }];
 | 
						|
 | 
						|
  stores.forEach((store) => {
 | 
						|
    $("#selected").html(SelectedStoreTemplate(store));
 | 
						|
    $("#info").addClass("hidden");
 | 
						|
    let title = TitleTemplate(store);
 | 
						|
    $("title").html(title);
 | 
						|
    $("meta[name='title']").attr("content", title);
 | 
						|
    $("meta[name='description']").attr("content", metaDescription(store));
 | 
						|
    $("link[rel='canonical']").attr(
 | 
						|
      "href",
 | 
						|
      `https://www.nycbookstores.org/${slugify(store.name)}/`
 | 
						|
    );
 | 
						|
    mkDir(`./build/${slugify(store.name)}`);
 | 
						|
    writeFile(`./build/${slugify(store.name)}/index.html`, $.html());
 | 
						|
    pages.push({ url: `/${slugify(store.name)}/` });
 | 
						|
  });
 | 
						|
  simpleSitemapAndIndex({
 | 
						|
    hostname: "https://www.nycbookstores.org/",
 | 
						|
    destinationDir: `./build/`,
 | 
						|
    sourceData: pages,
 | 
						|
    gzip: false,
 | 
						|
  });
 | 
						|
});
 |