create-stackit 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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/bin/index.js +64 -0
  4. package/package.json +43 -0
  5. package/src/choices.js +63 -0
  6. package/src/generator.js +133 -0
  7. package/src/injectors/addons.js +311 -0
  8. package/src/injectors/structure.js +51 -0
  9. package/src/pm.js +94 -0
  10. package/src/prompts.js +179 -0
  11. package/src/utils.js +18 -0
  12. package/templates/next-js-redux-mui/README.md +15 -0
  13. package/templates/next-js-redux-mui/app/layout.jsx +12 -0
  14. package/templates/next-js-redux-mui/app/page.jsx +15 -0
  15. package/templates/next-js-redux-mui/app/providers.jsx +16 -0
  16. package/templates/next-js-redux-mui/package.json +22 -0
  17. package/templates/next-js-redux-mui/src/store/counterSlice.js +13 -0
  18. package/templates/next-js-redux-mui/src/store/store.js +8 -0
  19. package/templates/next-js-redux-mui/src/theme.js +9 -0
  20. package/templates/next-js-redux-shadcn/README.md +15 -0
  21. package/templates/next-js-redux-shadcn/app/globals.css +3 -0
  22. package/templates/next-js-redux-shadcn/app/layout.jsx +13 -0
  23. package/templates/next-js-redux-shadcn/app/page.jsx +15 -0
  24. package/templates/next-js-redux-shadcn/app/providers.jsx +11 -0
  25. package/templates/next-js-redux-shadcn/package.json +23 -0
  26. package/templates/next-js-redux-shadcn/src/store/counterSlice.js +13 -0
  27. package/templates/next-js-redux-shadcn/src/store/store.js +8 -0
  28. package/templates/next-js-zustand-mui/README.md +15 -0
  29. package/templates/next-js-zustand-mui/app/layout.jsx +16 -0
  30. package/templates/next-js-zustand-mui/app/page.jsx +18 -0
  31. package/templates/next-js-zustand-mui/package.json +21 -0
  32. package/templates/next-js-zustand-mui/src/theme.js +9 -0
  33. package/templates/next-js-zustand-shadcn/README.md +15 -0
  34. package/templates/next-js-zustand-shadcn/app/globals.css +3 -0
  35. package/templates/next-js-zustand-shadcn/app/layout.jsx +10 -0
  36. package/templates/next-js-zustand-shadcn/app/page.jsx +18 -0
  37. package/templates/next-js-zustand-shadcn/package.json +22 -0
  38. package/templates/next-ts-redux-mui/README.md +15 -0
  39. package/templates/next-ts-redux-mui/app/layout.tsx +14 -0
  40. package/templates/next-ts-redux-mui/app/page.tsx +15 -0
  41. package/templates/next-ts-redux-mui/app/providers.tsx +16 -0
  42. package/templates/next-ts-redux-mui/package.json +27 -0
  43. package/templates/next-ts-redux-mui/src/store/counterSlice.ts +16 -0
  44. package/templates/next-ts-redux-mui/src/store/store.ts +11 -0
  45. package/templates/next-ts-redux-mui/src/theme.ts +9 -0
  46. package/templates/next-ts-redux-shadcn/README.md +15 -0
  47. package/templates/next-ts-redux-shadcn/app/globals.css +3 -0
  48. package/templates/next-ts-redux-shadcn/app/layout.tsx +15 -0
  49. package/templates/next-ts-redux-shadcn/app/page.tsx +15 -0
  50. package/templates/next-ts-redux-shadcn/app/providers.tsx +11 -0
  51. package/templates/next-ts-redux-shadcn/package.json +27 -0
  52. package/templates/next-ts-redux-shadcn/src/store/counterSlice.ts +16 -0
  53. package/templates/next-ts-redux-shadcn/src/store/store.ts +11 -0
  54. package/templates/next-ts-zustand-mui/README.md +15 -0
  55. package/templates/next-ts-zustand-mui/app/layout.tsx +18 -0
  56. package/templates/next-ts-zustand-mui/app/page.tsx +18 -0
  57. package/templates/next-ts-zustand-mui/package.json +26 -0
  58. package/templates/next-ts-zustand-mui/src/theme.ts +9 -0
  59. package/templates/next-ts-zustand-shadcn/README.md +15 -0
  60. package/templates/next-ts-zustand-shadcn/app/globals.css +3 -0
  61. package/templates/next-ts-zustand-shadcn/app/layout.tsx +12 -0
  62. package/templates/next-ts-zustand-shadcn/app/page.tsx +18 -0
  63. package/templates/next-ts-zustand-shadcn/package.json +26 -0
  64. package/templates/vite-js-redux-mui/README.md +15 -0
  65. package/templates/vite-js-redux-mui/package.json +24 -0
  66. package/templates/vite-js-redux-mui/src/App.jsx +14 -0
  67. package/templates/vite-js-redux-mui/src/main.jsx +18 -0
  68. package/templates/vite-js-redux-mui/src/store/counterSlice.js +13 -0
  69. package/templates/vite-js-redux-mui/src/store/store.js +8 -0
  70. package/templates/vite-js-redux-mui/src/theme.js +9 -0
  71. package/templates/vite-js-redux-shadcn/README.md +15 -0
  72. package/templates/vite-js-redux-shadcn/package.json +24 -0
  73. package/templates/vite-js-redux-shadcn/src/App.jsx +14 -0
  74. package/templates/vite-js-redux-shadcn/src/index.css +3 -0
  75. package/templates/vite-js-redux-shadcn/src/main.jsx +14 -0
  76. package/templates/vite-js-redux-shadcn/src/store/counterSlice.js +13 -0
  77. package/templates/vite-js-redux-shadcn/src/store/store.js +8 -0
  78. package/templates/vite-js-zustand-mui/README.md +15 -0
  79. package/templates/vite-js-zustand-mui/package.json +23 -0
  80. package/templates/vite-js-zustand-mui/src/App.jsx +17 -0
  81. package/templates/vite-js-zustand-mui/src/main.jsx +14 -0
  82. package/templates/vite-js-zustand-mui/src/theme.js +9 -0
  83. package/templates/vite-js-zustand-shadcn/README.md +15 -0
  84. package/templates/vite-js-zustand-shadcn/package.json +23 -0
  85. package/templates/vite-js-zustand-shadcn/src/App.jsx +17 -0
  86. package/templates/vite-js-zustand-shadcn/src/index.css +3 -0
  87. package/templates/vite-js-zustand-shadcn/src/main.jsx +10 -0
  88. package/templates/vite-ts-redux-mui/README.md +15 -0
  89. package/templates/vite-ts-redux-mui/package.json +27 -0
  90. package/templates/vite-ts-redux-mui/src/App.tsx +14 -0
  91. package/templates/vite-ts-redux-mui/src/main.tsx +18 -0
  92. package/templates/vite-ts-redux-mui/src/store/counterSlice.ts +16 -0
  93. package/templates/vite-ts-redux-mui/src/store/store.ts +11 -0
  94. package/templates/vite-ts-redux-mui/src/theme.ts +9 -0
  95. package/templates/vite-ts-redux-shadcn/README.md +15 -0
  96. package/templates/vite-ts-redux-shadcn/package.json +27 -0
  97. package/templates/vite-ts-redux-shadcn/src/App.tsx +14 -0
  98. package/templates/vite-ts-redux-shadcn/src/index.css +3 -0
  99. package/templates/vite-ts-redux-shadcn/src/main.tsx +14 -0
  100. package/templates/vite-ts-redux-shadcn/src/store/counterSlice.ts +16 -0
  101. package/templates/vite-ts-redux-shadcn/src/store/store.ts +11 -0
  102. package/templates/vite-ts-zustand-mui/README.md +15 -0
  103. package/templates/vite-ts-zustand-mui/package.json +26 -0
  104. package/templates/vite-ts-zustand-mui/src/App.tsx +17 -0
  105. package/templates/vite-ts-zustand-mui/src/main.tsx +14 -0
  106. package/templates/vite-ts-zustand-mui/src/theme.ts +9 -0
  107. package/templates/vite-ts-zustand-shadcn/README.md +15 -0
  108. package/templates/vite-ts-zustand-shadcn/package.json +23 -0
  109. package/templates/vite-ts-zustand-shadcn/src/main.tsx +2 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YOUR_NAME
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # create-stackit
2
+
3
+ Pragmatic React / Next.js starter generator. Answer a few questions and get a
4
+ production-ready project with your exact stack — no manual wiring required.
5
+
6
+ ## Quick start
7
+
8
+ ```bash
9
+ npm create stackit@latest
10
+ # or
11
+ npx create-stackit
12
+ # or pass a project name directly
13
+ npx create-stackit my-app
14
+ ```
15
+
16
+ The CLI walks you through every choice interactively.
17
+
18
+ ---
19
+
20
+ ## What gets generated
21
+
22
+ Each project is assembled from one of **16 curated templates** covering every
23
+ combination of the four axes below, then enriched by whichever add-ons you
24
+ select.
25
+
26
+ | Axis | Options |
27
+ |---|---|
28
+ | Framework | Vite + React · Next.js |
29
+ | Language | TypeScript · JavaScript |
30
+ | State | Zustand · Redux Toolkit |
31
+ | UI | shadcn/ui + Tailwind · Material UI |
32
+
33
+ ### Folder structure
34
+
35
+ | Style | Layout |
36
+ |---|---|
37
+ | **Modular** *(default)* | `src/features/<name>/` — self-contained feature modules |
38
+ | **Atomic** | `src/components/atoms / molecules / organisms / templates` |
39
+
40
+ ### Add-ons (toggle at prompt time)
41
+
42
+ | Add-on | What it injects |
43
+ |---|---|
44
+ | TanStack Query | `QueryClient`, `useApiQuery`, `useApiMutation` hooks |
45
+ | Axios | Pre-configured `api` client with auth + error interceptors |
46
+ | react-hook-form + zod | Form library wired with a zod resolver |
47
+ | react-hot-toast | `<AppToaster>` component + API-error event bridge |
48
+ | Vitest + RTL | Test scripts, jsdom environment, `@testing-library` deps |
49
+ | ESLint + Prettier | Lint/format scripts, `eslint-config-prettier` |
50
+ | Custom hooks | `useDebounce`, `useThrottle`, `useToggle`, `usePrevious` |
51
+ | Auth boilerplate | Scaffold for JWT + refresh token flow |
52
+
53
+ ---
54
+
55
+ ## CLI flags
56
+
57
+ Every prompt can be pre-answered with a flag — the CLI skips those questions
58
+ and goes straight to the rest.
59
+
60
+ ```
61
+ Usage: create-stackit [project-name] [options]
62
+
63
+ Arguments:
64
+ project-name name of the project directory
65
+
66
+ Options:
67
+ -f, --framework <fw> vite | next
68
+ -l, --language <lang> ts | js
69
+ -s, --state <state> zustand | redux
70
+ -u, --ui <ui> shadcn | mui
71
+ --structure <s> atomic | modular
72
+ --pm <pm> npm | pnpm | yarn (auto-detected if omitted)
73
+ --skip-install skip dependency installation
74
+ --skip-git skip git initialization
75
+ -V, --version output the version number
76
+ -h, --help display help
77
+ ```
78
+
79
+ ### Examples
80
+
81
+ ```bash
82
+ # Fully interactive
83
+ npx create-stackit
84
+
85
+ # Pre-fill name + framework, answer the rest interactively
86
+ npx create-stackit my-app --framework vite
87
+
88
+ # Fully non-interactive (no prompts at all)
89
+ npx create-stackit my-app \
90
+ --framework next \
91
+ --language ts \
92
+ --state redux \
93
+ --ui shadcn \
94
+ --structure modular \
95
+ --pm pnpm
96
+
97
+ # Skip heavy steps during development
98
+ npx create-stackit my-app --skip-install --skip-git
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Package manager support
104
+
105
+ `create-stackit` detects whichever package manager you are already using
106
+ (by checking lock files in the current directory) and uses it automatically.
107
+ Pass `--pm` to override.
108
+
109
+ | Flag | Installs with | Runs scripts with |
110
+ |---|---|---|
111
+ | `npm` | `npm install` | `npm run dev` |
112
+ | `pnpm` | `pnpm install` | `pnpm dev` |
113
+ | `yarn` | `yarn` | `yarn dev` |
114
+
115
+ ---
116
+
117
+ ## After generation
118
+
119
+ ```bash
120
+ cd my-app
121
+ ```
122
+
123
+ If you passed `--skip-install`, install dependencies first:
124
+
125
+ ```bash
126
+ npm install # npm
127
+ pnpm install # pnpm
128
+ yarn # yarn
129
+ ```
130
+
131
+ Start the dev server:
132
+
133
+ ```bash
134
+ npm run dev # npm
135
+ pnpm dev # pnpm
136
+ yarn dev # yarn
137
+ ```
138
+
139
+ Build for production:
140
+
141
+ ```bash
142
+ npm run build # npm
143
+ pnpm build # pnpm
144
+ yarn build # yarn
145
+ ```
146
+
147
+ For **Next.js** projects — serve the production build:
148
+
149
+ ```bash
150
+ npm run start # npm
151
+ pnpm start # pnpm
152
+ yarn start # yarn
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Requirements
158
+
159
+ - **Node.js** `>=18.18.0` (LTS 20.x recommended)
160
+ - **Git** — optional, only needed when "Initialize a git repository?" is accepted
161
+
162
+ ---
163
+
164
+ ## Template matrix
165
+
166
+ All 16 template keys follow the pattern `{framework}-{lang}-{state}-{ui}`:
167
+
168
+ ```
169
+ vite-ts-zustand-shadcn vite-ts-zustand-mui
170
+ vite-ts-redux-shadcn vite-ts-redux-mui
171
+ vite-js-zustand-shadcn vite-js-zustand-mui
172
+ vite-js-redux-shadcn vite-js-redux-mui
173
+
174
+ next-ts-zustand-shadcn next-ts-zustand-mui
175
+ next-ts-redux-shadcn next-ts-redux-mui
176
+ next-js-zustand-shadcn next-js-zustand-mui
177
+ next-js-redux-shadcn next-js-redux-mui
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Contributing
183
+
184
+ 1. Fork and clone the repo.
185
+ 2. `npm install`
186
+ 3. `npm run test:local` — scaffold a project into `./test-app` and verify it.
187
+ 4. To add a new template, create `templates/<key>/` and add the key to
188
+ `SUPPORTED_COMBOS` in `src/choices.js`.
189
+
190
+ ---
191
+
192
+ ## License
193
+
194
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { runPrompts } from '../src/prompts.js';
6
+ import { generateProject } from '../src/generator.js';
7
+ import { logger, printBanner } from '../src/utils.js';
8
+ import {runScript, installCommandString} from '../src/pm.js'
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('create-stackit')
14
+ .description('Pragmatic Front-End starter generator')
15
+ .version('0.1.0')
16
+ .argument('[project-name]', 'name of the project directory')
17
+ .option('-f, --framework <framework>', 'vite | next')
18
+ .option('l, --language <language>', 'js | ts')
19
+ .option('-s, --state <state>', 'zustand | redux')
20
+ .option('-u, --ui <ui>', 'shadcn | mui')
21
+ .option('--structure <structure>', 'atomic | modular')
22
+ .option('--pm <pm>', 'npm | pnpm | yarn (auto-detected if omitted)')
23
+ .option('--skip-install', 'skip dependency installation')
24
+ .option('--skip-git', 'skip git initialization')
25
+ .action(async (projectName, options) => {
26
+ try {
27
+ printBanner();
28
+
29
+ // Merge CLI flags with interactive prompts
30
+ const answers = await runPrompts({
31
+ projectName,
32
+ framework: options.framework,
33
+ language: options.language,
34
+ state: options.state,
35
+ ui: options.ui,
36
+ structure: options.structure,
37
+ packageManager: options.packageManager
38
+ });
39
+
40
+ await generateProject({
41
+ ...answers,
42
+ skipInstall: options.skipInstall,
43
+ skipGit: options.skipGit,
44
+ });
45
+
46
+ const pm = answers.packageManager || 'npm';
47
+ logger.success('\n✨ Project created successfully!\n');
48
+ logger.info(` cd ${answers.projectName}`);
49
+ if (options.skipInstall) {
50
+ logger.info(`${installCommandString(pm)}`);
51
+ }
52
+ logger.info(` ${runScript(pm, 'dev')}\n`);
53
+ } catch (err) {
54
+ if (err && err.isCancelled) {
55
+ logger.warn('\n✋ Cancelled.\n');
56
+ process.exit(0);
57
+ }
58
+ logger.error('\n❌ Failed to create project:\n');
59
+ console.error(chalk.red(err?.stack || err?.message || err));
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ program.parseAsync(process.argv);
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "create-stackit",
3
+ "version": "0.1.0",
4
+ "description": "Pragmatic React/Next.js starter generator with curated, production-grade templates",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-stackit": "./bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "start": "node ./bin/index.js",
16
+ "test:local": "node ./bin/index.js test-app"
17
+ },
18
+ "keywords": [
19
+ "react",
20
+ "vite",
21
+ "next",
22
+ "starter",
23
+ "boilerplate",
24
+ "cli",
25
+ "zustand",
26
+ "redux",
27
+ "shadcn",
28
+ "tanstack-query"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "chalk": "^5.3.0",
36
+ "commander": "^12.1.0",
37
+ "execa": "^9.5.1",
38
+ "fs-extra": "^11.2.0",
39
+ "ora": "^8.1.1",
40
+ "prompts": "^2.4.2",
41
+ "validate-npm-package-name": "^6.0.0"
42
+ }
43
+ }
package/src/choices.js ADDED
@@ -0,0 +1,63 @@
1
+ // Centralized choices. Adding a new template? Update SUPPORTED_COMBOS too.
2
+ export const PINNED_NODE_VERSION = '20.18.0';
3
+ export const ENGINES_NODE_RANGE = '>=18.18.0';
4
+
5
+ export const CHOICES = {
6
+ framework: [
7
+ { title: 'Vite + React', value: 'vite', description: 'SPA, fast HMR, lightweight' },
8
+ { title: 'Next.js', value: 'next', description: 'SSR/SSG, app router, SEO-ready' },
9
+ ],
10
+ language: [
11
+ { title: 'TypeScript', value: 'ts', description: 'Type-safe, IDE-friendly' },
12
+ { title: 'JavaScript', value: 'js', description: 'No compilation step' },
13
+ ],
14
+ state: [
15
+ { title: 'Zustand', value: 'zustand', description: 'Lightweight, modern, minimal boilerplate' },
16
+ { title: 'Redux Toolkit', value: 'redux', description: 'Enterprise, predictable, devtools-rich' },
17
+ ],
18
+ ui: [
19
+ { title: 'shadcn/ui + Tailwind', value: 'shadcn', description: 'Customizable, copy-paste components' },
20
+ { title: 'Material UI', value: 'mui', description: 'Battle-tested, large component library' },
21
+ ],
22
+ structure: [
23
+ { title: 'Atomic', value: 'atomic', description: 'atoms / molecules / organisms' },
24
+ { title: 'Modular (feature-based)', value: 'modular', description: 'features/auth, features/dashboard...' },
25
+ ],
26
+ packageManager: [
27
+ { title: 'npm', value: 'npm' },
28
+ { title: 'pnpm', value: 'pnpm', description: 'Fast, disk-efficient' },
29
+ { title: 'yarn', value: 'yarn', description: 'Classic v1 (stable)' },
30
+ ],
31
+ addons: [
32
+ { title: 'TanStack Query (data fetching)', value: 'tanstack-query', selected: true },
33
+ { title: 'Axios + interceptors wrapper', value: 'axios', selected: true },
34
+ { title: 'react-hook-form + zod', value: 'forms', selected: true },
35
+ { title: 'react-hot-toast (toasts)', value: 'toast', selected: true },
36
+ { title: 'Vitest + RTL (testing)', value: 'testing', selected: false },
37
+ { title: 'ESLint + Prettier', value: 'lint', selected: true },
38
+ { title: 'Custom hooks library (useDebounce, useThrottle, etc.)', value: 'hooks', selected: true },
39
+ { title: 'Auth boilerplate (JWT + refresh)', value: 'auth', selected: false },
40
+ ],
41
+ };
42
+
43
+ // All 16 combos: vite|next × ts|js × zustand|redux × shadcn|mui
44
+ export const SUPPORTED_COMBOS = new Set([
45
+ 'vite-ts-zustand-shadcn',
46
+ 'vite-ts-zustand-mui',
47
+ 'vite-ts-redux-shadcn',
48
+ 'vite-ts-redux-mui',
49
+ 'vite-js-zustand-shadcn',
50
+ 'vite-js-zustand-mui',
51
+ 'vite-js-redux-shadcn',
52
+ 'vite-js-redux-mui',
53
+ 'next-ts-zustand-shadcn',
54
+ 'next-ts-zustand-mui',
55
+ 'next-ts-redux-shadcn',
56
+ 'next-ts-redux-mui',
57
+ 'next-js-zustand-shadcn',
58
+ 'next-js-zustand-mui',
59
+ 'next-js-redux-shadcn',
60
+ 'next-js-redux-mui',
61
+ ]);
62
+
63
+ export const FALLBACK_MAP = {};
@@ -0,0 +1,133 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import fs from 'fs-extra';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { execa } from 'execa';
7
+ import { SUPPORTED_COMBOS, FALLBACK_MAP, ENGINES_NODE_RANGE } from './choices.js';
8
+ import { logger } from './utils.js';
9
+ import { applyAddons } from './injectors/addons.js';
10
+ import { applyStructure } from './injectors/structure.js';
11
+ import { getInstallCommand, getPackageManagerField } from './pm.js';
12
+
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
16
+
17
+ export async function generateProject(opts) {
18
+ const {
19
+ projectName,
20
+ targetDir,
21
+ templateKey,
22
+ framework,
23
+ state,
24
+ ui,
25
+ structure,
26
+ addons = [],
27
+ initGit,
28
+ skipInstall,
29
+ skipGit,
30
+ packageManager = 'npm',
31
+ } = opts;
32
+
33
+ // 1. Resolve template (with fallback)
34
+ const resolvedTemplate = resolveTemplate(templateKey);
35
+ if (resolvedTemplate.fallback) {
36
+ logger.warn(
37
+ `Combo "${templateKey}" not directly supported. Using "${resolvedTemplate.key}" as base and patching diff.`
38
+ );
39
+ }
40
+
41
+ const templatePath = path.join(TEMPLATES_DIR, resolvedTemplate.key);
42
+ if (!fs.existsSync(templatePath)) {
43
+ throw new Error(
44
+ `Template not found at ${templatePath}. Did you forget to scaffold it?`
45
+ );
46
+ }
47
+
48
+ // 2. Copy template
49
+ const copySpinner = ora('Copying template files...').start();
50
+ try {
51
+ await fs.ensureDir(targetDir);
52
+ await fs.copy(templatePath, targetDir, {
53
+ filter: (src) => !src.includes('node_modules') && !src.endsWith('.lock'),
54
+ });
55
+ copySpinner.succeed('Template files copied');
56
+ } catch (err) {
57
+ copySpinner.fail('Copy failed');
58
+ throw err;
59
+ }
60
+
61
+ // 3. Patch package.json with project name
62
+ await patchPackageJson(targetDir, projectName, packageManager);
63
+
64
+ // 3a. Write .nvmrc for Node version pinning
65
+ await fs.outputFile(path.join(targetDir, '.nvmrc'), `${`PINNED_NODE_VERSION`}\n`)
66
+
67
+ // 4. Apply structure (atomic vs modular folder layout)
68
+ await applyStructure({ targetDir, framework, structure });
69
+
70
+ // 5. Apply add-ons (axios, query, forms, etc.)
71
+ await applyAddons({ targetDir, framework, state, ui, addons });
72
+
73
+ // 6. If fallback was used, swap UI / state
74
+ if (resolvedTemplate.fallback) {
75
+ logger.info(chalk.dim(' ↳ Patching UI/state diff...'));
76
+ // Hook for diff-patcher; stubbed for now
77
+ }
78
+
79
+ // 7. Install
80
+ if (!skipInstall) {
81
+ const installSpinner = ora('Installing dependencies (this may take a minute)...').start();
82
+ try {
83
+ await execa('npm', ['install'], { cwd: targetDir });
84
+ installSpinner.succeed('Dependencies installed');
85
+ } catch (err) {
86
+ installSpinner.fail(
87
+ `${packageManager} install failed — you can run it manually`
88
+ );
89
+ logger.warn(err.shortMessage || err.message);
90
+ }
91
+ }
92
+
93
+ // 8. Git init
94
+ if (initGit && !skipGit) {
95
+ const gitSpinner = ora('Initializing git repository...').start();
96
+ try {
97
+ await execa('git', ['init'], { cwd: targetDir });
98
+ await execa('git', ['add', '.'], { cwd: targetDir });
99
+ await execa(
100
+ 'git',
101
+ ['commit', '-m', 'chore: initial commit from create-stackit'],
102
+ { cwd: targetDir }
103
+ );
104
+ gitSpinner.succeed('Git repository initialized');
105
+ } catch (err) {
106
+ gitSpinner.warn('Git init skipped (git not available or failed)');
107
+ }
108
+ }
109
+ }
110
+
111
+ function resolveTemplate(templateKey) {
112
+ if (SUPPORTED_COMBOS.has(templateKey)) {
113
+ return { key: templateKey, fallback: false };
114
+ }
115
+ const fallbackKey = FALLBACK_MAP[templateKey];
116
+ if (fallbackKey) {
117
+ return { key: fallbackKey, fallback: true };
118
+ }
119
+ throw new Error(
120
+ `Unsupported combination: ${templateKey}. Supported: ${[...SUPPORTED_COMBOS].join(', ')}`
121
+ );
122
+ }
123
+
124
+ async function patchPackageJson(targetDir, projectName, packageManager) {
125
+ const pkgPath = path.join(targetDir, 'package.json');
126
+ if (!fs.existsSync(pkgPath)) return;
127
+ const pkg = await fs.readJson(pkgPath);
128
+ pkg.name = projectName;
129
+ pkg.version = '0.1.0';
130
+ pkg.engines = { ...(pkg.engines || {}), node: ENGINES_NODE_RANGE };
131
+ pkg.packageManager = getPackageManagerField(packageManager);
132
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
133
+ }