@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.
Files changed (111) hide show
  1. package/README.md +248 -0
  2. package/dist/index.js +14124 -635
  3. package/dist/index.js.map +1 -1
  4. package/dist/resources/components/nuxt/site-source/app/app.vue +4 -2
  5. package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +50 -50
  6. package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationHeader.vue → app/Header.vue} +3 -3
  7. package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationShell.vue → app/Shell.vue} +2 -2
  8. package/dist/resources/components/nuxt/site-source/app/components/{site/ApplicationSidebar.vue → app/Sidebar.vue} +2 -2
  9. package/dist/resources/components/nuxt/site-source/app/components/{ui/CheckoutStatus.vue → checkout/Status.vue} +8 -3
  10. package/dist/resources/components/nuxt/site-source/app/components/{ui/CheckoutSummary.vue → checkout/Summary.vue} +11 -11
  11. package/dist/resources/components/nuxt/site-source/app/components/{site/CookieConsent.vue → consent/Banner.vue} +3 -3
  12. package/dist/resources/components/nuxt/site-source/app/components/{site/ContentArticlePage.vue → content/Article.vue} +3 -3
  13. package/dist/resources/components/nuxt/site-source/app/components/{site/ContentListPage.vue → content/List.vue} +2 -2
  14. package/dist/resources/components/nuxt/site-source/app/components/{ui/GoogleSignInButton.vue → google/SignInButton.vue} +3 -3
  15. package/dist/resources/components/nuxt/site-source/app/components/{site/LocaleSwitcher.vue → locale/Menu.vue} +2 -2
  16. package/dist/resources/components/nuxt/site-source/app/components/site/{MarketingFooter.vue → Footer.vue} +8 -8
  17. package/dist/resources/components/nuxt/site-source/app/components/site/{MarketingHeader.vue → Header.vue} +27 -27
  18. package/dist/resources/components/nuxt/site-source/app/components/site/Shell.vue +29 -0
  19. package/dist/resources/components/nuxt/site-source/app/components/{site/ThemeSwitcher.vue → theme/Menu.vue} +2 -2
  20. package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseButton.vue → Button.vue} +1 -8
  21. package/dist/resources/components/nuxt/site-source/app/components/ui/InfiniteScroll.vue +2 -2
  22. package/dist/resources/components/nuxt/site-source/app/components/{ui/YoutubeEmbed.vue → youtube/Embed.vue} +10 -2
  23. package/dist/resources/components/nuxt/site-source/app/composables/useConsent.ts +8 -10
  24. package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +9 -14
  25. package/dist/resources/components/nuxt/site-source/app/composables/useSeo.ts +3 -1
  26. package/dist/resources/components/nuxt/site-source/app/composables/useSiteShell.ts +19 -22
  27. package/dist/resources/components/nuxt/site-source/app/consent/types.ts +59 -0
  28. package/dist/resources/components/nuxt/site-source/app/content/types.ts +17 -0
  29. package/dist/resources/components/nuxt/site-source/app/layouts/app.vue +2 -2
  30. package/dist/resources/components/nuxt/site-source/app/layouts/default.vue +2 -2
  31. package/dist/resources/components/nuxt/site-source/app/navigation/types.ts +68 -0
  32. package/dist/resources/components/nuxt/site-source/app/plugins/consent.ts +2 -2
  33. package/dist/resources/components/nuxt/site-source/app/site/config.ts +35 -0
  34. package/dist/resources/components/registry.json +63 -51
  35. package/dist/resources/deploy/ci/github/caller.yml +38 -0
  36. package/dist/resources/deploy/ci/onedev/import.yml +14 -0
  37. package/dist/resources/deploy/nginx/cache.conf +17 -0
  38. package/dist/resources/deploy/nginx/compression.conf +23 -0
  39. package/dist/resources/deploy/nginx/proxy.conf +15 -0
  40. package/dist/resources/deploy/profiles/go-service/compose.yaml +19 -0
  41. package/dist/resources/deploy/profiles/nuxt-node/Dockerfile +17 -0
  42. package/dist/resources/deploy/profiles/static-files/nginx/site.conf.template +55 -0
  43. package/dist/resources/deploy/profiles/static-nginx/Dockerfile +18 -0
  44. package/dist/resources/deploy/profiles/static-nginx/nginx/default.conf.template +28 -0
  45. package/dist/resources/deploy/targets/compose-host/deploy.config.example.json +114 -0
  46. package/dist/resources/deploy/traffic/nginx/stackonward-nginx-traffic +96 -0
  47. package/dist/resources/registry.json +30 -30
  48. package/dist/resources/templates/base/.claude/docs/architecture.md +4 -0
  49. package/dist/resources/templates/base/AGENTS.md +15 -0
  50. package/dist/resources/templates/base/CLAUDE.md +15 -0
  51. package/dist/resources/templates/go-backend/.dockerignore +25 -3
  52. package/dist/resources/templates/go-backend/deploy.config.json +26 -0
  53. package/dist/resources/templates/go-backend/docs/runtime-provenance.json +9 -6
  54. package/dist/resources/templates/go-backend/docs/source-provenance.json +33 -22
  55. package/dist/resources/templates/go-backend/internal/common/enums/register_locale.go +7 -0
  56. package/dist/resources/templates/go-backend/internal/common/enums/register_locale_test.go +30 -0
  57. package/dist/resources/templates/go-backend/internal/common/locale/locale.go +102 -76
  58. package/dist/resources/templates/go-backend/internal/common/locale/locale_test.go +168 -45
  59. package/dist/resources/templates/go-backend/internal/core/commandreplay/model.go +5 -5
  60. package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence.go +20 -7
  61. package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence_test.go +51 -22
  62. package/dist/resources/templates/go-backend/internal/core/commandreplay/repository.go +2 -2
  63. package/dist/resources/templates/go-backend/internal/core/commandreplay/service.go +13 -24
  64. package/dist/resources/templates/go-backend/internal/core/commandreplay/service_persistence_test.go +98 -13
  65. package/dist/resources/templates/go-backend/internal/core/commandreplay/service_test.go +61 -36
  66. package/dist/resources/templates/go-backend/internal/core/commandreplay/types.go +6 -6
  67. package/dist/resources/templates/go-backend/internal/core/integrationevent/config.go +24 -0
  68. package/dist/resources/templates/go-backend/internal/core/integrationevent/errors.go +53 -0
  69. package/dist/resources/templates/go-backend/internal/core/integrationevent/model.go +71 -0
  70. package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence.go +193 -0
  71. package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence_postgres_test.go +178 -0
  72. package/dist/resources/templates/go-backend/internal/core/integrationevent/persistence_test.go +168 -0
  73. package/dist/resources/templates/go-backend/internal/core/integrationevent/provider.go +16 -0
  74. package/dist/resources/templates/go-backend/internal/core/integrationevent/registry.go +69 -0
  75. package/dist/resources/templates/go-backend/internal/core/integrationevent/repository.go +17 -0
  76. package/dist/resources/templates/go-backend/internal/core/integrationevent/service.go +283 -0
  77. package/dist/resources/templates/go-backend/internal/core/integrationevent/service_test.go +433 -0
  78. package/dist/resources/templates/go-backend/internal/core/integrationevent/types.go +67 -0
  79. package/dist/resources/templates/go-backend/migrations/000001_infrastructure.down.sql +6 -0
  80. package/dist/resources/templates/go-backend/migrations/000001_infrastructure.up.sql +176 -16
  81. package/dist/resources/templates/go-backend/tests/infrastructure.sql +109 -6
  82. package/dist/resources/templates/nuxt/.claude/docs/stack.md +5 -1
  83. package/dist/resources/templates/nuxt/.dockerignore +19 -0
  84. package/dist/resources/templates/nuxt/README.md +3 -3
  85. package/dist/resources/templates/nuxt/app/app.config.ts +1 -1
  86. package/dist/resources/templates/nuxt/app/i18n/locales/en.json +1 -1
  87. package/dist/resources/templates/nuxt/app/pages/app/index.vue +1 -1
  88. package/dist/resources/templates/nuxt/app/pages/authors/[slug].vue +2 -2
  89. package/dist/resources/templates/nuxt/app/pages/blog/[slug].vue +4 -3
  90. package/dist/resources/templates/nuxt/app/pages/blog/category/[category].vue +2 -2
  91. package/dist/resources/templates/nuxt/app/pages/blog/index.vue +2 -2
  92. package/dist/resources/templates/nuxt/app/pages/legal/cookies.vue +3 -3
  93. package/dist/resources/templates/nuxt/app/pages/legal/privacy.vue +2 -2
  94. package/dist/resources/templates/nuxt/app/pages/legal/terms.vue +2 -2
  95. package/dist/resources/templates/nuxt/app/pages/ui/index.vue +45 -45
  96. package/dist/resources/templates/nuxt/deploy.config.json +27 -0
  97. package/dist/resources/templates/nuxt/nuxt.config.ts +4 -10
  98. package/dist/resources/templates/nuxt/package.json +8 -8
  99. package/dist/resources/templates/nuxt/test/e2e/rendering.test.ts +1 -0
  100. package/dist/resources/templates/nuxt/test/unit/consent.test.ts +4 -7
  101. package/package.json +5 -3
  102. package/dist/resources/components/nuxt/site-source/app/components/site/MarketingShell.vue +0 -33
  103. package/dist/resources/components/nuxt/site-source/app/types/site-shell.ts +0 -172
  104. /package/dist/resources/components/nuxt/site-source/app/components/{site/ConsentSettingsButton.vue → consent/Settings.vue} +0 -0
  105. /package/dist/resources/components/nuxt/site-source/app/components/{site/StaticPage.vue → content/Document.vue} +0 -0
  106. /package/dist/resources/components/nuxt/site-source/app/components/{site/ToastViewport.vue → feedback/Toast.vue} +0 -0
  107. /package/dist/resources/components/nuxt/site-source/app/components/site/{SiteBrand.vue → Brand.vue} +0 -0
  108. /package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseGradientBorder.vue → GradientBorder.vue} +0 -0
  109. /package/dist/resources/components/nuxt/site-source/app/components/ui/{BaseModal.vue → Modal.vue} +0 -0
  110. /package/dist/resources/components/nuxt/site-source/app/components/ui/{BasePopover.vue → Popover.vue} +0 -0
  111. /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
- <BaseText as="p" tone="muted" size="lg">
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
- </BaseText>
78
+ </UiText>
79
79
  </div>
80
80
 
81
81
  <div class="system-lab__grid">
82
- <BaseGradientBorder radius="10px" class="system-lab__panel system-lab__panel--featured">
82
+ <UiGradientBorder radius="10px" class="system-lab__panel system-lab__panel--featured">
83
83
  <div class="system-lab__panel-inner">
84
84
  <div>
85
- <BaseText as="h2" size="xl" weight="bold">Actions</BaseText>
86
- <BaseText as="p" tone="muted">
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
- </BaseText>
88
+ </UiText>
89
89
  </div>
90
90
  <div class="system-lab__actions">
91
- <BaseButton label="Generate set" leading-icon="i-lucide-wand-sparkles" />
92
- <BaseButton label="Compare outputs" color="neutral" variant="outline" />
93
- <BaseButton label="Review brief" variant="soft" trailing-icon="i-lucide-arrow-right" />
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
- </BaseGradientBorder>
96
+ </UiGradientBorder>
97
97
 
98
98
  <section class="system-lab__panel">
99
- <BaseText as="h2" size="xl" weight="bold">Inputs</BaseText>
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
- <BaseText as="h2" size="xl" weight="bold">Modal shell</BaseText>
109
- <BaseText as="p" tone="muted">
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
- </BaseText>
112
- <BaseButton
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
- <BaseModal
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
- <BaseText as="p" tone="muted">
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
- </BaseText>
128
+ </UiText>
129
129
  </template>
130
130
  <template #footer="{ close }">
131
- <BaseButton label="Close" color="neutral" variant="outline" @click="close" />
132
- <BaseButton label="Approve" @click="close" />
131
+ <UiButton label="Close" color="neutral" variant="outline" @click="close" />
132
+ <UiButton label="Approve" @click="close" />
133
133
  </template>
134
- </BaseModal>
134
+ </UiModal>
135
135
  </section>
136
136
 
137
137
  <section class="system-lab__panel">
138
- <BaseText as="h2" size="xl" weight="bold">Popover actions</BaseText>
139
- <BasePopover
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
- <BaseButton
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
- </BasePopover>
154
+ </UiPopover>
155
155
  </section>
156
156
 
157
157
  <section class="system-lab__panel">
158
- <BaseText as="h2" size="xl" weight="bold">Identity and analytics</BaseText>
159
- <BaseText as="p" tone="muted">
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
- </BaseText>
162
+ </UiText>
163
163
  <div class="system-lab__actions system-lab__actions--stacked">
164
164
  <GoogleSignInButton />
165
- <BaseButton
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
- <BaseText as="h2" size="xl" weight="bold">Video embed</BaseText>
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
- <BaseText as="h2" size="xl" weight="bold">Infinite list</BaseText>
182
- <InfiniteScroll
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
- <BaseText weight="semibold">{{ item.title }}</BaseText>
191
- <BaseText as="p" tone="muted" size="sm">{{ item.summary }}</BaseText>
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
- </InfiniteScroll>
194
+ </UiInfiniteScroll>
195
195
  </section>
196
196
 
197
197
  <section class="system-lab__panel">
198
- <BaseText as="h2" size="xl" weight="bold">Checkout status</BaseText>
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
- <BaseText as="h2" size="xl" weight="bold">Checkout summary</BaseText>
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
- <BaseText as="h3" size="lg" weight="bold">Generated checkout URL</BaseText>
213
- <BaseText as="p" tone="muted" size="sm">
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
- </BaseText>
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
- <BaseText as="h2" size="xl" weight="bold">Text constraints</BaseText>
223
+ <UiText as="h2" size="xl" weight="bold">Text constraints</UiText>
224
224
  <div class="system-lab__text-row">
225
- <BaseText truncate>
225
+ <UiText truncate>
226
226
  Long generated asset labels stay inside their container without resizing navigation or
227
227
  checkout rows.
228
- </BaseText>
229
- <BaseText :line-clamp="2" tone="muted">
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
- </BaseText>
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 siteEnv = process.env.NUXT_PUBLIC_SITE_ENV || "development";
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
- googleTagManager: {
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: siteEnv,
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
- env: siteEnv,
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.2",
32
- "@stackonward/google-tag-manager-nuxt": "^0.2.3",
33
- "@stackonward/nuxt-layer-base": "^0.0.3",
34
- "@stackonward/nuxt-site-shell": "^0.2.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.0",
49
- "@stackonward/eslint-config": "^1.0.0",
50
- "@stackonward/prettier-config": "^0.0.2",
51
- "@stackonward/stylelint-config": "^0.1.0",
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",
@@ -8,6 +8,7 @@ describe("application rendering", () => {
8
8
  const html = await $fetch<string>("/");
9
9
 
10
10
  expect(html).toContain("<html");
11
+ expect(html).toContain("<title>Product Media Studio - generated-nuxt</title>");
11
12
  expect(html).toContain("<main");
12
13
  });
13
14
  });
@@ -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
- ConsentLifecycleError,
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: SiteShellConsentConfig = {
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 = createSiteConsentController({
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.3",
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/google-tag-manager": "0.1.1",
40
- "@stackonward/api-client": "0.0.2"
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
- }