create-zenith 1.3.18 → 1.3.20

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.
Files changed (40) hide show
  1. package/README.md +24 -1
  2. package/dist/cli.js +297 -60
  3. package/dist/index.js +297 -60
  4. package/package.json +4 -4
  5. package/templates/basic/LICENSE +21 -0
  6. package/templates/basic/README.md +11 -0
  7. package/templates/basic/package.json +18 -0
  8. package/templates/basic/src/layouts/DefaultLayout.zen +20 -0
  9. package/templates/basic/src/pages/index.zen +24 -0
  10. package/templates/basic/src/public/logo.svg +601 -0
  11. package/templates/basic/src/styles/global.css +61 -0
  12. package/templates/basic/tsconfig.json +30 -0
  13. package/templates/basic/zenith.config.js +5 -0
  14. package/templates/css/LICENSE +21 -0
  15. package/templates/css/README.md +11 -0
  16. package/templates/css/package.json +18 -0
  17. package/templates/css/src/layouts/DefaultLayout.zen +26 -0
  18. package/templates/css/src/pages/about.zen +19 -0
  19. package/templates/css/src/pages/blog.zen +19 -0
  20. package/templates/css/src/pages/docs.zen +19 -0
  21. package/templates/css/src/pages/index.zen +26 -0
  22. package/templates/css/src/public/logo.svg +601 -0
  23. package/templates/css/src/styles/global.css +66 -0
  24. package/templates/css/tsconfig.json +30 -0
  25. package/templates/css/zenith.config.js +4 -0
  26. package/templates/features/eslint/eslint.config.js +30 -0
  27. package/templates/features/prettier/.prettierignore +3 -0
  28. package/templates/features/prettier/.prettierrc +7 -0
  29. package/templates/tailwind/LICENSE +21 -0
  30. package/templates/tailwind/README.md +11 -0
  31. package/templates/tailwind/package.json +20 -0
  32. package/templates/tailwind/src/layouts/DefaultLayout.zen +28 -0
  33. package/templates/tailwind/src/pages/about.zen +20 -0
  34. package/templates/tailwind/src/pages/blog.zen +19 -0
  35. package/templates/tailwind/src/pages/docs.zen +19 -0
  36. package/templates/tailwind/src/pages/index.zen +31 -0
  37. package/templates/tailwind/src/public/logo.svg +601 -0
  38. package/templates/tailwind/src/styles/globals.css +3 -0
  39. package/templates/tailwind/tsconfig.json +30 -0
  40. package/templates/tailwind/zenith.config.js +4 -0
package/README.md CHANGED
@@ -17,7 +17,8 @@ The official CLI for scaffolding new Zenith applications. Fast, animated, and de
17
17
  - **Interactive UX**: Built with `@clack/prompts` for intuitive arrow-key navigation and clear visual indicators.
18
18
  - **Reliable Fallbacks**: Automatically detects CI environments and non-TTY pipes to provide clean, static output.
19
19
  - **Smart Detection**: automatically detects your preferred package manager (Bun, pnpm, Yarn, or npm).
20
- - **Batteries Included**: Optional setup for ESLint, Prettier, and TypeScript path aliases.
20
+ - **Template Authority**: Scaffold generation now reads only from `templates/` (`basic`, `css`, `tailwind`), which is the single source of truth for starter projects.
21
+ - **Tool-Agnostic Output**: ESLint, Prettier, and TypeScript path aliases are opt-in. If you answer `No`, the generated project contains no scripts, dependencies, config files, or ignore files for that tool.
21
22
 
22
23
  ## Quick Start
23
24
 
@@ -40,6 +41,21 @@ pnpm create zenith
40
41
  | `-h, --help` | Show usage information |
41
42
  | `-v, --version` | Show version number |
42
43
 
44
+ ## Optional Tooling Contract
45
+
46
+ During scaffold, `create-zenith` asks whether to include:
47
+
48
+ - ESLint
49
+ - Prettier
50
+ - TypeScript path aliases
51
+
52
+ Tooling behavior is strict:
53
+
54
+ - If you enable ESLint, the project gets `eslint.config.js`, lint scripts, and matching ESLint dependencies.
55
+ - If you disable ESLint, the project contains zero ESLint references.
56
+ - If you enable Prettier, the project gets `.prettierrc`, `.prettierignore`, a format script, and the Prettier dependency.
57
+ - If you disable Prettier, the project contains zero Prettier references.
58
+
43
59
  ## Beta Version Pinning
44
60
 
45
61
  Zenith beta currently pins `@zenithbuild/core` to `0.5.0-beta.2.20` and leaf packages (compiler, cli, runtime, router, bundler) to `0.5.0-beta.2.20`. This is intentional — core contains the CLI entry point and may bump independently for bin/CLI fixes without touching the engine.
@@ -50,8 +66,15 @@ If you see version mismatches after install, delete `node_modules` and `package-
50
66
 
51
67
  - Generated apps now depend on `@zenithbuild/core@latest` so new installs track the current stable framework release.
52
68
  - Template downloads now resolve from `zenithbuild/framework`, which is the active monorepo source of truth.
69
+ - Starter templates now live under `templates/`, and the scaffolder no longer depends on `examples/`.
70
+ - ESLint and Prettier are now feature overlays, so opting out leaves no stray config or dependency references in the scaffolded app.
53
71
  - Verified scaffold → install → build coverage lives in `tests/template-regression.spec.mjs`.
54
72
 
73
+ ## Templates vs Examples
74
+
75
+ - `templates/` is authoritative for scaffolding.
76
+ - `examples/` is demo-only when present and is not part of the scaffold source of truth.
77
+
55
78
  ## Development
56
79
 
57
80
  ```bash
package/dist/cli.js CHANGED
@@ -2553,6 +2553,33 @@ class dD extends x {
2553
2553
  }
2554
2554
  var A;
2555
2555
  A = new WeakMap;
2556
+ var OD = Object.defineProperty;
2557
+ var PD = (e, u, t) => (u in e) ? OD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t;
2558
+ var J = (e, u, t) => (PD(e, typeof u != "symbol" ? u + "" : u, t), t);
2559
+
2560
+ class LD extends x {
2561
+ constructor(u) {
2562
+ super(u, false), J(this, "options"), J(this, "cursor", 0), this.options = u.options, this.cursor = this.options.findIndex(({ value: t }) => t === u.initialValue), this.cursor === -1 && (this.cursor = 0), this.changeValue(), this.on("cursor", (t) => {
2563
+ switch (t) {
2564
+ case "left":
2565
+ case "up":
2566
+ this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
2567
+ break;
2568
+ case "down":
2569
+ case "right":
2570
+ this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
2571
+ break;
2572
+ }
2573
+ this.changeValue();
2574
+ });
2575
+ }
2576
+ get _value() {
2577
+ return this.options[this.cursor];
2578
+ }
2579
+ changeValue() {
2580
+ this.value = this._value.value;
2581
+ }
2582
+ }
2556
2583
  class RD extends x {
2557
2584
  get valueWithCursor() {
2558
2585
  if (this.state === "submit")
@@ -2615,6 +2642,16 @@ var b2 = (t) => {
2615
2642
  return import_picocolors3.default.green(C);
2616
2643
  }
2617
2644
  };
2645
+ var G2 = (t) => {
2646
+ const { cursor: n, options: r2, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5));
2647
+ let l2 = 0;
2648
+ n >= l2 + a - 3 ? l2 = Math.max(Math.min(n - a + 3, r2.length - a), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
2649
+ const $2 = a < r2.length && l2 > 0, g2 = a < r2.length && l2 + a < r2.length;
2650
+ return r2.slice(l2, l2 + a).map((p2, v2, f) => {
2651
+ const j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g2;
2652
+ return j2 || E ? import_picocolors3.default.dim("...") : i(p2, v2 + l2 === n);
2653
+ });
2654
+ };
2618
2655
  var he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
2619
2656
  const n = `${import_picocolors3.default.gray(o)}
2620
2657
  ${b2(this.state)} ${t.message}
@@ -2655,6 +2692,38 @@ ${import_picocolors3.default.cyan(d2)}
2655
2692
  }
2656
2693
  } }).prompt();
2657
2694
  };
2695
+ var ve = (t) => {
2696
+ const n = (r2, i) => {
2697
+ const s = r2.label ?? String(r2.value);
2698
+ switch (i) {
2699
+ case "selected":
2700
+ return `${import_picocolors3.default.dim(s)}`;
2701
+ case "active":
2702
+ return `${import_picocolors3.default.green(k2)} ${s} ${r2.hint ? import_picocolors3.default.dim(`(${r2.hint})`) : ""}`;
2703
+ case "cancelled":
2704
+ return `${import_picocolors3.default.strikethrough(import_picocolors3.default.dim(s))}`;
2705
+ default:
2706
+ return `${import_picocolors3.default.dim(P2)} ${import_picocolors3.default.dim(s)}`;
2707
+ }
2708
+ };
2709
+ return new LD({ options: t.options, initialValue: t.initialValue, render() {
2710
+ const r2 = `${import_picocolors3.default.gray(o)}
2711
+ ${b2(this.state)} ${t.message}
2712
+ `;
2713
+ switch (this.state) {
2714
+ case "submit":
2715
+ return `${r2}${import_picocolors3.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
2716
+ case "cancel":
2717
+ return `${r2}${import_picocolors3.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
2718
+ ${import_picocolors3.default.gray(o)}`;
2719
+ default:
2720
+ return `${r2}${import_picocolors3.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
2721
+ ${import_picocolors3.default.cyan(o)} `)}
2722
+ ${import_picocolors3.default.cyan(d2)}
2723
+ `;
2724
+ }
2725
+ } }).prompt();
2726
+ };
2658
2727
  var xe = (t = "") => {
2659
2728
  process.stdout.write(`${import_picocolors3.default.gray(d2)} ${import_picocolors3.default.red(t)}
2660
2729
 
@@ -2687,7 +2756,7 @@ var M2 = { message: (t = "", { symbol: n = import_picocolors3.default.gray(o) }
2687
2756
  }, error: (t) => {
2688
2757
  M2.message(t, { symbol: import_picocolors3.default.red(K2) });
2689
2758
  } };
2690
- var J = `${import_picocolors3.default.gray(o)} `;
2759
+ var J2 = `${import_picocolors3.default.gray(o)} `;
2691
2760
  var Y2 = ({ indicator: t = "dots" } = {}) => {
2692
2761
  const n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r2 = V2 ? 80 : 120, i = process.env.CI === "true";
2693
2762
  let s, c, a = false, l2 = "", $2, g2 = performance.now();
@@ -2777,6 +2846,12 @@ async function confirm(opts) {
2777
2846
  initialValue: opts.initialValue ?? true
2778
2847
  });
2779
2848
  }
2849
+ async function select(opts) {
2850
+ if (!isTTY()) {
2851
+ return opts.initialValue || opts.options[0]?.value;
2852
+ }
2853
+ return await ve(opts);
2854
+ }
2780
2855
  function isCancel(value) {
2781
2856
  return pD(value);
2782
2857
  }
@@ -2819,10 +2894,124 @@ var log = {
2819
2894
  message: (message) => getUiMode(process).plain ? console.log(message) : M2.message(message)
2820
2895
  };
2821
2896
 
2897
+ // src/template-features.ts
2898
+ var FEATURE_DEFINITIONS = {
2899
+ eslint: {
2900
+ templatePath: "templates/features/eslint",
2901
+ packagePatch: {
2902
+ scripts: {
2903
+ lint: "eslint ."
2904
+ },
2905
+ devDependencies: {
2906
+ eslint: "^9.39.2",
2907
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
2908
+ "@typescript-eslint/parser": "^8.53.0"
2909
+ }
2910
+ }
2911
+ },
2912
+ prettier: {
2913
+ templatePath: "templates/features/prettier",
2914
+ packagePatch: {
2915
+ scripts: {
2916
+ format: "prettier --write ."
2917
+ },
2918
+ devDependencies: {
2919
+ prettier: "^3.7.4"
2920
+ }
2921
+ }
2922
+ }
2923
+ };
2924
+ function mergeRecord(target, patch) {
2925
+ if (!patch) {
2926
+ return target;
2927
+ }
2928
+ return { ...target || {}, ...patch };
2929
+ }
2930
+ function selectedTemplateFeaturePaths(options) {
2931
+ const paths = [];
2932
+ if (options.eslint) {
2933
+ paths.push(FEATURE_DEFINITIONS.eslint.templatePath);
2934
+ }
2935
+ if (options.prettier) {
2936
+ paths.push(FEATURE_DEFINITIONS.prettier.templatePath);
2937
+ }
2938
+ return paths;
2939
+ }
2940
+ function applyPackageFeatures(manifest, options) {
2941
+ const nextManifest = {
2942
+ ...manifest,
2943
+ scripts: { ...manifest.scripts || {} },
2944
+ devDependencies: { ...manifest.devDependencies || {} }
2945
+ };
2946
+ for (const featureName of Object.keys(FEATURE_DEFINITIONS)) {
2947
+ if (!options[featureName]) {
2948
+ continue;
2949
+ }
2950
+ const patch = FEATURE_DEFINITIONS[featureName].packagePatch;
2951
+ nextManifest.scripts = mergeRecord(nextManifest.scripts, patch.scripts);
2952
+ nextManifest.devDependencies = mergeRecord(nextManifest.devDependencies, patch.devDependencies);
2953
+ }
2954
+ if (nextManifest.scripts && Object.keys(nextManifest.scripts).length === 0) {
2955
+ delete nextManifest.scripts;
2956
+ }
2957
+ if (nextManifest.devDependencies && Object.keys(nextManifest.devDependencies).length === 0) {
2958
+ delete nextManifest.devDependencies;
2959
+ }
2960
+ return nextManifest;
2961
+ }
2962
+
2963
+ // src/templates.ts
2964
+ var DEFAULT_TEMPLATE = "css";
2965
+ var TEMPLATE_DEFINITIONS = {
2966
+ basic: {
2967
+ name: "basic",
2968
+ label: "Basic",
2969
+ hint: "Blank starter with a single page and minimal CSS",
2970
+ templatePath: "templates/basic",
2971
+ usesTailwind: false
2972
+ },
2973
+ css: {
2974
+ name: "css",
2975
+ label: "CSS",
2976
+ hint: "Multi-page starter with a small curated CSS setup",
2977
+ templatePath: "templates/css",
2978
+ usesTailwind: false
2979
+ },
2980
+ tailwind: {
2981
+ name: "tailwind",
2982
+ label: "Tailwind",
2983
+ hint: "Multi-page starter with Tailwind enabled",
2984
+ templatePath: "templates/tailwind",
2985
+ usesTailwind: true
2986
+ }
2987
+ };
2988
+ var TEMPLATE_ALIASES = {
2989
+ starter: "css",
2990
+ "starter-tailwindcss": "tailwind"
2991
+ };
2992
+ function resolveTemplateName(value) {
2993
+ const normalized = String(value || "").trim().toLowerCase();
2994
+ if (!normalized) {
2995
+ return null;
2996
+ }
2997
+ if (normalized in TEMPLATE_DEFINITIONS) {
2998
+ return normalized;
2999
+ }
3000
+ return TEMPLATE_ALIASES[normalized] || null;
3001
+ }
3002
+ function getTemplateDefinition(templateName) {
3003
+ return TEMPLATE_DEFINITIONS[templateName];
3004
+ }
3005
+ function templateSelectOptions() {
3006
+ return Object.values(TEMPLATE_DEFINITIONS).map((definition) => ({
3007
+ value: definition.name,
3008
+ label: definition.label,
3009
+ hint: definition.hint
3010
+ }));
3011
+ }
3012
+
2822
3013
  // src/index.ts
2823
3014
  var GITHUB_REPO = "zenithbuild/framework";
2824
- var DEFAULT_TEMPLATE = "examples/starter";
2825
- var TAILWIND_TEMPLATE = "examples/starter-tailwindcss";
2826
3015
  var __filename2 = fileURLToPath(import.meta.url);
2827
3016
  var __dirname2 = path.dirname(__filename2);
2828
3017
  function getCliVersion() {
@@ -2845,6 +3034,15 @@ function resolveLocalTemplatePath(templatePath) {
2845
3034
  const candidate = path.resolve(__dirname2, "..", templatePath);
2846
3035
  return fs.existsSync(candidate) ? candidate : null;
2847
3036
  }
3037
+ function copyDirectoryContents(sourceDir, targetDir) {
3038
+ fs.mkdirSync(targetDir, { recursive: true });
3039
+ for (const entry of fs.readdirSync(sourceDir)) {
3040
+ fs.cpSync(path.join(sourceDir, entry), path.join(targetDir, entry), {
3041
+ recursive: true,
3042
+ force: true
3043
+ });
3044
+ }
3045
+ }
2848
3046
  function detectPackageManager() {
2849
3047
  const userAgent = process.env.npm_config_user_agent || "";
2850
3048
  if (userAgent.includes("bun"))
@@ -2875,16 +3073,22 @@ function hasGit() {
2875
3073
  return false;
2876
3074
  }
2877
3075
  }
2878
- async function downloadTemplate(targetDir, templatePath) {
2879
- const localTemplatePath = resolveLocalTemplatePath(templatePath);
3076
+ async function downloadTemplate(targetDir, templatePaths) {
3077
+ const localTemplatePaths = templatePaths.map((templatePath) => ({
3078
+ templatePath,
3079
+ localPath: resolveLocalTemplatePath(templatePath)
3080
+ }));
2880
3081
  const forceLocal = process.env.CREATE_ZENITH_TEMPLATE_MODE === "local" || flagEnabled2(process.env.CREATE_ZENITH_OFFLINE);
2881
3082
  const preferLocal = process.env.CREATE_ZENITH_PREFER_LOCAL !== "0";
2882
- if (localTemplatePath && (forceLocal || preferLocal)) {
2883
- fs.cpSync(localTemplatePath, targetDir, { recursive: true });
3083
+ if (localTemplatePaths.every((entry) => entry.localPath) && (forceLocal || preferLocal)) {
3084
+ for (const entry of localTemplatePaths) {
3085
+ copyDirectoryContents(entry.localPath, targetDir);
3086
+ }
2884
3087
  return;
2885
3088
  }
2886
- if (forceLocal && !localTemplatePath) {
2887
- throw new Error(`Local template not found: ${templatePath}`);
3089
+ if (forceLocal) {
3090
+ const missingPath = localTemplatePaths.find((entry) => !entry.localPath)?.templatePath;
3091
+ throw new Error(`Local template not found: ${missingPath}`);
2888
3092
  }
2889
3093
  const tempDir = path.join(os2.tmpdir(), `zenith-template-${Date.now()}`);
2890
3094
  try {
@@ -2892,13 +3096,15 @@ async function downloadTemplate(targetDir, templatePath) {
2892
3096
  execSync(`git clone --depth 1 --filter=blob:none --sparse https://github.com/${GITHUB_REPO}.git "${tempDir}"`, {
2893
3097
  stdio: "pipe"
2894
3098
  });
2895
- const repoTemplatePath = `packages/create-zenith/${templatePath}`;
2896
- execSync(`git sparse-checkout set ${repoTemplatePath}`, {
3099
+ const repoTemplatePaths = templatePaths.map((templatePath) => `packages/create-zenith/${templatePath}`);
3100
+ execSync(`git sparse-checkout set ${repoTemplatePaths.join(" ")}`, {
2897
3101
  cwd: tempDir,
2898
3102
  stdio: "pipe"
2899
3103
  });
2900
- const templateSource = path.join(tempDir, repoTemplatePath);
2901
- fs.cpSync(templateSource, targetDir, { recursive: true });
3104
+ for (const repoTemplatePath of repoTemplatePaths) {
3105
+ const templateSource = path.join(tempDir, repoTemplatePath);
3106
+ copyDirectoryContents(templateSource, targetDir);
3107
+ }
2902
3108
  } else {
2903
3109
  const tarballUrl = `https://github.com/${GITHUB_REPO}/archive/refs/heads/main.tar.gz`;
2904
3110
  const tarballPath = path.join(os2.tmpdir(), `zenith-${Date.now()}.tar.gz`);
@@ -2909,8 +3115,10 @@ async function downloadTemplate(targetDir, templatePath) {
2909
3115
  if (!extractedDir) {
2910
3116
  throw new Error("Failed to extract template from GitHub");
2911
3117
  }
2912
- const templateSource = path.join(tempDir, extractedDir, "packages/create-zenith", templatePath);
2913
- fs.cpSync(templateSource, targetDir, { recursive: true });
3118
+ for (const templatePath of templatePaths) {
3119
+ const templateSource = path.join(tempDir, extractedDir, "packages/create-zenith", templatePath);
3120
+ copyDirectoryContents(templateSource, targetDir);
3121
+ }
2914
3122
  fs.unlinkSync(tarballPath);
2915
3123
  }
2916
3124
  } finally {
@@ -2919,7 +3127,30 @@ async function downloadTemplate(targetDir, templatePath) {
2919
3127
  }
2920
3128
  }
2921
3129
  }
2922
- async function gatherOptions(providedName, withTailwind) {
3130
+ function readOptionOverride(name) {
3131
+ const value = process.env[name];
3132
+ if (value == null || value.trim() === "") {
3133
+ return;
3134
+ }
3135
+ return flagEnabled2(value);
3136
+ }
3137
+ function readTemplateOverride(name) {
3138
+ const value = process.env[name];
3139
+ if (value == null || value.trim() === "") {
3140
+ return;
3141
+ }
3142
+ const templateName = resolveTemplateName(value);
3143
+ if (!templateName) {
3144
+ throw new Error(`Unsupported template "${value}". Use basic, css, or tailwind.`);
3145
+ }
3146
+ return templateName;
3147
+ }
3148
+ async function gatherOptions(providedName, requestedTemplate) {
3149
+ const eslintOverride = readOptionOverride("CREATE_ZENITH_ESLINT");
3150
+ const prettierOverride = readOptionOverride("CREATE_ZENITH_PRETTIER");
3151
+ const pathAliasOverride = readOptionOverride("CREATE_ZENITH_PATH_ALIAS");
3152
+ const templateOverride = readTemplateOverride("CREATE_ZENITH_TEMPLATE");
3153
+ const selectedTemplate = requestedTemplate || templateOverride || DEFAULT_TEMPLATE;
2923
3154
  let name = providedName;
2924
3155
  if (!name) {
2925
3156
  const nameResult = await text({
@@ -2957,72 +3188,56 @@ async function gatherOptions(providedName, withTailwind) {
2957
3188
  log.info("Non-interactive mode detected, using defaults...");
2958
3189
  return {
2959
3190
  name,
2960
- eslint: true,
2961
- prettier: true,
2962
- pathAlias: true,
2963
- tailwind: withTailwind ?? false
3191
+ eslint: eslintOverride ?? true,
3192
+ prettier: prettierOverride ?? true,
3193
+ pathAlias: pathAliasOverride ?? true,
3194
+ template: selectedTemplate
2964
3195
  };
2965
3196
  }
2966
- const tailwindResult = withTailwind !== undefined ? withTailwind : await confirm({
2967
- message: "Add Tailwind CSS for styling?",
2968
- initialValue: true
3197
+ const templateResult = requestedTemplate ?? templateOverride ?? await select({
3198
+ message: "Choose a starter template",
3199
+ options: templateSelectOptions(),
3200
+ initialValue: DEFAULT_TEMPLATE
2969
3201
  });
2970
- if (isCancel(tailwindResult))
3202
+ if (requestedTemplate === undefined && templateOverride === undefined && isCancel(templateResult)) {
2971
3203
  handleCancel();
2972
- const eslintResult = await confirm({
3204
+ }
3205
+ const eslintResult = eslintOverride ?? await confirm({
2973
3206
  message: "Add ESLint for code linting?",
2974
3207
  initialValue: true
2975
3208
  });
2976
- if (isCancel(eslintResult))
3209
+ if (eslintOverride === undefined && isCancel(eslintResult))
2977
3210
  handleCancel();
2978
- const prettierResult = await confirm({
3211
+ const prettierResult = prettierOverride ?? await confirm({
2979
3212
  message: "Add Prettier for code formatting?",
2980
3213
  initialValue: true
2981
3214
  });
2982
- if (isCancel(prettierResult))
3215
+ if (prettierOverride === undefined && isCancel(prettierResult))
2983
3216
  handleCancel();
2984
- const pathAliasResult = await confirm({
3217
+ const pathAliasResult = pathAliasOverride ?? await confirm({
2985
3218
  message: "Add TypeScript path alias (@/*)?",
2986
3219
  initialValue: true
2987
3220
  });
2988
- if (isCancel(pathAliasResult))
3221
+ if (pathAliasOverride === undefined && isCancel(pathAliasResult))
2989
3222
  handleCancel();
2990
3223
  return {
2991
3224
  name,
2992
3225
  eslint: eslintResult,
2993
3226
  prettier: prettierResult,
2994
3227
  pathAlias: pathAliasResult,
2995
- tailwind: tailwindResult
3228
+ template: templateResult
2996
3229
  };
2997
3230
  }
2998
3231
  async function createProject(options) {
2999
3232
  const targetDir = path.resolve(process.cwd(), options.name);
3000
- const templatePath = options.tailwind ? TAILWIND_TEMPLATE : DEFAULT_TEMPLATE;
3001
- await downloadTemplate(targetDir, templatePath);
3233
+ const templatePath = getTemplateDefinition(options.template).templatePath;
3234
+ const templateFeaturePaths = selectedTemplateFeaturePaths(options);
3235
+ await downloadTemplate(targetDir, [templatePath, ...templateFeaturePaths]);
3002
3236
  const pkgPath = path.join(targetDir, "package.json");
3003
3237
  if (fs.existsSync(pkgPath)) {
3004
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
3238
+ const pkg = applyPackageFeatures(JSON.parse(fs.readFileSync(pkgPath, "utf8")), options);
3005
3239
  pkg.name = options.name;
3006
3240
  pkg.version = "0.1.0";
3007
- if (!options.eslint) {
3008
- delete pkg.devDependencies?.["eslint"];
3009
- delete pkg.devDependencies?.["@typescript-eslint/eslint-plugin"];
3010
- delete pkg.devDependencies?.["@typescript-eslint/parser"];
3011
- delete pkg.scripts?.lint;
3012
- const eslintPath = path.join(targetDir, ".eslintrc.json");
3013
- if (fs.existsSync(eslintPath))
3014
- fs.unlinkSync(eslintPath);
3015
- }
3016
- if (!options.prettier) {
3017
- delete pkg.devDependencies?.["prettier"];
3018
- delete pkg.scripts?.format;
3019
- const prettierRc = path.join(targetDir, ".prettierrc");
3020
- const prettierIgnore = path.join(targetDir, ".prettierignore");
3021
- if (fs.existsSync(prettierRc))
3022
- fs.unlinkSync(prettierRc);
3023
- if (fs.existsSync(prettierIgnore))
3024
- fs.unlinkSync(prettierIgnore);
3025
- }
3026
3241
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4));
3027
3242
  }
3028
3243
  const tsconfigPath = path.join(targetDir, "tsconfig.json");
@@ -3040,10 +3255,10 @@ async function createProject(options) {
3040
3255
  fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 4));
3041
3256
  }
3042
3257
  }
3043
- async function create(appName, withTailwind) {
3258
+ async function create(appName, requestedTemplate) {
3044
3259
  await intro();
3045
- const options = await gatherOptions(appName, withTailwind);
3046
- const templateLabel = options.tailwind ? "starter-tailwindcss" : "starter";
3260
+ const options = await gatherOptions(appName, requestedTemplate);
3261
+ const templateLabel = getTemplateDefinition(options.template).name;
3047
3262
  showScaffoldSummary(options.name, templateLabel);
3048
3263
  console.log("");
3049
3264
  log.step(`Creating ${bold(options.name)}...`);
@@ -3088,7 +3303,8 @@ if (args.includes("--help") || args.includes("-h")) {
3088
3303
  console.log("Options:");
3089
3304
  console.log(" -h, --help Show this help message");
3090
3305
  console.log(" -v, --version Show version number");
3091
- console.log(" --with-tailwind Initialize with Tailwind CSS v4 template");
3306
+ console.log(" --template <name> Choose template: basic, css, or tailwind");
3307
+ console.log(" --with-tailwind Alias for --template tailwind");
3092
3308
  console.log("");
3093
3309
  console.log("Examples:");
3094
3310
  console.log(" npx create-zenith my-app");
@@ -3101,9 +3317,30 @@ if (args.includes("--version") || args.includes("-v")) {
3101
3317
  console.log(`create-zenith ${VERSION}`);
3102
3318
  process.exit(0);
3103
3319
  }
3104
- var projectName = args.find((arg) => !arg.startsWith("-"));
3105
- var withTailwind = args.includes("--with-tailwind") ? true : undefined;
3106
- create(projectName, withTailwind).catch((err) => {
3320
+ var projectName;
3321
+ var requestedTemplate;
3322
+ for (let index = 0;index < args.length; index += 1) {
3323
+ const arg = args[index];
3324
+ if (arg === "--with-tailwind") {
3325
+ requestedTemplate = "tailwind";
3326
+ continue;
3327
+ }
3328
+ if (arg === "--template") {
3329
+ const templateValue = args[index + 1];
3330
+ const templateName = resolveTemplateName(templateValue);
3331
+ if (!templateName) {
3332
+ error(`Unsupported template "${templateValue || ""}". Use basic, css, or tailwind.`);
3333
+ process.exit(1);
3334
+ }
3335
+ requestedTemplate = templateName;
3336
+ index += 1;
3337
+ continue;
3338
+ }
3339
+ if (!arg.startsWith("-") && !projectName) {
3340
+ projectName = arg;
3341
+ }
3342
+ }
3343
+ create(projectName, requestedTemplate).catch((err) => {
3107
3344
  const mode = getUiMode(process);
3108
3345
  if (mode.plain) {
3109
3346
  console.log("ERROR: SCAFFOLD_ERROR");