spaps 0.7.2 → 0.7.4
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_TOOLS.json +10 -11
- package/README.md +267 -110
- package/assets/local-runtime/Dockerfile +28 -0
- package/assets/local-runtime/alembic/env.py +101 -0
- package/assets/local-runtime/alembic/path_bootstrap.py +71 -0
- package/assets/local-runtime/alembic/versions/000000000001_baseline_consolidated_schema.py +1076 -0
- package/assets/local-runtime/alembic/versions/000000000002_fix_column_types_to_match_prod.py +83 -0
- package/assets/local-runtime/alembic/versions/000000000003_fix_email_template_key_uniqueness.py +49 -0
- package/assets/local-runtime/alembic/versions/000000000004_add_hold_duration_minutes_to_dayrate_config.py +30 -0
- package/assets/local-runtime/alembic/versions/000000000005_resource_scoped_entitlements.py +77 -0
- package/assets/local-runtime/alembic/versions/000000000006_cfo_rbac_add_is_admin.py +37 -0
- package/assets/local-runtime/alembic/versions/000000000007_agent_approvals.py +158 -0
- package/assets/local-runtime/alembic/versions/000000000008_add_company_id_to_cfo_connections.py +35 -0
- package/assets/local-runtime/alembic/versions/000000000009_tx_signing.py +62 -0
- package/assets/local-runtime/alembic/versions/000000000010_affiliate_referrals.py +235 -0
- package/assets/local-runtime/alembic/versions/000000000011_checkin_call_booking.py +137 -0
- package/assets/local-runtime/alembic/versions/000000000012_subscription_application_scoping.py +55 -0
- package/assets/local-runtime/alembic/versions/000000000013_refresh_token_anomaly_context.py +61 -0
- package/assets/local-runtime/alembic/versions/000000000014_buildooor_dayrate_hire_schedule.py +39 -0
- package/assets/local-runtime/alembic/versions/000000000015_support_telemetry_platform.py +112 -0
- package/assets/local-runtime/alembic/versions/000000000016_issue_reporting_platform.py +54 -0
- package/assets/local-runtime/alembic/versions/000000000017_issue_reporting_platform_import_tracking.py +44 -0
- package/assets/local-runtime/alembic/versions/000000000018_authorization_policy_engine.py +76 -0
- package/assets/local-runtime/alembic.ini +47 -0
- package/assets/local-runtime/docker-compose.yml +61 -0
- package/assets/local-runtime/manifest.json +8 -0
- package/assets/local-runtime/scripts/container-entrypoint.sh +13 -0
- package/assets/local-runtime/scripts/fetch-prod-db.sh +112 -0
- package/assets/local-runtime/scripts/run-migrations.sh +96 -0
- package/package.json +5 -4
- package/src/ai-helper.js +176 -234
- package/src/ai-tool-spec.js +52 -20
- package/src/auth/api-key.js +119 -0
- package/src/auth/client-id.js +136 -0
- package/src/auth/client.js +169 -0
- package/src/auth/credentials.js +110 -0
- package/src/auth/device-flow.js +159 -0
- package/src/auth/env.js +57 -0
- package/src/auth/handlers.js +462 -0
- package/src/auth/http.js +74 -0
- package/src/cli-dispatcher.js +155 -24
- package/src/docs-system.js +7 -7
- package/src/error-handler.js +42 -0
- package/src/fixture-kernel.js +1143 -0
- package/src/handlers.js +252 -15
- package/src/help-system.js +3 -1
- package/src/local-runtime.js +258 -0
- package/src/local-server.js +597 -199
- package/src/project-scaffolder.js +441 -0
package/AI_TOOLS.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Auth + payments via SPAPS
|
|
3
|
+
"version": "0.7.4",
|
|
4
|
+
"description": "Auth + payments via SPAPS. Runtime auth requirements come from /health/local-mode or `npx spaps tools --json`.",
|
|
5
5
|
"base_url": "http://localhost:3301",
|
|
6
6
|
"auth": {
|
|
7
|
-
"local_mode":
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
"local_mode": null,
|
|
8
|
+
"mode_source": "/health/local-mode",
|
|
9
|
+
"api_key_required_when_local_mode_disabled": true,
|
|
10
|
+
"header": "X-API-Key",
|
|
11
|
+
"env": "SPAPS_API_KEY"
|
|
12
12
|
},
|
|
13
13
|
"tools": [
|
|
14
14
|
{
|
|
15
15
|
"name": "login",
|
|
16
|
-
"description": "Login with email/password.
|
|
16
|
+
"description": "Login with email/password. Send X-API-Key when local mode is disabled.",
|
|
17
17
|
"method": "POST",
|
|
18
18
|
"path": "/api/auth/login",
|
|
19
19
|
"parameters": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"name": "list_products",
|
|
74
|
-
"description": "List products
|
|
74
|
+
"description": "List products exposed by the SPAPS server.",
|
|
75
75
|
"method": "GET",
|
|
76
76
|
"path": "/api/stripe/products",
|
|
77
77
|
"parameters": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
"name": "request_magic_link",
|
|
87
|
-
"description": "Send a magic link for passwordless login
|
|
87
|
+
"description": "Send a magic link for passwordless login.",
|
|
88
88
|
"method": "POST",
|
|
89
89
|
"path": "/api/auth/magic-link",
|
|
90
90
|
"parameters": {
|
|
@@ -111,4 +111,3 @@
|
|
|
111
111
|
}
|
|
112
112
|
]
|
|
113
113
|
}
|
|
114
|
-
|
package/README.md
CHANGED
|
@@ -1,223 +1,380 @@
|
|
|
1
1
|
# spaps
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Test SPAPS auth in a new app before you build auth backend infrastructure.
|
|
4
|
+
|
|
5
|
+
`spaps` is the shortest path from "I have a new frontend" to "this app can hit authenticated SPAPS routes locally." It wraps the local SPAPS server, tells you what auth mode the server is actually in, scaffolds starter code, and provisions a real local application when the server requires one.
|
|
4
6
|
|
|
5
7
|
## TL;DR
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
### The problem
|
|
10
|
+
|
|
11
|
+
You want to wire auth into a new app, but you do not want to design and ship your own auth backend first.
|
|
12
|
+
|
|
13
|
+
### The shortcut
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
Run SPAPS locally, inspect the current auth mode, then either use local mode directly or provision a real local app with one command. The CLI tells you which mode the server is actually in, so you do not have to guess.
|
|
10
16
|
|
|
11
17
|
### Why Use `spaps`?
|
|
12
18
|
|
|
13
|
-
|
|
|
19
|
+
| Need | `spaps` gives you |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| "Can I test auth in this app today?" | `spaps local`, `status`, and `quickstart` |
|
|
22
|
+
| "Do I need a real app key or not?" | Runtime truth from `/health/local-mode` |
|
|
23
|
+
| "Can you scaffold the wiring for me?" | `spaps create <name> --template ...` |
|
|
24
|
+
| "I want a working local app, not a fake contract" | Self-service provisioning when `SELF_SERVICE_PASSWORD` is available |
|
|
25
|
+
| "If provisioning fails, tell me exactly why" | Explicit `scaffold_only` fallback and warnings |
|
|
26
|
+
| "I want persistent local personas, roles, and entitlements in this repo" | `spaps fixtures ...` and a repo-local `.spaps/` kernel |
|
|
27
|
+
|
|
28
|
+
## No Backend Yet? Start Here
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx spaps local
|
|
32
|
+
npx spaps quickstart --json | jq '.auth'
|
|
33
|
+
SELF_SERVICE_PASSWORD=your-password npx spaps create demo-app --template react
|
|
34
|
+
cd demo-app
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
What happens next depends on the server mode:
|
|
39
|
+
|
|
40
|
+
| Server state | What `spaps` does | What you do |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `local_mode_active: true` | Exposes local-mode hints and test personas | Use the starter against `localhost`; no app key provisioning required |
|
|
43
|
+
| `local_mode_active: false` and `SELF_SERVICE_PASSWORD` is set | Provisions a real local SPAPS app and writes the working key into `.env.local` | Start wiring auth flows in your app immediately |
|
|
44
|
+
| Server unreachable or no self-service password | Scaffolds files only and tells you why provisioning was skipped | Bring the server up or rerun with `SELF_SERVICE_PASSWORD` later |
|
|
45
|
+
|
|
46
|
+
This package targets `Node.js >=22`.
|
|
47
|
+
|
|
48
|
+
## Local Runtime Modes
|
|
49
|
+
|
|
50
|
+
`spaps local` now has two runtime paths:
|
|
51
|
+
|
|
52
|
+
| Runtime path | When it is used | What it does |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `repo` | You are running from the `sweet-potato` checkout | Uses the repo's Docker Compose stack and source-mounted assets |
|
|
55
|
+
| `bundle` | You installed `spaps` from npm elsewhere | Uses bundled Docker assets plus the published `spaps-server-quickstart` package |
|
|
56
|
+
|
|
57
|
+
The default is `auto`: use repo assets when they exist, otherwise fall back to the bundled runtime. The bundled runtime defaults to `SPAPS_LOCAL_MODE=true`, so RBAC fixtures and test personas work out of the box.
|
|
58
|
+
|
|
59
|
+
## Local Data Sources
|
|
60
|
+
|
|
61
|
+
`spaps local` separates runtime selection from base data selection:
|
|
62
|
+
|
|
63
|
+
| Data source | What it does |
|
|
14
64
|
| --- | --- |
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
65
|
+
| `empty` | Boot an empty local DB and let migrations create schema |
|
|
66
|
+
| `prod-cache` | Restore the cached SPAPS production dump before the API starts, then reuse it until the cached dump changes |
|
|
67
|
+
| `prod-fresh` | Force a fresh production dump fetch, restore it, then boot the API |
|
|
68
|
+
| `--from-backup <path>` | Restore a specific `.sql.gz` dump file before boot |
|
|
69
|
+
|
|
70
|
+
Restore order is always: restore base dump first, then let the API container run migrations to head on top of it.
|
|
19
71
|
|
|
20
|
-
##
|
|
72
|
+
## Install
|
|
21
73
|
|
|
22
|
-
|
|
74
|
+
Run it without installing:
|
|
23
75
|
|
|
24
76
|
```bash
|
|
25
77
|
npx spaps help
|
|
26
78
|
```
|
|
27
79
|
|
|
28
|
-
|
|
80
|
+
Install globally:
|
|
29
81
|
|
|
30
82
|
```bash
|
|
31
83
|
npm install -g spaps
|
|
32
84
|
```
|
|
33
85
|
|
|
34
|
-
|
|
86
|
+
Add it to a project:
|
|
35
87
|
|
|
36
88
|
```bash
|
|
37
89
|
npm install spaps
|
|
38
90
|
```
|
|
39
91
|
|
|
40
|
-
## Quick
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
| Step | Command | Why |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| 1 | `npx spaps local` | Start the local SPAPS stack |
|
|
97
|
+
| 1b | `npx spaps local --data-source prod-cache` | Start the local stack with a cached prod-backed base DB |
|
|
98
|
+
| 2 | `npx spaps status --json` | Confirm the server is reachable |
|
|
99
|
+
| 3 | `npx spaps quickstart --json` | See whether local mode is active or a real app key is required |
|
|
100
|
+
| 4 | `npx spaps create my-app --template react` | Scaffold a starter and, when possible, provision a real local app |
|
|
101
|
+
| 5 | `npx spaps tools --json` | Export the current AI tool contract for agents or tests |
|
|
102
|
+
| 6 | `npx spaps fixtures apply` | Materialize repo-local personas into Playwright/browser artifacts |
|
|
103
|
+
|
|
104
|
+
## CLI Surface
|
|
105
|
+
|
|
106
|
+
| Command | Purpose | Common flags |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `spaps local [stop]` | Start or stop the local server workflow | `--port`, `--runtime-dir`, `--runtime-source`, `--data-source`, `--detach`, `--fresh`, `--from-backup`, `--open`, `--json` |
|
|
109
|
+
| `spaps status` | Check whether the local server is running | `--port`, `--json` |
|
|
110
|
+
| `spaps quickstart` | Print quick-start instructions | `--port`, `--json` |
|
|
111
|
+
| `spaps init` | Create a starter `.env.local` | `--json` |
|
|
112
|
+
| `spaps create <name>` | Scaffold a SPAPS starter project directory and try local provisioning | `--template`, `--dir`, `--port`, `--force`, `--json` |
|
|
113
|
+
| `spaps fixtures <subcommand>` | Manage repo-local `.spaps` auth fixtures | `--dir`, `--port`, `--base-url`, `--persona`, `--format`, `--force`, `--json` |
|
|
114
|
+
| `spaps docs` | Browse or search bundled docs | `--interactive`, `--search`, `--json` |
|
|
115
|
+
| `spaps tools` | Emit the AI tool spec | `--port`, `--format`, `--json` |
|
|
116
|
+
| `spaps doctor` | Diagnose local environment problems | `--port`, `--stripe`, `--json` |
|
|
117
|
+
|
|
118
|
+
Example command usage:
|
|
41
119
|
|
|
42
120
|
```bash
|
|
43
|
-
|
|
44
|
-
|
|
121
|
+
spaps local --port 3400 --detach
|
|
122
|
+
spaps local --runtime-source bundle --runtime-dir ./.skillbox/spaps-local
|
|
123
|
+
spaps local --runtime-source repo --data-source prod-cache
|
|
124
|
+
spaps local --runtime-source repo --fresh --data-source prod-fresh
|
|
125
|
+
spaps local --from-backup ~/.cache/spaps/db/prod.sql.gz
|
|
126
|
+
spaps status --json
|
|
127
|
+
spaps create my-app --template react
|
|
128
|
+
spaps login --json
|
|
129
|
+
spaps fixtures apply --base-url http://localhost:5173
|
|
130
|
+
spaps fixtures storage-state --persona admin
|
|
131
|
+
spaps docs --search secure-messages
|
|
132
|
+
spaps doctor --stripe mock
|
|
133
|
+
spaps local stop
|
|
134
|
+
```
|
|
45
135
|
|
|
46
|
-
|
|
47
|
-
npx spaps status
|
|
136
|
+
## CLI Auth
|
|
48
137
|
|
|
49
|
-
|
|
50
|
-
npx spaps init
|
|
138
|
+
`spaps login` uses the active FastAPI device-flow contract:
|
|
51
139
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
140
|
+
- device authorization at `/api/cli/device/*`
|
|
141
|
+
- authenticated follow-up calls at `/api/auth/*`
|
|
142
|
+
- standard SPAPS response envelopes
|
|
143
|
+
|
|
144
|
+
The CLI no longer assumes a built-in `spaps-cli` application slug. Resolve the client id in this order:
|
|
55
145
|
|
|
56
|
-
|
|
146
|
+
- `--client-id <app-slug>`
|
|
147
|
+
- `SPAPS_CLI_CLIENT_ID`
|
|
148
|
+
- repo-local `spaps.app.json`
|
|
149
|
+
- repo-local `.spaps/app.json`
|
|
150
|
+
- `/health/local-mode` test application metadata
|
|
57
151
|
|
|
58
|
-
|
|
152
|
+
Authenticated follow-up calls such as `whoami`, `logout`, and token refresh still need a normal SPAPS app key when the server is not in local mode. The CLI resolves that key from:
|
|
59
153
|
|
|
60
|
-
|
|
154
|
+
- `SPAPS_API_KEY`
|
|
155
|
+
- `NEXT_PUBLIC_SPAPS_API_KEY`
|
|
156
|
+
- `VITE_SPAPS_API_KEY`
|
|
157
|
+
- repo-local `.env.local`
|
|
158
|
+
- repo-local `.env`
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
61
161
|
|
|
62
162
|
```bash
|
|
63
|
-
spaps
|
|
64
|
-
|
|
65
|
-
spaps
|
|
66
|
-
spaps local --open
|
|
67
|
-
spaps local stop
|
|
163
|
+
npx spaps login --client-id my-app
|
|
164
|
+
SPAPS_CLI_CLIENT_ID=my-app npx spaps login
|
|
165
|
+
VITE_SPAPS_API_KEY=spaps_pub_demo npx spaps whoami --json
|
|
68
166
|
```
|
|
69
167
|
|
|
70
|
-
|
|
168
|
+
Still reserved and not finished:
|
|
71
169
|
|
|
72
|
-
-
|
|
73
|
-
- `--detach`
|
|
74
|
-
- `--fresh`
|
|
75
|
-
- `--from-backup <path>`
|
|
76
|
-
- `--open`
|
|
77
|
-
- `--json`
|
|
170
|
+
- `spaps types`
|
|
78
171
|
|
|
79
|
-
|
|
172
|
+
## Create A Starter Project
|
|
80
173
|
|
|
81
|
-
|
|
82
|
-
spaps status
|
|
83
|
-
spaps status --port 3400
|
|
84
|
-
spaps status --json
|
|
85
|
-
```
|
|
174
|
+
`spaps create` ships a local-first starter kit rather than a full framework generator. It creates a new directory with:
|
|
86
175
|
|
|
87
|
-
|
|
176
|
+
- `spaps.app.json` for the machine-readable SPAPS app contract
|
|
177
|
+
- `.env.local` pointing at local SPAPS and, when available, a working template-appropriate key
|
|
178
|
+
- `package.json` with `spaps-sdk`
|
|
179
|
+
- a small template-specific integration starter
|
|
88
180
|
|
|
89
|
-
|
|
90
|
-
spaps quickstart
|
|
91
|
-
spaps quickstart --port 3400
|
|
92
|
-
spaps quickstart --json
|
|
93
|
-
```
|
|
181
|
+
When the local server is reachable and `SELF_SERVICE_PASSWORD` is set, `create` also provisions a real local SPAPS application. When that is not possible, it falls back to scaffold-only mode and tells you why.
|
|
94
182
|
|
|
95
|
-
|
|
183
|
+
Supported templates:
|
|
96
184
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
185
|
+
- `nextjs`
|
|
186
|
+
- `react`
|
|
187
|
+
- `node`
|
|
188
|
+
- `vanilla`
|
|
101
189
|
|
|
102
|
-
|
|
190
|
+
Examples:
|
|
103
191
|
|
|
104
192
|
```bash
|
|
105
|
-
spaps
|
|
106
|
-
spaps
|
|
107
|
-
spaps
|
|
108
|
-
spaps docs --json
|
|
193
|
+
npx spaps create my-app --template react
|
|
194
|
+
npx spaps create my-api --template node --dir ./services/my-api
|
|
195
|
+
SELF_SERVICE_PASSWORD=your-password npx spaps create my-app --template react
|
|
109
196
|
```
|
|
110
197
|
|
|
111
|
-
|
|
198
|
+
The generated starter is template-aware:
|
|
112
199
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
spaps
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
- browser templates write a publishable key when provisioning succeeds
|
|
201
|
+
- server templates write a server key when provisioning succeeds
|
|
202
|
+
- `spaps.app.json` records provisioning status and application id, but never stores raw keys
|
|
203
|
+
|
|
204
|
+
## Repo-Local Fixtures
|
|
205
|
+
|
|
206
|
+
Use `.spaps/` when you want persistent local personas for clicking around, Playwright, or app-level auth tests without inventing ad hoc storage keys in each repo.
|
|
119
207
|
|
|
120
|
-
|
|
208
|
+
`spaps fixtures init` creates:
|
|
209
|
+
|
|
210
|
+
- `.spaps/app.json` for runtime and browser target settings
|
|
211
|
+
- `.spaps/users.json` for personas and profile data
|
|
212
|
+
- `.spaps/roles.json` for RBAC grants
|
|
213
|
+
- `.spaps/entitlements.json` for entitlement grants
|
|
214
|
+
- `.spaps/browser/` for generated storage-state and header artifacts
|
|
215
|
+
|
|
216
|
+
Then run:
|
|
121
217
|
|
|
122
218
|
```bash
|
|
123
|
-
spaps
|
|
124
|
-
spaps
|
|
125
|
-
spaps doctor --stripe mock
|
|
126
|
-
spaps doctor --json
|
|
219
|
+
npx spaps fixtures apply --base-url http://localhost:5173
|
|
220
|
+
npx spaps fixtures storage-state --persona admin
|
|
127
221
|
```
|
|
128
222
|
|
|
129
|
-
|
|
223
|
+
What `apply` emits:
|
|
130
224
|
|
|
131
|
-
|
|
225
|
+
- `browser/<persona>.storage-state.json` for Playwright `storageState`
|
|
226
|
+
- `browser/<persona>.headers.json` for `extraHTTPHeaders`
|
|
227
|
+
- `browser/<persona>.context.json` with merged persona/runtime metadata
|
|
228
|
+
- `public/spaps-dev-auth.js` for frontend-only auth/RBAC dev mode
|
|
132
229
|
|
|
133
|
-
-
|
|
134
|
-
- `spaps types`
|
|
230
|
+
This does not try to import browser password-manager state. It writes deterministic, repo-local test artifacts instead.
|
|
135
231
|
|
|
136
|
-
|
|
232
|
+
If you want a human to launch the frontend and click around without a mock backend, include the generated bridge before your app boots:
|
|
137
233
|
|
|
138
|
-
|
|
234
|
+
```html
|
|
235
|
+
<script src="/spaps-dev-auth.js"></script>
|
|
236
|
+
```
|
|
139
237
|
|
|
140
|
-
|
|
238
|
+
That bridge does three things:
|
|
141
239
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
240
|
+
- seeds SDK-compatible localStorage keys like `sweet_potato_user` and `sweet_potato_access_token`
|
|
241
|
+
- intercepts `/api/auth/user`, `/api/auth/login`, `/api/auth/logout`, `/api/entitlements`, and `/api/entitlements/check`
|
|
242
|
+
- renders a tiny persona switcher so you can flip between `user`, `admin`, and `premium`
|
|
145
243
|
|
|
146
|
-
|
|
244
|
+
## Middleware Example
|
|
147
245
|
|
|
148
|
-
|
|
149
|
-
res.json({ ok: true });
|
|
150
|
-
});
|
|
246
|
+
The main module exports admin and permission helpers for Express-style apps.
|
|
151
247
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
248
|
+
```js
|
|
249
|
+
const express = require("express");
|
|
250
|
+
const { requireAdmin, requirePermission } = require("spaps");
|
|
251
|
+
|
|
252
|
+
const app = express();
|
|
253
|
+
const customAdmins = ["admin@example.com"];
|
|
254
|
+
|
|
255
|
+
app.get(
|
|
256
|
+
"/admin",
|
|
257
|
+
requireAdmin({ customAdmins }),
|
|
258
|
+
(_req, res) => {
|
|
259
|
+
res.json({ ok: true });
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
app.post(
|
|
264
|
+
"/admin/products",
|
|
265
|
+
requirePermission("manage_products", { customAdmins }),
|
|
266
|
+
(_req, res) => {
|
|
267
|
+
res.json({ created: true });
|
|
268
|
+
},
|
|
269
|
+
);
|
|
155
270
|
```
|
|
156
271
|
|
|
157
272
|
## What `spaps init` Writes
|
|
158
273
|
|
|
159
|
-
`spaps init` creates a `.env.local` with a local API URL starter and leaves existing
|
|
274
|
+
`spaps init` creates a `.env.local` file with a local API URL starter and leaves an existing file alone if one is already present.
|
|
160
275
|
|
|
161
276
|
## Troubleshooting
|
|
162
277
|
|
|
163
278
|
### Port already in use
|
|
164
279
|
|
|
165
|
-
Start on
|
|
280
|
+
Start on another port:
|
|
166
281
|
|
|
167
282
|
```bash
|
|
168
283
|
npx spaps local --port 3400
|
|
169
284
|
```
|
|
170
285
|
|
|
171
|
-
###
|
|
286
|
+
### I need deterministic portable runtime files
|
|
287
|
+
|
|
288
|
+
Pin the bundled runtime directory explicitly:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
npx spaps local --runtime-source bundle --runtime-dir ./.skillbox/spaps-local
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
This is the recommended path for managed environments such as skillbox.
|
|
295
|
+
|
|
296
|
+
### I need machine-readable output
|
|
172
297
|
|
|
173
|
-
Use `--json` on
|
|
298
|
+
Use `--json` on commands that support it, including `local`, `status`, `quickstart`, `init`, `fixtures`, `docs`, `tools`, and `doctor`.
|
|
174
299
|
|
|
175
|
-
###
|
|
300
|
+
### The local server is not responding
|
|
176
301
|
|
|
177
|
-
Check
|
|
302
|
+
Check current status first:
|
|
178
303
|
|
|
179
304
|
```bash
|
|
180
305
|
npx spaps status
|
|
181
306
|
```
|
|
182
307
|
|
|
183
|
-
|
|
308
|
+
If needed, restart with a clean boot:
|
|
184
309
|
|
|
185
310
|
```bash
|
|
186
311
|
npx spaps local --fresh
|
|
187
312
|
```
|
|
188
313
|
|
|
314
|
+
If you want the old "db=fresh from prod" behavior through the portable CLI:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npx spaps local --fresh --data-source prod-fresh
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
If you want to keep a cached prod-backed base DB around for day-to-day work:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npx spaps local --data-source prod-cache
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `spaps create` only scaffolded files
|
|
327
|
+
|
|
328
|
+
That means one of two things:
|
|
329
|
+
|
|
330
|
+
- the local server was unreachable
|
|
331
|
+
- the server is not in local mode and `SELF_SERVICE_PASSWORD` was missing or invalid
|
|
332
|
+
|
|
333
|
+
Check the current mode first:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
npx spaps quickstart --json
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
If the server requires provisioning, rerun with:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
SELF_SERVICE_PASSWORD=your-password npx spaps create my-app --template react --force
|
|
343
|
+
```
|
|
344
|
+
|
|
189
345
|
## Limitations
|
|
190
346
|
|
|
191
|
-
- The
|
|
192
|
-
- `create`
|
|
193
|
-
-
|
|
347
|
+
- The CLI is aimed at local workflows. It is not a full deployment or hosting tool.
|
|
348
|
+
- `create` does not run `create-next-app`, Vite, or Express generators for you; it scaffolds the SPAPS integration layer.
|
|
349
|
+
- `types` is still a placeholder today.
|
|
350
|
+
- The middleware ships a legacy default admin configuration in code; public-facing apps should pass explicit `customAdmins` instead of relying on that default.
|
|
194
351
|
|
|
195
352
|
## FAQ
|
|
196
353
|
|
|
197
|
-
###
|
|
354
|
+
### What is this package actually for?
|
|
355
|
+
|
|
356
|
+
Use it when you want to prove auth wiring in a new app before you build backend auth infrastructure yourself.
|
|
357
|
+
|
|
358
|
+
### Does this package include the TypeScript SDK?
|
|
198
359
|
|
|
199
|
-
No. The SDK is
|
|
360
|
+
No. The SDK is published separately as `spaps-sdk`.
|
|
200
361
|
|
|
201
|
-
### Can I use the CLI without
|
|
362
|
+
### Can I use the CLI without a global install?
|
|
202
363
|
|
|
203
364
|
Yes. Use `npx spaps ...`.
|
|
204
365
|
|
|
205
|
-
### Does `spaps init` overwrite
|
|
366
|
+
### Does `spaps init` overwrite an existing env file?
|
|
206
367
|
|
|
207
368
|
No. It skips `.env.local` if the file already exists.
|
|
208
369
|
|
|
209
370
|
### What does `spaps tools` output?
|
|
210
371
|
|
|
211
|
-
An OpenAI-style tool spec for the local SPAPS surface.
|
|
212
|
-
|
|
213
|
-
### Is the admin middleware available as an import?
|
|
214
|
-
|
|
215
|
-
Yes. The package exports the admin helpers from its main module and via the `./middleware` export path.
|
|
372
|
+
An OpenAI-style tool spec for the local SPAPS surface. The auth section is derived from `/health/local-mode` when the local server is reachable.
|
|
216
373
|
|
|
217
|
-
|
|
374
|
+
### Is the middleware available from a subpath?
|
|
218
375
|
|
|
219
|
-
|
|
376
|
+
Yes. You can import from the main module or `spaps/middleware`.
|
|
220
377
|
|
|
221
378
|
## License
|
|
222
379
|
|
|
223
|
-
|
|
380
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Portable SPAPS local runtime image.
|
|
2
|
+
# Built from bundled assets shipped inside the npm package.
|
|
3
|
+
|
|
4
|
+
FROM python:3.12-slim AS base
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
RUN apt-get update && \
|
|
9
|
+
apt-get install -y --no-install-recommends \
|
|
10
|
+
libpq-dev \
|
|
11
|
+
gcc \
|
|
12
|
+
curl \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
ARG SPAPS_SERVER_QUICKSTART_VERSION=0.5.0
|
|
16
|
+
RUN pip install --no-cache-dir "spaps-server-quickstart==${SPAPS_SERVER_QUICKSTART_VERSION}"
|
|
17
|
+
|
|
18
|
+
COPY alembic.ini /app/alembic.ini
|
|
19
|
+
COPY alembic/ /app/alembic/
|
|
20
|
+
COPY scripts/ /app/scripts/
|
|
21
|
+
|
|
22
|
+
ENV APP_ROOT=/app
|
|
23
|
+
ENV PYTHONUNBUFFERED=1
|
|
24
|
+
|
|
25
|
+
EXPOSE 8000
|
|
26
|
+
|
|
27
|
+
ENTRYPOINT ["/app/scripts/container-entrypoint.sh"]
|
|
28
|
+
CMD ["uvicorn", "spaps_server_quickstart.spaps_app:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alembic environment for SPAPS.
|
|
3
|
+
|
|
4
|
+
Imports all domain models from spaps_server_quickstart.domains so that
|
|
5
|
+
``Base.metadata`` contains every table. Alembic uses this metadata for
|
|
6
|
+
autogenerate and schema diffing.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from logging.config import fileConfig
|
|
14
|
+
|
|
15
|
+
from alembic import context
|
|
16
|
+
from sqlalchemy import engine_from_config, pool
|
|
17
|
+
|
|
18
|
+
# Ensure Alembic helper modules are importable, then prefer the active
|
|
19
|
+
# quickstart source tree (for local Docker this should be the live /app/src bind
|
|
20
|
+
# mount, not the packaged fallback copied into the image).
|
|
21
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
22
|
+
|
|
23
|
+
from path_bootstrap import ensure_quickstart_src_path # noqa: E402
|
|
24
|
+
|
|
25
|
+
ensure_quickstart_src_path(__file__, sys.path)
|
|
26
|
+
|
|
27
|
+
from spaps_server_quickstart.domains._db.base import Base # noqa: E402
|
|
28
|
+
|
|
29
|
+
# Phase 1+: import domain models here so Base.metadata picks them up
|
|
30
|
+
# from spaps_server_quickstart.domains.users.models import User # noqa: E402, F401
|
|
31
|
+
# from spaps_server_quickstart.domains.auth.models import ... # noqa: E402, F401
|
|
32
|
+
from spaps_server_quickstart.domains.approval_authz_prod_hardening.models import GovernanceAuthzAuditEvent # noqa: E402, F401
|
|
33
|
+
from spaps_server_quickstart.domains.support_telemetry_platform.models import SupportCase, SupportCaseEvent # noqa: E402, F401
|
|
34
|
+
from spaps_server_quickstart.domains.issue_reporting_platform.models import IssueReport # noqa: E402, F401
|
|
35
|
+
|
|
36
|
+
config = context.config
|
|
37
|
+
|
|
38
|
+
if config.config_file_name is not None:
|
|
39
|
+
fileConfig(config.config_file_name)
|
|
40
|
+
|
|
41
|
+
target_metadata = Base.metadata
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_database_url() -> str:
|
|
45
|
+
"""Resolve the database URL from environment or settings."""
|
|
46
|
+
url = os.environ.get("DATABASE_URL", "")
|
|
47
|
+
if url:
|
|
48
|
+
# Alembic runs synchronously — convert asyncpg to psycopg
|
|
49
|
+
if "asyncpg" in url:
|
|
50
|
+
url = url.replace("postgresql+asyncpg", "postgresql+psycopg")
|
|
51
|
+
return url
|
|
52
|
+
|
|
53
|
+
# Fallback: try loading from settings
|
|
54
|
+
try:
|
|
55
|
+
from spaps_server_quickstart.spaps_settings import get_spaps_settings
|
|
56
|
+
|
|
57
|
+
settings = get_spaps_settings()
|
|
58
|
+
return settings.sync_database_url
|
|
59
|
+
except Exception:
|
|
60
|
+
return "postgresql+psycopg://postgres:postgres@localhost:5432/spaps"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run_migrations_offline() -> None:
|
|
64
|
+
"""Run migrations in 'offline' mode (generate SQL without DB connection)."""
|
|
65
|
+
url = get_database_url()
|
|
66
|
+
context.configure(
|
|
67
|
+
url=url,
|
|
68
|
+
target_metadata=target_metadata,
|
|
69
|
+
literal_binds=True,
|
|
70
|
+
dialect_opts={"paramstyle": "named"},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
with context.begin_transaction():
|
|
74
|
+
context.run_migrations()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_migrations_online() -> None:
|
|
78
|
+
"""Run migrations with a live database connection."""
|
|
79
|
+
alembic_config = config.get_section(config.config_ini_section, {})
|
|
80
|
+
alembic_config["sqlalchemy.url"] = get_database_url()
|
|
81
|
+
|
|
82
|
+
connectable = engine_from_config(
|
|
83
|
+
alembic_config,
|
|
84
|
+
prefix="sqlalchemy.",
|
|
85
|
+
poolclass=pool.NullPool,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
with connectable.connect() as connection:
|
|
89
|
+
context.configure(
|
|
90
|
+
connection=connection,
|
|
91
|
+
target_metadata=target_metadata,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
with context.begin_transaction():
|
|
95
|
+
context.run_migrations()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if context.is_offline_mode():
|
|
99
|
+
run_migrations_offline()
|
|
100
|
+
else:
|
|
101
|
+
run_migrations_online()
|