@tsdoctor/snapshot 0.2.0 → 0.2.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/content-hash.js CHANGED
@@ -49,14 +49,89 @@ function hashContent(content) {
49
49
  const normalized = normalizeContent(content);
50
50
  return createHash("sha256").update(normalized).digest("hex");
51
51
  }
52
+ const TIMESTAMP_KEYS = /* @__PURE__ */ new Set([
53
+ "publishedTime",
54
+ "modifiedTime",
55
+ "article:published_time",
56
+ "article:modified_time"
57
+ ]);
58
+ const JSON_LD_DATE_KEYS = /* @__PURE__ */ new Set([
59
+ "datePublished",
60
+ "dateModified",
61
+ "uploadDate"
62
+ ]);
63
+ const JSON_LD_BODY_KEYS = /* @__PURE__ */ new Set([
64
+ "children",
65
+ "innerHTML",
66
+ "textContent"
67
+ ]);
68
+ function isRecord(value) {
69
+ return typeof value === "object" && value !== null && !Array.isArray(value);
70
+ }
71
+ /**
72
+ * Strips a JSON-LD script body of its date fields.
73
+ *
74
+ * @remarks
75
+ * The body arrives as a string, so it must be parsed before its dates can be
76
+ * removed. A body that does not parse as JSON is returned unchanged rather
77
+ * than throwing — an unparseable body is still content worth hashing.
78
+ */
79
+ function stripJsonLdBody(body) {
80
+ let parsed;
81
+ try {
82
+ parsed = JSON.parse(body);
83
+ } catch {
84
+ return body;
85
+ }
86
+ return JSON.stringify(stripTimestamps(parsed));
87
+ }
88
+ /**
89
+ * Recursively removes timestamp-valued entries from a frontmatter value.
90
+ *
91
+ * @remarks
92
+ * Timestamps appear in two shapes. In the meta-pair form the value lives in a
93
+ * `content` field whose sibling `property`/`name` names a timestamp
94
+ * (`article:published_time`, `article:modified_time`). In the JSON-LD form it
95
+ * is an object key (`datePublished`, `dateModified`) inside a script body.
96
+ * Both are stripped; everything else survives so that an `og:image`,
97
+ * `og:description`, canonical `href` or JSON-LD version change is visible to
98
+ * change detection.
99
+ *
100
+ * The walk must be recursive: `head` is an array of `[tagName, attrs]` pairs,
101
+ * so a shallow pass would see nothing.
102
+ */
103
+ function stripTimestamps(value) {
104
+ if (Array.isArray(value)) return value.map(stripTimestamps);
105
+ if (!isRecord(value)) return value;
106
+ const property = value["property"] ?? value["name"];
107
+ const isTimestampTag = typeof property === "string" && TIMESTAMP_KEYS.has(property);
108
+ const isJsonLd = value["type"] === "application/ld+json";
109
+ const result = {};
110
+ for (const key of Object.keys(value).sort()) {
111
+ if (JSON_LD_DATE_KEYS.has(key)) continue;
112
+ if (isTimestampTag && key === "content") continue;
113
+ const entry = value[key];
114
+ if (isJsonLd && JSON_LD_BODY_KEYS.has(key) && typeof entry === "string") {
115
+ result[key] = stripJsonLdBody(entry);
116
+ continue;
117
+ }
118
+ result[key] = stripTimestamps(entry);
119
+ }
120
+ return result;
121
+ }
52
122
  /**
53
123
  * Generates a SHA-256 hash of frontmatter fields.
54
124
  *
55
125
  * @remarks
56
- * Excludes timestamp-related fields (`publishedTime`, `modifiedTime`, `head`,
126
+ * Excludes the top-level timestamp fields (`publishedTime`, `modifiedTime`,
57
127
  * `article:published_time`, `article:modified_time`) to prevent circular
58
- * dependencies in change detection. Keys are sorted alphabetically before
59
- * hashing to ensure consistent results regardless of object key order.
128
+ * dependencies in change detection, and strips timestamp-valued entries
129
+ * recursively from every remaining value including the `head` array's meta
130
+ * pairs and the date fields inside a JSON-LD script body. Everything else in
131
+ * `head` participates in the hash, so an `og:image`, `og:description` or
132
+ * canonical URL change marks the page modified. Keys are sorted
133
+ * alphabetically before hashing to ensure consistent results regardless of
134
+ * object key order.
60
135
  *
61
136
  * @param frontmatter - The frontmatter object to hash
62
137
  * @returns Hexadecimal SHA-256 hash string
@@ -75,15 +150,11 @@ function hashContent(content) {
75
150
  */
76
151
  function hashFrontmatter(frontmatter) {
77
152
  const filtered = {};
78
- for (const [key, value] of Object.entries(frontmatter)) {
79
- if (key === "publishedTime" || key === "modifiedTime" || key === "head" || key === "article:published_time" || key === "article:modified_time") continue;
80
- filtered[key] = value;
153
+ for (const key of Object.keys(frontmatter).sort()) {
154
+ if (TIMESTAMP_KEYS.has(key)) continue;
155
+ filtered[key] = stripTimestamps(frontmatter[key]);
81
156
  }
82
- const sorted = Object.keys(filtered).sort().reduce((acc, key) => {
83
- acc[key] = filtered[key];
84
- return acc;
85
- }, {});
86
- const json = JSON.stringify(sorted);
157
+ const json = JSON.stringify(filtered);
87
158
  return createHash("sha256").update(json).digest("hex");
88
159
  }
89
160
 
package/index.d.ts CHANGED
@@ -48,10 +48,15 @@ declare function hashContent(content: string): string;
48
48
  * Generates a SHA-256 hash of frontmatter fields.
49
49
  *
50
50
  * @remarks
51
- * Excludes timestamp-related fields (`publishedTime`, `modifiedTime`, `head`,
51
+ * Excludes the top-level timestamp fields (`publishedTime`, `modifiedTime`,
52
52
  * `article:published_time`, `article:modified_time`) to prevent circular
53
- * dependencies in change detection. Keys are sorted alphabetically before
54
- * hashing to ensure consistent results regardless of object key order.
53
+ * dependencies in change detection, and strips timestamp-valued entries
54
+ * recursively from every remaining value including the `head` array's meta
55
+ * pairs and the date fields inside a JSON-LD script body. Everything else in
56
+ * `head` participates in the hash, so an `og:image`, `og:description` or
57
+ * canonical URL change marks the page modified. Keys are sorted
58
+ * alphabetically before hashing to ensure consistent results regardless of
59
+ * object key order.
55
60
  *
56
61
  * @param frontmatter - The frontmatter object to hash
57
62
  * @returns Hexadecimal SHA-256 hash string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsdoctor/snapshot",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Incremental-build snapshot tracking for static documentation pipelines: a schema-versioned SQLite store of per-file content hashes and timestamps, built on @effected/store, plus the pure SHA-256 content-hashing helpers that feed it.",
6
6
  "keywords": [