create-kywi-app 0.11.0 → 0.12.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/assets/agent-patterns.md +96 -10
- package/lib/templates.mjs +91 -76
- package/package.json +1 -1
package/assets/agent-patterns.md
CHANGED
|
@@ -76,7 +76,7 @@ the full proposal — ten minutes of sign-off beats re-entering fifty nodes.
|
|
|
76
76
|
| Hardcode a list (testimonials, logos, posts, products, team) | Folder of nodes + a Feed + the Feed Display module (§1) |
|
|
77
77
|
| Create one-off pages with identical field shapes | A custom content type + one node per item (§2) |
|
|
78
78
|
| Filter/group content by topic, industry, audience | Categories & tags, queried by feeds (§3) |
|
|
79
|
-
| Copy-paste a CTA/banner/snippet across pages | Save it as a reusable component (§4) |
|
|
79
|
+
| Copy-paste a CTA/banner/snippet across pages | Save it as a reusable component — linked, so one edit updates them all (§4) |
|
|
80
80
|
| Write page sections as JSX | Build them as layout sections/modules the owner can edit (§5) |
|
|
81
81
|
| Build a page as one column of full-width headings and paragraphs | Compose: section bands, columns, object modules (§5) |
|
|
82
82
|
| Put a custom module's editable copy in a `json` prop | One `text`/`textarea`/`richText` prop per field the owner edits (§5) |
|
|
@@ -179,14 +179,99 @@ Anything designed once and used on multiple pages — CTA bands, promo banners,
|
|
|
179
179
|
newsletter signup blocks, campaign snippets, "as seen in" strips — should be a
|
|
180
180
|
**reusable component**, not copy-pasted sections.
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
182
|
+
A component is a **live source**, not a snapshot. Every page that places one
|
|
183
|
+
holds a *link* to it (a `componentId` on the node), and the link is resolved on
|
|
184
|
+
every request — so writing a new definition changes every page that uses it
|
|
185
|
+
immediately, published pages included, with nothing to re-publish. That is the
|
|
186
|
+
whole point, and it is also the thing to be careful with.
|
|
187
|
+
|
|
188
|
+
**What can be one.** A single module, a whole section (band), or a personalized /
|
|
189
|
+
A-B tested band with its variants intact — the three `type` values are `module`,
|
|
190
|
+
`section` and `variantContainer`. A band's variants travel with it; which arm a
|
|
191
|
+
visitor sees is still decided per request, per placement, so the same personalized
|
|
192
|
+
component can sit on five pages and target correctly on each.
|
|
193
|
+
|
|
194
|
+
**In the editors** (front-of-site overlay or the admin Layout tab): select the
|
|
195
|
+
module, section or band → **Save as component** → name it. The selection is
|
|
196
|
+
replaced in place by a linked instance, marked with an outline and a pill naming
|
|
197
|
+
the component. Place it again from the section picker's *Components* rows.
|
|
198
|
+
|
|
199
|
+
**Editing a linked instance ends in a question.** There are no per-prop overrides
|
|
200
|
+
in v1: an instance renders its component exactly. So when you edit one and save,
|
|
201
|
+
Kywi asks, naming the blast radius ("updates 3 instances across 2 pages, including
|
|
202
|
+
published"):
|
|
203
|
+
|
|
204
|
+
- **Save and update all instances** — the edit becomes the component. Every
|
|
205
|
+
instance everywhere follows it, live.
|
|
206
|
+
- **Save to this page only** — this instance *detaches*: it keeps the edit as its
|
|
207
|
+
own independent content and stops following the component. Everything else is
|
|
208
|
+
untouched.
|
|
209
|
+
|
|
210
|
+
**Rule of thumb:** the second time you paste the same section, stop and make it a
|
|
211
|
+
component. The owner should be able to update the sitewide CTA in one place, ten
|
|
212
|
+
minutes before their webinar.
|
|
213
|
+
|
|
214
|
+
**Managing them:** *Components* in the admin nav is the library — every component
|
|
215
|
+
with the number of pages using it, rename, an editing canvas for the definition,
|
|
216
|
+
and delete. Deleting **detaches every instance first**: each page keeps rendering
|
|
217
|
+
exactly what it rendered, as its own copy, and stops following.
|
|
218
|
+
|
|
219
|
+
### Components over the API
|
|
220
|
+
|
|
221
|
+
The admin UI is one client; the endpoints are the contract. `siteId` is optional
|
|
222
|
+
and defaults to the resolved site — components are **per-site** in v1, and
|
|
223
|
+
per-site authorization applies.
|
|
224
|
+
|
|
225
|
+
1. `POST /api/v1/components` — `{ name, type, definition, moduleType?, description? }`.
|
|
226
|
+
The `definition` is the node itself: a `section` component stores the whole
|
|
227
|
+
`LayoutSection`, a `variantContainer` the whole container, a `module` component
|
|
228
|
+
stores `{ props, style }` and names its `moduleType`.
|
|
229
|
+
2. **Place it** by writing a layout that carries the link. There is no separate
|
|
230
|
+
"insert" endpoint: `PUT /api/v1/content/:type/:id/layout` with the definition's
|
|
231
|
+
own shape plus `componentId` on the node, giving the placement a fresh `id` of
|
|
232
|
+
its own. The node's inline content is the *fallback copy* — what renders if the
|
|
233
|
+
component ever stops resolving — so write the definition's content there, not a
|
|
234
|
+
placeholder.
|
|
235
|
+
|
|
236
|
+
**An `ab_test` placement must mint its own `experimentId`.** A
|
|
237
|
+
`variantContainer` definition normally carries an `experimentId` — the one the
|
|
238
|
+
band had on the page it was saved from — because `mode: 'ab_test'` without one
|
|
239
|
+
is invalid, so it cannot simply be left out of the definition. A placement that
|
|
240
|
+
names no binding of its own **inherits the definition's**. Place that component
|
|
241
|
+
on three pages and all three arms report into a *single* experiment run; a
|
|
242
|
+
`winnerId` that travels the same way locks every placement to a finished test,
|
|
243
|
+
permanently serving one arm on pages nobody tested. So when you write an
|
|
244
|
+
`ab_test` placement: set a **fresh `experimentId`** on the placement node (any
|
|
245
|
+
uuid — one run per placement is the whole model) and **never copy `winnerId`**.
|
|
246
|
+
The editors do exactly this (`buildConnectedVariantContainerNode`), and a
|
|
247
|
+
definition saved from an editor carries no `winnerId` at all; a definition
|
|
248
|
+
POSTed straight over the API can, so read what you are placing. A
|
|
249
|
+
`personalization` container has no binding and needs none.
|
|
250
|
+
3. `GET /api/v1/components/:id/usage` — where it is placed (`instances`, `pages`,
|
|
251
|
+
the page ids and node ids, and trashed pages counted separately). This is the
|
|
252
|
+
blast radius; read it *before* changing a definition.
|
|
253
|
+
4. `PUT /api/v1/components/:id` — a body with `definition` **is the propagation
|
|
254
|
+
write**: every linked instance renders the new definition on its next request.
|
|
255
|
+
A body with only `name` / `description` is a rename and changes nothing any page
|
|
256
|
+
renders.
|
|
257
|
+
5. `POST /api/v1/components/:id/detach-all` — materialize the definition into every
|
|
258
|
+
page that links it and drop the links; each page keeps rendering identically.
|
|
259
|
+
6. `DELETE /api/v1/components/:id` — refuses (409) while instances exist; add
|
|
260
|
+
`?detachAll=1` to detach-then-delete. `GET /api/v1/components?withUsage=1` folds
|
|
261
|
+
the counts into a list, in one read.
|
|
262
|
+
|
|
263
|
+
**Two refusals to design around.** A component definition may not link to another
|
|
264
|
+
component (one level only, deliberately: a nested link is invisible to usage counts
|
|
265
|
+
and the delete gate, so the editors *materialize* an inner component when you save
|
|
266
|
+
a section that contains one — the inner instance stops following its own source, and
|
|
267
|
+
the dialog says so). And a stored layout that a live layout could not hold is
|
|
268
|
+
rejected at the door, because detaching would copy it into every page that links it.
|
|
269
|
+
|
|
270
|
+
**Component writes bypass approvals in v1.** They are not routed through
|
|
271
|
+
changesets or the review workflow — a `PUT` with a definition is live on publish
|
|
272
|
+
pages the moment it returns. On a site with an editorial workflow, treat a
|
|
273
|
+
definition write as a publish, not a draft: no draft, schedule or review step
|
|
274
|
+
holds it back. Changeset integration is a follow-up.
|
|
190
275
|
|
|
191
276
|
## 5. Page layouts & modules — not hardcoded JSX
|
|
192
277
|
|
|
@@ -588,7 +673,8 @@ early, so ask at scoping time.
|
|
|
588
673
|
- Creating types and entering content before the owner signed off on a
|
|
589
674
|
content model.
|
|
590
675
|
- A hardcoded array of testimonials/logos/posts in a page component.
|
|
591
|
-
- The same CTA JSX pasted on four pages
|
|
676
|
+
- The same CTA JSX pasted on four pages — or the same section duplicated on four
|
|
677
|
+
pages in the editor, which the owner then has to update four times (§4).
|
|
592
678
|
- A `<form>` that POSTs to a hand-rolled route (or nowhere).
|
|
593
679
|
- Marketing imagery in `/public`.
|
|
594
680
|
- A "blog" that is a folder of `.mdx` files the owner can't edit.
|
package/lib/templates.mjs
CHANGED
|
@@ -54,7 +54,11 @@ function packageJson(a) {
|
|
|
54
54
|
dev: 'next dev',
|
|
55
55
|
build: 'next build',
|
|
56
56
|
start: 'next start',
|
|
57
|
-
|
|
57
|
+
// Plain `kywi migrate` (not --push): the CLI bootstraps a migration
|
|
58
|
+
// journal via `drizzle-kit generate` on first run and grows it on
|
|
59
|
+
// every schema-bearing release after (kywi-cms#141) — a fresh
|
|
60
|
+
// scaffold is journal-managed from its very first `pnpm migrate`.
|
|
61
|
+
migrate: 'kywi migrate',
|
|
58
62
|
seed: 'kywi seed',
|
|
59
63
|
},
|
|
60
64
|
dependencies: {
|
|
@@ -249,6 +253,34 @@ function tsconfig() {
|
|
|
249
253
|
) + '\n'
|
|
250
254
|
}
|
|
251
255
|
|
|
256
|
+
// ── types/css.d.ts ────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Ambient declaration for stylesheet imports.
|
|
260
|
+
*
|
|
261
|
+
* WHY THIS FILE EXISTS (found by the scaffold compile gate, kywi-cms#147 T6).
|
|
262
|
+
* `app/(site)/kywi-front-edit.tsx` pulls the admin design system in LAZILY —
|
|
263
|
+
* `await import('@kywi-software/core/admin/styles.css')` inside `next/dynamic`'s
|
|
264
|
+
* factory — so the ~4.7k-line stylesheet never enters the public browse bundle.
|
|
265
|
+
* TypeScript treats a *dynamic* import of a non-code file as an ordinary module
|
|
266
|
+
* import and reports TS2307 for it, unlike a top-level `import 'x.css'` statement,
|
|
267
|
+
* which it lets through as a resource import. Next type-checks during `next build`,
|
|
268
|
+
* so without this declaration a freshly scaffolded project failed its own build on
|
|
269
|
+
* generated code — which nothing caught, because the reference app imports that
|
|
270
|
+
* stylesheet in statement form and the scaffold's own output had never been
|
|
271
|
+
* type-checked at all.
|
|
272
|
+
*
|
|
273
|
+
* `*.module.css` is deliberately NOT declared here: Next's own types declare it
|
|
274
|
+
* with a real `classes` record, and the more specific pattern wins.
|
|
275
|
+
*/
|
|
276
|
+
function cssTypes() {
|
|
277
|
+
return `// Stylesheet imports (including the lazy \`await import('…/styles.css')\` in
|
|
278
|
+
// app/(site)/kywi-front-edit.tsx, which TypeScript checks like any other module).
|
|
279
|
+
// CSS Modules are typed by Next itself — the more specific pattern wins.
|
|
280
|
+
declare module '*.css'
|
|
281
|
+
`
|
|
282
|
+
}
|
|
283
|
+
|
|
252
284
|
// ── next.config.mjs ───────────────────────────────────────────────────────────
|
|
253
285
|
|
|
254
286
|
function nextConfig() {
|
|
@@ -408,16 +440,19 @@ function libSite() {
|
|
|
408
440
|
import type {
|
|
409
441
|
LayoutDocument,
|
|
410
442
|
RegionNode,
|
|
411
|
-
|
|
443
|
+
VariantContainer,
|
|
412
444
|
FeedItemsResolver,
|
|
413
|
-
ComponentResolver,
|
|
414
|
-
SectionComponentResolver,
|
|
415
445
|
PersonalizationState,
|
|
416
446
|
} from '@kywi-software/core/layout'
|
|
417
|
-
import {
|
|
447
|
+
import {
|
|
448
|
+
isLayoutSection,
|
|
449
|
+
collectComponentRefs,
|
|
450
|
+
componentDetachSource,
|
|
451
|
+
resolveComponentPlacements,
|
|
452
|
+
applyPageVariant,
|
|
453
|
+
} from '@kywi-software/core/layout'
|
|
418
454
|
import { resolveContentByPath, normalizePath } from '@kywi-software/core/nav'
|
|
419
455
|
import { getFeedBySlug, getComponentById, resolveLocaleFromRequest } from '@kywi-software/core'
|
|
420
|
-
import { resolveComponentDefinition, resolveSectionComponentDefinition } from '@kywi-software/core/admin/server'
|
|
421
456
|
import {
|
|
422
457
|
evaluateActiveAudiences,
|
|
423
458
|
getSelfIdWidgetConfig,
|
|
@@ -581,77 +616,48 @@ export function buildFeedResolver(runtime: KywiRuntime): FeedItemsResolver {
|
|
|
581
616
|
}
|
|
582
617
|
}
|
|
583
618
|
|
|
584
|
-
// ───
|
|
585
|
-
|
|
586
|
-
function* moduleNodesInRegions(regions: Record<string, RegionNode[]>): Generator<ModuleNode> {
|
|
587
|
-
for (const nodes of Object.values(regions)) {
|
|
588
|
-
for (const node of nodes) {
|
|
589
|
-
if (isLayoutSection(node)) {
|
|
590
|
-
for (const col of node.columns) for (const m of col.nodes) yield m
|
|
591
|
-
} else {
|
|
592
|
-
for (const s of node.defaultSections) for (const col of s.columns) for (const m of col.nodes) yield m
|
|
593
|
-
for (const v of node.variants) for (const s of v.sections) for (const col of s.columns) for (const m of col.nodes) yield m
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
619
|
+
// ─── Linked components: server-side pre-resolution (#46, #69, #147) ──────────
|
|
598
620
|
|
|
599
|
-
/**
|
|
600
|
-
|
|
601
|
-
* return a synchronous {@link ComponentResolver} over that snapshot — the
|
|
602
|
-
* renderer resolves components during render, so they must be resolved up front.
|
|
603
|
-
* Returns undefined when the layout references no components (skip the prop).
|
|
604
|
-
*/
|
|
605
|
-
export async function buildComponentResolver(
|
|
606
|
-
layout: LayoutDocument,
|
|
607
|
-
runtime: KywiRuntime,
|
|
608
|
-
): Promise<ComponentResolver | undefined> {
|
|
609
|
-
const ids = new Set<string>()
|
|
610
|
-
for (const node of moduleNodesInRegions(layout.regions)) {
|
|
611
|
-
if (node.componentId) ids.add(node.componentId)
|
|
612
|
-
}
|
|
613
|
-
if (ids.size === 0) return undefined
|
|
621
|
+
/** Fetch a set of component rows once and index them by id. */
|
|
622
|
+
async function loadComponents(ids: string[], runtime: KywiRuntime) {
|
|
614
623
|
const { db, siteId } = runtime
|
|
615
624
|
const entries = await Promise.all(
|
|
616
|
-
|
|
625
|
+
ids.map(async (id) => [id, await getComponentById(db, id, siteId)] as const),
|
|
617
626
|
)
|
|
618
|
-
|
|
619
|
-
return (componentId) => resolveComponentDefinition(map.get(componentId) ?? null)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/** Yield every section-level component reference (\`section.componentId\`) in the
|
|
623
|
-
* layout, descending into variantContainer default + variant sections (#69). */
|
|
624
|
-
function* sectionComponentIds(regions: Record<string, RegionNode[]>): Generator<string> {
|
|
625
|
-
for (const nodes of Object.values(regions)) {
|
|
626
|
-
for (const node of nodes) {
|
|
627
|
-
if (isLayoutSection(node)) {
|
|
628
|
-
if (node.componentId) yield node.componentId
|
|
629
|
-
} else {
|
|
630
|
-
for (const s of node.defaultSections) if (s.componentId) yield s.componentId
|
|
631
|
-
for (const v of node.variants) for (const s of v.sections) if (s.componentId) yield s.componentId
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
627
|
+
return new Map(entries)
|
|
635
628
|
}
|
|
636
629
|
|
|
637
630
|
/**
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
631
|
+
* Resolve every linked component (\`componentId\`) the layout places, ON THE
|
|
632
|
+
* SERVER, before the document reaches the renderer.
|
|
633
|
+
*
|
|
634
|
+
* This is the ONLY way a Server Component can render linked components.
|
|
635
|
+
* \`KywiLayout\` is a client component and its \`componentResolver\` /
|
|
636
|
+
* \`sectionComponentResolver\` / \`variantContainerComponentResolver\` props are
|
|
637
|
+
* FUNCTIONS: passing them from here throws "Functions cannot be passed directly
|
|
638
|
+
* to Client Components" and the page 500s. Pre-resolution hands the renderer a
|
|
639
|
+
* plain document instead — only data crosses the boundary — and produces exactly
|
|
640
|
+
* the markup the resolvers were meant to produce, \`data-component-id\` included.
|
|
641
|
+
*
|
|
642
|
+
* \`collectComponentRefs\` is the engine's own link walker, the same one the
|
|
643
|
+
* instance index and detach-all use, so this never falls behind on where a link
|
|
644
|
+
* may live. It also knows that a connected section's inline columns are fallback
|
|
645
|
+
* copy rather than placements, so links buried in one are not fetched — they
|
|
646
|
+
* only render when that section's own component has gone missing.
|
|
647
|
+
*
|
|
648
|
+
* Definitions are read per request with no cache, so editing a component is live
|
|
649
|
+
* on the next page view.
|
|
642
650
|
*/
|
|
643
|
-
export async function
|
|
651
|
+
export async function resolveLayoutComponents(
|
|
644
652
|
layout: LayoutDocument,
|
|
645
653
|
runtime: KywiRuntime,
|
|
646
|
-
): Promise<
|
|
647
|
-
const
|
|
648
|
-
if (
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
654
|
+
): Promise<LayoutDocument> {
|
|
655
|
+
const refs = collectComponentRefs(layout)
|
|
656
|
+
if (refs.length === 0) return layout
|
|
657
|
+
const map = await loadComponents([...new Set(refs.map((ref) => ref.componentId))], runtime)
|
|
658
|
+
return resolveComponentPlacements(layout, (componentId) =>
|
|
659
|
+
componentDetachSource(map.get(componentId) ?? null),
|
|
652
660
|
)
|
|
653
|
-
const map = new Map(entries)
|
|
654
|
-
return (componentId) => resolveSectionComponentDefinition(map.get(componentId) ?? null)
|
|
655
661
|
}
|
|
656
662
|
|
|
657
663
|
// ─── Personalization + experiments (#50) ─────────────────────────────────────
|
|
@@ -716,7 +722,7 @@ export async function resolvePersonalization(
|
|
|
716
722
|
}
|
|
717
723
|
}
|
|
718
724
|
|
|
719
|
-
function* variantContainersInRegions(regions: Record<string, RegionNode[]>): Generator<
|
|
725
|
+
function* variantContainersInRegions(regions: Record<string, RegionNode[]>): Generator<VariantContainer> {
|
|
720
726
|
for (const nodes of Object.values(regions)) {
|
|
721
727
|
for (const node of nodes) {
|
|
722
728
|
if (!isLayoutSection(node)) yield node
|
|
@@ -738,10 +744,17 @@ async function resolveExperimentAssignments(
|
|
|
738
744
|
visitorId: string,
|
|
739
745
|
): Promise<Record<string, string | null>> {
|
|
740
746
|
if (!layout) return {}
|
|
741
|
-
|
|
747
|
+
// Pre-resolve FIRST, then read the containers. A container component saved
|
|
748
|
+
// with \`mode: 'ab_test'\` keeps its experiment in the DEFINITION; a placement
|
|
749
|
+
// that carries only a \`componentId\` would otherwise look like a plain
|
|
750
|
+
// container to this pass, no assignment would be made, and every visitor would
|
|
751
|
+
// see the Default arm of a live experiment. After pre-resolution there are no
|
|
752
|
+
// unresolved placements left to special-case — the containers here are the
|
|
753
|
+
// containers that render.
|
|
754
|
+
const resolved = await resolveLayoutComponents(applyPageVariant(layout, audienceId), runtime)
|
|
742
755
|
const experimentIds = new Set<string>()
|
|
743
756
|
for (const node of variantContainersInRegions(resolved.regions)) {
|
|
744
|
-
if (
|
|
757
|
+
if (node.mode === 'ab_test' && !node.winnerId && node.experimentId) {
|
|
745
758
|
experimentIds.add(node.experimentId)
|
|
746
759
|
}
|
|
747
760
|
}
|
|
@@ -1346,8 +1359,7 @@ import {
|
|
|
1346
1359
|
clientRuntimeEnabled,
|
|
1347
1360
|
transparencyNoticeEnabled,
|
|
1348
1361
|
buildFeedResolver,
|
|
1349
|
-
|
|
1350
|
-
buildSectionComponentResolver,
|
|
1362
|
+
resolveLayoutComponents,
|
|
1351
1363
|
localeAlternates,
|
|
1352
1364
|
mediaUrl,
|
|
1353
1365
|
requestBaseUrl,
|
|
@@ -1499,12 +1511,14 @@ export default async function PublicPage({ params, searchParams }: Params & Sear
|
|
|
1499
1511
|
// (menuSlug → a real menu; the tree otherwise) — the same seam as feeds,
|
|
1500
1512
|
// so a nav module dropped into any region/section just works (#112).
|
|
1501
1513
|
const currentPath = String(node['path'] ?? '/')
|
|
1502
|
-
const
|
|
1514
|
+
const navHydrated = await hydrateLayoutNav(
|
|
1503
1515
|
feedsHydrated,
|
|
1504
1516
|
runtime.scope.menus.createHydrationResolver(runtime.siteId, currentPath),
|
|
1505
1517
|
)
|
|
1506
|
-
|
|
1507
|
-
|
|
1518
|
+
// Linked components resolve HERE, on the server, into the document itself:
|
|
1519
|
+
// KywiLayout is a client component and the renderer's resolver props are
|
|
1520
|
+
// functions, which cannot cross the RSC boundary (#147).
|
|
1521
|
+
const hydrated = await resolveLayoutComponents(navHydrated, runtime)
|
|
1508
1522
|
content = (
|
|
1509
1523
|
<article className="page page--layout" data-kywi-content-id={contentId}>
|
|
1510
1524
|
{head}
|
|
@@ -1512,8 +1526,6 @@ export default async function PublicPage({ params, searchParams }: Params & Sear
|
|
|
1512
1526
|
layout={hydrated}
|
|
1513
1527
|
personalization={perso.personalization}
|
|
1514
1528
|
moduleComponents={moduleComponents}
|
|
1515
|
-
{...(componentResolver ? { componentResolver } : {})}
|
|
1516
|
-
{...(sectionComponentResolver ? { sectionComponentResolver } : {})}
|
|
1517
1529
|
>
|
|
1518
1530
|
{Object.keys(hydrated.regions).map((name) => (
|
|
1519
1531
|
<KywiRegion key={name} name={name} />
|
|
@@ -2694,6 +2706,9 @@ export function buildFileSet(answers) {
|
|
|
2694
2706
|
'package.json': packageJson(answers),
|
|
2695
2707
|
'kywi.config.ts': kywiConfig(answers),
|
|
2696
2708
|
'tsconfig.json': tsconfig(),
|
|
2709
|
+
// Ambient module declarations the generated code needs in order to type-check
|
|
2710
|
+
// (see `cssTypes`). Every mode: the admin catch-all imports the stylesheet too.
|
|
2711
|
+
'types/css.d.ts': cssTypes(),
|
|
2697
2712
|
'next.config.mjs': nextConfig(),
|
|
2698
2713
|
'.env.example': envExample(),
|
|
2699
2714
|
'.gitignore': gitignore(),
|
package/package.json
CHANGED