astris-python 0.1.0__py3-none-any.whl
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.
- astris/__init__.py +7 -0
- astris/assets/favicon.ico +0 -0
- astris/auth/__init__.py +29 -0
- astris/auth/installer.py +512 -0
- astris/auth/session.py +241 -0
- astris/cli.py +421 -0
- astris/config.py +50 -0
- astris/database/__init__.py +28 -0
- astris/database/migrations.py +259 -0
- astris/database/session.py +108 -0
- astris/http/__init__.py +25 -0
- astris/http/static.py +31 -0
- astris/inertia/__init__.py +8 -0
- astris/inertia/exceptions.py +133 -0
- astris/inertia/response.py +99 -0
- astris/inertia/shared.py +176 -0
- astris/inertia/vite.py +89 -0
- astris/installer.py +595 -0
- astris/kernel.py +230 -0
- astris/py.typed +0 -0
- astris/routing/__init__.py +31 -0
- astris/routing/router.py +38 -0
- astris/security/__init__.py +3 -0
- astris/security/csrf.py +100 -0
- astris_python-0.1.0.dist-info/METADATA +241 -0
- astris_python-0.1.0.dist-info/RECORD +29 -0
- astris_python-0.1.0.dist-info/WHEEL +4 -0
- astris_python-0.1.0.dist-info/entry_points.txt +5 -0
- astris_python-0.1.0.dist-info/licenses/LICENSE +21 -0
astris/installer.py
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
if sys.platform == "win32":
|
|
9
|
+
if isinstance(sys.stdout, io.TextIOWrapper):
|
|
10
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
11
|
+
if isinstance(sys.stderr, io.TextIOWrapper):
|
|
12
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
13
|
+
|
|
14
|
+
installer_cli = typer.Typer(
|
|
15
|
+
name="astris",
|
|
16
|
+
help="Astris Framework Installer",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
invoke_without_command=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@installer_cli.callback()
|
|
24
|
+
def callback():
|
|
25
|
+
"""Astris Framework Installer."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@installer_cli.command()
|
|
29
|
+
def new(
|
|
30
|
+
name: str = typer.Argument(..., help="The name of the new project directory"),
|
|
31
|
+
local_path: str | None = typer.Option(
|
|
32
|
+
None, "--local", "-l", help="Path to local Astris repo for development"
|
|
33
|
+
),
|
|
34
|
+
auth: bool = typer.Option(
|
|
35
|
+
True,
|
|
36
|
+
"--auth/--no-auth",
|
|
37
|
+
help="Scaffold full-stack authentication starter kit (default: enabled)",
|
|
38
|
+
),
|
|
39
|
+
):
|
|
40
|
+
"""Scaffold a brand-new Astris full-stack project."""
|
|
41
|
+
project_dir = Path.cwd() / name
|
|
42
|
+
|
|
43
|
+
if project_dir.exists():
|
|
44
|
+
typer.secho(f"Error: Directory '{name}' already exists!", fg=typer.colors.RED)
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
|
|
47
|
+
typer.secho(
|
|
48
|
+
f"š Crafting your Astris application: {name}", fg=typer.colors.CYAN, bold=True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 1. Directory layout (Domain Driven + Full-Stack Resources)
|
|
52
|
+
core_dir = project_dir / "app" / "core"
|
|
53
|
+
shared_dir = project_dir / "app" / "shared"
|
|
54
|
+
modules_dir = project_dir / "app" / "modules"
|
|
55
|
+
welcome_module_dir = modules_dir / "welcome"
|
|
56
|
+
database_dir = project_dir / "database" / "migrations"
|
|
57
|
+
public_dir = project_dir / "public"
|
|
58
|
+
views_dir = project_dir / "resources" / "views"
|
|
59
|
+
css_dir = project_dir / "resources" / "css"
|
|
60
|
+
js_dir = project_dir / "resources" / "js"
|
|
61
|
+
pages_dir = js_dir / "Pages"
|
|
62
|
+
components_dir = js_dir / "Components"
|
|
63
|
+
|
|
64
|
+
for directory in [
|
|
65
|
+
core_dir,
|
|
66
|
+
shared_dir,
|
|
67
|
+
welcome_module_dir,
|
|
68
|
+
database_dir,
|
|
69
|
+
public_dir,
|
|
70
|
+
views_dir,
|
|
71
|
+
css_dir,
|
|
72
|
+
js_dir,
|
|
73
|
+
pages_dir,
|
|
74
|
+
components_dir,
|
|
75
|
+
]:
|
|
76
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
(project_dir / "app" / "__init__.py").touch()
|
|
79
|
+
(core_dir / "__init__.py").touch()
|
|
80
|
+
(shared_dir / "__init__.py").touch()
|
|
81
|
+
(modules_dir / "__init__.py").touch()
|
|
82
|
+
(welcome_module_dir / "__init__.py").touch()
|
|
83
|
+
|
|
84
|
+
# Default framework favicon
|
|
85
|
+
try:
|
|
86
|
+
import importlib.resources as pkg_resources
|
|
87
|
+
|
|
88
|
+
favicon_bytes = (
|
|
89
|
+
pkg_resources.files("astris.assets").joinpath("favicon.ico").read_bytes()
|
|
90
|
+
)
|
|
91
|
+
(public_dir / "favicon.ico").write_bytes(favicon_bytes)
|
|
92
|
+
except (ImportError, OSError, TypeError):
|
|
93
|
+
local_favicon = Path(__file__).parent / "assets" / "favicon.ico"
|
|
94
|
+
if local_favicon.exists():
|
|
95
|
+
(public_dir / "favicon.ico").write_bytes(local_favicon.read_bytes())
|
|
96
|
+
|
|
97
|
+
from astris.database.migrations import ensure_migration_setup
|
|
98
|
+
|
|
99
|
+
ensure_migration_setup(project_dir)
|
|
100
|
+
|
|
101
|
+
# 1b. Centralized settings: app/core/config.py
|
|
102
|
+
config_stub = """from astris.config import Settings as BaseAppSettings
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Settings(BaseAppSettings):
|
|
106
|
+
\"\"\"Extend application settings with custom environment variables.\"\"\"
|
|
107
|
+
|
|
108
|
+
# Add custom settings here (e.g. STRIPE_KEY, REDIS_URL, etc.)
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
settings = Settings()
|
|
113
|
+
"""
|
|
114
|
+
(core_dir / "config.py").write_text(config_stub, encoding="utf-8")
|
|
115
|
+
|
|
116
|
+
import secrets
|
|
117
|
+
|
|
118
|
+
# 2. Environment configuration (.env and .env.example)
|
|
119
|
+
app_key = secrets.token_urlsafe(32)
|
|
120
|
+
env_content = f"""APP_NAME={name}
|
|
121
|
+
APP_ENV=local
|
|
122
|
+
APP_DEBUG=true
|
|
123
|
+
APP_KEY={app_key}
|
|
124
|
+
|
|
125
|
+
DATABASE_URL=sqlite:///database/app.db
|
|
126
|
+
"""
|
|
127
|
+
env_example_content = f"""APP_NAME={name}
|
|
128
|
+
APP_ENV=local
|
|
129
|
+
APP_DEBUG=true
|
|
130
|
+
APP_KEY=
|
|
131
|
+
|
|
132
|
+
DATABASE_URL=sqlite:///database/app.db
|
|
133
|
+
"""
|
|
134
|
+
(project_dir / ".env").write_text(env_content, encoding="utf-8")
|
|
135
|
+
(project_dir / ".env.example").write_text(env_example_content, encoding="utf-8")
|
|
136
|
+
|
|
137
|
+
# 3. pyproject.toml
|
|
138
|
+
pyproject_content = f'''[project]
|
|
139
|
+
name = "{name.lower().replace("_", "-")}"
|
|
140
|
+
version = "0.1.0"
|
|
141
|
+
description = "An Astris web application"
|
|
142
|
+
readme = "README.md"
|
|
143
|
+
requires-python = ">=3.11"
|
|
144
|
+
dependencies = []
|
|
145
|
+
'''
|
|
146
|
+
(project_dir / "pyproject.toml").write_text(pyproject_content, encoding="utf-8")
|
|
147
|
+
project_readme = f"""<p align="center" style="padding: 20px 0 10px 0;">
|
|
148
|
+
<a href="https://github.com/TheFelixGomez/astris">
|
|
149
|
+
<img src="https://raw.githubusercontent.com/TheFelixGomez/astris/main/.github/assets/astris-logo-name.png" alt="Astris" width="380">
|
|
150
|
+
</a>
|
|
151
|
+
</p>
|
|
152
|
+
|
|
153
|
+
<p align="center">
|
|
154
|
+
<strong>The modern full-stack web framework for Python.</strong><br>
|
|
155
|
+
Full-stack simplicity with modern Python performance.
|
|
156
|
+
</p>
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
# {name}
|
|
161
|
+
|
|
162
|
+
An application built with **Astris**.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## š Getting Started
|
|
167
|
+
|
|
168
|
+
### 1. Install Frontend Dependencies
|
|
169
|
+
```bash
|
|
170
|
+
npm install
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 2. Start Development Server
|
|
174
|
+
Launch the full-stack development server:
|
|
175
|
+
```bash
|
|
176
|
+
uv run orbit serve
|
|
177
|
+
```
|
|
178
|
+
Open **`http://localhost:8000`** in your browser.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## šŖ Orbit CLI Commands
|
|
183
|
+
|
|
184
|
+
| Command | Description |
|
|
185
|
+
| :--- | :--- |
|
|
186
|
+
| `uv run orbit serve` | Start full-stack development server with hot-reloading |
|
|
187
|
+
| `uv run orbit make:module <name>` | Scaffold a complete domain module (Controller, Model, View) |
|
|
188
|
+
| `uv run orbit make:controller <name>` | Generate an Astris controller |
|
|
189
|
+
| `uv run orbit make:model <name>` | Generate a database model |
|
|
190
|
+
| `uv run orbit migrate` | Run all pending database migrations |
|
|
191
|
+
| `uv run orbit make:migration "<message>"` | Auto-generate a new database schema migration |
|
|
192
|
+
| `uv run orbit key:generate` | Generate a new 32-byte secret `APP_KEY` in `.env` |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## š” Project Architecture
|
|
197
|
+
|
|
198
|
+
* **`app/modules/`**: Domain modules with controllers, models, and routes.
|
|
199
|
+
* **`app/core/config.py`**: Centralized application configuration.
|
|
200
|
+
* **`resources/js/Pages/`**: Frontend single-page application views.
|
|
201
|
+
* **`database/migrations/`**: Database schema migrations managed by Orbit.
|
|
202
|
+
* **`public/`**: Web root directory for static assets (favicon, images, robots.txt).
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## š About Astris
|
|
207
|
+
|
|
208
|
+
**Astris** is a modern full-stack web framework for Python with expressive, type-safe elegance. Designed to help developers build and ship modern web applications with speed and simplicity.
|
|
209
|
+
|
|
210
|
+
* **Repository**: [https://github.com/TheFelixGomez/astris](https://github.com/TheFelixGomez/astris)
|
|
211
|
+
* **Author**: Felix Gomez ([@TheFelixGomez](https://github.com/TheFelixGomez))
|
|
212
|
+
"""
|
|
213
|
+
(project_dir / "README.md").write_text(project_readme, encoding="utf-8")
|
|
214
|
+
(project_dir / ".gitignore").write_text(
|
|
215
|
+
".venv/\nnode_modules/\n__pycache__/\n*.pyc\n.env\npublic/build/\n",
|
|
216
|
+
encoding="utf-8",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# 3. main.py entry point
|
|
220
|
+
main_content = """from astris import Astris
|
|
221
|
+
|
|
222
|
+
app = Astris()
|
|
223
|
+
"""
|
|
224
|
+
(project_dir / "main.py").write_text(main_content, encoding="utf-8")
|
|
225
|
+
|
|
226
|
+
# 4. Default Welcome Controller using InertiaResponse
|
|
227
|
+
welcome_controller = """from astris.http import Request
|
|
228
|
+
from astris.inertia import InertiaResponse
|
|
229
|
+
from astris.routing import Controller
|
|
230
|
+
|
|
231
|
+
controller = Controller(tags=["Home"])
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@controller.get("/")
|
|
235
|
+
async def index(request: Request) -> InertiaResponse:
|
|
236
|
+
return InertiaResponse(
|
|
237
|
+
request,
|
|
238
|
+
"Welcome",
|
|
239
|
+
props={
|
|
240
|
+
"status": "online",
|
|
241
|
+
"message": "Welcome to your Astris application! š",
|
|
242
|
+
"version": "0.1.0",
|
|
243
|
+
"api_docs_url": "/docs",
|
|
244
|
+
"redoc_url": "/redoc",
|
|
245
|
+
"docs_url": "https://astris.dev/docs",
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
"""
|
|
249
|
+
(welcome_module_dir / "welcome_controller.py").write_text(
|
|
250
|
+
welcome_controller, encoding="utf-8"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# 5. Frontend: package.json
|
|
254
|
+
package_json_content = f'''{{
|
|
255
|
+
"name": "{name.lower().replace("_", "-")}",
|
|
256
|
+
"private": true,
|
|
257
|
+
"type": "module",
|
|
258
|
+
"scripts": {{
|
|
259
|
+
"dev": "vite",
|
|
260
|
+
"build": "vite build"
|
|
261
|
+
}},
|
|
262
|
+
"dependencies": {{
|
|
263
|
+
"@inertiajs/vue3": "^3.6.1",
|
|
264
|
+
"vue": "^3.5.41"
|
|
265
|
+
}},
|
|
266
|
+
"devDependencies": {{
|
|
267
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
268
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
269
|
+
"tailwindcss": "^4.3.3",
|
|
270
|
+
"typescript": "^7.0.2",
|
|
271
|
+
"vite": "^8.2.2"
|
|
272
|
+
}}
|
|
273
|
+
}}
|
|
274
|
+
'''
|
|
275
|
+
(project_dir / "package.json").write_text(package_json_content, encoding="utf-8")
|
|
276
|
+
|
|
277
|
+
# 6. Frontend: vite.config.ts
|
|
278
|
+
vite_config_content = """import { defineConfig } from "vite";
|
|
279
|
+
import vue from "@vitejs/plugin-vue";
|
|
280
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
281
|
+
import { resolve } from "path";
|
|
282
|
+
|
|
283
|
+
export default defineConfig({
|
|
284
|
+
plugins: [tailwindcss(), vue()],
|
|
285
|
+
publicDir: false,
|
|
286
|
+
resolve: {
|
|
287
|
+
alias: {
|
|
288
|
+
"@": resolve(import.meta.dirname, "resources/js"),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
build: {
|
|
292
|
+
outDir: "public/build",
|
|
293
|
+
emptyOutDir: true,
|
|
294
|
+
manifest: true,
|
|
295
|
+
rollupOptions: {
|
|
296
|
+
input: "resources/js/app.ts",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
server: {
|
|
300
|
+
origin: "http://localhost:5173",
|
|
301
|
+
port: 5173,
|
|
302
|
+
strictPort: true,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
"""
|
|
306
|
+
(project_dir / "vite.config.ts").write_text(vite_config_content, encoding="utf-8")
|
|
307
|
+
|
|
308
|
+
# 7. Frontend: tsconfig.json
|
|
309
|
+
tsconfig_content = """{
|
|
310
|
+
"compilerOptions": {
|
|
311
|
+
"target": "ESNext",
|
|
312
|
+
"useDefineForClassFields": true,
|
|
313
|
+
"module": "ESNext",
|
|
314
|
+
"moduleResolution": "Bundler",
|
|
315
|
+
"strict": true,
|
|
316
|
+
"jsx": "preserve",
|
|
317
|
+
"resolveJsonModule": true,
|
|
318
|
+
"isolatedModules": true,
|
|
319
|
+
"esModuleInterop": true,
|
|
320
|
+
"lib": ["ESNext", "DOM"],
|
|
321
|
+
"skipLibCheck": true,
|
|
322
|
+
"baseUrl": ".",
|
|
323
|
+
"paths": {
|
|
324
|
+
"@/*": ["resources/js/*"]
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.vue"]
|
|
328
|
+
}
|
|
329
|
+
"""
|
|
330
|
+
(project_dir / "tsconfig.json").write_text(tsconfig_content, encoding="utf-8")
|
|
331
|
+
|
|
332
|
+
# 8. Frontend: resources/css/app.css (Tailwind CSS v4)
|
|
333
|
+
(css_dir / "app.css").write_text('@import "tailwindcss";\n', encoding="utf-8")
|
|
334
|
+
|
|
335
|
+
# 9. Frontend: resources/views/root.html
|
|
336
|
+
root_html_content = """<!DOCTYPE html>
|
|
337
|
+
<html lang="en">
|
|
338
|
+
<head>
|
|
339
|
+
<meta charset="UTF-8">
|
|
340
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
341
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
342
|
+
<title>Astris Application</title>
|
|
343
|
+
</head>
|
|
344
|
+
<body class="bg-slate-950 text-slate-100 antialiased font-sans">
|
|
345
|
+
@inertia
|
|
346
|
+
|
|
347
|
+
@vite
|
|
348
|
+
</body>
|
|
349
|
+
</html>
|
|
350
|
+
"""
|
|
351
|
+
(views_dir / "root.html").write_text(root_html_content, encoding="utf-8")
|
|
352
|
+
|
|
353
|
+
# 10. Frontend: resources/js/app.ts
|
|
354
|
+
app_ts_content = """import "../css/app.css";
|
|
355
|
+
import { createApp, h } from "vue";
|
|
356
|
+
import { createInertiaApp } from "@inertiajs/vue3";
|
|
357
|
+
|
|
358
|
+
const el = document.getElementById("app");
|
|
359
|
+
|
|
360
|
+
if (!el || !el.dataset.page) {
|
|
361
|
+
throw new Error("Inertia root element (#app) or data-page attribute not found in the DOM.");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const initialPage = JSON.parse(el.dataset.page);
|
|
365
|
+
const pages = import.meta.glob("./Pages/**/*.vue", { eager: true });
|
|
366
|
+
|
|
367
|
+
createInertiaApp({
|
|
368
|
+
page: initialPage,
|
|
369
|
+
resolve: (name) => {
|
|
370
|
+
const page: any = pages[`./Pages/${name}.vue`];
|
|
371
|
+
if (!page) {
|
|
372
|
+
throw new Error(`Page component "${name}" not found in ./Pages/`);
|
|
373
|
+
}
|
|
374
|
+
return page.default ?? page;
|
|
375
|
+
},
|
|
376
|
+
setup({ el, App, props, plugin }) {
|
|
377
|
+
createApp({ render: () => h(App, props) })
|
|
378
|
+
.use(plugin)
|
|
379
|
+
.mount(el);
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
"""
|
|
383
|
+
(js_dir / "app.ts").write_text(app_ts_content, encoding="utf-8")
|
|
384
|
+
|
|
385
|
+
# 10. Frontend: resources/js/Components/AstrisLogo.vue
|
|
386
|
+
astris_logo_vue_content = """<template>
|
|
387
|
+
<svg
|
|
388
|
+
viewBox="0 0 792 792"
|
|
389
|
+
fill="currentColor"
|
|
390
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
391
|
+
>
|
|
392
|
+
<path d="m50 757h88.8l37.8-92.3c-31.4-1.6-59-6.3-82.4-14.2z"/>
|
|
393
|
+
<path d="m596.3 550.8q-11 6.5-22.3 12.6l79.1 193.6h88.9l-98.1-236.5q-22.4 15.6-47.6 30.3z"/>
|
|
394
|
+
<path d="m210.1 582.6l185.9-454.7 147.5 360.9c25-14.4 48-29.9 68.2-46l-169.2-407.8h-93l-221.6 534.1c21.2 8.3 49.5 12.9 82.2 13.5z"/>
|
|
395
|
+
<path fill-rule="evenodd" d="m372.5 466.8l23.5 76.4 23.5-76.4 69-23.5-68.8-23.8-23.7-75.8-23.7 75.8-68.8 23.8 69 23.5z"/>
|
|
396
|
+
<path d="m756.2 309.9c-18.3-50.9-96.5-72.1-201.4-61.6 81.6-3.1 141.7 15.8 156.8 58 26.6 74.2-96.3 192.3-273.9 256.1-177.7 63.7-343.3 55.2-370-19-15.5-43.4 19.7-99.6 87.1-151.5-90.4 60.9-137.6 131.1-118.9 183.2 29.4 82 214.3 90.6 413.1 19.3 198.8-71.3 336.6-202.5 307.2-284.5z"/>
|
|
397
|
+
<path d="m90 533.2c2.1 6 5.3 11.5 9.4 16.6q-2.3-3.9-3.9-8.2c-12.7-35.5 18.2-82.5 77.1-127.1l12.8-31c-71.7 50.7-110.7 107.1-95.4 149.7z"/>
|
|
398
|
+
<path d="m437 275.7c-30 6.8-61.1 15.9-92.6 27.2q-0.3 0.1-0.6 0.2l-10.1 24.7q8.5-3.3 17.3-6.4c31.8-11.4 63.2-20.7 93.3-27.8l-7.4-17.9z"/>
|
|
399
|
+
<path d="m688.7 323.7q1.4 4.1 2.1 8.4c0-6.6-1.1-12.9-3.2-18.9-13.1-36.3-63.2-53.7-132.2-52.9l7 16.8c66.6-1.8 114.4 13.4 126.3 46.6z"/>
|
|
400
|
+
</svg>
|
|
401
|
+
</template>
|
|
402
|
+
"""
|
|
403
|
+
(components_dir / "AstrisLogo.vue").write_text(
|
|
404
|
+
astris_logo_vue_content, encoding="utf-8"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# 11. Frontend: resources/js/Pages/Welcome.vue
|
|
408
|
+
welcome_vue_content = """<script setup lang="ts">
|
|
409
|
+
import { Link, usePage } from '@inertiajs/vue3';
|
|
410
|
+
import AstrisLogo from '../Components/AstrisLogo.vue';
|
|
411
|
+
|
|
412
|
+
interface Props {
|
|
413
|
+
status: string;
|
|
414
|
+
message: string;
|
|
415
|
+
version?: string;
|
|
416
|
+
api_docs_url?: string;
|
|
417
|
+
redoc_url?: string;
|
|
418
|
+
docs_url?: string;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
withDefaults(defineProps<Props>(), {
|
|
422
|
+
version: "0.1.0",
|
|
423
|
+
api_docs_url: "/docs",
|
|
424
|
+
redoc_url: "/redoc",
|
|
425
|
+
docs_url: "https://github.com/TheFelixGomez/astris",
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const page = usePage();
|
|
429
|
+
</script>
|
|
430
|
+
|
|
431
|
+
<template>
|
|
432
|
+
<main class="min-h-screen flex flex-col justify-between bg-slate-950 text-slate-100 p-6 font-sans relative overflow-hidden selection:bg-sky-500 selection:text-white">
|
|
433
|
+
<!-- Subtle Background Ambient Glow -->
|
|
434
|
+
<div class="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-sky-500/10 blur-[120px] rounded-full pointer-events-none -z-10"></div>
|
|
435
|
+
|
|
436
|
+
<!-- Top Navigation -->
|
|
437
|
+
<header class="flex justify-between items-center max-w-5xl w-full mx-auto py-2">
|
|
438
|
+
<div class="flex items-center gap-2.5">
|
|
439
|
+
<AstrisLogo class="w-8 h-8 text-sky-400" />
|
|
440
|
+
<span class="font-bold text-lg tracking-tight text-white">Astris</span>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<nav class="flex items-center gap-3">
|
|
444
|
+
<template v-if="page.props.auth?.user">
|
|
445
|
+
<Link
|
|
446
|
+
href="/dashboard"
|
|
447
|
+
class="px-4 py-2 rounded-xl text-sm font-medium text-sky-400 bg-sky-500/10 border border-sky-500/20 hover:bg-sky-500/20 hover:border-sky-500/40 transition duration-200"
|
|
448
|
+
>
|
|
449
|
+
Dashboard →
|
|
450
|
+
</Link>
|
|
451
|
+
</template>
|
|
452
|
+
<template v-else>
|
|
453
|
+
<Link
|
|
454
|
+
href="/login"
|
|
455
|
+
class="px-3.5 py-1.5 rounded-xl text-sm font-medium text-slate-300 hover:text-white transition duration-200"
|
|
456
|
+
>
|
|
457
|
+
Sign In
|
|
458
|
+
</Link>
|
|
459
|
+
<Link
|
|
460
|
+
href="/register"
|
|
461
|
+
class="px-4 py-1.5 rounded-xl text-sm font-medium text-white bg-sky-500 hover:bg-sky-400 shadow-md shadow-sky-500/20 transition duration-200"
|
|
462
|
+
>
|
|
463
|
+
Register
|
|
464
|
+
</Link>
|
|
465
|
+
</template>
|
|
466
|
+
</nav>
|
|
467
|
+
</header>
|
|
468
|
+
|
|
469
|
+
<!-- Main Hero Content -->
|
|
470
|
+
<div class="max-w-3xl w-full my-auto mx-auto text-center py-10">
|
|
471
|
+
<!-- Hero Logo & Title -->
|
|
472
|
+
<div class="mb-10 flex flex-col items-center">
|
|
473
|
+
<div class="relative mb-6 group">
|
|
474
|
+
<div class="absolute -inset-2 bg-gradient-to-r from-sky-500 to-indigo-500 rounded-3xl blur-lg opacity-30 group-hover:opacity-60 transition duration-500"></div>
|
|
475
|
+
<div class="relative p-4 rounded-2xl bg-slate-900/80 border border-slate-800 backdrop-blur shadow-2xl">
|
|
476
|
+
<AstrisLogo class="w-16 h-16 text-sky-400" />
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<h1 class="text-4xl sm:text-5xl font-extrabold tracking-tight text-white mb-3">
|
|
481
|
+
Astris
|
|
482
|
+
</h1>
|
|
483
|
+
<p class="text-lg sm:text-xl text-slate-400 max-w-xl mx-auto leading-relaxed mb-5">
|
|
484
|
+
{{ message }}
|
|
485
|
+
</p>
|
|
486
|
+
|
|
487
|
+
<!-- Version & Status Badge -->
|
|
488
|
+
<div class="inline-flex items-center gap-2 px-3.5 py-1 rounded-full bg-slate-900/80 border border-slate-800 text-xs font-mono text-slate-300 shadow-sm backdrop-blur">
|
|
489
|
+
<span class="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_#34d399] animate-pulse"></span>
|
|
490
|
+
<span>Status: <span class="text-emerald-400 font-semibold">{{ status }}</span></span>
|
|
491
|
+
<span class="text-slate-600">•</span>
|
|
492
|
+
<span class="text-sky-400">v{{ version }}</span>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<!-- Quick Navigation Cards -->
|
|
497
|
+
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 text-left mb-10">
|
|
498
|
+
<!-- Interactive API Docs -->
|
|
499
|
+
<a
|
|
500
|
+
:href="api_docs_url"
|
|
501
|
+
target="_blank"
|
|
502
|
+
rel="noopener noreferrer"
|
|
503
|
+
class="group p-5 rounded-2xl bg-slate-900/60 hover:bg-slate-900/90 border border-slate-800 hover:border-sky-500/40 backdrop-blur shadow-lg transition-all duration-200 hover:-translate-y-1"
|
|
504
|
+
>
|
|
505
|
+
<div class="w-9 h-9 rounded-xl bg-sky-500/10 border border-sky-500/20 flex items-center justify-center text-sky-400 text-lg mb-3.5 group-hover:scale-110 transition duration-200">
|
|
506
|
+
ā”
|
|
507
|
+
</div>
|
|
508
|
+
<h3 class="text-sm font-semibold text-white mb-1 group-hover:text-sky-400 transition">Swagger API Docs</h3>
|
|
509
|
+
<p class="text-xs text-slate-400 leading-relaxed">Interactive OpenAPI interface to explore and test endpoints.</p>
|
|
510
|
+
</a>
|
|
511
|
+
|
|
512
|
+
<!-- ReDoc API Docs -->
|
|
513
|
+
<a
|
|
514
|
+
:href="redoc_url"
|
|
515
|
+
target="_blank"
|
|
516
|
+
rel="noopener noreferrer"
|
|
517
|
+
class="group p-5 rounded-2xl bg-slate-900/60 hover:bg-slate-900/90 border border-slate-800 hover:border-sky-500/40 backdrop-blur shadow-lg transition-all duration-200 hover:-translate-y-1"
|
|
518
|
+
>
|
|
519
|
+
<div class="w-9 h-9 rounded-xl bg-sky-500/10 border border-sky-500/20 flex items-center justify-center text-sky-400 text-lg mb-3.5 group-hover:scale-110 transition duration-200">
|
|
520
|
+
š
|
|
521
|
+
</div>
|
|
522
|
+
<h3 class="text-sm font-semibold text-white mb-1 group-hover:text-sky-400 transition">ReDoc Schema</h3>
|
|
523
|
+
<p class="text-xs text-slate-400 leading-relaxed">Clean, structured documentation for API schemas and models.</p>
|
|
524
|
+
</a>
|
|
525
|
+
|
|
526
|
+
<!-- Project / Framework Documentation -->
|
|
527
|
+
<a
|
|
528
|
+
:href="docs_url"
|
|
529
|
+
target="_blank"
|
|
530
|
+
rel="noopener noreferrer"
|
|
531
|
+
class="group p-5 rounded-2xl bg-slate-900/60 hover:bg-slate-900/90 border border-slate-800 hover:border-sky-500/40 backdrop-blur shadow-lg transition-all duration-200 hover:-translate-y-1"
|
|
532
|
+
>
|
|
533
|
+
<div class="w-9 h-9 rounded-xl bg-sky-500/10 border border-sky-500/20 flex items-center justify-center text-sky-400 text-lg mb-3.5 group-hover:scale-110 transition duration-200">
|
|
534
|
+
š
|
|
535
|
+
</div>
|
|
536
|
+
<h3 class="text-sm font-semibold text-white mb-1 group-hover:text-sky-400 transition">Astris Docs</h3>
|
|
537
|
+
<p class="text-xs text-slate-400 leading-relaxed">Official guides, controllers, Inertia integration, and tutorials.</p>
|
|
538
|
+
</a>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<!-- Quick Command Tip -->
|
|
542
|
+
<div class="p-3.5 rounded-xl bg-slate-900/70 border border-slate-800/80 text-xs font-mono text-slate-400 flex items-center justify-center gap-2 shadow-inner">
|
|
543
|
+
<span>Get started:</span>
|
|
544
|
+
<code class="text-sky-400 bg-slate-950 px-2 py-0.5 rounded border border-slate-800">uv run orbit make:module billing</code>
|
|
545
|
+
<span class="text-slate-600">•</span>
|
|
546
|
+
<code class="text-indigo-400 bg-slate-950 px-2 py-0.5 rounded border border-slate-800">uv run orbit serve</code>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<!-- Footer -->
|
|
551
|
+
<footer class="text-center text-xs text-slate-500 py-4">
|
|
552
|
+
Built with ā¤ļø by <a href="https://github.com/TheFelixGomez" target="_blank" rel="noopener noreferrer" class="text-slate-400 hover:text-slate-300 underline underline-offset-4 transition">Felix Gomez</a>.
|
|
553
|
+
</footer>
|
|
554
|
+
</main>
|
|
555
|
+
</template>
|
|
556
|
+
"""
|
|
557
|
+
(pages_dir / "Welcome.vue").write_text(welcome_vue_content, encoding="utf-8")
|
|
558
|
+
|
|
559
|
+
if auth:
|
|
560
|
+
from astris.auth.installer import install_auth_starter
|
|
561
|
+
|
|
562
|
+
install_auth_starter(project_dir)
|
|
563
|
+
typer.secho(
|
|
564
|
+
"ā Scaffolded full-stack authentication starter kit", fg=typer.colors.GREEN
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# 11. Initialize Python virtualenv and resolve dependencies
|
|
568
|
+
typer.echo("š¦ Initializing virtual environment and resolving dependencies...")
|
|
569
|
+
subprocess.run(["uv", "venv"], cwd=project_dir, check=True)
|
|
570
|
+
|
|
571
|
+
if local_path:
|
|
572
|
+
resolved_local = str(Path(local_path).resolve())
|
|
573
|
+
subprocess.run(["uv", "add", resolved_local], cwd=project_dir, check=True)
|
|
574
|
+
else:
|
|
575
|
+
sibling_astris = (Path.cwd() / "astris").resolve()
|
|
576
|
+
parent_astris = (Path.cwd() / ".." / "astris").resolve()
|
|
577
|
+
|
|
578
|
+
if (sibling_astris / "pyproject.toml").exists():
|
|
579
|
+
subprocess.run(
|
|
580
|
+
["uv", "add", str(sibling_astris)], cwd=project_dir, check=True
|
|
581
|
+
)
|
|
582
|
+
elif (parent_astris / "pyproject.toml").exists():
|
|
583
|
+
subprocess.run(
|
|
584
|
+
["uv", "add", str(parent_astris)], cwd=project_dir, check=True
|
|
585
|
+
)
|
|
586
|
+
else:
|
|
587
|
+
subprocess.run(["uv", "add", "astris-python"], cwd=project_dir, check=True)
|
|
588
|
+
|
|
589
|
+
typer.secho(
|
|
590
|
+
f"\nā Project {name} created successfully!", fg=typer.colors.GREEN, bold=True
|
|
591
|
+
)
|
|
592
|
+
typer.echo("\nTo get started, run:")
|
|
593
|
+
typer.secho(f" cd {name}", fg=typer.colors.YELLOW)
|
|
594
|
+
typer.secho(" npm install", fg=typer.colors.YELLOW)
|
|
595
|
+
typer.secho(" uv run orbit serve\n", fg=typer.colors.YELLOW)
|