From 7f0af2c0c1dc83887de64852b0c543aa35ca8ed3 Mon Sep 17 00:00:00 2001 From: Carter McBride <18412686+carterworks@users.noreply.github.com> Date: Tue, 18 Jun 2024 21:35:07 -0600 Subject: [PATCH] Switch from eslint to biome for linting and formatting --- .eslintrc.json | 50 ------------ bun.lockb | Bin 57714 -> 69812 bytes eslint.config.js | 9 +++ package.json | 21 ++--- src/@types/bubo.d.ts | 16 ++-- src/index.ts | 183 ++++++++++++++++++++++--------------------- src/renderer.ts | 34 ++++---- src/utilities.ts | 118 ++++++++++++++-------------- 8 files changed, 198 insertions(+), 233 deletions(-) delete mode 100644 .eslintrc.json create mode 100644 eslint.config.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index efced33..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "indent": [ - "error", - 2 - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ], - "no-trailing-spaces": [ - 2, - { - "skipBlankLines": false - } - ], - "no-multiple-empty-lines": [ - "error", - { - "max": 2, - "maxEOF": 1 - } - ], - "@typescript-eslint/no-var-requires": 0 - } -} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 552b1ed22d043ab88924adca9e1dce109cc6465b..323630e3111aab59481047fd6b9b1f3ec4a75863 100755 GIT binary patch delta 20215 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 \ No newline at end of file + | string + | number + | boolean + | { [x: string]: JSONValue } + | Array; diff --git a/src/index.ts b/src/index.ts index 61762b8..60e7338 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,25 +12,25 @@ import fetch from "node-fetch"; import Parser from "rss-parser"; -import { Feeds, FeedItem } from "./@types/bubo"; -import { Response } from "node-fetch"; +import type { Feeds, FeedItem } from "./@types/bubo"; +import type { Response } from "node-fetch"; import { render } from "./renderer.js"; import { - getLink, - getTitle, - getTimestamp, - parseFeed, - getFeedList, - getBuboInfo + getLink, + getTitle, + getTimestamp, + parseFeed, + getFeedList, + getBuboInfo, } from "./utilities.js"; -import { writeFile } from "fs/promises"; +import { writeFile } from "node:fs/promises"; import chalk from "chalk"; const buboInfo = await getBuboInfo(); const parser = new Parser(); const feedList = await getFeedList(); const feedListLength = - Object.entries(feedList).flat(2).length - Object.keys(feedList).length; + Object.entries(feedList).flat(2).length - Object.keys(feedList).length; /** * contentFromAllFeeds = Contains normalized, aggregated feed data and is passed to template renderer at the end @@ -42,14 +42,14 @@ const errors: unknown[] = []; // benchmarking data + utility const initTime = Date.now(); const benchmark = (startTime: number) => - chalk.cyanBright.bold(`${(Date.now() - startTime) / 1000} seconds`); + chalk.cyanBright.bold(`${(Date.now() - startTime) / 1000} seconds`); /** * These values are used to control throttling/batching the fetches: * - MAX_CONNECTION = max number of fetches to contain in a batch * - DELAY_MS = the delay in milliseconds between batches */ -const MAX_CONNECTIONS = Infinity; +const MAX_CONNECTIONS = Number.POSITIVE_INFINITY; const DELAY_MS = 850; const error = chalk.bold.red; @@ -66,26 +66,26 @@ let completed = 0; * and we want to build the static output. */ const finishBuild: () => void = async () => { - completed++; - // if this isn't the last feed, just return early - if (completed !== feedListLength) return; + completed++; + // if this isn't the last feed, just return early + if (completed !== feedListLength) return; - process.stdout.write("\nDone fetching everything!\n"); + process.stdout.write("\nDone fetching everything!\n"); - // generate the static HTML output from our template renderer - const output = render({ - data: contentFromAllFeeds, - errors: errors, - info: buboInfo - }); + // generate the static HTML output from our template renderer + const output = render({ + data: contentFromAllFeeds, + errors: errors, + info: buboInfo, + }); - // write the output to public/index.html - await writeFile("./public/index.html", output); - process.stdout.write( - `\nFinished writing to output:\n- ${feedListLength} feeds in ${benchmark( - initTime - )}\n- ${errors.length} errors\n` - ); + // write the output to public/index.html + await writeFile("./public/index.html", output); + process.stdout.write( + `\nFinished writing to output:\n- ${feedListLength} feeds in ${benchmark( + initTime, + )}\n- ${errors.length} errors\n`, + ); }; /** @@ -96,77 +96,80 @@ const finishBuild: () => void = async () => { * @returns Promise */ const processFeed = - ({ - group, - feed, - startTime - }: { - group: string; - feed: string; - startTime: number; - }) => - async (response: Response): Promise => { - const body = await parseFeed(response); - //skip to the next one if this didn't work out - if (!body) return; + ({ + group, + feed, + startTime, + }: { + group: string; + feed: string; + startTime: number; + }) => + async (response: Response): Promise => { + const body = await parseFeed(response); + //skip to the next one if this didn't work out + if (!body) return; - try { - const contents: FeedItem = ( - typeof body === "string" ? await parser.parseString(body) : body - ) as FeedItem; + try { + const contents: FeedItem = ( + typeof body === "string" ? await parser.parseString(body) : body + ) as FeedItem; - contents.feed = feed; - contents.title = getTitle(contents); - contents.link = getLink(contents); + contents.feed = feed; + contents.title = getTitle(contents); + contents.link = getLink(contents); - // try to normalize date attribute naming - contents?.items?.forEach(item => { - item.timestamp = getTimestamp(item); - item.title = getTitle(item); - item.link = getLink(item); - }); + // try to normalize date attribute naming + for (const item of contents.items) { + item.timestamp = getTimestamp(item); + item.title = getTitle(item); + item.link = getLink(item); + } - contentFromAllFeeds[group].push(contents as object); - process.stdout.write( - `${success("Successfully fetched:")} ${feed} - ${benchmark(startTime)}\n` - ); - } catch (err) { - process.stdout.write( - `${error("Error processing:")} ${feed} - ${benchmark( - startTime - )}\n${err}\n` - ); - errors.push(`Error processing: ${feed}\n\t${err}`); - } + contentFromAllFeeds[group].push(contents as object); + process.stdout.write( + `${success("Successfully fetched:")} ${feed} - ${benchmark(startTime)}\n`, + ); + } catch (err) { + process.stdout.write( + `${error("Error processing:")} ${feed} - ${benchmark( + startTime, + )}\n${err}\n`, + ); + errors.push(`Error processing: ${feed}\n\t${err}`); + } - finishBuild(); - }; + finishBuild(); + }; // go through each group of feeds and process const processFeeds = () => { - let idx = 0; + let idx = 0; - for (const [group, feeds] of Object.entries(feedList)) { - contentFromAllFeeds[group] = []; + for (const [group, feeds] of Object.entries(feedList)) { + contentFromAllFeeds[group] = []; - for (const feed of feeds) { - const startTime = Date.now(); - setTimeout(() => { - process.stdout.write(`Fetching: ${feed}...\n`); + for (const feed of feeds) { + const startTime = Date.now(); + setTimeout( + () => { + process.stdout.write(`Fetching: ${feed}...\n`); - fetch(feed) - .then(processFeed({ group, feed, startTime })) - .catch(err => { - process.stdout.write( - error(`Error fetching ${feed} ${benchmark(startTime)}\n`) - ); - errors.push(`Error fetching ${feed} ${err.toString()}\n`); - finishBuild(); - }); - }, (idx % (feedListLength / MAX_CONNECTIONS)) * DELAY_MS); - idx++; - } - } + fetch(feed) + .then(processFeed({ group, feed, startTime })) + .catch((err) => { + process.stdout.write( + error(`Error fetching ${feed} ${benchmark(startTime)}\n`), + ); + errors.push(`Error fetching ${feed} ${err.toString()}\n`); + finishBuild(); + }); + }, + (idx % (feedListLength / MAX_CONNECTIONS)) * DELAY_MS, + ); + idx++; + } + } }; -processFeeds(); \ No newline at end of file +processFeeds(); diff --git a/src/renderer.ts b/src/renderer.ts index 2e5a00d..a42fcc3 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -6,39 +6,39 @@ import nunjucks from "nunjucks"; const env: nunjucks.Environment = nunjucks.configure({ autoescape: true }); -import { readFile } from "fs/promises"; -import { Feeds, JSONValue } from "./@types/bubo"; +import { readFile } from "node:fs/promises"; +import type { Feeds, JSONValue } from "./@types/bubo"; /** * Global filters for my Nunjucks templates */ -env.addFilter("formatDate", function (dateString): string { - const date: Date = new Date(parseInt(dateString)); - return !isNaN(date.getTime()) ? date.toLocaleDateString() : dateString; +env.addFilter("formatDate", (dateString): string => { + const date: Date = new Date(Number.parseInt(dateString)); + return !Number.isNaN(date.getTime()) ? date.toLocaleDateString() : dateString; }); env.addGlobal("now", new Date().toUTCString()); // load the template const template: string = ( - await readFile(new URL("../config/template.html", import.meta.url)) + await readFile(new URL("../config/template.html", import.meta.url)) ).toString(); // generate the static HTML output from our template renderer const render = ({ - data, - errors, - info + data, + errors, + info, }: { - data: Feeds; - errors: unknown[]; - info?: JSONValue; + data: Feeds; + errors: unknown[]; + info?: JSONValue; }) => { - return env.renderString(template, { - data, - errors, - info - }); + return env.renderString(template, { + data, + errors, + info, + }); }; export { render }; diff --git a/src/utilities.ts b/src/utilities.ts index 75f4339..9a881cd 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -1,90 +1,90 @@ /* - There's a little inconsistency with how feeds report certain things like - title, links and timestamps. These helpers try to normalize that bit and - provide an order-of-operations list of properties to look for. + There's a little inconsistency with how feeds report certain things like + title, links and timestamps. These helpers try to normalize that bit and + provide an order-of-operations list of properties to look for. - Note: these are tightly-coupled to the template and a personal preference. + Note: these are tightly-coupled to the template and a personal preference. */ -import { Response } from "node-fetch"; -import { readFile } from "fs/promises"; -import { FeedItem, JSONValue } from "./@types/bubo"; +import type { Response } from "node-fetch"; +import { readFile } from "node:fs/promises"; +import type { FeedItem, JSONValue } from "./@types/bubo"; export const getLink = (obj: FeedItem): string => { - const link_values: string[] = ["link", "url", "guid", "home_page_url"]; - const keys: string[] = Object.keys(obj); - const link_property: string | undefined = link_values.find(link_value => - keys.includes(link_value) - ); - return link_property ? (obj[link_property] as string) : ""; + const link_values: string[] = ["link", "url", "guid", "home_page_url"]; + const keys: string[] = Object.keys(obj); + const link_property: string | undefined = link_values.find((link_value) => + keys.includes(link_value), + ); + return link_property ? (obj[link_property] as string) : ""; }; // fallback to URL for the title if not present // (title -> url -> link) export const getTitle = (obj: FeedItem): string => { - const title_values: string[] = ["title", "url", "link"]; - const keys: string[] = Object.keys(obj); + const title_values: string[] = ["title", "url", "link"]; + const keys: string[] = Object.keys(obj); - // if title is empty for some reason, fall back on url or link - const title_property: string | undefined = title_values.find( - title_value => keys.includes(title_value) && obj[title_value] - ); - return title_property ? (obj[title_property] as string) : ""; + // if title is empty for some reason, fall back on url or link + const title_property: string | undefined = title_values.find( + (title_value) => keys.includes(title_value) && obj[title_value], + ); + return title_property ? (obj[title_property] as string) : ""; }; // More dependable way to get timestamps export const getTimestamp = (obj: FeedItem): string => { - const dateString: string = ( - obj.pubDate || - obj.isoDate || - obj.date || - obj.date_published - ).toString(); - const timestamp: number = new Date(dateString).getTime(); - return isNaN(timestamp) ? dateString : timestamp.toString(); + const dateString: string = ( + obj.pubDate || + obj.isoDate || + obj.date || + obj.date_published + ).toString(); + const timestamp: number = new Date(dateString).getTime(); + return Number.isNaN(timestamp) ? dateString : timestamp.toString(); }; // parse RSS/XML or JSON feeds export async function parseFeed(response: Response): Promise { - const contentType = response.headers.get("content-type")?.split(";")[0]; + const contentType = response.headers.get("content-type")?.split(";")[0]; - if (!contentType) return {}; + if (!contentType) return {}; - const rssFeed = [contentType] - .map(item => - [ - "application/atom+xml", - "application/rss+xml", - "application/xml", - "text/xml", - "text/html" // this is kind of a gamble - ].includes(item) - ? response.text() - : false - ) - .filter(_ => _)[0]; + const rssFeed = [contentType] + .map((item) => + [ + "application/atom+xml", + "application/rss+xml", + "application/xml", + "text/xml", + "text/html", // this is kind of a gamble + ].includes(item) + ? response.text() + : false, + ) + .filter((_) => _)[0]; - const jsonFeed = [contentType] - .map(item => - ["application/json", "application/feed+json"].includes(item) - ? (response.json() as Promise) - : false - ) - .filter(_ => _)[0]; + const jsonFeed = [contentType] + .map((item) => + ["application/json", "application/feed+json"].includes(item) + ? (response.json() as Promise) + : false, + ) + .filter((_) => _)[0]; - return (rssFeed && rssFeed) || (jsonFeed && jsonFeed) || {}; + return (rssFeed && rssFeed) || (jsonFeed && jsonFeed) || {}; } export const getFeedList = async (): Promise => { - return JSON.parse( - ( - await readFile(new URL("../config/feeds.json", import.meta.url)) - ).toString() - ); + return JSON.parse( + ( + await readFile(new URL("../config/feeds.json", import.meta.url)) + ).toString(), + ); }; export const getBuboInfo = async (): Promise => { - return JSON.parse( - (await readFile(new URL("../package.json", import.meta.url))).toString() - ); + return JSON.parse( + (await readFile(new URL("../package.json", import.meta.url))).toString(), + ); };