create-foldkit-app 0.26.0 → 0.27.1

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 (32) hide show
  1. package/README.md +12 -2
  2. package/dist/commands/create.js +28 -11
  3. package/dist/index.js +4 -1
  4. package/dist/rendering.js +20 -0
  5. package/dist/utils/files.js +20 -4
  6. package/dist/utils/packages.js +41 -12
  7. package/package.json +1 -1
  8. package/templates/package-managers/pnpm/pnpm-workspace.yaml +1 -0
  9. package/templates/rendering/ssg/README.md +61 -0
  10. package/templates/rendering/ssg/package.json +14 -0
  11. package/templates/rendering/ssg/scripts/build.mjs +40 -0
  12. package/templates/rendering/ssg/scripts/prerender.ts +71 -0
  13. package/templates/rendering/ssg/src/entry.server.ts +18 -0
  14. package/templates/rendering/ssg/src/entry.ts +28 -0
  15. package/templates/rendering/ssg/src/main.ts +176 -0
  16. package/templates/rendering/ssg/src/route.ts +20 -0
  17. package/templates/rendering/ssg/src/scene.test.ts +27 -0
  18. package/templates/rendering/ssg/src/vite-env.d.ts +19 -0
  19. package/templates/rendering/ssg/tsconfig.json +16 -0
  20. package/templates/rendering/ssg/vite.config.ts +17 -0
  21. package/templates/rendering/ssr/README.md +65 -0
  22. package/templates/rendering/ssr/package.json +14 -0
  23. package/templates/rendering/ssr/scripts/build.mjs +36 -0
  24. package/templates/rendering/ssr/server/main.ts +205 -0
  25. package/templates/rendering/ssr/src/cookie.ts +13 -0
  26. package/templates/rendering/ssr/src/entry.server.ts +50 -0
  27. package/templates/rendering/ssr/src/entry.ts +17 -0
  28. package/templates/rendering/ssr/src/main.ts +155 -0
  29. package/templates/rendering/ssr/src/scene.test.ts +43 -0
  30. package/templates/rendering/ssr/src/vite-env.d.ts +19 -0
  31. package/templates/rendering/ssr/tsconfig.json +16 -0
  32. package/templates/rendering/ssr/vite.config.ts +17 -0
@@ -0,0 +1,155 @@
1
+ import { Effect, Match as M, Schema as S } from 'effect'
2
+ import { Command, Runtime } from 'foldkit'
3
+ import { type Document, type HtmlBuilder } from 'foldkit/html'
4
+ import { m } from 'foldkit/message'
5
+ import { evo } from 'foldkit/struct'
6
+
7
+ import { Button } from '@foldkit/ui'
8
+
9
+ import { COUNT_COOKIE } from './cookie'
10
+
11
+ // MODEL
12
+
13
+ export const Model = S.Struct({
14
+ count: S.Number,
15
+ renderedAt: S.String,
16
+ renderedOn: S.Literals(['Server', 'Client']),
17
+ })
18
+ export type Model = typeof Model.Type
19
+
20
+ // FLAGS
21
+
22
+ export const Flags = S.Struct({
23
+ initialCount: S.Number,
24
+ renderedAt: S.String,
25
+ renderedOn: S.Literals(['Server', 'Client']),
26
+ })
27
+ export type Flags = typeof Flags.Type
28
+
29
+ // MESSAGE
30
+
31
+ export const ClickedDecrement = m('ClickedDecrement')
32
+ export const ClickedIncrement = m('ClickedIncrement')
33
+ export const CompletedPersistCount = m('CompletedPersistCount')
34
+
35
+ export const Message = S.Union([
36
+ ClickedDecrement,
37
+ ClickedIncrement,
38
+ CompletedPersistCount,
39
+ ])
40
+ export type Message = typeof Message.Type
41
+
42
+ // UPDATE
43
+
44
+ export const update = (
45
+ model: Model,
46
+ message: Message,
47
+ ): readonly [Model, ReadonlyArray<Command.Command<Message>>] =>
48
+ M.value(message).pipe(
49
+ M.withReturnType<
50
+ readonly [Model, ReadonlyArray<Command.Command<Message>>]
51
+ >(),
52
+ M.tagsExhaustive({
53
+ ClickedDecrement: () => {
54
+ const nextCount = model.count - 1
55
+ return [
56
+ evo(model, { count: () => nextCount }),
57
+ [PersistCount({ count: nextCount })],
58
+ ]
59
+ },
60
+ ClickedIncrement: () => {
61
+ const nextCount = model.count + 1
62
+ return [
63
+ evo(model, { count: () => nextCount }),
64
+ [PersistCount({ count: nextCount })],
65
+ ]
66
+ },
67
+ CompletedPersistCount: () => [model, []],
68
+ }),
69
+ )
70
+
71
+ // COMMAND
72
+
73
+ const COUNT_COOKIE_MAX_AGE_SECONDS = 31536000
74
+
75
+ export const PersistCount = Command.define('PersistCount', {
76
+ args: { count: S.Number },
77
+ messages: [CompletedPersistCount],
78
+ execute: ({ count }) =>
79
+ Effect.try(() => {
80
+ document.cookie = `${COUNT_COOKIE}=${count}; path=/; max-age=${COUNT_COOKIE_MAX_AGE_SECONDS}`
81
+ }).pipe(
82
+ Effect.map(() => CompletedPersistCount()),
83
+ Effect.catch(() => Effect.succeed(CompletedPersistCount())),
84
+ ),
85
+ })
86
+
87
+ // INIT
88
+
89
+ export const init: Runtime.ApplicationInit<Model, Message, Flags> = flags => [
90
+ {
91
+ count: flags.initialCount,
92
+ renderedAt: flags.renderedAt,
93
+ renderedOn: flags.renderedOn,
94
+ },
95
+ [],
96
+ ]
97
+
98
+ // VIEW
99
+
100
+ export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
101
+ title: `Count ${model.count}`,
102
+ body: h.div(
103
+ [
104
+ h.Class(
105
+ 'min-h-screen bg-white flex flex-col items-center justify-center gap-6 p-6',
106
+ ),
107
+ ],
108
+ [
109
+ h.h1(
110
+ [h.Class('text-2xl font-semibold text-gray-800')],
111
+ ['Server-rendered counter'],
112
+ ),
113
+ h.p(
114
+ [h.Id('count'), h.Class('text-6xl font-bold text-gray-800')],
115
+ [model.count.toString()],
116
+ ),
117
+ h.div(
118
+ [h.Class('flex flex-wrap justify-center gap-4')],
119
+ [
120
+ Button.view(
121
+ {
122
+ onClick: ClickedDecrement(),
123
+ toView: attributes =>
124
+ h.button([...attributes.button, h.Class(buttonStyle)], ['-']),
125
+ },
126
+ h,
127
+ ),
128
+ Button.view(
129
+ {
130
+ onClick: ClickedIncrement(),
131
+ toView: attributes =>
132
+ h.button([...attributes.button, h.Class(buttonStyle)], ['+']),
133
+ },
134
+ h,
135
+ ),
136
+ ],
137
+ ),
138
+ h.p(
139
+ [h.Id('provenance'), h.Class('text-sm text-gray-500')],
140
+ [`Rendered on the ${model.renderedOn} at ${model.renderedAt}`],
141
+ ),
142
+ h.p(
143
+ [h.Class('text-sm text-gray-500 max-w-md text-center')],
144
+ [
145
+ 'The count persists in a cookie. Reload the page and the server ' +
146
+ 'renders your latest count into the HTML before any JavaScript runs.',
147
+ ],
148
+ ),
149
+ ],
150
+ ),
151
+ })
152
+
153
+ // STYLE
154
+
155
+ const buttonStyle = 'bg-black text-white hover:bg-gray-700 px-4 py-2 transition'
@@ -0,0 +1,43 @@
1
+ import { Command, click, expect, given, role, scene, text } from 'foldkit/scene'
2
+ import { describe, test } from 'vitest'
3
+
4
+ import {
5
+ CompletedPersistCount,
6
+ Model,
7
+ PersistCount,
8
+ update,
9
+ view,
10
+ } from './main'
11
+
12
+ const initialModel = Model.make({
13
+ count: 3,
14
+ renderedAt: '2026-07-26T00:00:00.000Z',
15
+ renderedOn: 'Server',
16
+ })
17
+
18
+ describe('view', () => {
19
+ test('renders the count and provenance line', () => {
20
+ scene(
21
+ { update, view },
22
+ given(initialModel),
23
+ expect(text('3')).toExist(),
24
+ expect(
25
+ text('Rendered on the Server at 2026-07-26T00:00:00.000Z'),
26
+ ).toExist(),
27
+ expect(role('button', { name: '+' })).toExist(),
28
+ expect(role('button', { name: '-' })).toExist(),
29
+ )
30
+ })
31
+
32
+ test('clicking + increments and persists the count', () => {
33
+ scene(
34
+ { update, view },
35
+ given(initialModel),
36
+ click(role('button', { name: '+' })),
37
+ expect(text('4')).toExist(),
38
+ Command.expectHas(PersistCount({ count: 4 })),
39
+ Command.resolve(PersistCount({ count: 4 }), CompletedPersistCount()),
40
+ Command.expectNone(),
41
+ )
42
+ })
43
+ })
@@ -0,0 +1,19 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ // `@foldkit/vite-plugin` compiles the deployment's build id in here, from its
4
+ // `buildId` option or the `FOLDKIT_BUILD_ID` environment variable. The server
5
+ // entry hands it to `renderToString` and the client entry to `Runtime.hydrate`,
6
+ // which is how hydration tells a page from this deployment apart from one served
7
+ // by another.
8
+ //
9
+ // Declared as required rather than optional because this project's build always
10
+ // supplies one. A build that did not would produce `undefined` here, and both
11
+ // entries refuse it at runtime; typing it as optional would only push that
12
+ // failure past the compiler and into the served page.
13
+ interface ImportMetaEnv {
14
+ readonly FOLDKIT_BUILD_ID: string
15
+ }
16
+
17
+ interface ImportMeta {
18
+ readonly env: ImportMetaEnv
19
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "exactOptionalPropertyTypes": true,
12
+ "isolatedModules": true,
13
+ "noEmit": true
14
+ },
15
+ "include": ["src/**/*", "server/**/*"]
16
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ import { foldkit } from '@foldkit/vite-plugin'
4
+ import tailwindcss from '@tailwindcss/vite'
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ tailwindcss(),
9
+ foldkit({
10
+ devToolsMcpPort: 9988,
11
+ ssr: { serverEntry: '/src/entry.server.ts' },
12
+ }),
13
+ ],
14
+ optimizeDeps: {
15
+ entries: ['src/entry.ts'],
16
+ },
17
+ })