@el7ven/cookie-kit 0.2.15

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.
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <div class="cookie-consent" :data-cookie-kit-theme="theme" :style="themeVarsStyle">
3
+ <!-- Cookie Drawer (Unified Component) -->
4
+ <CookieDrawer
5
+ ref="drawerRef"
6
+ :is-visible="isVisible"
7
+ :is-settings-mode="isSettingsMode"
8
+ :current-tab="currentTab"
9
+ :categories="categories"
10
+ :config="config"
11
+ :consent-version="consentVersion"
12
+ :capabilities="capabilities"
13
+ :is-v2="isV2"
14
+ @close="handleClose"
15
+ @open-settings="handleOpenSettings"
16
+ @select-tab="handleSelectTab"
17
+ @toggle-category="handleToggleCategory"
18
+ @accept-selection="handleAcceptSelection"
19
+ @accept-all="handleAcceptAll"
20
+ @reject-all="handleRejectAll"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script setup>
26
+ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
27
+ import { useCookieKit } from './index.js'
28
+ import CookieDrawer from './CookieDrawer.vue'
29
+
30
+ // Props for customization
31
+ const props = defineProps({
32
+ config: {
33
+ type: Object,
34
+ default: () => ({
35
+ categories: {
36
+ necessary: { required: true, enabled: true, name: 'Essential Cookies' },
37
+ analytics: { required: false, enabled: true, name: 'Analytics Cookies' },
38
+ marketing: { required: false, enabled: false, name: 'Marketing Cookies' }
39
+ },
40
+ consentExpireDays: 365,
41
+ version: '0.2.0',
42
+ theme: 'light'
43
+ })
44
+ }
45
+ })
46
+
47
+ // Use the new composable
48
+ const {
49
+ consent,
50
+ acceptAll,
51
+ rejectAll,
52
+ acceptCategory,
53
+ hasConsent,
54
+ hasCategoryConsent
55
+ } = useCookieKit(props.config)
56
+
57
+ // Local state
58
+ const isVisible = computed(() => !consent.value?.hasConsented)
59
+ const isSettingsMode = ref(false)
60
+ const currentTab = ref('privacy')
61
+ const categories = ref(props.config.categories)
62
+ const consentVersion = computed(() => props.config.version || '0.2.0')
63
+ const capabilities = computed(() => ({}))
64
+ const isV2 = computed(() => true)
65
+ const theme = computed(() => props.config.theme || 'light')
66
+ const themeVarsStyle = computed(() => {
67
+ const vars = props.config.themeVars || {}
68
+ return Object.fromEntries(
69
+ Object.entries(vars).map(([key, value]) => [key.startsWith('--') ? key : `--${key}`, value])
70
+ )
71
+ })
72
+ const drawerRef = ref(null)
73
+
74
+ const enabledSettingsTabs = computed(() => {
75
+ return Object.keys(props.config.categories || {}).filter(categoryId => {
76
+ return props.config.categories[categoryId]?.enabled
77
+ })
78
+ })
79
+
80
+ const openSettings = () => {
81
+ isSettingsMode.value = true
82
+ }
83
+
84
+ const closeSettings = () => {
85
+ if (consent.value?.hasConsented) {
86
+ return
87
+ }
88
+ isSettingsMode.value = false
89
+ }
90
+
91
+ const acceptSelection = () => {
92
+ // Save current category states
93
+ Object.keys(categories.value).forEach(id => {
94
+ const isEnabled = categories.value[id]?.enabled
95
+ if (isEnabled !== undefined) {
96
+ acceptCategory(id, isEnabled)
97
+ }
98
+ })
99
+ }
100
+
101
+ const selectTab = (tabId) => {
102
+ currentTab.value = tabId
103
+ }
104
+
105
+ const getDrawerDialogElement = () => {
106
+ const exposed = drawerRef.value?.dialogElement
107
+ const result = typeof exposed === 'function' ? exposed() : exposed?.value || exposed || null
108
+ return result
109
+ }
110
+
111
+ // Focus trap logic (simplified)
112
+ watch(isVisible, async (visible) => {
113
+ if (visible) {
114
+ await nextTick()
115
+ await new Promise(resolve => setTimeout(resolve, 50))
116
+ const dialogElement = getDrawerDialogElement()
117
+ if (dialogElement && props.config.debug) {
118
+ console.log('[CookieConsent] Focus trap activated')
119
+ }
120
+ }
121
+ })
122
+
123
+ // Event handlers
124
+ const handleAcceptAll = () => {
125
+ acceptAll()
126
+ }
127
+
128
+ const handleRejectAll = () => {
129
+ rejectAll()
130
+ }
131
+
132
+ const handleAcceptSelection = () => {
133
+ acceptSelection()
134
+ }
135
+
136
+ const handleOpenSettings = () => {
137
+ openSettings()
138
+ }
139
+
140
+ const handleSelectTab = (tabId) => {
141
+ selectTab(tabId)
142
+ }
143
+
144
+ const handleToggleCategory = (categoryId) => {
145
+ const current = categories.value[categoryId]
146
+ if (current) {
147
+ categories.value[categoryId] = { ...current, enabled: !current.enabled }
148
+ }
149
+ }
150
+
151
+ const handleClose = () => {
152
+ if (isSettingsMode.value) {
153
+ closeSettings()
154
+ }
155
+ }
156
+
157
+ // Cleanup on unmount
158
+ onUnmounted(() => {
159
+ // Add cleanup if needed
160
+ })
161
+
162
+ // Expose methods for external use
163
+ defineExpose({
164
+ acceptAll: handleAcceptAll,
165
+ rejectAll: handleRejectAll,
166
+ resetConsent: () => {
167
+ // Reset logic if needed
168
+ }
169
+ })
170
+ </script>
171
+
172
+ <style lang="scss" scoped>
173
+ .cookie-consent {
174
+ position: relative;
175
+
176
+ &--has-modal {
177
+ min-height: 100vh;
178
+ }
179
+ }
180
+ </style>
@@ -0,0 +1,203 @@
1
+ <template>
2
+ <!-- Backdrop with opacity animation -->
3
+ <Transition name="backdrop-fade" appear>
4
+ <div
5
+ v-if="isVisible && !isEssentialBanner"
6
+ class="cookie-drawer__backdrop"
7
+ @click="handleBackdropClick"
8
+ ></div>
9
+ </Transition>
10
+
11
+ <!-- Drawer with slide animation -->
12
+ <Transition :name="drawerAnimation" appear>
13
+ <div
14
+ v-if="isVisible"
15
+ :class="['cookie-drawer__content-wrapper', drawerClasses, rootClasses]"
16
+ >
17
+ <div ref="dialogRef" class="cookie-drawer__content" role="dialog" aria-modal="true" @click.stop>
18
+ <!-- Header -->
19
+ <div class="cookie-drawer__header">
20
+ <h3 class="cookie-drawer__title">
21
+ {{ isSettingsMode
22
+ ? (config.texts?.settings?.title || 'Cookie Settings')
23
+ : (config.mode === 'essential'
24
+ ? (config.texts?.essential?.title || 'Essential Cookies')
25
+ : (config.texts?.gdpr?.title || 'Cookie Consent'))
26
+ }}
27
+ </h3>
28
+ <!-- Message -->
29
+ <div v-if="!isSettingsMode && !isEssentialBanner" class="cookie-drawer__message">
30
+ {{ config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.' }}
31
+ </div>
32
+
33
+ <button
34
+ v-if="isSettingsMode"
35
+ @click="handleClose"
36
+ class="cookie-drawer__close"
37
+ aria-label="Close"
38
+ >
39
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
40
+ <path d="M6.40013 18.3079L5.69238 17.6001L11.2924 12.0001L5.69238 6.40013L6.40013 5.69238L12.0001 11.2924L17.6001 5.69238L18.3079 6.40013L12.7079 12.0001L18.3079 17.6001L17.6001 18.3079L12.0001 12.7079L6.40013 18.3079Z" fill="#0D0B3D"/>
41
+ </svg>
42
+ </button>
43
+ </div>
44
+
45
+ <!-- Essential Banner -->
46
+ <div v-if="!isSettingsMode && isEssentialBanner" class="cookie-drawer__banner">
47
+ <div class="cookie-drawer__essential">
48
+ <div class="cookie-drawer__message cookie-drawer__message--essential">
49
+ {{ config.texts?.essential?.message || 'We use essential cookies to make our site work. By using our site, you accept our use of essential cookies.' }}
50
+ </div>
51
+ <div class="cookie-drawer__actions">
52
+ <button @click="$emit('acceptAll')" class="cookie-drawer__button cookie-drawer__button--primary">
53
+ {{ config.texts?.essential?.accept || 'Accept' }}
54
+ </button>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- GDPR Banner/Settings -->
60
+ <div v-else class="cookie-drawer__body">
61
+ <!-- Banner Mode -->
62
+ <div v-if="!isSettingsMode" class="cookie-drawer__banner">
63
+ <div class="cookie-drawer__message">
64
+ {{ config.texts?.gdpr?.message || 'We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.' }}
65
+ </div>
66
+ <div class="cookie-drawer__actions">
67
+ <button @click="$emit('rejectAll')" class="cookie-drawer__button cookie-drawer__button--secondary">
68
+ {{ config.texts?.gdpr?.reject || 'Reject All' }}
69
+ </button>
70
+ <button @click="$emit('acceptAll')" class="cookie-drawer__button cookie-drawer__button--primary">
71
+ {{ config.texts?.gdpr?.accept || 'Accept All' }}
72
+ </button>
73
+ <button @click="$emit('openSettings')" class="cookie-drawer__button cookie-drawer__button--link">
74
+ {{ config.texts?.gdpr?.settings || 'Customize' }}
75
+ </button>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- Settings Mode -->
80
+ <div v-else class="cookie-drawer__settings">
81
+ <!-- Tabs -->
82
+ <div v-if="enabledSettingsTabs.length > 1" class="cookie-drawer__tabs">
83
+ <button
84
+ v-for="tab in enabledSettingsTabs"
85
+ :key="tab"
86
+ :class="['cookie-drawer__tab', { 'cookie-drawer__tab--active': currentTab === tab }]"
87
+ @click="$emit('selectTab', tab)"
88
+ >
89
+ {{ getCategoryTitle(tab) }}
90
+ </button>
91
+ </div>
92
+
93
+ <!-- Tab Content -->
94
+ <div class="cookie-drawer__tab-content">
95
+ <div v-if="currentTab === 'privacy'" class="cookie-drawer__privacy">
96
+ <h4>{{ config.texts?.settings?.privacy?.title || 'Privacy Policy' }}</h4>
97
+ <p>{{ config.texts?.settings?.privacy?.description || 'Learn about how we use cookies and protect your privacy.' }}</p>
98
+ </div>
99
+
100
+ <div v-else class="cookie-drawer__category">
101
+ <div class="cookie-drawer__category-header">
102
+ <h4>{{ getCategoryTitle(currentTab) }}</h4>
103
+ <label class="cookie-drawer__toggle">
104
+ <input
105
+ type="checkbox"
106
+ :checked="categories[currentTab]?.enabled"
107
+ :disabled="categories[currentTab]?.required"
108
+ @change="$emit('toggleCategory', currentTab)"
109
+ />
110
+ <span class="cookie-drawer__toggle-slider"></span>
111
+ </label>
112
+ </div>
113
+ <div class="cookie-drawer__category-description">
114
+ {{ getCategoryDescription(currentTab) }}
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Settings Actions -->
120
+ <div class="cookie-drawer__settings-actions">
121
+ <button @click="$emit('acceptSelection')" class="cookie-drawer__button cookie-drawer__button--primary">
122
+ {{ config.texts?.settings?.save || 'Save Preferences' }}
123
+ </button>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </Transition>
130
+ </template>
131
+
132
+ <script setup>
133
+ import { computed, ref } from 'vue'
134
+
135
+ const props = defineProps({
136
+ isVisible: Boolean,
137
+ isSettingsMode: Boolean,
138
+ currentTab: String,
139
+ categories: Object,
140
+ config: Object,
141
+ consentVersion: String,
142
+ capabilities: Object,
143
+ isV2: Boolean
144
+ })
145
+
146
+ const emit = defineEmits([
147
+ 'close',
148
+ 'openSettings',
149
+ 'selectTab',
150
+ 'toggleCategory',
151
+ 'acceptSelection',
152
+ 'acceptAll',
153
+ 'rejectAll'
154
+ ])
155
+
156
+ const dialogRef = ref(null)
157
+
158
+ const isEssentialBanner = computed(() => props.config.mode === 'essential')
159
+
160
+ const drawerClasses = computed(() => ({
161
+ 'cookie-drawer__content-wrapper--settings': props.isSettingsMode,
162
+ 'cookie-drawer__content-wrapper--banner': !props.isSettingsMode
163
+ }))
164
+
165
+ const rootClasses = computed(() => ({
166
+ 'cookie-drawer__content-wrapper--bottom': true
167
+ }))
168
+
169
+ const drawerAnimation = computed(() =>
170
+ props.isSettingsMode ? 'drawer-slide-up' : 'banner-slide-up'
171
+ )
172
+
173
+ const enabledSettingsTabs = computed(() => {
174
+ return ['privacy', ...Object.keys(props.categories || {}).filter(id =>
175
+ props.categories[id]?.enabled
176
+ )]
177
+ })
178
+
179
+ const getCategoryTitle = (categoryId) => {
180
+ return props.categories[categoryId]?.name || categoryId
181
+ }
182
+
183
+ const getCategoryDescription = (categoryId) => {
184
+ return props.categories[categoryId]?.description || `Description for ${categoryId} cookies.`
185
+ }
186
+
187
+ const handleBackdropClick = () => {
188
+ if (!props.isSettingsMode) {
189
+ emit('close')
190
+ }
191
+ }
192
+
193
+ const handleClose = () => {
194
+ emit('close')
195
+ }
196
+
197
+ // Expose dialog element for focus trap
198
+ defineExpose({
199
+ dialogElement: () => dialogRef.value
200
+ })
201
+ </script>
202
+
203
+ <style src="@el7ven/cookie-consent/dist/styles"></style>
@@ -0,0 +1,2 @@
1
+ export { default as CookieConsent } from '../CookieConsent.vue'
2
+ export { default as CookieDrawer } from '../CookieDrawer.vue'
@@ -0,0 +1,182 @@
1
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
2
+
3
+ export function useCookieConsent(config = {}) {
4
+ const isVisible = ref(false)
5
+ const isSettingsMode = ref(false)
6
+ const currentTab = ref('categories')
7
+ const consentVersion = ref('1.0.0')
8
+
9
+ const categories = ref({
10
+ necessary: {
11
+ required: true,
12
+ enabled: true,
13
+ name: 'Essential Cookies',
14
+ description: 'These cookies are necessary for the website to function and cannot be switched off.'
15
+ },
16
+ analytics: {
17
+ required: false,
18
+ enabled: false,
19
+ name: 'Analytics Cookies',
20
+ description: 'These cookies help us understand how visitors interact with our website.'
21
+ },
22
+ marketing: {
23
+ required: false,
24
+ enabled: false,
25
+ name: 'Marketing Cookies',
26
+ description: 'These cookies are used to track visitors across websites to show relevant ads.'
27
+ }
28
+ })
29
+
30
+ const capabilities = ref({
31
+ hasConsented: false,
32
+ canShowSettings: true,
33
+ canAcceptAll: true,
34
+ canRejectAll: true,
35
+ canCustomize: true
36
+ })
37
+
38
+ const isV2 = ref(true)
39
+
40
+ const hasConsent = computed(() => {
41
+ return localStorage.getItem('cookie-consent') !== null
42
+ })
43
+
44
+ const consentData = computed(() => {
45
+ const stored = localStorage.getItem('cookie-consent')
46
+ return stored ? JSON.parse(stored) : null
47
+ })
48
+
49
+ const showConsent = () => {
50
+ isVisible.value = true
51
+ }
52
+
53
+ const hideConsent = () => {
54
+ isVisible.value = false
55
+ }
56
+
57
+ const openSettings = () => {
58
+ isSettingsMode.value = true
59
+ }
60
+
61
+ const closeSettings = () => {
62
+ isSettingsMode.value = false
63
+ }
64
+
65
+ const selectTab = (tab) => {
66
+ currentTab.value = tab
67
+ }
68
+
69
+ const toggleCategory = (categoryName, enabled) => {
70
+ if (categories.value[categoryName] && !categories.value[categoryName].required) {
71
+ categories.value[categoryName].enabled = enabled
72
+ }
73
+ }
74
+
75
+ const acceptSelection = () => {
76
+ const consent = {
77
+ version: consentVersion.value,
78
+ timestamp: new Date().toISOString(),
79
+ categories: Object.keys(categories.value).reduce((acc, key) => {
80
+ acc[key] = categories.value[key].enabled
81
+ return acc
82
+ }, {})
83
+ }
84
+
85
+ localStorage.setItem('cookie-consent', JSON.stringify(consent))
86
+ capabilities.value.hasConsented = true
87
+ hideConsent()
88
+ closeSettings()
89
+ }
90
+
91
+ const acceptAll = () => {
92
+ Object.keys(categories.value).forEach(key => {
93
+ categories.value[key].enabled = true
94
+ })
95
+ acceptSelection()
96
+ }
97
+
98
+ const rejectAll = () => {
99
+ Object.keys(categories.value).forEach(key => {
100
+ if (!categories.value[key].required) {
101
+ categories.value[key].enabled = false
102
+ }
103
+ })
104
+ acceptSelection()
105
+ }
106
+
107
+ const resetConsent = () => {
108
+ localStorage.removeItem('cookie-consent')
109
+ capabilities.value.hasConsented = false
110
+ Object.keys(categories.value).forEach(key => {
111
+ if (!categories.value[key].required) {
112
+ categories.value[key].enabled = false
113
+ }
114
+ })
115
+ }
116
+
117
+ const handleClose = () => {
118
+ hideConsent()
119
+ closeSettings()
120
+ }
121
+
122
+ const handleOpenSettings = () => {
123
+ openSettings()
124
+ }
125
+
126
+ const handleSelectTab = (tab) => {
127
+ selectTab(tab)
128
+ }
129
+
130
+ const handleToggleCategory = (categoryName, enabled) => {
131
+ toggleCategory(categoryName, enabled)
132
+ }
133
+
134
+ const handleAcceptSelection = () => {
135
+ acceptSelection()
136
+ }
137
+
138
+ const handleAcceptAll = () => {
139
+ acceptAll()
140
+ }
141
+
142
+ const handleRejectAll = () => {
143
+ rejectAll()
144
+ }
145
+
146
+ onMounted(() => {
147
+ if (!hasConsent.value) {
148
+ showConsent()
149
+ } else {
150
+ capabilities.value.hasConsented = true
151
+ }
152
+ })
153
+
154
+ return {
155
+ isVisible,
156
+ isSettingsMode,
157
+ currentTab,
158
+ categories,
159
+ capabilities,
160
+ isV2,
161
+ consentVersion,
162
+ hasConsent,
163
+ consentData,
164
+ showConsent,
165
+ hideConsent,
166
+ openSettings,
167
+ closeSettings,
168
+ selectTab,
169
+ toggleCategory,
170
+ acceptSelection,
171
+ acceptAll,
172
+ rejectAll,
173
+ resetConsent,
174
+ handleClose,
175
+ handleOpenSettings,
176
+ handleSelectTab,
177
+ handleToggleCategory,
178
+ handleAcceptSelection,
179
+ handleAcceptAll,
180
+ handleRejectAll
181
+ }
182
+ }
@@ -0,0 +1,58 @@
1
+ import { computed, ref } from 'vue'
2
+ import { createCookieKit, DEFAULT_CONFIG } from '../core/index.js'
3
+ import { VERSION, getVersion, getVersionInfo, logVersion } from './version.js'
4
+
5
+ export { createCookieKit, DEFAULT_CONFIG, VERSION, getVersion, getVersionInfo, logVersion }
6
+
7
+ export function useCookieKitVue(userConfig = {}) {
8
+ const core = createCookieKit(userConfig)
9
+
10
+ const consent = ref(core.getConsent())
11
+ const categories = ref(consent.value?.categories || core.getDefaultCategoriesState())
12
+
13
+ core.on('consentChanged', ({ consent: nextConsent, categories: nextCategories }) => {
14
+ consent.value = nextConsent
15
+ categories.value = { ...nextCategories }
16
+ })
17
+
18
+ const hasConsented = computed(() => consent.value?.hasConsented === true)
19
+
20
+ const acceptAll = async () => {
21
+ const next = await core.acceptAll('banner')
22
+ consent.value = next
23
+ categories.value = { ...next.categories }
24
+ return next
25
+ }
26
+
27
+ const rejectAll = async () => {
28
+ const next = await core.rejectAll('banner')
29
+ consent.value = next
30
+ categories.value = { ...next.categories }
31
+ return next
32
+ }
33
+
34
+ const acceptSelected = async (selectedIds) => {
35
+ const next = await core.acceptSelected(selectedIds, 'settings')
36
+ consent.value = next
37
+ categories.value = { ...next.categories }
38
+ return next
39
+ }
40
+
41
+ const resetConsent = () => {
42
+ core.clearConsent()
43
+ consent.value = null
44
+ categories.value = core.getDefaultCategoriesState()
45
+ }
46
+
47
+ return {
48
+ core,
49
+ consent,
50
+ categories,
51
+ hasConsented,
52
+ acceptAll,
53
+ rejectAll,
54
+ acceptSelected,
55
+ resetConsent,
56
+ hasCategoryConsent: category => core.hasCategoryConsent(category)
57
+ }
58
+ }
@@ -0,0 +1,40 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { useCookieKitVue } from './index.js'
5
+
6
+ function createMemoryStorageAdapter() {
7
+ const memory = new Map()
8
+
9
+ return {
10
+ getItem: key => memory.get(key) ?? null,
11
+ setItem: (key, value) => memory.set(key, value),
12
+ removeItem: key => memory.delete(key),
13
+ defaultKey: 'cookie_consent'
14
+ }
15
+ }
16
+
17
+ test('vue adapter: composable state updates after actions', async () => {
18
+ const kit = useCookieKitVue({
19
+ storageAdapter: createMemoryStorageAdapter(),
20
+ categories: {
21
+ necessary: { required: true, enabled: true },
22
+ analytics: { required: false, enabled: true }
23
+ }
24
+ })
25
+
26
+ assert.equal(kit.hasConsented.value, false)
27
+
28
+ await kit.acceptAll()
29
+
30
+ assert.equal(kit.hasConsented.value, true)
31
+ assert.equal(kit.consent.value?.categories?.necessary, true)
32
+ assert.equal(kit.consent.value?.categories?.analytics, true)
33
+
34
+ kit.resetConsent()
35
+
36
+ assert.equal(kit.hasConsented.value, false)
37
+ assert.equal(kit.consent.value, null)
38
+ assert.equal(kit.categories.value.necessary, true)
39
+ assert.equal(kit.categories.value.analytics, false)
40
+ })
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Version metadata for package runtime.
3
+ */
4
+
5
+ export const VERSION = '1.0.0-beta.0'
6
+ export const PACKAGE_NAME = '@el7ven/cookie-consent/vue'
7
+
8
+ // Vue adapter version metadata.
9
+ export const VERSION_INFO = {
10
+ version: VERSION,
11
+ name: PACKAGE_NAME,
12
+ buildDate: new Date().toISOString(),
13
+ environment: 'production'
14
+ }
15
+
16
+ // Return package version.
17
+ export function getVersion() {
18
+ return VERSION
19
+ }
20
+
21
+ // Return full version metadata.
22
+ export function getVersionInfo() {
23
+ return VERSION_INFO
24
+ }
25
+
26
+ // Print runtime version in debug-friendly format.
27
+ export function logVersion() {
28
+ console.log(`💚 ${PACKAGE_NAME} v${VERSION}`)
29
+ }