@strav/spring 0.4.30 → 1.0.0-alpha.28
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/README.md +11 -52
- package/package.json +19 -20
- package/src/args.ts +119 -0
- package/src/cli.ts +134 -0
- package/src/index.ts +10 -176
- package/src/prompts.ts +49 -127
- package/src/scaffold.ts +115 -36
- package/src/spring_error.ts +11 -0
- package/src/templates/shared/README.md.tt +37 -0
- package/src/templates/shared/_dot_env.example.tt +12 -0
- package/src/templates/shared/_dot_env.tt +12 -0
- package/src/templates/shared/_dot_gitignore +12 -0
- package/src/templates/shared/app/console/_dot_gitkeep +0 -0
- package/src/templates/shared/app/exceptions/_dot_gitkeep +0 -0
- package/src/templates/shared/app/http/controllers/_dot_gitkeep +0 -0
- package/src/templates/shared/app/http/middleware/_dot_gitkeep +0 -0
- package/src/templates/shared/app/http/requests/_dot_gitkeep +0 -0
- package/src/templates/shared/app/jobs/_dot_gitkeep +0 -0
- package/src/templates/shared/app/mail/_dot_gitkeep +0 -0
- package/src/templates/shared/app/models/_dot_gitkeep +0 -0
- package/src/templates/shared/app/notifications/_dot_gitkeep +0 -0
- package/src/templates/shared/app/policies/_dot_gitkeep +0 -0
- package/src/templates/shared/app/providers/app_provider.ts +18 -0
- package/src/templates/shared/app/repositories/_dot_gitkeep +0 -0
- package/src/templates/shared/bin/strav.ts +21 -0
- package/src/templates/shared/bootstrap/app.ts +13 -0
- package/src/templates/shared/bootstrap/providers.ts +29 -0
- package/src/templates/shared/config/app.ts.tt +13 -0
- package/src/templates/shared/config/http.ts +9 -0
- package/src/templates/shared/config/logger.ts +9 -0
- package/src/templates/shared/database/factories/_dot_gitkeep +0 -0
- package/src/templates/shared/database/migrations/_dot_gitkeep +0 -0
- package/src/templates/shared/database/schemas/_dot_gitkeep +0 -0
- package/src/templates/shared/database/seeders/_dot_gitkeep +0 -0
- package/src/templates/shared/package.json.tt +22 -0
- package/src/templates/shared/routes/api.ts +11 -0
- package/src/templates/shared/routes/console.ts +10 -0
- package/src/templates/shared/storage/cache/_dot_gitkeep +0 -0
- package/src/templates/shared/storage/logs/_dot_gitkeep +0 -0
- package/src/templates/shared/storage/uploads/_dot_gitkeep +0 -0
- package/src/templates/shared/tests/feature/healthz.test.ts.tt +19 -0
- package/src/templates/shared/tests/unit/_dot_gitkeep +0 -0
- package/src/templates/shared/tsconfig.json +21 -11
- package/src/templates/web/README.md.tt +42 -0
- package/src/templates/web/_dot_gitignore +13 -0
- package/src/templates/web/app/providers/app_provider.ts +21 -0
- package/src/templates/web/bootstrap/providers.ts +34 -0
- package/src/templates/web/config/http.ts +17 -8
- package/src/templates/web/config/view.ts +7 -7
- package/src/templates/web/package.json.tt +26 -0
- package/src/templates/web/public/assets/_dot_gitkeep +0 -0
- package/src/templates/web/resources/css/app.css +41 -0
- package/src/templates/web/resources/ts/islands/counter.vue +11 -0
- package/src/templates/web/resources/ts/islands/setup.ts +13 -0
- package/src/templates/web/resources/views/components/_dot_gitkeep +0 -0
- package/src/templates/web/resources/views/errors/404.strav +8 -0
- package/src/templates/web/resources/views/errors/500.strav +8 -0
- package/src/templates/web/resources/views/layouts/app.strav.tt +15 -0
- package/src/templates/web/resources/views/pages/index.strav.tt +12 -0
- package/src/templates/web/routes/broadcast.ts +9 -0
- package/src/templates/web/routes/web.ts +19 -0
- package/src/templates/web/tests/browser/_dot_gitkeep +0 -0
- package/src/version.ts +9 -0
- package/src/templates/api/app/controllers/controller.ts +0 -15
- package/src/templates/api/app/controllers/user_controller.ts +0 -69
- package/src/templates/api/config/database.ts +0 -9
- package/src/templates/api/config/http.ts +0 -17
- package/src/templates/api/database/factories/user_factory.ts +0 -11
- package/src/templates/api/database/schemas/user.ts +0 -13
- package/src/templates/api/database/seeders/database_seeder.ts +0 -8
- package/src/templates/api/database/seeders/user_seeder.ts +0 -15
- package/src/templates/api/index.ts +0 -11
- package/src/templates/api/package.json +0 -24
- package/src/templates/api/start/providers.ts +0 -10
- package/src/templates/api/start/routes.ts +0 -22
- package/src/templates/shared/config/app.ts +0 -7
- package/src/templates/shared/config/encryption.ts +0 -5
- package/src/templates/shared/package.json +0 -24
- package/src/templates/shared/storage/uploads/.gitkeep +0 -1
- package/src/templates/shared/strav.ts +0 -2
- package/src/templates/shared/tests/example.test.ts +0 -11
- package/src/templates/web/index.ts +0 -28
- package/src/templates/web/package.json +0 -26
- package/src/templates/web/public/builds/.gitkeep +0 -1
- package/src/templates/web/public/css/.gitkeep +0 -1
- package/src/templates/web/resources/css/app.scss +0 -77
- package/src/templates/web/resources/islands/counter.vue +0 -31
- package/src/templates/web/resources/views/layouts/app.strav +0 -18
- package/src/templates/web/resources/views/pages/index.strav +0 -32
- package/src/templates/web/start/providers.ts +0 -11
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { HttpKernel } from '@strav/http'
|
|
3
|
+
import { createApp } from '../../bootstrap/app.ts'
|
|
4
|
+
import { providers } from '../../bootstrap/providers.ts'
|
|
5
|
+
|
|
6
|
+
describe('GET /healthz', () => {
|
|
7
|
+
test('returns 200 { ok: true }', async () => {
|
|
8
|
+
const app = createApp()
|
|
9
|
+
app.useProviders(providers())
|
|
10
|
+
await app.start()
|
|
11
|
+
|
|
12
|
+
const kernel = app.resolve(HttpKernel)
|
|
13
|
+
const res = await kernel.handle(new Request('http://localhost/healthz'))
|
|
14
|
+
expect(res.status).toBe(200)
|
|
15
|
+
expect(await res.json()).toEqual({ ok: true })
|
|
16
|
+
|
|
17
|
+
await app.shutdown()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
File without changes
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"
|
|
4
|
-
"target": "ES2022",
|
|
4
|
+
"target": "ESNext",
|
|
5
5
|
"module": "ESNext",
|
|
6
6
|
"moduleResolution": "bundler",
|
|
7
|
+
"lib": ["ESNext"],
|
|
8
|
+
"types": ["bun"],
|
|
9
|
+
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noUncheckedIndexedAccess": true,
|
|
12
|
+
"noImplicitOverride": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"emitDecoratorMetadata": true,
|
|
17
|
+
|
|
7
18
|
"allowImportingTsExtensions": true,
|
|
8
|
-
"
|
|
9
|
-
"
|
|
19
|
+
"verbatimModuleSyntax": true,
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"resolveJsonModule": true,
|
|
10
23
|
"skipLibCheck": true,
|
|
11
|
-
"allowSyntheticDefaultImports": true,
|
|
12
24
|
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"useDefineForClassFields": false,
|
|
16
|
-
"types": ["bun-types"]
|
|
25
|
+
|
|
26
|
+
"noEmit": true
|
|
17
27
|
},
|
|
18
28
|
"include": ["**/*.ts"],
|
|
19
|
-
"exclude": ["node_modules", "
|
|
20
|
-
}
|
|
29
|
+
"exclude": ["node_modules", "storage"]
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A Strav 1.0 full-stack application scaffolded by `@strav/spring --web`.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun strav view:build # compile resources/ts/islands/ into public/assets/islands/
|
|
10
|
+
bun strav serve # HTTP server on http://localhost:3000
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Open `http://localhost:3000/` — the page at `resources/views/pages/index.strav`
|
|
14
|
+
renders, with the Vue 3 island defined in `resources/ts/islands/counter.vue`
|
|
15
|
+
hydrating on load.
|
|
16
|
+
|
|
17
|
+
## Layout
|
|
18
|
+
|
|
19
|
+
This project follows the layout in `spec/directory-structure.md`:
|
|
20
|
+
|
|
21
|
+
- `app/` — application code (`http/`, `models/`, `providers/`, …).
|
|
22
|
+
- `bin/strav.ts` — single dispatcher; `bun strav <command>` invokes it.
|
|
23
|
+
- `bootstrap/{app,providers}.ts` — Application factory + provider list.
|
|
24
|
+
- `config/` — typed config; one file per package, each exports `default`.
|
|
25
|
+
- `routes/{api,web,console}.ts` — declared route entry points.
|
|
26
|
+
- `resources/`
|
|
27
|
+
- `views/pages/**/*.strav` — auto-routed pages via `@strav/view`.
|
|
28
|
+
- `views/layouts/`, `views/components/`, `views/errors/` — the rest of the templates.
|
|
29
|
+
- `ts/islands/*.vue` + `setup.ts` — Vue 3 islands. Bundled into one
|
|
30
|
+
`islands.js` by `bun strav view:build`.
|
|
31
|
+
- `css/app.css` — base stylesheet (plain CSS; swap in Tailwind /
|
|
32
|
+
vanilla-extract / your choice).
|
|
33
|
+
- `public/` — static assets served by `routes/web.ts`. `public/assets/`
|
|
34
|
+
receives the islands bundle output.
|
|
35
|
+
- `tests/feature/` — HTTP-level integration tests.
|
|
36
|
+
- `tests/browser/` — Playwright-driven (you install Playwright when you need it).
|
|
37
|
+
|
|
38
|
+
## Adding more packages
|
|
39
|
+
|
|
40
|
+
The scaffolded app starts with `@strav/{kernel,http,view,cli}`. Add
|
|
41
|
+
`@strav/database`, `@strav/auth`, `@strav/queue`, `@strav/broadcast` (and
|
|
42
|
+
their config + provider entries in `bootstrap/providers.ts`) as you need them.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Router } from '@strav/http'
|
|
2
|
+
import { type Application, ServiceProvider } from '@strav/kernel'
|
|
3
|
+
import { registerApiRoutes } from '../../routes/api.ts'
|
|
4
|
+
import { registerWebRoutes } from '../../routes/web.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Application-level wiring: registers routes, app-owned bindings, custom
|
|
8
|
+
* middleware. Runs in `register()` so the router still accepts route
|
|
9
|
+
* additions — `HttpProvider.boot()` compiles the trie + locks the
|
|
10
|
+
* registry; `ViewProvider.boot()` then layers auto-routed pages on top.
|
|
11
|
+
*/
|
|
12
|
+
export class AppProvider extends ServiceProvider {
|
|
13
|
+
override readonly name = 'app'
|
|
14
|
+
override readonly dependencies = ['http']
|
|
15
|
+
|
|
16
|
+
override register(app: Application): void {
|
|
17
|
+
const router = app.resolve(Router)
|
|
18
|
+
registerApiRoutes(router)
|
|
19
|
+
registerWebRoutes(router)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { HttpConsoleProvider, HttpProvider } from '@strav/http'
|
|
2
|
+
import { ConfigProvider, LoggerProvider, type ServiceProvider } from '@strav/kernel'
|
|
3
|
+
import { ViewConsoleProvider, ViewProvider } from '@strav/view'
|
|
4
|
+
import { AppProvider } from '../app/providers/app_provider.ts'
|
|
5
|
+
import appConfig from '../config/app.ts'
|
|
6
|
+
import httpConfig from '../config/http.ts'
|
|
7
|
+
import loggerConfig from '../config/logger.ts'
|
|
8
|
+
import viewConfig from '../config/view.ts'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default provider list. Order is not load-bearing — the container does a
|
|
12
|
+
* dependency-aware topo sort at boot. Keep providers grouped by package
|
|
13
|
+
* for readability.
|
|
14
|
+
*
|
|
15
|
+
* `ViewProvider` discovers `.strav` files under `resources/views/pages/`
|
|
16
|
+
* and registers a route for each at boot. `ViewConsoleProvider` adds the
|
|
17
|
+
* `view:build` / `view:cache` / `view:clear` commands.
|
|
18
|
+
*/
|
|
19
|
+
export function providers(): ServiceProvider[] {
|
|
20
|
+
return [
|
|
21
|
+
new ConfigProvider({
|
|
22
|
+
app: appConfig,
|
|
23
|
+
http: httpConfig,
|
|
24
|
+
logger: loggerConfig,
|
|
25
|
+
view: viewConfig,
|
|
26
|
+
}),
|
|
27
|
+
new LoggerProvider(),
|
|
28
|
+
new HttpProvider(),
|
|
29
|
+
new HttpConsoleProvider(),
|
|
30
|
+
new ViewProvider(),
|
|
31
|
+
new ViewConsoleProvider(),
|
|
32
|
+
new AppProvider(),
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { env } from '@strav/kernel'
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Expose stack traces in error responses. Convenient locally; never
|
|
6
|
+
* leave this on in production.
|
|
7
|
+
*/
|
|
8
|
+
exposeStackTrace: env('APP_ENV', 'local') !== 'production',
|
|
9
|
+
/**
|
|
10
|
+
* Static-asset root. GET / HEAD requests that no route handles fall
|
|
11
|
+
* through to a file lookup under this directory before the 404 path
|
|
12
|
+
* runs. Path traversal (`..`) is rejected by the kernel.
|
|
13
|
+
*
|
|
14
|
+
* `bun strav view:build` writes the islands bundle into
|
|
15
|
+
* `public/assets/islands/` — make sure your stylesheet build does the
|
|
16
|
+
* same so the layout's `<link rel="stylesheet" href="/assets/app.css">`
|
|
17
|
+
* resolves.
|
|
18
|
+
*/
|
|
19
|
+
publicDir: 'public',
|
|
20
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { env } from '@strav/kernel'
|
|
2
|
+
import type { ViewConfig } from '@strav/view'
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
directory: 'resources/views',
|
|
5
|
-
cache: env
|
|
6
|
-
|
|
6
|
+
cache: env('APP_ENV', 'local') === 'production',
|
|
7
|
+
islandsDir: 'resources/ts/islands',
|
|
8
|
+
islandsOut: 'public/assets/islands',
|
|
7
9
|
pages: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
}
|
|
10
|
+
autoRoute: true,
|
|
11
|
+
},
|
|
12
|
+
} satisfies ViewConfig
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"strav": "bun bin/strav.ts",
|
|
8
|
+
"dev": "bun --hot bin/strav.ts serve",
|
|
9
|
+
"build": "bun bin/strav.ts view:build",
|
|
10
|
+
"test": "bun test"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@strav/cli": "{{stravVersion}}",
|
|
14
|
+
"@strav/http": "{{stravVersion}}",
|
|
15
|
+
"@strav/kernel": "{{stravVersion}}",
|
|
16
|
+
"@strav/view": "{{stravVersion}}",
|
|
17
|
+
"vue": "^3.5.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "latest",
|
|
21
|
+
"@vue/compiler-sfc": "^3.5.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"bun": ">=1.3.14"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Bare-bones starter styles. Swap for Tailwind, vanilla-extract, or your
|
|
2
|
+
* stylesheet of choice — just point the <link rel="stylesheet"> in
|
|
3
|
+
* `resources/views/layouts/app.strav` at the right built output. */
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--bg: #ffffff;
|
|
7
|
+
--fg: #111111;
|
|
8
|
+
--accent: #3b82f6;
|
|
9
|
+
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
10
|
+
line-height: 1.5;
|
|
11
|
+
color-scheme: light dark;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #0f0f10;
|
|
17
|
+
--fg: #f5f5f5;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body {
|
|
22
|
+
margin: 0;
|
|
23
|
+
background: var(--bg);
|
|
24
|
+
color: var(--fg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main {
|
|
28
|
+
max-width: 48rem;
|
|
29
|
+
margin: 0 auto;
|
|
30
|
+
padding: 2rem 1.25rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
button {
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
border-radius: 0.375rem;
|
|
36
|
+
border: 1px solid var(--accent);
|
|
37
|
+
background: var(--accent);
|
|
38
|
+
color: #fff;
|
|
39
|
+
padding: 0.5rem 1rem;
|
|
40
|
+
font: inherit;
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ start?: number }>()
|
|
5
|
+
// biome-ignore lint/correctness/noUnusedVariables: bound by the <template> below
|
|
6
|
+
const count = ref(props.start ?? 0)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<button @click="count++">Count is {{ count }}</button>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared setup for every Vue island on the page. Runs once on the single
|
|
5
|
+
* `createApp(Root)` that the `@strav/view` islands bundler produces, so
|
|
6
|
+
* plugins (Pinia, vue-router, i18n, …) and global directives go here.
|
|
7
|
+
*
|
|
8
|
+
* Export a default function. The bundler invokes it with the app instance.
|
|
9
|
+
*/
|
|
10
|
+
export default (app: App): void => {
|
|
11
|
+
// Example: app.use(createPinia())
|
|
12
|
+
void app
|
|
13
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
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" />
|
|
6
|
+
<title>@yield('title', '{{projectName}}')</title>
|
|
7
|
+
<link rel="stylesheet" href="/assets/app.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
@yield('content')
|
|
12
|
+
</main>
|
|
13
|
+
<script type="module" src="/assets/islands/islands.js" defer></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@extends('layouts.app')
|
|
2
|
+
|
|
3
|
+
@set('title', '{{projectName}}')
|
|
4
|
+
|
|
5
|
+
@section('content')
|
|
6
|
+
<h1>Welcome to {{projectName}}</h1>
|
|
7
|
+
<p>You're looking at <code>resources/views/pages/index.strav</code>.</p>
|
|
8
|
+
|
|
9
|
+
<p>The button below is a Vue 3 island — see <code>resources/ts/islands/counter.vue</code>. Run <code>bun strav view:build</code> to compile islands into <code>public/assets/islands/</code>.</p>
|
|
10
|
+
|
|
11
|
+
@island('Counter', { start: 0 })
|
|
12
|
+
@endsection
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE / broadcast channel declarations. Empty until the app installs
|
|
3
|
+
* `@strav/broadcast` and declares its first channel — at which point
|
|
4
|
+
* a `Broadcaster` instance + per-channel authorize callbacks live here.
|
|
5
|
+
*
|
|
6
|
+
* Spec anchor (`spec/directory-structure.md` lists `routes/broadcast.ts`)
|
|
7
|
+
* but slice B doesn't ship the `@strav/broadcast` integration.
|
|
8
|
+
*/
|
|
9
|
+
export {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Router } from '@strav/http'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTML / browser-facing routes that need to be hand-declared (login forms,
|
|
5
|
+
* dynamic redirects, sitemap.xml — anything that can't be a static
|
|
6
|
+
* `.strav` page).
|
|
7
|
+
*
|
|
8
|
+
* Two things you may NOT need to wire here:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Auto-routed pages** — `@strav/view` registers a GET route for
|
|
11
|
+
* every `.strav` file under `resources/views/pages/` automatically.
|
|
12
|
+
* 2. **Static assets** — set `publicDir: 'public'` in `config/http.ts`
|
|
13
|
+
* (already wired in the scaffolded config) and the HTTP kernel will
|
|
14
|
+
* serve files from `public/` for any unrouted GET / HEAD request.
|
|
15
|
+
* Path traversal is rejected; routed paths still win over disk.
|
|
16
|
+
*/
|
|
17
|
+
export function registerWebRoutes(_router: Router): void {
|
|
18
|
+
// Hand-declared web routes go here.
|
|
19
|
+
}
|
|
File without changes
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework version that scaffolded apps pin to in their generated
|
|
3
|
+
* `package.json`. Bumped manually at release; see the template-strategy ADR.
|
|
4
|
+
*
|
|
5
|
+
* Spring's own `package.json` version tracks the workspace alpha for now,
|
|
6
|
+
* but this constant is what *generated apps* depend on — keep them in sync
|
|
7
|
+
* on each release until spring formally cuts independent versions.
|
|
8
|
+
*/
|
|
9
|
+
export const STRAV_VERSION = '^1.0.0-alpha.28'
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Context } from '@strav/http'
|
|
2
|
-
|
|
3
|
-
export abstract class Controller {
|
|
4
|
-
protected async respond<T>(ctx: Context, data: T, status = 200) {
|
|
5
|
-
return ctx.json(data, status)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
protected async error(ctx: Context, message: string, status = 400) {
|
|
9
|
-
return ctx.json({ error: message }, status)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
protected async notFound(ctx: Context, message = 'Not found') {
|
|
13
|
-
return ctx.json({ error: message }, 404)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { Context } from '@strav/http'
|
|
2
|
-
import { Controller } from './controller.ts'
|
|
3
|
-
import User from '../models/user.ts'
|
|
4
|
-
|
|
5
|
-
export default class UserController extends Controller {
|
|
6
|
-
async index(ctx: Context) {
|
|
7
|
-
const users = await User.all()
|
|
8
|
-
return this.respond(ctx, { users })
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async show(ctx: Context) {
|
|
12
|
-
const { id } = ctx.params
|
|
13
|
-
const user = await User.find(id)
|
|
14
|
-
|
|
15
|
-
if (!user) {
|
|
16
|
-
return this.notFound(ctx, 'User not found')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return this.respond(ctx, { user })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async store(ctx: Context) {
|
|
23
|
-
const { email, name, password } = await ctx.request.json()
|
|
24
|
-
|
|
25
|
-
if (!email || !name || !password) {
|
|
26
|
-
return this.error(ctx, 'Email, name, and password are required')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const user = await User.create({
|
|
30
|
-
id: crypto.randomUUID(),
|
|
31
|
-
email,
|
|
32
|
-
name,
|
|
33
|
-
password_hash: await Bun.password.hash(password),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return this.respond(ctx, { user }, 201)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async update(ctx: Context) {
|
|
40
|
-
const { id } = ctx.params
|
|
41
|
-
const user = await User.find(id)
|
|
42
|
-
|
|
43
|
-
if (!user) {
|
|
44
|
-
return this.notFound(ctx, 'User not found')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const { email, name } = await ctx.request.json()
|
|
48
|
-
|
|
49
|
-
if (email) user.email = email
|
|
50
|
-
if (name) user.name = name
|
|
51
|
-
|
|
52
|
-
await user.save()
|
|
53
|
-
|
|
54
|
-
return this.respond(ctx, { user })
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async destroy(ctx: Context) {
|
|
58
|
-
const { id } = ctx.params
|
|
59
|
-
const user = await User.find(id)
|
|
60
|
-
|
|
61
|
-
if (!user) {
|
|
62
|
-
return this.notFound(ctx, 'User not found')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
await user.delete()
|
|
66
|
-
|
|
67
|
-
return this.respond(ctx, { message: 'User deleted successfully' })
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { env } from '@strav/kernel'
|
|
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
|
-
|
|
9
|
-
// Full application URL (optional - will be constructed from host/port/domain if not set)
|
|
10
|
-
app_url: env('APP_URL'),
|
|
11
|
-
|
|
12
|
-
cors: {
|
|
13
|
-
enabled: true,
|
|
14
|
-
origin: ['http://localhost:3000', 'http://localhost:5173'],
|
|
15
|
-
credentials: true,
|
|
16
|
-
},
|
|
17
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Factory } from '@strav/testing'
|
|
2
|
-
import User from '../../app/models/user.ts'
|
|
3
|
-
|
|
4
|
-
export const UserFactory = Factory.define(User, (seq) => ({
|
|
5
|
-
id: crypto.randomUUID(),
|
|
6
|
-
email: `user-${seq}@example.com`,
|
|
7
|
-
name: `User ${seq}`,
|
|
8
|
-
password_hash: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
|
9
|
-
email_verified_at: new Date(),
|
|
10
|
-
remember_token: null,
|
|
11
|
-
}))
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { defineSchema, t, Archetype } from '@strav/database'
|
|
2
|
-
|
|
3
|
-
export default defineSchema('user', {
|
|
4
|
-
archetype: Archetype.Entity,
|
|
5
|
-
fields: {
|
|
6
|
-
id: t.uuid().primaryKey(),
|
|
7
|
-
email: t.string().email().unique().required(),
|
|
8
|
-
name: t.string().required(),
|
|
9
|
-
password_hash: t.string().required(),
|
|
10
|
-
email_verified_at: t.timestamp().nullable(),
|
|
11
|
-
remember_token: t.string(100).nullable(),
|
|
12
|
-
},
|
|
13
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Seeder } from '@strav/database'
|
|
2
|
-
import { UserFactory } from '../factories/user_factory.ts'
|
|
3
|
-
|
|
4
|
-
export default class UserSeeder extends Seeder {
|
|
5
|
-
async run(): Promise<void> {
|
|
6
|
-
// Create admin user
|
|
7
|
-
await UserFactory.create({
|
|
8
|
-
email: 'admin@example.com',
|
|
9
|
-
name: 'Admin User',
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
// Create test users
|
|
13
|
-
await UserFactory.createMany(10)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "__PROJECT_NAME__",
|
|
3
|
-
"version": "0.1.0",
|
|
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
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@strav/kernel": "__STRAV_VERSION__",
|
|
14
|
-
"@strav/http": "__STRAV_VERSION__",
|
|
15
|
-
"@strav/database": "__STRAV_VERSION__",
|
|
16
|
-
"@strav/cli": "__STRAV_VERSION__",
|
|
17
|
-
"reflect-metadata": "^0.2.2"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/bun": "latest",
|
|
21
|
-
"@strav/testing": "__STRAV_VERSION__",
|
|
22
|
-
"typescript": "^5.9.3"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { DatabaseProvider } from "@strav/database"
|
|
2
|
-
import { HttpProvider } from "@strav/http"
|
|
3
|
-
import { ConfigProvider, EncryptionProvider, ServiceProvider } from "@strav/kernel"
|
|
4
|
-
|
|
5
|
-
export const providers: ServiceProvider[] = [
|
|
6
|
-
new ConfigProvider(),
|
|
7
|
-
new HttpProvider(),
|
|
8
|
-
new DatabaseProvider(),
|
|
9
|
-
new EncryptionProvider(),
|
|
10
|
-
]
|