@strav/spring 0.4.31 → 1.0.0-alpha.29

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 (88) hide show
  1. package/README.md +11 -52
  2. package/package.json +19 -20
  3. package/src/args.ts +119 -0
  4. package/src/cli.ts +134 -0
  5. package/src/index.ts +10 -176
  6. package/src/prompts.ts +49 -127
  7. package/src/scaffold.ts +115 -36
  8. package/src/spring_error.ts +11 -0
  9. package/src/templates/shared/README.md.tt +37 -0
  10. package/src/templates/shared/_dot_env.example.tt +12 -0
  11. package/src/templates/shared/_dot_env.tt +12 -0
  12. package/src/templates/shared/_dot_gitignore +12 -0
  13. package/src/templates/shared/app/console/_dot_gitkeep +0 -0
  14. package/src/templates/shared/app/exceptions/_dot_gitkeep +0 -0
  15. package/src/templates/shared/app/http/controllers/_dot_gitkeep +0 -0
  16. package/src/templates/shared/app/http/middleware/_dot_gitkeep +0 -0
  17. package/src/templates/shared/app/http/requests/_dot_gitkeep +0 -0
  18. package/src/templates/shared/app/jobs/_dot_gitkeep +0 -0
  19. package/src/templates/shared/app/mail/_dot_gitkeep +0 -0
  20. package/src/templates/shared/app/models/_dot_gitkeep +0 -0
  21. package/src/templates/shared/app/notifications/_dot_gitkeep +0 -0
  22. package/src/templates/shared/app/policies/_dot_gitkeep +0 -0
  23. package/src/templates/shared/app/providers/app_provider.ts +18 -0
  24. package/src/templates/shared/app/repositories/_dot_gitkeep +0 -0
  25. package/src/templates/shared/bin/strav.ts +21 -0
  26. package/src/templates/shared/bootstrap/app.ts +13 -0
  27. package/src/templates/shared/bootstrap/providers.ts +24 -0
  28. package/src/templates/shared/config/app.ts.tt +13 -0
  29. package/src/templates/shared/config/http.ts +9 -0
  30. package/src/templates/shared/config/logger.ts +9 -0
  31. package/src/templates/shared/database/factories/_dot_gitkeep +0 -0
  32. package/src/templates/shared/database/migrations/_dot_gitkeep +0 -0
  33. package/src/templates/shared/database/schemas/_dot_gitkeep +0 -0
  34. package/src/templates/shared/database/seeders/_dot_gitkeep +0 -0
  35. package/src/templates/shared/package.json.tt +22 -0
  36. package/src/templates/shared/routes/api.ts +11 -0
  37. package/src/templates/shared/routes/console.ts +10 -0
  38. package/src/templates/shared/storage/cache/_dot_gitkeep +0 -0
  39. package/src/templates/shared/storage/logs/_dot_gitkeep +0 -0
  40. package/src/templates/shared/storage/uploads/_dot_gitkeep +0 -0
  41. package/src/templates/shared/tests/feature/healthz.test.ts.tt +19 -0
  42. package/src/templates/shared/tests/unit/_dot_gitkeep +0 -0
  43. package/src/templates/shared/tsconfig.json +21 -11
  44. package/src/templates/web/README.md.tt +42 -0
  45. package/src/templates/web/_dot_gitignore +13 -0
  46. package/src/templates/web/app/providers/app_provider.ts +21 -0
  47. package/src/templates/web/bootstrap/providers.ts +31 -0
  48. package/src/templates/web/config/http.ts +17 -8
  49. package/src/templates/web/config/view.ts +26 -7
  50. package/src/templates/web/package.json.tt +26 -0
  51. package/src/templates/web/public/assets/_dot_gitkeep +0 -0
  52. package/src/templates/web/resources/css/app.css +32 -0
  53. package/src/templates/web/resources/views/components/_dot_gitkeep +0 -0
  54. package/src/templates/web/resources/views/errors/404.strav +8 -0
  55. package/src/templates/web/resources/views/errors/500.strav +8 -0
  56. package/src/templates/web/resources/views/layouts/app.strav.tt +46 -0
  57. package/src/templates/web/resources/views/pages/index.strav.tt +47 -0
  58. package/src/templates/web/routes/broadcast.ts +9 -0
  59. package/src/templates/web/routes/web.ts +19 -0
  60. package/src/templates/web/tests/browser/_dot_gitkeep +0 -0
  61. package/src/version.ts +9 -0
  62. package/src/templates/api/app/controllers/controller.ts +0 -15
  63. package/src/templates/api/app/controllers/user_controller.ts +0 -69
  64. package/src/templates/api/config/database.ts +0 -9
  65. package/src/templates/api/config/http.ts +0 -17
  66. package/src/templates/api/database/factories/user_factory.ts +0 -11
  67. package/src/templates/api/database/schemas/user.ts +0 -13
  68. package/src/templates/api/database/seeders/database_seeder.ts +0 -8
  69. package/src/templates/api/database/seeders/user_seeder.ts +0 -15
  70. package/src/templates/api/index.ts +0 -11
  71. package/src/templates/api/package.json +0 -24
  72. package/src/templates/api/start/providers.ts +0 -10
  73. package/src/templates/api/start/routes.ts +0 -22
  74. package/src/templates/shared/config/app.ts +0 -7
  75. package/src/templates/shared/config/encryption.ts +0 -5
  76. package/src/templates/shared/package.json +0 -24
  77. package/src/templates/shared/storage/uploads/.gitkeep +0 -1
  78. package/src/templates/shared/strav.ts +0 -2
  79. package/src/templates/shared/tests/example.test.ts +0 -11
  80. package/src/templates/web/index.ts +0 -28
  81. package/src/templates/web/package.json +0 -26
  82. package/src/templates/web/public/builds/.gitkeep +0 -1
  83. package/src/templates/web/public/css/.gitkeep +0 -1
  84. package/src/templates/web/resources/css/app.scss +0 -77
  85. package/src/templates/web/resources/islands/counter.vue +0 -31
  86. package/src/templates/web/resources/views/layouts/app.strav +0 -18
  87. package/src/templates/web/resources/views/pages/index.strav +0 -32
  88. package/src/templates/web/start/providers.ts +0 -11
@@ -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
- "strict": true,
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
- "noEmit": true,
9
- "composite": false,
19
+ "verbatimModuleSyntax": true,
20
+ "isolatedModules": true,
21
+ "esModuleInterop": true,
22
+ "resolveJsonModule": true,
10
23
  "skipLibCheck": true,
11
- "allowSyntheticDefaultImports": true,
12
24
  "forceConsistentCasingInFileNames": true,
13
- "experimentalDecorators": true,
14
- "emitDecoratorMetadata": true,
15
- "useDefineForClassFields": false,
16
- "types": ["bun-types"]
25
+
26
+ "noEmit": true
17
27
  },
18
28
  "include": ["**/*.ts"],
19
- "exclude": ["node_modules", "dist"]
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,13 @@
1
+ node_modules
2
+ .env
3
+ .env.local
4
+ storage/cache/*
5
+ !storage/cache/.gitkeep
6
+ storage/logs/*
7
+ !storage/logs/.gitkeep
8
+ storage/uploads/*
9
+ !storage/uploads/.gitkeep
10
+ public/assets/islands/
11
+ public/assets/app.css
12
+ *.log
13
+ .DS_Store
@@ -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,31 @@
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
+
6
+ /**
7
+ * Default provider list. Order is not load-bearing — the container does a
8
+ * dependency-aware topo sort at boot. Keep providers grouped by package
9
+ * for readability.
10
+ *
11
+ * `ConfigProvider.fromDirectory('config')` auto-discovers every
12
+ * `config/*.ts` file and keys them by basename — `config/view.ts` →
13
+ * `config('view.*')`, and so on. To add a new config slot, drop a file
14
+ * with a `default export` into `config/`. No edits to this list
15
+ * required.
16
+ *
17
+ * `ViewProvider` discovers `.strav` files under `resources/views/pages/`
18
+ * and registers a route for each at boot. `ViewConsoleProvider` adds the
19
+ * `view:build` / `view:cache` / `view:clear` commands.
20
+ */
21
+ export async function providers(): Promise<ServiceProvider[]> {
22
+ return [
23
+ await ConfigProvider.fromDirectory('config'),
24
+ new LoggerProvider(),
25
+ new HttpProvider(),
26
+ new HttpConsoleProvider(),
27
+ new ViewProvider(),
28
+ new ViewConsoleProvider(),
29
+ new AppProvider(),
30
+ ]
31
+ }
@@ -1,11 +1,20 @@
1
1
  import { env } from '@strav/kernel'
2
2
 
3
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
- }
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,31 @@
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.bool('VIEW_CACHE', true),
6
- assets: ['css/app.css', 'builds/islands.js'],
6
+ cache: env('APP_ENV', 'local') === 'production',
7
+
8
+ // CSS bundling — `view:build` reads each input, walks `@import`s
9
+ // via Bun's CSS bundler, and writes to `outputDir`. The layout's
10
+ // `@css` directive emits the matching `<link rel="stylesheet">`.
11
+ css: {
12
+ inputs: ['resources/css/app.css'],
13
+ outputDir: 'public/assets',
14
+ linkPath: 'app.css',
15
+ },
16
+
17
+ // Asset versioning for `@asset` / `@css`. `publicDir` mirrors the
18
+ // layout under `/assets/...` so `app.css` resolves to a file at
19
+ // `public/assets/app.css` and a URL at `/assets/app.css`.
20
+ // Versioning is mtime-based by default; drop a
21
+ // `public/assets/manifest.json` to switch to fingerprinted
22
+ // filenames.
23
+ assets: {
24
+ publicDir: 'public/assets',
25
+ prefix: '/assets/',
26
+ },
27
+
7
28
  pages: {
8
- directory: 'pages',
9
- enabled: true,
10
- indexFile: 'index.strav'
11
- }
12
- }
29
+ autoRoute: true,
30
+ },
31
+ } 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,32 @@
1
+ /* Spring `--web` starter — vanilla CSS that pairs with the Tailwind
2
+ * CDN loaded by `resources/views/layouts/app.strav`. Everything below
3
+ * is what Tailwind doesn't cover: the body background grid, the
4
+ * fluid hero type, and the global text/background colour reset.
5
+ *
6
+ * Swap for your own stylesheet of choice — point the layout's `@css`
7
+ * directive (resolved via `config.view.css`) at whatever you build.
8
+ */
9
+
10
+ body {
11
+ background-color: #ffffff;
12
+ color: #000000;
13
+ }
14
+
15
+ /* Faint red grid overlay across the page. The 40px cells line up
16
+ * with the Tailwind 8/12-step spacing rhythm. */
17
+ .bg-grid-precision {
18
+ background-size: 40px 40px;
19
+ background-image:
20
+ linear-gradient(to right, rgba(255, 68, 51, 0.05) 1px, transparent 1px),
21
+ linear-gradient(to bottom, rgba(255, 68, 51, 0.05) 1px, transparent 1px);
22
+ }
23
+
24
+ /* Fluid display type for the hero — clamps between 3rem at narrow
25
+ * widths and 5.5rem at wide. Letter-spacing tightens at scale; the
26
+ * line-height is sub-1 so the two stacked lines hug. */
27
+ .text-huge {
28
+ font-size: clamp(3rem, 8vw, 5.5rem);
29
+ line-height: 0.95;
30
+ letter-spacing: -0.04em;
31
+ font-weight: 800;
32
+ }
@@ -0,0 +1,8 @@
1
+ @extends('layouts.app')
2
+
3
+ @set('title', 'Not found')
4
+
5
+ @section('content')
6
+ <h1>404 — Not found</h1>
7
+ <p>The page you asked for doesn't exist.</p>
8
+ @endsection
@@ -0,0 +1,8 @@
1
+ @extends('layouts.app')
2
+
3
+ @set('title', 'Server error')
4
+
5
+ @section('content')
6
+ <h1>500 — Something went wrong</h1>
7
+ <p>An unexpected error occurred. Please try again shortly.</p>
8
+ @endsection
@@ -0,0 +1,46 @@
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
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ primary: '#ff4433',
14
+ 'on-surface': '#000000',
15
+ 'on-surface-variant': '#666666',
16
+ outline: '#e5e5e5',
17
+ surface: '#ffffff',
18
+ },
19
+ fontFamily: {
20
+ sans: ['system-ui', '-apple-system', 'sans-serif'],
21
+ mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
22
+ },
23
+ },
24
+ },
25
+ }
26
+ </script>
27
+ @css
28
+ </head>
29
+ <body class="min-h-screen flex flex-col selection:bg-primary selection:text-white bg-grid-precision">
30
+ <main class="flex-grow w-full mx-auto px-6 md:px-12 py-16 md:py-24 max-w-[800px]">
31
+ @yield('content')
32
+ </main>
33
+
34
+ <footer class="border-t border-black bg-white py-12 px-6 md:px-12 mt-auto">
35
+ <div class="max-w-[1400px] mx-auto flex flex-col md:flex-row justify-between items-center gap-8">
36
+ <div class="text-[10px] font-mono tracking-[0.3em] uppercase text-on-surface-variant">
37
+ © 2026 Strav
38
+ </div>
39
+ <div class="flex gap-12">
40
+ <a class="text-sm font-bold uppercase tracking-tighter hover:text-primary transition-colors" href="https://strav.dev">Docs</a>
41
+ <a class="text-sm font-bold uppercase tracking-tighter hover:text-primary transition-colors" href="https://github.com/strav/strav">Repository</a>
42
+ </div>
43
+ </div>
44
+ </footer>
45
+ </body>
46
+ </html>
@@ -0,0 +1,47 @@
1
+ @extends('layouts.app')
2
+
3
+ @set('title', '{{projectName}}')
4
+
5
+ @section('content')
6
+ {{-- Hero --}}
7
+ <div class="mb-24">
8
+ <div class="inline-block border border-primary px-3 py-1 mb-8 text-[11px] font-mono tracking-widest uppercase text-primary">
9
+ V1.0.0-AI • Local Environment Active
10
+ </div>
11
+ <h1 class="text-huge mb-8">
12
+ The Fast Path for<br>
13
+ <span class="text-primary">{{projectName}}</span>
14
+ </h1>
15
+ <p class="text-xl md:text-2xl text-on-surface-variant max-w-2xl leading-tight font-medium">
16
+ An industrial-grade architecture for modern web applications. Engineered for speed and the uncompromising developer.
17
+ </p>
18
+ </div>
19
+
20
+ {{-- Edit these to get started --}}
21
+ <div class="grid gap-0 border border-black bg-white">
22
+ <div class="p-8 md:p-12">
23
+ <h2 class="text-3xl font-bold tracking-tighter mb-8 italic">Edit these to get started</h2>
24
+
25
+ <div class="flex flex-col divide-y divide-black/10 border-t border-black/10">
26
+ <div class="py-4 flex flex-col md:flex-row md:justify-between md:items-center gap-2 group">
27
+ <code class="font-mono text-primary group-hover:underline">resources/views/pages/index.strav</code>
28
+ <span class="text-[10px] font-mono uppercase text-on-surface-variant">This Page</span>
29
+ </div>
30
+ <div class="py-4 flex flex-col md:flex-row md:justify-between md:items-center gap-2">
31
+ <code class="font-mono text-black">resources/views/layouts/app.strav</code>
32
+ <span class="text-[10px] font-mono uppercase text-on-surface-variant">Layout Shell</span>
33
+ </div>
34
+ <div class="py-4 flex flex-col md:flex-row md:justify-between md:items-center gap-2">
35
+ <code class="font-mono text-black">resources/css/app.css</code>
36
+ <span class="text-[10px] font-mono uppercase text-on-surface-variant">Styles</span>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="mt-12 flex flex-wrap gap-4">
41
+ <span class="text-[10px] font-mono font-bold px-2 py-1 border border-black">BUN</span>
42
+ <span class="text-[10px] font-mono font-bold px-2 py-1 border border-black">TYPESCRIPT</span>
43
+ <span class="text-[10px] font-mono font-bold px-2 py-1 border border-primary text-primary">POSTGRESQL</span>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ @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.29'
@@ -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,9 +0,0 @@
1
- import { env } from '@strav/kernel'
2
-
3
- export default {
4
- host: env('DB_HOST', 'localhost'),
5
- port: env.int('DB_PORT', 5432),
6
- database: env('DB_DATABASE', '__DB_NAME__'),
7
- username: env('DB_USER', 'postgres'),
8
- password: env('DB_PASSWORD', ''),
9
- }
@@ -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,8 +0,0 @@
1
- import { Seeder } from '@strav/database'
2
- import UserSeeder from './user_seeder.ts'
3
-
4
- export default class DatabaseSeeder extends Seeder {
5
- async run(): Promise<void> {
6
- await this.call(UserSeeder)
7
- }
8
- }
@@ -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,11 +0,0 @@
1
- import 'reflect-metadata'
2
- import { app } from '@strav/kernel'
3
- import { providers } from './start/providers'
4
-
5
- // Register service providers
6
- app.useProviders(providers)
7
-
8
- // Load routes
9
- await import('./start/routes')
10
-
11
- await app.start()