astro-noemail 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.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Velohost
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # astro-noemail
2
+
3
+ **Build-time email address obfuscation for Astro**
4
+
5
+ `astro-noemail` is a tiny, static-first Astro integration that protects email addresses from basic scrapers by converting them into HTML entities **at build time**.
6
+
7
+ There is:
8
+ - no JavaScript at runtime
9
+ - no hydration
10
+ - no DOM mutation in the browser
11
+ - no adapter dependency
12
+
13
+ Just safer HTML.
14
+
15
+ ---
16
+
17
+ ## Why this exists
18
+
19
+ Email addresses published in plain HTML are routinely harvested by simple bots.
20
+
21
+ Most solutions rely on:
22
+ - JavaScript obfuscation
23
+ - runtime DOM rewriting
24
+ - client-side decoding
25
+
26
+ These approaches:
27
+ - increase page weight
28
+ - break with JS disabled
29
+ - add unnecessary complexity
30
+
31
+ `astro-noemail` solves this once, **during the build**, and produces static, cache-safe output.
32
+
33
+ ---
34
+
35
+ ## What it does
36
+
37
+ On `astro build`, the plugin:
38
+
39
+ - scans generated `.html` files in the output directory
40
+ - finds plain-text email addresses
41
+ - replaces them with numeric HTML entities
42
+
43
+ Example:
44
+
45
+ ```html
46
+ info@velohost.co.uk
47
+ ```
48
+
49
+ becomes:
50
+
51
+ ```html
52
+ info@velohost.co.uk
53
+ ```
54
+
55
+ Browsers render this normally, but basic scrapers do not.
56
+
57
+ ---
58
+
59
+ ## What it does NOT do
60
+
61
+ This plugin deliberately does **not**:
62
+
63
+ - run at runtime
64
+ - inject JavaScript
65
+ - parse the DOM
66
+ - modify Astro source files
67
+ - touch files outside the build output
68
+ - expose environment variables
69
+ - interfere with adapters or rendering
70
+
71
+ It operates **only** on final HTML output.
72
+
73
+ ---
74
+
75
+ ## Safety guarantees
76
+
77
+ The obfuscation logic is hardened to:
78
+
79
+ - skip `<script>`, `<style>`, and `<noscript>` blocks
80
+ - avoid double-encoding existing HTML entities
81
+ - only mutate files when a change is required
82
+ - never crash or fail a build
83
+
84
+ Astro-specific HTML rewriting is respected.
85
+
86
+ ---
87
+
88
+ ## Installation
89
+
90
+ ```bash
91
+ npm install astro-noemail
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Usage
97
+
98
+ Add the integration to your Astro config:
99
+
100
+ ```js
101
+ import { defineConfig } from "astro/config";
102
+ import astroNoEmail from "astro-noemail";
103
+
104
+ export default defineConfig({
105
+ integrations: [
106
+ astroNoEmail()
107
+ ]
108
+ });
109
+ ```
110
+
111
+ That’s it.
112
+
113
+ No configuration is required.
114
+
115
+ ---
116
+
117
+ ## Optional configuration
118
+
119
+ ### Disable the plugin
120
+
121
+ ```js
122
+ astroNoEmail({
123
+ enabled: false
124
+ })
125
+ ```
126
+
127
+ This can be useful for local testing or special builds.
128
+
129
+ ---
130
+
131
+ ## Mailto links
132
+
133
+ Email addresses inside visible text **and** `mailto:` links are obfuscated.
134
+
135
+ The links continue to function normally in browsers.
136
+
137
+ ---
138
+
139
+ ## `<noscript>` behaviour
140
+
141
+ Astro may rewrite `<noscript>` content during compilation.
142
+
143
+ If an email address is moved outside of a `<noscript>` block by Astro itself, it will be treated as normal HTML text and obfuscated.
144
+
145
+ This behaviour is intentional and considered safe.
146
+
147
+ ---
148
+
149
+ ## CDN & caching
150
+
151
+ Because all changes are static:
152
+
153
+ - output can be cached aggressively
154
+ - the plugin works behind any CDN
155
+ - Cloudflare, Netlify, Vercel, S3, and similar platforms are fully supported
156
+
157
+ ---
158
+
159
+ ## Failure behaviour
160
+
161
+ If a file cannot be processed:
162
+
163
+ - the error is logged
164
+ - the build continues
165
+ - the site is not broken
166
+
167
+ The plugin must never block a deployment.
168
+
169
+ ---
170
+
171
+ ## License
172
+
173
+ MIT
174
+
175
+ ---
176
+
177
+ ## Author
178
+
179
+ Built and maintained by **Velohost**
180
+
181
+ Website: https://velohost.co.uk/
182
+
183
+ Project page:
184
+ https://velohost.co.uk/plugins/astro-noemail/
@@ -0,0 +1,10 @@
1
+ import type { AstroIntegration } from "astro";
2
+ type NoEmailOptions = {
3
+ /**
4
+ * Enable or disable the plugin.
5
+ * Default: true
6
+ */
7
+ enabled?: boolean;
8
+ };
9
+ export default function astroNoEmail(options?: NoEmailOptions): AstroIntegration;
10
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export default function astroNoEmail(options = {}) {
4
+ const { enabled = true } = options;
5
+ return {
6
+ name: "astro-noemail",
7
+ hooks: {
8
+ "astro:build:done"({ dir }) {
9
+ if (!enabled)
10
+ return;
11
+ const outDir = new URL(dir).pathname;
12
+ if (!outDir || !fs.existsSync(outDir))
13
+ return;
14
+ walk(outDir, filePath => {
15
+ if (!filePath.endsWith(".html"))
16
+ return;
17
+ const original = fs.readFileSync(filePath, "utf-8");
18
+ const updated = obfuscateEmailsSafely(original);
19
+ if (updated !== original) {
20
+ fs.writeFileSync(filePath, updated, "utf-8");
21
+ }
22
+ });
23
+ console.log("[astro-noemail] email obfuscation complete");
24
+ }
25
+ }
26
+ };
27
+ }
28
+ /* ---------------------------------------------
29
+ Helpers
30
+ --------------------------------------------- */
31
+ function walk(dir, cb) {
32
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
33
+ const fullPath = path.join(dir, entry.name);
34
+ entry.isDirectory() ? walk(fullPath, cb) : cb(fullPath);
35
+ }
36
+ }
37
+ /**
38
+ * Safely obfuscate emails:
39
+ * - skips script/style/noscript
40
+ * - avoids double-encoding
41
+ */
42
+ function obfuscateEmailsSafely(html) {
43
+ const BLOCK_TAGS = /<(script|style|noscript)[^>]*>[\s\S]*?<\/\1>/gi;
44
+ const emailRegex = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
45
+ const blocks = [];
46
+ // Temporarily remove unsafe blocks
47
+ const stripped = html.replace(BLOCK_TAGS, match => {
48
+ blocks.push(match);
49
+ return `__ASTRO_NOEMAIL_BLOCK_${blocks.length - 1}__`;
50
+ });
51
+ const processed = stripped.replace(emailRegex, match => {
52
+ // Prevent double-encoding
53
+ if (match.includes("&#"))
54
+ return match;
55
+ return match
56
+ .split("")
57
+ .map(char => `&#${char.charCodeAt(0)};`)
58
+ .join("");
59
+ });
60
+ // Restore blocks untouched
61
+ return processed.replace(/__ASTRO_NOEMAIL_BLOCK_(\d+)__/g, (_, i) => blocks[Number(i)]);
62
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "astro-noemail",
3
+ "version": "0.1.0",
4
+ "description": "Prevents email address harvesting by obfuscating emails in generated Astro HTML at build time",
5
+ "homepage": "https://velohost.co.uk/plugins/astro-noemail/",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/velohost/astro-noemail.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/velohost/astro-noemail/issues"
12
+ },
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "keywords": [
16
+ "withastro",
17
+ "astro-integration",
18
+ "security",
19
+ "email",
20
+ "spam",
21
+ "scraping",
22
+ "static-site"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc"
35
+ },
36
+ "peerDependencies": {
37
+ "astro": "^4.0.0 || ^5.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.3",
41
+ "typescript": "^5.0.0"
42
+ }
43
+ }