create-better-system 1.0.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 (3) hide show
  1. package/README.md +158 -0
  2. package/index.js +246 -0
  3. package/package.json +13 -0
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # create-better-system
2
+
3
+ CLI to scaffold new projects from the [Better System](https://github.com/brunogilferro/better-system) template.
4
+
5
+ ## What it does
6
+
7
+ 1. Clones the Better System monorepo template
8
+ 2. Asks 5 questions about your project
9
+ 3. Fills in design tokens, fonts, and CSS variables automatically
10
+ 4. Leaves you with a ready-to-code project
11
+
12
+ ---
13
+
14
+ ## Usage
15
+
16
+ ```bash
17
+ npx create-better-system
18
+ ```
19
+
20
+ Or install globally:
21
+
22
+ ```bash
23
+ npm install -g create-better-system
24
+ create-better-system
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Questions asked
30
+
31
+ | Question | Default | Notes |
32
+ |----------|---------|-------|
33
+ | Project name | — | Used as folder name and package name |
34
+ | Color theme | Dark only | Dark / Light / Both (with toggle) |
35
+ | Heading font | Poppins | Any Google Font name |
36
+ | Primary accent color | `#D4AF37` | Main brand color |
37
+ | Secondary accent color | `#38bdf8` | Secondary highlights |
38
+
39
+ ---
40
+
41
+ ## After creation
42
+
43
+ ```bash
44
+ # 1. Enter the project folder
45
+ cd <project-name>
46
+
47
+ # 2. Install dependencies
48
+ pnpm install
49
+
50
+ # 3. Set up backend environment
51
+ cp apps/backend/.env.example apps/backend/.env
52
+ # Edit apps/backend/.env and set APP_KEY and any other required values
53
+
54
+ # 4. Run database migrations
55
+ cd apps/backend
56
+ node ace migration:run
57
+
58
+ # 5. Start the project
59
+ cd ../..
60
+ pnpm dev
61
+ ```
62
+
63
+ Frontend runs on `http://localhost:3000`
64
+ Backend runs on `http://localhost:3333`
65
+
66
+ ---
67
+
68
+ ## What gets configured automatically
69
+
70
+ - `docs/figma-design-rules.md` — filled with your design tokens
71
+ - `apps/frontend/app/globals.css` — CSS variables set from your tokens
72
+ - `apps/frontend/app/layout.tsx` — heading font imported from `next/font/google`
73
+ - `package.json` — project name updated
74
+
75
+ ---
76
+
77
+ ## Using AI in your project
78
+
79
+ Once the project is open in Cursor, the AI already knows your architecture, design tokens, and rules.
80
+
81
+ ### Create a complete feature (backend + frontend)
82
+
83
+ ```
84
+ new-feature: courses with name, description and price
85
+ ```
86
+
87
+ The AI will generate the migration, model, validator, service, controller, route, types, hook, and page automatically.
88
+
89
+ ---
90
+
91
+ ### Implement a screen from Figma
92
+
93
+ ```
94
+ figma-to-next-screen: [paste Figma link here]
95
+ ```
96
+
97
+ The AI reads the design, creates an architecture plan, implements with shadcn + your design tokens, and refines against the Figma screenshot.
98
+
99
+ ---
100
+
101
+ ### Generate only the backend for a resource (new table)
102
+
103
+ ```
104
+ backend-from-schema: product with name (string), price (number), stock (integer)
105
+ ```
106
+
107
+ Generates migration, model, validator, transformer, service, controller, and route.
108
+
109
+ ---
110
+
111
+ ### Generate backend from an existing database table
112
+
113
+ ```bash
114
+ # First, pull the current schema from the database
115
+ cd apps/backend && node ace db:pull
116
+ ```
117
+
118
+ Then in Cursor:
119
+
120
+ ```
121
+ database-to-feature: tabela products
122
+ ```
123
+
124
+ Generates model (enhanced from db:pull), validator, transformer, service, controller, and route. **No migration created** — the table already exists.
125
+
126
+ For tables that already exist, you can also use:
127
+
128
+ ```
129
+ backend-from-schema: product [existing table] with name, price, stock
130
+ ```
131
+
132
+ ---
133
+
134
+ ### Write tests
135
+
136
+ ```
137
+ write-tests: apps/backend/app/services/product_service.ts
138
+ ```
139
+
140
+ Generates unit and integration tests following the project's conventions.
141
+
142
+ ---
143
+
144
+ ## Template includes
145
+
146
+ - **Frontend**: Next.js 16, React 19, Tailwind v4, shadcn/ui (14 components), React Query
147
+ - **Backend**: AdonisJS v7 with auth (signup, login, logout, profile)
148
+ - **Type-safe API**: Tuyau client pre-configured in `lib/api.ts`
149
+ - **Cursor AI skills**: `figma-to-next-screen`, `backend-from-schema`, `new-feature`, `write-tests`
150
+ - **Cursor AI rules**: frontend, backend, accessibility, commits
151
+ - **Docs**: design-system, components-registry, data-pattern, api-conventions, error-handling, state-management, figma-design-rules
152
+
153
+ ---
154
+
155
+ ## Requirements
156
+
157
+ - Node.js 18+
158
+ - pnpm
package/index.js ADDED
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+
3
+ import inquirer from 'inquirer'
4
+ import { execa } from 'execa'
5
+ import fs from 'fs-extra'
6
+ import path from 'path'
7
+
8
+ const THEME_PRESETS = {
9
+ dark: {
10
+ bgPrimary: '#0a0e16',
11
+ bgSecondary: '#0e1117',
12
+ bgSurface: '#111827',
13
+ textPrimary: '#e0e0d0',
14
+ textSecondary: '#6a7282',
15
+ borderPrimary: '#1a2233',
16
+ borderSecondary: '#2a3545',
17
+ },
18
+ light: {
19
+ bgPrimary: '#ffffff',
20
+ bgSecondary: '#f9fafb',
21
+ bgSurface: '#f3f4f6',
22
+ textPrimary: '#111827',
23
+ textSecondary: '#6b7280',
24
+ borderPrimary: '#e5e7eb',
25
+ borderSecondary: '#d1d5db',
26
+ },
27
+ }
28
+
29
+ // Converts a font name like "Open Sans" to the Next.js import format "Open_Sans"
30
+ function toNextFontName(fontName) {
31
+ return fontName.trim().replace(/\s+/g, '_')
32
+ }
33
+
34
+ // Converts a font name to a valid JS variable name: "Open Sans" → "openSans"
35
+ function toVarName(fontName) {
36
+ const words = fontName.trim().split(/\s+/)
37
+ return words
38
+ .map((w, i) => (i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()))
39
+ .join('')
40
+ }
41
+
42
+ function colorTable(colors) {
43
+ return [
44
+ '| Token | Value | Usage |',
45
+ '|----------------------|----------------------|----------------------|',
46
+ `| \`--bg-primary\` | ${colors.bgPrimary.padEnd(20)} | Main page background |`,
47
+ `| \`--bg-secondary\` | ${colors.bgSecondary.padEnd(20)} | Secondary surfaces |`,
48
+ `| \`--bg-surface\` | ${colors.bgSurface.padEnd(20)} | Cards, panels |`,
49
+ `| \`--text-primary\` | ${colors.textPrimary.padEnd(20)} | Primary text |`,
50
+ `| \`--text-secondary\` | ${colors.textSecondary.padEnd(20)} | Muted text |`,
51
+ `| \`--border-primary\` | ${colors.borderPrimary.padEnd(20)} | Default borders |`,
52
+ `| \`--border-secondary\` | ${colors.borderSecondary.padEnd(20)} | Subtle dividers |`,
53
+ ].join('\n')
54
+ }
55
+
56
+ // ─── Prompts ──────────────────────────────────────────────────────────────────
57
+
58
+ console.log('\n🚀 Better System — Create new project\n')
59
+
60
+ const answers = await inquirer.prompt([
61
+ {
62
+ name: 'name',
63
+ message: 'Project name?',
64
+ validate: (v) => v.trim().length > 0 || 'Name is required',
65
+ },
66
+ {
67
+ name: 'theme',
68
+ type: 'list',
69
+ message: 'Color theme?',
70
+ choices: [
71
+ { name: 'Dark only', value: 'dark' },
72
+ { name: 'Light only', value: 'light' },
73
+ { name: 'Both (dark + light with toggle)', value: 'both' },
74
+ ],
75
+ default: 'dark',
76
+ },
77
+ {
78
+ name: 'headingFont',
79
+ message: 'Heading font (e.g. Poppins, Sora)?',
80
+ default: 'Poppins',
81
+ },
82
+ {
83
+ name: 'accentPrimary',
84
+ message: 'Primary accent color (hex)?',
85
+ default: '#D4AF37',
86
+ },
87
+ {
88
+ name: 'accentSecondary',
89
+ message: 'Secondary accent color (hex)?',
90
+ default: '#38bdf8',
91
+ },
92
+ ])
93
+
94
+ // ─── Resolve values ───────────────────────────────────────────────────────────
95
+
96
+ const projectName = answers.name.trim()
97
+ const projectPath = path.resolve(process.cwd(), projectName)
98
+
99
+ const isBoth = answers.theme === 'both'
100
+ const isDark = answers.theme === 'dark'
101
+ const rootColors = isBoth ? THEME_PRESETS.light : (isDark ? THEME_PRESETS.dark : THEME_PRESETS.light)
102
+ const darkColors = THEME_PRESETS.dark
103
+
104
+ // Font helpers
105
+ const headingFontNextName = toNextFontName(answers.headingFont)
106
+ const headingFontVarName = toVarName(answers.headingFont) + 'Font'
107
+
108
+ // ─── Clone template ───────────────────────────────────────────────────────────
109
+
110
+ console.log('\n📦 Cloning Better System template...')
111
+ await execa('npx', ['degit', 'brunogilferro/better-system', projectName], {
112
+ stdio: 'inherit',
113
+ })
114
+
115
+ // ─── Update layout.tsx with heading font ─────────────────────────────────────
116
+
117
+ console.log('🔤 Configuring fonts...')
118
+
119
+ const layoutPath = path.join(projectPath, 'apps', 'frontend', 'app', 'layout.tsx')
120
+ let layout = await fs.readFile(layoutPath, 'utf-8')
121
+
122
+ layout = layout
123
+ .replace(
124
+ '// __HEADING_FONT_IMPORT__',
125
+ `import { ${headingFontNextName} } from "next/font/google"`
126
+ )
127
+ .replace(
128
+ '// __HEADING_FONT_DECLARATION__',
129
+ `const ${headingFontVarName} = ${headingFontNextName}({\n variable: "--font-heading",\n subsets: ["latin"],\n weight: ["400", "500", "700"],\n})`
130
+ )
131
+ .replaceAll('__HEADING_FONT_CLASS__', `\${${headingFontVarName}.variable}`)
132
+ .replaceAll('__PROJECT_NAME__', projectName)
133
+
134
+ await fs.writeFile(layoutPath, layout)
135
+
136
+ // ─── Fill globals.css placeholders ───────────────────────────────────────────
137
+
138
+ console.log('💅 Configuring CSS variables...')
139
+
140
+ const cssPath = path.join(projectPath, 'apps', 'frontend', 'app', 'globals.css')
141
+ let css = await fs.readFile(cssPath, 'utf-8')
142
+
143
+ const cssReplacements = {
144
+ '__HEADING_FONT__': answers.headingFont,
145
+ '__BODY_FONT__': 'Inter',
146
+ '__ACCENT_PRIMARY__': answers.accentPrimary,
147
+ '__ACCENT_PRIMARY_HOVER__': answers.accentPrimary,
148
+ '__ACCENT_SECONDARY__': answers.accentSecondary,
149
+ '__BG_PRIMARY__': rootColors.bgPrimary,
150
+ '__BG_SECONDARY__': rootColors.bgSecondary,
151
+ '__BG_SURFACE__': rootColors.bgSurface,
152
+ '__TEXT_PRIMARY__': rootColors.textPrimary,
153
+ '__TEXT_SECONDARY__': rootColors.textSecondary,
154
+ '__BORDER_PRIMARY__': rootColors.borderPrimary,
155
+ '__BORDER_SECONDARY__': rootColors.borderSecondary,
156
+ }
157
+
158
+ for (const [placeholder, value] of Object.entries(cssReplacements)) {
159
+ css = css.replaceAll(placeholder, value)
160
+ }
161
+
162
+ if (isBoth) {
163
+ const darkReplacements = {
164
+ '__DARK_BG_PRIMARY__': darkColors.bgPrimary,
165
+ '__DARK_BG_SECONDARY__': darkColors.bgSecondary,
166
+ '__DARK_BG_SURFACE__': darkColors.bgSurface,
167
+ '__DARK_TEXT_PRIMARY__': darkColors.textPrimary,
168
+ '__DARK_TEXT_SECONDARY__': darkColors.textSecondary,
169
+ '__DARK_BORDER_PRIMARY__': darkColors.borderPrimary,
170
+ '__DARK_BORDER_SECONDARY__': darkColors.borderSecondary,
171
+ }
172
+ for (const [placeholder, value] of Object.entries(darkReplacements)) {
173
+ css = css.replaceAll(placeholder, value)
174
+ }
175
+ css = css.replace('/* DARK_THEME_BLOCK_START */', '').replace('/* DARK_THEME_BLOCK_END */', '')
176
+ } else {
177
+ css = css.replace(
178
+ /\/\* DARK_THEME_BLOCK_START \*\/[\s\S]*?\/\* DARK_THEME_BLOCK_END \*\//,
179
+ ''
180
+ )
181
+ }
182
+
183
+ await fs.writeFile(cssPath, css)
184
+
185
+ // ─── Fill figma-design-rules.md placeholders ─────────────────────────────────
186
+
187
+ console.log('🎨 Configuring design tokens...')
188
+
189
+ const figmaRulesPath = path.join(projectPath, 'docs', 'figma-design-rules.md')
190
+ let figmaRules = await fs.readFile(figmaRulesPath, 'utf-8')
191
+
192
+ let colorsSection
193
+ if (isBoth) {
194
+ colorsSection = [
195
+ '## Colors — Light theme (default)',
196
+ '',
197
+ colorTable(THEME_PRESETS.light),
198
+ '',
199
+ '## Colors — Dark theme (`.dark` class on `<html>`)',
200
+ '',
201
+ colorTable(THEME_PRESETS.dark),
202
+ ].join('\n')
203
+ } else {
204
+ const label = isDark ? 'Dark theme' : 'Light theme'
205
+ colorsSection = `## Colors — ${label}\n\n${colorTable(rootColors)}`
206
+ }
207
+
208
+ const figmaReplacements = {
209
+ '__THEME_MODE__': answers.theme,
210
+ '__HEADING_FONT__': answers.headingFont,
211
+ '__BODY_FONT__': 'Inter',
212
+ '__ACCENT_PRIMARY__': answers.accentPrimary,
213
+ '__ACCENT_PRIMARY_HOVER__': answers.accentPrimary,
214
+ '__ACCENT_SECONDARY__': answers.accentSecondary,
215
+ '__COLORS_SECTION__': colorsSection,
216
+ '__CSS_BLOCK__': isBoth ? 'Both light and dark — see globals.css' : `Single theme (${answers.theme})`,
217
+ }
218
+
219
+ for (const [placeholder, value] of Object.entries(figmaReplacements)) {
220
+ figmaRules = figmaRules.replaceAll(placeholder, value)
221
+ }
222
+
223
+ await fs.writeFile(figmaRulesPath, figmaRules)
224
+
225
+ // ─── Update root package.json ─────────────────────────────────────────────────
226
+
227
+ console.log('📝 Updating package.json...')
228
+
229
+ const rootPkgPath = path.join(projectPath, 'package.json')
230
+ const rootPkg = await fs.readJson(rootPkgPath)
231
+ rootPkg.name = projectName
232
+ await fs.writeJson(rootPkgPath, rootPkg, { spaces: 2 })
233
+
234
+ // ─── Done ─────────────────────────────────────────────────────────────────────
235
+
236
+ console.log(`\n✅ Project "${projectName}" created!`)
237
+
238
+ if (isBoth) {
239
+ console.log(`\n💡 Both themes enabled.`)
240
+ console.log(` Light is the default. Add class="dark" to <html> to activate dark.`)
241
+ }
242
+
243
+ console.log(`\nNext steps:`)
244
+ console.log(` cd ${projectName}`)
245
+ console.log(` pnpm install`)
246
+ console.log(` pnpm dev\n`)
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-better-system",
3
+ "version": "1.0.0",
4
+ "bin": {
5
+ "create-better-system": "index.js"
6
+ },
7
+ "type": "module",
8
+ "dependencies": {
9
+ "inquirer": "^9.0.0",
10
+ "execa": "^8.0.0",
11
+ "fs-extra": "^11.0.0"
12
+ }
13
+ }