astro-consent 1.0.5 → 1.0.7

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/dist/cli.cjs CHANGED
@@ -69,8 +69,9 @@ let source = fs.readFileSync(configPath, "utf8");
69
69
  REMOVE MODE
70
70
  ───────────────────────────────────── */
71
71
  if (command === "remove") {
72
- source = source.replace(/\s*astroConsent\s*\(\s*\{[\s\S]*?\}\s*\),?/gm, "");
73
- source = source.replace(/import\s+astroConsent\s+from\s+["']astro-consent["'];?\n?/, "");
72
+ source = source
73
+ .replace(/\s*astroConsent\s*\([\s\S]*?\),?/gm, "")
74
+ .replace(/import\s+astroConsent\s+from\s+["']astro-consent["'];?\n?/, "");
74
75
  fs.writeFileSync(configPath, source.trim() + "\n", "utf8");
75
76
  const cssFile = path.join(CWD, "src", "cookiebanner.css");
76
77
  if (fs.existsSync(cssFile))
@@ -87,29 +88,26 @@ if (!fs.existsSync(cssDir)) {
87
88
  fs.mkdirSync(cssDir, { recursive: true });
88
89
  }
89
90
  /* ─────────────────────────────────────
90
- Create FULL CSS VARIABLE OVERRIDE
91
+ Create CSS override file (ONCE)
91
92
  ───────────────────────────────────── */
92
93
  if (!fs.existsSync(cssFile)) {
93
94
  fs.writeFileSync(cssFile, `/* =========================================================
94
95
  astro-consent — FULL THEME VARIABLES
95
- All visuals are controlled from here.
96
- This file is NEVER overwritten.
96
+ This file is NEVER overwritten
97
97
  ========================================================= */
98
98
 
99
99
  :root {
100
- /* ───── Core ───── */
101
100
  --cb-font: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
102
101
 
103
- /* ───── Banner ───── */
104
- --cb-bg: rgba(12,18,32,0.88);
102
+ --cb-bg: #0c1220;
105
103
  --cb-border: rgba(255,255,255,0.08);
106
- --cb-text: #e5e7eb;
104
+ --cb-text: #ffffff;
107
105
  --cb-muted: #9ca3af;
108
106
  --cb-link: #60a5fa;
107
+
109
108
  --cb-radius: 16px;
110
109
  --cb-shadow: 0 20px 40px rgba(0,0,0,0.35);
111
110
 
112
- /* ───── Buttons ───── */
113
111
  --cb-btn-radius: 999px;
114
112
  --cb-btn-padding: 10px 18px;
115
113
 
@@ -117,195 +115,43 @@ if (!fs.existsSync(cssFile)) {
117
115
  --cb-accept-text: #052e16;
118
116
 
119
117
  --cb-reject-bg: #374151;
120
- --cb-reject-text: #e5e7eb;
118
+ --cb-reject-text: #ffffff;
121
119
 
122
120
  --cb-manage-bg: transparent;
123
- --cb-manage-text: #e5e7eb;
121
+ --cb-manage-text: #ffffff;
124
122
  --cb-manage-border: rgba(255,255,255,0.15);
125
123
 
126
- /* ───── Modal ───── */
127
- --cb-modal-backdrop: rgba(0,0,0,0.55);
128
124
  --cb-modal-bg: #0c1220;
125
+ --cb-modal-backdrop: rgba(0,0,0,0.55);
129
126
  --cb-modal-radius: 18px;
130
127
  --cb-modal-width: 480px;
131
128
 
132
- /* ───── Toggles ───── */
133
129
  --cb-toggle-off-bg: #374151;
134
130
  --cb-toggle-on-bg: #22c55e;
135
131
  --cb-toggle-knob: #ffffff;
136
132
  }
137
-
138
- /* =========================================================
139
- Banner
140
- ========================================================= */
141
-
142
- #astro-cookie-banner {
143
- position: fixed;
144
- left: 16px;
145
- right: 16px;
146
- bottom: 16px;
147
- z-index: 9999;
148
- font-family: var(--cb-font);
149
- }
150
-
151
- .cb-container {
152
- max-width: 1200px;
153
- margin: 0 auto;
154
- padding: 20px 24px;
155
- display: flex;
156
- gap: 24px;
157
- justify-content: space-between;
158
- align-items: center;
159
-
160
- background: var(--cb-bg);
161
- backdrop-filter: blur(14px);
162
- border-radius: var(--cb-radius);
163
- border: 1px solid var(--cb-border);
164
- box-shadow: var(--cb-shadow);
165
- color: var(--cb-text);
166
- }
167
-
168
- .cb-title {
169
- font-size: 16px;
170
- font-weight: 600;
171
- }
172
-
173
- .cb-desc {
174
- font-size: 14px;
175
- color: var(--cb-muted);
176
- }
177
-
178
- .cb-desc a {
179
- color: var(--cb-link);
180
- text-decoration: none;
181
- }
182
-
183
- .cb-actions {
184
- display: flex;
185
- gap: 10px;
186
- }
187
-
188
- /* =========================================================
189
- Buttons
190
- ========================================================= */
191
-
192
- .cb-actions button {
193
- padding: var(--cb-btn-padding);
194
- border-radius: var(--cb-btn-radius);
195
- border: 0;
196
- font-size: 14px;
197
- font-weight: 600;
198
- cursor: pointer;
199
- }
200
-
201
- .cb-accept {
202
- background: var(--cb-accept-bg);
203
- color: var(--cb-accept-text);
204
- }
205
-
206
- .cb-reject {
207
- background: var(--cb-reject-bg);
208
- color: var(--cb-reject-text);
209
- }
210
-
211
- .cb-manage {
212
- background: var(--cb-manage-bg);
213
- color: var(--cb-manage-text);
214
- border: 1px solid var(--cb-manage-border);
215
- }
216
-
217
- /* =========================================================
218
- Modal
219
- ========================================================= */
220
-
221
- #astro-cookie-modal {
222
- position: fixed;
223
- inset: 0;
224
- background: var(--cb-modal-backdrop);
225
- display: flex;
226
- align-items: center;
227
- justify-content: center;
228
- z-index: 10000;
229
- }
230
-
231
- .cb-modal {
232
- width: 100%;
233
- max-width: var(--cb-modal-width);
234
- background: var(--cb-modal-bg);
235
- border-radius: var(--cb-modal-radius);
236
- padding: 24px;
237
- color: var(--cb-text);
238
- }
239
-
240
- /* =========================================================
241
- Toggles
242
- ========================================================= */
243
-
244
- .cb-toggle {
245
- width: 44px;
246
- height: 24px;
247
- background: var(--cb-toggle-off-bg);
248
- border-radius: 999px;
249
- position: relative;
250
- cursor: pointer;
251
- }
252
-
253
- .cb-toggle span {
254
- position: absolute;
255
- width: 18px;
256
- height: 18px;
257
- background: var(--cb-toggle-knob);
258
- border-radius: 50%;
259
- top: 3px;
260
- left: 3px;
261
- transition: transform 0.2s ease;
262
- }
263
-
264
- .cb-toggle.active {
265
- background: var(--cb-toggle-on-bg);
266
- }
267
-
268
- .cb-toggle.active span {
269
- transform: translateX(20px);
270
- }
271
-
272
- /* =========================================================
273
- Mobile
274
- ========================================================= */
275
-
276
- @media (max-width: 640px) {
277
- .cb-container {
278
- flex-direction: column;
279
- align-items: stretch;
280
- gap: 16px;
281
- }
282
- }
283
133
  `, "utf8");
284
- console.log("🎨 Created src/cookiebanner.css (CSS variables enabled)");
134
+ console.log("🎨 Created src/cookiebanner.css");
285
135
  }
286
136
  /* ─────────────────────────────────────
287
- Inject Astro integration
137
+ Inject Astro integration ONLY
288
138
  ───────────────────────────────────── */
289
139
  if (!source.includes(`from "astro-consent"`)) {
290
140
  source = `import astroConsent from "astro-consent";\n${source}`;
291
141
  }
292
142
  if (!source.includes("astroConsent(")) {
293
- const injection = ` astroConsent({
143
+ source = source.replace(/integrations\s*:\s*\[/, match => `${match}
144
+ astroConsent({
294
145
  siteName: "My Website",
295
146
  policyUrl: "/privacy",
296
147
  consent: {
297
148
  days: 30,
298
149
  storageKey: "astro-consent"
299
- },
300
- categories: {
301
- analytics: false,
302
- marketing: false
303
150
  }
304
151
  }),
305
- `;
306
- source = source.replace(/integrations\s*:\s*\[/, match => `${match}\n${injection}`);
152
+ `);
307
153
  }
308
154
  fs.writeFileSync(configPath, source, "utf8");
309
155
  console.log("\n🎉 astro-consent installed successfully");
310
- console.log("👉 Edit src/cookiebanner.css to theme everything");
156
+ console.log("👉 Style everything via src/cookiebanner.css");
311
157
  console.log("👉 Run `astro-consent remove` to uninstall\n");
@@ -1,15 +1,23 @@
1
1
  /**
2
- * Default configuration values.
3
- * These are ONLY used as fallbacks if the user omits something.
4
- * User config always takes priority.
2
+ * DEFAULT_CONFIG
3
+ * ---------------------------------------------------------
4
+ * Fallback values ONLY.
5
+ *
6
+ * These are used:
7
+ * - when the user omits a value
8
+ * - during internal normalisation
9
+ *
10
+ * User-provided config ALWAYS takes priority.
5
11
  */
6
12
  export const DEFAULT_CONFIG = {
7
13
  siteName: "This website",
8
14
  policyUrl: "/privacy",
9
15
  consent: {
10
16
  enabled: true,
17
+ // Number of days consent remains valid
11
18
  days: 30,
12
- storageKey: "astro-cookie-consent"
19
+ // Must match runtime + frontend API
20
+ storageKey: "astro-consent"
13
21
  },
14
22
  categories: {
15
23
  essential: {
@@ -8,11 +8,9 @@ import { DEFAULT_CONFIG } from "./defaults.js";
8
8
  * Cache-busted to ensure updates are picked up during dev.
9
9
  */
10
10
  export async function loadUserConfig(projectRoot) {
11
- const configPath = path.join(projectRoot, "src", "cookiebanner", "config.ts");
11
+ const configPath = path.join(projectRoot, "src", "astro-consent", "config.ts");
12
12
  let userConfig = {};
13
13
  try {
14
- // 🔑 IMPORTANT:
15
- // Bust Node ESM import cache using file modified time
16
14
  const stat = fs.statSync(configPath);
17
15
  const cacheBuster = `?v=${stat.mtimeMs}`;
18
16
  const imported = await import(
@@ -21,20 +19,11 @@ export async function loadUserConfig(projectRoot) {
21
19
  userConfig = imported?.default ?? {};
22
20
  }
23
21
  catch (err) {
24
- console.warn("[cookiebanner] Failed to load user config, falling back to defaults:", err);
22
+ console.warn("[astro-consent] Failed to load user config, falling back to defaults");
25
23
  }
26
24
  return {
27
- /* ─────────────────────────────
28
- Site name
29
- ───────────────────────────── */
30
25
  siteName: userConfig.siteName ?? DEFAULT_CONFIG.siteName,
31
- /* ─────────────────────────────
32
- Policy URL
33
- ───────────────────────────── */
34
26
  policyUrl: userConfig.policyUrl ?? DEFAULT_CONFIG.policyUrl,
35
- /* ─────────────────────────────
36
- Consent settings
37
- ───────────────────────────── */
38
27
  consent: {
39
28
  enabled: userConfig.consent?.enabled ??
40
29
  DEFAULT_CONFIG.consent.enabled,
@@ -43,15 +32,14 @@ export async function loadUserConfig(projectRoot) {
43
32
  storageKey: userConfig.consent?.storageKey ??
44
33
  DEFAULT_CONFIG.consent.storageKey
45
34
  },
46
- /* ─────────────────────────────
47
- Categories
48
- ───────────────────────────── */
49
35
  categories: mergeCategories(userConfig.categories, DEFAULT_CONFIG.categories)
50
36
  };
51
37
  }
52
38
  /**
53
39
  * Merge category config safely.
54
- * Defaults are preserved, user overrides where provided.
40
+ * - Defaults are preserved
41
+ * - User overrides win
42
+ * - Custom categories are supported
55
43
  */
56
44
  function mergeCategories(userCategories, defaultCategories) {
57
45
  const merged = {};
@@ -62,11 +50,14 @@ function mergeCategories(userCategories, defaultCategories) {
62
50
  ...(userCategories?.[key] ?? {})
63
51
  };
64
52
  }
65
- // Include any custom categories the user added
53
+ // Add user-defined custom categories safely
66
54
  if (userCategories) {
67
55
  for (const key of Object.keys(userCategories)) {
68
56
  if (!merged[key]) {
69
- merged[key] = userCategories[key];
57
+ merged[key] = {
58
+ ...userCategories[key],
59
+ enabled: userCategories[key].enabled ?? false
60
+ };
70
61
  }
71
62
  }
72
63
  }
package/dist/index.js CHANGED
@@ -15,201 +15,35 @@ export default function astroConsent(options = {}) {
15
15
  hooks: {
16
16
  "astro:config:setup": ({ injectScript }) => {
17
17
  /* ─────────────────────────────────────
18
- Styles (banner + modal)
18
+ LOAD USER CSS (required)
19
19
  ───────────────────────────────────── */
20
- injectScript("head-inline", `
21
- const style = document.createElement("style");
22
- style.innerHTML = \`
23
- :root {
24
- --cb-bg: rgba(12,18,32,.88);
25
- --cb-border: rgba(255,255,255,.08);
26
- --cb-text: #e5e7eb;
27
- --cb-muted: #9ca3af;
28
- --cb-link: #60a5fa;
29
- --cb-accept: #22c55e;
30
- --cb-reject: #374151;
31
- }
32
-
33
- /* ───── Banner ───── */
34
-
35
- #astro-consent-banner {
36
- position: fixed;
37
- left: 16px;
38
- right: 16px;
39
- bottom: 16px;
40
- z-index: 9999;
41
- font-family: system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
42
- }
43
-
44
- .cb-container {
45
- max-width: 1200px;
46
- margin: 0 auto;
47
- padding: 20px 24px;
48
- display: flex;
49
- gap: 24px;
50
- justify-content: space-between;
51
- align-items: center;
52
-
53
- background: var(--cb-bg);
54
- backdrop-filter: blur(14px);
55
- border-radius: 16px;
56
- border: 1px solid var(--cb-border);
57
- box-shadow: 0 20px 40px rgba(0,0,0,.35);
58
-
59
- color: var(--cb-text);
60
- }
61
-
62
- .cb-text {
63
- max-width: 760px;
64
- }
65
-
66
- .cb-title {
67
- font-size: 16px;
68
- font-weight: 600;
69
- }
70
-
71
- .cb-desc {
72
- font-size: 14px;
73
- color: var(--cb-muted);
74
- }
75
-
76
- .cb-desc a {
77
- color: var(--cb-link);
78
- text-decoration: none;
79
- }
80
-
81
- .cb-desc a:hover {
82
- text-decoration: underline;
83
- }
84
-
85
- .cb-actions {
86
- display: flex;
87
- gap: 10px;
88
- flex-shrink: 0;
89
- }
90
-
91
- .cb-actions button {
92
- padding: 10px 18px;
93
- border-radius: 999px;
94
- border: 0;
95
- font-size: 14px;
96
- font-weight: 600;
97
- cursor: pointer;
98
- }
99
-
100
- .cb-accept {
101
- background: var(--cb-accept);
102
- color: #052e16;
103
- }
104
-
105
- .cb-reject {
106
- background: var(--cb-reject);
107
- color: #e5e7eb;
108
- }
109
-
110
- .cb-manage {
111
- background: transparent;
112
- color: var(--cb-text);
113
- border: 1px solid var(--cb-border);
114
- }
115
-
116
- /* ───── Modal ───── */
117
-
118
- #astro-consent-modal {
119
- position: fixed;
120
- inset: 0;
121
- background: rgba(0,0,0,.55);
122
- display: flex;
123
- align-items: center;
124
- justify-content: center;
125
- z-index: 10000;
126
- font-family: system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
127
- }
128
-
129
- .cb-modal {
130
- width: 100%;
131
- max-width: 480px;
132
- background: #0c1220;
133
- border-radius: 18px;
134
- padding: 24px;
135
- border: 1px solid var(--cb-border);
136
- color: var(--cb-text);
137
- }
138
-
139
- .cb-row {
140
- display: flex;
141
- justify-content: space-between;
142
- align-items: center;
143
- padding: 12px 0;
144
- border-bottom: 1px solid var(--cb-border);
145
- }
146
-
147
- .cb-row:last-child {
148
- border-bottom: 0;
149
- }
150
-
151
- .cb-toggle {
152
- width: 44px;
153
- height: 24px;
154
- background: #374151;
155
- border-radius: 999px;
156
- position: relative;
157
- cursor: pointer;
158
- }
159
-
160
- .cb-toggle span {
161
- position: absolute;
162
- width: 18px;
163
- height: 18px;
164
- background: #fff;
165
- border-radius: 50%;
166
- top: 3px;
167
- left: 3px;
168
- transition: transform .2s;
169
- }
170
-
171
- .cb-toggle.active {
172
- background: var(--cb-accept);
173
- }
174
-
175
- .cb-toggle.active span {
176
- transform: translateX(20px);
177
- }
178
-
179
- @media (max-width: 640px) {
180
- .cb-container {
181
- flex-direction: column;
182
- align-items: stretch;
183
- gap: 16px;
184
- }
185
- }
186
- \`;
187
- document.head.appendChild(style);
188
- `);
20
+ injectScript("head-inline", `@import "/src/cookiebanner.css";`);
189
21
  /* ─────────────────────────────────────
190
- Consent runtime
22
+ Consent runtime (NO CSS)
191
23
  ───────────────────────────────────── */
192
24
  injectScript("page", `
193
25
  (() => {
194
26
  const KEY = "${storageKey}";
195
27
  const TTL = ${ttl};
196
28
 
197
- function now(){ return Date.now(); }
29
+ function now() { return Date.now(); }
198
30
 
199
- function read(){
200
- try{
31
+ function read() {
32
+ try {
201
33
  const raw = localStorage.getItem(KEY);
202
- if(!raw) return null;
34
+ if (!raw) return null;
203
35
  const data = JSON.parse(raw);
204
- if(data.expiresAt < now()){
36
+ if (data.expiresAt < now()) {
205
37
  localStorage.removeItem(KEY);
206
38
  return null;
207
39
  }
208
40
  return data;
209
- }catch{ return null; }
41
+ } catch {
42
+ return null;
43
+ }
210
44
  }
211
45
 
212
- function write(categories){
46
+ function write(categories) {
213
47
  localStorage.setItem(KEY, JSON.stringify({
214
48
  updatedAt: now(),
215
49
  expiresAt: now() + TTL,
@@ -220,7 +54,7 @@ document.head.appendChild(style);
220
54
  window.astroConsent = {
221
55
  get: read,
222
56
  set: write,
223
- reset(){
57
+ reset() {
224
58
  localStorage.removeItem(KEY);
225
59
  location.reload();
226
60
  }
@@ -228,7 +62,7 @@ document.head.appendChild(style);
228
62
  })();
229
63
  `);
230
64
  /* ─────────────────────────────────────
231
- Banner + modal UI
65
+ Banner + modal UI (NO CSS)
232
66
  ───────────────────────────────────── */
233
67
  injectScript("page", `
234
68
  (() => {
@@ -241,7 +75,7 @@ document.head.appendChild(style);
241
75
 
242
76
  banner.innerHTML = \`
243
77
  <div class="cb-container">
244
- <div class="cb-text">
78
+ <div>
245
79
  <div class="cb-title">${siteName} uses cookies</div>
246
80
  <div class="cb-desc">
247
81
  Choose how your data is used.
@@ -259,18 +93,18 @@ document.head.appendChild(style);
259
93
  document.body.appendChild(banner);
260
94
 
261
95
  banner.querySelector(".cb-accept").onclick = () => {
262
- window.astroConsent.set({ essential:true, analytics:true, marketing:true });
96
+ window.astroConsent.set({ essential: true, analytics: true, marketing: true });
263
97
  banner.remove();
264
98
  };
265
99
 
266
100
  banner.querySelector(".cb-reject").onclick = () => {
267
- window.astroConsent.set({ essential:true });
101
+ window.astroConsent.set({ essential: true });
268
102
  banner.remove();
269
103
  };
270
104
 
271
105
  banner.querySelector(".cb-manage").onclick = openModal;
272
106
 
273
- function openModal(){
107
+ function openModal() {
274
108
  const modal = document.createElement("div");
275
109
  modal.id = "astro-consent-modal";
276
110
 
@@ -293,7 +127,7 @@ document.head.appendChild(style);
293
127
  <div class="cb-toggle" data-key="marketing"><span></span></div>
294
128
  </div>
295
129
 
296
- <div class="cb-actions" style="margin-top:16px;justify-content:flex-end">
130
+ <div class="cb-actions">
297
131
  <button class="cb-accept">Save preferences</button>
298
132
  </div>
299
133
  </div>
@@ -303,7 +137,7 @@ document.head.appendChild(style);
303
137
 
304
138
  modal.querySelectorAll(".cb-toggle").forEach(toggle => {
305
139
  const key = toggle.getAttribute("data-key");
306
- if(state[key]) toggle.classList.add("active");
140
+ if (state[key]) toggle.classList.add("active");
307
141
 
308
142
  toggle.onclick = () => {
309
143
  state[key] = !state[key];
@@ -312,13 +146,13 @@ document.head.appendChild(style);
312
146
  });
313
147
 
314
148
  modal.querySelector(".cb-accept").onclick = () => {
315
- window.astroConsent.set({ essential:true, ...state });
149
+ window.astroConsent.set({ essential: true, ...state });
316
150
  modal.remove();
317
151
  banner.remove();
318
152
  };
319
153
 
320
154
  modal.onclick = e => {
321
- if(e.target === modal) modal.remove();
155
+ if (e.target === modal) modal.remove();
322
156
  };
323
157
  }
324
158
  })();
@@ -114,4 +114,4 @@ export const DEFAULT_CSS = `
114
114
  color: var(--cb-btn-text);
115
115
  border-color: var(--cb-btn-bg);
116
116
  }
117
- `;
117
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-consent",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A privacy-first, GDPR-compliant cookie consent banner for Astro with a built-in preferences modal, zero dependencies, and full theme control.",
5
5
  "type": "module",
6
6
  "author": {