webring 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.js CHANGED
@@ -1,40 +1,59 @@
1
1
  import { z } from "zod";
2
+ /** An RSS source */
2
3
  const SourceSchema = z.object({
3
- // the url of the feed
4
+ /** The URL of an RSS feed */
4
5
  url: z.string(),
5
- // a title for the feed
6
- title: z.string(),
6
+ /** A title to describe the feed */
7
+ title: z.string().describe("A title for the feed"),
7
8
  });
9
+ /** Configuration for the cache */
8
10
  const CacheConfigurationSchema = z.object({
9
- // how long to cache a results for
11
+ /** How long to cache a result for */
10
12
  cache_duration_minutes: z.number().default(60),
13
+ /** The location of a file to use as a cache */
11
14
  cache_file: z.string().default("cache.json"),
12
15
  });
16
+ /** A configuration object with caching possibly configured */
13
17
  const ConfigurationSchema = z.object({
14
- // list of sources to fetch
18
+ /** A list of sources to fetch */
15
19
  sources: SourceSchema.array(),
16
- // how many entries to return
20
+ /** Return the n latest updates from the source list. */
17
21
  number: z.number().default(3),
18
- // how many words to truncate the preview to
22
+ /** How many words the preview field should be truncated to in characters after HTML has been sanitized and parsed. */
19
23
  truncate: z.number().default(300),
24
+ /** Configuration for the cache */
20
25
  cache: CacheConfigurationSchema.optional(),
21
26
  });
27
+ /** A configuration object with caching definitely configured */
22
28
  export const CachedConfigurationSchema = ConfigurationSchema.extend({
29
+ /** Configuration for the cache */
23
30
  cache: CacheConfigurationSchema,
24
31
  });
32
+ /** A single entry from an RSS feed */
25
33
  const ResultEntrySchema = z.object({
34
+ /** The title of the entry */
26
35
  title: z.string(),
36
+ /** A direct link to the entry */
27
37
  url: z.string(),
38
+ /** The date of the entry */
28
39
  date: z.coerce.date(),
40
+ /** The source the entry is from */
29
41
  source: SourceSchema,
42
+ /** A preview of the entry. This may contain sanitized HTML. */
30
43
  preview: z.string().optional(),
31
44
  });
45
+ /** A list of results */
32
46
  const ResultSchema = z.array(ResultEntrySchema);
47
+ /** A single cache entry */
33
48
  export const CacheEntrySchema = z.object({
49
+ /** The time a source was last checked */
34
50
  timestamp: z.coerce.date(),
51
+ /** The data from the source */
35
52
  data: ResultEntrySchema,
36
53
  });
54
+ /** A mapping of source URLs to cache entries */
37
55
  export const CacheSchema = z.record(CacheEntrySchema);
56
+ /** The expected format fetched RSS feed entries */
38
57
  export const FeedEntrySchema = z
39
58
  .object({
40
59
  title: z.string(),
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,17 +1,34 @@
1
1
  {
2
2
  "name": "webring",
3
+ "description": "Collect the latest RSS items from your favorite feeds.",
4
+ "author": {
5
+ "name": "Jerred Shepherd",
6
+ "email": "npm@sjer.red",
7
+ "url": "https://sjer.red"
8
+ },
9
+ "homepage": "https://github.com/shepherdjerred/webring",
10
+ "license": "GPL-3.0-only",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/shepherdjerred/webring.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/shepherdjerred/webring/issues"
17
+ },
3
18
  "type": "module",
4
- "version": "1.1.0",
19
+ "version": "1.1.2",
5
20
  "scripts": {
6
21
  "prepare": "husky",
7
22
  "lint": "eslint src",
8
23
  "build": "tsc",
9
- "test": "vitest --disable-console-intercept"
24
+ "watch": "tsc -w",
25
+ "test": "vitest --disable-console-intercept",
26
+ "typedoc": "typedoc src/index.ts"
10
27
  },
11
28
  "main": "dist/index.js",
12
29
  "types": "dist/index.d.ts",
13
30
  "dependencies": {
14
- "remeda": "^2.0.1",
31
+ "remeda": "^2.5.0",
15
32
  "rss-parser": "^3.13.0",
16
33
  "sanitize-html": "^2.13.0",
17
34
  "truncate-html": "^1.1.1",
@@ -20,20 +37,24 @@
20
37
  "devDependencies": {
21
38
  "@commitlint/cli": "^19.3.0",
22
39
  "@commitlint/config-conventional": "^19.2.2",
23
- "@eslint/js": "^9.4.0",
40
+ "@eslint/js": "^9.7.0",
24
41
  "@tsconfig/node20": "^20.1.4",
25
42
  "@tsconfig/strictest": "^2.0.5",
26
- "@types/node": "^20.14.0",
43
+ "@types/express": "^4.17.21",
44
+ "@types/node": "^20.14.10",
27
45
  "@types/sanitize-html": "^2.11.0",
28
- "@typescript-eslint/eslint-plugin": "^7.11.0",
29
- "@typescript-eslint/parser": "^7.11.0",
46
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
47
+ "@typescript-eslint/parser": "^7.16.0",
30
48
  "eslint": "^8.57.0",
49
+ "express": "^4.19.2",
31
50
  "husky": "^9.0.11",
32
- "lint-staged": "^15.2.5",
33
- "prettier": "^3.3.0",
34
- "typescript": "^5.4.5",
35
- "typescript-eslint": "^7.11.0",
36
- "vitest": "^1.6.0"
51
+ "lint-staged": "^15.2.7",
52
+ "prettier": "^3.3.3",
53
+ "typedoc": "^0.26.4",
54
+ "typedoc-plugin-zod": "^1.2.0",
55
+ "typescript": "^5.5.3",
56
+ "typescript-eslint": "^7.16.0",
57
+ "vitest": "^2.0.2"
37
58
  },
38
59
  "lint-staged": {
39
60
  "*.{ts,tsx}": "eslint --cache --fix",
@@ -1,146 +1,7 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`it should fetch an RSS feed with caching 1`] = `
4
- [
5
- {
6
- "date": 2024-06-05T00:00:00.000Z,
7
- "preview": "TIL: Asymmetric Cryptography in Go",
8
- "source": {
9
- "title": "Jerred Shepherd",
10
- "url": "https://sjer.red/rss.xml",
11
- },
12
- "title": "TIL: Asymmetric Cryptography in Go",
13
- "url": "https://sjer.red/blog/til/2024-06-05/",
14
- },
15
- ]
16
- `;
3
+ exports[`it should fetch an RSS feed with caching 1`] = `"[{"title":"Creating an already-completed asynchronous activity in C++/WinRT, part 4","url":"https://devblogs.microsoft.com/oldnewthing/20240712-00/?p=109967","date":"2024-07-12T14:00:00.000Z","source":{"url":"http://localhost:PORT/rss-10.xml","title":"rss 10"},"preview":"Failing is easy. Failing correctly is hard. The post Creating an already-completed asynchronous activity in C++/WinRT, part 4 appeared first on The Old New Thing."}]"`;
17
4
 
18
- exports[`it should fetch an RSS feed without caching 1`] = `
19
- [
20
- {
21
- "date": 2024-06-05T00:00:00.000Z,
22
- "preview": "TIL: Asymmetric Cryptography in Go",
23
- "source": {
24
- "title": "Jerred Shepherd",
25
- "url": "https://sjer.red/rss.xml",
26
- },
27
- "title": "TIL: Asymmetric Cryptography in Go",
28
- "url": "https://sjer.red/blog/til/2024-06-05/",
29
- },
30
- ]
31
- `;
5
+ exports[`it should fetch an RSS feed without caching 1`] = `"[{"title":"Creating an already-completed asynchronous activity in C++/WinRT, part 4","url":"https://devblogs.microsoft.com/oldnewthing/20240712-00/?p=109967","date":"2024-07-12T14:00:00.000Z","source":{"title":"rss 10","url":"http://localhost:PORT/rss-10.xml"},"preview":"Failing is easy. Failing correctly is hard. The post Creating an already-completed asynchronous activity in C++/WinRT, part 4 appeared first on The Old New Thing."}]"`;
32
6
 
33
- exports[`it should fetch several RSS feeds 1`] = `
34
- [
35
- {
36
- "date": 2024-06-20T14:00:00.000Z,
37
- "preview": "Looking for constructors that take a character count. The post How to convert between different types of counted-string string types appeared first on The Old New Thing.",
38
- "source": {
39
- "title": "The Old New Thing",
40
- "url": "https://devblogs.microsoft.com/oldnewthing/feed",
41
- },
42
- "title": "How to convert between different types of counted-string string types",
43
- "url": "https://devblogs.microsoft.com/oldnewthing/20240620-00/?p=109922",
44
- },
45
- {
46
- "date": 2024-06-19T00:00:00.000Z,
47
- "preview": "The recent innovations in the AI space, most notably those such as GPT-4, obviously have far-reaching implications for society, ranging from the utopian eliminating of drudgery, to the dystopian damage to the livelihood of artists in a capitalist society, to existential threats to humanity itself. I...",
48
- "source": {
49
- "title": "Ludicity",
50
- "url": "https://ludic.mataroa.blog/rss/",
51
- },
52
- "title": "I Will Fucking Piledrive You If You Mention AI Again",
53
- "url": "https://ludic.mataroa.blog/blog/i-will-fucking-piledrive-you-if-you-mention-ai-again/",
54
- },
55
- {
56
- "date": 2024-06-19T00:00:00.000Z,
57
- "preview": "I love this meetup so much",
58
- "source": {
59
- "title": "Xe Iaso",
60
- "url": "https://xeiaso.net/blog.rss",
61
- },
62
- "title": "AI Tinkerers Ottawa v2.5.0 trip report",
63
- "url": "https://xeiaso.net/notes/2024/ait-ottawa-2.5.0/",
64
- },
65
- {
66
- "date": 2024-06-08T04:30:00.000Z,
67
- "preview": "Fresh water from snow, at 70 below!",
68
- "source": {
69
- "title": "brr.fyi",
70
- "url": "https://brr.fyi/feed.xml",
71
- },
72
- "title": "South Pole Water Infrastructure",
73
- "url": "https://brr.fyi/posts/south-pole-water-infrastructure",
74
- },
75
- {
76
- "date": 2024-06-01T00:00:00.000Z,
77
- "preview": ".dog-line { display: flex; flex-wrap: nowrap; flex-direction: row; width: 100%; height: 10rem; margin-top: 2rem; margin-bottom: 2rem; } .dog-line img { flex-grow: 1; height: auto; margin: 0; padding: 0; object-fit: contain; } .dog-grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap...",
78
- "source": {
79
- "title": "Sam Rose",
80
- "url": "https://samwho.dev/rss.xml",
81
- },
82
- "title": "A Commitment to Art and Dogs",
83
- "url": "https://samwho.dev/dogs/",
84
- },
85
- {
86
- "date": 2024-05-31T00:00:00.000Z,
87
- "preview": "This is an archive of some posts in a forum thread titled "Beware of Bioware" in a now defunct forum, with comments from that forum as well as blog comments from a now defunct blog that archived that made the first attempt to archive this content. The original posts were deleted shortly after being ...",
88
- "source": {
89
- "title": "Dan Luu",
90
- "url": "https://danluu.com/atom.xml",
91
- },
92
- "title": "Work-life balance at Bioware",
93
- "url": "https://danluu.com/bioware/",
94
- },
95
- {
96
- "date": 2024-05-24T00:00:00.000Z,
97
- "preview": "I needed a bit of a break from “real work” recently, so I started a new programming project that was low-stakes and purely recreational. On April 21st, I set out to see how much of a Unix-like operating system for x86_64 targets that I could put together in about a month. The result is Bunnix. Not i...",
98
- "source": {
99
- "title": "Drew DeVault",
100
- "url": "https://drewdevault.com/blog/index.xml",
101
- },
102
- "title": "Writing a Unix clone in about a month",
103
- "url": "https://drewdevault.com/2024/05/24/2024-05-24-Bunnix.html",
104
- },
105
- {
106
- "date": 2024-02-27T12:00:00.000Z,
107
- "preview": "The dream of soaring in the sky like a bird has captivated the human mind for ages. Although many failed, some eventually succeeded in achieving that goal. These days we take air transportation for granted, but the physics of flight can still be puzzling. In this article we’ll investigate what makes...",
108
- "source": {
109
- "title": "Bartosz Ciechanowski",
110
- "url": "https://ciechanow.ski/atom.xml",
111
- },
112
- "title": "Airfoil",
113
- "url": "https://ciechanow.ski/airfoil/",
114
- },
115
- {
116
- "date": 2024-01-29T00:00:00.000Z,
117
- "preview": "Web components won't take web development by storm, or show us the One True Way to build websites. What they will do is let us collectively build a rich ecosystem of dynamic components that work with any web stack.",
118
- "source": {
119
- "title": "Jake Lazaroff",
120
- "url": "https://jakelazaroff.com/rss.xml",
121
- },
122
- "title": "The Web Component Success Story",
123
- "url": "https://jakelazaroff.com/words/the-web-component-success-story/",
124
- },
125
- {
126
- "date": 2023-04-07T00:00:00.000Z,
127
- "preview": "In early 2019, some months after completing a rehab program for drug addiction, I was in a very open-minded headspace where I wanted to challenge myself and find ways to improve as a person. Drugs had filled my life with secrecy and lies, but that life was over. Although I was unsure of my next step...",
128
- "source": {
129
- "title": "Andreas Kling",
130
- "url": "https://awesomekling.github.io/feed.xml",
131
- },
132
- "title": "Making myself uncomfortable again",
133
- "url": "https://awesomekling.github.io/Making-myself-uncomfortable-again/",
134
- },
135
- {
136
- "date": 2022-09-17T00:00:00.000Z,
137
- "preview": undefined,
138
- "source": {
139
- "title": "Explained From First Principles",
140
- "url": "https://explained-from-first-principles.com/feed.xml",
141
- },
142
- "title": "Number theory explained from first principles",
143
- "url": "https://explained-from-first-principles.com/number-theory/",
144
- },
145
- ]
146
- `;
7
+ exports[`it should fetch several RSS feeds 1`] = `"[{"title":"Creating an already-completed asynchronous activity in C++/WinRT, part 4","url":"https://devblogs.microsoft.com/oldnewthing/20240712-00/?p=109967","date":"2024-07-12T14:00:00.000Z","source":{"title":"rss 10","url":"http://localhost:PORT/rss-10.xml"},"preview":"Failing is easy. Failing correctly is hard. The post Creating an already-completed asynchronous activity in C++/WinRT, part 4 appeared first on The Old New Thing."},{"title":"Garlic and gravel","url":"https://www.henrikkarlsson.xyz/p/garlic-and-gravel","date":"2024-07-12T11:34:25.000Z","source":{"title":"rss 16","url":"http://localhost:PORT/rss-16.xml"},"preview":"fragments"},{"title":"My Glorious Ascension To Thought Leadership","url":"https://ludic.mataroa.blog/blog/my-glorious-ascension-to-thought-leadership/","date":"2024-07-11T00:00:00.000Z","source":{"title":"rss 5","url":"http://localhost:PORT/rss-5.xml"},"preview":"I. This Escalated Quickly I mentioned one post ago that this blog has done approximately 2M+ lifetime views, with over a million of them being in the past two weeks in response to the now-infamous rant about AI, then I went out of my way to not write the most maximally viral thing possible for perso..."}]"`;
package/src/cache.ts CHANGED
@@ -10,8 +10,8 @@ import {
10
10
  type CacheConfiguration,
11
11
  } from "./types.js";
12
12
  import { fetch } from "./fetch.js";
13
- import fs from "fs/promises";
14
13
  import { asyncMapFilterUndefined } from "./util.js";
14
+ import fs from "fs/promises";
15
15
 
16
16
  async function loadCache({ cache_file }: CacheConfiguration): Promise<Cache> {
17
17
  try {
@@ -24,7 +24,10 @@ async function loadCache({ cache_file }: CacheConfiguration): Promise<Cache> {
24
24
  }
25
25
 
26
26
  async function saveCache({ cache_file }: CacheConfiguration, cache: Cache) {
27
- await fs.mkdir(cache_file.split("/").slice(0, -1).join("/"), { recursive: true });
27
+ const dir = cache_file.split("/").slice(0, -1).join("/");
28
+ if (dir !== "") {
29
+ await fs.mkdir(cache_file.split("/").slice(0, -1).join("/"), { recursive: true });
30
+ }
28
31
  await fs.writeFile(cache_file, JSON.stringify(cache));
29
32
  }
30
33
 
package/src/fetch.ts CHANGED
@@ -14,6 +14,7 @@ export async function fetchAll(config: Configuration) {
14
14
 
15
15
  export async function fetch(source: Source, length: number): Promise<ResultEntry | undefined> {
16
16
  const parser = new Parser();
17
+
17
18
  try {
18
19
  const feed = await parser.parseURL(source.url);
19
20
 
package/src/index.test.ts CHANGED
@@ -4,96 +4,58 @@ import { run } from "./index.js";
4
4
  import { tmpdir } from "os";
5
5
  import { mkdtemp } from "fs/promises";
6
6
  import { join } from "path";
7
+ import express from "express";
7
8
 
8
- // TODO: intercept network requests
9
- test("it should fetch an RSS feed without caching", async () => {
9
+ const app = express();
10
+ app.use(express.static("src/testdata"));
11
+
12
+ const port = Math.floor(Math.random() * 10000) + 3000;
13
+ app.listen(port, () => {
14
+ console.log(`Test server listening at http://localhost:${port.toString()}`);
15
+ });
16
+
17
+ function createUrl(path: string): string {
18
+ return `http://localhost:${port.toString()}/${path}`;
19
+ }
20
+
21
+ function createSources(count: number): Configuration["sources"] {
22
+ return Array.from({ length: count }, (_, i) => ({
23
+ title: `rss ${i.toString()}`,
24
+ url: createUrl(`rss-${i.toString()}.xml`),
25
+ }));
26
+ }
27
+
28
+ test("it should fetch an RSS feed without caching", { timeout: 30000 }, async () => {
10
29
  const config: Configuration = {
11
- sources: [
12
- {
13
- title: "Jerred Shepherd",
14
- url: "https://sjer.red/rss.xml",
15
- },
16
- ],
30
+ sources: createSources(19),
17
31
  number: 1,
18
32
  truncate: 300,
19
33
  };
20
34
 
21
35
  const result = await run(config);
22
- expect(result).toMatchSnapshot();
36
+ let string = JSON.stringify(result);
37
+ // replace the port number with a fixed value
38
+ string = string.replaceAll(port.toString(), "PORT");
39
+ expect(string).toMatchSnapshot();
23
40
  });
24
41
 
25
- test("it should fetch several RSS feeds", async () => {
42
+ test("it should fetch several RSS feeds", { timeout: 30000 }, async () => {
26
43
  const config: Configuration = {
27
- sources: [
28
- {
29
- url: "https://drewdevault.com/blog/index.xml",
30
- title: "Drew DeVault",
31
- },
32
- {
33
- url: "https://danluu.com/atom.xml",
34
- title: "Dan Luu",
35
- },
36
- {
37
- url: "https://jakelazaroff.com/rss.xml",
38
- title: "Jake Lazaroff",
39
- },
40
- {
41
- url: "https://awesomekling.github.io/feed.xml",
42
- title: "Andreas Kling",
43
- },
44
- {
45
- url: "https://xeiaso.net/blog.rss",
46
- title: "Xe Iaso",
47
- },
48
- {
49
- url: "https://ciechanow.ski/atom.xml",
50
- title: "Bartosz Ciechanowski",
51
- },
52
- {
53
- url: "https://explained-from-first-principles.com/feed.xml",
54
- title: "Explained From First Principles",
55
- },
56
- {
57
- url: "http://www.aaronsw.com/2002/feeds/pgessays.rss",
58
- title: "Paul Graham",
59
- },
60
- {
61
- url: "https://samwho.dev/rss.xml",
62
- title: "Sam Rose",
63
- },
64
- {
65
- url: "https://rachelbythebay.com/w/atom.xml",
66
- title: "Rachel Kroll",
67
- },
68
- {
69
- url: "https://brr.fyi/feed.xml",
70
- title: "brr.fyi",
71
- },
72
- {
73
- url: "https://devblogs.microsoft.com/oldnewthing/feed",
74
- title: "The Old New Thing",
75
- },
76
- {
77
- url: "https://ludic.mataroa.blog/rss/",
78
- title: "Ludicity",
79
- },
80
- ],
81
- number: 20,
44
+ sources: createSources(19),
45
+ number: 3,
82
46
  truncate: 300,
83
47
  };
84
48
 
85
49
  const result = await run(config);
86
- expect(result).toMatchSnapshot();
50
+ let string = JSON.stringify(result);
51
+ // replace the port number with a fixed value
52
+ string = string.replaceAll(port.toString(), "PORT");
53
+ expect(string).toMatchSnapshot();
87
54
  });
88
55
 
89
- test("it should fetch an RSS feed with caching", async () => {
56
+ test("it should fetch an RSS feed with caching", { timeout: 30000 }, async () => {
90
57
  const config: Configuration = {
91
- sources: [
92
- {
93
- title: "Jerred Shepherd",
94
- url: "https://sjer.red/rss.xml",
95
- },
96
- ],
58
+ sources: createSources(19),
97
59
  number: 1,
98
60
  truncate: 300,
99
61
  cache: {
@@ -103,7 +65,10 @@ test("it should fetch an RSS feed with caching", async () => {
103
65
  };
104
66
 
105
67
  const result = await run(config);
106
- expect(result).toMatchSnapshot();
68
+ let string = JSON.stringify(result);
69
+ // replace the port number with a fixed value
70
+ string = string.replaceAll(port.toString(), "PORT");
71
+ expect(string).toMatchSnapshot();
107
72
  });
108
73
 
109
74
  // https://sdorra.dev/posts/2024-02-12-vitest-tmpdir