vela 0.10.13 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/bin.js +1790 -1266
- package/dist/bin.js.map +4 -4
- package/package.json +2 -2
- package/templates/minimal/components.json +4 -2
- package/templates/minimal/package.template.json +7 -3
- package/templates/minimal/src/app.css +57 -53
- package/templates/minimal/src/lib/components/ui/button/button.svelte +22 -13
- package/templates/minimal/src/lib/components/ui/sonner/sonner.svelte +23 -2
- package/templates/minimal/src/routes/(public)/+page.svelte +5 -4
- package/templates/minimal/vite.config.ts +1 -3
- package/templates/server/apply.sh +51 -11
- package/templates/server/destroy.sh +2 -2
- package/templates/server/lib.sh +38 -0
- package/templates/server/origin.sh +62 -0
- package/templates/static/components.json +4 -2
- package/templates/static/package.template.json +4 -3
- package/templates/static/src/app.css +57 -53
- package/templates/static/src/lib/components/ui/button/button.svelte +22 -13
- package/templates/static/src/lib/components/ui/sonner/sonner.svelte +23 -2
- package/templates/static/src/routes/(public)/+page.svelte +5 -4
- package/templates/ui/css/gray.css +0 -68
- package/templates/ui/css/neutral.css +0 -68
- package/templates/ui/css/slate.css +0 -68
- package/templates/ui/css/stone.css +0 -68
- package/templates/ui/css/zinc.css +0 -68
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vela",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A CLI for creating and updating SvelteKit projects",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^1.7.0",
|
|
36
36
|
"@faker-js/faker": "^10.6.0",
|
|
37
|
-
"@velastack/patterns": "^0.
|
|
37
|
+
"@velastack/patterns": "^0.2.2",
|
|
38
38
|
"@velastack/pocketbase-codegen": "^0.1.0",
|
|
39
39
|
"annotate-json-schema": "^0.1.0",
|
|
40
40
|
"commander": "^13.1.0",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
|
3
|
+
"style": "vega",
|
|
3
4
|
"tailwind": {
|
|
4
5
|
"css": "src/app.css",
|
|
5
|
-
"baseColor": "
|
|
6
|
+
"baseColor": "neutral"
|
|
6
7
|
},
|
|
7
8
|
"aliases": {
|
|
8
9
|
"components": "$lib/components",
|
|
@@ -12,5 +13,6 @@
|
|
|
12
13
|
"lib": "$lib"
|
|
13
14
|
},
|
|
14
15
|
"typescript": true,
|
|
15
|
-
"registry": "https://shadcn-svelte.com/registry"
|
|
16
|
+
"registry": "https://shadcn-svelte.com/registry",
|
|
17
|
+
"iconLibrary": "lucide"
|
|
16
18
|
}
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
"test:server": "vela test:server"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@lucide/svelte": "^1.
|
|
18
|
+
"@lucide/svelte": "^1.40.0",
|
|
19
19
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
20
20
|
"@sveltejs/kit": "^2.70.3",
|
|
21
21
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
22
22
|
"@tailwindcss/forms": "^0.5.11",
|
|
23
23
|
"@tailwindcss/typography": "^0.5.19",
|
|
24
24
|
"@tailwindcss/vite": "^4.3.3",
|
|
25
|
+
"@types/node": "^26.4.1",
|
|
25
26
|
"@types/supertest": "^6.0.3",
|
|
26
27
|
"@velastack/kit": "^0.3.0",
|
|
27
28
|
"@velastack/pocketbase": "^0.2.2",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"pocketbase-sveltekit": "^0.28.0",
|
|
32
33
|
"prettier": "^3.9.6",
|
|
33
34
|
"prettier-plugin-svelte": "^4.1.1",
|
|
34
|
-
"shadcn-svelte": "^1.
|
|
35
|
+
"shadcn-svelte": "^1.6.0",
|
|
35
36
|
"supertest": "^7.2.2",
|
|
36
37
|
"svelte": "^5.56.10",
|
|
37
38
|
"svelte-check": "^4.6.0",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"sveltekit-flash-message": "^2.4.6",
|
|
41
42
|
"sveltekit-superforms": "^2.30.2",
|
|
42
43
|
"tailwind-merge": "^3.5.0",
|
|
43
|
-
"tailwind-variants": "^3.
|
|
44
|
+
"tailwind-variants": "^3.3.0",
|
|
44
45
|
"tailwindcss": "^4.3.3",
|
|
45
46
|
"tw-animate-css": "^1.3.5",
|
|
46
47
|
"typescript": "^6.0.3",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"vite": "^8.2.2",
|
|
49
50
|
"vitest": "^4.1.11",
|
|
50
51
|
"zod": "^4.1.11"
|
|
52
|
+
},
|
|
53
|
+
"overrides": {
|
|
54
|
+
"vitest": "^4.1.11"
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
2
|
@import 'tw-animate-css';
|
|
3
|
+
@import 'shadcn-svelte/tailwind.css';
|
|
3
4
|
@plugin '@tailwindcss/typography';
|
|
4
5
|
|
|
5
6
|
@custom-variant dark (&:is(.dark *));
|
|
@@ -7,70 +8,70 @@
|
|
|
7
8
|
:root {
|
|
8
9
|
--radius: 0.625rem;
|
|
9
10
|
--background: oklch(1 0 0);
|
|
10
|
-
--foreground: oklch(0.
|
|
11
|
+
--foreground: oklch(0.145 0 0);
|
|
11
12
|
--card: oklch(1 0 0);
|
|
12
|
-
--card-foreground: oklch(0.
|
|
13
|
+
--card-foreground: oklch(0.145 0 0);
|
|
13
14
|
--popover: oklch(1 0 0);
|
|
14
|
-
--popover-foreground: oklch(0.
|
|
15
|
-
--primary: oklch(0.
|
|
16
|
-
--primary-foreground: oklch(0.
|
|
17
|
-
--secondary: oklch(0.
|
|
18
|
-
--secondary-foreground: oklch(0.
|
|
19
|
-
--muted: oklch(0.
|
|
20
|
-
--muted-foreground: oklch(0.
|
|
21
|
-
--accent: oklch(0.
|
|
22
|
-
--accent-foreground: oklch(0.
|
|
15
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
16
|
+
--primary: oklch(0.205 0 0);
|
|
17
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
18
|
+
--secondary: oklch(0.97 0 0);
|
|
19
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
20
|
+
--muted: oklch(0.97 0 0);
|
|
21
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
22
|
+
--accent: oklch(0.97 0 0);
|
|
23
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
23
24
|
--destructive: oklch(0.577 0.245 27.325);
|
|
24
|
-
--border: oklch(0.
|
|
25
|
-
--input: oklch(0.
|
|
26
|
-
--ring: oklch(0.
|
|
27
|
-
--chart-1: oklch(0.
|
|
28
|
-
--chart-2: oklch(0.
|
|
29
|
-
--chart-3: oklch(0.
|
|
30
|
-
--chart-4: oklch(0.
|
|
31
|
-
--chart-5: oklch(0.
|
|
32
|
-
--sidebar: oklch(0.
|
|
33
|
-
--sidebar-foreground: oklch(0.
|
|
34
|
-
--sidebar-primary: oklch(0.
|
|
35
|
-
--sidebar-primary-foreground: oklch(0.
|
|
36
|
-
--sidebar-accent: oklch(0.
|
|
37
|
-
--sidebar-accent-foreground: oklch(0.
|
|
38
|
-
--sidebar-border: oklch(0.
|
|
39
|
-
--sidebar-ring: oklch(0.
|
|
25
|
+
--border: oklch(0.922 0 0);
|
|
26
|
+
--input: oklch(0.922 0 0);
|
|
27
|
+
--ring: oklch(0.708 0 0);
|
|
28
|
+
--chart-1: oklch(0.87 0 0);
|
|
29
|
+
--chart-2: oklch(0.556 0 0);
|
|
30
|
+
--chart-3: oklch(0.439 0 0);
|
|
31
|
+
--chart-4: oklch(0.371 0 0);
|
|
32
|
+
--chart-5: oklch(0.269 0 0);
|
|
33
|
+
--sidebar: oklch(0.985 0 0);
|
|
34
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
35
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
36
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
37
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
38
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
39
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
40
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
.dark {
|
|
43
|
-
--background: oklch(0.
|
|
44
|
-
--foreground: oklch(0.
|
|
45
|
-
--card: oklch(0.
|
|
46
|
-
--card-foreground: oklch(0.
|
|
47
|
-
--popover: oklch(0.
|
|
48
|
-
--popover-foreground: oklch(0.
|
|
49
|
-
--primary: oklch(0.
|
|
50
|
-
--primary-foreground: oklch(0.
|
|
51
|
-
--secondary: oklch(0.
|
|
52
|
-
--secondary-foreground: oklch(0.
|
|
53
|
-
--muted: oklch(0.
|
|
54
|
-
--muted-foreground: oklch(0.
|
|
55
|
-
--accent: oklch(0.
|
|
56
|
-
--accent-foreground: oklch(0.
|
|
44
|
+
--background: oklch(0.145 0 0);
|
|
45
|
+
--foreground: oklch(0.985 0 0);
|
|
46
|
+
--card: oklch(0.205 0 0);
|
|
47
|
+
--card-foreground: oklch(0.985 0 0);
|
|
48
|
+
--popover: oklch(0.205 0 0);
|
|
49
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
50
|
+
--primary: oklch(0.922 0 0);
|
|
51
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
52
|
+
--secondary: oklch(0.269 0 0);
|
|
53
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
54
|
+
--muted: oklch(0.269 0 0);
|
|
55
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
56
|
+
--accent: oklch(0.269 0 0);
|
|
57
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
57
58
|
--destructive: oklch(0.704 0.191 22.216);
|
|
58
59
|
--border: oklch(1 0 0 / 10%);
|
|
59
60
|
--input: oklch(1 0 0 / 15%);
|
|
60
|
-
--ring: oklch(0.
|
|
61
|
-
--chart-1: oklch(0.
|
|
62
|
-
--chart-2: oklch(0.
|
|
63
|
-
--chart-3: oklch(0.
|
|
64
|
-
--chart-4: oklch(0.
|
|
65
|
-
--chart-5: oklch(0.
|
|
66
|
-
--sidebar: oklch(0.
|
|
67
|
-
--sidebar-foreground: oklch(0.
|
|
61
|
+
--ring: oklch(0.556 0 0);
|
|
62
|
+
--chart-1: oklch(0.87 0 0);
|
|
63
|
+
--chart-2: oklch(0.556 0 0);
|
|
64
|
+
--chart-3: oklch(0.439 0 0);
|
|
65
|
+
--chart-4: oklch(0.371 0 0);
|
|
66
|
+
--chart-5: oklch(0.269 0 0);
|
|
67
|
+
--sidebar: oklch(0.205 0 0);
|
|
68
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
68
69
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
69
|
-
--sidebar-primary-foreground: oklch(0.
|
|
70
|
-
--sidebar-accent: oklch(0.
|
|
71
|
-
--sidebar-accent-foreground: oklch(0.
|
|
70
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
71
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
72
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
72
73
|
--sidebar-border: oklch(1 0 0 / 10%);
|
|
73
|
-
--sidebar-ring: oklch(0.
|
|
74
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
@theme inline {
|
|
@@ -109,6 +110,9 @@
|
|
|
109
110
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
110
111
|
--color-sidebar-border: var(--sidebar-border);
|
|
111
112
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
113
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
114
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
115
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
@layer base {
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import { cn, type WithElementRef } from '$lib/utils';
|
|
3
|
-
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
4
2
|
import { type VariantProps, tv } from 'tailwind-variants';
|
|
3
|
+
import { cn, type WithElementRef } from '$lib/utils.js';
|
|
4
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
5
5
|
|
|
6
6
|
export const buttonVariants = tv({
|
|
7
|
-
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:
|
|
7
|
+
base: "rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
8
8
|
variants: {
|
|
9
9
|
variant: {
|
|
10
|
-
default: 'bg-primary text-primary-foreground
|
|
11
|
-
destructive:
|
|
12
|
-
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
|
10
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
|
|
13
11
|
outline:
|
|
14
|
-
'bg-background shadow-xs hover:bg-
|
|
15
|
-
secondary:
|
|
16
|
-
|
|
12
|
+
'border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
|
|
13
|
+
secondary:
|
|
14
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
|
15
|
+
ghost:
|
|
16
|
+
'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
|
|
17
|
+
destructive:
|
|
18
|
+
'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
|
|
17
19
|
link: 'text-primary underline-offset-4 hover:underline'
|
|
18
20
|
},
|
|
19
21
|
size: {
|
|
20
|
-
default:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
default:
|
|
23
|
+
'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
24
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
25
|
+
sm: 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5',
|
|
26
|
+
lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
27
|
+
icon: 'size-9',
|
|
28
|
+
'icon-xs':
|
|
29
|
+
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
30
|
+
'icon-sm':
|
|
31
|
+
'size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md',
|
|
32
|
+
'icon-lg': 'size-10'
|
|
24
33
|
}
|
|
25
34
|
},
|
|
26
35
|
defaultVariants: {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
|
3
2
|
import { mode } from 'mode-watcher';
|
|
3
|
+
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
|
4
|
+
import Loader2Icon from '@lucide/svelte/icons/loader-2';
|
|
5
|
+
import CircleCheckIcon from '@lucide/svelte/icons/circle-check';
|
|
6
|
+
import OctagonXIcon from '@lucide/svelte/icons/octagon-x';
|
|
7
|
+
import InfoIcon from '@lucide/svelte/icons/info';
|
|
8
|
+
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
|
|
4
9
|
|
|
5
10
|
let { ...restProps }: SonnerProps = $props();
|
|
6
11
|
</script>
|
|
@@ -10,4 +15,20 @@
|
|
|
10
15
|
class="toaster group"
|
|
11
16
|
style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
|
|
12
17
|
{...restProps}
|
|
13
|
-
|
|
18
|
+
>
|
|
19
|
+
{#snippet loadingIcon()}
|
|
20
|
+
<Loader2Icon class="size-4 animate-spin" />
|
|
21
|
+
{/snippet}
|
|
22
|
+
{#snippet successIcon()}
|
|
23
|
+
<CircleCheckIcon class="size-4" />
|
|
24
|
+
{/snippet}
|
|
25
|
+
{#snippet errorIcon()}
|
|
26
|
+
<OctagonXIcon class="size-4" />
|
|
27
|
+
{/snippet}
|
|
28
|
+
{#snippet infoIcon()}
|
|
29
|
+
<InfoIcon class="size-4" />
|
|
30
|
+
{/snippet}
|
|
31
|
+
{#snippet warningIcon()}
|
|
32
|
+
<TriangleAlertIcon class="size-4" />
|
|
33
|
+
{/snippet}
|
|
34
|
+
</Sonner>
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
<p class="text-muted-foreground mt-4 text-lg">
|
|
23
23
|
A SvelteKit app with a PocketBase backend already migrated behind it, and types synced from
|
|
24
24
|
your schema as it changes. This page is
|
|
25
|
-
<code class="bg-muted rounded px-1.5 py-0.5 font-mono text-base"
|
|
26
|
-
|
|
25
|
+
<code class="bg-muted rounded px-1.5 py-0.5 font-mono text-base"
|
|
26
|
+
>src/routes/(public)/+page.svelte</code
|
|
27
|
+
>, and it is yours to delete.
|
|
27
28
|
</p>
|
|
28
29
|
|
|
29
30
|
<div class="mt-8 flex items-center justify-center gap-3">
|
|
@@ -68,8 +69,8 @@
|
|
|
68
69
|
<div class="bg-card text-card-foreground rounded-lg border p-6">
|
|
69
70
|
<h3 class="font-medium">Your interface</h3>
|
|
70
71
|
<p class="text-muted-foreground mt-2">
|
|
71
|
-
shadcn components, copied into your project. Yours to restyle, with no dependency
|
|
72
|
-
|
|
72
|
+
shadcn components, copied into your project. Yours to restyle, with no dependency to
|
|
73
|
+
fight.
|
|
73
74
|
</p>
|
|
74
75
|
<code
|
|
75
76
|
class="bg-muted text-muted-foreground mt-4 block overflow-x-auto rounded-md px-3 py-2 font-mono text-xs"
|
|
@@ -12,9 +12,7 @@ export default defineConfig({
|
|
|
12
12
|
filename.split(/[/\\]/).includes('node_modules') ? undefined : true
|
|
13
13
|
},
|
|
14
14
|
adapter: adapter(),
|
|
15
|
-
...(process.env.VELA_ORIGIN
|
|
16
|
-
? { prerender: { origin: process.env.VELA_ORIGIN } }
|
|
17
|
-
: {})
|
|
15
|
+
...(process.env.VELA_ORIGIN ? { prerender: { origin: process.env.VELA_ORIGIN } } : {})
|
|
18
16
|
})
|
|
19
17
|
]
|
|
20
18
|
});
|
|
@@ -24,6 +24,7 @@ APP_NAME=$INSTANCE
|
|
|
24
24
|
APP_ID=$INSTANCE
|
|
25
25
|
ENV_TAG=prod
|
|
26
26
|
DOMAIN=""
|
|
27
|
+
MANAGED=""
|
|
27
28
|
HEALTH_PATH=/
|
|
28
29
|
KEEP=5
|
|
29
30
|
PB_VERSION=""
|
|
@@ -37,6 +38,7 @@ while [ $# -gt 0 ]; do
|
|
|
37
38
|
--app-id) APP_ID=$2; shift 2 ;;
|
|
38
39
|
--env) ENV_TAG=$2; shift 2 ;;
|
|
39
40
|
--domain) DOMAIN=$2; shift 2 ;;
|
|
41
|
+
--managed) MANAGED=$2; shift 2 ;;
|
|
40
42
|
--health-path) HEALTH_PATH=$2; shift 2 ;;
|
|
41
43
|
--keep) KEEP=$2; shift 2 ;;
|
|
42
44
|
--pb-version) PB_VERSION=$2; shift 2 ;;
|
|
@@ -88,7 +90,10 @@ PORTS=$(allocate_ports "$INSTANCE")
|
|
|
88
90
|
WEB_PORT=$(printf '%s' "$PORTS" | jq -r .web)
|
|
89
91
|
PB_PORT=$(printf '%s' "$PORTS" | jq -r .pb)
|
|
90
92
|
|
|
93
|
+
# The canonical host: a domain the user's own DNS points here outranks a
|
|
94
|
+
# managed velastack.app name, which then redirects to it at the edge.
|
|
91
95
|
PRIMARY_DOMAIN=$(printf '%s' "$DOMAIN" | cut -d, -f1 | tr -d ' ')
|
|
96
|
+
[ -n "$PRIMARY_DOMAIN" ] || PRIMARY_DOMAIN=$(printf '%s' "$MANAGED" | cut -d, -f1 | tr -d ' ')
|
|
92
97
|
if [ -n "$PRIMARY_DOMAIN" ]; then
|
|
93
98
|
ORIGIN="https://$PRIMARY_DOMAIN"
|
|
94
99
|
else
|
|
@@ -325,17 +330,28 @@ ACTIVATED=1
|
|
|
325
330
|
state_merge "$INSTANCE" "$(jq -c -n \
|
|
326
331
|
--arg app "$APP_ID" --arg name "$APP_NAME" --arg env "$ENV_TAG" \
|
|
327
332
|
--arg instance "$INSTANCE" --arg release "$RELEASE" --arg previous "$PREVIOUS" \
|
|
328
|
-
--arg domain "$DOMAIN" --arg
|
|
333
|
+
--arg domain "$DOMAIN" --arg managed "$MANAGED" --arg url "$ORIGIN" \
|
|
334
|
+
--arg health "$HEALTH_PATH" --arg pb "$PB_VERSION" \
|
|
329
335
|
--arg sha "$GIT_SHA" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
330
336
|
--argjson web "$WEB_PORT" --argjson pbport "$PB_PORT" --argjson backend "$BACKEND" \
|
|
331
337
|
'{appId: $app, name: $name, env: $env, instance: $instance,
|
|
332
338
|
activeRelease: $release, previousRelease: $previous, domain: $domain,
|
|
339
|
+
managed: $managed, url: $url,
|
|
333
340
|
healthCheckPath: $health, pocketbaseVersion: $pb, gitSha: $sha,
|
|
334
341
|
webPort: $web, pbPort: $pbport, backend: ($backend == 1), deployedAt: $at}')"
|
|
335
342
|
|
|
336
343
|
# ------------------------------------------------------------------ routing
|
|
344
|
+
#
|
|
345
|
+
# Two files, written independently and each only if the whole Caddy config
|
|
346
|
+
# still validates with it in place (`caddy_install` keeps the previous one
|
|
347
|
+
# otherwise). Hosts the user's DNS points here get a site block of their own
|
|
348
|
+
# and a certificate each; managed velastack.app hosts arrive through the
|
|
349
|
+
# Worker at this server's origin site and are picked out by header.
|
|
337
350
|
|
|
338
351
|
CADDY_SNIPPET="$VELA_ETC/caddy/$INSTANCE.caddy"
|
|
352
|
+
ROUTE_SNIPPET="$VELA_ETC/caddy/routes/$INSTANCE.route"
|
|
353
|
+
ROUTING_CHANGED=0
|
|
354
|
+
|
|
339
355
|
if [ -n "$DOMAIN" ]; then
|
|
340
356
|
hosts=$(printf '%s' "$DOMAIN" | tr ',' '\n' | sed 's/^ *//; s/ *$//' | grep -v '^$' | paste -sd, - | sed 's/,/, /g')
|
|
341
357
|
tmp=$(mktemp "$VELA_ETC/caddy/.snippet.XXXXXX")
|
|
@@ -343,18 +359,42 @@ if [ -n "$DOMAIN" ]; then
|
|
|
343
359
|
printf '# Managed by vela - app %s (%s)\n' "$APP_NAME" "$INSTANCE"
|
|
344
360
|
printf '%s {\n\treverse_proxy 127.0.0.1:%s\n}\n' "$hosts" "$WEB_PORT"
|
|
345
361
|
} > "$tmp"
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if ! caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
|
|
349
|
-
rm -f "$CADDY_SNIPPET"
|
|
350
|
-
die "generated Caddy config for $DOMAIN is invalid"
|
|
351
|
-
fi
|
|
352
|
-
systemctl reload caddy
|
|
362
|
+
caddy_install "$tmp" "$CADDY_SNIPPET" || die "generated Caddy config for $DOMAIN is invalid"
|
|
363
|
+
ROUTING_CHANGED=1
|
|
353
364
|
elif [ -f "$CADDY_SNIPPET" ]; then
|
|
354
365
|
rm -f "$CADDY_SNIPPET"
|
|
355
|
-
|
|
366
|
+
ROUTING_CHANGED=1
|
|
356
367
|
fi
|
|
357
368
|
|
|
369
|
+
if [ -n "$MANAGED" ]; then
|
|
370
|
+
[ -f "$VELA_ETC/caddy/00-origin.caddy" ] \
|
|
371
|
+
|| die "managed hostnames need this server's origin site - the CLI runs origin.sh first"
|
|
372
|
+
# A matcher name is an identifier; instance ids carry dashes.
|
|
373
|
+
matcher=vela_$(printf '%s' "$INSTANCE" | tr -c 'a-zA-Z0-9_' '_')
|
|
374
|
+
tmp=$(mktemp "$VELA_ETC/caddy/routes/.route.XXXXXX")
|
|
375
|
+
{
|
|
376
|
+
printf '# Managed by vela - app %s (%s)\n' "$APP_NAME" "$INSTANCE"
|
|
377
|
+
printf '@%s {\n' "$matcher"
|
|
378
|
+
printf '%s' "$MANAGED" | tr ',' '\n' | sed 's/^ *//; s/ *$//' | grep -v '^$' \
|
|
379
|
+
| while IFS= read -r host; do printf '\theader X-Velastack-Host %s\n' "$host"; done
|
|
380
|
+
printf '}\n'
|
|
381
|
+
# The app should see the public host and the visitor, not the origin
|
|
382
|
+
# name and the Cloudflare edge that carried the request here.
|
|
383
|
+
printf 'handle @%s {\n' "$matcher"
|
|
384
|
+
printf '\treverse_proxy 127.0.0.1:%s {\n' "$WEB_PORT"
|
|
385
|
+
printf '\t\theader_up Host {http.request.header.X-Velastack-Host}\n'
|
|
386
|
+
printf '\t\theader_up X-Forwarded-For {http.request.header.X-Velastack-Client-IP}\n'
|
|
387
|
+
printf '\t}\n}\n'
|
|
388
|
+
} > "$tmp"
|
|
389
|
+
caddy_install "$tmp" "$ROUTE_SNIPPET" || die "generated Caddy route for $MANAGED is invalid"
|
|
390
|
+
ROUTING_CHANGED=1
|
|
391
|
+
elif [ -f "$ROUTE_SNIPPET" ]; then
|
|
392
|
+
rm -f "$ROUTE_SNIPPET"
|
|
393
|
+
ROUTING_CHANGED=1
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
[ "$ROUTING_CHANGED" = 0 ] || caddy_reload
|
|
397
|
+
|
|
358
398
|
|
|
359
399
|
# ------------------------------------------------------------------- pruning
|
|
360
400
|
|
|
@@ -379,7 +419,7 @@ if [ "$KEEP" -gt 0 ] 2>/dev/null; then
|
|
|
379
419
|
fi
|
|
380
420
|
|
|
381
421
|
emit_result \
|
|
382
|
-
--arg instance "$INSTANCE" --arg release "$RELEASE" --arg domain "$DOMAIN" \
|
|
422
|
+
--arg instance "$INSTANCE" --arg release "$RELEASE" --arg domain "$DOMAIN" --arg managed "$MANAGED" \
|
|
383
423
|
--arg url "$ORIGIN" --argjson web "$WEB_PORT" --argjson pb "$PB_PORT" --argjson su "$SU_CREATED" \
|
|
384
|
-
'{instance: $instance, release: $release, domain: $domain, url: $url,
|
|
424
|
+
'{instance: $instance, release: $release, domain: $domain, managed: $managed, url: $url,
|
|
385
425
|
webPort: $web, pbPort: $pb, superuserCreated: ($su == 1)}'
|
|
@@ -34,8 +34,8 @@ for unit in "$(unit_web "$INSTANCE")" "$(unit_pb "$INSTANCE")"; do
|
|
|
34
34
|
done
|
|
35
35
|
|
|
36
36
|
log "removing routing"
|
|
37
|
-
rm -f "$VELA_ETC/caddy/$INSTANCE.caddy"
|
|
38
|
-
|
|
37
|
+
rm -f "$VELA_ETC/caddy/$INSTANCE.caddy" "$VELA_ETC/caddy/routes/$INSTANCE.route"
|
|
38
|
+
caddy_reload || true
|
|
39
39
|
|
|
40
40
|
log "removing releases"
|
|
41
41
|
rm -rf "$APP/releases" "$APP/deps" "$APP/current" "$APP/bin"
|
package/templates/server/lib.sh
CHANGED
|
@@ -228,3 +228,41 @@ assert_superuser_auth() {
|
|
|
228
228
|
-X POST -H 'content-type: application/json' --data-binary @- \
|
|
229
229
|
"http://127.0.0.1:$port/api/collections/_superusers/auth-with-password"
|
|
230
230
|
}
|
|
231
|
+
|
|
232
|
+
# ------------------------------------------------------------------- caddy
|
|
233
|
+
|
|
234
|
+
CADDYFILE=${CADDYFILE:-/etc/caddy/Caddyfile}
|
|
235
|
+
|
|
236
|
+
caddy_valid() {
|
|
237
|
+
caddy validate --config "$CADDYFILE" --adapter caddyfile >/dev/null 2>&1
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Put a generated snippet in place only if the whole Caddy config still
|
|
241
|
+
# validates with it there. The file it replaces is kept and restored on
|
|
242
|
+
# failure, so a bad generation cannot take a working route down.
|
|
243
|
+
#
|
|
244
|
+
# usage: caddy_install <tmp> <dest> [mode] [owner]
|
|
245
|
+
caddy_install() {
|
|
246
|
+
local tmp=$1 dest=$2 mode=${3:-0644} owner=${4:-root:root} backup=""
|
|
247
|
+
chmod "$mode" "$tmp"
|
|
248
|
+
chown "$owner" "$tmp"
|
|
249
|
+
if [ -f "$dest" ]; then
|
|
250
|
+
backup=$(mktemp "$(dirname "$dest")/.previous.XXXXXX")
|
|
251
|
+
cp -p "$dest" "$backup"
|
|
252
|
+
fi
|
|
253
|
+
mv -f "$tmp" "$dest"
|
|
254
|
+
if caddy_valid; then
|
|
255
|
+
[ -z "$backup" ] || rm -f "$backup"
|
|
256
|
+
return 0
|
|
257
|
+
fi
|
|
258
|
+
if [ -n "$backup" ]; then
|
|
259
|
+
mv -f "$backup" "$dest"
|
|
260
|
+
else
|
|
261
|
+
rm -f "$dest"
|
|
262
|
+
fi
|
|
263
|
+
return 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
caddy_reload() {
|
|
267
|
+
systemctl reload caddy >/dev/null 2>&1 || systemctl restart caddy
|
|
268
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Publish this server's origin site: the one hostname the velastack.app Worker
|
|
4
|
+
# proxies managed hostnames to.
|
|
5
|
+
#
|
|
6
|
+
# Reads /etc/vela/origin.json, which the CLI writes after registering the
|
|
7
|
+
# server with velastack.dev. The site block refuses anything without the
|
|
8
|
+
# server's token, then hands matching requests to per-instance route files
|
|
9
|
+
# that apply.sh writes under routes/. Idempotent, and quiet when nothing has
|
|
10
|
+
# changed, so a deploy does not reload Caddy for no reason.
|
|
11
|
+
#
|
|
12
|
+
# usage: origin.sh
|
|
13
|
+
set -Eeuo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
16
|
+
# shellcheck source=lib.sh
|
|
17
|
+
. "$SCRIPT_DIR/lib.sh"
|
|
18
|
+
|
|
19
|
+
require_provisioned
|
|
20
|
+
[ "$(id -u)" -eq 0 ] || die "origin must run as root"
|
|
21
|
+
|
|
22
|
+
ORIGIN_FILE="$VELA_ETC/origin.json"
|
|
23
|
+
[ -f "$ORIGIN_FILE" ] || die "$ORIGIN_FILE is missing - the CLI registers the server before running this"
|
|
24
|
+
|
|
25
|
+
HOST=$(jq -er .originHost "$ORIGIN_FILE") || die "$ORIGIN_FILE has no originHost"
|
|
26
|
+
TOKEN=$(jq -er .token "$ORIGIN_FILE") || die "$ORIGIN_FILE has no token"
|
|
27
|
+
|
|
28
|
+
ROUTES="$VELA_ETC/caddy/routes"
|
|
29
|
+
mkdir -p "$ROUTES"
|
|
30
|
+
chmod 0755 "$ROUTES"
|
|
31
|
+
# An import glob that matches nothing is a hard error in Caddy, so keep a
|
|
32
|
+
# comment-only file in place before the first managed instance is deployed.
|
|
33
|
+
if [ ! -f "$ROUTES/00-vela.route" ]; then
|
|
34
|
+
printf '# Managed by vela. Instance routes are written alongside this file.\n' \
|
|
35
|
+
> "$ROUTES/00-vela.route"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
SNIPPET="$VELA_ETC/caddy/00-origin.caddy"
|
|
39
|
+
desired=$(
|
|
40
|
+
printf '# Managed by vela - origin for velastack.app managed hostnames\n'
|
|
41
|
+
printf '%s {\n' "$HOST"
|
|
42
|
+
printf '\t@untrusted not header X-Velastack-Origin-Token "%s"\n' "$TOKEN"
|
|
43
|
+
printf '\thandle @untrusted {\n\t\trespond 403\n\t}\n'
|
|
44
|
+
printf '\timport %s/*.route\n' "$ROUTES"
|
|
45
|
+
printf '\thandle {\n\t\trespond 404\n\t}\n'
|
|
46
|
+
printf '}\n'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if [ -f "$SNIPPET" ] && [ "$(cat "$SNIPPET")" = "$desired" ]; then
|
|
50
|
+
emit_result --arg host "$HOST" '{originHost: $host, changed: false}'
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
tmp=$(mktemp "$VELA_ETC/caddy/.origin.XXXXXX")
|
|
55
|
+
printf '%s\n' "$desired" > "$tmp"
|
|
56
|
+
# Readable by the caddy user (which is what `caddy reload` runs as) and no one
|
|
57
|
+
# else: the token must not leak to the apps, which run as $VELA_USER.
|
|
58
|
+
caddy_install "$tmp" "$SNIPPET" 0640 root:caddy \
|
|
59
|
+
|| die "generated origin config for $HOST is invalid"
|
|
60
|
+
caddy_reload
|
|
61
|
+
|
|
62
|
+
emit_result --arg host "$HOST" '{originHost: $host, changed: true}'
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
|
3
|
+
"style": "vega",
|
|
3
4
|
"tailwind": {
|
|
4
5
|
"css": "src/app.css",
|
|
5
|
-
"baseColor": "
|
|
6
|
+
"baseColor": "neutral"
|
|
6
7
|
},
|
|
7
8
|
"aliases": {
|
|
8
9
|
"components": "$lib/components",
|
|
@@ -12,5 +13,6 @@
|
|
|
12
13
|
"lib": "$lib"
|
|
13
14
|
},
|
|
14
15
|
"typescript": true,
|
|
15
|
-
"registry": "https://shadcn-svelte.com/registry"
|
|
16
|
+
"registry": "https://shadcn-svelte.com/registry",
|
|
17
|
+
"iconLibrary": "lucide"
|
|
16
18
|
}
|
|
@@ -14,25 +14,26 @@
|
|
|
14
14
|
"format": "prettier --write ."
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@lucide/svelte": "^1.
|
|
17
|
+
"@lucide/svelte": "^1.40.0",
|
|
18
18
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
19
19
|
"@sveltejs/kit": "^2.70.3",
|
|
20
20
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
21
21
|
"@tailwindcss/forms": "^0.5.11",
|
|
22
22
|
"@tailwindcss/typography": "^0.5.19",
|
|
23
23
|
"@tailwindcss/vite": "^4.3.3",
|
|
24
|
+
"@types/node": "^26.4.1",
|
|
24
25
|
"@velastack/kit": "^0.3.0",
|
|
25
26
|
"clsx": "^2.1.1",
|
|
26
27
|
"mode-watcher": "^1.1.0",
|
|
27
28
|
"prettier": "^3.9.6",
|
|
28
29
|
"prettier-plugin-svelte": "^4.1.1",
|
|
29
|
-
"shadcn-svelte": "^1.
|
|
30
|
+
"shadcn-svelte": "^1.6.0",
|
|
30
31
|
"svelte": "^5.56.10",
|
|
31
32
|
"svelte-check": "^4.6.0",
|
|
32
33
|
"svelte-meta-tags": "^5.0.2",
|
|
33
34
|
"svelte-sonner": "^1.1.0",
|
|
34
35
|
"tailwind-merge": "^3.5.0",
|
|
35
|
-
"tailwind-variants": "^3.
|
|
36
|
+
"tailwind-variants": "^3.3.0",
|
|
36
37
|
"tailwindcss": "^4.3.3",
|
|
37
38
|
"tw-animate-css": "^1.3.5",
|
|
38
39
|
"typescript": "^6.0.3",
|