create-emdash 0.0.4 → 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/dist/index.mjs CHANGED
@@ -77,12 +77,36 @@ const CLOUDFLARE_TEMPLATES = {
77
77
  };
78
78
  /** Build select options from a config object, preserving literal key types */
79
79
  function selectOptions(obj) {
80
- return Object.keys(obj).map((key) => ({
80
+ return Object.keys(obj).filter((k) => k in obj).map((key) => ({
81
81
  value: key,
82
82
  label: obj[key].name,
83
83
  hint: obj[key].description
84
84
  }));
85
85
  }
86
+ async function selectTemplate(platform) {
87
+ if (platform === "node") {
88
+ const key = await p.select({
89
+ message: "Which template?",
90
+ options: selectOptions(NODE_TEMPLATES),
91
+ initialValue: "blog"
92
+ });
93
+ if (p.isCancel(key)) {
94
+ p.cancel("Operation cancelled.");
95
+ process.exit(0);
96
+ }
97
+ return NODE_TEMPLATES[key];
98
+ }
99
+ const key = await p.select({
100
+ message: "Which template?",
101
+ options: selectOptions(CLOUDFLARE_TEMPLATES),
102
+ initialValue: "blog"
103
+ });
104
+ if (p.isCancel(key)) {
105
+ p.cancel("Operation cancelled.");
106
+ process.exit(0);
107
+ }
108
+ return CLOUDFLARE_TEMPLATES[key];
109
+ }
86
110
  async function main() {
87
111
  console.clear();
88
112
  console.log(`\n ${pc.bold(pc.cyan("— E M D A S H —"))}\n`);
@@ -122,26 +146,13 @@ async function main() {
122
146
  label: "Node.js",
123
147
  hint: "SQLite + local file storage"
124
148
  }],
125
- initialValue: "node"
149
+ initialValue: "cloudflare"
126
150
  });
127
151
  if (p.isCancel(platform)) {
128
152
  p.cancel("Operation cancelled.");
129
153
  process.exit(0);
130
154
  }
131
- const templateKey = platform === "node" ? await p.select({
132
- message: "Which template?",
133
- options: selectOptions(NODE_TEMPLATES),
134
- initialValue: "blog"
135
- }) : await p.select({
136
- message: "Which template?",
137
- options: selectOptions(CLOUDFLARE_TEMPLATES),
138
- initialValue: "blog"
139
- });
140
- if (p.isCancel(templateKey)) {
141
- p.cancel("Operation cancelled.");
142
- process.exit(0);
143
- }
144
- const templateConfig = platform === "node" ? NODE_TEMPLATES[templateKey] : CLOUDFLARE_TEMPLATES[templateKey];
155
+ const templateConfig = await selectTemplate(platform);
145
156
  const detectedPm = detectPackageManager();
146
157
  const pm = await p.select({
147
158
  message: "Which package manager?",
@@ -209,7 +220,7 @@ async function main() {
209
220
  }
210
221
  const steps = [`cd ${projectName}`];
211
222
  if (!shouldInstall) steps.push(installCmd);
212
- steps.push(runCmd("bootstrap"), runCmd("dev"));
223
+ steps.push(runCmd("dev"));
213
224
  p.note(steps.join("\n"), "Next steps");
214
225
  p.outro(`${pc.green("Done!")} Your EmDash project is ready at ${pc.cyan(projectName)}`);
215
226
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * create-emdash\n *\n * Interactive CLI for creating new EmDash projects\n *\n * Usage: npm create emdash@latest\n */\n\nimport { exec } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nimport * as p from \"@clack/prompts\";\nimport { downloadTemplate } from \"giget\";\nimport pc from \"picocolors\";\n\nconst PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;\n\nconst GITHUB_REPO = \"emdash-cms/templates\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\n/** Detect which package manager invoked us, or fall back to npm */\nfunction detectPackageManager(): PackageManager {\n\tconst agent = process.env.npm_config_user_agent ?? \"\";\n\tif (agent.startsWith(\"pnpm\")) return \"pnpm\";\n\tif (agent.startsWith(\"yarn\")) return \"yarn\";\n\tif (agent.startsWith(\"bun\")) return \"bun\";\n\treturn \"npm\";\n}\n\ntype Platform = \"node\" | \"cloudflare\";\n\ninterface TemplateConfig {\n\tname: string;\n\tdescription: string;\n\t/** Directory name in the templates repo */\n\tdir: string;\n}\n\nconst NODE_TEMPLATES = {\n\tblog: {\n\t\tname: \"Blog\",\n\t\tdescription: \"A blog with posts, pages, and authors\",\n\t\tdir: \"blog\",\n\t},\n\tstarter: {\n\t\tname: \"Starter\",\n\t\tdescription: \"A general-purpose starter with posts and pages\",\n\t\tdir: \"starter\",\n\t},\n\tmarketing: {\n\t\tname: \"Marketing\",\n\t\tdescription: \"A marketing site with landing pages and CTAs\",\n\t\tdir: \"marketing\",\n\t},\n\tportfolio: {\n\t\tname: \"Portfolio\",\n\t\tdescription: \"A portfolio site with projects and case studies\",\n\t\tdir: \"portfolio\",\n\t},\n\tblank: {\n\t\tname: \"Blank\",\n\t\tdescription: \"A minimal starter with no content or styling\",\n\t\tdir: \"blank\",\n\t},\n} as const satisfies Record<string, TemplateConfig>;\n\nconst CLOUDFLARE_TEMPLATES = {\n\tblog: {\n\t\tname: \"Blog\",\n\t\tdescription: \"A blog with posts, pages, and authors\",\n\t\tdir: \"blog-cloudflare\",\n\t},\n\tstarter: {\n\t\tname: \"Starter\",\n\t\tdescription: \"A general-purpose starter with posts and pages\",\n\t\tdir: \"starter-cloudflare\",\n\t},\n\tmarketing: {\n\t\tname: \"Marketing\",\n\t\tdescription: \"A marketing site with landing pages and CTAs\",\n\t\tdir: \"marketing-cloudflare\",\n\t},\n\tportfolio: {\n\t\tname: \"Portfolio\",\n\t\tdescription: \"A portfolio site with projects and case studies\",\n\t\tdir: \"portfolio-cloudflare\",\n\t},\n} as const satisfies Record<string, TemplateConfig>;\n\ntype NodeTemplate = keyof typeof NODE_TEMPLATES;\ntype CloudflareTemplate = keyof typeof CLOUDFLARE_TEMPLATES;\n\n/** Build select options from a config object, preserving literal key types */\nfunction selectOptions<K extends string>(\n\tobj: Readonly<Record<K, Readonly<{ name: string; description: string }>>>,\n): { value: K; label: string; hint: string }[] {\n\treturn (Object.keys(obj) as K[]).map((key) => ({\n\t\tvalue: key,\n\t\tlabel: obj[key].name,\n\t\thint: obj[key].description,\n\t}));\n}\n\nasync function main() {\n\tconsole.clear();\n\n\tconsole.log(`\\n ${pc.bold(pc.cyan(\"— E M D A S H —\"))}\\n`);\n\tp.intro(\"Create a new EmDash project\");\n\n\tconst projectName = await p.text({\n\t\tmessage: \"Project name?\",\n\t\tplaceholder: \"my-site\",\n\t\tdefaultValue: \"my-site\",\n\t\tvalidate: (value) => {\n\t\t\tif (!value) return \"Project name is required\";\n\t\t\tif (!PROJECT_NAME_PATTERN.test(value))\n\t\t\t\treturn \"Project name can only contain lowercase letters, numbers, and hyphens\";\n\t\t\treturn undefined;\n\t\t},\n\t});\n\n\tif (p.isCancel(projectName)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst projectDir = resolve(process.cwd(), projectName);\n\n\tif (existsSync(projectDir)) {\n\t\tconst overwrite = await p.confirm({\n\t\t\tmessage: `Directory ${projectName} already exists. Overwrite?`,\n\t\t\tinitialValue: false,\n\t\t});\n\n\t\tif (p.isCancel(overwrite) || !overwrite) {\n\t\t\tp.cancel(\"Operation cancelled.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\t// Step 1: pick platform\n\tconst platform = await p.select<Platform>({\n\t\tmessage: \"Where will you deploy?\",\n\t\toptions: [\n\t\t\t{\n\t\t\t\tvalue: \"cloudflare\",\n\t\t\t\tlabel: \"Cloudflare Workers\",\n\t\t\t\thint: \"D1 + R2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"node\",\n\t\t\t\tlabel: \"Node.js\",\n\t\t\t\thint: \"SQLite + local file storage\",\n\t\t\t},\n\t\t],\n\t\tinitialValue: \"node\",\n\t});\n\n\tif (p.isCancel(platform)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Step 2: pick template\n\tconst templateKey =\n\t\tplatform === \"node\"\n\t\t\t? await p.select<NodeTemplate>({\n\t\t\t\t\tmessage: \"Which template?\",\n\t\t\t\t\toptions: selectOptions(NODE_TEMPLATES),\n\t\t\t\t\tinitialValue: \"blog\",\n\t\t\t\t})\n\t\t\t: await p.select<CloudflareTemplate>({\n\t\t\t\t\tmessage: \"Which template?\",\n\t\t\t\t\toptions: selectOptions(CLOUDFLARE_TEMPLATES),\n\t\t\t\t\tinitialValue: \"blog\",\n\t\t\t\t});\n\n\tif (p.isCancel(templateKey)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst templateConfig =\n\t\tplatform === \"node\"\n\t\t\t? NODE_TEMPLATES[templateKey as NodeTemplate]\n\t\t\t: CLOUDFLARE_TEMPLATES[templateKey as CloudflareTemplate];\n\n\t// Step 3: pick package manager\n\tconst detectedPm = detectPackageManager();\n\tconst pm = await p.select<PackageManager>({\n\t\tmessage: \"Which package manager?\",\n\t\toptions: [\n\t\t\t{ value: \"pnpm\", label: \"pnpm\" },\n\t\t\t{ value: \"npm\", label: \"npm\" },\n\t\t\t{ value: \"yarn\", label: \"yarn\" },\n\t\t\t{ value: \"bun\", label: \"bun\" },\n\t\t],\n\t\tinitialValue: detectedPm,\n\t});\n\n\tif (p.isCancel(pm)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Step 4: install dependencies?\n\tconst shouldInstall = await p.confirm({\n\t\tmessage: \"Install dependencies?\",\n\t\tinitialValue: true,\n\t});\n\n\tif (p.isCancel(shouldInstall)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst installCmd = `${pm} install`;\n\tconst runCmd = (script: string) => (pm === \"npm\" ? `npm run ${script}` : `${pm} ${script}`);\n\n\tconst s = p.spinner();\n\ts.start(\"Creating project...\");\n\n\ttry {\n\t\tawait downloadTemplate(`github:${GITHUB_REPO}/${templateConfig.dir}`, {\n\t\t\tdir: projectDir,\n\t\t\tforce: true,\n\t\t});\n\n\t\t// Set project name in package.json\n\t\tconst pkgPath = resolve(projectDir, \"package.json\");\n\t\tif (existsSync(pkgPath)) {\n\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\t\tpkg.name = projectName;\n\n\t\t\t// Add emdash config if template has seed data\n\t\t\tconst seedPath = resolve(projectDir, \"seed\", \"seed.json\");\n\t\t\tif (existsSync(seedPath)) {\n\t\t\t\tpkg.emdash = {\n\t\t\t\t\tlabel: templateConfig.name,\n\t\t\t\t\tseed: \"seed/seed.json\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\twriteFileSync(pkgPath, JSON.stringify(pkg, null, 2));\n\t\t}\n\n\t\ts.stop(\"Project created!\");\n\n\t\tif (shouldInstall) {\n\t\t\ts.start(`Installing dependencies with ${pc.cyan(pm)}...`);\n\t\t\ttry {\n\t\t\t\tawait execAsync(installCmd, { cwd: projectDir });\n\t\t\t\ts.stop(\"Dependencies installed!\");\n\t\t\t} catch {\n\t\t\t\ts.stop(\"Failed to install dependencies\");\n\t\t\t\tp.log.warn(`Run ${pc.cyan(`cd ${projectName} && ${installCmd}`)} manually`);\n\t\t\t}\n\t\t}\n\n\t\tconst steps = [`cd ${projectName}`];\n\t\tif (!shouldInstall) steps.push(installCmd);\n\t\tsteps.push(runCmd(\"bootstrap\"), runCmd(\"dev\"));\n\n\t\tp.note(steps.join(\"\\n\"), \"Next steps\");\n\n\t\tp.outro(`${pc.green(\"Done!\")} Your EmDash project is ready at ${pc.cyan(projectName)}`);\n\t} catch (error) {\n\t\ts.stop(\"Failed to create project\");\n\t\tp.log.error(error instanceof Error ? error.message : String(error));\n\t\tprocess.exit(1);\n\t}\n}\n\nmain().catch((error) => {\n\tconsole.error(error);\n\tprocess.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * create-emdash\n *\n * Interactive CLI for creating new EmDash projects\n *\n * Usage: npm create emdash@latest\n */\n\nimport { exec } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nimport * as p from \"@clack/prompts\";\nimport { downloadTemplate } from \"giget\";\nimport pc from \"picocolors\";\n\nconst PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;\n\nconst GITHUB_REPO = \"emdash-cms/templates\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\n/** Detect which package manager invoked us, or fall back to npm */\nfunction detectPackageManager(): PackageManager {\n\tconst agent = process.env.npm_config_user_agent ?? \"\";\n\tif (agent.startsWith(\"pnpm\")) return \"pnpm\";\n\tif (agent.startsWith(\"yarn\")) return \"yarn\";\n\tif (agent.startsWith(\"bun\")) return \"bun\";\n\treturn \"npm\";\n}\n\ntype Platform = \"node\" | \"cloudflare\";\n\ninterface TemplateConfig {\n\tname: string;\n\tdescription: string;\n\t/** Directory name in the templates repo */\n\tdir: string;\n}\n\nconst NODE_TEMPLATES = {\n\tblog: {\n\t\tname: \"Blog\",\n\t\tdescription: \"A blog with posts, pages, and authors\",\n\t\tdir: \"blog\",\n\t},\n\tstarter: {\n\t\tname: \"Starter\",\n\t\tdescription: \"A general-purpose starter with posts and pages\",\n\t\tdir: \"starter\",\n\t},\n\tmarketing: {\n\t\tname: \"Marketing\",\n\t\tdescription: \"A marketing site with landing pages and CTAs\",\n\t\tdir: \"marketing\",\n\t},\n\tportfolio: {\n\t\tname: \"Portfolio\",\n\t\tdescription: \"A portfolio site with projects and case studies\",\n\t\tdir: \"portfolio\",\n\t},\n\tblank: {\n\t\tname: \"Blank\",\n\t\tdescription: \"A minimal starter with no content or styling\",\n\t\tdir: \"blank\",\n\t},\n} as const satisfies Record<string, TemplateConfig>;\n\nconst CLOUDFLARE_TEMPLATES = {\n\tblog: {\n\t\tname: \"Blog\",\n\t\tdescription: \"A blog with posts, pages, and authors\",\n\t\tdir: \"blog-cloudflare\",\n\t},\n\tstarter: {\n\t\tname: \"Starter\",\n\t\tdescription: \"A general-purpose starter with posts and pages\",\n\t\tdir: \"starter-cloudflare\",\n\t},\n\tmarketing: {\n\t\tname: \"Marketing\",\n\t\tdescription: \"A marketing site with landing pages and CTAs\",\n\t\tdir: \"marketing-cloudflare\",\n\t},\n\tportfolio: {\n\t\tname: \"Portfolio\",\n\t\tdescription: \"A portfolio site with projects and case studies\",\n\t\tdir: \"portfolio-cloudflare\",\n\t},\n} as const satisfies Record<string, TemplateConfig>;\n\ntype NodeTemplate = keyof typeof NODE_TEMPLATES;\ntype CloudflareTemplate = keyof typeof CLOUDFLARE_TEMPLATES;\n\n/** Build select options from a config object, preserving literal key types */\nfunction selectOptions<K extends string>(\n\tobj: Readonly<Record<K, Readonly<{ name: string; description: string }>>>,\n): { value: K; label: string; hint: string }[] {\n\tconst keys: K[] = Object.keys(obj).filter((k): k is K => k in obj);\n\treturn keys.map((key) => ({\n\t\tvalue: key,\n\t\tlabel: obj[key].name,\n\t\thint: obj[key].description,\n\t}));\n}\n\nasync function selectTemplate(platform: Platform): Promise<TemplateConfig> {\n\tif (platform === \"node\") {\n\t\tconst key = await p.select<NodeTemplate>({\n\t\t\tmessage: \"Which template?\",\n\t\t\toptions: selectOptions(NODE_TEMPLATES),\n\t\t\tinitialValue: \"blog\",\n\t\t});\n\t\tif (p.isCancel(key)) {\n\t\t\tp.cancel(\"Operation cancelled.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t\treturn NODE_TEMPLATES[key];\n\t}\n\tconst key = await p.select<CloudflareTemplate>({\n\t\tmessage: \"Which template?\",\n\t\toptions: selectOptions(CLOUDFLARE_TEMPLATES),\n\t\tinitialValue: \"blog\",\n\t});\n\tif (p.isCancel(key)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\treturn CLOUDFLARE_TEMPLATES[key];\n}\n\nasync function main() {\n\tconsole.clear();\n\n\tconsole.log(`\\n ${pc.bold(pc.cyan(\"— E M D A S H —\"))}\\n`);\n\tp.intro(\"Create a new EmDash project\");\n\n\tconst projectName = await p.text({\n\t\tmessage: \"Project name?\",\n\t\tplaceholder: \"my-site\",\n\t\tdefaultValue: \"my-site\",\n\t\tvalidate: (value) => {\n\t\t\tif (!value) return \"Project name is required\";\n\t\t\tif (!PROJECT_NAME_PATTERN.test(value))\n\t\t\t\treturn \"Project name can only contain lowercase letters, numbers, and hyphens\";\n\t\t\treturn undefined;\n\t\t},\n\t});\n\n\tif (p.isCancel(projectName)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst projectDir = resolve(process.cwd(), projectName);\n\n\tif (existsSync(projectDir)) {\n\t\tconst overwrite = await p.confirm({\n\t\t\tmessage: `Directory ${projectName} already exists. Overwrite?`,\n\t\t\tinitialValue: false,\n\t\t});\n\n\t\tif (p.isCancel(overwrite) || !overwrite) {\n\t\t\tp.cancel(\"Operation cancelled.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\t// Step 1: pick platform\n\tconst platform = await p.select<Platform>({\n\t\tmessage: \"Where will you deploy?\",\n\t\toptions: [\n\t\t\t{\n\t\t\t\tvalue: \"cloudflare\",\n\t\t\t\tlabel: \"Cloudflare Workers\",\n\t\t\t\thint: \"D1 + R2\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"node\",\n\t\t\t\tlabel: \"Node.js\",\n\t\t\t\thint: \"SQLite + local file storage\",\n\t\t\t},\n\t\t],\n\t\tinitialValue: \"cloudflare\",\n\t});\n\n\tif (p.isCancel(platform)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Step 2: pick template\n\tconst templateConfig = await selectTemplate(platform);\n\n\t// Step 3: pick package manager\n\tconst detectedPm = detectPackageManager();\n\tconst pm = await p.select<PackageManager>({\n\t\tmessage: \"Which package manager?\",\n\t\toptions: [\n\t\t\t{ value: \"pnpm\", label: \"pnpm\" },\n\t\t\t{ value: \"npm\", label: \"npm\" },\n\t\t\t{ value: \"yarn\", label: \"yarn\" },\n\t\t\t{ value: \"bun\", label: \"bun\" },\n\t\t],\n\t\tinitialValue: detectedPm,\n\t});\n\n\tif (p.isCancel(pm)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Step 4: install dependencies?\n\tconst shouldInstall = await p.confirm({\n\t\tmessage: \"Install dependencies?\",\n\t\tinitialValue: true,\n\t});\n\n\tif (p.isCancel(shouldInstall)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst installCmd = `${pm} install`;\n\tconst runCmd = (script: string) => (pm === \"npm\" ? `npm run ${script}` : `${pm} ${script}`);\n\n\tconst s = p.spinner();\n\ts.start(\"Creating project...\");\n\n\ttry {\n\t\tawait downloadTemplate(`github:${GITHUB_REPO}/${templateConfig.dir}`, {\n\t\t\tdir: projectDir,\n\t\t\tforce: true,\n\t\t});\n\n\t\t// Set project name in package.json\n\t\tconst pkgPath = resolve(projectDir, \"package.json\");\n\t\tif (existsSync(pkgPath)) {\n\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\t\tpkg.name = projectName;\n\n\t\t\t// Add emdash config if template has seed data\n\t\t\tconst seedPath = resolve(projectDir, \"seed\", \"seed.json\");\n\t\t\tif (existsSync(seedPath)) {\n\t\t\t\tpkg.emdash = {\n\t\t\t\t\tlabel: templateConfig.name,\n\t\t\t\t\tseed: \"seed/seed.json\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\twriteFileSync(pkgPath, JSON.stringify(pkg, null, 2));\n\t\t}\n\n\t\ts.stop(\"Project created!\");\n\n\t\tif (shouldInstall) {\n\t\t\ts.start(`Installing dependencies with ${pc.cyan(pm)}...`);\n\t\t\ttry {\n\t\t\t\tawait execAsync(installCmd, { cwd: projectDir });\n\t\t\t\ts.stop(\"Dependencies installed!\");\n\t\t\t} catch {\n\t\t\t\ts.stop(\"Failed to install dependencies\");\n\t\t\t\tp.log.warn(`Run ${pc.cyan(`cd ${projectName} && ${installCmd}`)} manually`);\n\t\t\t}\n\t\t}\n\n\t\tconst steps = [`cd ${projectName}`];\n\t\tif (!shouldInstall) steps.push(installCmd);\n\t\tsteps.push(runCmd(\"dev\"));\n\n\t\tp.note(steps.join(\"\\n\"), \"Next steps\");\n\n\t\tp.outro(`${pc.green(\"Done!\")} Your EmDash project is ready at ${pc.cyan(projectName)}`);\n\t} catch (error) {\n\t\ts.stop(\"Failed to create project\");\n\t\tp.log.error(error instanceof Error ? error.message : String(error));\n\t\tprocess.exit(1);\n\t}\n}\n\nmain().catch((error) => {\n\tconsole.error(error);\n\tprocess.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAaA,MAAM,YAAY,UAAU,KAAK;AAMjC,MAAM,uBAAuB;AAE7B,MAAM,cAAc;;AAKpB,SAAS,uBAAuC;CAC/C,MAAM,QAAQ,QAAQ,IAAI,yBAAyB;AACnD,KAAI,MAAM,WAAW,OAAO,CAAE,QAAO;AACrC,KAAI,MAAM,WAAW,OAAO,CAAE,QAAO;AACrC,KAAI,MAAM,WAAW,MAAM,CAAE,QAAO;AACpC,QAAO;;AAYR,MAAM,iBAAiB;CACtB,MAAM;EACL,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,SAAS;EACR,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,WAAW;EACV,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,WAAW;EACV,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,OAAO;EACN,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD;AAED,MAAM,uBAAuB;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,SAAS;EACR,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,WAAW;EACV,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD,WAAW;EACV,MAAM;EACN,aAAa;EACb,KAAK;EACL;CACD;;AAMD,SAAS,cACR,KAC8C;AAE9C,QADkB,OAAO,KAAK,IAAI,CAAC,QAAQ,MAAc,KAAK,IAAI,CACtD,KAAK,SAAS;EACzB,OAAO;EACP,OAAO,IAAI,KAAK;EAChB,MAAM,IAAI,KAAK;EACf,EAAE;;AAGJ,eAAe,eAAe,UAA6C;AAC1E,KAAI,aAAa,QAAQ;EACxB,MAAM,MAAM,MAAM,EAAE,OAAqB;GACxC,SAAS;GACT,SAAS,cAAc,eAAe;GACtC,cAAc;GACd,CAAC;AACF,MAAI,EAAE,SAAS,IAAI,EAAE;AACpB,KAAE,OAAO,uBAAuB;AAChC,WAAQ,KAAK,EAAE;;AAEhB,SAAO,eAAe;;CAEvB,MAAM,MAAM,MAAM,EAAE,OAA2B;EAC9C,SAAS;EACT,SAAS,cAAc,qBAAqB;EAC5C,cAAc;EACd,CAAC;AACF,KAAI,EAAE,SAAS,IAAI,EAAE;AACpB,IAAE,OAAO,uBAAuB;AAChC,UAAQ,KAAK,EAAE;;AAEhB,QAAO,qBAAqB;;AAG7B,eAAe,OAAO;AACrB,SAAQ,OAAO;AAEf,SAAQ,IAAI,OAAO,GAAG,KAAK,GAAG,KAAK,kBAAkB,CAAC,CAAC,IAAI;AAC3D,GAAE,MAAM,8BAA8B;CAEtC,MAAM,cAAc,MAAM,EAAE,KAAK;EAChC,SAAS;EACT,aAAa;EACb,cAAc;EACd,WAAW,UAAU;AACpB,OAAI,CAAC,MAAO,QAAO;AACnB,OAAI,CAAC,qBAAqB,KAAK,MAAM,CACpC,QAAO;;EAGT,CAAC;AAEF,KAAI,EAAE,SAAS,YAAY,EAAE;AAC5B,IAAE,OAAO,uBAAuB;AAChC,UAAQ,KAAK,EAAE;;CAGhB,MAAM,aAAa,QAAQ,QAAQ,KAAK,EAAE,YAAY;AAEtD,KAAI,WAAW,WAAW,EAAE;EAC3B,MAAM,YAAY,MAAM,EAAE,QAAQ;GACjC,SAAS,aAAa,YAAY;GAClC,cAAc;GACd,CAAC;AAEF,MAAI,EAAE,SAAS,UAAU,IAAI,CAAC,WAAW;AACxC,KAAE,OAAO,uBAAuB;AAChC,WAAQ,KAAK,EAAE;;;CAKjB,MAAM,WAAW,MAAM,EAAE,OAAiB;EACzC,SAAS;EACT,SAAS,CACR;GACC,OAAO;GACP,OAAO;GACP,MAAM;GACN,EACD;GACC,OAAO;GACP,OAAO;GACP,MAAM;GACN,CACD;EACD,cAAc;EACd,CAAC;AAEF,KAAI,EAAE,SAAS,SAAS,EAAE;AACzB,IAAE,OAAO,uBAAuB;AAChC,UAAQ,KAAK,EAAE;;CAIhB,MAAM,iBAAiB,MAAM,eAAe,SAAS;CAGrD,MAAM,aAAa,sBAAsB;CACzC,MAAM,KAAK,MAAM,EAAE,OAAuB;EACzC,SAAS;EACT,SAAS;GACR;IAAE,OAAO;IAAQ,OAAO;IAAQ;GAChC;IAAE,OAAO;IAAO,OAAO;IAAO;GAC9B;IAAE,OAAO;IAAQ,OAAO;IAAQ;GAChC;IAAE,OAAO;IAAO,OAAO;IAAO;GAC9B;EACD,cAAc;EACd,CAAC;AAEF,KAAI,EAAE,SAAS,GAAG,EAAE;AACnB,IAAE,OAAO,uBAAuB;AAChC,UAAQ,KAAK,EAAE;;CAIhB,MAAM,gBAAgB,MAAM,EAAE,QAAQ;EACrC,SAAS;EACT,cAAc;EACd,CAAC;AAEF,KAAI,EAAE,SAAS,cAAc,EAAE;AAC9B,IAAE,OAAO,uBAAuB;AAChC,UAAQ,KAAK,EAAE;;CAGhB,MAAM,aAAa,GAAG,GAAG;CACzB,MAAM,UAAU,WAAoB,OAAO,QAAQ,WAAW,WAAW,GAAG,GAAG,GAAG;CAElF,MAAM,IAAI,EAAE,SAAS;AACrB,GAAE,MAAM,sBAAsB;AAE9B,KAAI;AACH,QAAM,iBAAiB,UAAU,YAAY,GAAG,eAAe,OAAO;GACrE,KAAK;GACL,OAAO;GACP,CAAC;EAGF,MAAM,UAAU,QAAQ,YAAY,eAAe;AACnD,MAAI,WAAW,QAAQ,EAAE;GACxB,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AACtD,OAAI,OAAO;AAIX,OAAI,WADa,QAAQ,YAAY,QAAQ,YAAY,CACjC,CACvB,KAAI,SAAS;IACZ,OAAO,eAAe;IACtB,MAAM;IACN;AAGF,iBAAc,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE,CAAC;;AAGrD,IAAE,KAAK,mBAAmB;AAE1B,MAAI,eAAe;AAClB,KAAE,MAAM,gCAAgC,GAAG,KAAK,GAAG,CAAC,KAAK;AACzD,OAAI;AACH,UAAM,UAAU,YAAY,EAAE,KAAK,YAAY,CAAC;AAChD,MAAE,KAAK,0BAA0B;WAC1B;AACP,MAAE,KAAK,iCAAiC;AACxC,MAAE,IAAI,KAAK,OAAO,GAAG,KAAK,MAAM,YAAY,MAAM,aAAa,CAAC,WAAW;;;EAI7E,MAAM,QAAQ,CAAC,MAAM,cAAc;AACnC,MAAI,CAAC,cAAe,OAAM,KAAK,WAAW;AAC1C,QAAM,KAAK,OAAO,MAAM,CAAC;AAEzB,IAAE,KAAK,MAAM,KAAK,KAAK,EAAE,aAAa;AAEtC,IAAE,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,mCAAmC,GAAG,KAAK,YAAY,GAAG;UAC/E,OAAO;AACf,IAAE,KAAK,2BAA2B;AAClC,IAAE,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;AACnE,UAAQ,KAAK,EAAE;;;AAIjB,MAAM,CAAC,OAAO,UAAU;AACvB,SAAQ,MAAM,MAAM;AACpB,SAAQ,KAAK,EAAE;EACd"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-emdash",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "Create a new EmDash CMS project",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",