@stravigor/create 0.4.9 → 0.5.1
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 +1 -1
- package/src/index.ts +4 -4
- package/src/scaffold.ts +49 -15
- package/src/templates/api/config/http.ts +7 -0
- package/src/templates/api/index.ts +29 -0
- package/src/templates/api/start/routes.ts +12 -0
- package/src/templates/api/tests/health.test.ts.tpl +12 -0
- package/src/templates/shared/.env.tpl +13 -0
- package/src/templates/shared/.gitignore.tpl +5 -0
- package/src/templates/shared/app/controllers/.gitkeep +0 -0
- package/src/templates/shared/app/models/.gitkeep +0 -0
- package/src/templates/shared/config/app.ts +7 -0
- package/src/templates/shared/config/database.ts +9 -0
- package/src/templates/shared/config/encryption.ts +6 -0
- package/src/templates/shared/database/migrations/.gitkeep +0 -0
- package/src/templates/shared/database/schemas/user.ts +10 -0
- package/src/templates/shared/package.json +21 -0
- package/src/templates/shared/strav.ts +2 -0
- package/src/templates/shared/tsconfig.json +24 -0
- package/src/templates/web/config/http.ts +8 -0
- package/src/templates/web/config/session.ts +7 -0
- package/src/templates/web/config/view.ts +4 -0
- package/src/templates/web/index.ts +39 -0
- package/src/templates/web/public/styles.css +53 -0
- package/src/templates/web/start/routes.ts +10 -0
- package/src/templates/web/tests/health.test.ts.tpl +12 -0
- package/src/templates/web/views/welcome.strav +21 -0
- package/src/templates/api.ts +0 -74
- package/src/templates/shared.ts +0 -164
- package/src/templates/web.ts +0 -179
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
import { select, input } from './prompts.ts'
|
|
5
|
-
import { scaffold } from './scaffold.ts'
|
|
6
|
-
import
|
|
5
|
+
import { scaffold, type ScaffoldOptions } from './scaffold.ts'
|
|
6
|
+
import pkg from '../package.json'
|
|
7
7
|
|
|
8
|
-
const VERSION =
|
|
8
|
+
const VERSION = pkg.version
|
|
9
9
|
|
|
10
10
|
// ── Colors ──────────────────────────────────────────────────────────
|
|
11
11
|
|
|
@@ -47,7 +47,7 @@ function parseArgs(): ParsedArgs {
|
|
|
47
47
|
}
|
|
48
48
|
} else if (arg === '--db') {
|
|
49
49
|
result.db = args[++i]
|
|
50
|
-
} else if (!arg.startsWith('-') && !result.projectName) {
|
|
50
|
+
} else if (arg && !arg.startsWith('-') && !result.projectName) {
|
|
51
51
|
result.projectName = arg
|
|
52
52
|
}
|
|
53
53
|
}
|
package/src/scaffold.ts
CHANGED
|
@@ -1,20 +1,54 @@
|
|
|
1
|
-
import { mkdirSync } from 'node:fs'
|
|
1
|
+
import { readdirSync, mkdirSync, statSync } from 'node:fs'
|
|
2
2
|
import { join, dirname } from 'node:path'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import pkg from '../package.json'
|
|
4
|
+
|
|
5
|
+
export interface ScaffoldOptions {
|
|
6
|
+
projectName: string
|
|
7
|
+
template: 'api' | 'web'
|
|
8
|
+
dbName: string
|
|
9
|
+
}
|
|
6
10
|
|
|
7
11
|
export async function scaffold(root: string, opts: ScaffoldOptions): Promise<void> {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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)
|
|
19
52
|
}
|
|
53
|
+
return result
|
|
20
54
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import 'reflect-metadata'
|
|
2
|
+
import { app } from '@stravigor/core/core'
|
|
3
|
+
import { router } from '@stravigor/core/http'
|
|
4
|
+
import { ConfigProvider, DatabaseProvider, EncryptionProvider } from '@stravigor/core/providers'
|
|
5
|
+
import BaseModel from '@stravigor/core/orm/base_model'
|
|
6
|
+
import Database from '@stravigor/core/database/database'
|
|
7
|
+
import Server from '@stravigor/core/http/server'
|
|
8
|
+
import { ExceptionHandler } from '@stravigor/core/exceptions'
|
|
9
|
+
|
|
10
|
+
// Register service providers
|
|
11
|
+
app.use(new ConfigProvider()).use(new DatabaseProvider()).use(new EncryptionProvider())
|
|
12
|
+
|
|
13
|
+
// Boot services (loads config, connects database, derives encryption keys)
|
|
14
|
+
await app.start()
|
|
15
|
+
|
|
16
|
+
// Initialize ORM
|
|
17
|
+
new BaseModel(app.resolve(Database))
|
|
18
|
+
|
|
19
|
+
// Configure router
|
|
20
|
+
router.useExceptionHandler(new ExceptionHandler(true))
|
|
21
|
+
router.cors()
|
|
22
|
+
|
|
23
|
+
// Load routes
|
|
24
|
+
await import('./start/routes')
|
|
25
|
+
|
|
26
|
+
// Start HTTP server
|
|
27
|
+
app.singleton(Server)
|
|
28
|
+
const server = app.resolve(Server)
|
|
29
|
+
server.start(router)
|
|
@@ -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
|
+
})
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { env } from '@stravigor/core/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
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineSchema, t, Archetype } from '@stravigor/core/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,21 @@
|
|
|
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/core": "__CORE_VERSION__",
|
|
13
|
+
"luxon": "^3.7.2",
|
|
14
|
+
"reflect-metadata": "^0.2.2"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/bun": "latest",
|
|
18
|
+
"@types/luxon": "^3.7.1",
|
|
19
|
+
"@stravigor/testing": "__CORE_VERSION__"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -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,39 @@
|
|
|
1
|
+
import 'reflect-metadata'
|
|
2
|
+
import { app } from '@stravigor/core/core'
|
|
3
|
+
import { router } from '@stravigor/core/http'
|
|
4
|
+
import {
|
|
5
|
+
ConfigProvider,
|
|
6
|
+
DatabaseProvider,
|
|
7
|
+
EncryptionProvider,
|
|
8
|
+
SessionProvider,
|
|
9
|
+
ViewProvider,
|
|
10
|
+
} from '@stravigor/core/providers'
|
|
11
|
+
import BaseModel from '@stravigor/core/orm/base_model'
|
|
12
|
+
import Database from '@stravigor/core/database/database'
|
|
13
|
+
import Server from '@stravigor/core/http/server'
|
|
14
|
+
import { ExceptionHandler } from '@stravigor/core/exceptions'
|
|
15
|
+
|
|
16
|
+
// Register service providers
|
|
17
|
+
app
|
|
18
|
+
.use(new ConfigProvider())
|
|
19
|
+
.use(new DatabaseProvider())
|
|
20
|
+
.use(new EncryptionProvider())
|
|
21
|
+
.use(new SessionProvider())
|
|
22
|
+
.use(new ViewProvider())
|
|
23
|
+
|
|
24
|
+
// Boot services (loads config, connects database, derives encryption keys, starts sessions)
|
|
25
|
+
await app.start()
|
|
26
|
+
|
|
27
|
+
// Initialize ORM
|
|
28
|
+
new BaseModel(app.resolve(Database))
|
|
29
|
+
|
|
30
|
+
// Configure router
|
|
31
|
+
router.useExceptionHandler(new ExceptionHandler(true))
|
|
32
|
+
|
|
33
|
+
// Load routes
|
|
34
|
+
await import('./start/routes')
|
|
35
|
+
|
|
36
|
+
// Start HTTP server
|
|
37
|
+
app.singleton(Server)
|
|
38
|
+
const server = app.resolve(Server)
|
|
39
|
+
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,10 @@
|
|
|
1
|
+
import { router } from '@stravigor/core/http'
|
|
2
|
+
import { view } from '@stravigor/core/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
|
+
})
|
|
@@ -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>
|
package/src/templates/api.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { TemplateFile, ScaffoldOptions } from './shared.ts'
|
|
2
|
-
|
|
3
|
-
export function getApiFiles(opts: ScaffoldOptions): TemplateFile[] {
|
|
4
|
-
return [
|
|
5
|
-
{ path: 'index.ts', content: indexTs(opts) },
|
|
6
|
-
{ path: 'config/http.ts', content: configHttp() },
|
|
7
|
-
{ path: 'start/routes.ts', content: routes(opts) },
|
|
8
|
-
]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function indexTs(opts: ScaffoldOptions): string {
|
|
12
|
-
return `import 'reflect-metadata'
|
|
13
|
-
import { app } from '@stravigor/core/core'
|
|
14
|
-
import { router } from '@stravigor/core/http'
|
|
15
|
-
import {
|
|
16
|
-
ConfigProvider, DatabaseProvider, EncryptionProvider,
|
|
17
|
-
} from '@stravigor/core/providers'
|
|
18
|
-
import BaseModel from '@stravigor/core/orm/base_model'
|
|
19
|
-
import Database from '@stravigor/core/database/database'
|
|
20
|
-
import Server from '@stravigor/core/http/server'
|
|
21
|
-
import { ExceptionHandler } from '@stravigor/core/exceptions'
|
|
22
|
-
|
|
23
|
-
// Register service providers
|
|
24
|
-
app
|
|
25
|
-
.use(new ConfigProvider())
|
|
26
|
-
.use(new DatabaseProvider())
|
|
27
|
-
.use(new EncryptionProvider())
|
|
28
|
-
|
|
29
|
-
// Boot services (loads config, connects database, derives encryption keys)
|
|
30
|
-
await app.start()
|
|
31
|
-
|
|
32
|
-
// Initialize ORM
|
|
33
|
-
new BaseModel(app.resolve(Database))
|
|
34
|
-
|
|
35
|
-
// Configure router
|
|
36
|
-
router.useExceptionHandler(new ExceptionHandler(true))
|
|
37
|
-
router.cors()
|
|
38
|
-
|
|
39
|
-
// Load routes
|
|
40
|
-
await import('./start/routes')
|
|
41
|
-
|
|
42
|
-
// Start HTTP server
|
|
43
|
-
app.singleton(Server)
|
|
44
|
-
const server = app.resolve(Server)
|
|
45
|
-
server.start(router)
|
|
46
|
-
`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function configHttp(): string {
|
|
50
|
-
return `import { env } from '@stravigor/core/helpers/env'
|
|
51
|
-
|
|
52
|
-
export default {
|
|
53
|
-
host: env('HOST', '0.0.0.0'),
|
|
54
|
-
port: env.int('PORT', 3000),
|
|
55
|
-
domain: env('DOMAIN', 'localhost'),
|
|
56
|
-
}
|
|
57
|
-
`
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function routes(opts: ScaffoldOptions): string {
|
|
61
|
-
return `import { router } from '@stravigor/core/http'
|
|
62
|
-
|
|
63
|
-
router.get('/', () => {
|
|
64
|
-
return Response.json({
|
|
65
|
-
name: '${opts.projectName}',
|
|
66
|
-
status: 'running',
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
router.get('/health', () => {
|
|
71
|
-
return Response.json({ status: 'ok' })
|
|
72
|
-
})
|
|
73
|
-
`
|
|
74
|
-
}
|
package/src/templates/shared.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
export interface TemplateFile {
|
|
2
|
-
path: string
|
|
3
|
-
content: string
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface ScaffoldOptions {
|
|
7
|
-
projectName: string
|
|
8
|
-
template: 'api' | 'web'
|
|
9
|
-
dbName: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getSharedFiles(opts: ScaffoldOptions): TemplateFile[] {
|
|
13
|
-
const appKey = crypto.randomUUID()
|
|
14
|
-
|
|
15
|
-
return [
|
|
16
|
-
{ path: 'package.json', content: packageJson(opts) },
|
|
17
|
-
{ path: 'tsconfig.json', content: tsconfig() },
|
|
18
|
-
{ path: '.env', content: dotEnv(opts, appKey) },
|
|
19
|
-
{ path: '.gitignore', content: gitignore() },
|
|
20
|
-
{ path: 'strav.ts', content: stravTs() },
|
|
21
|
-
{ path: 'config/app.ts', content: configApp() },
|
|
22
|
-
{ path: 'config/database.ts', content: configDatabase(opts) },
|
|
23
|
-
{ path: 'config/encryption.ts', content: configEncryption() },
|
|
24
|
-
{ path: 'database/schemas/.gitkeep', content: '' },
|
|
25
|
-
{ path: 'tests/health.test.ts', content: healthTest(opts) },
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function packageJson(opts: ScaffoldOptions): string {
|
|
30
|
-
return (
|
|
31
|
-
JSON.stringify(
|
|
32
|
-
{
|
|
33
|
-
name: opts.projectName,
|
|
34
|
-
version: '0.0.1',
|
|
35
|
-
type: 'module',
|
|
36
|
-
private: true,
|
|
37
|
-
scripts: {
|
|
38
|
-
dev: 'bun --hot index.ts',
|
|
39
|
-
start: 'bun index.ts',
|
|
40
|
-
test: 'bun test tests/',
|
|
41
|
-
},
|
|
42
|
-
dependencies: {
|
|
43
|
-
'@stravigor/core': '^0.4.0',
|
|
44
|
-
luxon: '^3.7.2',
|
|
45
|
-
'reflect-metadata': '^0.2.2',
|
|
46
|
-
},
|
|
47
|
-
devDependencies: {
|
|
48
|
-
'@types/bun': 'latest',
|
|
49
|
-
'@types/luxon': '^3.7.1',
|
|
50
|
-
'@stravigor/testing': '^0.4.0',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
null,
|
|
54
|
-
2
|
|
55
|
-
) + '\n'
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function tsconfig(): string {
|
|
60
|
-
return (
|
|
61
|
-
JSON.stringify(
|
|
62
|
-
{
|
|
63
|
-
compilerOptions: {
|
|
64
|
-
lib: ['ESNext'],
|
|
65
|
-
target: 'ESNext',
|
|
66
|
-
module: 'ESNext',
|
|
67
|
-
moduleDetection: 'force',
|
|
68
|
-
allowJs: true,
|
|
69
|
-
moduleResolution: 'bundler',
|
|
70
|
-
allowImportingTsExtensions: true,
|
|
71
|
-
noEmit: true,
|
|
72
|
-
experimentalDecorators: true,
|
|
73
|
-
emitDecoratorMetadata: true,
|
|
74
|
-
strict: true,
|
|
75
|
-
skipLibCheck: true,
|
|
76
|
-
noFallthroughCasesInSwitch: true,
|
|
77
|
-
noUnusedLocals: false,
|
|
78
|
-
noUnusedParameters: false,
|
|
79
|
-
},
|
|
80
|
-
include: ['**/*.ts'],
|
|
81
|
-
},
|
|
82
|
-
null,
|
|
83
|
-
2
|
|
84
|
-
) + '\n'
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function dotEnv(opts: ScaffoldOptions, appKey: string): string {
|
|
89
|
-
return `APP_ENV=local
|
|
90
|
-
APP_DEBUG=true
|
|
91
|
-
APP_KEY=${appKey}
|
|
92
|
-
|
|
93
|
-
HOST=0.0.0.0
|
|
94
|
-
PORT=3000
|
|
95
|
-
DOMAIN=localhost
|
|
96
|
-
|
|
97
|
-
DB_HOST=127.0.0.1
|
|
98
|
-
DB_PORT=5432
|
|
99
|
-
DB_USERNAME=postgres
|
|
100
|
-
DB_PASSWORD=
|
|
101
|
-
DB_DATABASE=${opts.dbName}
|
|
102
|
-
`
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function gitignore(): string {
|
|
106
|
-
return `node_modules/
|
|
107
|
-
.env
|
|
108
|
-
app/
|
|
109
|
-
database/migrations/
|
|
110
|
-
*.log
|
|
111
|
-
`
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function stravTs(): string {
|
|
115
|
-
return `#!/usr/bin/env bun
|
|
116
|
-
import '@stravigor/core/cli/strav'
|
|
117
|
-
`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function configApp(): string {
|
|
121
|
-
return `import { env } from '@stravigor/core/helpers/env'
|
|
122
|
-
|
|
123
|
-
export default {
|
|
124
|
-
env: env('APP_ENV', 'local'),
|
|
125
|
-
debug: env.bool('APP_DEBUG', true),
|
|
126
|
-
key: env('APP_KEY'),
|
|
127
|
-
}
|
|
128
|
-
`
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function configDatabase(opts: ScaffoldOptions): string {
|
|
132
|
-
return `import { env } from '@stravigor/core/helpers/env'
|
|
133
|
-
|
|
134
|
-
export default {
|
|
135
|
-
host: env('DB_HOST', '127.0.0.1'),
|
|
136
|
-
port: env.int('DB_PORT', 5432),
|
|
137
|
-
username: env('DB_USERNAME', 'postgres'),
|
|
138
|
-
password: env('DB_PASSWORD', ''),
|
|
139
|
-
database: env('DB_DATABASE', '${opts.dbName}'),
|
|
140
|
-
}
|
|
141
|
-
`
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function configEncryption(): string {
|
|
145
|
-
return `import { env } from '@stravigor/core/helpers/env'
|
|
146
|
-
|
|
147
|
-
export default {
|
|
148
|
-
key: env('APP_KEY'),
|
|
149
|
-
previousKeys: [],
|
|
150
|
-
}
|
|
151
|
-
`
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function healthTest(opts: ScaffoldOptions): string {
|
|
155
|
-
const path = opts.template === 'web' ? '/api/health' : '/health'
|
|
156
|
-
return `import { test, expect } from 'bun:test'
|
|
157
|
-
|
|
158
|
-
test('health check returns ok', async () => {
|
|
159
|
-
const res = await fetch('http://localhost:3000${path}')
|
|
160
|
-
const json = await res.json()
|
|
161
|
-
expect(json.status).toBe('ok')
|
|
162
|
-
})
|
|
163
|
-
`
|
|
164
|
-
}
|
package/src/templates/web.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import type { TemplateFile, ScaffoldOptions } from './shared.ts'
|
|
2
|
-
|
|
3
|
-
export function getWebFiles(opts: ScaffoldOptions): TemplateFile[] {
|
|
4
|
-
return [
|
|
5
|
-
{ path: 'index.ts', content: indexTs(opts) },
|
|
6
|
-
{ path: 'config/http.ts', content: configHttp() },
|
|
7
|
-
{ path: 'config/session.ts', content: configSession() },
|
|
8
|
-
{ path: 'config/view.ts', content: configView() },
|
|
9
|
-
{ path: 'start/routes.ts', content: routes(opts) },
|
|
10
|
-
{ path: 'views/welcome.strav', content: welcomeView() },
|
|
11
|
-
{ path: 'public/styles.css', content: stylesCss() },
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function indexTs(opts: ScaffoldOptions): string {
|
|
16
|
-
return `import 'reflect-metadata'
|
|
17
|
-
import { app } from '@stravigor/core/core'
|
|
18
|
-
import { router } from '@stravigor/core/http'
|
|
19
|
-
import {
|
|
20
|
-
ConfigProvider, DatabaseProvider, EncryptionProvider, SessionProvider, ViewProvider,
|
|
21
|
-
} from '@stravigor/core/providers'
|
|
22
|
-
import BaseModel from '@stravigor/core/orm/base_model'
|
|
23
|
-
import Database from '@stravigor/core/database/database'
|
|
24
|
-
import Server from '@stravigor/core/http/server'
|
|
25
|
-
import { ExceptionHandler } from '@stravigor/core/exceptions'
|
|
26
|
-
|
|
27
|
-
// Register service providers
|
|
28
|
-
app
|
|
29
|
-
.use(new ConfigProvider())
|
|
30
|
-
.use(new DatabaseProvider())
|
|
31
|
-
.use(new EncryptionProvider())
|
|
32
|
-
.use(new SessionProvider())
|
|
33
|
-
.use(new ViewProvider())
|
|
34
|
-
|
|
35
|
-
// Boot services (loads config, connects database, derives encryption keys, starts sessions)
|
|
36
|
-
await app.start()
|
|
37
|
-
|
|
38
|
-
// Initialize ORM
|
|
39
|
-
new BaseModel(app.resolve(Database))
|
|
40
|
-
|
|
41
|
-
// Configure router
|
|
42
|
-
router.useExceptionHandler(new ExceptionHandler(true))
|
|
43
|
-
|
|
44
|
-
// Load routes
|
|
45
|
-
await import('./start/routes')
|
|
46
|
-
|
|
47
|
-
// Start HTTP server
|
|
48
|
-
app.singleton(Server)
|
|
49
|
-
const server = app.resolve(Server)
|
|
50
|
-
server.start(router)
|
|
51
|
-
`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function configHttp(): string {
|
|
55
|
-
return `import { env } from '@stravigor/core/helpers/env'
|
|
56
|
-
|
|
57
|
-
export default {
|
|
58
|
-
host: env('HOST', '0.0.0.0'),
|
|
59
|
-
port: env.int('PORT', 3000),
|
|
60
|
-
domain: env('DOMAIN', 'localhost'),
|
|
61
|
-
public: './public',
|
|
62
|
-
}
|
|
63
|
-
`
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function configSession(): string {
|
|
67
|
-
return `export default {
|
|
68
|
-
cookie: 'strav_session',
|
|
69
|
-
lifetime: 120,
|
|
70
|
-
httpOnly: true,
|
|
71
|
-
secure: false,
|
|
72
|
-
sameSite: 'lax' as const,
|
|
73
|
-
}
|
|
74
|
-
`
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function configView(): string {
|
|
78
|
-
return `export default {
|
|
79
|
-
directory: 'views',
|
|
80
|
-
cache: false,
|
|
81
|
-
}
|
|
82
|
-
`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function routes(opts: ScaffoldOptions): string {
|
|
86
|
-
return `import { router } from '@stravigor/core/http'
|
|
87
|
-
import { view } from '@stravigor/core/view'
|
|
88
|
-
|
|
89
|
-
router.get('/', async () => {
|
|
90
|
-
return view('welcome', { name: '${opts.projectName}' })
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
router.get('/api/health', () => {
|
|
94
|
-
return Response.json({ status: 'ok' })
|
|
95
|
-
})
|
|
96
|
-
`
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function welcomeView(): string {
|
|
100
|
-
return `<!DOCTYPE html>
|
|
101
|
-
<html lang="en">
|
|
102
|
-
<head>
|
|
103
|
-
<meta charset="UTF-8">
|
|
104
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
105
|
-
<title>{{ name }} — Strav</title>
|
|
106
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
107
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
108
|
-
<link href="https://fonts.googleapis.com/css2?family=Barlow+Semi+Condensed:wght@400;600;700&display=swap" rel="stylesheet">
|
|
109
|
-
<link rel="stylesheet" href="/styles.css">
|
|
110
|
-
</head>
|
|
111
|
-
<body>
|
|
112
|
-
<div class="container">
|
|
113
|
-
<h1>Welcome to Strav</h1>
|
|
114
|
-
<p>Your application <strong>{{ name }}</strong> is up and running.</p>
|
|
115
|
-
<div class="links">
|
|
116
|
-
<a href="https://github.com/nicoyambura/stravigor" target="_blank">Documentation</a>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</body>
|
|
120
|
-
</html>
|
|
121
|
-
`
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function stylesCss(): string {
|
|
125
|
-
return `* {
|
|
126
|
-
margin: 0;
|
|
127
|
-
padding: 0;
|
|
128
|
-
box-sizing: border-box;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
body {
|
|
132
|
-
font-family: 'Barlow Semi Condensed', sans-serif;
|
|
133
|
-
background: #f8fafc;
|
|
134
|
-
color: #1e293b;
|
|
135
|
-
min-height: 100vh;
|
|
136
|
-
display: flex;
|
|
137
|
-
align-items: center;
|
|
138
|
-
justify-content: center;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.container {
|
|
142
|
-
text-align: center;
|
|
143
|
-
padding: 2rem;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
h1 {
|
|
147
|
-
font-size: 2.5rem;
|
|
148
|
-
font-weight: 700;
|
|
149
|
-
margin-bottom: 1rem;
|
|
150
|
-
color: oklch(57.7% 0.245 27.325);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
p {
|
|
154
|
-
font-size: 1.125rem;
|
|
155
|
-
color: #64748b;
|
|
156
|
-
margin-bottom: 2rem;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
p strong {
|
|
160
|
-
color: #1e293b;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.links a {
|
|
164
|
-
color: #2563eb;
|
|
165
|
-
text-decoration: none;
|
|
166
|
-
font-size: 0.875rem;
|
|
167
|
-
font-weight: 600;
|
|
168
|
-
border: 1px solid #e2e8f0;
|
|
169
|
-
padding: 0.5rem 1rem;
|
|
170
|
-
border-radius: 0.375rem;
|
|
171
|
-
transition: all 0.2s;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.links a:hover {
|
|
175
|
-
background: #eff6ff;
|
|
176
|
-
border-color: #2563eb;
|
|
177
|
-
}
|
|
178
|
-
`
|
|
179
|
-
}
|