@strav/create 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +46 -0
  3. package/package.json +24 -0
  4. package/src/index.ts +164 -0
  5. package/src/prompts.ts +135 -0
  6. package/src/scaffold.ts +54 -0
  7. package/src/templates/api/config/http.ts +7 -0
  8. package/src/templates/api/index.ts +30 -0
  9. package/src/templates/api/start/routes.ts +12 -0
  10. package/src/templates/api/tests/health.test.ts.tpl +12 -0
  11. package/src/templates/shared/.env.tpl +13 -0
  12. package/src/templates/shared/.gitignore.tpl +5 -0
  13. package/src/templates/shared/app/controllers/.gitkeep +0 -0
  14. package/src/templates/shared/app/models/.gitkeep +0 -0
  15. package/src/templates/shared/config/app.ts +7 -0
  16. package/src/templates/shared/config/database.ts +11 -0
  17. package/src/templates/shared/config/encryption.ts +6 -0
  18. package/src/templates/shared/database/migrations/.gitkeep +0 -0
  19. package/src/templates/shared/database/schemas/user.ts +10 -0
  20. package/src/templates/shared/package.json +25 -0
  21. package/src/templates/shared/strav.ts +2 -0
  22. package/src/templates/shared/tsconfig.json +24 -0
  23. package/src/templates/web/config/http.ts +8 -0
  24. package/src/templates/web/config/session.ts +7 -0
  25. package/src/templates/web/config/view.ts +4 -0
  26. package/src/templates/web/index.ts +36 -0
  27. package/src/templates/web/public/styles.css +53 -0
  28. package/src/templates/web/resources/islands/setup.ts +11 -0
  29. package/src/templates/web/resources/styles/.gitkeep +0 -0
  30. package/src/templates/web/resources/views/welcome.strav +21 -0
  31. package/src/templates/web/start/routes.ts +10 -0
  32. package/src/templates/web/tests/health.test.ts.tpl +12 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.5.3
4
+
5
+ ### Added
6
+
7
+ - **Web template**: `resources/islands/setup.ts` stub for shared Vue app configuration (plugin installation, global provides)
8
+
9
+ ## 0.1.1
10
+
11
+ ### Changed
12
+
13
+ - Applied consistent code formatting across all source files
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @stravigor/create
2
+
3
+ Scaffold a new [Strav](https://www.npmjs.com/package/@stravigor/core) application.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ bunx @stravigor/create@latest my-app --api # headless REST API
9
+ bunx @stravigor/create@latest my-app --web # full-stack with views
10
+ bunx @stravigor/create@latest my-app # interactive prompt
11
+ ```
12
+
13
+ ## Templates
14
+
15
+ - **api** — Headless REST API with CORS enabled
16
+ - **web** — Full-stack with `.strav` views, sessions, and static files
17
+
18
+ ## Options
19
+
20
+ ```
21
+ bunx @stravigor/create <project-name> [options]
22
+
23
+ --api Headless REST API template
24
+ --web Full-stack template with views and static files
25
+ --template, -t api|web Alias for --api / --web
26
+ --db <name> Database name (default: project name)
27
+ -h, --help Show help
28
+ ```
29
+
30
+ ## What's scaffolded
31
+
32
+ ```
33
+ my-app/
34
+ ├── index.ts Server entry point
35
+ ├── strav.ts CLI (migrations, generators)
36
+ ├── config/ Configuration files
37
+ ├── database/schemas/ Schema definitions
38
+ ├── start/routes.ts Route registration
39
+ ├── tests/ Test files
40
+ ├── .env Environment variables
41
+ └── package.json
42
+ ```
43
+
44
+ ## License
45
+
46
+ MIT
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@strav/create",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Scaffold a new Strav application",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "strav",
9
+ "stravigor",
10
+ "bun",
11
+ "framework",
12
+ "scaffold",
13
+ "create"
14
+ ],
15
+ "bin": {
16
+ "@strav/create": "./src/index.ts"
17
+ },
18
+ "files": [
19
+ "src/",
20
+ "package.json",
21
+ "README.md",
22
+ "CHANGELOG.md"
23
+ ]
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from 'node:fs'
3
+ import { join, resolve } from 'node:path'
4
+ import { select, input } from './prompts.ts'
5
+ import { scaffold, type ScaffoldOptions } from './scaffold.ts'
6
+ import pkg from '../package.json'
7
+
8
+ const VERSION = pkg.version
9
+
10
+ // ── Colors ──────────────────────────────────────────────────────────
11
+
12
+ const bold = (s: string) => `\x1b[1m${s}\x1b[0m`
13
+ const dim = (s: string) => `\x1b[2m${s}\x1b[0m`
14
+ const green = (s: string) => `\x1b[32m${s}\x1b[0m`
15
+ const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`
16
+ const red = (s: string) => `\x1b[31m${s}\x1b[0m`
17
+
18
+ // ── Arg parsing ─────────────────────────────────────────────────────
19
+
20
+ interface ParsedArgs {
21
+ projectName?: string
22
+ template?: 'api' | 'web'
23
+ db?: string
24
+ help?: boolean
25
+ }
26
+
27
+ function parseArgs(): ParsedArgs {
28
+ const args = process.argv.slice(2)
29
+ const result: ParsedArgs = {}
30
+
31
+ for (let i = 0; i < args.length; i++) {
32
+ const arg = args[i]
33
+
34
+ if (arg === '--help' || arg === '-h') {
35
+ result.help = true
36
+ } else if (arg === '--api') {
37
+ result.template = 'api'
38
+ } else if (arg === '--web') {
39
+ result.template = 'web'
40
+ } else if (arg === '--template' || arg === '-t') {
41
+ const val = args[++i]
42
+ if (val === 'api' || val === 'web') {
43
+ result.template = val
44
+ } else {
45
+ console.error(red(` Invalid template: ${val}. Use "api" or "web".`))
46
+ process.exit(1)
47
+ }
48
+ } else if (arg === '--db') {
49
+ result.db = args[++i]
50
+ } else if (arg && !arg.startsWith('-') && !result.projectName) {
51
+ result.projectName = arg
52
+ }
53
+ }
54
+
55
+ return result
56
+ }
57
+
58
+ function printUsage(): void {
59
+ console.log(`
60
+ ${bold('@stravigor/create')} ${dim(`v${VERSION}`)}
61
+
62
+ ${bold('Usage:')}
63
+ bunx @stravigor/create ${cyan('<project-name>')} [options]
64
+
65
+ ${bold('Options:')}
66
+ --api Headless REST API template
67
+ --web Full-stack template with views and static files
68
+ --template, -t ${dim('api|web')} Alias for --api / --web
69
+ --db ${dim('<name>')} Database name (default: project name)
70
+ -h, --help Show this help message
71
+ `)
72
+ }
73
+
74
+ function toSnakeCase(name: string): string {
75
+ return name.replace(/-/g, '_')
76
+ }
77
+
78
+ // ── Main ────────────────────────────────────────────────────────────
79
+
80
+ async function main(): Promise<void> {
81
+ const args = parseArgs()
82
+
83
+ if (args.help) {
84
+ printUsage()
85
+ process.exit(0)
86
+ }
87
+
88
+ console.log()
89
+ console.log(` ${bold('@stravigor/create')} ${dim(`v${VERSION}`)}`)
90
+ console.log()
91
+
92
+ // Project name
93
+ if (!args.projectName) {
94
+ printUsage()
95
+ process.exit(1)
96
+ }
97
+
98
+ const projectName = args.projectName
99
+ const root = resolve(projectName)
100
+
101
+ // Validate
102
+ if (existsSync(root)) {
103
+ console.error(red(` Directory "${projectName}" already exists.`))
104
+ process.exit(1)
105
+ }
106
+
107
+ if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
108
+ console.error(
109
+ red(` Invalid project name. Use only letters, numbers, hyphens, and underscores.`)
110
+ )
111
+ process.exit(1)
112
+ }
113
+
114
+ // Template
115
+ let template = args.template
116
+ if (!template) {
117
+ template = (await select('Which template?', [
118
+ { label: 'api', value: 'api', description: 'Headless REST API' },
119
+ { label: 'web', value: 'web', description: 'Full-stack with views and static files' },
120
+ ])) as 'api' | 'web'
121
+ }
122
+
123
+ // Database name
124
+ const defaultDb = toSnakeCase(projectName)
125
+ const dbName = args.db ?? (await input('Database name:', defaultDb))
126
+
127
+ console.log()
128
+
129
+ // Scaffold
130
+ const opts: ScaffoldOptions = { projectName, template, dbName }
131
+ await scaffold(root, opts)
132
+ console.log(` ${green('+')} Scaffolded project files`)
133
+
134
+ // Install dependencies
135
+ console.log(` ${dim('...')} Installing dependencies`)
136
+ const install = Bun.spawn(['bun', 'install'], { cwd: root, stdout: 'ignore', stderr: 'pipe' })
137
+ const exitCode = await install.exited
138
+
139
+ if (exitCode !== 0) {
140
+ const stderr = await new Response(install.stderr).text()
141
+ console.error(red(` Failed to install dependencies:`))
142
+ console.error(dim(` ${stderr}`))
143
+ process.exit(1)
144
+ }
145
+
146
+ console.log(` ${green('+')} Installed dependencies`)
147
+
148
+ // Done
149
+ console.log()
150
+ console.log(` ${green('Project created successfully!')}`)
151
+ console.log()
152
+ console.log(` Next steps:`)
153
+ console.log()
154
+ console.log(` ${dim('$')} cd ${projectName}`)
155
+ console.log(` ${dim('$')} bun --hot index.ts`)
156
+ console.log()
157
+ console.log(` ${dim('Then open http://localhost:3000')}`)
158
+ console.log()
159
+ }
160
+
161
+ main().catch(err => {
162
+ console.error(red(` Error: ${err instanceof Error ? err.message : err}`))
163
+ process.exit(1)
164
+ })
package/src/prompts.ts ADDED
@@ -0,0 +1,135 @@
1
+ const ESC = '\x1b'
2
+ const ARROW_UP = `${ESC}[A`
3
+ const ARROW_DOWN = `${ESC}[B`
4
+ const ENTER = '\r'
5
+
6
+ interface Choice {
7
+ label: string
8
+ value: string
9
+ description: string
10
+ }
11
+
12
+ export async function select(message: string, choices: Choice[]): Promise<string> {
13
+ let selected = 0
14
+
15
+ const render = () => {
16
+ // Move cursor up to overwrite previous render (except first time)
17
+ process.stdout.write(`\x1b[${choices.length}A`)
18
+ for (let i = 0; i < choices.length; i++) {
19
+ const prefix = i === selected ? '\x1b[36m>\x1b[0m' : ' '
20
+ const choice = choices[i]!
21
+ const label = i === selected ? `\x1b[1m${choice.label}\x1b[0m` : choice.label
22
+ const desc = `\x1b[2m${choice.description}\x1b[0m`
23
+ process.stdout.write(`\x1b[2K ${prefix} ${label} ${desc}\n`)
24
+ }
25
+ }
26
+
27
+ process.stdout.write(` \x1b[1m${message}\x1b[0m\n`)
28
+ // Print initial lines so render() can overwrite them
29
+ for (const choice of choices) {
30
+ process.stdout.write('\n')
31
+ }
32
+ render()
33
+
34
+ return new Promise(resolve => {
35
+ const stdin = process.stdin
36
+ stdin.setRawMode(true)
37
+ stdin.resume()
38
+ stdin.setEncoding('utf8')
39
+
40
+ let buffer = ''
41
+
42
+ const onData = (data: string) => {
43
+ buffer += data
44
+
45
+ // Check for Ctrl+C
46
+ if (buffer.includes('\x03')) {
47
+ stdin.setRawMode(false)
48
+ stdin.pause()
49
+ stdin.removeListener('data', onData)
50
+ process.stdout.write('\n')
51
+ process.exit(0)
52
+ }
53
+
54
+ // Process escape sequences
55
+ while (buffer.length > 0) {
56
+ if (buffer.startsWith(ARROW_UP)) {
57
+ selected = (selected - 1 + choices.length) % choices.length
58
+ render()
59
+ buffer = buffer.slice(ARROW_UP.length)
60
+ } else if (buffer.startsWith(ARROW_DOWN)) {
61
+ selected = (selected + 1) % choices.length
62
+ render()
63
+ buffer = buffer.slice(ARROW_DOWN.length)
64
+ } else if (buffer.startsWith(ENTER)) {
65
+ stdin.setRawMode(false)
66
+ stdin.pause()
67
+ stdin.removeListener('data', onData)
68
+ resolve(choices[selected]!.value)
69
+ buffer = ''
70
+ return
71
+ } else if (buffer.startsWith(ESC)) {
72
+ // Incomplete escape sequence, wait for more data
73
+ break
74
+ } else {
75
+ // Discard unrecognized input
76
+ buffer = buffer.slice(1)
77
+ }
78
+ }
79
+ }
80
+
81
+ stdin.on('data', onData)
82
+ })
83
+ }
84
+
85
+ export async function input(message: string, defaultValue: string): Promise<string> {
86
+ process.stdout.write(` \x1b[1m${message}\x1b[0m \x1b[2m(${defaultValue})\x1b[0m `)
87
+
88
+ return new Promise(resolve => {
89
+ const stdin = process.stdin
90
+ stdin.setRawMode(true)
91
+ stdin.resume()
92
+ stdin.setEncoding('utf8')
93
+
94
+ let value = ''
95
+
96
+ const onData = (data: string) => {
97
+ for (const char of data) {
98
+ if (char === '\x03') {
99
+ // Ctrl+C
100
+ stdin.setRawMode(false)
101
+ stdin.pause()
102
+ stdin.removeListener('data', onData)
103
+ process.stdout.write('\n')
104
+ process.exit(0)
105
+ }
106
+
107
+ if (char === '\r' || char === '\n') {
108
+ stdin.setRawMode(false)
109
+ stdin.pause()
110
+ stdin.removeListener('data', onData)
111
+ process.stdout.write('\n')
112
+ resolve(value || defaultValue)
113
+ return
114
+ }
115
+
116
+ if (char === '\x7f' || char === '\b') {
117
+ // Backspace
118
+ if (value.length > 0) {
119
+ value = value.slice(0, -1)
120
+ process.stdout.write('\b \b')
121
+ }
122
+ continue
123
+ }
124
+
125
+ // Printable characters
126
+ if (char >= ' ') {
127
+ value += char
128
+ process.stdout.write(char)
129
+ }
130
+ }
131
+ }
132
+
133
+ stdin.on('data', onData)
134
+ })
135
+ }
@@ -0,0 +1,54 @@
1
+ import { readdirSync, mkdirSync, statSync } from 'node:fs'
2
+ import { join, dirname } from 'node:path'
3
+ import pkg from '../package.json'
4
+
5
+ export interface ScaffoldOptions {
6
+ projectName: string
7
+ template: 'api' | 'web'
8
+ dbName: string
9
+ }
10
+
11
+ export async function scaffold(root: string, opts: ScaffoldOptions): Promise<void> {
12
+ const templatesDir = join(import.meta.dir, 'templates')
13
+ const appKey = crypto.randomUUID()
14
+
15
+ const replacements: Record<string, string> = {
16
+ __PROJECT_NAME__: opts.projectName,
17
+ __DB_NAME__: opts.dbName,
18
+ __APP_KEY__: appKey,
19
+ __CORE_VERSION__: `^${pkg.version}`,
20
+ }
21
+
22
+ // Copy shared files first, then template-specific (may override shared)
23
+ await copyDir(join(templatesDir, 'shared'), root, replacements)
24
+ await copyDir(join(templatesDir, opts.template), root, replacements)
25
+ }
26
+
27
+ async function copyDir(
28
+ srcDir: string,
29
+ destDir: string,
30
+ replacements: Record<string, string>
31
+ ): Promise<void> {
32
+ const entries = readdirSync(srcDir)
33
+
34
+ for (const entry of entries) {
35
+ const srcPath = join(srcDir, entry)
36
+ const destPath = join(destDir, entry.replace(/\.tpl$/, ''))
37
+
38
+ if (statSync(srcPath).isDirectory()) {
39
+ await copyDir(srcPath, destPath, replacements)
40
+ } else {
41
+ mkdirSync(dirname(destPath), { recursive: true })
42
+ const content = await Bun.file(srcPath).text()
43
+ await Bun.write(destPath, applyReplacements(content, replacements))
44
+ }
45
+ }
46
+ }
47
+
48
+ function applyReplacements(content: string, replacements: Record<string, string>): string {
49
+ let result = content
50
+ for (const [placeholder, value] of Object.entries(replacements)) {
51
+ result = result.replaceAll(placeholder, value)
52
+ }
53
+ return result
54
+ }
@@ -0,0 +1,7 @@
1
+ import { env } from '@stravigor/kernel/helpers/env'
2
+
3
+ export default {
4
+ host: env('HOST', '0.0.0.0'),
5
+ port: env.int('PORT', 3000),
6
+ domain: env('DOMAIN', 'localhost'),
7
+ }
@@ -0,0 +1,30 @@
1
+ import 'reflect-metadata'
2
+ import { app } from '@stravigor/kernel/core'
3
+ import { router } from '@stravigor/http/http'
4
+ import { ConfigProvider, EncryptionProvider } from '@stravigor/kernel/providers'
5
+ import { DatabaseProvider } from '@stravigor/database/providers'
6
+ import BaseModel from '@stravigor/database/orm/base_model'
7
+ import Database from '@stravigor/database/database/database'
8
+ import Server from '@stravigor/http/http/server'
9
+ import { ExceptionHandler } from '@stravigor/kernel/exceptions'
10
+
11
+ // Register service providers
12
+ app.use(new ConfigProvider()).use(new DatabaseProvider()).use(new EncryptionProvider())
13
+
14
+ // Boot services (loads config, connects database, derives encryption keys)
15
+ await app.start()
16
+
17
+ // Initialize ORM
18
+ new BaseModel(app.resolve(Database))
19
+
20
+ // Configure router
21
+ router.useExceptionHandler(new ExceptionHandler(true))
22
+ router.cors()
23
+
24
+ // Load routes
25
+ await import('./start/routes')
26
+
27
+ // Start HTTP server
28
+ app.singleton(Server)
29
+ const server = app.resolve(Server)
30
+ server.start(router)
@@ -0,0 +1,12 @@
1
+ import { router } from '@stravigor/http/http'
2
+
3
+ router.get('/', () => {
4
+ return Response.json({
5
+ name: '__PROJECT_NAME__',
6
+ status: 'running',
7
+ })
8
+ })
9
+
10
+ router.get('/health', () => {
11
+ return Response.json({ status: 'ok' })
12
+ })
@@ -0,0 +1,12 @@
1
+ import { test, expect } from 'bun:test'
2
+ import { TestCase } from '@stravigor/testing'
3
+
4
+ const t = await TestCase.boot({
5
+ routes: () => import('../start/routes'),
6
+ })
7
+
8
+ test('health check returns ok', async () => {
9
+ const res = await t.get('/health')
10
+ expect(res.status).toBe(200)
11
+ expect(await res.json()).toEqual({ status: 'ok' })
12
+ })
@@ -0,0 +1,13 @@
1
+ APP_ENV=local
2
+ APP_DEBUG=true
3
+ APP_KEY=__APP_KEY__
4
+
5
+ HOST=0.0.0.0
6
+ PORT=3000
7
+ DOMAIN=localhost
8
+
9
+ DB_HOST=127.0.0.1
10
+ DB_PORT=5432
11
+ DB_USERNAME=postgres
12
+ DB_PASSWORD=
13
+ DB_DATABASE=__DB_NAME__
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ .env
3
+ app/
4
+ database/migrations/
5
+ *.log
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ import { env } from '@stravigor/kernel/helpers/env'
2
+
3
+ export default {
4
+ env: env('APP_ENV', 'local'),
5
+ debug: env.bool('APP_DEBUG', true),
6
+ key: env('APP_KEY'),
7
+ }
@@ -0,0 +1,11 @@
1
+ import { env } from '@stravigor/kernel/helpers/env'
2
+
3
+ export default {
4
+ host: env('DB_HOST', '127.0.0.1'),
5
+ port: env.int('DB_PORT', 5432),
6
+ username: env('DB_USERNAME', 'postgres'),
7
+ password: env('DB_PASSWORD', ''),
8
+ database: env('DB_DATABASE', '__DB_NAME__'),
9
+ pool: env.int('DB_POOL_MAX', 10),
10
+ idleTimeout: env.int('DB_IDLE_TIMEOUT', 20),
11
+ }
@@ -0,0 +1,6 @@
1
+ import { env } from '@stravigor/kernel/helpers/env'
2
+
3
+ export default {
4
+ key: env('APP_KEY'),
5
+ previousKeys: [],
6
+ }
@@ -0,0 +1,10 @@
1
+ import { defineSchema, t, Archetype } from '@stravigor/database/schema'
2
+
3
+ export default defineSchema('user', {
4
+ archetype: Archetype.Entity,
5
+ fields: {
6
+ email: t.varchar(255).required().unique().index(),
7
+ name: t.varchar(255).required(),
8
+ password: t.varchar(255).required().sensitive(),
9
+ },
10
+ })
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "bun --hot index.ts",
8
+ "start": "bun index.ts",
9
+ "test": "bun test tests/"
10
+ },
11
+ "dependencies": {
12
+ "@stravigor/kernel": "__CORE_VERSION__",
13
+ "@stravigor/http": "__CORE_VERSION__",
14
+ "@stravigor/view": "__CORE_VERSION__",
15
+ "@stravigor/database": "__CORE_VERSION__",
16
+ "@stravigor/cli": "__CORE_VERSION__",
17
+ "luxon": "^3.7.2",
18
+ "reflect-metadata": "^0.2.2"
19
+ },
20
+ "devDependencies": {
21
+ "@types/bun": "latest",
22
+ "@types/luxon": "^3.7.1",
23
+ "@stravigor/testing": "__CORE_VERSION__"
24
+ }
25
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ import '@stravigor/cli/cli/strav'
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": [
4
+ "ESNext"
5
+ ],
6
+ "target": "ESNext",
7
+ "module": "ESNext",
8
+ "moduleDetection": "force",
9
+ "allowJs": true,
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "noEmit": true,
13
+ "experimentalDecorators": true,
14
+ "emitDecoratorMetadata": true,
15
+ "strict": true,
16
+ "skipLibCheck": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUnusedLocals": false,
19
+ "noUnusedParameters": false
20
+ },
21
+ "include": [
22
+ "**/*.ts"
23
+ ]
24
+ }
@@ -0,0 +1,8 @@
1
+ import { env } from '@stravigor/kernel/helpers/env'
2
+
3
+ export default {
4
+ host: env('HOST', '0.0.0.0'),
5
+ port: env.int('PORT', 3000),
6
+ domain: env('DOMAIN', 'localhost'),
7
+ public: './public',
8
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ cookie: 'strav_session',
3
+ lifetime: 120,
4
+ httpOnly: true,
5
+ secure: false,
6
+ sameSite: 'lax' as const,
7
+ }
@@ -0,0 +1,4 @@
1
+ export default {
2
+ directory: 'resources/views',
3
+ cache: false,
4
+ }
@@ -0,0 +1,36 @@
1
+ import 'reflect-metadata'
2
+ import { app } from '@stravigor/kernel/core'
3
+ import { router } from '@stravigor/http/http'
4
+ import { ConfigProvider, EncryptionProvider } from '@stravigor/kernel/providers'
5
+ import { DatabaseProvider } from '@stravigor/database/providers'
6
+ import { SessionProvider } from '@stravigor/http/providers'
7
+ import { ViewProvider } from '@stravigor/view'
8
+ import BaseModel from '@stravigor/database/orm/base_model'
9
+ import Database from '@stravigor/database/database/database'
10
+ import Server from '@stravigor/http/http/server'
11
+ import { ExceptionHandler } from '@stravigor/kernel/exceptions'
12
+
13
+ // Register service providers
14
+ app
15
+ .use(new ConfigProvider())
16
+ .use(new DatabaseProvider())
17
+ .use(new EncryptionProvider())
18
+ .use(new SessionProvider())
19
+ .use(new ViewProvider())
20
+
21
+ // Boot services (loads config, connects database, derives encryption keys, starts sessions)
22
+ await app.start()
23
+
24
+ // Initialize ORM
25
+ new BaseModel(app.resolve(Database))
26
+
27
+ // Configure router
28
+ router.useExceptionHandler(new ExceptionHandler(true))
29
+
30
+ // Load routes
31
+ await import('./start/routes')
32
+
33
+ // Start HTTP server
34
+ app.singleton(Server)
35
+ const server = app.resolve(Server)
36
+ server.start(router)
@@ -0,0 +1,53 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Barlow Semi Condensed', sans-serif;
9
+ background: #f8fafc;
10
+ color: #1e293b;
11
+ min-height: 100vh;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ .container {
18
+ text-align: center;
19
+ padding: 2rem;
20
+ }
21
+
22
+ h1 {
23
+ font-size: 2.5rem;
24
+ font-weight: 700;
25
+ margin-bottom: 1rem;
26
+ color: oklch(57.7% 0.245 27.325);
27
+ }
28
+
29
+ p {
30
+ font-size: 1.125rem;
31
+ color: #64748b;
32
+ margin-bottom: 2rem;
33
+ }
34
+
35
+ p strong {
36
+ color: #1e293b;
37
+ }
38
+
39
+ .links a {
40
+ color: #2563eb;
41
+ text-decoration: none;
42
+ font-size: 0.875rem;
43
+ font-weight: 600;
44
+ border: 1px solid #e2e8f0;
45
+ padding: 0.5rem 1rem;
46
+ border-radius: 0.375rem;
47
+ transition: all 0.2s;
48
+ }
49
+
50
+ .links a:hover {
51
+ background: #eff6ff;
52
+ border-color: #2563eb;
53
+ }
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vue'
2
+
3
+ export default function setup(app: App) {
4
+ // Install plugins, provide globals, or register components here.
5
+ // This runs once before any island is mounted.
6
+ //
7
+ // Examples:
8
+ // app.use(pinia)
9
+ // app.provide('api', apiClient)
10
+ // app.component('Icon', Icon)
11
+ }
File without changes
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ name }} — Strav</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Barlow+Semi+Condensed:wght@400;600;700&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="/styles.css">
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <h1>Welcome to Strav</h1>
15
+ <p>Your application <strong>{{ name }}</strong> is up and running.</p>
16
+ <div class="links">
17
+ <a href="https://github.com/nicoyambura/stravigor" target="_blank">Documentation</a>
18
+ </div>
19
+ </div>
20
+ </body>
21
+ </html>
@@ -0,0 +1,10 @@
1
+ import { router } from '@stravigor/http/http'
2
+ import { view } from '@stravigor/view'
3
+
4
+ router.get('/', async () => {
5
+ return view('welcome', { name: '__PROJECT_NAME__' })
6
+ })
7
+
8
+ router.get('/api/health', () => {
9
+ return Response.json({ status: 'ok' })
10
+ })
@@ -0,0 +1,12 @@
1
+ import { test, expect } from 'bun:test'
2
+ import { TestCase } from '@stravigor/testing'
3
+
4
+ const t = await TestCase.boot({
5
+ routes: () => import('../start/routes'),
6
+ })
7
+
8
+ test('health check returns ok', async () => {
9
+ const res = await t.get('/api/health')
10
+ expect(res.status).toBe(200)
11
+ expect(await res.json()).toEqual({ status: 'ok' })
12
+ })