@stacksjs/defaults 0.72.63 → 0.72.66
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.
|
@@ -276,14 +276,31 @@ Supported webhook event types: `payment_intent.succeeded`, `payment_intent.payme
|
|
|
276
276
|
|
|
277
277
|
### Setup Products from SaaS Config
|
|
278
278
|
|
|
279
|
+
Prefer the command; it is what an app author can discover:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
buddy stripe:setup --dry-run # report the plan of record, write nothing
|
|
283
|
+
buddy stripe:setup # apply it
|
|
284
|
+
```
|
|
285
|
+
|
|
279
286
|
```typescript
|
|
280
|
-
import { createStripeProduct } from '@stacksjs/payments'
|
|
287
|
+
import { createStripeProduct, formatSetupReport } from '@stacksjs/payments'
|
|
281
288
|
|
|
282
|
-
const result = await createStripeProduct()
|
|
283
|
-
|
|
289
|
+
const result = await createStripeProduct({ dryRun: true })
|
|
290
|
+
if (!result.isErr)
|
|
291
|
+
console.log(formatSetupReport(result.value).join('\n'))
|
|
284
292
|
```
|
|
285
293
|
|
|
286
|
-
This iterates `saas.plans
|
|
294
|
+
This iterates `saas.plans` and reconciles each one against the live account: a
|
|
295
|
+
product is matched by name and reused, and each pricing option is matched by its
|
|
296
|
+
`lookup_key` (from the `key` field). Prices are immutable in Stripe, so a changed
|
|
297
|
+
amount is applied by creating a new price with `transfer_lookup_key: true`, which
|
|
298
|
+
moves the key atomically and leaves the superseded price active so existing
|
|
299
|
+
subscriptions keep billing.
|
|
300
|
+
|
|
301
|
+
Re-running is safe and converges. It does not use `products.search` on purpose:
|
|
302
|
+
that index is eventually consistent, so a second run inside the lag window would
|
|
303
|
+
find nothing and create a duplicate.
|
|
287
304
|
|
|
288
305
|
### Utility Functions
|
|
289
306
|
|
package/bootstrap.ts
CHANGED
|
@@ -134,7 +134,27 @@ if (mounts('dashboard', feature('dashboard'))) {
|
|
|
134
134
|
await route.register(frameworkPath('defaults/routes/dashboard-api.ts'))
|
|
135
135
|
// The dev dashboard boots through this file rather than the application
|
|
136
136
|
// router's importRoutes() path, so load model-declared useApi routes here too.
|
|
137
|
-
|
|
137
|
+
//
|
|
138
|
+
// Package first, vendored second, matching importRoutes() and start.ts. This
|
|
139
|
+
// line used to import the vendored copy only, and nothing re-vendors that
|
|
140
|
+
// file: `@stacksjs/orm` publishes `dist/routes.js` and no `routes.ts`, so an
|
|
141
|
+
// app kept whatever generator its copy froze at however often it upgraded.
|
|
142
|
+
// An old enough copy compares route paths literally, so a generated
|
|
143
|
+
// `PATCH /api/sites/{id}` does not recognise a hand-written
|
|
144
|
+
// `/api/sites/{siteId}` as the same endpoint and registers alongside it,
|
|
145
|
+
// and the hand-written handler's authorization check stops running
|
|
146
|
+
// (stacksjs/stacks#2364).
|
|
147
|
+
//
|
|
148
|
+
// Held in a variable so the specifier resolves at runtime rather than while
|
|
149
|
+
// transpiling, where an unresolvable literal would fail this module instead
|
|
150
|
+
// of throwing where it can be caught.
|
|
151
|
+
const ormRoutesPackage = '@stacksjs/orm/routes'
|
|
152
|
+
try {
|
|
153
|
+
await import(ormRoutesPackage)
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
await import(frameworkPath('orm/routes.ts'))
|
|
157
|
+
}
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
// Email webhook + unsubscribe routes (stacksjs/stacks#1881, #1880).
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.66",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@iconify-json/f7": "^1.2.2",
|
|
53
53
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
54
|
-
"@stacksjs/mobile": "^0.72.
|
|
54
|
+
"@stacksjs/mobile": "^0.72.66",
|
|
55
55
|
"@stacksjs/sanitizer": "^0.2.113"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
|
|
3
|
+
# Push to deploy.
|
|
4
|
+
#
|
|
5
|
+
# Gated on CI rather than firing on the push itself: `buddy deploy` activates a
|
|
6
|
+
# release atomically, so the failure mode of deploying an untested commit is a
|
|
7
|
+
# live site running it, not a red check somebody notices later.
|
|
8
|
+
on:
|
|
9
|
+
workflow_run:
|
|
10
|
+
workflows: [CI]
|
|
11
|
+
types: [completed]
|
|
12
|
+
branches: [main]
|
|
13
|
+
|
|
14
|
+
# Ship a specific commit without pushing, and reach environments that no
|
|
15
|
+
# branch maps to.
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
inputs:
|
|
18
|
+
environment:
|
|
19
|
+
description: Which environment to deploy
|
|
20
|
+
type: choice
|
|
21
|
+
options: [production, staging, development]
|
|
22
|
+
default: production
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: deploy-${{ github.event.inputs.environment || 'production' }}
|
|
26
|
+
# Never cancel a deploy in flight. Half of one is a release uploaded but not
|
|
27
|
+
# activated, or services restarted against the previous release's env.
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
deploy:
|
|
32
|
+
# `workflow_run` fires for failed runs too — that is the point of the
|
|
33
|
+
# gate, so check the conclusion.
|
|
34
|
+
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
# Attach a GitHub Environment to get required reviewers and to scope these
|
|
37
|
+
# secrets to it.
|
|
38
|
+
environment: ${{ github.event.inputs.environment || 'production' }}
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout
|
|
42
|
+
uses: actions/checkout@v6
|
|
43
|
+
with:
|
|
44
|
+
# The exact commit CI approved, not whatever main points at now.
|
|
45
|
+
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
|
|
46
|
+
|
|
47
|
+
- name: Setup Bun
|
|
48
|
+
uses: oven-sh/setup-bun@v2
|
|
49
|
+
|
|
50
|
+
- name: Setup Pantry
|
|
51
|
+
uses: home-lang/pantry/packages/action@main
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: bun install --frozen-lockfile
|
|
55
|
+
|
|
56
|
+
# The deploy talks to the server as root over SSH with BatchMode on, so
|
|
57
|
+
# the key has to be in an agent before it runs — there is no prompt to
|
|
58
|
+
# fall back to.
|
|
59
|
+
- name: Load the deploy key
|
|
60
|
+
uses: webfactory/ssh-agent@v0.9.0
|
|
61
|
+
with:
|
|
62
|
+
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
|
|
63
|
+
|
|
64
|
+
- name: Deploy
|
|
65
|
+
run: ./buddy deploy --${{ github.event.inputs.environment == 'staging' && 'staging' || github.event.inputs.environment == 'development' && 'dev' || 'prod' }} --yes
|
|
66
|
+
env:
|
|
67
|
+
# Decrypts .env.production. Without it the encrypted values silently
|
|
68
|
+
# fall back to defaults, which is how an app deploys with the wrong
|
|
69
|
+
# APP_KEY and invalidates every session it had.
|
|
70
|
+
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
|
|
71
|
+
# DNS records are written through the registrar that administers the
|
|
72
|
+
# zone, declared in config/cloud.ts. Keep these in the environment
|
|
73
|
+
# even when they are also in .env.production — a missing one now
|
|
74
|
+
# fails loudly instead of reaching for another provider's keys.
|
|
75
|
+
PORKBUN_API_KEY: ${{ secrets.PORKBUN_API_KEY }}
|
|
76
|
+
PORKBUN_SECRET_KEY: ${{ secrets.PORKBUN_SECRET_KEY }}
|
|
77
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|