astro-consent 1.0.16 โ†’ 2.0.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/dist/cli.cjs CHANGED
@@ -37,9 +37,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const path = __importStar(require("node:path"));
39
39
  const process = __importStar(require("node:process"));
40
+ const cssTemplate_js_1 = require("./templates/cssTemplate.cjs");
40
41
  const CWD = process.cwd();
41
42
  const args = process.argv.slice(2);
42
- const command = args[0] ?? "install";
43
+ const command = args[0] ?? "init";
44
+ const flags = new Set(args.slice(1));
45
+ const isDryRun = flags.has("--dry-run");
46
+ const isJson = flags.has("--json");
43
47
  const ASTRO_CONFIG_FILES = [
44
48
  "astro.config.mjs",
45
49
  "astro.config.ts",
@@ -57,289 +61,249 @@ function exitWith(message, code = 1) {
57
61
  console.error(`\nโŒ ${message}\n`);
58
62
  process.exit(code);
59
63
  }
64
+ function printHelp() {
65
+ console.log(`
66
+ astro-consent
67
+
68
+ Usage:
69
+ npx astro-consent init
70
+ npx astro-consent remove
71
+ npx astro-consent doctor
72
+ npx astro-consent status
73
+
74
+ Backwards compatible alias:
75
+ npx astro-consent
76
+
77
+ Flags:
78
+ --dry-run Show what would change without writing files
79
+ --json Output machine-readable doctor/status data
80
+ `);
81
+ process.exit(0);
82
+ }
83
+ function readConfig() {
84
+ const configPath = findAstroConfig();
85
+ if (!configPath) {
86
+ exitWith("No astro.config.(mjs|ts|js) found. Run this in the root of an Astro project.");
87
+ }
88
+ return {
89
+ configPath,
90
+ source: fs.readFileSync(configPath, "utf8")
91
+ };
92
+ }
93
+ function ensureCssFile(dryRun = false) {
94
+ const cssDir = path.join(CWD, "src", "cookiebanner");
95
+ const cssFile = path.join(cssDir, "styles.css");
96
+ if (!fs.existsSync(cssDir)) {
97
+ if (!dryRun)
98
+ fs.mkdirSync(cssDir, { recursive: true });
99
+ }
100
+ if (!fs.existsSync(cssFile)) {
101
+ if (!dryRun) {
102
+ fs.writeFileSync(cssFile, cssTemplate_js_1.DEFAULT_CSS.trimStart(), "utf8");
103
+ console.log("๐ŸŽจ Created src/cookiebanner/styles.css");
104
+ }
105
+ }
106
+ return cssFile;
107
+ }
108
+ function buildIntegrationBlock() {
109
+ return ` // astro-consent:start
110
+ astroConsent({
111
+ siteName: "My Website",
112
+ headline: "Manage cookie preferences for My Website",
113
+ description: "We use cookies to improve site performance, measure traffic, and support marketing.",
114
+ acceptLabel: "Accept all",
115
+ rejectLabel: "Reject all",
116
+ manageLabel: "Manage preferences",
117
+ cookiePolicyUrl: "/cookie-policy",
118
+ privacyPolicyUrl: "/privacy",
119
+ displayUntilIdle: true,
120
+ displayIdleDelayMs: 1000,
121
+ consent: {
122
+ days: 30,
123
+ storageKey: "astro-consent"
124
+ }
125
+ }),
126
+ // astro-consent:end
127
+ `;
128
+ }
129
+ function removeIntegrationBlock(source) {
130
+ return source.replace(/\n\s*\/\/ astro-consent:start[\s\S]*?\/\/ astro-consent:end\s*,?/m, "\n");
131
+ }
60
132
  /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
61
133
  Locate astro.config
62
134
 
63
135
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
64
- const configPath = findAstroConfig();
65
- if (!configPath) {
66
- exitWith("No astro.config.(mjs|ts|js) found. Run this in the root of an Astro project.");
136
+ if (command === "--help" || command === "-h" || command === "help") {
137
+ printHelp();
67
138
  }
68
- let source = fs.readFileSync(configPath, "utf8");
69
139
  /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
70
140
  REMOVE MODE
71
141
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
72
142
  if (command === "remove") {
73
- source = source
74
- .replace(/\s*astroConsent\s*\([\s\S]*?\),?/gm, "")
75
- .replace(/import\s+astroConsent\s+from\s+["']astro-consent["'];?\n?/, "");
76
- fs.writeFileSync(configPath, source.trim() + "\n", "utf8");
77
- const cssFile = path.join(CWD, "src", "cookiebanner.css");
78
- if (fs.existsSync(cssFile))
143
+ const { configPath, source: original } = readConfig();
144
+ let source = removeIntegrationBlock(original);
145
+ source = source.replace(/import\s+astroConsent\s+from\s+["']astro-consent["'];?\n?/, "");
146
+ if (!isDryRun) {
147
+ fs.writeFileSync(configPath, source.trim() + "\n", "utf8");
148
+ }
149
+ const cssFile = path.join(CWD, "src", "cookiebanner", "styles.css");
150
+ if (!isDryRun && fs.existsSync(cssFile))
79
151
  fs.unlinkSync(cssFile);
80
- console.log("\n๐Ÿงน astro-consent fully removed\n");
152
+ console.log(isDryRun ? "\n๐Ÿงช Dry run: astro-consent would be removed\n" : "\n๐Ÿงน astro-consent fully removed\n");
81
153
  process.exit(0);
82
154
  }
83
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
84
- INSTALL MODE
85
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
86
- const cssDir = path.join(CWD, "src");
87
- const cssFile = path.join(cssDir, "cookiebanner.css");
88
- if (!fs.existsSync(cssDir)) {
89
- fs.mkdirSync(cssDir, { recursive: true });
90
- }
91
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
92
- Create CSS file ONCE (theme + structure)
93
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
94
- if (!fs.existsSync(cssFile)) {
95
- fs.writeFileSync(cssFile, `/* =========================================================
96
- astro-consent โ€” FULL THEME + STRUCTURE
97
- This file is NEVER overwritten.
98
- ========================================================= */
99
-
100
- :root {
101
- --cb-font: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
102
-
103
- --cb-bg: var(--color-surface, #0c1220);
104
- --cb-border: var(--color-border, rgba(255,255,255,0.08));
105
- --cb-shadow: 0 20px 40px rgba(15, 23, 42, 0.08);
106
-
107
- --cb-text: var(--color-text, #ffffff);
108
- --cb-muted: var(--color-muted, #9ca3af);
109
- --cb-link: var(--color-primary, #60a5fa);
110
-
111
- --cb-radius: 16px;
112
- --cb-btn-radius: 999px;
113
- --cb-btn-padding: 10px 18px;
114
-
115
- --cb-accept-bg: var(--color-cta, #22c55e);
116
- --cb-accept-text: #ffffff;
117
-
118
- --cb-reject-bg: var(--cb-border);
119
- --cb-reject-text: var(--cb-text);
120
-
121
- --cb-manage-bg: transparent;
122
- --cb-manage-text: var(--cb-text);
123
- --cb-manage-border: var(--cb-border);
124
-
125
- --cb-modal-bg: var(--cb-bg);
126
- --cb-modal-backdrop: rgba(15, 23, 42, 0.45);
127
- --cb-modal-radius: 18px;
128
- --cb-modal-width: 480px;
129
-
130
- --cb-toggle-off-bg: var(--cb-border);
131
- --cb-toggle-on-bg: var(--color-accent, #22c55e);
132
- --cb-toggle-knob: #ffffff;
133
- }
134
-
135
- /* ===============================
136
- Banner
137
- =============================== */
138
-
139
- #astro-consent-banner {
140
- position: fixed;
141
- left: 16px;
142
- right: 16px;
143
- bottom: 16px;
144
- z-index: 9999;
145
- font-family: var(--cb-font);
146
- }
147
-
148
- .cb-container {
149
- max-width: 1200px;
150
- margin: 0 auto;
151
- padding: 20px 24px;
152
- display: flex;
153
- gap: 24px;
154
- justify-content: space-between;
155
- align-items: center;
156
-
157
- background: var(--cb-bg);
158
- border-radius: var(--cb-radius);
159
- border: 1px solid var(--cb-border);
160
- box-shadow: var(--cb-shadow);
161
- color: var(--cb-text);
162
- }
163
-
164
- .cb-title {
165
- font-size: 16px;
166
- font-weight: 600;
167
- }
168
-
169
- .cb-desc {
170
- margin-top: 4px;
171
- font-size: 14px;
172
- color: var(--cb-muted);
173
- }
174
-
175
- .cb-desc a {
176
- color: var(--cb-link);
177
- text-decoration: none;
178
- }
179
-
180
- .cb-desc a:hover {
181
- text-decoration: underline;
182
- }
183
-
184
- /* ===============================
185
- Buttons
186
- =============================== */
187
-
188
- .cb-actions {
189
- display: flex;
190
- gap: 10px;
191
- }
192
-
193
- .cb-actions button {
194
- padding: var(--cb-btn-padding);
195
- border-radius: var(--cb-btn-radius);
196
- border: 1px solid transparent;
197
- font-size: 14px;
198
- font-weight: 600;
199
- cursor: pointer;
200
- }
201
-
202
- .cb-accept {
203
- background: var(--cb-accept-bg);
204
- color: var(--cb-accept-text);
205
- }
206
-
207
- .cb-reject {
208
- background: var(--cb-reject-bg);
209
- color: var(--cb-reject-text);
210
- }
211
-
212
- .cb-manage {
213
- background: var(--cb-manage-bg);
214
- color: var(--cb-manage-text);
215
- border: 1px solid var(--cb-manage-border);
216
- }
217
-
218
- /* ===============================
219
- Modal
220
- =============================== */
221
-
222
- #astro-consent-modal {
223
- position: fixed;
224
- inset: 0;
225
- background: var(--cb-modal-backdrop);
226
- display: flex;
227
- align-items: center;
228
- justify-content: center;
229
- z-index: 10000;
230
- }
231
-
232
- .cb-modal {
233
- width: 100%;
234
- max-width: var(--cb-modal-width);
235
- background: var(--cb-modal-bg);
236
- border-radius: var(--cb-modal-radius);
237
- padding: 28px;
238
- color: var(--cb-text);
239
- border: 1px solid var(--cb-border);
240
- }
241
-
242
- /* ===============================
243
- Modal rows (SPACING FIXED)
244
- =============================== */
245
-
246
- .cb-row {
247
- display: flex;
248
- justify-content: space-between;
249
- align-items: center;
250
- gap: 16px;
251
- padding: 18px 0;
252
- border-bottom: 1px solid var(--cb-border);
253
- }
254
-
255
- .cb-row:last-of-type {
256
- border-bottom: 0;
257
- padding-bottom: 32px;
258
- }
259
-
260
- /* ===============================
261
- Modal actions
262
- =============================== */
263
-
264
- .cb-modal .cb-actions {
265
- margin-top: 24px;
266
- padding-top: 20px;
267
- border-top: 1px solid var(--cb-border);
268
- display: flex;
269
- justify-content: flex-end;
270
- }
271
-
272
- /* ===============================
273
- Toggles
274
- =============================== */
275
-
276
- .cb-toggle {
277
- width: 44px;
278
- height: 24px;
279
- background: var(--cb-toggle-off-bg);
280
- border-radius: 999px;
281
- position: relative;
282
- cursor: pointer;
283
- flex-shrink: 0;
284
- }
285
-
286
- .cb-toggle span {
287
- position: absolute;
288
- width: 18px;
289
- height: 18px;
290
- background: var(--cb-toggle-knob);
291
- border-radius: 50%;
292
- top: 3px;
293
- left: 3px;
294
- transition: transform 0.2s ease;
295
- }
296
-
297
- .cb-toggle.active {
298
- background: var(--cb-toggle-on-bg);
299
- }
300
-
301
- .cb-toggle.active span {
302
- transform: translateX(20px);
155
+ if (command === "doctor") {
156
+ const { configPath, source } = readConfig();
157
+ const cssFile = path.join(CWD, "src", "cookiebanner", "styles.css");
158
+ const layoutPath = path.join(CWD, "src", "layouts", "BaseLayout.astro");
159
+ const findings = [];
160
+ const checks = [];
161
+ const hasImport = source.includes(`from "astro-consent"`) ||
162
+ source.includes(`from 'astro-consent'`);
163
+ checks.push({
164
+ label: "Config import",
165
+ ok: hasImport,
166
+ detail: hasImport
167
+ ? "astro.config includes astro-consent import"
168
+ : "astro.config is missing the astro-consent import"
169
+ });
170
+ if (!hasImport) {
171
+ findings.push({
172
+ issue: "astro.config.* does not import astro-consent",
173
+ fix: "Run `npx astro-consent init` or add `import astroConsent from \"astro-consent\";` manually."
174
+ });
175
+ }
176
+ const hasIntegration = source.includes("astroConsent(");
177
+ checks.push({
178
+ label: "Integration",
179
+ ok: hasIntegration,
180
+ detail: hasIntegration
181
+ ? "astroConsent(...) is present in integrations"
182
+ : "astroConsent(...) is missing from integrations"
183
+ });
184
+ if (!hasIntegration) {
185
+ findings.push({
186
+ issue: "astro.config.* does not include astroConsent(...) in integrations",
187
+ fix: "Add `astroConsent(...)` inside the `integrations` array."
188
+ });
189
+ }
190
+ const hasCss = fs.existsSync(cssFile);
191
+ checks.push({
192
+ label: "Stylesheet",
193
+ ok: hasCss,
194
+ detail: hasCss
195
+ ? "src/cookiebanner/styles.css exists"
196
+ : "src/cookiebanner/styles.css is missing"
197
+ });
198
+ if (!hasCss) {
199
+ findings.push({
200
+ issue: "src/cookiebanner/styles.css is missing",
201
+ fix: "Run `npx astro-consent init` to recreate the stylesheet."
202
+ });
203
+ }
204
+ let hasLayoutImport = false;
205
+ if (fs.existsSync(layoutPath)) {
206
+ const layoutSource = fs.readFileSync(layoutPath, "utf8");
207
+ hasLayoutImport = layoutSource.includes(`../cookiebanner/styles.css`);
208
+ checks.push({
209
+ label: "Layout import",
210
+ ok: hasLayoutImport,
211
+ detail: hasLayoutImport
212
+ ? "BaseLayout imports ../cookiebanner/styles.css"
213
+ : "BaseLayout is missing the stylesheet import"
214
+ });
215
+ if (!hasLayoutImport) {
216
+ findings.push({
217
+ issue: "BaseLayout.astro does not import ../cookiebanner/styles.css",
218
+ fix: "Add `import \"../cookiebanner/styles.css\";` near the top of `src/layouts/BaseLayout.astro`."
219
+ });
220
+ }
221
+ }
222
+ else {
223
+ checks.push({
224
+ label: "Layout import",
225
+ ok: false,
226
+ detail: "src/layouts/BaseLayout.astro is missing"
227
+ });
228
+ findings.push({
229
+ issue: "src/layouts/BaseLayout.astro is missing",
230
+ fix: "Create or update a shared layout to import `../cookiebanner/styles.css`."
231
+ });
232
+ }
233
+ const summary = {
234
+ ok: findings.length === 0,
235
+ checks,
236
+ findings
237
+ };
238
+ if (isJson) {
239
+ console.log(JSON.stringify(summary, null, 2));
240
+ process.exit(summary.ok ? 0 : 1);
241
+ }
242
+ console.log("\nastro-consent doctor");
243
+ for (const check of checks) {
244
+ console.log(`${check.ok ? "โœ…" : "โŒ"} ${check.label}: ${check.detail}`);
245
+ }
246
+ if (findings.length === 0) {
247
+ console.log("\nAll required wiring is present.\n");
248
+ process.exit(0);
249
+ }
250
+ console.log("\nNext fixes:");
251
+ for (const finding of findings) {
252
+ console.log(`- ${finding.issue}`);
253
+ console.log(` Fix: ${finding.fix}`);
254
+ }
255
+ console.log("");
256
+ process.exit(1);
303
257
  }
304
-
305
- /* ===============================
306
- Mobile
307
- =============================== */
308
-
309
- @media (max-width: 640px) {
310
- .cb-container {
311
- flex-direction: column;
312
- align-items: stretch;
313
- gap: 16px;
314
- }
315
-
316
- .cb-actions {
317
- justify-content: flex-end;
318
- flex-wrap: wrap;
319
- }
258
+ if (command === "status") {
259
+ const { source } = readConfig();
260
+ const cssFile = path.join(CWD, "src", "cookiebanner", "styles.css");
261
+ const layoutPath = path.join(CWD, "src", "layouts", "BaseLayout.astro");
262
+ const linked = fs.existsSync(path.join(CWD, "node_modules", "astro-consent"));
263
+ const consentKeyMatch = source.match(/storageKey:\s*["']([^"']+)["']/);
264
+ const consentKey = consentKeyMatch?.[1] ?? "astro-consent";
265
+ const report = {
266
+ configWired: source.includes(`from "astro-consent"`) &&
267
+ source.includes("astroConsent("),
268
+ cssExists: fs.existsSync(cssFile),
269
+ linkedInstall: linked,
270
+ consentStorageKey: consentKey,
271
+ consentStored: "browser-only; inspect via window.astroConsent.get()"
272
+ };
273
+ if (isJson) {
274
+ console.log(JSON.stringify(report, null, 2));
275
+ process.exit(0);
276
+ }
277
+ console.log("\nastro-consent status");
278
+ console.log(`${report.configWired ? "โœ…" : "โŒ"} Config wiring`);
279
+ console.log(`${report.cssExists ? "โœ…" : "โŒ"} CSS file exists`);
280
+ console.log(`${report.linkedInstall ? "โœ…" : "โŒ"} Linked install present`);
281
+ console.log(`โ„น๏ธ Consent storage key: ${report.consentStorageKey}`);
282
+ console.log(`โ„น๏ธ Consent state: ${report.consentStored}`);
283
+ console.log(`โ„น๏ธ Check in browser: window.astroConsent.get()`);
284
+ console.log("");
285
+ process.exit(0);
320
286
  }
321
- `, "utf8");
322
- console.log("๐ŸŽจ Created src/cookiebanner.css");
287
+ if (command !== "init" && command !== "install") {
288
+ printHelp();
323
289
  }
324
290
  /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
325
291
  Inject Astro integration
326
292
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
293
+ const { configPath, source: original } = readConfig();
294
+ let source = original;
295
+ ensureCssFile(isDryRun);
327
296
  if (!source.includes(`from "astro-consent"`)) {
328
297
  source = `import astroConsent from "astro-consent";\n${source}`;
329
298
  }
330
- if (!source.includes("astroConsent(")) {
331
- source = source.replace(/integrations\s*:\s*\[/, match => `${match}
332
- astroConsent({
333
- siteName: "My Website",
334
- policyUrl: "/privacy",
335
- consent: {
336
- days: 30,
337
- storageKey: "astro-consent"
338
- }
339
- }),
340
- `);
299
+ if (!source.includes("astro-consent:start")) {
300
+ source = source.replace(/integrations\s*:\s*\[/, match => `${match}\n${buildIntegrationBlock()}`);
301
+ }
302
+ if (!isDryRun) {
303
+ fs.writeFileSync(configPath, source, "utf8");
341
304
  }
342
- fs.writeFileSync(configPath, source, "utf8");
343
- console.log("\n๐ŸŽ‰ astro-consent installed successfully");
344
- console.log("๐Ÿ‘‰ Style everything via src/cookiebanner.css");
305
+ console.log(isDryRun
306
+ ? "\n๐Ÿงช Dry run: astro-consent would be installed successfully"
307
+ : "\n๐ŸŽ‰ astro-consent installed successfully");
308
+ console.log("๐Ÿ‘‰ Edit src/cookiebanner/styles.css to theme the banner and modal.");
345
309
  console.log("๐Ÿ‘‰ Run `astro-consent remove` to uninstall\n");
package/dist/index.d.ts CHANGED
@@ -1,7 +1,17 @@
1
1
  import type { AstroIntegration } from "astro";
2
2
  export interface AstroConsentOptions {
3
3
  siteName?: string;
4
+ headline?: string;
5
+ description?: string;
6
+ acceptLabel?: string;
7
+ rejectLabel?: string;
8
+ manageLabel?: string;
9
+ cookiePolicyUrl?: string;
10
+ privacyPolicyUrl?: string;
4
11
  policyUrl?: string;
12
+ presentation?: "banner" | "overlay";
13
+ displayUntilIdle?: boolean;
14
+ displayIdleDelayMs?: number;
5
15
  consent?: {
6
16
  days?: number;
7
17
  storageKey?: string;