cookiecrumbs 0.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/LICENSE +134 -0
- package/README.md +190 -0
- package/dist/cli/src/api.js +192 -0
- package/dist/cli/src/auth.js +106 -0
- package/dist/cli/src/commands/_shared.js +78 -0
- package/dist/cli/src/commands/alerts.js +85 -0
- package/dist/cli/src/commands/auth.js +92 -0
- package/dist/cli/src/commands/declaration.js +45 -0
- package/dist/cli/src/commands/diff.js +26 -0
- package/dist/cli/src/commands/domains.js +44 -0
- package/dist/cli/src/commands/export.js +136 -0
- package/dist/cli/src/commands/init.js +134 -0
- package/dist/cli/src/commands/install.js +77 -0
- package/dist/cli/src/commands/issues.js +61 -0
- package/dist/cli/src/commands/link.js +41 -0
- package/dist/cli/src/commands/logs.js +98 -0
- package/dist/cli/src/commands/open.js +45 -0
- package/dist/cli/src/commands/pull.js +89 -0
- package/dist/cli/src/commands/push.js +110 -0
- package/dist/cli/src/commands/scan.js +94 -0
- package/dist/cli/src/commands/schedule.js +97 -0
- package/dist/cli/src/commands/services.js +143 -0
- package/dist/cli/src/commands/sites.js +111 -0
- package/dist/cli/src/commands/status.js +90 -0
- package/dist/cli/src/commands/templates.js +133 -0
- package/dist/cli/src/commands/tokens.js +50 -0
- package/dist/cli/src/commands/usage.js +41 -0
- package/dist/cli/src/commands/versions.js +95 -0
- package/dist/cli/src/commands/webhooks.js +164 -0
- package/dist/cli/src/configpkg.js +10 -0
- package/dist/cli/src/diff.js +63 -0
- package/dist/cli/src/errors.js +20 -0
- package/dist/cli/src/frameworks.js +141 -0
- package/dist/cli/src/index.js +100 -0
- package/dist/cli/src/jobs.js +59 -0
- package/dist/cli/src/merge.js +38 -0
- package/dist/cli/src/output.js +112 -0
- package/dist/cli/src/project.js +269 -0
- package/dist/cli/src/util.js +122 -0
- package/dist/config/rules_reference.json +569 -0
- package/dist/config/src/canon.js +36 -0
- package/dist/config/src/declaration.js +38 -0
- package/dist/config/src/defaults.js +804 -0
- package/dist/config/src/export.js +130 -0
- package/dist/config/src/index.js +16 -0
- package/dist/config/src/lint.js +139 -0
- package/dist/config/src/regimes.js +62 -0
- package/dist/config/src/rules.js +90 -0
- package/dist/config/src/schema.js +323 -0
- package/dist/config/src/theme.js +147 -0
- package/dist/config/src/verify.js +51 -0
- package/dist/config/src/webhooks.js +309 -0
- package/package.json +62 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// Standard Webhooks (https://www.standardwebhooks.com) — the one implementation shared by the
|
|
2
|
+
// cc-worker dispatcher, the CLI (`cookiecrumbs verify-webhook`), the sample receiver in
|
|
3
|
+
// docs/platform/samples/webhook-receiver.mjs and the tests.
|
|
4
|
+
//
|
|
5
|
+
// webhook-id: the delivery id (public.webhook_deliveries.id)
|
|
6
|
+
// webhook-timestamp: unix seconds
|
|
7
|
+
// webhook-signature: `v1,<base64>` (space separated when several keys are valid)
|
|
8
|
+
//
|
|
9
|
+
// The signature is HMAC-SHA256 over `${id}.${timestamp}.${body}`, keyed with the *raw bytes* of the
|
|
10
|
+
// secret: a `whsec_`-prefixed secret is the base64 of those bytes, anything else is used as UTF-8.
|
|
11
|
+
//
|
|
12
|
+
// No imports: SHA-256, HMAC and base64 are implemented here so the module runs unchanged in Node,
|
|
13
|
+
// Deno, the edge runtime and the browser, and so a customer can copy the verifier into any project.
|
|
14
|
+
export const WEBHOOK_SIGNATURE_VERSION = "v1";
|
|
15
|
+
/** Standard Webhooks recommends five minutes of clock tolerance. */
|
|
16
|
+
export const WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
17
|
+
export const WEBHOOK_SECRET_PREFIX = "whsec_";
|
|
18
|
+
export const WEBHOOK_ID_HEADER = "webhook-id";
|
|
19
|
+
export const WEBHOOK_TIMESTAMP_HEADER = "webhook-timestamp";
|
|
20
|
+
export const WEBHOOK_SIGNATURE_HEADER = "webhook-signature";
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// SHA-256
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const K = new Uint32Array([
|
|
25
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
26
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
27
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
28
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
29
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
30
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
31
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
32
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
33
|
+
]);
|
|
34
|
+
const rotr = (x, n) => ((x >>> n) | (x << (32 - n))) >>> 0;
|
|
35
|
+
/** SHA-256 of `data`, as 32 bytes. */
|
|
36
|
+
export function sha256(data) {
|
|
37
|
+
const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
38
|
+
const len = data.length;
|
|
39
|
+
const padded = new Uint8Array((((len + 8) >> 6) + 1) * 64);
|
|
40
|
+
padded.set(data);
|
|
41
|
+
padded[len] = 0x80;
|
|
42
|
+
const view = new DataView(padded.buffer);
|
|
43
|
+
// length in bits, as a 64-bit big-endian number
|
|
44
|
+
view.setUint32(padded.length - 8, Math.floor(len / 0x20000000) >>> 0);
|
|
45
|
+
view.setUint32(padded.length - 4, ((len % 0x20000000) * 8) >>> 0);
|
|
46
|
+
const w = new Uint32Array(64);
|
|
47
|
+
for (let off = 0; off < padded.length; off += 64) {
|
|
48
|
+
for (let i = 0; i < 16; i++)
|
|
49
|
+
w[i] = view.getUint32(off + i * 4);
|
|
50
|
+
for (let i = 16; i < 64; i++) {
|
|
51
|
+
const x = w[i - 15];
|
|
52
|
+
const y = w[i - 2];
|
|
53
|
+
const s0 = (rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3)) >>> 0;
|
|
54
|
+
const s1 = (rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10)) >>> 0;
|
|
55
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
56
|
+
}
|
|
57
|
+
let a = h[0];
|
|
58
|
+
let b = h[1];
|
|
59
|
+
let c = h[2];
|
|
60
|
+
let d = h[3];
|
|
61
|
+
let e = h[4];
|
|
62
|
+
let f = h[5];
|
|
63
|
+
let g = h[6];
|
|
64
|
+
let hh = h[7];
|
|
65
|
+
for (let i = 0; i < 64; i++) {
|
|
66
|
+
const s1 = (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) >>> 0;
|
|
67
|
+
const ch = ((e & f) ^ (~e & g)) >>> 0;
|
|
68
|
+
const t1 = (hh + s1 + ch + K[i] + w[i]) >>> 0;
|
|
69
|
+
const s0 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) >>> 0;
|
|
70
|
+
const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0;
|
|
71
|
+
const t2 = (s0 + maj) >>> 0;
|
|
72
|
+
hh = g;
|
|
73
|
+
g = f;
|
|
74
|
+
f = e;
|
|
75
|
+
e = (d + t1) >>> 0;
|
|
76
|
+
d = c;
|
|
77
|
+
c = b;
|
|
78
|
+
b = a;
|
|
79
|
+
a = (t1 + t2) >>> 0;
|
|
80
|
+
}
|
|
81
|
+
h[0] = (h[0] + a) >>> 0;
|
|
82
|
+
h[1] = (h[1] + b) >>> 0;
|
|
83
|
+
h[2] = (h[2] + c) >>> 0;
|
|
84
|
+
h[3] = (h[3] + d) >>> 0;
|
|
85
|
+
h[4] = (h[4] + e) >>> 0;
|
|
86
|
+
h[5] = (h[5] + f) >>> 0;
|
|
87
|
+
h[6] = (h[6] + g) >>> 0;
|
|
88
|
+
h[7] = (h[7] + hh) >>> 0;
|
|
89
|
+
}
|
|
90
|
+
const out = new Uint8Array(32);
|
|
91
|
+
const ov = new DataView(out.buffer);
|
|
92
|
+
for (let i = 0; i < 8; i++)
|
|
93
|
+
ov.setUint32(i * 4, h[i]);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/** HMAC-SHA256 (RFC 2104), as 32 bytes. */
|
|
97
|
+
export function hmacSha256(key, message) {
|
|
98
|
+
const block = new Uint8Array(64);
|
|
99
|
+
if (key.length > 64)
|
|
100
|
+
block.set(sha256(key));
|
|
101
|
+
else
|
|
102
|
+
block.set(key);
|
|
103
|
+
const inner = new Uint8Array(64 + message.length);
|
|
104
|
+
const outer = new Uint8Array(64 + 32);
|
|
105
|
+
for (let i = 0; i < 64; i++) {
|
|
106
|
+
inner[i] = block[i] ^ 0x36;
|
|
107
|
+
outer[i] = block[i] ^ 0x5c;
|
|
108
|
+
}
|
|
109
|
+
inner.set(message, 64);
|
|
110
|
+
outer.set(sha256(inner), 64);
|
|
111
|
+
return sha256(outer);
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// base64 / utf-8 / hex helpers (no Buffer, no atob)
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
117
|
+
export function toBase64(bytes) {
|
|
118
|
+
let out = "";
|
|
119
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
120
|
+
const b0 = bytes[i];
|
|
121
|
+
const b1 = bytes[i + 1];
|
|
122
|
+
const b2 = bytes[i + 2];
|
|
123
|
+
out += B64[b0 >> 2];
|
|
124
|
+
out += B64[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)];
|
|
125
|
+
out += b1 === undefined ? "=" : B64[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)];
|
|
126
|
+
out += b2 === undefined ? "=" : B64[b2 & 63];
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
export function fromBase64(text) {
|
|
131
|
+
const clean = text.replace(/[\s=]/g, "").replace(/-/g, "+").replace(/_/g, "/");
|
|
132
|
+
const out = new Uint8Array(Math.floor((clean.length * 6) / 8));
|
|
133
|
+
let acc = 0;
|
|
134
|
+
let bits = 0;
|
|
135
|
+
let n = 0;
|
|
136
|
+
for (const ch of clean) {
|
|
137
|
+
const v = B64.indexOf(ch);
|
|
138
|
+
if (v < 0)
|
|
139
|
+
throw new Error("invalid base64");
|
|
140
|
+
acc = (acc << 6) | v;
|
|
141
|
+
bits += 6;
|
|
142
|
+
if (bits >= 8) {
|
|
143
|
+
bits -= 8;
|
|
144
|
+
out[n++] = (acc >> bits) & 0xff;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out.subarray(0, n);
|
|
148
|
+
}
|
|
149
|
+
export const toHex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
150
|
+
const utf8 = (s) => new TextEncoder().encode(s);
|
|
151
|
+
/** Length-independent, value-constant-time comparison of two ASCII strings. */
|
|
152
|
+
export function timingSafeEqual(a, b) {
|
|
153
|
+
const x = utf8(a);
|
|
154
|
+
const y = utf8(b);
|
|
155
|
+
let diff = x.length ^ y.length;
|
|
156
|
+
const n = Math.max(x.length, y.length);
|
|
157
|
+
for (let i = 0; i < n; i++)
|
|
158
|
+
diff |= (x[i] ?? 0) ^ (y[i] ?? 0);
|
|
159
|
+
return diff === 0;
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Secrets
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/** The HMAC key of a secret: base64 body for `whsec_…`, raw UTF-8 otherwise. */
|
|
165
|
+
export function webhookSecretKey(secret) {
|
|
166
|
+
const s = (secret ?? "").trim();
|
|
167
|
+
if (!s)
|
|
168
|
+
throw new Error("webhook secret is empty");
|
|
169
|
+
return s.startsWith(WEBHOOK_SECRET_PREFIX) ? fromBase64(s.slice(WEBHOOK_SECRET_PREFIX.length)) : utf8(s);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A new `whsec_` + base64(32 random bytes) secret. Uses the platform CSPRNG; throws when the runtime
|
|
173
|
+
* has none (the database generates the real secrets — this exists for tests and local receivers).
|
|
174
|
+
*/
|
|
175
|
+
export function generateWebhookSecret(bytes = 32) {
|
|
176
|
+
const c = globalThis.crypto;
|
|
177
|
+
if (!c?.getRandomValues)
|
|
178
|
+
throw new Error("no CSPRNG available in this runtime");
|
|
179
|
+
return WEBHOOK_SECRET_PREFIX + toBase64(c.getRandomValues(new Uint8Array(bytes)));
|
|
180
|
+
}
|
|
181
|
+
function unixSeconds(t) {
|
|
182
|
+
if (t === undefined || t === null || t === "")
|
|
183
|
+
return String(Math.floor(Date.now() / 1000));
|
|
184
|
+
if (t instanceof Date)
|
|
185
|
+
return String(Math.floor(t.getTime() / 1000));
|
|
186
|
+
const n = typeof t === "number" ? t : Number(t);
|
|
187
|
+
if (!Number.isFinite(n))
|
|
188
|
+
throw new Error(`invalid timestamp: ${String(t)}`);
|
|
189
|
+
// Anything past the year 33658 in seconds is really milliseconds.
|
|
190
|
+
return String(Math.floor(n > 1e12 ? n / 1000 : n));
|
|
191
|
+
}
|
|
192
|
+
export function signWebhook(input) {
|
|
193
|
+
const id = String(input.id ?? "");
|
|
194
|
+
if (!id)
|
|
195
|
+
throw new Error("webhook id is required");
|
|
196
|
+
const timestamp = unixSeconds(input.timestamp);
|
|
197
|
+
const body = input.body ?? "";
|
|
198
|
+
const signedContent = `${id}.${timestamp}.${body}`;
|
|
199
|
+
const mac = hmacSha256(webhookSecretKey(input.secret), utf8(signedContent));
|
|
200
|
+
const signature = `${WEBHOOK_SIGNATURE_VERSION},${toBase64(mac)}`;
|
|
201
|
+
return {
|
|
202
|
+
id,
|
|
203
|
+
timestamp,
|
|
204
|
+
signature,
|
|
205
|
+
signedContent,
|
|
206
|
+
headers: {
|
|
207
|
+
"content-type": "application/json; charset=utf-8",
|
|
208
|
+
[WEBHOOK_ID_HEADER]: id,
|
|
209
|
+
[WEBHOOK_TIMESTAMP_HEADER]: timestamp,
|
|
210
|
+
[WEBHOOK_SIGNATURE_HEADER]: signature,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function headerValue(headers, name) {
|
|
215
|
+
if (!headers)
|
|
216
|
+
return null;
|
|
217
|
+
const anyH = headers;
|
|
218
|
+
if (typeof anyH.get === "function") {
|
|
219
|
+
const v = anyH.get(name);
|
|
220
|
+
return v == null ? null : String(v);
|
|
221
|
+
}
|
|
222
|
+
const rec = headers;
|
|
223
|
+
for (const key of Object.keys(rec)) {
|
|
224
|
+
if (key.toLowerCase() !== name)
|
|
225
|
+
continue;
|
|
226
|
+
const v = rec[key];
|
|
227
|
+
if (v == null)
|
|
228
|
+
return null;
|
|
229
|
+
return Array.isArray(v) ? (v.length ? String(v[0]) : null) : String(v);
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Verifies a Standard Webhooks request. Returns a result object rather than throwing so a receiver
|
|
235
|
+
* can log the reason and answer 400 without a try/catch.
|
|
236
|
+
*/
|
|
237
|
+
export function verifyWebhook(input) {
|
|
238
|
+
const id = headerValue(input.headers, WEBHOOK_ID_HEADER);
|
|
239
|
+
const ts = headerValue(input.headers, WEBHOOK_TIMESTAMP_HEADER);
|
|
240
|
+
const sigHeader = headerValue(input.headers, WEBHOOK_SIGNATURE_HEADER);
|
|
241
|
+
if (!id)
|
|
242
|
+
return { ok: false, reason: "missing_id", message: `No ${WEBHOOK_ID_HEADER} header.` };
|
|
243
|
+
if (!ts)
|
|
244
|
+
return { ok: false, reason: "missing_timestamp", message: `No ${WEBHOOK_TIMESTAMP_HEADER} header.` };
|
|
245
|
+
if (!sigHeader)
|
|
246
|
+
return { ok: false, reason: "missing_signature", message: `No ${WEBHOOK_SIGNATURE_HEADER} header.` };
|
|
247
|
+
const seconds = Number(ts);
|
|
248
|
+
if (!Number.isFinite(seconds) || !/^\d{1,15}$/.test(ts.trim())) {
|
|
249
|
+
return { ok: false, reason: "invalid_timestamp", message: `${WEBHOOK_TIMESTAMP_HEADER} must be unix seconds.` };
|
|
250
|
+
}
|
|
251
|
+
const tolerance = input.toleranceSeconds ?? WEBHOOK_TOLERANCE_SECONDS;
|
|
252
|
+
if (tolerance > 0) {
|
|
253
|
+
const nowSeconds = Math.floor((input.now ?? Date.now()) / 1000);
|
|
254
|
+
if (seconds < nowSeconds - tolerance) {
|
|
255
|
+
return { ok: false, reason: "timestamp_too_old", message: `Timestamp is ${nowSeconds - seconds} s old (tolerance ${tolerance} s).` };
|
|
256
|
+
}
|
|
257
|
+
if (seconds > nowSeconds + tolerance) {
|
|
258
|
+
return { ok: false, reason: "timestamp_too_new", message: `Timestamp is ${seconds - nowSeconds} s in the future (tolerance ${tolerance} s).` };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let expected;
|
|
262
|
+
try {
|
|
263
|
+
const mac = hmacSha256(webhookSecretKey(input.secret), utf8(`${id}.${ts}.${input.body ?? ""}`));
|
|
264
|
+
expected = toBase64(mac);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
return { ok: false, reason: "invalid_secret", message: e.message };
|
|
268
|
+
}
|
|
269
|
+
const parts = sigHeader.split(/\s+/).filter(Boolean);
|
|
270
|
+
let sawV1 = false;
|
|
271
|
+
let matched = false;
|
|
272
|
+
for (const part of parts) {
|
|
273
|
+
const comma = part.indexOf(",");
|
|
274
|
+
if (comma < 0)
|
|
275
|
+
continue;
|
|
276
|
+
if (part.slice(0, comma) !== WEBHOOK_SIGNATURE_VERSION)
|
|
277
|
+
continue;
|
|
278
|
+
sawV1 = true;
|
|
279
|
+
// No early exit: every v1 candidate is compared so the timing does not leak which one matched.
|
|
280
|
+
if (timingSafeEqual(part.slice(comma + 1), expected))
|
|
281
|
+
matched = true;
|
|
282
|
+
}
|
|
283
|
+
if (!sawV1)
|
|
284
|
+
return { ok: false, reason: "no_v1_signature", message: `${WEBHOOK_SIGNATURE_HEADER} carries no v1 signature.` };
|
|
285
|
+
if (!matched)
|
|
286
|
+
return { ok: false, reason: "signature_mismatch", message: "The v1 signature does not match the body." };
|
|
287
|
+
return { ok: true, id, timestamp: seconds, signature: `${WEBHOOK_SIGNATURE_VERSION},${expected}` };
|
|
288
|
+
}
|
|
289
|
+
/** The 12 CookieCrumbs event types (mirrors private.webhook_event_types() in migration 0036). */
|
|
290
|
+
export const WEBHOOK_EVENT_TYPES = [
|
|
291
|
+
"scan.completed",
|
|
292
|
+
"scan.failed",
|
|
293
|
+
"tracker.new",
|
|
294
|
+
"issue.opened",
|
|
295
|
+
"issue.resolved",
|
|
296
|
+
"alert.raised",
|
|
297
|
+
"config.published",
|
|
298
|
+
"version.promoted",
|
|
299
|
+
"export.completed",
|
|
300
|
+
"declaration.updated",
|
|
301
|
+
"install.broken",
|
|
302
|
+
"plan.changed",
|
|
303
|
+
];
|
|
304
|
+
export function isWebhookEnvelope(value) {
|
|
305
|
+
if (!value || typeof value !== "object")
|
|
306
|
+
return false;
|
|
307
|
+
const v = value;
|
|
308
|
+
return typeof v.id === "string" && typeof v.type === "string" && typeof v.created_at === "string" && typeof v.org_id === "string" && "data" in v;
|
|
309
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cookiecrumbs",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CookieCrumbs command line: push and pull banner configuration as code, publish versions, run hosted scans in CI, manage sites, services, schedules, issues, domains and templates, export consent logs and declarations",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "CookieCrumbs <hello@cookiecrumbs.eu> (https://cookiecrumbs.eu)",
|
|
7
|
+
"homepage": "https://cookiecrumbs.eu/docs/cli.html",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/SimonCmd/CookieCrumbs.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SimonCmd/CookieCrumbs/issues",
|
|
15
|
+
"email": "hello@cookiecrumbs.eu"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"cookiecrumbs",
|
|
19
|
+
"cookie-consent",
|
|
20
|
+
"consent-management",
|
|
21
|
+
"cookie-banner",
|
|
22
|
+
"gdpr",
|
|
23
|
+
"eprivacy",
|
|
24
|
+
"consent-mode",
|
|
25
|
+
"tracker-scanner",
|
|
26
|
+
"cli",
|
|
27
|
+
"ci"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"cookiecrumbs": "./dist/cli/src/index.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"pretest": "npm run build",
|
|
44
|
+
"test": "tsx --test test/*.test.ts"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@clack/prompts": "^0.11.0",
|
|
48
|
+
"commander": "^13.1.0",
|
|
49
|
+
"jiti": "^2.4.0",
|
|
50
|
+
"picocolors": "^1.1.1",
|
|
51
|
+
"zod": "^3.23.8"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@cookiecrumbs/config": "0.2.0",
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"tsx": "^4.19.0",
|
|
57
|
+
"typescript": "^5.7.0"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|