@yysng/astro-boilerplate 1.1.4 → 1.1.6
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/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yysng/astro-boilerplate",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Astro + Sanity Boilerplate with AEO Layers 1–5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./src/index.js"
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./components/*": "./src/components/*",
|
|
9
|
+
"./layouts/*": "./src/layouts/*",
|
|
10
|
+
"./styles/*": "./src/styles/*",
|
|
11
|
+
"./lib/*": "./src/lib/*",
|
|
12
|
+
"./utils/*": "./src/utils/*"
|
|
8
13
|
},
|
|
9
14
|
"main": "./src/index.js",
|
|
10
15
|
"files": [
|
|
11
16
|
"src"
|
|
12
17
|
],
|
|
13
18
|
"peerDependencies": {
|
|
19
|
+
"@portabletext/to-html": "^4.0.1",
|
|
14
20
|
"astro": "^5.0.0",
|
|
15
|
-
"sanity": "^4.0.0"
|
|
16
|
-
"@portabletext/to-html": "^4.0.1"
|
|
21
|
+
"sanity": "^4.0.0"
|
|
17
22
|
}
|
|
18
23
|
}
|
|
@@ -24,7 +24,9 @@ const {
|
|
|
24
24
|
jsonld = null, // page-level schemas
|
|
25
25
|
extraJson = [], // FAQPage, ItemList, etc.
|
|
26
26
|
breadcrumbs = null, // BreadcrumbList (JSON-LD)
|
|
27
|
-
geo = null // { placename, region, latitude, longitude }
|
|
27
|
+
geo = null, // { placename, region, latitude, longitude }
|
|
28
|
+
|
|
29
|
+
gtmId = null, // ✅ ADD THIS
|
|
28
30
|
} = Astro.props;
|
|
29
31
|
|
|
30
32
|
/* ------------------------------------------------------------
|
|
@@ -180,4 +182,15 @@ graph = graph.filter((item, idx, arr) => {
|
|
|
180
182
|
})}
|
|
181
183
|
></script>
|
|
182
184
|
)}
|
|
185
|
+
|
|
186
|
+
{gtmId && (
|
|
187
|
+
<script>
|
|
188
|
+
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
189
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
190
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|
191
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
|
192
|
+
})(window,document,'script','dataLayer','${gtmId}');`}
|
|
193
|
+
</script>
|
|
194
|
+
)}
|
|
195
|
+
|
|
183
196
|
</head>
|
|
@@ -4,14 +4,83 @@ import { CONTENT_REGISTRY } from "./registry.js";
|
|
|
4
4
|
import { schemas } from "./schemas.js";
|
|
5
5
|
import { getContentRoot } from "./config.js";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
function logAuditEvent({ section, incoming, merged }) {
|
|
9
|
+
const timestamp = new Date().toISOString();
|
|
10
|
+
|
|
11
|
+
console.log("[AI-EDIT]", {
|
|
12
|
+
section,
|
|
13
|
+
timestamp,
|
|
14
|
+
incoming, // what AI attempted to change
|
|
15
|
+
result: merged // final persisted state
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Merge only defined values from `incoming` into `existing`.
|
|
21
|
+
* - Undefined fields do NOT overwrite existing values
|
|
22
|
+
* - Nested objects (e.g. CTA) are merged recursively
|
|
23
|
+
*/
|
|
24
|
+
function mergeDefined(existing, incoming) {
|
|
25
|
+
const result = { ...existing };
|
|
26
|
+
|
|
27
|
+
for (const key of Object.keys(incoming)) {
|
|
28
|
+
const value = incoming[key];
|
|
29
|
+
|
|
30
|
+
// Skip undefined (true partial update behavior)
|
|
31
|
+
if (value === undefined) continue;
|
|
32
|
+
|
|
33
|
+
// Recursively merge objects (but not arrays)
|
|
34
|
+
if (
|
|
35
|
+
typeof value === "object" &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
!Array.isArray(value)
|
|
38
|
+
) {
|
|
39
|
+
result[key] = mergeDefined(existing[key] || {}, value);
|
|
40
|
+
} else {
|
|
41
|
+
result[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function updateContent(key, incoming) {
|
|
49
|
+
if (!incoming || typeof incoming !== "object") {
|
|
50
|
+
throw new Error("Incoming content must be an object");
|
|
51
|
+
}
|
|
52
|
+
|
|
8
53
|
const entry = CONTENT_REGISTRY[key];
|
|
9
|
-
if (!entry)
|
|
54
|
+
if (!entry) {
|
|
55
|
+
throw new Error(`Unknown content key: ${key}`);
|
|
56
|
+
}
|
|
10
57
|
|
|
11
58
|
const schema = schemas[key];
|
|
12
|
-
if (!schema)
|
|
13
|
-
|
|
59
|
+
if (!schema) {
|
|
60
|
+
throw new Error(`No schema defined for content key: ${key}`);
|
|
61
|
+
}
|
|
14
62
|
|
|
15
63
|
const filePath = path.join(getContentRoot(), entry.file);
|
|
16
|
-
|
|
64
|
+
|
|
65
|
+
let existing = {};
|
|
66
|
+
try {
|
|
67
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
68
|
+
existing = JSON.parse(raw);
|
|
69
|
+
} catch {}
|
|
70
|
+
|
|
71
|
+
const merged = mergeDefined(existing, incoming);
|
|
72
|
+
|
|
73
|
+
schema.validate(merged);
|
|
74
|
+
|
|
75
|
+
logAuditEvent({
|
|
76
|
+
section: key,
|
|
77
|
+
incoming,
|
|
78
|
+
merged
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await fs.writeFile(
|
|
82
|
+
filePath,
|
|
83
|
+
JSON.stringify(merged, null, 2),
|
|
84
|
+
"utf-8"
|
|
85
|
+
);
|
|
17
86
|
}
|