astro-helmet 1.1.0 → 1.2.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.
- package/README.md +3 -3
- package/package.json +6 -6
- package/src/Helmet.astro +29 -1
- package/src/main.ts +56 -0
package/README.md
CHANGED
|
@@ -171,8 +171,8 @@ JSON-LD script tags are rendered with priority `105`, placing them after regular
|
|
|
171
171
|
|
|
172
172
|
When provided with an array of `headItems`, `astro-helmet` will merge the items together.
|
|
173
173
|
|
|
174
|
-
`headItems.meta` are deduplicated by `name`, `property` and `
|
|
175
|
-
Meta items later in the array replace earlier items.
|
|
174
|
+
`headItems.meta` are deduplicated by `name`, `property`, `http-equiv` and `charset`.
|
|
175
|
+
Meta items later in the array replace earlier items. Meta tags without any of these keys (e.g. `itemprop`-only tags) are never deduplicated.
|
|
176
176
|
|
|
177
177
|
`title` and `base` items are also deduplicated, with the last item in the array taking precedence.
|
|
178
178
|
|
|
@@ -270,7 +270,7 @@ function applyPriority(tag: Tag): Required<Tag> {
|
|
|
270
270
|
break
|
|
271
271
|
|
|
272
272
|
case 'style':
|
|
273
|
-
priority = tag.innerHTML
|
|
273
|
+
priority = tag.innerHTML?.includes('@import') ? 30 : 51
|
|
274
274
|
break
|
|
275
275
|
|
|
276
276
|
case 'script':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-helmet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A document head manager for astro.",
|
|
6
6
|
"author": "Ryan Voitiskis <ryanvoitiskis@pm.me> (https://ryanvoitiskis.com/)",
|
|
@@ -30,24 +30,24 @@
|
|
|
30
30
|
"release": "semantic-release"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@astrojs/check": "^0.9.
|
|
33
|
+
"@astrojs/check": "^0.9.8",
|
|
34
34
|
"@eslint/js": "^9.39.2",
|
|
35
35
|
"@semantic-release/changelog": "^6.0.3",
|
|
36
36
|
"@semantic-release/git": "^10.0.1",
|
|
37
37
|
"@semantic-release/github": "^11.0.1",
|
|
38
38
|
"@semantic-release/npm": "^12.0.1",
|
|
39
|
-
"@vitest/coverage-v8": "^
|
|
40
|
-
"astro": "^
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
40
|
+
"astro": "^6.1.3",
|
|
41
41
|
"eslint": "^9.39.2",
|
|
42
42
|
"prettier": "^3.4.2",
|
|
43
43
|
"prettier-plugin-astro": "^0.14.1",
|
|
44
44
|
"semantic-release": "^24.2.0",
|
|
45
45
|
"typescript": "^5.7.2",
|
|
46
46
|
"typescript-eslint": "^8.55.0",
|
|
47
|
-
"vitest": "^
|
|
47
|
+
"vitest": "^4.1.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"astro": "^4.0.0 || ^5.0.0"
|
|
50
|
+
"astro": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|
package/src/Helmet.astro
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { type HeadItems, type Tag, renderHead } from './main'
|
|
2
|
+
import { type HeadItems, type Tag, renderHead, getInlineContent, getExternalResources } from './main'
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
headItems: HeadItems | HeadItems[]
|
|
@@ -12,6 +12,34 @@ interface Props {
|
|
|
12
12
|
const { headItems, options = {} } = Astro.props
|
|
13
13
|
const { applyPriority, omitHeadTags = false } = options
|
|
14
14
|
const head = renderHead(headItems, applyPriority)
|
|
15
|
+
|
|
16
|
+
// Register CSP hashes and resources when running on Astro 6+ with CSP enabled.
|
|
17
|
+
// Cast avoids typecheck failures for downstream consumers on Astro 4/5 where
|
|
18
|
+
// `csp` isn't declared on `AstroGlobal`.
|
|
19
|
+
const csp = (Astro as unknown as {
|
|
20
|
+
csp?: {
|
|
21
|
+
insertScriptHash(hash: string): void
|
|
22
|
+
insertStyleHash(hash: string): void
|
|
23
|
+
insertScriptResource(url: string): void
|
|
24
|
+
insertStyleResource(url: string): void
|
|
25
|
+
}
|
|
26
|
+
}).csp
|
|
27
|
+
if (csp) {
|
|
28
|
+
for (const { type, content } of getInlineContent(headItems)) {
|
|
29
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
30
|
+
'SHA-256',
|
|
31
|
+
new TextEncoder().encode(content)
|
|
32
|
+
)
|
|
33
|
+
const hash = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)))
|
|
34
|
+
if (type === 'script') csp.insertScriptHash(`sha256-${hash}`)
|
|
35
|
+
else csp.insertStyleHash(`sha256-${hash}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const { type, url } of getExternalResources(headItems)) {
|
|
39
|
+
if (type === 'script') csp.insertScriptResource(url)
|
|
40
|
+
else csp.insertStyleResource(url)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
15
43
|
---
|
|
16
44
|
|
|
17
45
|
{
|
package/src/main.ts
CHANGED
|
@@ -183,6 +183,62 @@ function applyPriorityDefault(tag: Tag): Required<Tag> {
|
|
|
183
183
|
return { ...tag, priority }
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
export function getInlineContent(
|
|
187
|
+
headItems: HeadItems | HeadItems[]
|
|
188
|
+
): { type: 'script' | 'style'; content: string }[] {
|
|
189
|
+
const items = Array.isArray(headItems)
|
|
190
|
+
? mergeHeadItems(headItems.map((i) => normaliseHeadItems(i)))
|
|
191
|
+
: normaliseHeadItems(headItems)
|
|
192
|
+
|
|
193
|
+
const result: { type: 'script' | 'style'; content: string }[] = []
|
|
194
|
+
|
|
195
|
+
for (const style of items.style) {
|
|
196
|
+
if (style.innerHTML) result.push({ type: 'style', content: style.innerHTML })
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const script of items.script) {
|
|
200
|
+
if (script.innerHTML)
|
|
201
|
+
result.push({ type: 'script', content: script.innerHTML })
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const item of items.jsonLd) {
|
|
205
|
+
result.push({
|
|
206
|
+
type: 'script',
|
|
207
|
+
content: escapeJsonLd(
|
|
208
|
+
JSON.stringify({ ...item, '@context': 'https://schema.org' })
|
|
209
|
+
)
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getExternalResources(
|
|
217
|
+
headItems: HeadItems | HeadItems[]
|
|
218
|
+
): { type: 'script' | 'style'; url: string }[] {
|
|
219
|
+
const items = Array.isArray(headItems)
|
|
220
|
+
? mergeHeadItems(headItems.map((i) => normaliseHeadItems(i)))
|
|
221
|
+
: normaliseHeadItems(headItems)
|
|
222
|
+
|
|
223
|
+
const result: { type: 'script' | 'style'; url: string }[] = []
|
|
224
|
+
|
|
225
|
+
for (const script of items.script) {
|
|
226
|
+
if (script.src) result.push({ type: 'script', url: script.src })
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const link of items.link) {
|
|
230
|
+
if (link.rel === 'stylesheet' && link.href)
|
|
231
|
+
result.push({ type: 'style', url: link.href })
|
|
232
|
+
else if (link.rel === 'preload' && link.href) {
|
|
233
|
+
if (link.as === 'script') result.push({ type: 'script', url: link.href })
|
|
234
|
+
else if (link.as === 'style')
|
|
235
|
+
result.push({ type: 'style', url: link.href })
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
}
|
|
241
|
+
|
|
186
242
|
function escapeJsonLd(json: string): string {
|
|
187
243
|
return json.replace(/<\//g, '<\\/')
|
|
188
244
|
}
|