create-creek-app 0.0.0-dev.0 → 0.1.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 +220 -0
- package/dist/apply-data.d.ts +5 -0
- package/dist/apply-data.js +33 -0
- package/dist/fetch.d.ts +20 -0
- package/dist/fetch.js +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +171 -0
- package/dist/prompts.d.ts +8 -0
- package/dist/prompts.js +29 -0
- package/dist/scaffold.d.ts +14 -0
- package/dist/scaffold.js +82 -0
- package/dist/templates.d.ts +39 -0
- package/dist/templates.js +12 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.js +20 -0
- package/package.json +40 -2
- package/bin/create-creek-app.js +0 -3
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# create-creek-app
|
|
2
|
+
|
|
3
|
+
Scaffold a new [Creek](https://creek.dev) project from a template.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-creek-app
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Interactive (human)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx create-creek-app
|
|
15
|
+
# prompts: template → project name → scaffold → install → git init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Non-interactive (agent / CI)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx create-creek-app my-blog --template blog --data '{"name":"Alice"}' --yes
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Template discovery
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# List all templates (JSON)
|
|
28
|
+
npx create-creek-app --list
|
|
29
|
+
|
|
30
|
+
# Print a template's JSON Schema
|
|
31
|
+
npx create-creek-app --template landing --schema
|
|
32
|
+
|
|
33
|
+
# Validate data before scaffolding
|
|
34
|
+
npx create-creek-app --template landing --validate --data '{"theme":"dark"}'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Third-party templates
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx create-creek-app --template github:user/my-template
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
npx create-creek-app [dir] [options]
|
|
47
|
+
|
|
48
|
+
dir Project directory (default: prompted or "my-creek-app")
|
|
49
|
+
|
|
50
|
+
-t, --template <name|github:user/repo> Template to use
|
|
51
|
+
--data <json> JSON data for template params
|
|
52
|
+
--data-file <path> JSON file for template params
|
|
53
|
+
--list List available templates (JSON)
|
|
54
|
+
--schema Print template JSON Schema
|
|
55
|
+
--validate Validate data against schema
|
|
56
|
+
--registry <url> Private template registry (enterprise)
|
|
57
|
+
-y, --yes Skip prompts, use defaults
|
|
58
|
+
--no-install Skip dependency installation
|
|
59
|
+
--no-git Skip git init
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Templates
|
|
63
|
+
|
|
64
|
+
| Template | Description | Capabilities |
|
|
65
|
+
|----------|-------------|-------------|
|
|
66
|
+
| `blank` | Minimal Creek project (no UI) | — |
|
|
67
|
+
| `landing` | Landing page with hero and CTA | — |
|
|
68
|
+
| `blog` | Blog with posts | D1 |
|
|
69
|
+
| `link-in-bio` | Social links page | — |
|
|
70
|
+
| `api` | REST API with Hono | D1 |
|
|
71
|
+
| `todo` | Realtime todo app | D1, Realtime |
|
|
72
|
+
| `dashboard` | Data dashboard | D1, Realtime |
|
|
73
|
+
| `form` | Form collector | D1 |
|
|
74
|
+
| `chatbot` | AI chatbot | D1, AI |
|
|
75
|
+
|
|
76
|
+
## Template Schema
|
|
77
|
+
|
|
78
|
+
Each template defines customizable parameters via [JSON Schema](https://json-schema.org/) in `creek-template.json`. This enables both human prompts and programmatic validation.
|
|
79
|
+
|
|
80
|
+
### creek-template.json
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"name": "landing",
|
|
85
|
+
"description": "Landing page with hero, features, and CTA",
|
|
86
|
+
"capabilities": [],
|
|
87
|
+
"thumbnail": "thumbnail.png",
|
|
88
|
+
"screenshot": "screenshot.png",
|
|
89
|
+
"schema": {
|
|
90
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": {
|
|
93
|
+
"title": { "type": "string", "default": "My Product" },
|
|
94
|
+
"tagline": { "type": "string", "default": "Ship faster with Creek" },
|
|
95
|
+
"theme": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"enum": ["light", "dark"],
|
|
98
|
+
"default": "dark"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Fields
|
|
106
|
+
|
|
107
|
+
| Field | Type | Required | Description |
|
|
108
|
+
|-------|------|----------|-------------|
|
|
109
|
+
| `name` | string | yes | Template identifier |
|
|
110
|
+
| `description` | string | yes | Short description |
|
|
111
|
+
| `capabilities` | string[] | yes | Required Creek resources: `d1`, `kv`, `r2`, `ai`, `realtime` |
|
|
112
|
+
| `thumbnail` | string | no | Relative path to thumbnail image (400x300 recommended) |
|
|
113
|
+
| `screenshot` | string | no | Relative path to full screenshot image |
|
|
114
|
+
| `schema` | object | no | JSON Schema defining customizable parameters |
|
|
115
|
+
|
|
116
|
+
### creek-data.json
|
|
117
|
+
|
|
118
|
+
Default values for the schema parameters. The app reads this file at runtime — no build-time string replacement.
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"title": "My Product",
|
|
123
|
+
"tagline": "Ship faster with Creek",
|
|
124
|
+
"theme": "dark"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Validation
|
|
129
|
+
|
|
130
|
+
Data is validated against the template's JSON Schema using [ajv](https://ajv.js.org/). Validation runs automatically during scaffold and can be triggered independently with `--validate`.
|
|
131
|
+
|
|
132
|
+
### Supported constraints
|
|
133
|
+
|
|
134
|
+
- **type** — `string`, `number`, `integer`, `boolean`, `array`, `object`
|
|
135
|
+
- **enum** — fixed set of allowed values
|
|
136
|
+
- **default** — applied when the property is omitted
|
|
137
|
+
- **required** — within object items (array entries, nested objects)
|
|
138
|
+
- **items** — schema for array elements
|
|
139
|
+
- **nested objects** — full recursive validation
|
|
140
|
+
|
|
141
|
+
### Validation examples
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Valid — passes
|
|
145
|
+
npx create-creek-app --template landing --validate --data '{"theme":"dark"}'
|
|
146
|
+
# → { "valid": true, "errors": [] }
|
|
147
|
+
|
|
148
|
+
# Invalid enum — fails
|
|
149
|
+
npx create-creek-app --template landing --validate --data '{"theme":"invalid"}'
|
|
150
|
+
# → { "valid": false, "errors": [{ "path": "/theme", "message": "must be equal to one of the allowed values" }] }
|
|
151
|
+
|
|
152
|
+
# Wrong type — fails
|
|
153
|
+
npx create-creek-app --template landing --validate --data '{"title":123}'
|
|
154
|
+
# → { "valid": false, "errors": [{ "path": "/title", "message": "must be string" }] }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Creating Custom Templates
|
|
158
|
+
|
|
159
|
+
A Creek template is a directory with at minimum a `package.json` and `creek.toml`. Add `creek-template.json` for schema-driven customization.
|
|
160
|
+
|
|
161
|
+
### Template structure
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
my-template/
|
|
165
|
+
├── creek-template.json ← schema + metadata (optional)
|
|
166
|
+
├── creek-data.json ← default values (optional)
|
|
167
|
+
├── creek.toml ← Creek project config
|
|
168
|
+
├── package.json
|
|
169
|
+
├── src/ ← your code
|
|
170
|
+
├── worker/index.ts ← edge worker (if needed)
|
|
171
|
+
└── _gitignore ← renamed to .gitignore on scaffold
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Best practices
|
|
175
|
+
|
|
176
|
+
1. **Use JSON Schema defaults** — every property in the schema should have a `default` so the template works without any `--data`
|
|
177
|
+
2. **Read config at runtime** — import `creek-data.json` in your code instead of using build-time placeholders. This lets users customize after scaffold by editing one file
|
|
178
|
+
3. **Keep schemas flat** — prefer top-level string/number/boolean properties. Use arrays only for naturally repeated structures (e.g., feature lists)
|
|
179
|
+
4. **Use `enum` for constrained choices** — themes, layouts, color schemes. Agents can discover valid options via `--schema`
|
|
180
|
+
5. **Name the `name` property** — include a `name` property in your schema. `create-creek-app` auto-populates it from the project directory
|
|
181
|
+
6. **Add a thumbnail** — 400x300 PNG for template gallery display. Optional, but recommended for visual templates
|
|
182
|
+
7. **Add a screenshot** — full-page screenshot showing the template in action. Optional
|
|
183
|
+
8. **Use `_gitignore`** — npm strips `.gitignore` from published packages. Use `_gitignore` and it will be renamed automatically
|
|
184
|
+
|
|
185
|
+
### Publishing
|
|
186
|
+
|
|
187
|
+
Host your template on GitHub and use it directly:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npx create-creek-app --template github:yourname/my-template
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Or submit it to the [Creek template gallery](https://github.com/solcreek/templates) via pull request.
|
|
194
|
+
|
|
195
|
+
## Agent Workflow
|
|
196
|
+
|
|
197
|
+
`create-creek-app` is designed for both humans and AI agents. Typical agent flow:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# 1. Discover templates
|
|
201
|
+
npx create-creek-app --list
|
|
202
|
+
|
|
203
|
+
# 2. Read schema for chosen template
|
|
204
|
+
npx create-creek-app --template landing --schema
|
|
205
|
+
|
|
206
|
+
# 3. Validate generated data
|
|
207
|
+
npx create-creek-app --template landing --validate --data '{"title":"Acme","theme":"dark"}'
|
|
208
|
+
|
|
209
|
+
# 4. Scaffold
|
|
210
|
+
npx create-creek-app my-site --template landing --data '{"title":"Acme"}' --yes
|
|
211
|
+
|
|
212
|
+
# 5. Deploy
|
|
213
|
+
cd my-site && npx creek deploy
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
All discovery and validation commands output JSON to stdout for programmatic consumption.
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
Apache-2.0
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge user-supplied data into creek-data.json (and update creek.toml project name).
|
|
3
|
+
* `defaults` is what was already in the template's creek-data.json.
|
|
4
|
+
*/
|
|
5
|
+
export declare function applyData(dir: string, userData: Record<string, unknown>, defaults: Record<string, unknown>): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
/**
|
|
4
|
+
* Merge user-supplied data into creek-data.json (and update creek.toml project name).
|
|
5
|
+
* `defaults` is what was already in the template's creek-data.json.
|
|
6
|
+
*/
|
|
7
|
+
export function applyData(dir, userData, defaults) {
|
|
8
|
+
const merged = { ...defaults, ...userData };
|
|
9
|
+
// Write merged creek-data.json
|
|
10
|
+
const dataPath = join(dir, "creek-data.json");
|
|
11
|
+
writeFileSync(dataPath, JSON.stringify(merged, null, 2) + "\n");
|
|
12
|
+
// If "name" was provided, also update creek.toml and package.json
|
|
13
|
+
if (typeof merged.name === "string") {
|
|
14
|
+
updateCreekTomlName(dir, merged.name);
|
|
15
|
+
updatePackageJsonName(dir, merged.name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function updateCreekTomlName(dir, name) {
|
|
19
|
+
const tomlPath = join(dir, "creek.toml");
|
|
20
|
+
if (!existsSync(tomlPath))
|
|
21
|
+
return;
|
|
22
|
+
let content = readFileSync(tomlPath, "utf-8");
|
|
23
|
+
content = content.replace(/^name\s*=\s*"[^"]*"/m, `name = "${name}"`);
|
|
24
|
+
writeFileSync(tomlPath, content);
|
|
25
|
+
}
|
|
26
|
+
function updatePackageJsonName(dir, name) {
|
|
27
|
+
const pkgPath = join(dir, "package.json");
|
|
28
|
+
if (!existsSync(pkgPath))
|
|
29
|
+
return;
|
|
30
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
31
|
+
pkg.name = name;
|
|
32
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
33
|
+
}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface FetchResult {
|
|
2
|
+
dir: string;
|
|
3
|
+
templateConfig: TemplateConfig | null;
|
|
4
|
+
defaultData: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface TemplateConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
capabilities: string[];
|
|
10
|
+
thumbnail?: string;
|
|
11
|
+
screenshot?: string;
|
|
12
|
+
schema?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Download a template into `dest`.
|
|
16
|
+
*
|
|
17
|
+
* Built-in templates: "landing" → github:solcreek/templates/landing
|
|
18
|
+
* Third-party: "github:user/repo" → passed to giget directly
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchTemplate(template: string, dest: string): Promise<FetchResult>;
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { downloadTemplate } from "giget";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
const TEMPLATE_REPO = "github:solcreek/templates";
|
|
5
|
+
/**
|
|
6
|
+
* Download a template into `dest`.
|
|
7
|
+
*
|
|
8
|
+
* Built-in templates: "landing" → github:solcreek/templates/landing
|
|
9
|
+
* Third-party: "github:user/repo" → passed to giget directly
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchTemplate(template, dest) {
|
|
12
|
+
const source = isThirdParty(template)
|
|
13
|
+
? template
|
|
14
|
+
: `${TEMPLATE_REPO}/${template}`;
|
|
15
|
+
const { dir } = await downloadTemplate(source, {
|
|
16
|
+
dir: dest,
|
|
17
|
+
force: true,
|
|
18
|
+
});
|
|
19
|
+
// Read creek-template.json if present
|
|
20
|
+
const configPath = join(dir, "creek-template.json");
|
|
21
|
+
let templateConfig = null;
|
|
22
|
+
if (existsSync(configPath)) {
|
|
23
|
+
templateConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
24
|
+
}
|
|
25
|
+
// Read creek-data.json defaults if present
|
|
26
|
+
const dataPath = join(dir, "creek-data.json");
|
|
27
|
+
let defaultData = {};
|
|
28
|
+
if (existsSync(dataPath)) {
|
|
29
|
+
defaultData = JSON.parse(readFileSync(dataPath, "utf-8"));
|
|
30
|
+
}
|
|
31
|
+
return { dir, templateConfig, defaultData };
|
|
32
|
+
}
|
|
33
|
+
function isThirdParty(template) {
|
|
34
|
+
return (template.includes(":") || template.includes("/") || template.startsWith("."));
|
|
35
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { mkdtempSync, readFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { TEMPLATES } from "./templates.js";
|
|
8
|
+
import { scaffold } from "./scaffold.js";
|
|
9
|
+
import { validateData } from "./validate.js";
|
|
10
|
+
import { fetchTemplate } from "./fetch.js";
|
|
11
|
+
import { promptTemplate, promptDir } from "./prompts.js";
|
|
12
|
+
const main = defineCommand({
|
|
13
|
+
meta: {
|
|
14
|
+
name: "create-creek-app",
|
|
15
|
+
description: "Create a new Creek project from a template",
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
dir: {
|
|
19
|
+
type: "positional",
|
|
20
|
+
description: "Project directory",
|
|
21
|
+
required: false,
|
|
22
|
+
},
|
|
23
|
+
template: {
|
|
24
|
+
type: "string",
|
|
25
|
+
alias: "t",
|
|
26
|
+
description: "Template name or github:user/repo",
|
|
27
|
+
},
|
|
28
|
+
data: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "JSON data for template params",
|
|
31
|
+
},
|
|
32
|
+
"data-file": {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Path to JSON file for template params",
|
|
35
|
+
},
|
|
36
|
+
list: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "List available templates (JSON)",
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
schema: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
description: "Print template JSON Schema",
|
|
44
|
+
default: false,
|
|
45
|
+
},
|
|
46
|
+
validate: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
description: "Validate data against template schema",
|
|
49
|
+
default: false,
|
|
50
|
+
},
|
|
51
|
+
registry: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Private template registry URL (enterprise)",
|
|
54
|
+
},
|
|
55
|
+
yes: {
|
|
56
|
+
type: "boolean",
|
|
57
|
+
alias: "y",
|
|
58
|
+
description: "Skip prompts, use defaults",
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
61
|
+
install: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
description: "Install dependencies (use --no-install to skip)",
|
|
64
|
+
default: true,
|
|
65
|
+
},
|
|
66
|
+
git: {
|
|
67
|
+
type: "boolean",
|
|
68
|
+
description: "Initialize git repo (use --no-git to skip)",
|
|
69
|
+
default: true,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
async run({ args }) {
|
|
73
|
+
// --registry: enterprise placeholder
|
|
74
|
+
if (args.registry) {
|
|
75
|
+
console.log(JSON.stringify({
|
|
76
|
+
error: "Private template registry is an enterprise feature. Coming soon — creek.dev/enterprise",
|
|
77
|
+
}));
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
// --list: output JSON array of templates
|
|
81
|
+
if (args.list) {
|
|
82
|
+
console.log(JSON.stringify(TEMPLATES, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// --schema: print template's JSON Schema
|
|
86
|
+
if (args.schema) {
|
|
87
|
+
const templateName = args.template;
|
|
88
|
+
if (!templateName) {
|
|
89
|
+
consola.error("--schema requires --template <name>");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const { templateConfig } = await fetchTemplate(templateName, makeTempDir());
|
|
93
|
+
if (!templateConfig?.schema) {
|
|
94
|
+
consola.error(`Template "${templateName}" has no schema`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
console.log(JSON.stringify(templateConfig.schema, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Parse user data from --data or --data-file
|
|
101
|
+
let userData = {};
|
|
102
|
+
if (args.data) {
|
|
103
|
+
try {
|
|
104
|
+
userData = JSON.parse(args.data);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
consola.error("Invalid JSON in --data");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (args["data-file"]) {
|
|
112
|
+
try {
|
|
113
|
+
userData = JSON.parse(readFileSync(args["data-file"], "utf-8"));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
consola.error(`Cannot read --data-file: ${args["data-file"]}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// --validate: validate data and exit
|
|
121
|
+
if (args.validate) {
|
|
122
|
+
const templateName = args.template;
|
|
123
|
+
if (!templateName) {
|
|
124
|
+
consola.error("--validate requires --template <name>");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const { templateConfig, defaultData } = await fetchTemplate(templateName, makeTempDir());
|
|
128
|
+
if (!templateConfig?.schema) {
|
|
129
|
+
console.log(JSON.stringify({ valid: true, errors: [] }));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const merged = { ...defaultData, ...userData };
|
|
133
|
+
const result = validateData(templateConfig.schema, merged);
|
|
134
|
+
console.log(JSON.stringify(result, null, 2));
|
|
135
|
+
process.exit(result.valid ? 0 : 1);
|
|
136
|
+
}
|
|
137
|
+
// Interactive or direct scaffold
|
|
138
|
+
let template = args.template;
|
|
139
|
+
let dir = args.dir;
|
|
140
|
+
if (!args.yes) {
|
|
141
|
+
// Interactive mode
|
|
142
|
+
if (!template) {
|
|
143
|
+
template = await promptTemplate();
|
|
144
|
+
}
|
|
145
|
+
if (!dir) {
|
|
146
|
+
dir = await promptDir();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Non-interactive: require --template, default dir
|
|
151
|
+
if (!template) {
|
|
152
|
+
template = "blank";
|
|
153
|
+
}
|
|
154
|
+
if (!dir) {
|
|
155
|
+
dir = "my-creek-app";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
await scaffold({
|
|
159
|
+
template,
|
|
160
|
+
dir,
|
|
161
|
+
data: userData,
|
|
162
|
+
install: args.install,
|
|
163
|
+
git: args.git,
|
|
164
|
+
silent: false,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
function makeTempDir() {
|
|
169
|
+
return mkdtempSync(join(tmpdir(), "creek-tpl-"));
|
|
170
|
+
}
|
|
171
|
+
runMain(main);
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { TEMPLATES } from "./templates.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interactive template picker — used when no --template flag is given.
|
|
5
|
+
*/
|
|
6
|
+
export async function promptTemplate() {
|
|
7
|
+
const choices = TEMPLATES.map((t) => ({
|
|
8
|
+
label: `${t.name} — ${t.description}`,
|
|
9
|
+
value: t.name,
|
|
10
|
+
hint: t.capabilities.length ? t.capabilities.join(", ") : undefined,
|
|
11
|
+
}));
|
|
12
|
+
const selected = await consola.prompt("Select a template:", {
|
|
13
|
+
type: "select",
|
|
14
|
+
options: choices,
|
|
15
|
+
});
|
|
16
|
+
// consola.prompt returns the value string for select type
|
|
17
|
+
return selected;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Prompt for project directory name.
|
|
21
|
+
*/
|
|
22
|
+
export async function promptDir() {
|
|
23
|
+
const dir = await consola.prompt("Project name:", {
|
|
24
|
+
type: "text",
|
|
25
|
+
default: "my-creek-app",
|
|
26
|
+
placeholder: "my-creek-app",
|
|
27
|
+
});
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ScaffoldOptions {
|
|
2
|
+
template: string;
|
|
3
|
+
dir: string;
|
|
4
|
+
data?: Record<string, unknown>;
|
|
5
|
+
install?: boolean;
|
|
6
|
+
git?: boolean;
|
|
7
|
+
silent?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ScaffoldResult {
|
|
10
|
+
dir: string;
|
|
11
|
+
template: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { resolve, basename } from "node:path";
|
|
2
|
+
import { existsSync, renameSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import { fetchTemplate } from "./fetch.js";
|
|
6
|
+
import { applyData } from "./apply-data.js";
|
|
7
|
+
import { validateData } from "./validate.js";
|
|
8
|
+
export async function scaffold(opts) {
|
|
9
|
+
const dest = resolve(opts.dir);
|
|
10
|
+
const projectName = basename(dest);
|
|
11
|
+
// 1. Fetch template
|
|
12
|
+
if (!opts.silent)
|
|
13
|
+
consola.start(`Downloading template: ${opts.template}`);
|
|
14
|
+
const { dir, templateConfig, defaultData } = await fetchTemplate(opts.template, dest);
|
|
15
|
+
// 2. Validate user data against schema (if schema exists)
|
|
16
|
+
const userData = { name: projectName, ...(opts.data ?? {}) };
|
|
17
|
+
if (templateConfig?.schema) {
|
|
18
|
+
const result = validateData(templateConfig.schema, { ...defaultData, ...userData });
|
|
19
|
+
if (!result.valid) {
|
|
20
|
+
consola.error("Data validation failed:");
|
|
21
|
+
for (const err of result.errors) {
|
|
22
|
+
consola.error(` ${err.path}: ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Template data validation failed");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// 3. Apply data
|
|
28
|
+
applyData(dir, userData, defaultData);
|
|
29
|
+
// 4. Rename _gitignore → .gitignore
|
|
30
|
+
const gitignoreSrc = resolve(dir, "_gitignore");
|
|
31
|
+
const gitignoreDest = resolve(dir, ".gitignore");
|
|
32
|
+
if (existsSync(gitignoreSrc)) {
|
|
33
|
+
renameSync(gitignoreSrc, gitignoreDest);
|
|
34
|
+
}
|
|
35
|
+
// 5. Remove creek-template.json from output (metadata, not project file)
|
|
36
|
+
const templateConfigPath = resolve(dir, "creek-template.json");
|
|
37
|
+
if (existsSync(templateConfigPath)) {
|
|
38
|
+
unlinkSync(templateConfigPath);
|
|
39
|
+
}
|
|
40
|
+
// 6. Install dependencies
|
|
41
|
+
if (opts.install !== false) {
|
|
42
|
+
const pkgPath = resolve(dir, "package.json");
|
|
43
|
+
if (existsSync(pkgPath)) {
|
|
44
|
+
if (!opts.silent)
|
|
45
|
+
consola.start("Installing dependencies...");
|
|
46
|
+
try {
|
|
47
|
+
execSync("npm install", { cwd: dir, stdio: "pipe" });
|
|
48
|
+
if (!opts.silent)
|
|
49
|
+
consola.success("Dependencies installed");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
consola.warn("Failed to install dependencies. Run `npm install` manually.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 7. Git init
|
|
57
|
+
if (opts.git !== false) {
|
|
58
|
+
try {
|
|
59
|
+
execSync("git init", { cwd: dir, stdio: "pipe" });
|
|
60
|
+
execSync("git add -A", { cwd: dir, stdio: "pipe" });
|
|
61
|
+
execSync('git commit -m "Initial commit from create-creek-app"', {
|
|
62
|
+
cwd: dir,
|
|
63
|
+
stdio: "pipe",
|
|
64
|
+
});
|
|
65
|
+
if (!opts.silent)
|
|
66
|
+
consola.success("Git repository initialized");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// git not available or failed — that's fine
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!opts.silent) {
|
|
73
|
+
console.log("");
|
|
74
|
+
consola.success(`Created ${projectName} with template "${opts.template}"`);
|
|
75
|
+
console.log("");
|
|
76
|
+
consola.info(" Next steps:");
|
|
77
|
+
consola.info(` cd ${projectName}`);
|
|
78
|
+
consola.info(" creek deploy Deploy to production");
|
|
79
|
+
consola.info(" creek dev Start local development");
|
|
80
|
+
}
|
|
81
|
+
return { dir, template: opts.template, name: projectName };
|
|
82
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Hardcoded template list — used for --list and interactive prompt. */
|
|
2
|
+
export declare const TEMPLATES: readonly [{
|
|
3
|
+
readonly name: "blank";
|
|
4
|
+
readonly description: "Minimal Creek project (no UI)";
|
|
5
|
+
readonly capabilities: string[];
|
|
6
|
+
}, {
|
|
7
|
+
readonly name: "landing";
|
|
8
|
+
readonly description: "Landing page with hero and CTA";
|
|
9
|
+
readonly capabilities: string[];
|
|
10
|
+
}, {
|
|
11
|
+
readonly name: "blog";
|
|
12
|
+
readonly description: "Blog with posts (D1 database)";
|
|
13
|
+
readonly capabilities: readonly ["d1"];
|
|
14
|
+
}, {
|
|
15
|
+
readonly name: "link-in-bio";
|
|
16
|
+
readonly description: "Social links page";
|
|
17
|
+
readonly capabilities: string[];
|
|
18
|
+
}, {
|
|
19
|
+
readonly name: "api";
|
|
20
|
+
readonly description: "REST API with Hono (D1)";
|
|
21
|
+
readonly capabilities: readonly ["d1"];
|
|
22
|
+
}, {
|
|
23
|
+
readonly name: "todo";
|
|
24
|
+
readonly description: "Realtime todo app (D1 + WebSocket)";
|
|
25
|
+
readonly capabilities: readonly ["d1", "realtime"];
|
|
26
|
+
}, {
|
|
27
|
+
readonly name: "dashboard";
|
|
28
|
+
readonly description: "Data dashboard (D1 + Realtime)";
|
|
29
|
+
readonly capabilities: readonly ["d1", "realtime"];
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "form";
|
|
32
|
+
readonly description: "Form collector (D1)";
|
|
33
|
+
readonly capabilities: readonly ["d1"];
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "chatbot";
|
|
36
|
+
readonly description: "AI chatbot (D1 + Workers AI)";
|
|
37
|
+
readonly capabilities: readonly ["d1", "ai"];
|
|
38
|
+
}];
|
|
39
|
+
export type TemplateName = (typeof TEMPLATES)[number]["name"];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Hardcoded template list — used for --list and interactive prompt. */
|
|
2
|
+
export const TEMPLATES = [
|
|
3
|
+
{ name: "blank", description: "Minimal Creek project (no UI)", capabilities: [] },
|
|
4
|
+
{ name: "landing", description: "Landing page with hero and CTA", capabilities: [] },
|
|
5
|
+
{ name: "blog", description: "Blog with posts (D1 database)", capabilities: ["d1"] },
|
|
6
|
+
{ name: "link-in-bio", description: "Social links page", capabilities: [] },
|
|
7
|
+
{ name: "api", description: "REST API with Hono (D1)", capabilities: ["d1"] },
|
|
8
|
+
{ name: "todo", description: "Realtime todo app (D1 + WebSocket)", capabilities: ["d1", "realtime"] },
|
|
9
|
+
{ name: "dashboard", description: "Data dashboard (D1 + Realtime)", capabilities: ["d1", "realtime"] },
|
|
10
|
+
{ name: "form", description: "Form collector (D1)", capabilities: ["d1"] },
|
|
11
|
+
{ name: "chatbot", description: "AI chatbot (D1 + Workers AI)", capabilities: ["d1", "ai"] },
|
|
12
|
+
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
errors: ValidationError[];
|
|
4
|
+
}
|
|
5
|
+
export interface ValidationError {
|
|
6
|
+
path: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validate data against a JSON Schema from creek-template.json.
|
|
11
|
+
* Returns { valid, errors }.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateData(schema: Record<string, unknown>, data: Record<string, unknown>): ValidationResult;
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
const ajv = new Ajv({ allErrors: true, useDefaults: true });
|
|
3
|
+
/**
|
|
4
|
+
* Validate data against a JSON Schema from creek-template.json.
|
|
5
|
+
* Returns { valid, errors }.
|
|
6
|
+
*/
|
|
7
|
+
export function validateData(schema, data) {
|
|
8
|
+
// Strip $schema meta field — ajv doesn't support 2020-12 meta-schema by default
|
|
9
|
+
const { $schema: _, ...schemaWithoutMeta } = schema;
|
|
10
|
+
const validate = ajv.compile(schemaWithoutMeta);
|
|
11
|
+
const valid = validate(data);
|
|
12
|
+
if (valid) {
|
|
13
|
+
return { valid: true, errors: [] };
|
|
14
|
+
}
|
|
15
|
+
const errors = (validate.errors ?? []).map((err) => ({
|
|
16
|
+
path: err.instancePath || "/",
|
|
17
|
+
message: err.message ?? "unknown error",
|
|
18
|
+
}));
|
|
19
|
+
return { valid: false, errors };
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-creek-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new Creek project from a template",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
|
-
"create-creek-app": "./
|
|
7
|
+
"create-creek-app": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"creek",
|
|
20
|
+
"create",
|
|
21
|
+
"scaffold",
|
|
22
|
+
"template",
|
|
23
|
+
"cloudflare",
|
|
24
|
+
"workers",
|
|
25
|
+
"d1"
|
|
26
|
+
],
|
|
27
|
+
"author": "SolCreek",
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"homepage": "https://creek.dev",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/solcreek/creek.git",
|
|
33
|
+
"directory": "packages/create-creek-app"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"citty": "^0.1.6",
|
|
37
|
+
"consola": "^3.4.2",
|
|
38
|
+
"giget": "^2.0.0",
|
|
39
|
+
"ajv": "^8.17.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"typescript": "^5.8.2",
|
|
44
|
+
"vitest": "^4.1.1"
|
|
7
45
|
}
|
|
8
46
|
}
|
package/bin/create-creek-app.js
DELETED