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 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
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Interactive template picker — used when no --template flag is given.
3
+ */
4
+ export declare function promptTemplate(): Promise<string>;
5
+ /**
6
+ * Prompt for project directory name.
7
+ */
8
+ export declare function promptDir(): Promise<string>;
@@ -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>;
@@ -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;
@@ -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.0.0-dev.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": "./bin/create-creek-app.js"
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
  }
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- console.log("create-creek-app");