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.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/index.js +64 -0
- package/package.json +43 -0
- package/src/choices.js +63 -0
- package/src/generator.js +133 -0
- package/src/injectors/addons.js +311 -0
- package/src/injectors/structure.js +51 -0
- package/src/pm.js +94 -0
- package/src/prompts.js +179 -0
- package/src/utils.js +18 -0
- package/templates/next-js-redux-mui/README.md +15 -0
- package/templates/next-js-redux-mui/app/layout.jsx +12 -0
- package/templates/next-js-redux-mui/app/page.jsx +15 -0
- package/templates/next-js-redux-mui/app/providers.jsx +16 -0
- package/templates/next-js-redux-mui/package.json +22 -0
- package/templates/next-js-redux-mui/src/store/counterSlice.js +13 -0
- package/templates/next-js-redux-mui/src/store/store.js +8 -0
- package/templates/next-js-redux-mui/src/theme.js +9 -0
- package/templates/next-js-redux-shadcn/README.md +15 -0
- package/templates/next-js-redux-shadcn/app/globals.css +3 -0
- package/templates/next-js-redux-shadcn/app/layout.jsx +13 -0
- package/templates/next-js-redux-shadcn/app/page.jsx +15 -0
- package/templates/next-js-redux-shadcn/app/providers.jsx +11 -0
- package/templates/next-js-redux-shadcn/package.json +23 -0
- package/templates/next-js-redux-shadcn/src/store/counterSlice.js +13 -0
- package/templates/next-js-redux-shadcn/src/store/store.js +8 -0
- package/templates/next-js-zustand-mui/README.md +15 -0
- package/templates/next-js-zustand-mui/app/layout.jsx +16 -0
- package/templates/next-js-zustand-mui/app/page.jsx +18 -0
- package/templates/next-js-zustand-mui/package.json +21 -0
- package/templates/next-js-zustand-mui/src/theme.js +9 -0
- package/templates/next-js-zustand-shadcn/README.md +15 -0
- package/templates/next-js-zustand-shadcn/app/globals.css +3 -0
- package/templates/next-js-zustand-shadcn/app/layout.jsx +10 -0
- package/templates/next-js-zustand-shadcn/app/page.jsx +18 -0
- package/templates/next-js-zustand-shadcn/package.json +22 -0
- package/templates/next-ts-redux-mui/README.md +15 -0
- package/templates/next-ts-redux-mui/app/layout.tsx +14 -0
- package/templates/next-ts-redux-mui/app/page.tsx +15 -0
- package/templates/next-ts-redux-mui/app/providers.tsx +16 -0
- package/templates/next-ts-redux-mui/package.json +27 -0
- package/templates/next-ts-redux-mui/src/store/counterSlice.ts +16 -0
- package/templates/next-ts-redux-mui/src/store/store.ts +11 -0
- package/templates/next-ts-redux-mui/src/theme.ts +9 -0
- package/templates/next-ts-redux-shadcn/README.md +15 -0
- package/templates/next-ts-redux-shadcn/app/globals.css +3 -0
- package/templates/next-ts-redux-shadcn/app/layout.tsx +15 -0
- package/templates/next-ts-redux-shadcn/app/page.tsx +15 -0
- package/templates/next-ts-redux-shadcn/app/providers.tsx +11 -0
- package/templates/next-ts-redux-shadcn/package.json +27 -0
- package/templates/next-ts-redux-shadcn/src/store/counterSlice.ts +16 -0
- package/templates/next-ts-redux-shadcn/src/store/store.ts +11 -0
- package/templates/next-ts-zustand-mui/README.md +15 -0
- package/templates/next-ts-zustand-mui/app/layout.tsx +18 -0
- package/templates/next-ts-zustand-mui/app/page.tsx +18 -0
- package/templates/next-ts-zustand-mui/package.json +26 -0
- package/templates/next-ts-zustand-mui/src/theme.ts +9 -0
- package/templates/next-ts-zustand-shadcn/README.md +15 -0
- package/templates/next-ts-zustand-shadcn/app/globals.css +3 -0
- package/templates/next-ts-zustand-shadcn/app/layout.tsx +12 -0
- package/templates/next-ts-zustand-shadcn/app/page.tsx +18 -0
- package/templates/next-ts-zustand-shadcn/package.json +26 -0
- package/templates/vite-js-redux-mui/README.md +15 -0
- package/templates/vite-js-redux-mui/package.json +24 -0
- package/templates/vite-js-redux-mui/src/App.jsx +14 -0
- package/templates/vite-js-redux-mui/src/main.jsx +18 -0
- package/templates/vite-js-redux-mui/src/store/counterSlice.js +13 -0
- package/templates/vite-js-redux-mui/src/store/store.js +8 -0
- package/templates/vite-js-redux-mui/src/theme.js +9 -0
- package/templates/vite-js-redux-shadcn/README.md +15 -0
- package/templates/vite-js-redux-shadcn/package.json +24 -0
- package/templates/vite-js-redux-shadcn/src/App.jsx +14 -0
- package/templates/vite-js-redux-shadcn/src/index.css +3 -0
- package/templates/vite-js-redux-shadcn/src/main.jsx +14 -0
- package/templates/vite-js-redux-shadcn/src/store/counterSlice.js +13 -0
- package/templates/vite-js-redux-shadcn/src/store/store.js +8 -0
- package/templates/vite-js-zustand-mui/README.md +15 -0
- package/templates/vite-js-zustand-mui/package.json +23 -0
- package/templates/vite-js-zustand-mui/src/App.jsx +17 -0
- package/templates/vite-js-zustand-mui/src/main.jsx +14 -0
- package/templates/vite-js-zustand-mui/src/theme.js +9 -0
- package/templates/vite-js-zustand-shadcn/README.md +15 -0
- package/templates/vite-js-zustand-shadcn/package.json +23 -0
- package/templates/vite-js-zustand-shadcn/src/App.jsx +17 -0
- package/templates/vite-js-zustand-shadcn/src/index.css +3 -0
- package/templates/vite-js-zustand-shadcn/src/main.jsx +10 -0
- package/templates/vite-ts-redux-mui/README.md +15 -0
- package/templates/vite-ts-redux-mui/package.json +27 -0
- package/templates/vite-ts-redux-mui/src/App.tsx +14 -0
- package/templates/vite-ts-redux-mui/src/main.tsx +18 -0
- package/templates/vite-ts-redux-mui/src/store/counterSlice.ts +16 -0
- package/templates/vite-ts-redux-mui/src/store/store.ts +11 -0
- package/templates/vite-ts-redux-mui/src/theme.ts +9 -0
- package/templates/vite-ts-redux-shadcn/README.md +15 -0
- package/templates/vite-ts-redux-shadcn/package.json +27 -0
- package/templates/vite-ts-redux-shadcn/src/App.tsx +14 -0
- package/templates/vite-ts-redux-shadcn/src/index.css +3 -0
- package/templates/vite-ts-redux-shadcn/src/main.tsx +14 -0
- package/templates/vite-ts-redux-shadcn/src/store/counterSlice.ts +16 -0
- package/templates/vite-ts-redux-shadcn/src/store/store.ts +11 -0
- package/templates/vite-ts-zustand-mui/README.md +15 -0
- package/templates/vite-ts-zustand-mui/package.json +26 -0
- package/templates/vite-ts-zustand-mui/src/App.tsx +17 -0
- package/templates/vite-ts-zustand-mui/src/main.tsx +14 -0
- package/templates/vite-ts-zustand-mui/src/theme.ts +9 -0
- package/templates/vite-ts-zustand-shadcn/README.md +15 -0
- package/templates/vite-ts-zustand-shadcn/package.json +23 -0
- 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 = {};
|
package/src/generator.js
ADDED
|
@@ -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
|
+
}
|