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.
- package/package.json +2 -1
- package/skeleton/.env.example +64 -0
- package/skeleton/README.md +46 -0
- package/skeleton/app/Console/Commands/.gitkeep +0 -0
- package/skeleton/app/Enums/UserStatus.ts +7 -0
- package/skeleton/app/Http/Controllers/HomeController.ts +78 -0
- package/skeleton/app/Http/Middleware/.gitkeep +0 -0
- package/skeleton/app/Models/User.ts +7 -0
- package/skeleton/app/Providers/AppServiceProvider.ts +25 -0
- package/skeleton/app/Providers/DatabaseServiceProvider.ts +17 -0
- package/skeleton/bootstrap/.gitkeep +0 -0
- package/skeleton/config/ai.ts +51 -0
- package/skeleton/config/app.ts +108 -0
- package/skeleton/config/auth.ts +51 -0
- package/skeleton/config/broadcasting.ts +93 -0
- package/skeleton/config/cache.ts +61 -0
- package/skeleton/config/cors.ts +77 -0
- package/skeleton/config/database.ts +120 -0
- package/skeleton/config/filesystem.ts +58 -0
- package/skeleton/config/hashing.ts +47 -0
- package/skeleton/config/heartbeat.ts +112 -0
- package/skeleton/config/logging.ts +58 -0
- package/skeleton/config/mail.ts +93 -0
- package/skeleton/config/notify.ts +141 -0
- package/skeleton/config/queue.ts +59 -0
- package/skeleton/config/search.ts +96 -0
- package/skeleton/config/services.ts +110 -0
- package/skeleton/config/session.ts +84 -0
- package/skeleton/config/vite.ts +33 -0
- package/skeleton/database/factories/.gitkeep +0 -0
- package/skeleton/database/migrations/001_create_users_table.ts +19 -0
- package/skeleton/database/migrations/002_create_personal_access_tokens_table.ts +22 -0
- package/skeleton/database/seeders/DatabaseSeeder.ts +7 -0
- package/skeleton/index.ts +20 -0
- package/skeleton/mantiq.ts +8 -0
- package/skeleton/package.json +34 -0
- package/skeleton/public/.gitkeep +0 -0
- package/skeleton/routes/api.ts +8 -0
- package/skeleton/routes/channels.ts +23 -0
- package/skeleton/routes/console.ts +24 -0
- package/skeleton/routes/web.ts +6 -0
- package/skeleton/storage/cache/.gitkeep +0 -0
- package/skeleton/storage/framework/.gitkeep +0 -0
- package/skeleton/tests/feature/api.test.ts +14 -0
- package/skeleton/tests/feature/home.test.ts +17 -0
- package/skeleton/tests/unit/example.test.ts +11 -0
- package/skeleton/tsconfig.json +27 -0
- package/src/index.ts +289 -25
- package/src/templates.ts +141 -945
- package/src/terminal.ts +64 -0
- package/stubs/api-only/routes/api.ts.stub +24 -0
- package/stubs/api-only/tests/feature/token-auth.test.ts.stub +69 -0
- package/stubs/auth/api/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
- package/stubs/auth/api/routes/api.ts.stub +24 -0
- package/stubs/auth/api/tests/feature/token-auth.test.ts.stub +69 -0
- package/stubs/auth/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
- package/stubs/auth/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
- package/stubs/auth/web/app/Http/Controllers/AuthController.ts.stub +43 -0
- package/stubs/auth/web/app/Http/Controllers/PageController.ts.stub +66 -0
- package/stubs/auth/web/routes/web.ts.stub +25 -0
- package/stubs/auth/web/svelte/src/App.svelte.stub +77 -0
- package/stubs/auth/web/svelte/src/pages.ts.stub +17 -0
- package/stubs/auth/web/tests/feature/auth.test.ts.stub +69 -0
- package/stubs/auth/web/vue/src/App.vue.stub +74 -0
- package/stubs/auth/web/vue/src/pages.ts.stub +17 -0
- package/stubs/manifest.json +630 -2
- package/stubs/noauth/app/Http/Controllers/PageController.ts.stub +41 -0
- package/stubs/noauth/app/Models/User.ts.stub +5 -0
- package/stubs/noauth/database/migrations/001_create_users_table.ts.stub +17 -0
- package/stubs/noauth/routes/api.ts.stub +16 -0
- package/stubs/noauth/routes/web.ts.stub +15 -0
- package/stubs/noauth/svelte/src/App.svelte.stub +68 -0
- package/stubs/noauth/svelte/src/pages.ts.stub +7 -0
- package/stubs/noauth/vue/src/App.vue.stub +62 -0
- package/stubs/noauth/vue/src/pages.ts.stub +7 -0
- package/stubs/react/src/App.tsx.stub +4 -2
- package/stubs/react/src/components/layout/search-dialog.tsx.stub +2 -2
- package/stubs/react/src/components/layout/sidebar-data.ts.stub +2 -2
- package/stubs/react/src/lib/api.ts.stub +30 -6
- package/stubs/react/src/pages/Login.tsx.stub +3 -3
- package/stubs/react/src/pages/users/dialogs.tsx.stub +7 -26
- package/stubs/react/vite.config.ts.stub +26 -3
- package/stubs/shared/app/Http/Controllers/ApiAuthController.ts.stub +57 -0
- package/stubs/shared/app/Http/Controllers/AuthController.ts.stub +14 -38
- package/stubs/shared/app/Http/Controllers/PageController.ts.stub +3 -3
- package/stubs/shared/app/Http/Controllers/UserController.ts.stub +61 -0
- package/stubs/shared/app/Http/Requests/LoginRequest.ts.stub +10 -0
- package/stubs/shared/app/Http/Requests/RegisterRequest.ts.stub +11 -0
- package/stubs/shared/app/Http/Requests/StoreUserRequest.ts.stub +11 -0
- package/stubs/shared/app/Http/Requests/UpdateUserRequest.ts.stub +11 -0
- package/stubs/shared/config/app.ts.stub +36 -0
- package/stubs/shared/config/vite.ts.stub +8 -0
- package/stubs/shared/database/factories/UserFactory.ts.stub +4 -6
- package/stubs/shared/routes/api.ts.stub +12 -102
- package/stubs/shared/routes/web.ts.stub +5 -3
- package/stubs/shared/tests/feature/auth.test.ts.stub +69 -0
- package/stubs/shared/tests/feature/users.test.ts.stub +90 -0
- package/stubs/svelte/src/App.svelte.stub +1 -1
- package/stubs/svelte/src/lib/api.ts.stub +30 -6
- package/stubs/svelte/src/main.ts.stub +3 -1
- package/stubs/svelte/src/pages/Login.svelte.stub +3 -3
- package/stubs/svelte/vite.config.ts.stub +20 -1
- package/stubs/tailwind-only/react/src/components/layout/app-sidebar.tsx.stub +68 -0
- package/stubs/tailwind-only/react/src/components/layout/authenticated-layout.tsx.stub +57 -0
- package/stubs/tailwind-only/react/src/components/layout/header.tsx.stub +52 -0
- package/stubs/tailwind-only/react/src/components/layout/main.tsx.stub +21 -0
- package/stubs/tailwind-only/react/src/components/layout/nav-group.tsx.stub +185 -0
- package/stubs/tailwind-only/react/src/components/layout/nav-user.tsx.stub +106 -0
- package/stubs/tailwind-only/react/src/components/layout/sidebar-data.ts.stub +58 -0
- package/stubs/tailwind-only/react/src/components/layout/theme-toggle.tsx.stub +36 -0
- package/stubs/tailwind-only/react/src/components/layout/top-nav.tsx.stub +72 -0
- package/stubs/tailwind-only/react/src/lib/utils.ts.stub +6 -0
- package/stubs/tailwind-only/react/src/pages/Dashboard.tsx.stub +205 -0
- package/stubs/tailwind-only/react/src/pages/Login.tsx.stub +122 -0
- package/stubs/tailwind-only/react/src/pages/Register.tsx.stub +137 -0
- package/stubs/tailwind-only/react/src/pages/Users.tsx.stub +426 -0
- package/stubs/tailwind-only/react/src/pages/account/layout.tsx.stub +64 -0
- package/stubs/tailwind-only/react/src/pages/account/preferences.tsx.stub +80 -0
- package/stubs/tailwind-only/react/src/pages/account/profile.tsx.stub +67 -0
- package/stubs/tailwind-only/react/src/pages/account/security.tsx.stub +91 -0
- package/stubs/tailwind-only/react/src/style.css.stub +14 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/app-sidebar.svelte.stub +104 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/authenticated-layout.svelte.stub +51 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/header.svelte.stub +66 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/main.svelte.stub +26 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-group.svelte.stub +131 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/nav-user.svelte.stub +104 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/sidebar-data.ts.stub +57 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/theme-toggle.svelte.stub +28 -0
- package/stubs/tailwind-only/svelte/src/lib/components/layout/top-nav.svelte.stub +99 -0
- package/stubs/tailwind-only/svelte/src/lib/utils.ts.stub +6 -0
- package/stubs/tailwind-only/svelte/src/pages/Dashboard.svelte.stub +192 -0
- package/stubs/tailwind-only/svelte/src/pages/Login.svelte.stub +120 -0
- package/stubs/tailwind-only/svelte/src/pages/Register.svelte.stub +134 -0
- package/stubs/tailwind-only/svelte/src/pages/Users.svelte.stub +293 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Layout.svelte.stub +61 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Preferences.svelte.stub +81 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Profile.svelte.stub +76 -0
- package/stubs/tailwind-only/svelte/src/pages/account/Security.svelte.stub +140 -0
- package/stubs/tailwind-only/svelte/src/style.css.stub +127 -0
- package/stubs/tailwind-only/vue/src/components/layout/AppSidebar.vue.stub +60 -0
- package/stubs/tailwind-only/vue/src/components/layout/AuthenticatedLayout.vue.stub +73 -0
- package/stubs/tailwind-only/vue/src/components/layout/Header.vue.stub +54 -0
- package/stubs/tailwind-only/vue/src/components/layout/Main.vue.stub +22 -0
- package/stubs/tailwind-only/vue/src/components/layout/NavGroup.vue.stub +107 -0
- package/stubs/tailwind-only/vue/src/components/layout/NavUser.vue.stub +104 -0
- package/stubs/tailwind-only/vue/src/components/layout/ThemeToggle.vue.stub +28 -0
- package/stubs/tailwind-only/vue/src/components/layout/TopNav.vue.stub +86 -0
- package/stubs/tailwind-only/vue/src/components/layout/sidebar-data.ts.stub +57 -0
- package/stubs/tailwind-only/vue/src/lib/utils.ts.stub +7 -0
- package/stubs/tailwind-only/vue/src/pages/Dashboard.vue.stub +195 -0
- package/stubs/tailwind-only/vue/src/pages/Login.vue.stub +121 -0
- package/stubs/tailwind-only/vue/src/pages/Register.vue.stub +137 -0
- package/stubs/tailwind-only/vue/src/pages/Users.vue.stub +401 -0
- package/stubs/tailwind-only/vue/src/pages/account/Layout.vue.stub +56 -0
- package/stubs/tailwind-only/vue/src/pages/account/Preferences.vue.stub +81 -0
- package/stubs/tailwind-only/vue/src/pages/account/Profile.vue.stub +69 -0
- package/stubs/tailwind-only/vue/src/pages/account/Security.vue.stub +85 -0
- package/stubs/tailwind-only/vue/src/style.css.stub +26 -0
- package/stubs/themes/corporate/react/src/components/layout/app-sidebar.tsx.stub +74 -0
- package/stubs/themes/corporate/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/corporate/react/src/pages/Dashboard.tsx.stub +310 -0
- package/stubs/themes/corporate/react/src/pages/Login.tsx.stub +122 -0
- package/stubs/themes/corporate/react/src/pages/Register.tsx.stub +144 -0
- package/stubs/themes/corporate/react/src/style.css.stub +135 -0
- package/stubs/themes/corporate/svelte/src/pages/Dashboard.svelte.stub +271 -0
- package/stubs/themes/corporate/svelte/src/pages/Login.svelte.stub +117 -0
- package/stubs/themes/corporate/svelte/src/pages/Register.svelte.stub +138 -0
- package/stubs/themes/corporate/svelte/src/style.css.stub +134 -0
- package/stubs/themes/corporate/vue/src/pages/Dashboard.vue.stub +325 -0
- package/stubs/themes/corporate/vue/src/pages/Login.vue.stub +118 -0
- package/stubs/themes/corporate/vue/src/pages/Register.vue.stub +139 -0
- package/stubs/themes/corporate/vue/src/style.css.stub +134 -0
- package/stubs/themes/minimal/react/src/components/layout/app-sidebar.tsx.stub +68 -0
- package/stubs/themes/minimal/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/minimal/react/src/pages/Dashboard.tsx.stub +166 -0
- package/stubs/themes/minimal/react/src/pages/Login.tsx.stub +95 -0
- package/stubs/themes/minimal/react/src/pages/Register.tsx.stub +73 -0
- package/stubs/themes/minimal/react/src/style.css.stub +142 -0
- package/stubs/themes/minimal/svelte/src/pages/Dashboard.svelte.stub +149 -0
- package/stubs/themes/minimal/svelte/src/pages/Login.svelte.stub +90 -0
- package/stubs/themes/minimal/svelte/src/pages/Register.svelte.stub +70 -0
- package/stubs/themes/minimal/svelte/src/style.css.stub +142 -0
- package/stubs/themes/minimal/vue/src/pages/Dashboard.vue.stub +163 -0
- package/stubs/themes/minimal/vue/src/pages/Login.vue.stub +91 -0
- package/stubs/themes/minimal/vue/src/pages/Register.vue.stub +73 -0
- package/stubs/themes/minimal/vue/src/style.css.stub +142 -0
- package/stubs/themes/starter/react/src/components/layout/app-sidebar.tsx.stub +74 -0
- package/stubs/themes/starter/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/starter/react/src/pages/Dashboard.tsx.stub +236 -0
- package/stubs/themes/starter/react/src/pages/Login.tsx.stub +131 -0
- package/stubs/themes/starter/react/src/pages/Register.tsx.stub +145 -0
- package/stubs/themes/starter/react/src/style.css.stub +141 -0
- package/stubs/themes/starter/svelte/src/pages/Dashboard.svelte.stub +212 -0
- package/stubs/themes/starter/svelte/src/pages/Login.svelte.stub +126 -0
- package/stubs/themes/starter/svelte/src/pages/Register.svelte.stub +139 -0
- package/stubs/themes/starter/svelte/src/style.css.stub +141 -0
- package/stubs/themes/starter/vue/src/pages/Dashboard.vue.stub +228 -0
- package/stubs/themes/starter/vue/src/pages/Login.vue.stub +127 -0
- package/stubs/themes/starter/vue/src/pages/Register.vue.stub +140 -0
- package/stubs/themes/starter/vue/src/style.css.stub +141 -0
- package/stubs/themes/workspace/react/src/components/layout/app-sidebar.tsx.stub +97 -0
- package/stubs/themes/workspace/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
- package/stubs/themes/workspace/react/src/pages/Dashboard.tsx.stub +304 -0
- package/stubs/themes/workspace/react/src/pages/Login.tsx.stub +131 -0
- package/stubs/themes/workspace/react/src/pages/Register.tsx.stub +82 -0
- package/stubs/themes/workspace/react/src/style.css.stub +138 -0
- package/stubs/themes/workspace/svelte/src/pages/Dashboard.svelte.stub +215 -0
- package/stubs/themes/workspace/svelte/src/pages/Login.svelte.stub +124 -0
- package/stubs/themes/workspace/svelte/src/pages/Register.svelte.stub +76 -0
- package/stubs/themes/workspace/svelte/src/style.css.stub +134 -0
- package/stubs/themes/workspace/vue/src/pages/Dashboard.vue.stub +220 -0
- package/stubs/themes/workspace/vue/src/pages/Login.vue.stub +128 -0
- package/stubs/themes/workspace/vue/src/pages/Register.vue.stub +80 -0
- package/stubs/themes/workspace/vue/src/style.css.stub +134 -0
- package/stubs/vue/src/App.vue.stub +2 -1
- package/stubs/vue/src/lib/api.ts.stub +30 -6
- package/stubs/vue/src/main.ts.stub +3 -1
- package/stubs/vue/src/pages/Login.vue.stub +3 -3
- 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
|
+
}
|
|
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,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')}
|
|
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' | '
|
|
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: '
|
|
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
|
-
|
|
102
|
-
const templates = getTemplates({ name: projectName, appKey, kit, ui })
|
|
188
|
+
let fileCount = 0
|
|
103
189
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
|
|
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,
|
|
214
|
+
await Bun.write(fullPath, content)
|
|
215
|
+
fileCount++
|
|
110
216
|
}
|
|
111
217
|
|
|
112
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
258
|
+
fileCount++
|
|
131
259
|
}
|
|
132
260
|
}
|
|
133
261
|
|
|
134
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
304
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
305
|
+
content = content.replaceAll(key, value)
|
|
306
|
+
}
|
|
143
307
|
await Bun.write(dest, content)
|
|
144
|
-
|
|
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(`${
|
|
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
|
|
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('
|
|
222
|
-
if (kit
|
|
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`)
|