@freshjuice/zest 1.0.0 → 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/README.md +178 -78
- package/dist/zest.de.js +692 -305
- package/dist/zest.de.js.map +1 -1
- package/dist/zest.de.min.js +1 -1
- package/dist/zest.en.js +692 -305
- package/dist/zest.en.js.map +1 -1
- package/dist/zest.en.min.js +1 -1
- package/dist/zest.es.js +692 -305
- package/dist/zest.es.js.map +1 -1
- package/dist/zest.es.min.js +1 -1
- package/dist/zest.esm.js +692 -305
- package/dist/zest.esm.js.map +1 -1
- package/dist/zest.esm.min.js +1 -1
- package/dist/zest.fr.js +692 -305
- package/dist/zest.fr.js.map +1 -1
- package/dist/zest.fr.min.js +1 -1
- package/dist/zest.headless.esm.js +2299 -0
- package/dist/zest.headless.esm.js.map +1 -0
- package/dist/zest.headless.esm.min.js +1 -0
- package/dist/zest.it.js +692 -305
- package/dist/zest.it.js.map +1 -1
- package/dist/zest.it.min.js +1 -1
- package/dist/zest.ja.js +692 -305
- package/dist/zest.ja.js.map +1 -1
- package/dist/zest.ja.min.js +1 -1
- package/dist/zest.js +692 -305
- package/dist/zest.js.map +1 -1
- package/dist/zest.min.js +1 -1
- package/dist/zest.nl.js +692 -305
- package/dist/zest.nl.js.map +1 -1
- package/dist/zest.nl.min.js +1 -1
- package/dist/zest.pl.js +692 -305
- package/dist/zest.pl.js.map +1 -1
- package/dist/zest.pl.min.js +1 -1
- package/dist/zest.pt.js +692 -305
- package/dist/zest.pt.js.map +1 -1
- package/dist/zest.pt.min.js +1 -1
- package/dist/zest.ru.js +692 -305
- package/dist/zest.ru.js.map +1 -1
- package/dist/zest.ru.min.js +1 -1
- package/dist/zest.uk.js +692 -305
- package/dist/zest.uk.js.map +1 -1
- package/dist/zest.uk.min.js +1 -1
- package/dist/zest.zh.js +692 -305
- package/dist/zest.zh.js.map +1 -1
- package/dist/zest.zh.min.js +1 -1
- package/package.json +16 -4
- package/src/core/cookie-interceptor.js +20 -5
- package/src/core/known-trackers.js +41 -14
- package/src/core/pattern-matcher.js +20 -5
- package/src/core/script-blocker.js +85 -79
- package/src/core/security.js +204 -0
- package/src/core/storage-interceptor.js +5 -1
- package/src/core-lifecycle.js +192 -0
- package/src/headless.js +133 -0
- package/src/index.js +73 -184
- package/src/storage/consent-store.js +32 -8
- package/src/ui/banner.js +11 -7
- package/src/ui/modal.js +16 -12
- package/src/ui/styles.js +25 -4
- package/src/ui/widget.js +3 -1
package/README.md
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
A lightweight cookie consent toolkit for GDPR/CCPA compliance.
|
|
9
9
|
|
|
10
|
-
- **Lightweight**
|
|
11
|
-
- **Zero dependencies**
|
|
12
|
-
- **Shadow DOM**
|
|
13
|
-
- **
|
|
14
|
-
- **Privacy-first**
|
|
10
|
+
- **Lightweight** — ~9KB gzipped (single language) / ~16KB (all 12 languages) / ~11KB (headless)
|
|
11
|
+
- **Zero dependencies** — Vanilla JavaScript
|
|
12
|
+
- **Shadow DOM** — Styles isolated from your site
|
|
13
|
+
- **Headless mode** — Bring your own UI & CSS, use only the consent engine
|
|
14
|
+
- **Privacy-first** — Respects Do Not Track / Global Privacy Control
|
|
15
|
+
- **Security-hardened** — XSS-safe templating, URL/color/regex validation, locked interceptors
|
|
15
16
|
|
|
16
17
|
## Quick Start
|
|
17
18
|
|
|
@@ -23,7 +24,7 @@ A lightweight cookie consent toolkit for GDPR/CCPA compliance.
|
|
|
23
24
|
<script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
With configuration:
|
|
27
28
|
|
|
28
29
|
```html
|
|
29
30
|
<script>
|
|
@@ -37,6 +38,23 @@ Or with configuration:
|
|
|
37
38
|
<script src="https://unpkg.com/@freshjuice/zest"></script>
|
|
38
39
|
```
|
|
39
40
|
|
|
41
|
+
As an npm dependency:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import Zest from '@freshjuice/zest';
|
|
45
|
+
|
|
46
|
+
Zest.init({ mode: 'safe', policyUrl: '/privacy' });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Two build flavors
|
|
50
|
+
|
|
51
|
+
| Entry | What you get | Min / Gzip |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `@freshjuice/zest` | Consent engine **+ Shadow DOM UI** (banner, modal, widget) | ~50 KB / **~16 KB** |
|
|
54
|
+
| `@freshjuice/zest/headless` | Consent engine only, **no UI / no CSS** — you build the UI | ~31 KB / **~11 KB** |
|
|
55
|
+
|
|
56
|
+
Use **headless** when you want full control over markup and styling.
|
|
57
|
+
|
|
40
58
|
## Configuration
|
|
41
59
|
|
|
42
60
|
### Via `window.ZestConfig`
|
|
@@ -49,10 +67,10 @@ window.ZestConfig = {
|
|
|
49
67
|
// Theme: 'light' | 'dark' | 'auto' (default: 'auto' follows system)
|
|
50
68
|
theme: 'auto',
|
|
51
69
|
|
|
52
|
-
// Accent color
|
|
70
|
+
// Accent color — must be a valid CSS color (hex, named, rgb/rgba, hsl/hsla)
|
|
53
71
|
accentColor: '#0071e3',
|
|
54
72
|
|
|
55
|
-
// Link to privacy policy
|
|
73
|
+
// Link to privacy policy — only http:/https:/mailto:/tel:/relative allowed
|
|
56
74
|
policyUrl: '/privacy',
|
|
57
75
|
|
|
58
76
|
// Show floating widget after consent
|
|
@@ -61,7 +79,7 @@ window.ZestConfig = {
|
|
|
61
79
|
// Consent expiration in days
|
|
62
80
|
expiration: 365,
|
|
63
81
|
|
|
64
|
-
// Callbacks
|
|
82
|
+
// Callbacks — wrapped in try/catch internally, safe to throw
|
|
65
83
|
callbacks: {
|
|
66
84
|
onAccept: (consent) => {},
|
|
67
85
|
onReject: () => {},
|
|
@@ -86,26 +104,85 @@ window.ZestConfig = {
|
|
|
86
104
|
## API
|
|
87
105
|
|
|
88
106
|
```javascript
|
|
89
|
-
// Show/hide UI
|
|
90
|
-
Zest.show()
|
|
91
|
-
Zest.hide()
|
|
92
|
-
Zest.showSettings()
|
|
93
|
-
Zest.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Zest.
|
|
98
|
-
Zest.
|
|
99
|
-
Zest.
|
|
107
|
+
// Show/hide UI (full build only)
|
|
108
|
+
Zest.show() // Show banner
|
|
109
|
+
Zest.hide() // Hide banner
|
|
110
|
+
Zest.showSettings() // Show settings modal
|
|
111
|
+
Zest.hideSettings() // Close settings modal
|
|
112
|
+
Zest.reset() // Clear consent + reshow banner
|
|
113
|
+
|
|
114
|
+
// Consent state
|
|
115
|
+
Zest.getConsent() // { essential, functional, analytics, marketing }
|
|
116
|
+
Zest.hasConsent('analytics') // boolean
|
|
117
|
+
Zest.hasConsentDecision() // boolean — has the user made a choice yet?
|
|
118
|
+
Zest.getConsentProof() // full consent cookie payload (compliance audit)
|
|
119
|
+
|
|
120
|
+
// Programmatic actions
|
|
121
|
+
Zest.acceptAll()
|
|
122
|
+
Zest.rejectAll()
|
|
123
|
+
Zest.updateConsent({ analytics: true, marketing: false }) // headless only
|
|
124
|
+
|
|
125
|
+
// DNT / GPC
|
|
126
|
+
Zest.isDoNotTrackEnabled()
|
|
127
|
+
Zest.getDNTDetails() // { enabled, source: 'dnt'|'gpc'|null }
|
|
128
|
+
|
|
129
|
+
// Events — subscribe helpers (also work with addEventListener)
|
|
130
|
+
Zest.on('zest:change', (e) => {})
|
|
131
|
+
Zest.once('zest:ready', (e) => {})
|
|
132
|
+
Zest.EVENTS // { READY, CONSENT, REJECT, CHANGE, SHOW, HIDE }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Headless mode — bring your own UI
|
|
136
|
+
|
|
137
|
+
Full control over markup and styling, no Shadow DOM, no inline CSS.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
import Zest from '@freshjuice/zest/headless';
|
|
141
|
+
|
|
142
|
+
Zest.init({
|
|
143
|
+
mode: 'safe',
|
|
144
|
+
respectDNT: true,
|
|
145
|
+
consentModeGoogle: true
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Decide when to show YOUR banner
|
|
149
|
+
if (!Zest.hasConsentDecision()) {
|
|
150
|
+
document.querySelector('#my-banner').classList.add('open');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Wire your buttons
|
|
154
|
+
document.querySelector('#accept').onclick = () => Zest.acceptAll();
|
|
155
|
+
document.querySelector('#reject').onclick = () => Zest.rejectAll();
|
|
156
|
+
|
|
157
|
+
document.querySelector('#save').onclick = () => {
|
|
158
|
+
Zest.updateConsent({
|
|
159
|
+
analytics: analyticsCheckbox.checked,
|
|
160
|
+
marketing: marketingCheckbox.checked,
|
|
161
|
+
functional: functionalCheckbox.checked
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Listen for changes
|
|
166
|
+
Zest.on(Zest.EVENTS.CHANGE, (e) => {
|
|
167
|
+
console.log('consent changed', e.detail.consent);
|
|
168
|
+
});
|
|
100
169
|
```
|
|
101
170
|
|
|
171
|
+
What headless gives you:
|
|
172
|
+
- All interceptors (cookies, storage, scripts) still work — just skip the built-in UI
|
|
173
|
+
- Same config surface (`mode`, `respectDNT`, `consentModeGoogle`, `blockedDomains`, `patterns`, etc.)
|
|
174
|
+
- **Does NOT auto-init** — you call `Zest.init()` when ready
|
|
175
|
+
- **Does NOT set `window.Zest`** — you import and use the module directly
|
|
176
|
+
|
|
177
|
+
See `examples/headless.html` for a complete working example.
|
|
178
|
+
|
|
102
179
|
## Do Not Track (DNT) / Global Privacy Control (GPC)
|
|
103
180
|
|
|
104
181
|
Zest respects browser privacy signals by default:
|
|
105
182
|
|
|
106
183
|
```javascript
|
|
107
184
|
window.ZestConfig = {
|
|
108
|
-
respectDNT: true,
|
|
185
|
+
respectDNT: true, // Respect DNT/GPC signals (default: true)
|
|
109
186
|
dntBehavior: 'reject' // What to do when DNT is enabled
|
|
110
187
|
};
|
|
111
188
|
```
|
|
@@ -116,10 +193,9 @@ window.ZestConfig = {
|
|
|
116
193
|
| `preselect` | Show banner with non-essential options unchecked |
|
|
117
194
|
| `ignore` | Ignore DNT/GPC signals completely |
|
|
118
195
|
|
|
119
|
-
**API methods:**
|
|
120
196
|
```javascript
|
|
121
|
-
Zest.isDoNotTrackEnabled() //
|
|
122
|
-
Zest.getDNTDetails() //
|
|
197
|
+
Zest.isDoNotTrackEnabled() // true if DNT or GPC is enabled
|
|
198
|
+
Zest.getDNTDetails() // { enabled: boolean, source: 'dnt' | 'gpc' | null }
|
|
123
199
|
```
|
|
124
200
|
|
|
125
201
|
## Blocking Modes
|
|
@@ -141,8 +217,6 @@ window.ZestConfig = {
|
|
|
141
217
|
|
|
142
218
|
### Custom Blocked Domains
|
|
143
219
|
|
|
144
|
-
Add your own domains to block:
|
|
145
|
-
|
|
146
220
|
```javascript
|
|
147
221
|
window.ZestConfig = {
|
|
148
222
|
mode: 'safe',
|
|
@@ -155,8 +229,6 @@ window.ZestConfig = {
|
|
|
155
229
|
|
|
156
230
|
### Manual Script Tagging
|
|
157
231
|
|
|
158
|
-
For any mode, you can explicitly tag scripts:
|
|
159
|
-
|
|
160
232
|
```html
|
|
161
233
|
<script data-consent-category="analytics" src="https://..."></script>
|
|
162
234
|
<script data-consent-category="marketing">
|
|
@@ -164,9 +236,11 @@ For any mode, you can explicitly tag scripts:
|
|
|
164
236
|
</script>
|
|
165
237
|
```
|
|
166
238
|
|
|
167
|
-
|
|
239
|
+
> **Note:** `data-consent-category="essential"` on third-party scripts is
|
|
240
|
+
> ignored — self-labeling as essential is a known bypass. Only
|
|
241
|
+
> `functional`, `analytics`, and `marketing` self-labels are honored.
|
|
168
242
|
|
|
169
|
-
|
|
243
|
+
### Allow Specific Scripts
|
|
170
244
|
|
|
171
245
|
```html
|
|
172
246
|
<script data-zest-allow src="https://cdn.example.com/library.js"></script>
|
|
@@ -190,18 +264,22 @@ document.addEventListener('zest:change', (e) => {
|
|
|
190
264
|
document.addEventListener('zest:ready', (e) => {
|
|
191
265
|
console.log('Zest initialized:', e.detail.consent);
|
|
192
266
|
});
|
|
267
|
+
|
|
268
|
+
// Or via the helpers
|
|
269
|
+
Zest.on(Zest.EVENTS.CHANGE, (e) => { /* ... */ });
|
|
270
|
+
Zest.once(Zest.EVENTS.READY, (e) => { /* ... */ });
|
|
193
271
|
```
|
|
194
272
|
|
|
195
273
|
## Google Consent Mode v2 / Microsoft UET Consent Mode
|
|
196
274
|
|
|
197
|
-
|
|
275
|
+
Optional — push consent state to Google and Microsoft advertising APIs.
|
|
198
276
|
|
|
199
277
|
### Enable via JavaScript
|
|
200
278
|
|
|
201
279
|
```javascript
|
|
202
280
|
window.ZestConfig = {
|
|
203
|
-
consentModeGoogle: true,
|
|
204
|
-
consentModeMicrosoft: true
|
|
281
|
+
consentModeGoogle: true,
|
|
282
|
+
consentModeMicrosoft: true
|
|
205
283
|
};
|
|
206
284
|
```
|
|
207
285
|
|
|
@@ -215,12 +293,10 @@ window.ZestConfig = {
|
|
|
215
293
|
></script>
|
|
216
294
|
```
|
|
217
295
|
|
|
218
|
-
### How it works
|
|
219
|
-
|
|
220
296
|
When enabled, Zest automatically:
|
|
221
297
|
|
|
222
298
|
1. Pushes a `'default'` denied state on page load (before any tracking scripts fire)
|
|
223
|
-
2. Pushes an `'update'`
|
|
299
|
+
2. Pushes an `'update'` whenever the user makes a choice
|
|
224
300
|
|
|
225
301
|
### Category mapping
|
|
226
302
|
|
|
@@ -231,11 +307,9 @@ When enabled, Zest automatically:
|
|
|
231
307
|
| `analytics` | `analytics_storage` | — |
|
|
232
308
|
| `marketing` | `ad_storage`, `ad_user_data`, `ad_personalization` | `ad_storage` |
|
|
233
309
|
|
|
234
|
-
No additional setup is needed — just include your Google Analytics/GTM or Microsoft UET tags as usual and Zest will handle the consent signaling.
|
|
235
|
-
|
|
236
310
|
## Localization
|
|
237
311
|
|
|
238
|
-
|
|
312
|
+
Built-in translations with auto-detection.
|
|
239
313
|
|
|
240
314
|
**Supported languages:** `en`, `de`, `es`, `fr`, `it`, `pt`, `nl`, `pl`, `uk`, `ru`, `ja`, `zh`
|
|
241
315
|
|
|
@@ -243,8 +317,9 @@ Zest has **built-in translations** with auto-detection.
|
|
|
243
317
|
|
|
244
318
|
| Bundle | Size (gzip) | Description |
|
|
245
319
|
|--------|-------------|-------------|
|
|
246
|
-
| `zest.min.js` | ~
|
|
247
|
-
| `zest.{lang}.min.js` | ~
|
|
320
|
+
| `zest.min.js` | ~16 KB | All 12 languages, auto-detects |
|
|
321
|
+
| `zest.{lang}.min.js` | ~9 KB | Single language (e.g. `zest.de.min.js`) |
|
|
322
|
+
| `zest.headless.esm.min.js` | ~11 KB | Logic only, no UI / no translations (ESM import) |
|
|
248
323
|
|
|
249
324
|
```html
|
|
250
325
|
<!-- Full bundle - auto-detects language -->
|
|
@@ -257,61 +332,40 @@ Zest has **built-in translations** with auto-detection.
|
|
|
257
332
|
### Language Detection
|
|
258
333
|
|
|
259
334
|
```javascript
|
|
260
|
-
window.ZestConfig = {
|
|
261
|
-
lang: 'auto' // Auto-detect (default)
|
|
262
|
-
};
|
|
335
|
+
window.ZestConfig = { lang: 'auto' }; // default
|
|
263
336
|
```
|
|
264
337
|
|
|
265
|
-
|
|
266
|
-
1. `lang` config option (if not 'auto')
|
|
267
|
-
2. `<html lang="de">` attribute
|
|
268
|
-
3. Browser language (`navigator.language`)
|
|
269
|
-
4. Fallback to English
|
|
338
|
+
Priority: `lang` config → `<html lang="...">` → `navigator.language` → English.
|
|
270
339
|
|
|
271
340
|
### Force Specific Language
|
|
272
341
|
|
|
273
342
|
```javascript
|
|
274
|
-
window.ZestConfig = {
|
|
275
|
-
lang: 'de' // Force German
|
|
276
|
-
};
|
|
343
|
+
window.ZestConfig = { lang: 'de' };
|
|
277
344
|
```
|
|
278
345
|
|
|
279
346
|
### Override Labels
|
|
280
347
|
|
|
281
348
|
```javascript
|
|
282
349
|
window.ZestConfig = {
|
|
283
|
-
lang: 'de',
|
|
350
|
+
lang: 'de',
|
|
284
351
|
labels: {
|
|
285
352
|
banner: {
|
|
286
|
-
title: 'Custom German Title'
|
|
353
|
+
title: 'Custom German Title'
|
|
287
354
|
}
|
|
288
355
|
}
|
|
289
356
|
};
|
|
290
357
|
```
|
|
291
358
|
|
|
292
|
-
Standalone JSON translation files
|
|
359
|
+
Standalone JSON translation files are in `/locales/`.
|
|
293
360
|
|
|
294
|
-
##
|
|
361
|
+
## Styling the UI (full build)
|
|
295
362
|
|
|
296
|
-
|
|
363
|
+
The UI is rendered inside a Shadow DOM with `mode: 'open'`, so your global
|
|
364
|
+
CSS can't reach inside the component. You have three options:
|
|
297
365
|
|
|
298
|
-
|
|
299
|
-
window.ZestConfig = {
|
|
300
|
-
customStyles: `
|
|
301
|
-
.zest-banner {
|
|
302
|
-
max-width: 600px;
|
|
303
|
-
}
|
|
304
|
-
.zest-btn--primary {
|
|
305
|
-
border-radius: 20px;
|
|
306
|
-
}
|
|
307
|
-
.zest-modal {
|
|
308
|
-
max-width: 600px;
|
|
309
|
-
}
|
|
310
|
-
`
|
|
311
|
-
};
|
|
312
|
-
```
|
|
366
|
+
### 1. CSS custom properties (inheritable through Shadow DOM)
|
|
313
367
|
|
|
314
|
-
|
|
368
|
+
The following custom properties are exposed on the host elements:
|
|
315
369
|
|
|
316
370
|
```css
|
|
317
371
|
zest-banner, zest-modal, zest-widget {
|
|
@@ -326,6 +380,32 @@ zest-banner, zest-modal, zest-widget {
|
|
|
326
380
|
}
|
|
327
381
|
```
|
|
328
382
|
|
|
383
|
+
### 2. `customStyles` config option
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
window.ZestConfig = {
|
|
387
|
+
customStyles: `
|
|
388
|
+
.zest-banner { max-width: 600px; }
|
|
389
|
+
.zest-btn--primary { border-radius: 20px; }
|
|
390
|
+
.zest-modal { max-width: 600px; }
|
|
391
|
+
`
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
> **Security note:** `customStyles` is sanitized — `@import`, `expression()`,
|
|
396
|
+
> external `url()` values, and selectors targeting the accept/reject
|
|
397
|
+
> buttons are stripped. This prevents clickjacking via invisible-button
|
|
398
|
+
> CSS attacks. Payloads over 20 KB are dropped entirely.
|
|
399
|
+
|
|
400
|
+
### 3. Style the host elements directly
|
|
401
|
+
|
|
402
|
+
The custom elements `zest-banner`, `zest-modal`, `zest-widget` live in the
|
|
403
|
+
light DOM — you can position, hide, or z-index them from your global CSS.
|
|
404
|
+
|
|
405
|
+
### Want full CSS control?
|
|
406
|
+
|
|
407
|
+
Use the **headless** entry and style your own markup however you like.
|
|
408
|
+
|
|
329
409
|
## Categories
|
|
330
410
|
|
|
331
411
|
| Category | ID | Default | Description |
|
|
@@ -335,18 +415,38 @@ zest-banner, zest-modal, zest-widget {
|
|
|
335
415
|
| Analytics | `analytics` | OFF | Usage tracking |
|
|
336
416
|
| Marketing | `marketing` | OFF | Advertising cookies |
|
|
337
417
|
|
|
418
|
+
Unknown cookies default to `marketing` (strictest).
|
|
419
|
+
|
|
420
|
+
## Security
|
|
421
|
+
|
|
422
|
+
Zest takes a defense-in-depth approach to security.
|
|
423
|
+
|
|
424
|
+
Highlights:
|
|
425
|
+
|
|
426
|
+
- All config-driven HTML is escaped via an internal `escapeHTML` pass
|
|
427
|
+
- `policyUrl` is validated against an allowlist (`http:`, `https:`,
|
|
428
|
+
`mailto:`, `tel:`, relative)
|
|
429
|
+
- `accentColor` must pass a strict color validator
|
|
430
|
+
- `customStyles` is sanitized (see above)
|
|
431
|
+
- Consent cookie JSON is schema-validated on read (prototype pollution safe)
|
|
432
|
+
- On HTTPS, the consent cookie is written with the `Secure` flag
|
|
433
|
+
- `window.Zest` is frozen and non-configurable once installed
|
|
434
|
+
- User callbacks are wrapped in try/catch so a throwing handler can't
|
|
435
|
+
break the consent flow
|
|
436
|
+
- Cookie / storage / script queues are size-capped (DoS prevention)
|
|
437
|
+
|
|
438
|
+
To report a vulnerability, open a private security advisory on GitHub.
|
|
439
|
+
|
|
338
440
|
## Config Schema
|
|
339
441
|
|
|
340
|
-
JSON Schema
|
|
442
|
+
JSON Schema for IDE autocompletion: [`zest.config.schema.json`](zest.config.schema.json)
|
|
341
443
|
|
|
342
444
|
## Contributing
|
|
343
445
|
|
|
344
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
345
|
-
|
|
346
446
|
1. Fork the repository
|
|
347
447
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
348
|
-
3. Commit your changes
|
|
349
|
-
4. Push to the branch
|
|
448
|
+
3. Commit your changes
|
|
449
|
+
4. Push to the branch
|
|
350
450
|
5. Open a Pull Request
|
|
351
451
|
|
|
352
452
|
## Credits
|