create-foldkit-app 0.20.0 → 0.21.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 +1 -0
- package/dist/commands/create.js +3 -4
- package/dist/examples.js +12 -0
- package/dist/utils/files.js +9 -5
- package/dist/utils/packages.js +8 -0
- package/package.json +1 -1
- package/templates/base/.oxlintrc.json +2 -15
- package/templates/base/AGENTS.md +3 -1
- package/templates/base/README.md +2 -2
- package/templates/base/gitignore +3 -0
- package/templates/base/package.json +2 -2
- package/templates/base/vitest.config.ts +1 -0
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ The CLI prompts you for a project name, starter example, and package manager. Pa
|
|
|
38
38
|
| `generative-art` | Perlin-noise flow field with evolving particle trails, mouse vortex, and high-frequency Messages |
|
|
39
39
|
| `auth` | Authentication with Submodels, OutMessage, and protected routes |
|
|
40
40
|
| `shopping-cart` | Complex state management with nested Models and routing |
|
|
41
|
+
| `checkout-machine` | Experimental state machine checkout with guarded branches and edge Commands |
|
|
41
42
|
| `pixel-art` | Pixel editor with undo/redo, UI components, and localStorage persistence |
|
|
42
43
|
| `websocket-chat` | Managed resources with WebSocket integration |
|
|
43
44
|
| `managed-resource-layer` | Layer-backed ManagedResource lifecycle with an Effect service |
|
package/dist/commands/create.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { Console, Effect, FileSystem,
|
|
2
|
+
import { Console, Effect, FileSystem, Option, Path } from 'effect';
|
|
3
3
|
import { Prompt } from 'effect/unstable/cli';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { examples } from '../examples.js';
|
|
6
6
|
import { createProject } from '../utils/files.js';
|
|
7
|
-
import { installDependencies } from '../utils/packages.js';
|
|
7
|
+
import { devCommand, installDependencies, } from '../utils/packages.js';
|
|
8
8
|
import { validateProjectName } from '../validateName.js';
|
|
9
9
|
const isWindows = process.platform === 'win32';
|
|
10
10
|
const promptForName = Prompt.text({
|
|
@@ -73,12 +73,11 @@ const installProjectDependencies = (projectPath, packageManager, example) => Eff
|
|
|
73
73
|
yield* Console.log(chalk.green('✅ Dependencies installed'));
|
|
74
74
|
yield* Console.log('');
|
|
75
75
|
});
|
|
76
|
-
const runDevServerCommand = (packageManager) => Match.value(packageManager).pipe(Match.when('pnpm', () => 'pnpm dev'), Match.when('npm', () => 'npm run dev'), Match.when('yarn', () => 'yarn dev'), Match.when('bun', () => 'bun dev'), Match.exhaustive);
|
|
77
76
|
const displaySuccessMessage = (name, packageManager) => Effect.gen(function* () {
|
|
78
77
|
yield* Console.log(chalk.bold('All systems nominal.'));
|
|
79
78
|
yield* Console.log('');
|
|
80
79
|
yield* Console.log(` > ${chalk.cyan('cd')} ${name}`);
|
|
81
|
-
yield* Console.log(` > ${chalk.cyan(
|
|
80
|
+
yield* Console.log(` > ${chalk.cyan(devCommand(packageManager))}`);
|
|
82
81
|
yield* Console.log('');
|
|
83
82
|
yield* Console.log(chalk.bold('AI-Assisted Development'));
|
|
84
83
|
yield* Console.log('');
|
package/dist/examples.js
CHANGED
|
@@ -11,12 +11,14 @@ export const EXAMPLE_VALUES = [
|
|
|
11
11
|
'api-cache',
|
|
12
12
|
'charting',
|
|
13
13
|
'routing',
|
|
14
|
+
'route-transitions',
|
|
14
15
|
'query-sync',
|
|
15
16
|
'snake',
|
|
16
17
|
'canvas-art',
|
|
17
18
|
'generative-art',
|
|
18
19
|
'auth',
|
|
19
20
|
'shopping-cart',
|
|
21
|
+
'checkout-machine',
|
|
20
22
|
'pixel-art',
|
|
21
23
|
'websocket-chat',
|
|
22
24
|
'managed-resource-layer',
|
|
@@ -87,6 +89,11 @@ export const examples = [
|
|
|
87
89
|
title: 'routing',
|
|
88
90
|
description: 'URL routing with parser combinators and route parameters',
|
|
89
91
|
},
|
|
92
|
+
{
|
|
93
|
+
value: 'route-transitions',
|
|
94
|
+
title: 'route-transitions',
|
|
95
|
+
description: 'Live transition log with entry, exit, and stayed navigation policies',
|
|
96
|
+
},
|
|
90
97
|
{
|
|
91
98
|
value: 'query-sync',
|
|
92
99
|
title: 'query-sync',
|
|
@@ -117,6 +124,11 @@ export const examples = [
|
|
|
117
124
|
title: 'shopping-cart',
|
|
118
125
|
description: 'Complex state management with nested models and routing',
|
|
119
126
|
},
|
|
127
|
+
{
|
|
128
|
+
value: 'checkout-machine',
|
|
129
|
+
title: 'checkout-machine',
|
|
130
|
+
description: 'Checkout workflow powered by the experimental state machine module with guarded branches and edge Commands',
|
|
131
|
+
},
|
|
120
132
|
{
|
|
121
133
|
value: 'pixel-art',
|
|
122
134
|
title: 'pixel-art',
|
package/dist/utils/files.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Array, Effect, FileSystem, Match, Option, Path, Record, Ref, Schema, String, pipe, } from 'effect';
|
|
2
2
|
import { HttpClient, HttpClientRequest, } from 'effect/unstable/http';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { devCommand, installCommand } from './packages.js';
|
|
4
5
|
const GITHUB_API_BASE_URL = 'https://api.github.com/repos/foldkit/foldkit/contents/examples';
|
|
5
6
|
const getTemplateRoot = Effect.gen(function* () {
|
|
6
7
|
const path = yield* Path.Path;
|
|
@@ -73,17 +74,20 @@ const createPackageManagerFiles = (projectPath, packageManager) => Effect.gen(fu
|
|
|
73
74
|
});
|
|
74
75
|
export const createProject = (name, projectPath, example, packageManager) => Effect.gen(function* () {
|
|
75
76
|
yield* createBaseFiles(projectPath);
|
|
76
|
-
yield* modifyBaseFiles(projectPath, name);
|
|
77
|
+
yield* modifyBaseFiles(projectPath, name, packageManager);
|
|
77
78
|
yield* createPackageManagerFiles(projectPath, packageManager);
|
|
78
79
|
yield* createExampleFiles(projectPath, example);
|
|
79
80
|
});
|
|
80
|
-
const
|
|
81
|
+
export const applyPackageManager = (readme, packageManager) => pipe(readme, String.replace('{{installCommand}}', installCommand(packageManager)), String.replace('{{devCommand}}', devCommand(packageManager)));
|
|
82
|
+
const modifyBaseFiles = (projectPath, name, packageManager) => Effect.gen(function* () {
|
|
81
83
|
const fs = yield* FileSystem.FileSystem;
|
|
82
84
|
const path = yield* Path.Path;
|
|
83
85
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
const packageJson = yield* fs.readFileString(packageJsonPath);
|
|
87
|
+
yield* fs.writeFileString(packageJsonPath, String.replace('{{name}}', name)(packageJson));
|
|
88
|
+
const readmePath = path.join(projectPath, 'README.md');
|
|
89
|
+
const readme = yield* fs.readFileString(readmePath);
|
|
90
|
+
yield* fs.writeFileString(readmePath, applyPackageManager(readme, packageManager));
|
|
87
91
|
});
|
|
88
92
|
const GitHubFileEntry = Schema.Struct({
|
|
89
93
|
name: Schema.String,
|
package/dist/utils/packages.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { Array, Effect, FileSystem, Match, Order, Path, Record, Result, Schema, pipe, } from 'effect';
|
|
2
2
|
import { HttpClient, HttpClientRequest } from 'effect/unstable/http';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
+
export const installCommand = (packageManager) => `${packageManager} install`;
|
|
5
|
+
const DEV_COMMANDS = {
|
|
6
|
+
pnpm: 'pnpm dev',
|
|
7
|
+
npm: 'npm run dev',
|
|
8
|
+
yarn: 'yarn dev',
|
|
9
|
+
bun: 'bun dev',
|
|
10
|
+
};
|
|
11
|
+
export const devCommand = (packageManager) => DEV_COMMANDS[packageManager];
|
|
4
12
|
const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com/foldkit/foldkit/main/examples';
|
|
5
13
|
const NPM_REGISTRY_BASE_URL = 'https://registry.npmjs.org';
|
|
6
14
|
const FOLDKIT_SCOPE_PREFIX = '@foldkit/';
|
package/package.json
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"extends": ["./node_modules/@foldkit/oxlint-plugin/recommended.json"],
|
|
3
4
|
"plugins": ["typescript"],
|
|
4
|
-
"jsPlugins": [
|
|
5
|
-
{
|
|
6
|
-
"name": "foldkit",
|
|
7
|
-
"specifier": "@foldkit/oxlint-plugin"
|
|
8
|
-
}
|
|
9
|
-
],
|
|
10
5
|
"categories": {
|
|
11
6
|
"correctness": "off"
|
|
12
7
|
},
|
|
@@ -24,15 +19,7 @@
|
|
|
24
19
|
"typescript/consistent-type-assertions": [
|
|
25
20
|
"error",
|
|
26
21
|
{ "assertionStyle": "never" }
|
|
27
|
-
]
|
|
28
|
-
"foldkit/no-noop-message": "error",
|
|
29
|
-
"foldkit/got-submodel-message-name": "error",
|
|
30
|
-
"foldkit/message-binding-matches-tag": "error",
|
|
31
|
-
"foldkit/got-prefix-requires-submodel-payload": "error",
|
|
32
|
-
"foldkit/no-empty-object-tagged-call": "error",
|
|
33
|
-
"foldkit/prefer-callable-message-constructor": "error",
|
|
34
|
-
"foldkit/command-binding-matches-name": "error",
|
|
35
|
-
"foldkit/no-module-level-mutable-state": "error"
|
|
22
|
+
]
|
|
36
23
|
},
|
|
37
24
|
"ignorePatterns": [
|
|
38
25
|
"dist/",
|
package/templates/base/AGENTS.md
CHANGED
|
@@ -66,7 +66,9 @@ For DOM operations (focus, scroll, modals, scroll lock), Foldkit ships a `Dom` m
|
|
|
66
66
|
|
|
67
67
|
### File Organization
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
The invariant: keep the runtime boot separate from the pure definitions. `src/entry.ts` calls `Runtime.makeApplication` and `Runtime.run`, and `index.html` references it. The definitions (Model, Messages, init, update, view, Commands) never call `Runtime.run`, so they stay importable from tests without booting a runtime as a side effect. Never call `Runtime.run` from `main.ts`.
|
|
70
|
+
|
|
71
|
+
For a small app the definitions all fit in one `src/main.ts`. Split a unit into its own file when it has _both_ a distinct reason to change _and_ a name you'd give it unprompted: the pure domain core into `timer.ts` or `domain.ts`, the view into `view.ts` (or a `components/` directory), a Command's owned resource into its own module. Split on that revealed seam, not on line count alone. A file that has grown large is _evidence_ a seam has formed, so treat its size as a prompt to re-check for one. Two splits are forced: extract Messages to `message.ts` when Commands need the constructors (this breaks the cycle between `command.ts` and `main.ts`), and colocate Commands with the update that returns them. Exemplars: counter and stopwatch are a single `main.ts`; kanban splits `domain` / `command` / `message` / `model`; typing-game splits views by page.
|
|
70
72
|
|
|
71
73
|
Use uppercase section headers (`// MODEL`, `// MESSAGE`, `// INIT`, `// UPDATE`, `// COMMAND`, `// VIEW`) for wayfinding.
|
|
72
74
|
|
package/templates/base/README.md
CHANGED
package/templates/base/gitignore
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "{{name}}",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit",
|
|
10
10
|
"format": "prettier -w .",
|
|
11
11
|
"test": "vitest run",
|
|
12
|
-
"lint": "oxlint"
|
|
12
|
+
"lint": "oxlint src"
|
|
13
13
|
}
|
|
14
14
|
}
|