create-mantiq 0.7.0 → 0.7.2

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 (220) hide show
  1. package/package.json +2 -1
  2. package/skeleton/.env.example +64 -0
  3. package/skeleton/README.md +46 -0
  4. package/skeleton/app/Console/Commands/.gitkeep +0 -0
  5. package/skeleton/app/Enums/UserStatus.ts +7 -0
  6. package/skeleton/app/Http/Controllers/HomeController.ts +78 -0
  7. package/skeleton/app/Http/Middleware/.gitkeep +0 -0
  8. package/skeleton/app/Models/User.ts +7 -0
  9. package/skeleton/app/Providers/AppServiceProvider.ts +25 -0
  10. package/skeleton/app/Providers/DatabaseServiceProvider.ts +17 -0
  11. package/skeleton/bootstrap/.gitkeep +0 -0
  12. package/skeleton/config/ai.ts +51 -0
  13. package/skeleton/config/app.ts +108 -0
  14. package/skeleton/config/auth.ts +51 -0
  15. package/skeleton/config/broadcasting.ts +93 -0
  16. package/skeleton/config/cache.ts +61 -0
  17. package/skeleton/config/cors.ts +77 -0
  18. package/skeleton/config/database.ts +120 -0
  19. package/skeleton/config/filesystem.ts +58 -0
  20. package/skeleton/config/hashing.ts +47 -0
  21. package/skeleton/config/heartbeat.ts +112 -0
  22. package/skeleton/config/logging.ts +58 -0
  23. package/skeleton/config/mail.ts +93 -0
  24. package/skeleton/config/notify.ts +141 -0
  25. package/skeleton/config/queue.ts +59 -0
  26. package/skeleton/config/search.ts +96 -0
  27. package/skeleton/config/services.ts +110 -0
  28. package/skeleton/config/session.ts +84 -0
  29. package/skeleton/config/vite.ts +33 -0
  30. package/skeleton/database/factories/.gitkeep +0 -0
  31. package/skeleton/database/migrations/001_create_users_table.ts +19 -0
  32. package/skeleton/database/migrations/002_create_personal_access_tokens_table.ts +22 -0
  33. package/skeleton/database/seeders/DatabaseSeeder.ts +7 -0
  34. package/skeleton/index.ts +20 -0
  35. package/skeleton/mantiq.ts +8 -0
  36. package/skeleton/package.json +34 -0
  37. package/skeleton/public/.gitkeep +0 -0
  38. package/skeleton/routes/api.ts +8 -0
  39. package/skeleton/routes/channels.ts +23 -0
  40. package/skeleton/routes/console.ts +24 -0
  41. package/skeleton/routes/web.ts +6 -0
  42. package/skeleton/storage/cache/.gitkeep +0 -0
  43. package/skeleton/storage/framework/.gitkeep +0 -0
  44. package/skeleton/tests/feature/api.test.ts +14 -0
  45. package/skeleton/tests/feature/home.test.ts +17 -0
  46. package/skeleton/tests/unit/example.test.ts +11 -0
  47. package/skeleton/tsconfig.json +27 -0
  48. package/src/index.ts +289 -25
  49. package/src/templates.ts +141 -945
  50. package/src/terminal.ts +64 -0
  51. package/stubs/api-only/routes/api.ts.stub +24 -0
  52. package/stubs/api-only/tests/feature/token-auth.test.ts.stub +69 -0
  53. package/stubs/auth/api/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
  54. package/stubs/auth/api/routes/api.ts.stub +24 -0
  55. package/stubs/auth/api/tests/feature/token-auth.test.ts.stub +69 -0
  56. package/stubs/auth/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
  57. package/stubs/auth/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
  58. package/stubs/auth/web/app/Http/Controllers/AuthController.ts.stub +43 -0
  59. package/stubs/auth/web/app/Http/Controllers/PageController.ts.stub +66 -0
  60. package/stubs/auth/web/routes/web.ts.stub +25 -0
  61. package/stubs/auth/web/svelte/src/App.svelte.stub +77 -0
  62. package/stubs/auth/web/svelte/src/pages.ts.stub +17 -0
  63. package/stubs/auth/web/tests/feature/auth.test.ts.stub +69 -0
  64. package/stubs/auth/web/vue/src/App.vue.stub +74 -0
  65. package/stubs/auth/web/vue/src/pages.ts.stub +17 -0
  66. package/stubs/manifest.json +630 -2
  67. package/stubs/noauth/app/Http/Controllers/PageController.ts.stub +41 -0
  68. package/stubs/noauth/app/Models/User.ts.stub +5 -0
  69. package/stubs/noauth/database/migrations/001_create_users_table.ts.stub +17 -0
  70. package/stubs/noauth/routes/api.ts.stub +16 -0
  71. package/stubs/noauth/routes/web.ts.stub +15 -0
  72. package/stubs/noauth/svelte/src/App.svelte.stub +68 -0
  73. package/stubs/noauth/svelte/src/pages.ts.stub +7 -0
  74. package/stubs/noauth/vue/src/App.vue.stub +62 -0
  75. package/stubs/noauth/vue/src/pages.ts.stub +7 -0
  76. package/stubs/react/src/App.tsx.stub +4 -2
  77. package/stubs/react/src/components/layout/search-dialog.tsx.stub +2 -2
  78. package/stubs/react/src/components/layout/sidebar-data.ts.stub +2 -2
  79. package/stubs/react/src/lib/api.ts.stub +30 -6
  80. package/stubs/react/src/pages/Login.tsx.stub +3 -3
  81. package/stubs/react/src/pages/users/dialogs.tsx.stub +7 -26
  82. package/stubs/react/vite.config.ts.stub +26 -3
  83. package/stubs/shared/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
  84. package/stubs/shared/app/Http/Controllers/AuthController.ts.stub +14 -38
  85. package/stubs/shared/app/Http/Controllers/PageController.ts.stub +3 -3
  86. package/stubs/shared/app/Http/Controllers/UserController.ts.stub +61 -0
  87. package/stubs/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
  88. package/stubs/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
  89. package/stubs/shared/app/Http/Requests/StoreUserRequest.ts.stub +11 -0
  90. package/stubs/shared/app/Http/Requests/UpdateUserRequest.ts.stub +11 -0
  91. package/stubs/shared/config/app.ts.stub +36 -0
  92. package/stubs/shared/config/vite.ts.stub +8 -0
  93. package/stubs/shared/database/factories/UserFactory.ts.stub +4 -6
  94. package/stubs/shared/routes/api.ts.stub +12 -102
  95. package/stubs/shared/routes/web.ts.stub +5 -3
  96. package/stubs/shared/tests/feature/auth.test.ts.stub +69 -0
  97. package/stubs/shared/tests/feature/users.test.ts.stub +90 -0
  98. package/stubs/svelte/src/App.svelte.stub +1 -1
  99. package/stubs/svelte/src/lib/api.ts.stub +30 -6
  100. package/stubs/svelte/src/main.ts.stub +3 -1
  101. package/stubs/svelte/src/pages/Login.svelte.stub +3 -3
  102. package/stubs/svelte/vite.config.ts.stub +20 -1
  103. package/stubs/tailwind-only/react/src/components/layout/app-sidebar.tsx.stub +68 -0
  104. package/stubs/tailwind-only/react/src/components/layout/authenticated-layout.tsx.stub +57 -0
  105. package/stubs/tailwind-only/react/src/components/layout/header.tsx.stub +52 -0
  106. package/stubs/tailwind-only/react/src/components/layout/main.tsx.stub +21 -0
  107. package/stubs/tailwind-only/react/src/components/layout/nav-group.tsx.stub +185 -0
  108. package/stubs/tailwind-only/react/src/components/layout/nav-user.tsx.stub +106 -0
  109. package/stubs/tailwind-only/react/src/components/layout/sidebar-data.ts.stub +58 -0
  110. package/stubs/tailwind-only/react/src/components/layout/theme-toggle.tsx.stub +36 -0
  111. package/stubs/tailwind-only/react/src/components/layout/top-nav.tsx.stub +72 -0
  112. package/stubs/tailwind-only/react/src/lib/utils.ts.stub +6 -0
  113. package/stubs/tailwind-only/react/src/pages/Dashboard.tsx.stub +205 -0
  114. package/stubs/tailwind-only/react/src/pages/Login.tsx.stub +122 -0
  115. package/stubs/tailwind-only/react/src/pages/Register.tsx.stub +137 -0
  116. package/stubs/tailwind-only/react/src/pages/Users.tsx.stub +426 -0
  117. package/stubs/tailwind-only/react/src/pages/account/layout.tsx.stub +64 -0
  118. package/stubs/tailwind-only/react/src/pages/account/preferences.tsx.stub +80 -0
  119. package/stubs/tailwind-only/react/src/pages/account/profile.tsx.stub +67 -0
  120. package/stubs/tailwind-only/react/src/pages/account/security.tsx.stub +91 -0
  121. package/stubs/tailwind-only/react/src/style.css.stub +14 -0
  122. package/stubs/tailwind-only/svelte/src/lib/components/layout/app-sidebar.svelte.stub +104 -0
  123. package/stubs/tailwind-only/svelte/src/lib/components/layout/authenticated-layout.svelte.stub +51 -0
  124. package/stubs/tailwind-only/svelte/src/lib/components/layout/header.svelte.stub +66 -0
  125. package/stubs/tailwind-only/svelte/src/lib/components/layout/main.svelte.stub +26 -0
  126. package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-group.svelte.stub +131 -0
  127. package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-user.svelte.stub +104 -0
  128. package/stubs/tailwind-only/svelte/src/lib/components/layout/sidebar-data.ts.stub +57 -0
  129. package/stubs/tailwind-only/svelte/src/lib/components/layout/theme-toggle.svelte.stub +28 -0
  130. package/stubs/tailwind-only/svelte/src/lib/components/layout/top-nav.svelte.stub +99 -0
  131. package/stubs/tailwind-only/svelte/src/lib/utils.ts.stub +6 -0
  132. package/stubs/tailwind-only/svelte/src/pages/Dashboard.svelte.stub +192 -0
  133. package/stubs/tailwind-only/svelte/src/pages/Login.svelte.stub +120 -0
  134. package/stubs/tailwind-only/svelte/src/pages/Register.svelte.stub +134 -0
  135. package/stubs/tailwind-only/svelte/src/pages/Users.svelte.stub +293 -0
  136. package/stubs/tailwind-only/svelte/src/pages/account/Layout.svelte.stub +61 -0
  137. package/stubs/tailwind-only/svelte/src/pages/account/Preferences.svelte.stub +81 -0
  138. package/stubs/tailwind-only/svelte/src/pages/account/Profile.svelte.stub +76 -0
  139. package/stubs/tailwind-only/svelte/src/pages/account/Security.svelte.stub +140 -0
  140. package/stubs/tailwind-only/svelte/src/style.css.stub +127 -0
  141. package/stubs/tailwind-only/vue/src/components/layout/AppSidebar.vue.stub +60 -0
  142. package/stubs/tailwind-only/vue/src/components/layout/AuthenticatedLayout.vue.stub +73 -0
  143. package/stubs/tailwind-only/vue/src/components/layout/Header.vue.stub +54 -0
  144. package/stubs/tailwind-only/vue/src/components/layout/Main.vue.stub +22 -0
  145. package/stubs/tailwind-only/vue/src/components/layout/NavGroup.vue.stub +107 -0
  146. package/stubs/tailwind-only/vue/src/components/layout/NavUser.vue.stub +104 -0
  147. package/stubs/tailwind-only/vue/src/components/layout/ThemeToggle.vue.stub +28 -0
  148. package/stubs/tailwind-only/vue/src/components/layout/TopNav.vue.stub +86 -0
  149. package/stubs/tailwind-only/vue/src/components/layout/sidebar-data.ts.stub +57 -0
  150. package/stubs/tailwind-only/vue/src/lib/utils.ts.stub +7 -0
  151. package/stubs/tailwind-only/vue/src/pages/Dashboard.vue.stub +195 -0
  152. package/stubs/tailwind-only/vue/src/pages/Login.vue.stub +121 -0
  153. package/stubs/tailwind-only/vue/src/pages/Register.vue.stub +137 -0
  154. package/stubs/tailwind-only/vue/src/pages/Users.vue.stub +401 -0
  155. package/stubs/tailwind-only/vue/src/pages/account/Layout.vue.stub +56 -0
  156. package/stubs/tailwind-only/vue/src/pages/account/Preferences.vue.stub +81 -0
  157. package/stubs/tailwind-only/vue/src/pages/account/Profile.vue.stub +69 -0
  158. package/stubs/tailwind-only/vue/src/pages/account/Security.vue.stub +85 -0
  159. package/stubs/tailwind-only/vue/src/style.css.stub +26 -0
  160. package/stubs/themes/corporate/react/src/components/layout/app-sidebar.tsx.stub +74 -0
  161. package/stubs/themes/corporate/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  162. package/stubs/themes/corporate/react/src/pages/Dashboard.tsx.stub +310 -0
  163. package/stubs/themes/corporate/react/src/pages/Login.tsx.stub +122 -0
  164. package/stubs/themes/corporate/react/src/pages/Register.tsx.stub +144 -0
  165. package/stubs/themes/corporate/react/src/style.css.stub +135 -0
  166. package/stubs/themes/corporate/svelte/src/pages/Dashboard.svelte.stub +271 -0
  167. package/stubs/themes/corporate/svelte/src/pages/Login.svelte.stub +117 -0
  168. package/stubs/themes/corporate/svelte/src/pages/Register.svelte.stub +138 -0
  169. package/stubs/themes/corporate/svelte/src/style.css.stub +134 -0
  170. package/stubs/themes/corporate/vue/src/pages/Dashboard.vue.stub +325 -0
  171. package/stubs/themes/corporate/vue/src/pages/Login.vue.stub +118 -0
  172. package/stubs/themes/corporate/vue/src/pages/Register.vue.stub +139 -0
  173. package/stubs/themes/corporate/vue/src/style.css.stub +134 -0
  174. package/stubs/themes/minimal/react/src/components/layout/app-sidebar.tsx.stub +68 -0
  175. package/stubs/themes/minimal/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  176. package/stubs/themes/minimal/react/src/pages/Dashboard.tsx.stub +166 -0
  177. package/stubs/themes/minimal/react/src/pages/Login.tsx.stub +95 -0
  178. package/stubs/themes/minimal/react/src/pages/Register.tsx.stub +73 -0
  179. package/stubs/themes/minimal/react/src/style.css.stub +142 -0
  180. package/stubs/themes/minimal/svelte/src/pages/Dashboard.svelte.stub +149 -0
  181. package/stubs/themes/minimal/svelte/src/pages/Login.svelte.stub +90 -0
  182. package/stubs/themes/minimal/svelte/src/pages/Register.svelte.stub +70 -0
  183. package/stubs/themes/minimal/svelte/src/style.css.stub +142 -0
  184. package/stubs/themes/minimal/vue/src/pages/Dashboard.vue.stub +163 -0
  185. package/stubs/themes/minimal/vue/src/pages/Login.vue.stub +91 -0
  186. package/stubs/themes/minimal/vue/src/pages/Register.vue.stub +73 -0
  187. package/stubs/themes/minimal/vue/src/style.css.stub +142 -0
  188. package/stubs/themes/starter/react/src/components/layout/app-sidebar.tsx.stub +74 -0
  189. package/stubs/themes/starter/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  190. package/stubs/themes/starter/react/src/pages/Dashboard.tsx.stub +236 -0
  191. package/stubs/themes/starter/react/src/pages/Login.tsx.stub +131 -0
  192. package/stubs/themes/starter/react/src/pages/Register.tsx.stub +145 -0
  193. package/stubs/themes/starter/react/src/style.css.stub +141 -0
  194. package/stubs/themes/starter/svelte/src/pages/Dashboard.svelte.stub +212 -0
  195. package/stubs/themes/starter/svelte/src/pages/Login.svelte.stub +126 -0
  196. package/stubs/themes/starter/svelte/src/pages/Register.svelte.stub +139 -0
  197. package/stubs/themes/starter/svelte/src/style.css.stub +141 -0
  198. package/stubs/themes/starter/vue/src/pages/Dashboard.vue.stub +228 -0
  199. package/stubs/themes/starter/vue/src/pages/Login.vue.stub +127 -0
  200. package/stubs/themes/starter/vue/src/pages/Register.vue.stub +140 -0
  201. package/stubs/themes/starter/vue/src/style.css.stub +141 -0
  202. package/stubs/themes/workspace/react/src/components/layout/app-sidebar.tsx.stub +97 -0
  203. package/stubs/themes/workspace/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  204. package/stubs/themes/workspace/react/src/pages/Dashboard.tsx.stub +304 -0
  205. package/stubs/themes/workspace/react/src/pages/Login.tsx.stub +131 -0
  206. package/stubs/themes/workspace/react/src/pages/Register.tsx.stub +82 -0
  207. package/stubs/themes/workspace/react/src/style.css.stub +138 -0
  208. package/stubs/themes/workspace/svelte/src/pages/Dashboard.svelte.stub +215 -0
  209. package/stubs/themes/workspace/svelte/src/pages/Login.svelte.stub +124 -0
  210. package/stubs/themes/workspace/svelte/src/pages/Register.svelte.stub +76 -0
  211. package/stubs/themes/workspace/svelte/src/style.css.stub +134 -0
  212. package/stubs/themes/workspace/vue/src/pages/Dashboard.vue.stub +220 -0
  213. package/stubs/themes/workspace/vue/src/pages/Login.vue.stub +128 -0
  214. package/stubs/themes/workspace/vue/src/pages/Register.vue.stub +80 -0
  215. package/stubs/themes/workspace/vue/src/style.css.stub +134 -0
  216. package/stubs/vue/src/App.vue.stub +2 -1
  217. package/stubs/vue/src/lib/api.ts.stub +30 -6
  218. package/stubs/vue/src/main.ts.stub +3 -1
  219. package/stubs/vue/src/pages/Login.vue.stub +3 -3
  220. package/stubs/vue/vite.config.ts.stub +20 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Console Routes
3
+ *
4
+ * Define scheduled tasks and console-only commands here.
5
+ * These are loaded automatically by the Discoverer.
6
+ *
7
+ * @example
8
+ * import { schedule } from '@mantiq/core'
9
+ *
10
+ * // Run every hour
11
+ * schedule.command('heartbeat:prune').hourly()
12
+ *
13
+ * // Run daily at midnight
14
+ * schedule.command('queue:prune-failed').daily()
15
+ *
16
+ * // Custom closure
17
+ * schedule.call(async () => {
18
+ * console.log('Running cleanup...')
19
+ * }).everyFiveMinutes()
20
+ */
21
+
22
+ export default function () {
23
+ // Define your scheduled tasks here
24
+ }
@@ -0,0 +1,6 @@
1
+ import type { Router } from '@mantiq/core'
2
+ import { HomeController } from '../app/Http/Controllers/HomeController.ts'
3
+
4
+ export default function (router: Router) {
5
+ router.get('/', [HomeController, 'index'])
6
+ }
File without changes
File without changes
@@ -0,0 +1,14 @@
1
+ import { describe, test } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.setup()
6
+
7
+ describe('API', () => {
8
+ test('GET /api/ping returns ok', async () => {
9
+ const res = await t.client.get('/api/ping')
10
+ res.assertOk()
11
+ await res.assertJson({ status: 'ok' })
12
+ await res.assertJsonHasKey('timestamp')
13
+ })
14
+ })
@@ -0,0 +1,17 @@
1
+ import { describe, test } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.setup()
6
+
7
+ describe('Home', () => {
8
+ test('GET / returns 200', async () => {
9
+ const res = await t.client.get('/')
10
+ res.assertOk()
11
+ })
12
+
13
+ test('GET / returns HTML', async () => {
14
+ const res = await t.client.get('/')
15
+ res.assertHeader('content-type')
16
+ })
17
+ })
@@ -0,0 +1,11 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+
3
+ describe('Example', () => {
4
+ test('true is truthy', () => {
5
+ expect(true).toBe(true)
6
+ })
7
+
8
+ test('math works', () => {
9
+ expect(1 + 1).toBe(2)
10
+ })
11
+ })
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": [
7
+ "ESNext"
8
+ ],
9
+ "types": [
10
+ "bun-types"
11
+ ],
12
+ "strict": true,
13
+ "noImplicitAny": true,
14
+ "strictNullChecks": true,
15
+ "noUncheckedIndexedAccess": true,
16
+ "noImplicitOverride": true,
17
+ "allowImportingTsExtensions": true,
18
+ "noEmit": true,
19
+ "skipLibCheck": true
20
+ },
21
+ "include": [
22
+ "./**/*"
23
+ ],
24
+ "exclude": [
25
+ "node_modules"
26
+ ]
27
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,45 @@
1
1
  #!/usr/bin/env bun
2
- import { existsSync, mkdirSync } from 'node:fs'
3
- import { dirname, resolve } from 'node:path'
2
+ import { existsSync, mkdirSync, readdirSync, statSync, readFileSync } from 'node:fs'
3
+ import { dirname, resolve, join, relative } from 'node:path'
4
4
  import { randomBytes } from 'node:crypto'
5
- import { getTemplates } from './templates.ts'
5
+ import { getTemplates, type Theme } from './templates.ts'
6
6
  import { Terminal } from './terminal.ts'
7
7
 
8
+ /**
9
+ * Recursively copy a directory, preserving structure.
10
+ * Skips node_modules, .git, bun.lock, *.sqlite files.
11
+ * `skipRelPaths` contains relative paths (from root src) to skip.
12
+ */
13
+ async function copyDirectory(src: string, dest: string, skipRelPaths?: Set<string>, rootSrc?: string): Promise<number> {
14
+ let count = 0
15
+ const root = rootSrc ?? src
16
+ const entries = readdirSync(src, { withFileTypes: true })
17
+ for (const entry of entries) {
18
+ const srcPath = join(src, entry.name)
19
+ const destPath = join(dest, entry.name)
20
+
21
+ // Skip files that shouldn't be copied
22
+ if (['node_modules', '.git', 'bun.lock', 'README.md'].includes(entry.name)) continue
23
+ if (entry.name.endsWith('.sqlite') || entry.name.endsWith('.sqlite-wal') || entry.name.endsWith('.sqlite-shm')) continue
24
+
25
+ // Skip paths in the conditional skip set
26
+ if (skipRelPaths) {
27
+ const rel = relative(root, srcPath)
28
+ if (skipRelPaths.has(rel)) continue
29
+ }
30
+
31
+ if (entry.isDirectory()) {
32
+ mkdirSync(destPath, { recursive: true })
33
+ count += await copyDirectory(srcPath, destPath, skipRelPaths, root)
34
+ } else {
35
+ mkdirSync(dirname(destPath), { recursive: true })
36
+ await Bun.write(destPath, Bun.file(srcPath))
37
+ count++
38
+ }
39
+ }
40
+ return count
41
+ }
42
+
8
43
  // ── ANSI helpers ─────────────────────────────────────────────────────────────
9
44
  const R = '\x1b[0m'
10
45
  const bold = (s: string) => `\x1b[1m${s}${R}`
@@ -47,7 +82,11 @@ if (!projectName) {
47
82
 
48
83
  ${bold('Options:')}
49
84
  --kit=${emerald('react|vue|svelte')} Frontend framework
50
- --ui=${emerald('shadcn')} UI component library (React only)
85
+ --ui=${emerald('shadcn|tailwind')} UI component library
86
+ --theme=${emerald('default|minimal|workspace|corporate|starter')}
87
+ Dashboard theme (shadcn only)
88
+ --auth=${emerald('builtin|none')} Authentication setup
89
+ --with=${emerald('ai,studio')} Optional packages (comma-separated)
51
90
  --no-git Skip git initialization
52
91
  --yes Accept defaults (non-interactive)
53
92
 
@@ -55,6 +94,8 @@ if (!projectName) {
55
94
  bun create mantiq my-app
56
95
  bun create mantiq my-app --kit=react
57
96
  bun create mantiq my-app --kit=react --ui=shadcn
97
+ bun create mantiq my-app --kit=react --auth=none
98
+ bun create mantiq my-app --kit=react --with=ai,studio
58
99
  `)
59
100
  process.exit(1)
60
101
  }
@@ -70,7 +111,15 @@ if (existsSync(projectDir)) {
70
111
  const term = new Terminal()
71
112
 
72
113
  let kit: Kit | undefined = flags['kit'] as Kit | undefined
73
- let ui: 'shadcn' | 'none' = (flags['ui'] as string) === 'shadcn' ? 'shadcn' : 'none'
114
+ let ui: 'shadcn' | 'tailwind' = (flags['ui'] as string) === 'tailwind' ? 'tailwind' : 'shadcn'
115
+ const validThemes = ['default', 'minimal', 'workspace', 'corporate', 'starter'] as const
116
+ let theme: Theme = (validThemes as readonly string[]).includes(flags['theme'] as string)
117
+ ? (flags['theme'] as Theme)
118
+ : 'default'
119
+ let auth: 'builtin' | 'none' = (flags['auth'] as string) === 'none' ? 'none' : 'builtin'
120
+ let optionalPackages: string[] = typeof flags['with'] === 'string'
121
+ ? flags['with'].split(',').map(s => s.trim()).filter(Boolean)
122
+ : []
74
123
 
75
124
  if (!isCI && !kit) {
76
125
  // Show branded header
@@ -81,11 +130,49 @@ if (!isCI && !kit) {
81
130
  { value: 'react', label: 'React' },
82
131
  { value: 'vue', label: 'Vue' },
83
132
  { value: 'svelte', label: 'Svelte' },
84
- { value: 'none', label: 'None', hint: 'API only' },
133
+ { value: 'none', label: 'Vanilla', hint: 'API only' },
85
134
  ])
86
135
 
87
136
  kit = framework === 'none' ? undefined : framework as Kit
88
137
 
138
+ // UI kit selection (only if a frontend framework was chosen)
139
+ if (kit && !flags['ui']) {
140
+ const uiChoice = await term.select('Choose a UI kit', [
141
+ { value: 'shadcn', label: 'shadcn + Tailwind' },
142
+ { value: 'tailwind', label: 'Tailwind only' },
143
+ ])
144
+ ui = uiChoice as 'shadcn' | 'tailwind'
145
+ }
146
+
147
+ // Theme selection (only for shadcn)
148
+ if (kit && ui === 'shadcn' && !flags['theme']) {
149
+ const themeChoice = await term.select('Choose a theme', [
150
+ { value: 'default', label: 'Default', hint: 'emerald, classic admin' },
151
+ { value: 'minimal', label: 'Minimal', hint: 'clean & focused' },
152
+ { value: 'workspace', label: 'Workspace', hint: 'warm & approachable' },
153
+ { value: 'corporate', label: 'Corporate', hint: 'professional & data-rich' },
154
+ { value: 'starter', label: 'Starter', hint: 'bold & marketing-ready' },
155
+ ])
156
+ theme = themeChoice as Theme
157
+ }
158
+
159
+ // Authentication selection
160
+ if (!flags['auth']) {
161
+ const authChoice = await term.select('Authentication', [
162
+ { value: 'builtin', label: 'Built-in', hint: 'session + token auth' },
163
+ { value: 'none', label: 'None' },
164
+ ])
165
+ auth = authChoice as 'builtin' | 'none'
166
+ }
167
+
168
+ // Optional packages selection
169
+ if (!flags['with']) {
170
+ optionalPackages = await term.multiSelect('Optional packages', [
171
+ { value: 'ai', label: 'AI', hint: '@mantiq/ai' },
172
+ { value: 'studio', label: 'Studio', hint: '@mantiq/studio — admin panel' },
173
+ ])
174
+ }
175
+
89
176
  } else {
90
177
  // Validate flags
91
178
  if (kit && !validKits.includes(kit as Kit)) {
@@ -98,19 +185,37 @@ if (!isCI && !kit) {
98
185
  // ── Generate ─────────────────────────────────────────────────────────────────
99
186
  term.step('Scaffolding project')
100
187
 
101
- const appKey = `base64:${randomBytes(32).toString('base64')}`
102
- const templates = getTemplates({ name: projectName, appKey, kit, ui })
188
+ let fileCount = 0
103
189
 
104
- // Write base skeleton (dynamic templates)
105
- const templateFiles = Object.keys(templates).sort()
106
- for (const relativePath of templateFiles) {
190
+ // Step 1: Copy the skeleton directory as the base
191
+ const skeletonDir = resolve(import.meta.dir, '..', 'skeleton')
192
+ if (existsSync(skeletonDir)) {
193
+ // Build conditional skip set for skeleton files
194
+ const skeletonSkips = new Set<string>()
195
+ if (auth === 'none') {
196
+ skeletonSkips.add('config/auth.ts')
197
+ skeletonSkips.add('database/migrations/002_create_personal_access_tokens_table.ts')
198
+ }
199
+ if (!optionalPackages.includes('ai')) {
200
+ skeletonSkips.add('config/ai.ts')
201
+ }
202
+ fileCount += await copyDirectory(skeletonDir, projectDir, skeletonSkips)
203
+ } else {
204
+ // Fallback: skeleton not bundled (shouldn't happen in published package)
205
+ console.error(' Skeleton directory not found')
206
+ }
207
+
208
+ // Step 2: Generate dynamic files (package.json, .env — overwrites skeleton versions)
209
+ const appKey = `base64:${randomBytes(32).toString('base64')}`
210
+ const templates = getTemplates({ name: projectName, appKey, kit, ui, theme, auth, optionalPackages })
211
+ for (const [relativePath, content] of Object.entries(templates)) {
107
212
  const fullPath = `${projectDir}/${relativePath}`
108
213
  mkdirSync(dirname(fullPath), { recursive: true })
109
- await Bun.write(fullPath, templates[relativePath]!)
214
+ await Bun.write(fullPath, content)
215
+ fileCount++
110
216
  }
111
217
 
112
- // Copy starter kit stubs (static files)
113
- let stubCount = 0
218
+ // Step 3: Overlay starter kit stubs (new files + route overrides)
114
219
  if (kit) {
115
220
  const stubsDir = resolve(import.meta.dir, '..', 'stubs')
116
221
  const manifestPath = resolve(stubsDir, 'manifest.json')
@@ -120,33 +225,171 @@ if (kit) {
120
225
  const kitManifest = manifest[kit]
121
226
  const sharedManifest = manifest.shared
122
227
 
123
- // Copy kit-specific stubs (src/, vite.config.ts, tsconfig.json, etc.)
228
+ // Auth-related shared stubs to skip when auth === 'none'
229
+ const authSharedTargets = new Set([
230
+ 'app/Http/Controllers/AuthController.ts',
231
+ 'app/Http/Controllers/PageController.ts',
232
+ 'app/Http/Requests/LoginRequest.ts',
233
+ 'app/Http/Requests/RegisterRequest.ts',
234
+ 'routes/web.ts',
235
+ 'tests/feature/auth.test.ts',
236
+ ])
237
+
238
+ // Targets that tailwind-only stubs will override — skip from shadcn kit
239
+ const tailwindOnlyManifest = manifest['tailwind-only']?.[kit]
240
+ const tailwindOverrideTargets = new Set<string>()
241
+ if (ui === 'tailwind' && tailwindOnlyManifest?.files) {
242
+ for (const { target } of tailwindOnlyManifest.files) {
243
+ tailwindOverrideTargets.add(target)
244
+ }
245
+ }
246
+
247
+ // Kit-specific stubs (src/, vite.config.ts, etc.)
124
248
  if (kitManifest?.files) {
125
249
  for (const { stub, target } of kitManifest.files) {
250
+ // Skip components.json when ui === 'tailwind'
251
+ if (ui === 'tailwind' && stub === 'components.json.stub') continue
252
+ // Skip files that will be overridden by tailwind-only stubs
253
+ if (ui === 'tailwind' && tailwindOverrideTargets.has(target)) continue
126
254
  const src = resolve(stubsDir, kit, stub)
127
255
  const dest = resolve(projectDir, target)
128
256
  mkdirSync(dirname(dest), { recursive: true })
129
257
  await Bun.write(dest, Bun.file(src))
130
- stubCount++
258
+ fileCount++
131
259
  }
132
260
  }
133
261
 
134
- // Copy shared stubs (routes, controllers, seeder) with placeholder replacement
262
+ // Tailwind-only overlay: replace shadcn-dependent components with plain Tailwind versions
263
+ if (ui === 'tailwind' && tailwindOnlyManifest?.files) {
264
+ for (const { stub, target } of tailwindOnlyManifest.files) {
265
+ const src = resolve(stubsDir, 'tailwind-only', kit, stub)
266
+ const dest = resolve(projectDir, target)
267
+ mkdirSync(dirname(dest), { recursive: true })
268
+ await Bun.write(dest, Bun.file(src))
269
+ fileCount++
270
+ }
271
+ }
272
+
273
+ // Theme overlay: replace pages/layouts/styles with theme-specific variants
274
+ if (ui === 'shadcn' && theme !== 'default' && kit) {
275
+ const themeManifest = manifest.themes?.[theme]?.[kit]
276
+ if (themeManifest?.files) {
277
+ for (const { stub, target } of themeManifest.files) {
278
+ const src = resolve(stubsDir, 'themes', theme, kit, stub)
279
+ const dest = resolve(projectDir, target)
280
+ mkdirSync(dirname(dest), { recursive: true })
281
+ await Bun.write(dest, Bun.file(src))
282
+ fileCount++
283
+ }
284
+ }
285
+ }
286
+
287
+ // Shared stubs (routes, controllers, config — overwrites skeleton versions)
135
288
  if (sharedManifest?.files) {
136
- const mainEntry = kitManifest?.mainEntry ?? 'src/main.ts'
289
+ // Build placeholder map from manifest
290
+ const placeholders: Record<string, string> = {}
291
+ if (sharedManifest.placeholders) {
292
+ for (const [key, values] of Object.entries(sharedManifest.placeholders)) {
293
+ placeholders[key] = (values as Record<string, string>)[kit!] ?? ''
294
+ }
295
+ }
296
+
137
297
  for (const { stub, target } of sharedManifest.files) {
298
+ // Skip auth-related stubs when auth === 'none'
299
+ if (auth === 'none' && authSharedTargets.has(target)) continue
138
300
  const src = resolve(stubsDir, 'shared', stub)
139
301
  const dest = resolve(projectDir, target)
140
302
  mkdirSync(dirname(dest), { recursive: true })
141
303
  let content = await Bun.file(src).text()
142
- content = content.replace(/\{\{MAIN_ENTRY\}\}/g, mainEntry)
304
+ for (const [key, value] of Object.entries(placeholders)) {
305
+ content = content.replaceAll(key, value)
306
+ }
143
307
  await Bun.write(dest, content)
144
- stubCount++
308
+ fileCount++
309
+ }
310
+ }
311
+
312
+ // Noauth overlay: replace auth-aware routes/controllers/models when auth === 'none'
313
+ if (auth === 'none') {
314
+ const noauthManifest = manifest.noauth
315
+ if (noauthManifest?.files) {
316
+ for (const { stub, target } of noauthManifest.files) {
317
+ const src = resolve(stubsDir, 'noauth', stub)
318
+ if (existsSync(src)) {
319
+ const dest = resolve(projectDir, target)
320
+ mkdirSync(dirname(dest), { recursive: true })
321
+ let content = await Bun.file(src).text()
322
+ // Apply shared placeholders
323
+ if (sharedManifest?.placeholders) {
324
+ for (const [key, values] of Object.entries(sharedManifest.placeholders)) {
325
+ const value = (values as Record<string, string>)[kit!] ?? ''
326
+ content = content.replaceAll(key, value)
327
+ }
328
+ }
329
+ await Bun.write(dest, content)
330
+ fileCount++
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ } else {
337
+ // API-only: overlay token-based auth stubs
338
+ const stubsDir = resolve(import.meta.dir, '..', 'stubs')
339
+
340
+ // Auth-related API-only stubs to skip when auth === 'none'
341
+ const authApiOnlyTargets = new Set([
342
+ 'app/Http/Controllers/ApiAuthController.ts',
343
+ 'app/Http/Requests/RegisterRequest.ts',
344
+ 'app/Http/Requests/LoginRequest.ts',
345
+ 'tests/feature/token-auth.test.ts',
346
+ ])
347
+
348
+ const apiOnlyFiles = [
349
+ { stub: 'api-only/routes/api.ts.stub', target: 'routes/api.ts' },
350
+ { stub: 'shared/app/Http/Controllers/ApiAuthController.ts.stub', target: 'app/Http/Controllers/ApiAuthController.ts' },
351
+ { stub: 'shared/app/Http/Controllers/UserController.ts.stub', target: 'app/Http/Controllers/UserController.ts' },
352
+ { stub: 'shared/app/Http/Requests/RegisterRequest.ts.stub', target: 'app/Http/Requests/RegisterRequest.ts' },
353
+ { stub: 'shared/app/Http/Requests/LoginRequest.ts.stub', target: 'app/Http/Requests/LoginRequest.ts' },
354
+ { stub: 'shared/app/Http/Requests/StoreUserRequest.ts.stub', target: 'app/Http/Requests/StoreUserRequest.ts' },
355
+ { stub: 'shared/app/Http/Requests/UpdateUserRequest.ts.stub', target: 'app/Http/Requests/UpdateUserRequest.ts' },
356
+ { stub: 'shared/database/seeders/DatabaseSeeder.ts.stub', target: 'database/seeders/DatabaseSeeder.ts' },
357
+ { stub: 'shared/database/factories/UserFactory.ts.stub', target: 'database/factories/UserFactory.ts' },
358
+ { stub: 'api-only/tests/feature/token-auth.test.ts.stub', target: 'tests/feature/token-auth.test.ts' },
359
+ ]
360
+ for (const { stub, target } of apiOnlyFiles) {
361
+ // Skip auth-related stubs when auth === 'none'
362
+ if (auth === 'none' && authApiOnlyTargets.has(target)) continue
363
+ const src = resolve(stubsDir, stub)
364
+ if (existsSync(src)) {
365
+ const dest = resolve(projectDir, target)
366
+ mkdirSync(dirname(dest), { recursive: true })
367
+ await Bun.write(dest, Bun.file(src))
368
+ fileCount++
369
+ }
370
+ }
371
+
372
+ // Noauth overlay for API-only
373
+ if (auth === 'none') {
374
+ const manifestPath = resolve(stubsDir, 'manifest.json')
375
+ if (existsSync(manifestPath)) {
376
+ const manifest = JSON.parse(await Bun.file(manifestPath).text())
377
+ const noauthManifest = manifest.noauth
378
+ if (noauthManifest?.files) {
379
+ for (const { stub, target } of noauthManifest.files) {
380
+ const src = resolve(stubsDir, 'noauth', stub)
381
+ if (existsSync(src)) {
382
+ const dest = resolve(projectDir, target)
383
+ mkdirSync(dirname(dest), { recursive: true })
384
+ await Bun.write(dest, Bun.file(src))
385
+ fileCount++
386
+ }
387
+ }
145
388
  }
146
389
  }
147
390
  }
148
391
  }
149
- console.log(` ${dim(`${templateFiles.length + stubCount} files created`)}`)
392
+ console.log(` ${dim(`${fileCount} files created`)}`)
150
393
 
151
394
  // ── Install dependencies ─────────────────────────────────────────────────────
152
395
  const spin = term.spinner('Installing dependencies')
@@ -159,8 +402,8 @@ const install = Bun.spawn(['bun', 'install'], {
159
402
  await install.exited
160
403
  spin.stop('Dependencies installed')
161
404
 
162
- // ── Install shadcn components (React always uses shadcn) ─────────────────────
163
- if (kit === 'react') {
405
+ // ── Install shadcn components ────────────────────────────────────────────────
406
+ if (kit === 'react' && ui === 'shadcn') {
164
407
  const shadcnSpin = term.spinner('Installing shadcn/ui components')
165
408
 
166
409
  const run = async (args: string[]) => {
@@ -203,6 +446,24 @@ if (kit) {
203
446
  buildSpin.stop('Frontend built')
204
447
  }
205
448
 
449
+ // ── Generate AI agent rules ──────────────────────────────────────────────────
450
+ const agentSpin = term.spinner('Generating AI agent rules')
451
+ try {
452
+ const agentGen = Bun.spawn(['bun', 'run', 'mantiq', 'agent:generate'], {
453
+ cwd: projectDir,
454
+ stdout: 'pipe',
455
+ stderr: 'pipe',
456
+ })
457
+ const agentExit = await agentGen.exited
458
+ if (agentExit === 0) {
459
+ agentSpin.stop('AI agent rules generated')
460
+ } else {
461
+ agentSpin.stop('AI agent rules skipped')
462
+ }
463
+ } catch {
464
+ agentSpin.stop('AI agent rules skipped')
465
+ }
466
+
206
467
  // ── Git init ─────────────────────────────────────────────────────────────────
207
468
  if (!noGit) {
208
469
  const gitSpin = term.spinner('Initializing git')
@@ -218,8 +479,11 @@ if (!noGit) {
218
479
 
219
480
  // ── Done ─────────────────────────────────────────────────────────────────────
220
481
  console.log(`\n ${emerald('✓')} ${bold(projectName)} created\n`)
221
- console.log(` ${dim('Framework')} ${kit ? bold(kit.charAt(0).toUpperCase() + kit.slice(1)) : dim('None (API only)')}`)
222
- if (kit === 'react') console.log(` ${dim('UI Kit')} ${ui === 'shadcn' ? bold('shadcn/ui') : dim('Plain Tailwind')}`)
482
+ console.log(` ${dim('Framework')} ${kit ? bold(kit.charAt(0).toUpperCase() + kit.slice(1)) : dim('Vanilla (API only)')}`)
483
+ if (kit) console.log(` ${dim('UI Kit')} ${ui === 'shadcn' ? bold('shadcn/ui') : bold('Tailwind')}`)
484
+ if (kit && ui === 'shadcn') console.log(` ${dim('Theme')} ${bold(theme.charAt(0).toUpperCase() + theme.slice(1))}`)
485
+ console.log(` ${dim('Auth')} ${auth === 'builtin' ? bold('Built-in') : dim('None')}`)
486
+ if (optionalPackages.length > 0) console.log(` ${dim('Extras')} ${bold(optionalPackages.join(', '))}`)
223
487
  console.log(`\n ${dim('Next steps:')}\n`)
224
488
  console.log(` cd ${projectName}`)
225
489
  console.log(` bun mantiq migrate`)