@stacksjs/defaults 0.74.3 → 0.74.5
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/ai/skills/stacks-auto-imports/SKILL.md +1 -1
- package/ai/skills/stacks-buddy/SKILL.md +53 -3
- package/ai/skills/stacks-cloud/SKILL.md +83 -11
- package/ai/skills/stacks-commerce/SKILL.md +1 -1
- package/ai/skills/stacks-composables/SKILL.md +1 -1
- package/ai/skills/stacks-dashboard/SKILL.md +2 -2
- package/ai/skills/stacks-deploy/SKILL.md +97 -24
- package/ai/skills/stacks-orm/SKILL.md +1 -1
- package/ai/skills/stacks-types/SKILL.md +1 -1
- package/ai/skills/stacks-writing-for-agents/SKILL.md +1 -1
- package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +2 -7
- package/app/Actions/Dashboard/Analytics/web-analytics-provider.ts +103 -0
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +24 -84
- package/app/Actions/Dashboard/Infrastructure/log-provider.ts +189 -0
- package/app/Actions/Dashboard/dashboard-provider.ts +170 -0
- package/app/Actions/Monitoring/ErrorGroupAction.ts +2 -4
- package/app/Actions/Monitoring/ErrorIndexAction.ts +2 -4
- package/app/Actions/Monitoring/ErrorShowAction.ts +4 -4
- package/app/Actions/Monitoring/ErrorStatsAction.ts +2 -4
- package/app/Actions/Monitoring/ErrorTimelineAction.ts +2 -4
- package/app/Actions/Monitoring/error-provider.ts +130 -0
- package/app/Models/EmailIdempotency.ts +4 -1
- package/app/Models/EmailSuppression.ts +4 -1
- package/app/Models/EmailWebhookEvent.ts +4 -1
- package/app/Models/Request.ts +4 -1
- package/functions/api-url.test.ts +61 -0
- package/functions/api-url.ts +19 -2
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/app/Actions/Buddy/CommandsAction.ts +0 -820
|
@@ -66,7 +66,7 @@ globalThis.toggleDark = toggleDark
|
|
|
66
66
|
- **Custom Functions**: From `resources/functions/` (counter, dark mode, GPX, geo utilities)
|
|
67
67
|
|
|
68
68
|
### Server Auto-Imports (100+)
|
|
69
|
-
- **All ORM Models**: User, Post, Author, Product, Order, Payment, Customer, etc. (
|
|
69
|
+
- **All ORM Models**: User, Post, Author, Product, Order, Payment, Customer, etc. (97 models)
|
|
70
70
|
- **Request Models**: UserRequest, PostRequest, OrderRequest, etc.
|
|
71
71
|
- **Actions**: Action types and helpers
|
|
72
72
|
- **Schema**: validation schema builder
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-buddy
|
|
3
|
-
description: Use when working with the Stacks CLI (buddy/bud/stacks/stx) - understanding
|
|
3
|
+
description: Use when working with the Stacks CLI (buddy/bud/stacks/stx) - understanding every command with its flags and options, adding custom commands, the make:* scaffolding commands, development server commands, build commands, deployment commands, email/mail commands, environment management, or domain/DNS commands. Covers @stacksjs/buddy and all CLI command files.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Buddy CLI
|
|
10
10
|
|
|
11
|
-
The complete CLI runtime for the Stacks framework
|
|
11
|
+
The complete CLI runtime for the Stacks framework, lazy-loaded for fast cold starts. `docs/guide/buddy/commands.md` lists every command and is generated from the runtime registry, so it is the count as well as the reference.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Core package: `storage/framework/core/buddy/src/`
|
|
@@ -367,7 +367,57 @@ buddy env:check # validate environment configuration
|
|
|
367
367
|
## Cloud & Deployment
|
|
368
368
|
|
|
369
369
|
### `buddy deploy` - Deploy to cloud
|
|
370
|
-
Handles full deployment workflow: prerequisites check, pantry install, env setup, APP_KEY,
|
|
370
|
+
Handles full deployment workflow: prerequisites check, pantry install, env setup, APP_KEY, provider credentials, domain setup, email DNS records (DKIM, MX, SPF, DMARC), mail user creation.
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
buddy deploy [env] # env is production | staging | development
|
|
374
|
+
--domain <domain> # override the domain this deploy publishes
|
|
375
|
+
-p/--project [project] # target a specific project
|
|
376
|
+
--prod # deploy to production
|
|
377
|
+
--staging # deploy to staging
|
|
378
|
+
--dev # deploy to development
|
|
379
|
+
--site <name> # deploy only this one site to the existing server
|
|
380
|
+
--docker # also build an OCI image with pantry and push it
|
|
381
|
+
--dry-run # preview the plan, change nothing
|
|
382
|
+
--yes # skip confirmation (required from a non-TTY)
|
|
383
|
+
-J/--json # machine-readable deployment preview
|
|
384
|
+
--verbose # verbose output
|
|
385
|
+
|
|
386
|
+
buddy deploy:rollback [site] # activate a preserved release (hetzner/ssh targets)
|
|
387
|
+
--env <environment> # environment to roll back (default: production)
|
|
388
|
+
--to <release> # preserved release id to activate
|
|
389
|
+
--dry-run # preview without changing the active release
|
|
390
|
+
--verbose # verbose output
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Where it deploys is `cloud.provider` in `config/cloud.ts`: `'aws'` (default, CloudFormation),
|
|
394
|
+
`'hetzner'` (provision a server, then deploy over SSH), or `'ssh'` (a host you already own).
|
|
395
|
+
`CLOUD_PROVIDER` overrides the config value.
|
|
396
|
+
|
|
397
|
+
### `buddy server:*` - An SSH deploy target
|
|
398
|
+
For `cloud.provider: 'ssh'`, a Linux box you already own. The `raspberry-pi` profile tunes the
|
|
399
|
+
bootstrap for a small single-board computer.
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
buddy server:flash # write an OS image to an SD card or USB disk
|
|
403
|
+
--os <name> # raspberry-pi-os-lite (default), raspberry-pi-os, ubuntu-24.04, ubuntu-26.04
|
|
404
|
+
--device <path> # the whole disk to write to, e.g. /dev/disk4
|
|
405
|
+
--list # list writable disks and exit
|
|
406
|
+
--dry-run # say what would happen without writing
|
|
407
|
+
--yes # skip the confirmation
|
|
408
|
+
buddy server:first-boot # write cloud-init user-data to the mounted boot partition
|
|
409
|
+
--hostname <name> # the board's hostname
|
|
410
|
+
--user <name> # the login user cloud-init creates
|
|
411
|
+
buddy server:doctor # preflight over SSH: arch, OS, memory, disk, sudo, clock, HTTPS
|
|
412
|
+
buddy server:setup # adopt and bootstrap the host (bun, rpx, systemd units)
|
|
413
|
+
buddy server:trust # install the box's local CA here, or emit a .mobileconfig
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Then `buddy deploy --prod` behaves exactly as it does for Hetzner. Connection details come from
|
|
417
|
+
`ssh.hosts` in `config/cloud.ts`, or from `TS_CLOUD_SSH_HOST`, `TS_CLOUD_SSH_USER`,
|
|
418
|
+
`TS_CLOUD_SSH_PORT` and `TS_CLOUD_SSH_KEY`, which win over the config. A host on a private address
|
|
419
|
+
publishes no DNS and requests no certificate; `TS_CLOUD_SSH_PUBLISH_DNS=1` forces publishing on
|
|
420
|
+
and `0` forces it off.
|
|
371
421
|
|
|
372
422
|
### `buddy cloud` - Cloud management
|
|
373
423
|
```bash
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-cloud
|
|
3
|
-
description: Use when deploying or managing cloud infrastructure for Stacks - AWS deployment via CloudFormation/CDK, server mode (EC2, ALB, VPC), serverless mode (Lambda, API Gateway, CloudFront), jump boxes, domain management (Route53), S3 storage, SES email, edge computing, security groups, IAM, or the cloud configuration. Covers @stacksjs/cloud, @stacksjs/deploy, storage/framework/cloud/, and cloud/.
|
|
3
|
+
description: Use when deploying or managing cloud infrastructure for Stacks - choosing between the AWS, Hetzner and SSH targets, AWS deployment via CloudFormation/CDK, server mode (EC2, ALB, VPC), serverless mode (Lambda, API Gateway, CloudFront), jump boxes, domain management (Route53), S3 storage, SES email, edge computing, security groups, IAM, the rpx gateway and systemd units on an SSH box, or the cloud configuration. Covers @stacksjs/cloud, @stacksjs/deploy, storage/framework/cloud/, and cloud/.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Bun >= 1.3.0, TypeScript, AWS
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript, AWS / Hetzner / any 64-bit Debian or Ubuntu host over SSH
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Stacks Cloud & Deployment
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Cloud infrastructure for Stacks, across three deploy targets, all driven through
|
|
12
|
+
`@stacksjs/ts-cloud`.
|
|
13
|
+
|
|
14
|
+
## Targets
|
|
15
|
+
|
|
16
|
+
`cloud.provider` in `config/cloud.ts` decides which one runs. `CLOUD_PROVIDER` in the environment
|
|
17
|
+
overrides it. With neither set, the provider is `aws`.
|
|
18
|
+
|
|
19
|
+
| `cloud.provider` | Infrastructure | Pipeline |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `'aws'` (default) | CloudFormation stack: EC2/ALB/VPC or Lambda/API Gateway/CloudFront | Template generated and applied |
|
|
22
|
+
| `'hetzner'` | One Hetzner Cloud server, firewall, SSH key, managed services | Tarball over SSH, systemd units, rpx gateway |
|
|
23
|
+
| `'ssh'` | A host you already own; nothing is provisioned | Same tarball pipeline, after an in-place bootstrap |
|
|
24
|
+
|
|
25
|
+
Read `config/cloud.ts` before advising anything provider-specific. This repo uses `'hetzner'`.
|
|
26
|
+
Most of this page is the AWS target; the Hetzner and SSH sections are marked as such.
|
|
12
27
|
|
|
13
28
|
## Key Paths
|
|
14
29
|
- Cloud package: `storage/framework/core/cloud/src/`
|
|
@@ -18,7 +33,7 @@ AWS-focused cloud deployment using CloudFormation via `@stacksjs/ts-cloud`.
|
|
|
18
33
|
- Cloud driver state: `storage/cloud/` (ts-cloud's `stateDir`, set in `config/cloud.ts`)
|
|
19
34
|
- Configuration: `config/cloud.ts`
|
|
20
35
|
|
|
21
|
-
## Deployment Modes
|
|
36
|
+
## Deployment Modes (AWS)
|
|
22
37
|
|
|
23
38
|
### Server Mode (EC2)
|
|
24
39
|
- EC2 instances with configurable types (t3.micro, t4g.nano, etc.)
|
|
@@ -37,7 +52,7 @@ AWS-focused cloud deployment using CloudFormation via `@stacksjs/ts-cloud`.
|
|
|
37
52
|
- SQS queues for background jobs
|
|
38
53
|
- DynamoDB (optional)
|
|
39
54
|
|
|
40
|
-
## Deployment Flow
|
|
55
|
+
## Deployment Flow (AWS)
|
|
41
56
|
|
|
42
57
|
```bash
|
|
43
58
|
buddy deploy # deploy to cloud
|
|
@@ -53,6 +68,51 @@ buddy deploy # deploy to cloud
|
|
|
53
68
|
- Tags: Environment, Project, ManagedBy
|
|
54
69
|
- OnFailure: ROLLBACK
|
|
55
70
|
|
|
71
|
+
## Hetzner and SSH targets
|
|
72
|
+
|
|
73
|
+
Both deploy the same way once a host exists: build each site, package a release tarball, copy it
|
|
74
|
+
over SSH, install and restart systemd units, preserve the prior release, and write this project's
|
|
75
|
+
rpx gateway fragment.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// config/cloud.ts
|
|
79
|
+
cloud: { provider: 'hetzner' }, // HCLOUD_TOKEN or hetzner.apiToken
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
cloud: { provider: 'ssh' },
|
|
84
|
+
ssh: {
|
|
85
|
+
profile: 'raspberry-pi', // or 'generic'
|
|
86
|
+
hosts: [{ host: 'pi-stacks.local', user: 'pi', port: 22, privateKeyPath: '~/.ssh/id_ed25519', role: 'app' }],
|
|
87
|
+
hostKey: 'pin', // 'pin' | 'accept-new' | 'insecure'
|
|
88
|
+
sudo: true,
|
|
89
|
+
publicIp: 'auto', // omit to stay LAN-only
|
|
90
|
+
lan: { hostname: 'pi-stacks.local', tls: 'local-ca' },
|
|
91
|
+
},
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Env beats config for the SSH target: `TS_CLOUD_SSH_HOST`, `TS_CLOUD_SSH_USER`,
|
|
95
|
+
`TS_CLOUD_SSH_PORT`, `TS_CLOUD_SSH_KEY`. The first host with no `role`, or `role: 'app'`, is the
|
|
96
|
+
one deployed to. Multi-host fleets are not supported.
|
|
97
|
+
|
|
98
|
+
**The LAN rule.** An SSH host on a private address publishes no DNS, requests no Let's Encrypt
|
|
99
|
+
certificate, skips the CDN and skips mail reconciliation, and prints the LAN URLs instead. Private
|
|
100
|
+
means RFC1918, `100.64/10` CGNAT, loopback, link-local, IPv6 unique-local or link-local, a
|
|
101
|
+
`.local` / `.internal` / `.lan` / `.intranet` / `.home.arpa` name, or a bare single-label
|
|
102
|
+
hostname. LAN HTTPS comes from a certificate authority on the box itself (rpx `localCa`), not from
|
|
103
|
+
Let's Encrypt. To publish: routable address, ports 80 and 443 forwarded, a site with a domain, and
|
|
104
|
+
`ssh.publicIp` set. `TS_CLOUD_SSH_PUBLISH_DNS=1` forces publishing on, `0` forces it off.
|
|
105
|
+
|
|
106
|
+
**Shared boxes.** Both targets support `cloud.attachTo: '<owner slug>'`. An attached project skips
|
|
107
|
+
provisioning and deploys only its own sites. Each project owns `/etc/rpx/sites.d/<slug>.json` and
|
|
108
|
+
replaces it wholesale, so `project.slug` must be unique on the box, and the config must declare
|
|
109
|
+
every domain that fragment currently serves or the deploy stops.
|
|
110
|
+
|
|
111
|
+
**State.** `storage/cloud/state/<stack>.json` records the host, SSH user and port, key path, host
|
|
112
|
+
key fingerprint, LAN address, profile and staging path. `buddy cloud` builds an `ssh` project's
|
|
113
|
+
fleet from the config plus those pins, since there is no provider API to enumerate; status reads
|
|
114
|
+
`unknown` because nothing polls the host. `buddy server:doctor` is what asks.
|
|
115
|
+
|
|
56
116
|
## Cloud Helper Functions
|
|
57
117
|
|
|
58
118
|
```typescript
|
|
@@ -136,7 +196,10 @@ export default {
|
|
|
136
196
|
|
|
137
197
|
```bash
|
|
138
198
|
buddy deploy # deploy application
|
|
139
|
-
buddy
|
|
199
|
+
buddy deploy:rollback [site] # activate a preserved release (hetzner/ssh)
|
|
200
|
+
buddy server:doctor # preflight an ssh host
|
|
201
|
+
buddy server:setup # adopt and bootstrap an ssh host
|
|
202
|
+
buddy cloud --diff # show infrastructure changes (aws)
|
|
140
203
|
buddy cloud --ssh # SSH into cloud
|
|
141
204
|
buddy cloud --invalidate-cache # invalidate CDN
|
|
142
205
|
buddy cloud:add --jump-box # add jump box instance
|
|
@@ -173,19 +236,28 @@ buddy domains:remove <domain> # remove domain
|
|
|
173
236
|
|
|
174
237
|
## Infrastructure Stack (storage/framework/cloud/)
|
|
175
238
|
|
|
176
|
-
- `deploy.ts
|
|
177
|
-
- `cdk.json
|
|
178
|
-
- `package.json
|
|
239
|
+
- `deploy.ts`: Main deployment script (CDK app entry)
|
|
240
|
+
- `cdk.json`: CDK configuration with 54 AWS context settings
|
|
241
|
+
- `package.json`: Cloud package dependencies
|
|
179
242
|
|
|
180
243
|
Stack naming: `{slugified-app-name}-cloud`
|
|
181
244
|
|
|
182
245
|
## Gotchas
|
|
246
|
+
- Check `cloud.provider` first. AWS advice is wrong for a Hetzner or SSH box, and vice versa
|
|
247
|
+
- On Hetzner and SSH there is no CloudFormation, no CDK, no stack, and `buddy cloud --diff` has
|
|
248
|
+
nothing to diff
|
|
249
|
+
- An SSH host is adopted, never created. `buddy server:doctor` checks it and `buddy server:setup`
|
|
250
|
+
bootstraps it before the first deploy
|
|
251
|
+
- A key named in `ssh.hosts[].privateKeyPath` that is not on disk is caught up front now; it used
|
|
252
|
+
to surface much later as a bare `Permission denied (publickey)`
|
|
253
|
+
- A Raspberry Pi has no real-time clock, so apt and ACME can both fail in the first minute after
|
|
254
|
+
boot until NTP corrects it
|
|
183
255
|
- AWS credentials MUST be configured (`buddy configure:aws` or env vars)
|
|
184
256
|
- Default region is `us-east-1` (from AWS_DEFAULT_REGION env)
|
|
185
257
|
- APP_KEY must be colon-separated format (validated during deployment)
|
|
186
258
|
- Server mode uses EC2 + ALB; serverless uses Lambda + API Gateway + CloudFront
|
|
187
|
-
- Jump boxes are optional
|
|
188
|
-
- `cloud:remove` with `--force` skips confirmation
|
|
259
|
+
- Jump boxes are optional, used for SSH access to private instances
|
|
260
|
+
- `cloud:remove` with `--force` skips confirmation and is destructive
|
|
189
261
|
- CDK toolkit stack is named `stacks-toolkit`
|
|
190
262
|
- Environment mapping: `local` → `development`, others preserved
|
|
191
263
|
- Deploy hooks run before/after deployment for custom logic
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Commerce
|
|
10
10
|
|
|
11
|
-
Comprehensive e-commerce module with 15 sub-modules and
|
|
11
|
+
Comprehensive e-commerce module with 15 sub-modules and 36 models.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Core package: `storage/framework/core/commerce/src/`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-composables
|
|
3
|
-
description: Use when creating or using reactive composables in STX templates -
|
|
3
|
+
description: Use when creating or using reactive composables in STX templates - 153 composables for state management, DOM interaction, sensors, animation, browser APIs, async operations, or the complete list of auto-imported composables. Covers @stacksjs/composables.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-dashboard
|
|
3
|
-
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the
|
|
3
|
+
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 399 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Dashboard
|
|
10
10
|
|
|
11
|
-
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views,
|
|
11
|
+
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 399 components, and a multi-section layout.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-deploy
|
|
3
|
-
description: Use when deploying a Stacks application - the deployment workflow (build → deploy), pre/post deploy hooks, server vs serverless mode selection, first-time deployment setup, deployment troubleshooting, or the buddy deploy command. For cloud infrastructure details (EC2, Lambda, CloudFormation, Route53, IAM), see stacks-cloud.
|
|
3
|
+
description: Use when deploying a Stacks application - picking a deploy target (AWS, Hetzner, or a host you own over SSH), the deployment workflow (build → deploy), pre/post deploy hooks, server vs serverless mode selection, first-time deployment setup, rollback, deployment troubleshooting, or the buddy deploy command. For cloud infrastructure details (EC2, Lambda, CloudFormation, Route53, IAM, rpx, systemd), see stacks-cloud.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Bun >= 1.3.0, TypeScript, AWS
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript, AWS / Hetzner / any 64-bit Debian or Ubuntu host over SSH
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,6 +10,23 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
10
10
|
|
|
11
11
|
The deployment workflow for Stacks applications.
|
|
12
12
|
|
|
13
|
+
## Deploy Targets
|
|
14
|
+
|
|
15
|
+
`cloud.provider` in `config/cloud.ts` decides where `buddy deploy` ships. `CLOUD_PROVIDER` in the
|
|
16
|
+
environment overrides it. With neither set, the provider is `aws`.
|
|
17
|
+
|
|
18
|
+
| `cloud.provider` | Target | What the deploy does |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `'aws'` (default) | AWS | Generates infrastructure, creates or updates a CloudFormation stack |
|
|
21
|
+
| `'hetzner'` | Hetzner Cloud | Provisions the server through the Hetzner API, then deploys over SSH |
|
|
22
|
+
| `'ssh'` | A host you already own | Adopts and bootstraps the host over SSH, then deploys to it |
|
|
23
|
+
|
|
24
|
+
Read the repo's own `config/cloud.ts` before assuming a target. This repo uses `'hetzner'`.
|
|
25
|
+
|
|
26
|
+
Hetzner and `ssh` share one pipeline: a release tarball copied over SSH, systemd units per site,
|
|
27
|
+
and the rpx gateway in front. They differ in whether a server is created for you. AWS is a
|
|
28
|
+
different pipeline entirely.
|
|
29
|
+
|
|
13
30
|
## Quick Deploy
|
|
14
31
|
|
|
15
32
|
```bash
|
|
@@ -20,36 +37,73 @@ Preview the complete environment-aware plan before changing anything:
|
|
|
20
37
|
|
|
21
38
|
```bash
|
|
22
39
|
buddy deploy --dry-run
|
|
23
|
-
buddy deploy --dry-run
|
|
40
|
+
buddy deploy staging --dry-run
|
|
24
41
|
buddy deploy --dry-run --site docs
|
|
25
42
|
buddy deploy --dry-run --json
|
|
26
43
|
```
|
|
27
44
|
|
|
28
|
-
The preview uses the same environment transformation and ts-cloud site
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
The JSON form emits a versioned `STACKS_DEPLOY_PREVIEW_JSON=` line for API and
|
|
34
|
-
tool consumers.
|
|
45
|
+
The preview uses the same environment transformation and ts-cloud site classification as a real
|
|
46
|
+
deploy. It reports the ordered validation, infrastructure, build, package, release, runtime,
|
|
47
|
+
gateway, DNS, TLS, and optional container operations. It exits before prerequisite setup, builds,
|
|
48
|
+
packaging, hooks, provider calls, persistence, DNS, TLS, or service restarts. The JSON form emits
|
|
49
|
+
a versioned `STACKS_DEPLOY_PREVIEW_JSON=` line for API and tool consumers.
|
|
35
50
|
|
|
36
51
|
## Deployment Prerequisites
|
|
37
52
|
|
|
53
|
+
**AWS** (`provider: 'aws'`)
|
|
54
|
+
|
|
38
55
|
1. **AWS credentials configured**: `buddy configure:aws`
|
|
39
56
|
2. **APP_KEY generated**: `buddy key:generate` (must be colon-separated format)
|
|
40
57
|
3. **APP_URL set** in `.env`
|
|
41
58
|
4. **Team configured** in `config/team.ts`
|
|
42
59
|
|
|
60
|
+
**Hetzner** (`provider: 'hetzner'`)
|
|
61
|
+
|
|
62
|
+
1. `HCLOUD_TOKEN` in the environment, or `hetzner.apiToken` in `config/cloud.ts`
|
|
63
|
+
2. `~/.ssh/id_ed25519.pub` on the machine deploying: the driver registers it on the server
|
|
64
|
+
|
|
65
|
+
**SSH** (`provider: 'ssh'`)
|
|
66
|
+
|
|
67
|
+
1. A host in `ssh.hosts`, or `TS_CLOUD_SSH_HOST` in the environment
|
|
68
|
+
2. A key that reaches it, and passwordless sudo when the user is not root
|
|
69
|
+
3. `buddy server:doctor` clean
|
|
70
|
+
|
|
43
71
|
## Deployment Flow
|
|
44
72
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
**AWS**
|
|
74
|
+
|
|
75
|
+
1. **Validation**: checks APP_KEY format, AWS region, app URL, team config
|
|
76
|
+
2. **Build**: compiles application for production
|
|
77
|
+
3. **Infrastructure**: generates CloudFormation template via ts-cloud
|
|
78
|
+
4. **Deploy**: creates or updates the CloudFormation stack
|
|
49
79
|
- Capabilities: CAPABILITY_IAM, CAPABILITY_NAMED_IAM
|
|
50
80
|
- OnFailure: ROLLBACK
|
|
51
81
|
- Tags: Environment, Project, ManagedBy
|
|
52
82
|
|
|
83
|
+
**Hetzner and SSH**
|
|
84
|
+
|
|
85
|
+
1. **Validation**: same prerequisite checks, plus the ts-cloud persistent-state capability check
|
|
86
|
+
2. **Infrastructure**: Hetzner creates or reuses the server; SSH checks and bootstraps the host
|
|
87
|
+
3. **Build and package**: builds each site, packages a release tarball
|
|
88
|
+
4. **Release**: copies over SSH, installs systemd units, restarts, preserves the prior release
|
|
89
|
+
5. **Gateway**: writes `/etc/rpx/sites.d/<slug>.json`
|
|
90
|
+
6. **DNS and TLS**: reconciles A records and issues certificates, when publishing is allowed
|
|
91
|
+
|
|
92
|
+
## The LAN rule (`provider: 'ssh'` only)
|
|
93
|
+
|
|
94
|
+
An SSH host on a private address publishes no DNS, requests no Let's Encrypt certificate, skips
|
|
95
|
+
the CDN and skips mail reconciliation. It prints the LAN URLs instead.
|
|
96
|
+
|
|
97
|
+
Private means RFC1918, `100.64/10` CGNAT, loopback, link-local, IPv6 unique-local or link-local,
|
|
98
|
+
a `.local` / `.internal` / `.lan` / `.intranet` / `.home.arpa` name, or a bare single-label
|
|
99
|
+
hostname. Publishing an A record for `192.168.1.42` would point every visitor's browser at
|
|
100
|
+
whatever occupies that address on their own network, and an ACME challenge cannot reach a host
|
|
101
|
+
the internet cannot route to.
|
|
102
|
+
|
|
103
|
+
To publish: give the host a routable address, forward 80 and 443 to it, declare a domain on a
|
|
104
|
+
site, and set `ssh.publicIp` (`'auto'` discovers it at deploy time). `TS_CLOUD_SSH_PUBLISH_DNS=1`
|
|
105
|
+
forces publishing on for a host behind a port forward the deploy cannot see; `0` forces it off.
|
|
106
|
+
|
|
53
107
|
## Deploy Hooks (cloud/deploy-script.ts)
|
|
54
108
|
|
|
55
109
|
```typescript
|
|
@@ -70,6 +124,8 @@ export default {
|
|
|
70
124
|
|
|
71
125
|
## Deployment Modes
|
|
72
126
|
|
|
127
|
+
Modes apply to the AWS target.
|
|
128
|
+
|
|
73
129
|
### Server Mode (default)
|
|
74
130
|
- EC2 instances with ALB
|
|
75
131
|
- Best for: full-stack apps, WebSocket support, persistent connections
|
|
@@ -83,8 +139,10 @@ export default {
|
|
|
83
139
|
## First Deployment Checklist
|
|
84
140
|
|
|
85
141
|
```bash
|
|
86
|
-
# 1. Configure
|
|
87
|
-
buddy configure:aws
|
|
142
|
+
# 1. Configure the provider
|
|
143
|
+
buddy configure:aws # aws
|
|
144
|
+
# hetzner: set HCLOUD_TOKEN
|
|
145
|
+
# ssh: buddy server:doctor, then buddy server:setup
|
|
88
146
|
|
|
89
147
|
# 2. Generate app key
|
|
90
148
|
buddy key:generate
|
|
@@ -96,25 +154,40 @@ buddy env:set APP_ENV production
|
|
|
96
154
|
# 4. Review cloud config
|
|
97
155
|
# Edit config/cloud.ts
|
|
98
156
|
|
|
99
|
-
# 5.
|
|
157
|
+
# 5. Preview, then deploy
|
|
158
|
+
buddy deploy --dry-run
|
|
100
159
|
buddy deploy
|
|
101
160
|
```
|
|
102
161
|
|
|
103
162
|
## CLI Commands
|
|
104
163
|
|
|
105
164
|
```bash
|
|
106
|
-
buddy deploy
|
|
165
|
+
buddy deploy [env] # deploy; env is production | staging | development
|
|
166
|
+
buddy deploy --prod # same, by flag (--staging, --dev)
|
|
107
167
|
buddy deploy --dry-run # preview without changing anything
|
|
168
|
+
buddy deploy --site <name> # deploy one site to the existing server
|
|
169
|
+
buddy deploy --domain <d> # override the domain this deploy publishes
|
|
170
|
+
buddy deploy --yes # skip confirmation (required in CI)
|
|
171
|
+
buddy deploy:rollback [site] # activate a preserved release (--env, --to, --dry-run)
|
|
172
|
+
buddy server:doctor # preflight an ssh host
|
|
173
|
+
buddy server:setup # adopt and bootstrap an ssh host
|
|
108
174
|
buddy cloud --diff # preview infrastructure changes before deploying
|
|
109
175
|
buddy cloud --ssh # SSH into deployed server
|
|
110
|
-
buddy cloud:remove # tear down infrastructure
|
|
176
|
+
buddy cloud:remove # tear down infrastructure (alias: undeploy)
|
|
111
177
|
buddy cloud:cleanup # clean retained resources
|
|
112
178
|
```
|
|
113
179
|
|
|
114
180
|
## Gotchas
|
|
115
|
-
-
|
|
181
|
+
- Check `cloud.provider` before advising anything provider-specific. Half the AWS advice is wrong
|
|
182
|
+
for a Hetzner or SSH box, and vice versa.
|
|
183
|
+
- `buddy deploy --prod` from a non-interactive shell refuses without `--yes`, rather than hanging
|
|
184
|
+
on the confirmation prompt.
|
|
116
185
|
- APP_KEY must be colon-separated format (validated during deployment)
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
|
|
186
|
+
- `buddy cloud --diff` shows changes BEFORE deploying, and only for AWS
|
|
187
|
+
- Deploy hooks run in the deployment process on your machine, not on the target server
|
|
188
|
+
- On Hetzner and SSH, a project's `/etc/rpx/sites.d/<slug>.json` is replaced wholesale, so
|
|
189
|
+
`project.slug` must be unique on a shared box and the config must declare every domain that
|
|
190
|
+
fragment serves
|
|
191
|
+
- `.env.<environment>` is decrypted at deploy time and shipped as every site's `.env`, so declare
|
|
192
|
+
`tenants` in `config/cloud.ts` on a shared box to strip other tenants' keys
|
|
193
|
+
- For infrastructure details, see the `stacks-cloud` skill
|
|
@@ -11,7 +11,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
11
11
|
## Key Paths
|
|
12
12
|
- Core ORM package: `storage/framework/core/orm/src/`
|
|
13
13
|
- ORM implementation: `storage/framework/orm/`
|
|
14
|
-
- Model definitions: `storage/framework/defaults/app/Models/` (
|
|
14
|
+
- Model definitions: `storage/framework/defaults/app/Models/` (97 models)
|
|
15
15
|
- Application models: `app/Models/`
|
|
16
16
|
- Default model templates: `storage/framework/defaults/app/Models/`
|
|
17
17
|
- ORM type globals: `storage/framework/types/orm-globals.d.ts`
|
|
@@ -86,7 +86,7 @@ All model CRUD events: `model:created`, `model:updated`, `model:deleted`
|
|
|
86
86
|
|
|
87
87
|
- **Content**: author, post, page
|
|
88
88
|
- **Core**: user, activity, campaign, comment, email-list, notification, social-post, subscription, tag
|
|
89
|
-
- **Commerce (
|
|
89
|
+
- **Commerce (36 models)**: cart, cart-item, category, coupon, customer, order, order-item, payment, product, product-variant, review, shipping-method, shipping-rate, tax-rate, transaction, gift-card, license-key, and more
|
|
90
90
|
|
|
91
91
|
All payloads are `Record<string, any>`.
|
|
92
92
|
|
|
@@ -13,7 +13,7 @@ project `AGENTS.md`, a doc reached by a pointer. The packaging differs, the
|
|
|
13
13
|
writing does not. The same levers make each one predictable, because the agent
|
|
14
14
|
takes the same *process* every run rather than producing the same output.
|
|
15
15
|
|
|
16
|
-
Stacks ships
|
|
16
|
+
Stacks ships 115 skills and expects projects to add their own under
|
|
17
17
|
`app/Skills/`, so this is the skill that keeps that set from turning to sludge.
|
|
18
18
|
The Stacks-specific mechanics (frontmatter, invocation, the override model,
|
|
19
19
|
what `buddy setup:ai` does with the result) are in
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
|
-
import { Request } from '@stacksjs/orm'
|
|
4
3
|
import { response } from '@stacksjs/router'
|
|
5
4
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
5
|
import {
|
|
7
|
-
buildWebAnalytics,
|
|
8
6
|
normalizeAnalyticsRange,
|
|
9
7
|
normalizeAnalyticsScope,
|
|
10
|
-
requestAnalyticsRow,
|
|
11
8
|
} from './request-analytics'
|
|
9
|
+
import { readDashboardWebAnalytics } from './web-analytics-provider'
|
|
12
10
|
|
|
13
11
|
export default new Action({
|
|
14
12
|
name: 'WebAnalyticsAction',
|
|
@@ -29,10 +27,7 @@ export default new Action({
|
|
|
29
27
|
}, 422)
|
|
30
28
|
}
|
|
31
29
|
try {
|
|
32
|
-
|
|
33
|
-
const rows = records.map(requestAnalyticsRow)
|
|
34
|
-
|
|
35
|
-
return buildWebAnalytics(rows, range, new Date(), scope)
|
|
30
|
+
return await readDashboardWebAnalytics({ range, scope })
|
|
36
31
|
}
|
|
37
32
|
catch (error) {
|
|
38
33
|
return dashboardOperationalError(error, 'Web analytics records could not be read.', 'WebAnalyticsAction')
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { DashboardProviderUnavailable } from '../dashboard-provider'
|
|
2
|
+
import type { AnalyticsRange, AnalyticsScope } from './request-analytics'
|
|
3
|
+
import { Request } from '@stacksjs/orm'
|
|
4
|
+
import { HQ_READ_UNAVAILABLE, readThroughProvider, resolveDashboardDriver } from '../dashboard-provider'
|
|
5
|
+
import { buildWebAnalytics, requestAnalyticsRow } from './request-analytics'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where the web Analytics section reads from.
|
|
9
|
+
*
|
|
10
|
+
* The local provider aggregates this application's own recorded requests, and
|
|
11
|
+
* it keeps the read exactly as `WebAnalyticsAction` performed it, including the
|
|
12
|
+
* ten thousand row ceiling. That ceiling is applied by id before the range
|
|
13
|
+
* filter runs, so a busy application reports on its ten thousand most recent
|
|
14
|
+
* requests rather than on the window that was asked for. That is existing
|
|
15
|
+
* behaviour, it is load bearing for the current numbers, and changing it here
|
|
16
|
+
* would make this refactor impossible to verify as behaviour preserving. It is
|
|
17
|
+
* worth fixing on its own, against its own tests.
|
|
18
|
+
*
|
|
19
|
+
* Only web analytics is seamed. Sales and marketing analytics aggregate orders
|
|
20
|
+
* and campaigns from this application's own tables, which no hosted analytics
|
|
21
|
+
* product has any view of.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const ROW_CEILING = 10_000
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The exact shape `buildWebAnalytics` returns, taken from the function itself.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately derived rather than declared. Writing this shape out by hand and
|
|
30
|
+
* annotating the builder with it would narrow `source` from the literal
|
|
31
|
+
* `'requests'` to `string` and would re-type the two empty arrays it returns,
|
|
32
|
+
* which is a visible type change in a refactor that is supposed to have none.
|
|
33
|
+
*/
|
|
34
|
+
export type DashboardWebAnalyticsPayload
|
|
35
|
+
= ReturnType<typeof buildWebAnalytics> & Partial<DashboardProviderUnavailable>
|
|
36
|
+
|
|
37
|
+
export interface DashboardWebAnalyticsQuery {
|
|
38
|
+
range: AnalyticsRange
|
|
39
|
+
scope: AnalyticsScope
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DashboardWebAnalyticsProvider {
|
|
43
|
+
webAnalytics: (query: DashboardWebAnalyticsQuery) => Promise<DashboardWebAnalyticsPayload>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An empty analytics payload, carrying the reason there is nothing to show.
|
|
48
|
+
*
|
|
49
|
+
* Built by running the real aggregation over no rows, so every key, every
|
|
50
|
+
* zero and every placeholder string is whatever the section already renders
|
|
51
|
+
* for an application that has served no traffic. A hand written empty object
|
|
52
|
+
* would drift from that the first time the builder gains a field.
|
|
53
|
+
*/
|
|
54
|
+
export function emptyWebAnalyticsPayload(
|
|
55
|
+
reason: string,
|
|
56
|
+
query: DashboardWebAnalyticsQuery,
|
|
57
|
+
): DashboardWebAnalyticsPayload {
|
|
58
|
+
return {
|
|
59
|
+
...buildWebAnalytics([], query.range, new Date(), query.scope),
|
|
60
|
+
unavailable: reason,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isWebAnalyticsPayload(payload: unknown): boolean {
|
|
65
|
+
if (!payload || typeof payload !== 'object')
|
|
66
|
+
return false
|
|
67
|
+
const candidate = payload as Partial<DashboardWebAnalyticsPayload>
|
|
68
|
+
return Boolean(candidate.overview)
|
|
69
|
+
&& Array.isArray(candidate.traffic)
|
|
70
|
+
&& Array.isArray(candidate.pages)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This application's own recorded requests.
|
|
75
|
+
*
|
|
76
|
+
* Not wrapped in a catch, so a local read failure still reaches the action's
|
|
77
|
+
* operational error exactly as it did before the seam existed.
|
|
78
|
+
*/
|
|
79
|
+
export const localWebAnalyticsProvider: DashboardWebAnalyticsProvider = {
|
|
80
|
+
async webAnalytics(query: DashboardWebAnalyticsQuery): Promise<DashboardWebAnalyticsPayload> {
|
|
81
|
+
const records = await Request.orderByDesc('id').limit(ROW_CEILING).get()
|
|
82
|
+
const rows = records.map(requestAnalyticsRow)
|
|
83
|
+
|
|
84
|
+
return buildWebAnalytics(rows, query.range, new Date(), query.scope)
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Reads the web Analytics section through whichever provider config selects. */
|
|
89
|
+
export async function readDashboardWebAnalytics(
|
|
90
|
+
query: DashboardWebAnalyticsQuery,
|
|
91
|
+
): Promise<DashboardWebAnalyticsPayload> {
|
|
92
|
+
const driver = await resolveDashboardDriver('analytics')
|
|
93
|
+
|
|
94
|
+
if (driver.name === 'local')
|
|
95
|
+
return localWebAnalyticsProvider.webAnalytics(query)
|
|
96
|
+
|
|
97
|
+
return readThroughProvider(
|
|
98
|
+
'analytics',
|
|
99
|
+
reason => emptyWebAnalyticsPayload(reason, query),
|
|
100
|
+
async () => emptyWebAnalyticsPayload(HQ_READ_UNAVAILABLE, query),
|
|
101
|
+
isWebAnalyticsPayload,
|
|
102
|
+
)
|
|
103
|
+
}
|