seo-crawl-audit 0.1.0

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.
@@ -0,0 +1,90 @@
1
+ import {
2
+ appendFile,
3
+ readFile,
4
+ unlink,
5
+ writeFile,
6
+ } from "node:fs/promises";
7
+ import { extname } from "node:path";
8
+
9
+ const SCHEMA_VERSION = 1;
10
+
11
+ function header(source) {
12
+ return {
13
+ type: "seo-audit-checkpoint",
14
+ schemaVersion: SCHEMA_VERSION,
15
+ source,
16
+ };
17
+ }
18
+
19
+ function isCompatible(value, source) {
20
+ return (
21
+ value?.type === "seo-audit-checkpoint" &&
22
+ value.schemaVersion === SCHEMA_VERSION &&
23
+ JSON.stringify(value.source) === JSON.stringify(source)
24
+ );
25
+ }
26
+
27
+ export function checkpointPathForOutput(output) {
28
+ const extension = extname(output);
29
+ if (!extension) {
30
+ return `${output}.checkpoint.ndjson`;
31
+ }
32
+
33
+ return `${output.slice(0, -extension.length)}.checkpoint.ndjson`;
34
+ }
35
+
36
+ export async function initializeCheckpoint(path, source) {
37
+ try {
38
+ const lines = (await readFile(path, "utf8")).split("\n");
39
+ const savedHeader = JSON.parse(lines[0]);
40
+
41
+ if (isCompatible(savedHeader, source)) {
42
+ const pages = [];
43
+ for (const line of lines.slice(1)) {
44
+ if (!line.trim()) {
45
+ continue;
46
+ }
47
+
48
+ try {
49
+ const record = JSON.parse(line);
50
+ if (record?.type === "page" && record.page?.url) {
51
+ pages.push(record.page);
52
+ }
53
+ } catch {
54
+ // A process can stop halfway through its final line. Earlier records
55
+ // remain valid and are still safe to resume from.
56
+ }
57
+ }
58
+
59
+ return { pages, resumed: pages.length > 0 };
60
+ }
61
+ } catch (error) {
62
+ if (error.code !== "ENOENT" && !(error instanceof SyntaxError)) {
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ await writeFile(path, `${JSON.stringify(header(source))}\n`, "utf8");
68
+ return { pages: [], resumed: false };
69
+ }
70
+
71
+ export async function appendCheckpointPages(path, pages) {
72
+ if (pages.length === 0) {
73
+ return;
74
+ }
75
+
76
+ const records = pages
77
+ .map((page) => JSON.stringify({ type: "page", page }))
78
+ .join("\n");
79
+ await appendFile(path, `${records}\n`, "utf8");
80
+ }
81
+
82
+ export async function removeCheckpoint(path) {
83
+ try {
84
+ await unlink(path);
85
+ } catch (error) {
86
+ if (error.code !== "ENOENT") {
87
+ throw error;
88
+ }
89
+ }
90
+ }