create-foldkit-app 0.17.0 → 0.17.2
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 +27 -16
- package/dist/commands/create.js +3 -3
- package/dist/examples.js +18 -0
- package/dist/utils/files.js +46 -14
- package/package.json +1 -1
- package/templates/package-managers/pnpm/pnpm-workspace.yaml +2 -0
package/README.md
CHANGED
|
@@ -18,22 +18,33 @@ The CLI prompts you for a project name, starter example, and package manager. Pa
|
|
|
18
18
|
|
|
19
19
|
## Examples
|
|
20
20
|
|
|
21
|
-
| Example
|
|
22
|
-
|
|
|
23
|
-
| `counter`
|
|
24
|
-
| `counters`
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
21
|
+
| Example | Description |
|
|
22
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
23
|
+
| `counter` | Simple increment/decrement with reset |
|
|
24
|
+
| `counters` | A dynamic list of Counter Submodels with per-instance routing via a wrapper Message |
|
|
25
|
+
| `todo` | CRUD operations with localStorage persistence |
|
|
26
|
+
| `stopwatch` | Timer with start/stop/reset |
|
|
27
|
+
| `crash-view` | Custom crash fallback UI with crash.view and crash.report |
|
|
28
|
+
| `slow-warnings` | Interactive lab for slow update, view, patch, and Subscription dependency warnings |
|
|
29
|
+
| `form` | Form validation with async email checking |
|
|
30
|
+
| `job-application` | Multi-step form with async validation, file uploads, and per-step error indicators |
|
|
31
|
+
| `weather` | HTTP requests with async state handling |
|
|
32
|
+
| `api-cache` | Query caching in the Model with stale-while-revalidate, request deduplication, and interval refetching |
|
|
33
|
+
| `routing` | URL routing with parser combinators and route parameters |
|
|
34
|
+
| `query-sync` | URL-driven filtering, sorting, and search with query parameters |
|
|
35
|
+
| `snake` | Classic game built with Subscriptions |
|
|
36
|
+
| `canvas-art` | Declarative 2D canvas with shapes, animation-frame Subscriptions, and pointer events |
|
|
37
|
+
| `generative-art` | Perlin-noise flow field with evolving particle trails, mouse vortex, and high-frequency Messages |
|
|
38
|
+
| `auth` | Authentication with Submodels, OutMessage, and protected routes |
|
|
39
|
+
| `shopping-cart` | Complex state management with nested Models and routing |
|
|
40
|
+
| `pixel-art` | Pixel editor with undo/redo, UI components, and localStorage persistence |
|
|
41
|
+
| `websocket-chat` | Managed resources with WebSocket integration |
|
|
42
|
+
| `managed-resource-layer` | Layer-backed ManagedResource lifecycle with an Effect service |
|
|
43
|
+
| `kanban` | Drag-and-drop board with fractional indexing, keyboard navigation, and screen reader announcements |
|
|
44
|
+
| `map` | Interactive MapLibre GL map with Mount and Subscriptions |
|
|
45
|
+
| `web-components` | QR code designer wiring third-party web components into Foldkit with CustomElement.define |
|
|
46
|
+
| `embedding` | A Foldkit widget embedded in a plain TypeScript host page with Ports and dispose |
|
|
47
|
+
| `ui-showcase` | Every Foldkit UI component with routing and Submodels |
|
|
37
48
|
|
|
38
49
|
## License
|
|
39
50
|
|
package/dist/commands/create.js
CHANGED
|
@@ -60,10 +60,10 @@ const validateProject = (name, projectPath, packageManager) => Effect.gen(functi
|
|
|
60
60
|
return yield* Effect.fail(`Package manager '${packageManager}' is not available. Please install it first.`);
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
|
-
const setupProject = (name, projectPath, example) => Effect.gen(function* () {
|
|
63
|
+
const setupProject = (name, projectPath, example, packageManager) => Effect.gen(function* () {
|
|
64
64
|
yield* Console.log(chalk.blue('🚀 Creating your Foldkit app...'));
|
|
65
65
|
yield* Console.log('');
|
|
66
|
-
yield* createProject(name, projectPath, example);
|
|
66
|
+
yield* createProject(name, projectPath, example, packageManager);
|
|
67
67
|
yield* Console.log(chalk.green(`✅ Created project`));
|
|
68
68
|
yield* Console.log('');
|
|
69
69
|
});
|
|
@@ -118,7 +118,7 @@ export const create = (input) => Effect.gen(function* () {
|
|
|
118
118
|
const path = yield* Path.Path;
|
|
119
119
|
const projectPath = path.resolve(name);
|
|
120
120
|
yield* validateProject(name, projectPath, packageManager);
|
|
121
|
-
yield* setupProject(name, projectPath, example);
|
|
121
|
+
yield* setupProject(name, projectPath, example, packageManager);
|
|
122
122
|
yield* installProjectDependencies(projectPath, packageManager, example);
|
|
123
123
|
yield* displaySuccessMessage(name, packageManager);
|
|
124
124
|
return name;
|
package/dist/examples.js
CHANGED
|
@@ -4,6 +4,7 @@ export const EXAMPLE_VALUES = [
|
|
|
4
4
|
'todo',
|
|
5
5
|
'stopwatch',
|
|
6
6
|
'crash-view',
|
|
7
|
+
'slow-warnings',
|
|
7
8
|
'form',
|
|
8
9
|
'job-application',
|
|
9
10
|
'weather',
|
|
@@ -17,7 +18,9 @@ export const EXAMPLE_VALUES = [
|
|
|
17
18
|
'shopping-cart',
|
|
18
19
|
'pixel-art',
|
|
19
20
|
'websocket-chat',
|
|
21
|
+
'managed-resource-layer',
|
|
20
22
|
'kanban',
|
|
23
|
+
'map',
|
|
21
24
|
'web-components',
|
|
22
25
|
'embedding',
|
|
23
26
|
'ui-showcase',
|
|
@@ -48,6 +51,11 @@ export const examples = [
|
|
|
48
51
|
title: 'crash-view',
|
|
49
52
|
description: 'Custom crash fallback UI with crash.view and crash.report',
|
|
50
53
|
},
|
|
54
|
+
{
|
|
55
|
+
value: 'slow-warnings',
|
|
56
|
+
title: 'slow-warnings',
|
|
57
|
+
description: 'Interactive lab for slow update, view, patch, and Subscription dependency warnings',
|
|
58
|
+
},
|
|
51
59
|
{
|
|
52
60
|
value: 'form',
|
|
53
61
|
title: 'form',
|
|
@@ -113,11 +121,21 @@ export const examples = [
|
|
|
113
121
|
title: 'websocket-chat',
|
|
114
122
|
description: 'Managed resources with WebSocket integration',
|
|
115
123
|
},
|
|
124
|
+
{
|
|
125
|
+
value: 'managed-resource-layer',
|
|
126
|
+
title: 'managed-resource-layer',
|
|
127
|
+
description: 'Layer-backed ManagedResource lifecycle with an Effect service',
|
|
128
|
+
},
|
|
116
129
|
{
|
|
117
130
|
value: 'kanban',
|
|
118
131
|
title: 'kanban',
|
|
119
132
|
description: 'Drag-and-drop board with fractional indexing, keyboard navigation, and screen reader announcements',
|
|
120
133
|
},
|
|
134
|
+
{
|
|
135
|
+
value: 'map',
|
|
136
|
+
title: 'map',
|
|
137
|
+
description: 'Interactive MapLibre GL map with Mount and Subscriptions',
|
|
138
|
+
},
|
|
121
139
|
{
|
|
122
140
|
value: 'web-components',
|
|
123
141
|
title: 'web-components',
|
package/dist/utils/files.js
CHANGED
|
@@ -2,18 +2,21 @@ import { Array, Effect, FileSystem, Match, Option, Path, Record, Ref, Schema, St
|
|
|
2
2
|
import { HttpClient, HttpClientRequest, } from 'effect/unstable/http';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
const GITHUB_API_BASE_URL = 'https://api.github.com/repos/foldkit/foldkit/contents/examples';
|
|
5
|
-
const
|
|
6
|
-
const fs = yield* FileSystem.FileSystem;
|
|
5
|
+
const getTemplateRoot = Effect.gen(function* () {
|
|
7
6
|
const path = yield* Path.Path;
|
|
8
7
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
|
|
8
|
+
return path.resolve(currentDir, '..', '..', 'templates');
|
|
9
|
+
});
|
|
10
|
+
const getTemplateFiles = (templateDir) => Effect.gen(function* () {
|
|
11
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12
|
+
const path = yield* Path.Path;
|
|
10
13
|
const fileContentByPath = yield* Ref.make({});
|
|
11
14
|
const processEntry = (dir) => (entry) => Effect.gen(function* () {
|
|
12
15
|
const fullPath = path.join(dir, entry);
|
|
13
16
|
const stat = yield* fs.stat(fullPath);
|
|
14
17
|
yield* Match.value(stat.type).pipe(Match.when('Directory', () => processDirectory(fullPath)), Match.when('File', () => Effect.gen(function* () {
|
|
15
18
|
const content = yield* fs.readFileString(fullPath);
|
|
16
|
-
const relativePath = path.relative(
|
|
19
|
+
const relativePath = path.relative(templateDir, fullPath);
|
|
17
20
|
yield* Ref.update(fileContentByPath, files => ({
|
|
18
21
|
...files,
|
|
19
22
|
[relativePath]: content,
|
|
@@ -26,20 +29,31 @@ const getBaseFiles = Effect.gen(function* () {
|
|
|
26
29
|
concurrency: 'unbounded',
|
|
27
30
|
});
|
|
28
31
|
});
|
|
29
|
-
yield* processDirectory(
|
|
32
|
+
yield* processDirectory(templateDir);
|
|
30
33
|
return yield* Ref.get(fileContentByPath);
|
|
31
34
|
});
|
|
32
|
-
|
|
33
|
-
yield*
|
|
34
|
-
yield*
|
|
35
|
-
yield*
|
|
35
|
+
const getBaseFiles = Effect.gen(function* () {
|
|
36
|
+
const path = yield* Path.Path;
|
|
37
|
+
const templateRoot = yield* getTemplateRoot;
|
|
38
|
+
return yield* getTemplateFiles(path.join(templateRoot, 'base'));
|
|
36
39
|
});
|
|
37
|
-
const
|
|
40
|
+
const getPackageManagerFiles = (packageManager) => Effect.gen(function* () {
|
|
38
41
|
const fs = yield* FileSystem.FileSystem;
|
|
39
42
|
const path = yield* Path.Path;
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
+
const templateRoot = yield* getTemplateRoot;
|
|
44
|
+
const templateDir = path.join(templateRoot, 'package-managers', packageManager);
|
|
45
|
+
const isTemplateDirectoryPresent = yield* fs.exists(templateDir);
|
|
46
|
+
if (isTemplateDirectoryPresent) {
|
|
47
|
+
return yield* getTemplateFiles(templateDir);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const createFiles = (projectPath, files) => Effect.gen(function* () {
|
|
54
|
+
const fs = yield* FileSystem.FileSystem;
|
|
55
|
+
const path = yield* Path.Path;
|
|
56
|
+
yield* pipe(files, Record.toEntries, Effect.forEach(([filePath, content]) => Effect.gen(function* () {
|
|
43
57
|
const targetPath = Match.value(filePath).pipe(Match.when('gitignore', () => '.gitignore'), Match.when('ignore', () => '.ignore'), Match.orElse(() => filePath));
|
|
44
58
|
const fullPath = path.join(projectPath, targetPath);
|
|
45
59
|
const dirPath = path.dirname(fullPath);
|
|
@@ -47,11 +61,29 @@ const createBaseFiles = (projectPath) => Effect.gen(function* () {
|
|
|
47
61
|
yield* fs.writeFileString(fullPath, content);
|
|
48
62
|
}), { concurrency: 'unbounded' }));
|
|
49
63
|
});
|
|
64
|
+
const createBaseFiles = (projectPath) => Effect.gen(function* () {
|
|
65
|
+
const fs = yield* FileSystem.FileSystem;
|
|
66
|
+
yield* fs.makeDirectory(projectPath, { recursive: true });
|
|
67
|
+
const baseFiles = yield* getBaseFiles;
|
|
68
|
+
yield* createFiles(projectPath, baseFiles);
|
|
69
|
+
});
|
|
70
|
+
const createPackageManagerFiles = (projectPath, packageManager) => Effect.gen(function* () {
|
|
71
|
+
const packageManagerFiles = yield* getPackageManagerFiles(packageManager);
|
|
72
|
+
yield* createFiles(projectPath, packageManagerFiles);
|
|
73
|
+
});
|
|
74
|
+
export const createProject = (name, projectPath, example, packageManager) => Effect.gen(function* () {
|
|
75
|
+
yield* createBaseFiles(projectPath);
|
|
76
|
+
yield* modifyBaseFiles(projectPath, name);
|
|
77
|
+
yield* createPackageManagerFiles(projectPath, packageManager);
|
|
78
|
+
yield* createExampleFiles(projectPath, example);
|
|
79
|
+
});
|
|
50
80
|
const modifyBaseFiles = (projectPath, name) => Effect.gen(function* () {
|
|
51
81
|
const fs = yield* FileSystem.FileSystem;
|
|
52
82
|
const path = yield* Path.Path;
|
|
53
83
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
54
|
-
|
|
84
|
+
const content = yield* fs.readFileString(packageJsonPath);
|
|
85
|
+
const updatedContent = String.replace('my-foldkit-app', name)(content);
|
|
86
|
+
yield* fs.writeFileString(packageJsonPath, updatedContent);
|
|
55
87
|
});
|
|
56
88
|
const GitHubFileEntry = Schema.Struct({
|
|
57
89
|
name: Schema.String,
|
package/package.json
CHANGED