@stackonward/cli 1.3.3 → 1.5.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 +248 -0
- package/dist/index.js +14124 -635
- package/dist/index.js.map +1 -1
- package/dist/resources/components/nuxt/site-source/app/app.vue +4 -2
- package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +50 -50
- package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationHeader.vue → app/Header.vue} +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationShell.vue → app/Shell.vue} +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationSidebar.vue → app/Sidebar.vue} +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/{ui/CheckoutStatus.vue → checkout/Status.vue} +8 -3
- package/dist/resources/components/nuxt/site-source/app/components/{ui/CheckoutSummary.vue → checkout/Summary.vue} +11 -11
- package/dist/resources/components/nuxt/site-source/app/components/{site/CookieConsent.vue → consent/Banner.vue} +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/{site/ContentArticlePage.vue → content/Article.vue} +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/{site/ContentListPage.vue → content/List.vue} +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/{ui/GoogleSignInButton.vue → google/SignInButton.vue} +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/{site/LocaleSwitcher.vue → locale/Menu.vue} +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/site/{MarketingFooter.vue → Footer.vue} +8 -8
- package/dist/resources/components/nuxt/site-source/app/components/site/{MarketingHeader.vue → Header.vue} +27 -27
- package/dist/resources/components/nuxt/site-source/app/components/site/Shell.vue +29 -0
- package/dist/resources/components/nuxt/site-source/app/components/{site/ThemeSwitcher.vue → theme/Menu.vue} +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseButton.vue → Button.vue} +1 -8
- package/dist/resources/components/nuxt/site-source/app/components/ui/InfiniteScroll.vue +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/{ui/YoutubeEmbed.vue → youtube/Embed.vue} +10 -2
- package/dist/resources/components/nuxt/site-source/app/composables/useConsent.ts +8 -10
- package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +9 -14
- package/dist/resources/components/nuxt/site-source/app/composables/useSeo.ts +3 -1
- package/dist/resources/components/nuxt/site-source/app/composables/useSiteShell.ts +19 -22
- package/dist/resources/components/nuxt/site-source/app/consent/types.ts +59 -0
- package/dist/resources/components/nuxt/site-source/app/content/types.ts +17 -0
- package/dist/resources/components/nuxt/site-source/app/layouts/app.vue +2 -2
- package/dist/resources/components/nuxt/site-source/app/layouts/default.vue +2 -2
- package/dist/resources/components/nuxt/site-source/app/navigation/types.ts +68 -0
- package/dist/resources/components/nuxt/site-source/app/plugins/consent.ts +2 -2
- package/dist/resources/components/nuxt/site-source/app/site/config.ts +35 -0
- package/dist/resources/components/registry.json +63 -51
- package/dist/resources/deploy/ci/github/caller.yml +38 -0
- package/dist/resources/deploy/ci/onedev/import.yml +14 -0
- package/dist/resources/deploy/nginx/cache.conf +17 -0
- package/dist/resources/deploy/nginx/compression.conf +23 -0
- package/dist/resources/deploy/nginx/proxy.conf +15 -0
- package/dist/resources/deploy/profiles/go-service/compose.yaml +19 -0
- package/dist/resources/deploy/profiles/nuxt-node/Dockerfile +17 -0
- package/dist/resources/deploy/profiles/static-files/nginx/site.conf.template +55 -0
- package/dist/resources/deploy/profiles/static-nginx/Dockerfile +18 -0
- package/dist/resources/deploy/profiles/static-nginx/nginx/default.conf.template +28 -0
- package/dist/resources/deploy/targets/compose-host/deploy.config.example.json +114 -0
- package/dist/resources/deploy/traffic/nginx/stackonward-nginx-traffic +96 -0
- package/dist/resources/registry.json +30 -30
- package/dist/resources/templates/base/.claude/docs/architecture.md +4 -0
- package/dist/resources/templates/base/AGENTS.md +15 -0
- package/dist/resources/templates/base/CLAUDE.md +15 -0
- package/dist/resources/templates/go-backend/.dockerignore +25 -3
- package/dist/resources/templates/go-backend/deploy.config.json +26 -0
- package/dist/resources/templates/go-backend/docs/runtime-provenance.json +9 -6
- package/dist/resources/templates/go-backend/docs/source-provenance.json +33 -22
- package/dist/resources/templates/go-backend/internal/common/enums/register_locale.go +7 -0
- package/dist/resources/templates/go-backend/internal/common/enums/register_locale_test.go +30 -0
- package/dist/resources/templates/go-backend/internal/common/locale/locale.go +102 -76
- package/dist/resources/templates/go-backend/internal/common/locale/locale_test.go +168 -45
- package/dist/resources/templates/go-backend/internal/core/commandreplay/model.go +5 -5
- package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence.go +20 -7
- package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence_test.go +51 -22
- package/dist/resources/templates/go-backend/internal/core/commandreplay/repository.go +2 -2
- package/dist/resources/templates/go-backend/internal/core/commandreplay/service.go +13 -24
- package/dist/resources/templates/go-backend/internal/core/commandreplay/service_persistence_test.go +98 -13
- package/dist/resources/templates/go-backend/internal/core/commandreplay/service_test.go +61 -36
- package/dist/resources/templates/go-backend/internal/core/commandreplay/types.go +6 -6
- package/dist/resources/templates/go-backend/internal/core/integrationevent/config.go +24 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/errors.go +53 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/model.go +71 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence.go +193 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence_postgres_test.go +178 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence_test.go +168 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/provider.go +16 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/registry.go +69 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/repository.go +17 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/service.go +283 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/service_test.go +433 -0
- package/dist/resources/templates/go-backend/internal/core/integrationevent/types.go +67 -0
- package/dist/resources/templates/go-backend/migrations/000001_infrastructure.down.sql +6 -0
- package/dist/resources/templates/go-backend/migrations/000001_infrastructure.up.sql +176 -16
- package/dist/resources/templates/go-backend/tests/infrastructure.sql +109 -6
- package/dist/resources/templates/nuxt/.claude/docs/stack.md +5 -1
- package/dist/resources/templates/nuxt/.dockerignore +19 -0
- package/dist/resources/templates/nuxt/README.md +3 -3
- package/dist/resources/templates/nuxt/app/app.config.ts +1 -1
- package/dist/resources/templates/nuxt/app/i18n/locales/en.json +1 -1
- package/dist/resources/templates/nuxt/app/pages/app/index.vue +1 -1
- package/dist/resources/templates/nuxt/app/pages/authors/[slug].vue +2 -2
- package/dist/resources/templates/nuxt/app/pages/blog/[slug].vue +4 -3
- package/dist/resources/templates/nuxt/app/pages/blog/category/[category].vue +2 -2
- package/dist/resources/templates/nuxt/app/pages/blog/index.vue +2 -2
- package/dist/resources/templates/nuxt/app/pages/legal/cookies.vue +3 -3
- package/dist/resources/templates/nuxt/app/pages/legal/privacy.vue +2 -2
- package/dist/resources/templates/nuxt/app/pages/legal/terms.vue +2 -2
- package/dist/resources/templates/nuxt/app/pages/ui/index.vue +45 -45
- package/dist/resources/templates/nuxt/deploy.config.json +27 -0
- package/dist/resources/templates/nuxt/nuxt.config.ts +4 -10
- package/dist/resources/templates/nuxt/package.json +8 -8
- package/dist/resources/templates/nuxt/test/e2e/rendering.test.ts +1 -0
- package/dist/resources/templates/nuxt/test/unit/consent.test.ts +4 -7
- package/package.json +5 -3
- package/dist/resources/components/nuxt/site-source/app/components/site/MarketingShell.vue +0 -33
- package/dist/resources/components/nuxt/site-source/app/types/site-shell.ts +0 -172
- /package/dist/resources/components/nuxt/site-source/app/components/{site/ConsentSettingsButton.vue → consent/Settings.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/{site/StaticPage.vue → content/Document.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/{site/ToastViewport.vue → feedback/Toast.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/site/{SiteBrand.vue → Brand.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseGradientBorder.vue → GradientBorder.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseModal.vue → Modal.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/ui/{BasePopover.vue → Popover.vue} +0 -0
- /package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseText.vue → Text.vue} +0 -0
|
@@ -72,31 +72,31 @@ useSeo({
|
|
|
72
72
|
<div class="system-lab__header">
|
|
73
73
|
<p class="system-lab__eyebrow">Design system</p>
|
|
74
74
|
<h1>Reusable components tuned for premium paid conversion.</h1>
|
|
75
|
-
<
|
|
75
|
+
<UiText as="p" tone="muted" size="lg">
|
|
76
76
|
Buttons, inputs, identity, analytics, video, infinite lists, checkout, and text constraints
|
|
77
77
|
share the same mobile-first visual language.
|
|
78
|
-
</
|
|
78
|
+
</UiText>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
81
|
<div class="system-lab__grid">
|
|
82
|
-
<
|
|
82
|
+
<UiGradientBorder radius="10px" class="system-lab__panel system-lab__panel--featured">
|
|
83
83
|
<div class="system-lab__panel-inner">
|
|
84
84
|
<div>
|
|
85
|
-
<
|
|
86
|
-
<
|
|
85
|
+
<UiText as="h2" size="xl" weight="bold">Actions</UiText>
|
|
86
|
+
<UiText as="p" tone="muted">
|
|
87
87
|
Primary actions are reserved for creation, checkout, and committed conversion steps.
|
|
88
|
-
</
|
|
88
|
+
</UiText>
|
|
89
89
|
</div>
|
|
90
90
|
<div class="system-lab__actions">
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
91
|
+
<UiButton label="Generate set" leading-icon="i-lucide-wand-sparkles" />
|
|
92
|
+
<UiButton label="Compare outputs" color="neutral" variant="outline" />
|
|
93
|
+
<UiButton label="Review brief" variant="soft" trailing-icon="i-lucide-arrow-right" />
|
|
94
94
|
</div>
|
|
95
95
|
</div>
|
|
96
|
-
</
|
|
96
|
+
</UiGradientBorder>
|
|
97
97
|
|
|
98
98
|
<section class="system-lab__panel">
|
|
99
|
-
<
|
|
99
|
+
<UiText as="h2" size="xl" weight="bold">Inputs</UiText>
|
|
100
100
|
<div class="system-lab__form">
|
|
101
101
|
<UInput icon="i-lucide-search" placeholder="Search campaign assets" />
|
|
102
102
|
<USelect v-model="selectedPlan" :items="planOptions" />
|
|
@@ -105,38 +105,38 @@ useSeo({
|
|
|
105
105
|
</section>
|
|
106
106
|
|
|
107
107
|
<section class="system-lab__panel">
|
|
108
|
-
<
|
|
109
|
-
<
|
|
108
|
+
<UiText as="h2" size="xl" weight="bold">Modal shell</UiText>
|
|
109
|
+
<UiText as="p" tone="muted">
|
|
110
110
|
The modal frame owns title, body, footer, close behavior, and scroll boundaries.
|
|
111
|
-
</
|
|
112
|
-
<
|
|
111
|
+
</UiText>
|
|
112
|
+
<UiButton
|
|
113
113
|
label="Open modal"
|
|
114
114
|
color="secondary"
|
|
115
115
|
variant="solid"
|
|
116
116
|
leading-icon="i-lucide-panel-top-open"
|
|
117
117
|
@click="modalOpen = true"
|
|
118
118
|
/>
|
|
119
|
-
<
|
|
119
|
+
<UiModal
|
|
120
120
|
v-model="modalOpen"
|
|
121
121
|
title="Approve campaign set"
|
|
122
122
|
description="Use slots for business-specific body and footer content."
|
|
123
123
|
>
|
|
124
124
|
<template #content>
|
|
125
|
-
<
|
|
125
|
+
<UiText as="p" tone="muted">
|
|
126
126
|
Campaign output is ready for review. The consuming product owns approval rules and
|
|
127
127
|
provider fulfillment.
|
|
128
|
-
</
|
|
128
|
+
</UiText>
|
|
129
129
|
</template>
|
|
130
130
|
<template #footer="{ close }">
|
|
131
|
-
<
|
|
132
|
-
<
|
|
131
|
+
<UiButton label="Close" color="neutral" variant="outline" @click="close" />
|
|
132
|
+
<UiButton label="Approve" @click="close" />
|
|
133
133
|
</template>
|
|
134
|
-
</
|
|
134
|
+
</UiModal>
|
|
135
135
|
</section>
|
|
136
136
|
|
|
137
137
|
<section class="system-lab__panel">
|
|
138
|
-
<
|
|
139
|
-
<
|
|
138
|
+
<UiText as="h2" size="xl" weight="bold">Popover actions</UiText>
|
|
139
|
+
<UiPopover
|
|
140
140
|
title="Quick actions"
|
|
141
141
|
description="Popover content and action rows share one base style."
|
|
142
142
|
:actions="[
|
|
@@ -145,24 +145,24 @@ useSeo({
|
|
|
145
145
|
]"
|
|
146
146
|
>
|
|
147
147
|
<template #reference>
|
|
148
|
-
<
|
|
148
|
+
<UiButton
|
|
149
149
|
label="Open actions"
|
|
150
150
|
variant="outline"
|
|
151
151
|
trailing-icon="i-lucide-chevron-down"
|
|
152
152
|
/>
|
|
153
153
|
</template>
|
|
154
|
-
</
|
|
154
|
+
</UiPopover>
|
|
155
155
|
</section>
|
|
156
156
|
|
|
157
157
|
<section class="system-lab__panel">
|
|
158
|
-
<
|
|
159
|
-
<
|
|
158
|
+
<UiText as="h2" size="xl" weight="bold">Identity and analytics</UiText>
|
|
159
|
+
<UiText as="p" tone="muted">
|
|
160
160
|
Google Identity and GA/GTM are config-driven, consent-aware, and replaceable at the
|
|
161
161
|
application boundary.
|
|
162
|
-
</
|
|
162
|
+
</UiText>
|
|
163
163
|
<div class="system-lab__actions system-lab__actions--stacked">
|
|
164
164
|
<GoogleSignInButton />
|
|
165
|
-
<
|
|
165
|
+
<UiButton
|
|
166
166
|
label="Track preview event"
|
|
167
167
|
color="neutral"
|
|
168
168
|
variant="outline"
|
|
@@ -173,13 +173,13 @@ useSeo({
|
|
|
173
173
|
</section>
|
|
174
174
|
|
|
175
175
|
<section class="system-lab__panel">
|
|
176
|
-
<
|
|
176
|
+
<UiText as="h2" size="xl" weight="bold">Video embed</UiText>
|
|
177
177
|
<YoutubeEmbed source="dQw4w9WgXcQ" title="Reusable YouTube embed" />
|
|
178
178
|
</section>
|
|
179
179
|
|
|
180
180
|
<section class="system-lab__panel">
|
|
181
|
-
<
|
|
182
|
-
<
|
|
181
|
+
<UiText as="h2" size="xl" weight="bold">Infinite list</UiText>
|
|
182
|
+
<UiInfiniteScroll
|
|
183
183
|
:loading="infiniteLoading"
|
|
184
184
|
:has-more="hasMoreInfiniteItems"
|
|
185
185
|
root-margin="180px 0px"
|
|
@@ -187,20 +187,20 @@ useSeo({
|
|
|
187
187
|
>
|
|
188
188
|
<ul class="system-lab__list">
|
|
189
189
|
<li v-for="item in visibleInfiniteItems" :key="item.id" class="system-lab__list-item">
|
|
190
|
-
<
|
|
191
|
-
<
|
|
190
|
+
<UiText weight="semibold">{{ item.title }}</UiText>
|
|
191
|
+
<UiText as="p" tone="muted" size="sm">{{ item.summary }}</UiText>
|
|
192
192
|
</li>
|
|
193
193
|
</ul>
|
|
194
|
-
</
|
|
194
|
+
</UiInfiniteScroll>
|
|
195
195
|
</section>
|
|
196
196
|
|
|
197
197
|
<section class="system-lab__panel">
|
|
198
|
-
<
|
|
198
|
+
<UiText as="h2" size="xl" weight="bold">Checkout status</UiText>
|
|
199
199
|
<CheckoutStatus status="pending" show-idle />
|
|
200
200
|
</section>
|
|
201
201
|
|
|
202
202
|
<section class="system-lab__panel system-lab__panel--wide">
|
|
203
|
-
<
|
|
203
|
+
<UiText as="h2" size="xl" weight="bold">Checkout summary</UiText>
|
|
204
204
|
<div class="system-lab__checkout-grid">
|
|
205
205
|
<CheckoutSummary
|
|
206
206
|
attempt-id="campaign-checkout"
|
|
@@ -209,27 +209,27 @@ useSeo({
|
|
|
209
209
|
@checkout="checkoutUrlPreview = $event"
|
|
210
210
|
/>
|
|
211
211
|
<div class="system-lab__preview">
|
|
212
|
-
<
|
|
213
|
-
<
|
|
212
|
+
<UiText as="h3" size="lg" weight="bold">Generated checkout URL</UiText>
|
|
213
|
+
<UiText as="p" tone="muted" size="sm">
|
|
214
214
|
Product apps own session creation and provider fulfillment; this layer owns the
|
|
215
215
|
reusable URL and summary frame.
|
|
216
|
-
</
|
|
216
|
+
</UiText>
|
|
217
217
|
<code>{{ checkoutUrlPreview || "Click the checkout action to generate a URL." }}</code>
|
|
218
218
|
</div>
|
|
219
219
|
</div>
|
|
220
220
|
</section>
|
|
221
221
|
|
|
222
222
|
<section class="system-lab__panel system-lab__panel--wide">
|
|
223
|
-
<
|
|
223
|
+
<UiText as="h2" size="xl" weight="bold">Text constraints</UiText>
|
|
224
224
|
<div class="system-lab__text-row">
|
|
225
|
-
<
|
|
225
|
+
<UiText truncate>
|
|
226
226
|
Long generated asset labels stay inside their container without resizing navigation or
|
|
227
227
|
checkout rows.
|
|
228
|
-
</
|
|
229
|
-
<
|
|
228
|
+
</UiText>
|
|
229
|
+
<UiText :line-clamp="2" tone="muted">
|
|
230
230
|
Multi-line campaign summaries can be clamped by the reusable text component so compact
|
|
231
231
|
cards, popovers, and tables remain stable.
|
|
232
|
-
</
|
|
232
|
+
</UiText>
|
|
233
233
|
</div>
|
|
234
234
|
</section>
|
|
235
235
|
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "{{projectName}}",
|
|
4
|
+
"profile": "nuxt-node",
|
|
5
|
+
"source": {
|
|
6
|
+
"verify": {
|
|
7
|
+
"executable": "pnpm",
|
|
8
|
+
"args": ["check"]
|
|
9
|
+
},
|
|
10
|
+
"build": {
|
|
11
|
+
"executable": "pnpm",
|
|
12
|
+
"args": ["build"]
|
|
13
|
+
},
|
|
14
|
+
"output": ".output"
|
|
15
|
+
},
|
|
16
|
+
"image": {
|
|
17
|
+
"dockerfile": "Dockerfile",
|
|
18
|
+
"context": ".",
|
|
19
|
+
"repository": "{{projectName}}",
|
|
20
|
+
"platform": "linux/amd64"
|
|
21
|
+
},
|
|
22
|
+
"environments": {
|
|
23
|
+
"production": {
|
|
24
|
+
"target": "registry-only"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineNuxtConfig } from "nuxt/config";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const environment = process.env.NUXT_PUBLIC_SITE_ENV || "development";
|
|
4
4
|
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || "https://example.com";
|
|
5
5
|
const gtmId = process.env.NUXT_PUBLIC_GTM_ID || "";
|
|
6
6
|
|
|
@@ -24,21 +24,15 @@ export default defineNuxtConfig({
|
|
|
24
24
|
alwaysRedirect: false,
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
|
-
components: [
|
|
28
|
-
{
|
|
29
|
-
path: "~/components",
|
|
30
|
-
pathPrefix: false,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
27
|
imports: {
|
|
34
28
|
dirs: ["~/composables"],
|
|
35
29
|
},
|
|
36
|
-
|
|
30
|
+
gtm: {
|
|
37
31
|
id: gtmId,
|
|
38
32
|
dataLayerName: process.env.NUXT_PUBLIC_ANALYTICS_DATA_LAYER || "dataLayer",
|
|
39
33
|
analytics: {
|
|
40
34
|
applicationId: "{{projectName}}",
|
|
41
|
-
environment
|
|
35
|
+
environment,
|
|
42
36
|
debug: process.env.NUXT_PUBLIC_ANALYTICS_DEBUG === "true",
|
|
43
37
|
consent: {
|
|
44
38
|
required: process.env.NUXT_PUBLIC_ANALYTICS_REQUIRE_CONSENT !== "false",
|
|
@@ -70,7 +64,7 @@ export default defineNuxtConfig({
|
|
|
70
64
|
},
|
|
71
65
|
site: {
|
|
72
66
|
url: process.env.NUXT_PUBLIC_SITE_URL || "",
|
|
73
|
-
|
|
67
|
+
environment,
|
|
74
68
|
name: process.env.NUXT_PUBLIC_SITE_NAME || "{{projectName}}",
|
|
75
69
|
description:
|
|
76
70
|
process.env.NUXT_PUBLIC_SITE_DESCRIPTION || "Premium product media production workspace.",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@stackonward/api-client": "^0.0.
|
|
32
|
-
"@stackonward/google-tag-manager-nuxt": "^0.
|
|
33
|
-
"@stackonward/nuxt-layer-base": "^0.0.
|
|
34
|
-
"@stackonward/nuxt-site-shell": "^0.
|
|
31
|
+
"@stackonward/api-client": "^0.0.3",
|
|
32
|
+
"@stackonward/google-tag-manager-nuxt": "^0.3.1",
|
|
33
|
+
"@stackonward/nuxt-layer-base": "^0.0.4",
|
|
34
|
+
"@stackonward/nuxt-site-shell": "^0.3.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@commitlint/cli": "^21.2.2",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"@nuxtjs/seo": "^5.3.2",
|
|
46
46
|
"@pinia/colada-nuxt": "^1.0.1",
|
|
47
47
|
"@pinia/nuxt": "^0.11.3",
|
|
48
|
-
"@stackonward/commitlint-config": "^0.1.
|
|
49
|
-
"@stackonward/eslint-config": "^1.0.
|
|
50
|
-
"@stackonward/prettier-config": "^0.0.
|
|
51
|
-
"@stackonward/stylelint-config": "^0.1.
|
|
48
|
+
"@stackonward/commitlint-config": "^0.1.1",
|
|
49
|
+
"@stackonward/eslint-config": "^1.0.1",
|
|
50
|
+
"@stackonward/prettier-config": "^0.0.3",
|
|
51
|
+
"@stackonward/stylelint-config": "^0.1.1",
|
|
52
52
|
"@types/node": "^22.0.0",
|
|
53
53
|
"@vueuse/nuxt": "^14.3.0",
|
|
54
54
|
"eslint": "^10.10.0",
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { computed, ref } from "vue";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
createSiteConsentController,
|
|
6
|
-
} from "../../app/composables/useConsent";
|
|
7
|
-
import type { ConsentState, SiteShellConsentConfig } from "../../app/types/site-shell";
|
|
3
|
+
import { ConsentLifecycleError, createConsentController } from "../../app/composables/useConsent";
|
|
4
|
+
import type { ConsentConfig, ConsentState } from "../../app/consent/types";
|
|
8
5
|
|
|
9
6
|
class ControlledStorage implements Storage {
|
|
10
7
|
readonly values = new Map<string, string>();
|
|
@@ -41,7 +38,7 @@ class ControlledStorage implements Storage {
|
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
const storageKey = "sample-analytics-consent-v1";
|
|
44
|
-
const config:
|
|
41
|
+
const config: ConsentConfig = {
|
|
45
42
|
enabled: true,
|
|
46
43
|
storageKey,
|
|
47
44
|
policyRevision: "1",
|
|
@@ -112,7 +109,7 @@ describe("site consent controller", () => {
|
|
|
112
109
|
persistence: "none",
|
|
113
110
|
error: null,
|
|
114
111
|
});
|
|
115
|
-
const controller =
|
|
112
|
+
const controller = createConsentController({
|
|
116
113
|
state,
|
|
117
114
|
config: computed(() => config),
|
|
118
115
|
analytics: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackonward/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "StackOnward CLI for creating projects and installing reusable components and snippets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,8 +36,10 @@
|
|
|
36
36
|
"ora": "^8.0.0",
|
|
37
37
|
"prompts": "^2.4.2",
|
|
38
38
|
"zod": "4",
|
|
39
|
-
"@stackonward/
|
|
40
|
-
"@stackonward/
|
|
39
|
+
"@stackonward/api-client": "0.0.3",
|
|
40
|
+
"@stackonward/google-tag-manager": "0.2.1",
|
|
41
|
+
"@stackonward/provisioning": "0.1.0",
|
|
42
|
+
"@stackonward/onedev-provisioning": "0.1.0"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
const LazyMarketingHeader = defineLazyHydrationComponent(
|
|
3
|
-
"idle",
|
|
4
|
-
() => import("./MarketingHeader.vue"),
|
|
5
|
-
);
|
|
6
|
-
const LazyMarketingFooter = defineLazyHydrationComponent(
|
|
7
|
-
"visible",
|
|
8
|
-
() => import("./MarketingFooter.vue"),
|
|
9
|
-
);
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<template>
|
|
13
|
-
<div class="marketing-shell">
|
|
14
|
-
<slot name="header">
|
|
15
|
-
<LazyMarketingHeader :hydrate-on-idle="800" />
|
|
16
|
-
</slot>
|
|
17
|
-
|
|
18
|
-
<main id="site-content" class="marketing-shell__main">
|
|
19
|
-
<slot />
|
|
20
|
-
</main>
|
|
21
|
-
|
|
22
|
-
<slot name="footer">
|
|
23
|
-
<LazyMarketingFooter hydrate-on-visible />
|
|
24
|
-
</slot>
|
|
25
|
-
|
|
26
|
-
<div class="site-floating-stack">
|
|
27
|
-
<BackToTop />
|
|
28
|
-
<slot name="consent">
|
|
29
|
-
<CookieConsent />
|
|
30
|
-
</slot>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef, DeepReadonly, Ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export interface LocalizedNavigationItemConfig {
|
|
4
|
-
readonly labelKey: string;
|
|
5
|
-
readonly to?: string;
|
|
6
|
-
readonly external?: boolean;
|
|
7
|
-
readonly descriptionKey?: string;
|
|
8
|
-
readonly children?: readonly LocalizedNavigationItemConfig[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface MarketingNavigationItem {
|
|
12
|
-
readonly label: string;
|
|
13
|
-
readonly to?: string;
|
|
14
|
-
readonly external?: boolean;
|
|
15
|
-
readonly description?: string;
|
|
16
|
-
readonly children?: readonly MarketingNavigationItem[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface AppNavigationItemConfig {
|
|
20
|
-
readonly labelKey: string;
|
|
21
|
-
readonly to: string;
|
|
22
|
-
readonly icon?: string;
|
|
23
|
-
readonly badge?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AppNavigationItem extends Omit<AppNavigationItemConfig, "labelKey"> {
|
|
27
|
-
readonly label: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface NavigationGroupConfig {
|
|
31
|
-
readonly labelKey?: string;
|
|
32
|
-
readonly items: readonly AppNavigationItemConfig[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface NavigationGroup {
|
|
36
|
-
readonly label?: string;
|
|
37
|
-
readonly items: readonly AppNavigationItem[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface FooterGroupConfig {
|
|
41
|
-
readonly titleKey: string;
|
|
42
|
-
readonly links: readonly LocalizedNavigationItemConfig[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface FooterGroup {
|
|
46
|
-
readonly title: string;
|
|
47
|
-
readonly links: readonly MarketingNavigationItem[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface SocialLinkConfig {
|
|
51
|
-
readonly labelKey: string;
|
|
52
|
-
readonly href: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface SocialLink {
|
|
56
|
-
readonly label: string;
|
|
57
|
-
readonly href: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface ContentListItem {
|
|
61
|
-
readonly title: string;
|
|
62
|
-
readonly description?: string;
|
|
63
|
-
readonly to: string;
|
|
64
|
-
readonly category?: string;
|
|
65
|
-
readonly date?: string;
|
|
66
|
-
readonly author?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ContentArticle {
|
|
70
|
-
readonly title: string;
|
|
71
|
-
readonly description?: string;
|
|
72
|
-
readonly category?: string;
|
|
73
|
-
readonly author?: string;
|
|
74
|
-
readonly date?: string;
|
|
75
|
-
readonly updatedAt?: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface StorageDisclosureConfig {
|
|
79
|
-
readonly name: string;
|
|
80
|
-
readonly category: "necessary" | "preferences" | "analytics";
|
|
81
|
-
readonly purposeKey: string;
|
|
82
|
-
readonly retentionKey: string;
|
|
83
|
-
readonly providerKey: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface SiteShellConsentConfig {
|
|
87
|
-
readonly enabled: boolean;
|
|
88
|
-
readonly storageKey: string;
|
|
89
|
-
readonly policyRevision: string;
|
|
90
|
-
readonly retentionDays: number;
|
|
91
|
-
readonly policyPath: string;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface SiteShellConfig {
|
|
95
|
-
readonly name: string;
|
|
96
|
-
readonly descriptionKey: string;
|
|
97
|
-
readonly url: string;
|
|
98
|
-
readonly defaultLocale: string;
|
|
99
|
-
readonly homePath: string;
|
|
100
|
-
readonly appPath: string;
|
|
101
|
-
readonly logoText: string;
|
|
102
|
-
readonly primaryActionLabelKey: string;
|
|
103
|
-
readonly secondaryActionLabelKey: string;
|
|
104
|
-
readonly secondaryActionPath: string;
|
|
105
|
-
readonly consent: SiteShellConsentConfig;
|
|
106
|
-
readonly storageDisclosures: readonly StorageDisclosureConfig[];
|
|
107
|
-
readonly navigation: {
|
|
108
|
-
readonly marketing: readonly LocalizedNavigationItemConfig[];
|
|
109
|
-
readonly app: readonly NavigationGroupConfig[];
|
|
110
|
-
readonly footer: readonly FooterGroupConfig[];
|
|
111
|
-
readonly legal: readonly LocalizedNavigationItemConfig[];
|
|
112
|
-
readonly social: readonly SocialLinkConfig[];
|
|
113
|
-
};
|
|
114
|
-
readonly content: {
|
|
115
|
-
readonly blogPath: string;
|
|
116
|
-
readonly authorPath: string;
|
|
117
|
-
readonly previewPath: string;
|
|
118
|
-
readonly sitemapSourcePath: string;
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface ResolvedSiteNavigation {
|
|
123
|
-
readonly marketingNav: Readonly<ComputedRef<readonly MarketingNavigationItem[]>>;
|
|
124
|
-
readonly appNavGroups: Readonly<ComputedRef<readonly NavigationGroup[]>>;
|
|
125
|
-
readonly footerGroups: Readonly<ComputedRef<readonly FooterGroup[]>>;
|
|
126
|
-
readonly legalLinks: Readonly<ComputedRef<readonly MarketingNavigationItem[]>>;
|
|
127
|
-
readonly socialLinks: Readonly<ComputedRef<readonly SocialLink[]>>;
|
|
128
|
-
readonly primaryActionLabel: Readonly<ComputedRef<string>>;
|
|
129
|
-
readonly secondaryActionLabel: Readonly<ComputedRef<string>>;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export type ConsentValue = "accepted" | "rejected";
|
|
133
|
-
|
|
134
|
-
export type ConsentPersistenceError =
|
|
135
|
-
| "read-failed"
|
|
136
|
-
| "write-failed"
|
|
137
|
-
| "clear-failed"
|
|
138
|
-
| "write-and-clear-failed";
|
|
139
|
-
|
|
140
|
-
export interface ConsentState {
|
|
141
|
-
decision: ConsentValue | null;
|
|
142
|
-
ready: boolean;
|
|
143
|
-
settingsOpen: boolean;
|
|
144
|
-
persistence: "none" | "persisted" | "cleared" | "cleanup-failed" | "session-only";
|
|
145
|
-
error: ConsentPersistenceError | null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export type ConsentInitializationResult =
|
|
149
|
-
| { status: "restored"; value: ConsentValue }
|
|
150
|
-
| { status: "undecided"; cleanup: "not-required" | "cleared" }
|
|
151
|
-
| { status: "undecided"; cleanup: "failed"; error: "clear-failed" }
|
|
152
|
-
| { status: "storage-unavailable"; error: "read-failed" };
|
|
153
|
-
|
|
154
|
-
export type ConsentMutationResult =
|
|
155
|
-
| { status: "persisted"; value: ConsentValue }
|
|
156
|
-
| { status: "cleared"; value: "rejected"; error: "write-failed" }
|
|
157
|
-
| {
|
|
158
|
-
status: "session-only";
|
|
159
|
-
value: ConsentValue;
|
|
160
|
-
error: "write-failed" | "write-and-clear-failed";
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
export interface SiteConsentController {
|
|
164
|
-
readonly state: DeepReadonly<Ref<ConsentState>>;
|
|
165
|
-
readonly visible: Readonly<ComputedRef<boolean>>;
|
|
166
|
-
initialize(): ConsentInitializationResult;
|
|
167
|
-
accept(): ConsentMutationResult;
|
|
168
|
-
reject(): ConsentMutationResult;
|
|
169
|
-
openSettings(): void;
|
|
170
|
-
closeSettings(): void;
|
|
171
|
-
dispose(): void;
|
|
172
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/resources/components/nuxt/site-source/app/components/site/{SiteBrand.vue → Brand.vue}
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseModal.vue → Modal.vue}
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseText.vue → Text.vue}
RENAMED
|
File without changes
|