spine-framework 0.3.124 → 0.3.125
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/.framework/cli/commands/doctor.ts +99 -61
- package/.framework/cli/commands/generate.ts +67 -49
- package/.framework/functions/_shared/cil-state.ts +11 -2
- package/.framework/functions/_shared/index.ts +14 -0
- package/.framework/functions/_shared/resolve-ids.ts +35 -11
- package/.framework/functions/admin-data.ts +18 -24
- package/.framework/src/contexts/AuthContext.tsx +21 -6
- package/.framework/src/lib/api.ts +3 -1
- package/netlify.toml +3 -0
- package/package.json +1 -1
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/doctor
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine doctor` command — comprehensive system validation for agentic IDE setup.
|
|
9
|
-
* Checks environment, database, migrations, integrity, and custom code.
|
|
10
|
-
*
|
|
11
|
-
* **Commands:**
|
|
12
|
-
* | Subcommand | Description |
|
|
13
|
-
* |-------------------|-------------------------------------------------------|
|
|
14
|
-
* | `doctor` | Run all checks and report status |
|
|
15
|
-
* | `doctor --json` | Output structured JSON for agent consumption |
|
|
16
|
-
* | `doctor --fix` | Attempt automatic remediation where possible |
|
|
17
|
-
*
|
|
18
|
-
* **Checks performed:**
|
|
19
|
-
* 1. Environment variables (SUPABASE_URL, SERVICE_ROLE_KEY)
|
|
20
|
-
* 2. Database connectivity and latency
|
|
21
|
-
* 3. Migration status (local vs applied)
|
|
22
|
-
* 4. Core integrity (manifest hash verification)
|
|
23
|
-
* 5. Custom code TypeScript compilation
|
|
24
|
-
* 6. Test suite status (last run results)
|
|
25
|
-
*
|
|
26
|
-
* **Exit codes:**
|
|
27
|
-
* - 0: All checks passed (healthy)
|
|
28
|
-
* - 1: One or more checks failed
|
|
29
|
-
* - 2: Check passed but warnings present (degraded)
|
|
30
|
-
*
|
|
31
|
-
* **Usage:**
|
|
32
|
-
* ```bash
|
|
33
|
-
* spine doctor
|
|
34
|
-
* spine doctor --json
|
|
35
|
-
* spine doctor --fix
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @seeAlso cli/context.ts (buildCliContext)
|
|
39
|
-
* @seeAlso functions/system.ts (health endpoint)
|
|
40
|
-
*/
|
|
41
1
|
|
|
2
|
+
|
|
3
|
+
// CIL[BEGIN chunk_id="doctor_module_header_1783405679" type="module_header" identity="module_header" parent="none"]
|
|
4
|
+
// CIL[DOC-MACRO purpose="Setup imports and path constants for doctor command"]
|
|
5
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure module dependencies and file paths"]
|
|
6
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
7
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="imports, paths" errors="none" side_effects="none"]
|
|
8
|
+
// CIL[DOC-MICRO logic_summary="Import CLI framework, database, and file system utilities" notable_decisions="[]"]
|
|
9
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
|
|
42
10
|
import type { Command } from 'commander'
|
|
43
11
|
import { buildCliContext, handleError } from '../context.ts'
|
|
44
12
|
import { adminDb } from '../../functions/_shared/index.ts'
|
|
@@ -48,9 +16,17 @@ import { fileURLToPath } from 'url'
|
|
|
48
16
|
|
|
49
17
|
const __filename = fileURLToPath(import.meta.url)
|
|
50
18
|
const __dirname = dirname(__filename)
|
|
19
|
+
// CIL[END chunk_id="doctor_module_header_1783405679"]
|
|
20
|
+
|
|
51
21
|
|
|
52
|
-
// ─── TYPES ─────────────────────────────────────────────────────────────────
|
|
53
22
|
|
|
23
|
+
// CIL[BEGIN chunk_id="doctor_CheckResult_1783405679" type="interface" identity="CheckResult" parent="none"]
|
|
24
|
+
// CIL[DOC-MACRO purpose="Define structure for individual health check results"]
|
|
25
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Standardize check result data format"]
|
|
26
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
27
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult interface" errors="none" side_effects="none"]
|
|
28
|
+
// CIL[DOC-MICRO logic_summary="Type definition for check status and metadata" notable_decisions="[]"]
|
|
29
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
|
|
54
30
|
interface CheckResult {
|
|
55
31
|
name: string
|
|
56
32
|
status: 'pass' | 'fail' | 'warn'
|
|
@@ -58,7 +34,15 @@ interface CheckResult {
|
|
|
58
34
|
details?: Record<string, any>
|
|
59
35
|
fixable?: boolean
|
|
60
36
|
}
|
|
61
|
-
|
|
37
|
+
// CIL[END chunk_id="doctor_CheckResult_1783405679"]
|
|
38
|
+
|
|
39
|
+
// CIL[BEGIN chunk_id="doctor_DoctorReport_1783405679" type="interface" identity="DoctorReport" parent="none"]
|
|
40
|
+
// CIL[DOC-MACRO purpose="Define structure for complete doctor health report"]
|
|
41
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Aggregate all check results into summary report"]
|
|
42
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
43
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="DoctorReport interface" errors="none" side_effects="none"]
|
|
44
|
+
// CIL[DOC-MICRO logic_summary="Type definition for overall health status and summary" notable_decisions="[]"]
|
|
45
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
|
|
62
46
|
interface DoctorReport {
|
|
63
47
|
overall: 'healthy' | 'degraded' | 'unhealthy'
|
|
64
48
|
checks: CheckResult[]
|
|
@@ -69,9 +53,17 @@ interface DoctorReport {
|
|
|
69
53
|
fixable: number
|
|
70
54
|
}
|
|
71
55
|
}
|
|
56
|
+
// CIL[END chunk_id="doctor_DoctorReport_1783405679"]
|
|
72
57
|
|
|
73
|
-
// ─── CHECK IMPLEMENTATIONS ─────────────────────────────────────────────────
|
|
74
58
|
|
|
59
|
+
|
|
60
|
+
// CIL[BEGIN chunk_id="doctor_checkEnvironment_1783405679" type="function" identity="checkEnvironment" parent="none"]
|
|
61
|
+
// CIL[DOC-MACRO purpose="Validate required environment variables are set"]
|
|
62
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Check SUPABASE_URL and SERVICE_ROLE_KEY presence"]
|
|
63
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
64
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="none" side_effects="none"]
|
|
65
|
+
// CIL[DOC-MICRO logic_summary="Check process.env for required vars and .xenv file existence" notable_decisions="[]"]
|
|
66
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
75
67
|
async function checkEnvironment(): Promise<CheckResult> {
|
|
76
68
|
const required = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY']
|
|
77
69
|
const missing = required.filter(v => !process.env[v])
|
|
@@ -86,7 +78,7 @@ async function checkEnvironment(): Promise<CheckResult> {
|
|
|
86
78
|
}
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
|
|
81
|
+
|
|
90
82
|
const xenvPath = resolve(__dirname, '../../.xenv')
|
|
91
83
|
const hasXenv = existsSync(xenvPath)
|
|
92
84
|
|
|
@@ -97,7 +89,15 @@ async function checkEnvironment(): Promise<CheckResult> {
|
|
|
97
89
|
details: { has_xenv: hasXenv, vars_present: required.length }
|
|
98
90
|
}
|
|
99
91
|
}
|
|
100
|
-
|
|
92
|
+
// CIL[END chunk_id="doctor_checkEnvironment_1783405679"]
|
|
93
|
+
|
|
94
|
+
// CIL[BEGIN chunk_id="doctor_checkDatabase_1783405679" type="function" identity="checkDatabase" parent="none"]
|
|
95
|
+
// CIL[DOC-MACRO purpose="Test database connectivity and measure latency"]
|
|
96
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify database connection and performance"]
|
|
97
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
98
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database connection errors" side_effects="none"]
|
|
99
|
+
// CIL[DOC-MICRO logic_summary="Query accounts table to test connection and measure response time" notable_decisions="[]"]
|
|
100
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
101
101
|
async function checkDatabase(): Promise<CheckResult> {
|
|
102
102
|
const start = Date.now()
|
|
103
103
|
try {
|
|
@@ -129,7 +129,15 @@ async function checkDatabase(): Promise<CheckResult> {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
// CIL[END chunk_id="doctor_checkDatabase_1783405679"]
|
|
133
|
+
|
|
134
|
+
// CIL[BEGIN chunk_id="doctor_checkMigrations_1783405679" type="function" identity="checkMigrations" parent="none"]
|
|
135
|
+
// CIL[DOC-MACRO purpose="Compare local migration files with applied database migrations"]
|
|
136
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Detect pending migrations and migration status"]
|
|
137
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
138
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database query errors" side_effects="none"]
|
|
139
|
+
// CIL[DOC-MICRO logic_summary="List local SQL files and compare with schema_migrations table" notable_decisions="[]"]
|
|
140
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
133
141
|
async function checkMigrations(): Promise<CheckResult> {
|
|
134
142
|
const migrationsDir = resolve(__dirname, '../../migrations_dayzero')
|
|
135
143
|
|
|
@@ -191,7 +199,15 @@ async function checkMigrations(): Promise<CheckResult> {
|
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
|
-
|
|
202
|
+
// CIL[END chunk_id="doctor_checkMigrations_1783405679"]
|
|
203
|
+
|
|
204
|
+
// CIL[BEGIN chunk_id="doctor_checkIntegrity_1783405679" type="function" identity="checkIntegrity" parent="none"]
|
|
205
|
+
// CIL[DOC-MACRO purpose="Validate core manifest file and integrity hash presence"]
|
|
206
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Check manifest file exists and has integrity data"]
|
|
207
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
208
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="file read/parse errors" side_effects="none"]
|
|
209
|
+
// CIL[DOC-MICRO logic_summary="Read manifest.json and verify integrity hash exists" notable_decisions="[]"]
|
|
210
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
195
211
|
async function checkIntegrity(): Promise<CheckResult> {
|
|
196
212
|
const manifestPath = resolve(__dirname, '../../.spine-manifest.json')
|
|
197
213
|
|
|
@@ -218,9 +234,7 @@ async function checkIntegrity(): Promise<CheckResult> {
|
|
|
218
234
|
}
|
|
219
235
|
}
|
|
220
236
|
|
|
221
|
-
|
|
222
|
-
// computing SHA256 of all core files, which is slow. We just check
|
|
223
|
-
// that a manifest exists and looks valid.
|
|
237
|
+
|
|
224
238
|
return {
|
|
225
239
|
name: 'integrity',
|
|
226
240
|
status: 'pass',
|
|
@@ -236,7 +250,15 @@ async function checkIntegrity(): Promise<CheckResult> {
|
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
|
-
|
|
253
|
+
// CIL[END chunk_id="doctor_checkIntegrity_1783405679"]
|
|
254
|
+
|
|
255
|
+
// CIL[BEGIN chunk_id="doctor_checkTestRuns_1783405679" type="function" identity="checkTestRuns" parent="none"]
|
|
256
|
+
// CIL[DOC-MACRO purpose="Check latest test run status and results"]
|
|
257
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Verify test suite execution and report status"]
|
|
258
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
259
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database query errors" side_effects="none"]
|
|
260
|
+
// CIL[DOC-MICRO logic_summary="Query test_runs table for most recent test execution" notable_decisions="[]"]
|
|
261
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
240
262
|
async function checkTestRuns(): Promise<CheckResult> {
|
|
241
263
|
try {
|
|
242
264
|
const { data, error } = await adminDb
|
|
@@ -281,9 +303,17 @@ async function checkTestRuns(): Promise<CheckResult> {
|
|
|
281
303
|
}
|
|
282
304
|
}
|
|
283
305
|
}
|
|
306
|
+
// CIL[END chunk_id="doctor_checkTestRuns_1783405679"]
|
|
284
307
|
|
|
285
|
-
// ─── FIX IMPLEMENTATIONS ───────────────────────────────────────────────────
|
|
286
308
|
|
|
309
|
+
|
|
310
|
+
// CIL[BEGIN chunk_id="doctor_tryFix_1783405679" type="function" identity="tryFix" parent="none"]
|
|
311
|
+
// CIL[DOC-MACRO purpose="Provide fix suggestions for failed health checks"]
|
|
312
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Generate remediation instructions for each check type"]
|
|
313
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
314
|
+
// CIL[DOC-CONTRACT inputs="CheckResult" outputs="string fix instructions" errors="none" side_effects="none"]
|
|
315
|
+
// CIL[DOC-MICRO logic_summary="Switch statement mapping check names to fix commands" notable_decisions="[]"]
|
|
316
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
287
317
|
async function tryFix(check: CheckResult): Promise<string> {
|
|
288
318
|
switch (check.name) {
|
|
289
319
|
case 'environment':
|
|
@@ -302,9 +332,17 @@ async function tryFix(check: CheckResult): Promise<string> {
|
|
|
302
332
|
return 'No automatic fix available'
|
|
303
333
|
}
|
|
304
334
|
}
|
|
335
|
+
// CIL[END chunk_id="doctor_tryFix_1783405679"]
|
|
336
|
+
|
|
305
337
|
|
|
306
|
-
// ─── COMMAND REGISTRATION ──────────────────────────────────────────────────
|
|
307
338
|
|
|
339
|
+
// CIL[BEGIN chunk_id="doctor_registerDoctorCommands_1783405679" type="function" identity="registerDoctorCommands" parent="none"]
|
|
340
|
+
// CIL[DOC-MACRO purpose="Register doctor CLI command and orchestrate health checks"]
|
|
341
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Execute all health checks and format results"]
|
|
342
|
+
// CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
|
|
343
|
+
// CIL[DOC-CONTRACT inputs="Command program" outputs="registered CLI command" errors="CLI errors" side_effects="process exit"]
|
|
344
|
+
// CIL[DOC-MICRO logic_summary="Run checks sequentially, aggregate results, output formatted report" notable_decisions="[]"]
|
|
345
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
308
346
|
export function registerDoctorCommands(program: Command) {
|
|
309
347
|
program
|
|
310
348
|
.command('doctor')
|
|
@@ -313,8 +351,7 @@ export function registerDoctorCommands(program: Command) {
|
|
|
313
351
|
.option('--fix', 'Show fix instructions for failed checks')
|
|
314
352
|
.action(async (opts) => {
|
|
315
353
|
try {
|
|
316
|
-
|
|
317
|
-
// even when env vars are missing
|
|
354
|
+
|
|
318
355
|
let ctx
|
|
319
356
|
try {
|
|
320
357
|
ctx = await buildCliContext()
|
|
@@ -322,16 +359,16 @@ export function registerDoctorCommands(program: Command) {
|
|
|
322
359
|
ctx = null
|
|
323
360
|
}
|
|
324
361
|
|
|
325
|
-
|
|
362
|
+
|
|
326
363
|
const checks: CheckResult[] = []
|
|
327
364
|
|
|
328
365
|
checks.push(await checkEnvironment())
|
|
329
366
|
|
|
330
|
-
|
|
367
|
+
|
|
331
368
|
if (checks[0].status === 'pass') {
|
|
332
369
|
checks.push(await checkDatabase())
|
|
333
370
|
|
|
334
|
-
|
|
371
|
+
|
|
335
372
|
if (checks[1].status === 'pass') {
|
|
336
373
|
checks.push(await checkMigrations())
|
|
337
374
|
checks.push(await checkTestRuns())
|
|
@@ -372,7 +409,7 @@ export function registerDoctorCommands(program: Command) {
|
|
|
372
409
|
|
|
373
410
|
checks.push(await checkIntegrity())
|
|
374
411
|
|
|
375
|
-
|
|
412
|
+
|
|
376
413
|
const failed = checks.filter(c => c.status === 'fail').length
|
|
377
414
|
const warnings = checks.filter(c => c.status === 'warn').length
|
|
378
415
|
const fixable = checks.filter(c => c.fixable).length
|
|
@@ -397,7 +434,7 @@ export function registerDoctorCommands(program: Command) {
|
|
|
397
434
|
}
|
|
398
435
|
}
|
|
399
436
|
|
|
400
|
-
|
|
437
|
+
|
|
401
438
|
if (opts.json) {
|
|
402
439
|
console.log(JSON.stringify(report, null, 2))
|
|
403
440
|
} else {
|
|
@@ -426,7 +463,7 @@ export function registerDoctorCommands(program: Command) {
|
|
|
426
463
|
console.log()
|
|
427
464
|
}
|
|
428
465
|
|
|
429
|
-
|
|
466
|
+
|
|
430
467
|
if (overall === 'unhealthy') {
|
|
431
468
|
process.exit(1)
|
|
432
469
|
} else if (overall === 'degraded') {
|
|
@@ -440,3 +477,4 @@ export function registerDoctorCommands(program: Command) {
|
|
|
440
477
|
}
|
|
441
478
|
})
|
|
442
479
|
}
|
|
480
|
+
// CIL[END chunk_id="doctor_registerDoctorCommands_1783405679"]
|
|
@@ -1,38 +1,12 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/generate
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine generate` command — code scaffolding for extending Spine safely.
|
|
9
|
-
* Generates type definitions, handlers, and UI pages in v2-custom/ only.
|
|
10
|
-
*
|
|
11
|
-
* **Commands:**
|
|
12
|
-
* | Subcommand | Description |
|
|
13
|
-
* |-------------------------|-------------------------------------------------------|
|
|
14
|
-
* | `generate type <slug>` | Create new type with handler and UI stubs |
|
|
15
|
-
* | `generate function <name>` | Create new Netlify function stub |
|
|
16
|
-
*
|
|
17
|
-
* **What gets generated for `type`:**
|
|
18
|
-
* 1. Type record in database (item type by default)
|
|
19
|
-
* 2. Handler stub: `v2-custom/functions/{slug}.ts`
|
|
20
|
-
* 3. UI page stub: `v2-custom/src/pages/{slug}/index.tsx`
|
|
21
|
-
*
|
|
22
|
-
* **What gets generated for `function`:**
|
|
23
|
-
* 1. Netlify function stub: `v2-custom/functions/{name}.ts`
|
|
24
|
-
* 2. Basic handler structure with middleware imports
|
|
25
|
-
*
|
|
26
|
-
* **Usage:**
|
|
27
|
-
* ```bash
|
|
28
|
-
* spine generate type ticket-v2
|
|
29
|
-
* spine generate function webhook-handler
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @seeAlso functions/types.ts (type CRUD API)
|
|
33
|
-
* @seeAlso functions/_shared/middleware.ts (handler structure)
|
|
34
|
-
*/
|
|
35
1
|
|
|
2
|
+
|
|
3
|
+
// CIL[BEGIN chunk_id="generate_module_header_1783475972" type="module_header" identity="module_header" parent="none"]
|
|
4
|
+
// CIL[DOC-MACRO purpose="Setup imports and path constants for generate command"]
|
|
5
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Configure module dependencies and file paths"]
|
|
6
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
7
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="imports, paths" errors="none" side_effects="none"]
|
|
8
|
+
// CIL[DOC-MICRO logic_summary="Import CLI framework, database, and file system utilities" notable_decisions="[]"]
|
|
9
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
|
|
36
10
|
import type { Command } from 'commander'
|
|
37
11
|
import { buildCliContext, handleError } from '../context.ts'
|
|
38
12
|
import { adminDb } from '../../functions/_shared/index.ts'
|
|
@@ -43,9 +17,17 @@ import { fileURLToPath } from 'url'
|
|
|
43
17
|
const __filename = fileURLToPath(import.meta.url)
|
|
44
18
|
const __dirname = dirname(__filename)
|
|
45
19
|
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
20
|
+
// CIL[END chunk_id="generate_module_header_1783475972"]
|
|
46
21
|
|
|
47
|
-
// ─── TYPE GENERATION ─────────────────────────────────────────────────────────
|
|
48
22
|
|
|
23
|
+
|
|
24
|
+
// CIL[BEGIN chunk_id="generate_generatetype_1783475972" type="function" identity="generateType" parent="none"]
|
|
25
|
+
// CIL[DOC-MACRO purpose="Generate complete type with database record and code stubs"]
|
|
26
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create type in DB and generate handler/UI files"]
|
|
27
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
28
|
+
// CIL[DOC-CONTRACT inputs="ctx, slug, options" outputs="none" errors="database errors, file errors" side_effects="file creation, DB writes"]
|
|
29
|
+
// CIL[DOC-MICRO logic_summary="Create DB type record, generate handler stub, generate UI page stub" notable_decisions="[]"]
|
|
30
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
49
31
|
async function generateType(ctx: any, slug: string, options: any): Promise<void> {
|
|
50
32
|
const name = options.name || slug.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
|
|
51
33
|
const kind = options.kind || 'item'
|
|
@@ -53,8 +35,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
|
|
|
53
35
|
|
|
54
36
|
console.log(`\n🔧 Generating type '${slug}'...`)
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
console.log(' Step 1: Creating type record in database...')
|
|
38
|
+
|
|
58
39
|
|
|
59
40
|
const { data: existing, error: checkErr } = await adminDb
|
|
60
41
|
.from('types')
|
|
@@ -63,7 +44,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
|
|
|
63
44
|
.eq('kind', kind)
|
|
64
45
|
.single()
|
|
65
46
|
|
|
66
|
-
if (checkErr && checkErr.code !== 'PGRST116') {
|
|
47
|
+
if (checkErr && checkErr.code !== 'PGRST116') {
|
|
67
48
|
throw new Error(`Failed to check existing type: ${checkErr.message}`)
|
|
68
49
|
}
|
|
69
50
|
|
|
@@ -111,8 +92,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
|
|
|
111
92
|
console.log(' ✓ Type record created')
|
|
112
93
|
}
|
|
113
94
|
|
|
114
|
-
|
|
115
|
-
console.log(' Step 2: Generating handler stub...')
|
|
95
|
+
|
|
116
96
|
const handlerPath = resolve(PROJECT_ROOT, `v2-custom/functions/${slug}.ts`)
|
|
117
97
|
|
|
118
98
|
if (existsSync(handlerPath) && !options.force) {
|
|
@@ -123,8 +103,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
|
|
|
123
103
|
console.log(` ✓ Handler stub created: v2-custom/functions/${slug}.ts`)
|
|
124
104
|
}
|
|
125
105
|
|
|
126
|
-
|
|
127
|
-
console.log(' Step 3: Generating UI page stub...')
|
|
106
|
+
|
|
128
107
|
const pageDir = resolve(PROJECT_ROOT, `v2-custom/src/pages/${slug}`)
|
|
129
108
|
const pagePath = resolve(pageDir, 'index.tsx')
|
|
130
109
|
|
|
@@ -143,7 +122,15 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
|
|
|
143
122
|
console.log(` 2. Edit v2-custom/src/pages/${slug}/index.tsx to customize the UI`)
|
|
144
123
|
console.log(` 3. Run 'spine dev' to see your changes`)
|
|
145
124
|
}
|
|
146
|
-
|
|
125
|
+
// CIL[END chunk_id="generate_generatetype_1783475972"]
|
|
126
|
+
|
|
127
|
+
// CIL[BEGIN chunk_id="generate_generatehandlerstub_1783475972" type="function" identity="generateHandlerStub" parent="none"]
|
|
128
|
+
// CIL[DOC-MACRO purpose="Generate TypeScript handler stub with CRUD methods"]
|
|
129
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create template handler code for new types"]
|
|
130
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
131
|
+
// CIL[DOC-CONTRACT inputs="slug, kind, name" outputs="string handler code" errors="none" side_effects="none"]
|
|
132
|
+
// CIL[DOC-MICRO logic_summary="Return template string with handler structure and HTTP methods" notable_decisions="[]"]
|
|
133
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
147
134
|
function generateHandlerStub(slug: string, kind: string, name: string): string {
|
|
148
135
|
return `/**
|
|
149
136
|
* @module ${slug}
|
|
@@ -185,7 +172,15 @@ export const handler = createHandler(async (ctx) => {
|
|
|
185
172
|
})
|
|
186
173
|
`
|
|
187
174
|
}
|
|
188
|
-
|
|
175
|
+
// CIL[END chunk_id="generate_generatehandlerstub_1783475972"]
|
|
176
|
+
|
|
177
|
+
// CIL[BEGIN chunk_id="generate_generatepagestub_1783475972" type="function" identity="generatePageStub" parent="none"]
|
|
178
|
+
// CIL[DOC-MACRO purpose="Generate React UI page stub for type management"]
|
|
179
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create template React component with API integration"]
|
|
180
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
181
|
+
// CIL[DOC-CONTRACT inputs="slug, name, kind" outputs="string React component code" errors="none" side_effects="none"]
|
|
182
|
+
// CIL[DOC-MICRO logic_summary="Return template string with React component and API hooks" notable_decisions="[]"]
|
|
183
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
189
184
|
function generatePageStub(slug: string, name: string, kind: string): string {
|
|
190
185
|
return `/**
|
|
191
186
|
* @page ${slug}/index
|
|
@@ -227,9 +222,17 @@ export default function ${name.replace(/\s+/g, '')}Page() {
|
|
|
227
222
|
}
|
|
228
223
|
`
|
|
229
224
|
}
|
|
225
|
+
// CIL[END chunk_id="generate_generatepagestub_1783475972"]
|
|
226
|
+
|
|
230
227
|
|
|
231
|
-
// ─── FUNCTION GENERATION ─────────────────────────────────────────────────────
|
|
232
228
|
|
|
229
|
+
// CIL[BEGIN chunk_id="generate_generatefunction_1783475972" type="function" identity="generateFunction" parent="none"]
|
|
230
|
+
// CIL[DOC-MACRO purpose="Generate standalone Netlify function stub"]
|
|
231
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create custom function file with basic structure"]
|
|
232
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
233
|
+
// CIL[DOC-CONTRACT inputs="name, options" outputs="none" errors="file errors" side_effects="file creation"]
|
|
234
|
+
// CIL[DOC-MICRO logic_summary="Check existing file, generate function stub, write to disk" notable_decisions="[]"]
|
|
235
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
233
236
|
async function generateFunction(name: string, options: any): Promise<void> {
|
|
234
237
|
console.log(`\n🔧 Generating function '${name}'...`)
|
|
235
238
|
|
|
@@ -250,7 +253,15 @@ async function generateFunction(name: string, options: any): Promise<void> {
|
|
|
250
253
|
console.log(` 1. Edit the handler to implement your logic`)
|
|
251
254
|
console.log(` 2. Run 'spine dev' to see your changes`)
|
|
252
255
|
}
|
|
253
|
-
|
|
256
|
+
// CIL[END chunk_id="generate_generatefunction_1783555400"]
|
|
257
|
+
|
|
258
|
+
// CIL[BEGIN chunk_id="generate_generatefunctionstub_1783555400" type="function" identity="generateFunctionStub" parent="none"]
|
|
259
|
+
// CIL[DOC-MACRO purpose="Generate standalone Netlify function template"]
|
|
260
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Create template function with action routing"]
|
|
261
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
262
|
+
// CIL[DOC-CONTRACT inputs="name, description" outputs="string function code" errors="none" side_effects="none"]
|
|
263
|
+
// CIL[DOC-MICRO logic_summary="Return template string with handler structure and action switch" notable_decisions="[]"]
|
|
264
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
254
265
|
function generateFunctionStub(name: string, description?: string): string {
|
|
255
266
|
return `/**
|
|
256
267
|
* @module ${name}
|
|
@@ -293,9 +304,15 @@ export const handler = createHandler(async (ctx) => {
|
|
|
293
304
|
})
|
|
294
305
|
`
|
|
295
306
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
307
|
+
// CIL[END chunk_id="generate_generatefunctionstub_1783555400"]
|
|
308
|
+
|
|
309
|
+
// CIL[BEGIN chunk_id="generate_registergeneratecommands_1783555400" type="function" identity="registerGenerateCommands" parent="none"]
|
|
310
|
+
// CIL[DOC-MACRO purpose="Register generate CLI commands for type and function scaffolding"]
|
|
311
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Setup CLI interface for code generation tools"]
|
|
312
|
+
// CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
|
|
313
|
+
// CIL[DOC-CONTRACT inputs="Command program" outputs="registered CLI commands" errors="CLI errors" side_effects="none"]
|
|
314
|
+
// CIL[DOC-MICRO logic_summary="Register 'generate type' and 'generate function' subcommands with options" notable_decisions="[]"]
|
|
315
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
|
|
299
316
|
export function registerGenerateCommands(program: Command) {
|
|
300
317
|
const generate = program
|
|
301
318
|
.command('generate')
|
|
@@ -330,3 +347,4 @@ export function registerGenerateCommands(program: Command) {
|
|
|
330
347
|
}
|
|
331
348
|
})
|
|
332
349
|
}
|
|
350
|
+
// CIL[END chunk_id="generate_registergeneratecommands_1783555400"]
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
import { adminDb } from './db'
|
|
11
11
|
|
|
12
|
-
export type FileStatus =
|
|
12
|
+
export type FileStatus =
|
|
13
|
+
| '0-discovered'
|
|
14
|
+
| '1-selected'
|
|
15
|
+
| '2-chunked'
|
|
16
|
+
| '3-first-pass-complete'
|
|
17
|
+
| '4-first-pass-embedded'
|
|
18
|
+
| '5-first-pass-mapped'
|
|
19
|
+
| '6-second-pass-complete'
|
|
20
|
+
| '7-second-pass-embedded'
|
|
21
|
+
| '8-second-pass-mapped'
|
|
13
22
|
|
|
14
23
|
export interface FileTreeNode {
|
|
15
24
|
id?: string | null
|
|
@@ -40,7 +49,7 @@ export function isSelectedPath(filePath: string, selectedPaths: string[]): boole
|
|
|
40
49
|
* Extract the file status from a cil_file item.
|
|
41
50
|
*/
|
|
42
51
|
export function getFileStatus(fileItem: any): FileStatus {
|
|
43
|
-
return fileItem.data?.status || 'discovered'
|
|
52
|
+
return fileItem.data?.status || '0-discovered'
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
/**
|
|
@@ -258,6 +258,20 @@ export { sanitizeRecordData, validateUpdatePermissions, canAccessRecord } from '
|
|
|
258
258
|
export { generateValidationSchema } from './schema-utils'
|
|
259
259
|
export type { ValidationSchema } from './schema-utils'
|
|
260
260
|
|
|
261
|
+
// ============================================
|
|
262
|
+
// ID Resolution — type, link type, account, agent lookups
|
|
263
|
+
// ============================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* resolveTypeId — resolve a single type UUID by kind + slug.
|
|
267
|
+
* resolveTypeIds — batch resolve multiple type UUIDs.
|
|
268
|
+
* resolveTypeRecord — resolve a type's full record (id + design_schema + validation_schema).
|
|
269
|
+
* Use this when creating items that need to be stamped with schema data.
|
|
270
|
+
* resolveLinkTypeId / resolveLinkTypeIds — resolve link type UUIDs by slug.
|
|
271
|
+
*/
|
|
272
|
+
export { resolveTypeId, resolveTypeIds, resolveTypeRecord, resolveLinkTypeId, resolveLinkTypeIds, resolveAccountId, resolveAgentId, resolvePromptConfigId } from './resolve-ids'
|
|
273
|
+
export type { TypeRecord } from './resolve-ids'
|
|
274
|
+
|
|
261
275
|
// ============================================
|
|
262
276
|
// Audit
|
|
263
277
|
// ============================================
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
// Slug-based ID resolution utilities
|
|
2
|
-
// Custom app code must never hardcode UUIDs — IDs differ on every fresh install.
|
|
3
|
-
// All functions that need entity IDs must call resolveIds() at request time.
|
|
4
|
-
|
|
5
1
|
import { adminDb } from './db'
|
|
6
2
|
|
|
7
|
-
// Resolve type IDs by (kind, slug) pairs
|
|
8
3
|
export async function resolveTypeIds(
|
|
9
4
|
pairs: Array<{ kind: string; slug: string }>
|
|
10
5
|
): Promise<Record<string, string>> {
|
|
@@ -32,13 +27,46 @@ export async function resolveTypeIds(
|
|
|
32
27
|
return result
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
// Resolve a single type ID — throws if not found
|
|
36
30
|
export async function resolveTypeId(kind: string, slug: string): Promise<string> {
|
|
37
31
|
const ids = await resolveTypeIds([{ kind, slug }])
|
|
38
32
|
return ids[`${kind}/${slug}`]
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
export interface TypeRecord {
|
|
36
|
+
id: string
|
|
37
|
+
slug: string
|
|
38
|
+
kind: string
|
|
39
|
+
design_schema: Record<string, any>
|
|
40
|
+
validation_schema: Record<string, any>
|
|
41
|
+
is_active: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a type record by kind + slug in a single query.
|
|
46
|
+
* Returns id, design_schema, and validation_schema — everything needed
|
|
47
|
+
* to stamp a new item record (mirrors the admin-data.ts create handler pattern).
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveTypeRecord(kind: string, slug: string): Promise<TypeRecord> {
|
|
50
|
+
const { data, error } = await adminDb
|
|
51
|
+
.from('types')
|
|
52
|
+
.select('id, slug, kind, design_schema, validation_schema, is_active')
|
|
53
|
+
.eq('kind', kind)
|
|
54
|
+
.eq('slug', slug)
|
|
55
|
+
.single()
|
|
56
|
+
|
|
57
|
+
if (error || !data) throw new Error(`Type not found: ${kind}/${slug}`)
|
|
58
|
+
if (!data.is_active) throw new Error(`Type is inactive: ${kind}/${slug}`)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
id: data.id,
|
|
62
|
+
slug: data.slug,
|
|
63
|
+
kind: data.kind,
|
|
64
|
+
design_schema: data.design_schema || {},
|
|
65
|
+
validation_schema: data.validation_schema || {},
|
|
66
|
+
is_active: data.is_active,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
42
70
|
export async function resolveLinkTypeIds(slugs: string[]): Promise<Record<string, string>> {
|
|
43
71
|
if (slugs.length === 0) return {}
|
|
44
72
|
|
|
@@ -61,13 +89,11 @@ export async function resolveLinkTypeIds(slugs: string[]): Promise<Record<string
|
|
|
61
89
|
return result
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
// Resolve a single link type ID — throws if not found
|
|
65
92
|
export async function resolveLinkTypeId(slug: string): Promise<string> {
|
|
66
93
|
const ids = await resolveLinkTypeIds([slug])
|
|
67
94
|
return ids[slug]
|
|
68
95
|
}
|
|
69
96
|
|
|
70
|
-
// Resolve account ID by slug
|
|
71
97
|
export async function resolveAccountId(slug: string): Promise<string> {
|
|
72
98
|
const { data, error } = await adminDb
|
|
73
99
|
.from('accounts')
|
|
@@ -79,7 +105,6 @@ export async function resolveAccountId(slug: string): Promise<string> {
|
|
|
79
105
|
return data.id
|
|
80
106
|
}
|
|
81
107
|
|
|
82
|
-
// Resolve AI agent ID by name
|
|
83
108
|
export async function resolveAgentId(name: string): Promise<string> {
|
|
84
109
|
const { data, error } = await adminDb
|
|
85
110
|
.from('ai_agents')
|
|
@@ -92,7 +117,6 @@ export async function resolveAgentId(name: string): Promise<string> {
|
|
|
92
117
|
return data.id
|
|
93
118
|
}
|
|
94
119
|
|
|
95
|
-
// Resolve prompt config ID by slug
|
|
96
120
|
export async function resolvePromptConfigId(slug: string): Promise<string> {
|
|
97
121
|
const { data, error } = await adminDb
|
|
98
122
|
.from('prompt_configs')
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
import { createHandler } from './_shared/middleware'
|
|
46
46
|
import { sanitizeRecordData } from './_shared/permissions'
|
|
47
47
|
import { adminDb } from './_shared/db'
|
|
48
|
-
import { resolveTypeId } from './_shared/resolve-ids'
|
|
48
|
+
import { resolveTypeId, resolveTypeRecord } from './_shared/resolve-ids'
|
|
49
49
|
import { fireCreateTriggers, fireUpdateTriggers, fireDeleteTriggers } from './_shared/trigger-engine'
|
|
50
50
|
// ─── CHUNK_END: ADMIN_DATA_OVERVIEW ──────────────────────────────────────────────
|
|
51
51
|
|
|
@@ -299,36 +299,30 @@ export const create = createHandler(async (ctx, body) => {
|
|
|
299
299
|
const e: any = new Error('Valid entity parameter is required'); e.statusCode = 400; throw e
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
// Resolve
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
// Resolve type record (id + design_schema + validation_schema) in one query.
|
|
303
|
+
// Accepts either type_slug (resolved by kind) or a direct type_id already in recordData.
|
|
304
|
+
let typeRecord: { id: string; design_schema: Record<string, any>; validation_schema: Record<string, any> }
|
|
305
|
+
|
|
306
|
+
if (body?.type_slug) {
|
|
305
307
|
const kind = ENTITY_TO_KIND[entity]
|
|
306
308
|
if (!kind) {
|
|
307
309
|
const e: any = new Error(`type_slug is not supported for entity: ${entity}`); e.statusCode = 400; throw e
|
|
308
310
|
}
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
typeRecord = await resolveTypeRecord(kind, body.type_slug)
|
|
312
|
+
} else if (recordData.type_id) {
|
|
313
|
+
const { data, error: typeErr } = await adminDb
|
|
314
|
+
.from('types')
|
|
315
|
+
.select('id, design_schema, validation_schema, is_active')
|
|
316
|
+
.eq('id', recordData.type_id)
|
|
317
|
+
.single()
|
|
318
|
+
if (typeErr || !data) throw new Error(`type_id not found: ${recordData.type_id}`)
|
|
319
|
+
if (!data.is_active) throw new Error(`type_id references an inactive type: ${recordData.type_id}`)
|
|
320
|
+
typeRecord = { id: data.id, design_schema: data.design_schema || {}, validation_schema: data.validation_schema || {} }
|
|
321
|
+
} else {
|
|
314
322
|
const e: any = new Error('type_id or type_slug is required — every runtime record must reference a type'); e.statusCode = 400; throw e
|
|
315
323
|
}
|
|
316
|
-
recordData.type_id = typeId
|
|
317
324
|
|
|
318
|
-
|
|
319
|
-
const { data: typeRecord, error: typeErr } = await adminDb
|
|
320
|
-
.from('types')
|
|
321
|
-
.select('id, design_schema, validation_schema, is_active')
|
|
322
|
-
.eq('id', typeId)
|
|
323
|
-
.single()
|
|
324
|
-
|
|
325
|
-
if (typeErr || !typeRecord) {
|
|
326
|
-
throw new Error(`type_id not found: ${typeId}`)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (!typeRecord.is_active) {
|
|
330
|
-
throw new Error(`type_id references an inactive type: ${typeId}`)
|
|
331
|
-
}
|
|
325
|
+
recordData.type_id = typeRecord.id
|
|
332
326
|
|
|
333
327
|
// Ensure the type has at least permissions=ALL (defensive — migration 062 guarantees this)
|
|
334
328
|
let designSchema = typeRecord.design_schema || {}
|
|
@@ -42,7 +42,8 @@ import { User } from '../types/auth'
|
|
|
42
42
|
const STORAGE_VERSION = 1
|
|
43
43
|
const STORAGE_KEY = 'spine_user'
|
|
44
44
|
/** Max age (ms) before stored user is considered stale and discarded */
|
|
45
|
-
const STORAGE_TTL_MS =
|
|
45
|
+
const STORAGE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
46
|
+
const SESSION_TIMEOUT_MS = 15000 // guard against stale Supabase sessions hanging on reload
|
|
46
47
|
|
|
47
48
|
interface StoredUserEnvelope {
|
|
48
49
|
version: number
|
|
@@ -80,6 +81,23 @@ function writeStoredUser(user: User | null) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Wrapper around supabase.auth.getSession() that times out to prevent the
|
|
86
|
+
* app from getting stuck on a corrupted/stale stored session.
|
|
87
|
+
*/
|
|
88
|
+
async function getSessionWithTimeout(): Promise<{ data: { session: any | null } }> {
|
|
89
|
+
try {
|
|
90
|
+
return await Promise.race([
|
|
91
|
+
supabase.auth.getSession(),
|
|
92
|
+
new Promise<{ data: { session: null } }>((resolve) =>
|
|
93
|
+
setTimeout(() => resolve({ data: { session: null } }), SESSION_TIMEOUT_MS)
|
|
94
|
+
)
|
|
95
|
+
])
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return { data: { session: null } }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
// ─── TYPES ───────────────────────────────────────────────────────────────────
|
|
84
102
|
|
|
85
103
|
/**
|
|
@@ -167,7 +185,6 @@ async function fetchUserContext(maxRetries = 2): Promise<User | null> {
|
|
|
167
185
|
|
|
168
186
|
const data = await response.json()
|
|
169
187
|
if (data.error || !data.data) return null
|
|
170
|
-
|
|
171
188
|
return data.data
|
|
172
189
|
} catch (error) {
|
|
173
190
|
lastError = error
|
|
@@ -177,8 +194,6 @@ async function fetchUserContext(maxRetries = 2): Promise<User | null> {
|
|
|
177
194
|
return null
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
|
-
|
|
181
|
-
console.error('fetchUserContext failed after retries:', lastError)
|
|
182
197
|
return null
|
|
183
198
|
}
|
|
184
199
|
|
|
@@ -274,7 +289,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
274
289
|
// ── Refresh ────────────────────────────────────────────────────────────────
|
|
275
290
|
|
|
276
291
|
const refreshUser = useCallback(async () => {
|
|
277
|
-
const { data: { session } } = await
|
|
292
|
+
const { data: { session } } = await getSessionWithTimeout()
|
|
278
293
|
|
|
279
294
|
if (!session?.user) {
|
|
280
295
|
clearUser()
|
|
@@ -330,7 +345,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
330
345
|
|
|
331
346
|
const checkAuth = async () => {
|
|
332
347
|
try {
|
|
333
|
-
const { data: { session } } = await
|
|
348
|
+
const { data: { session } } = await getSessionWithTimeout()
|
|
334
349
|
|
|
335
350
|
if (!session?.user) {
|
|
336
351
|
if (!cancelled) clearUser()
|
|
@@ -58,7 +58,7 @@ export function getAccountId(): string | null {
|
|
|
58
58
|
// ─── 401 INTERCEPTOR ─────────────────────────────────────────────────────────
|
|
59
59
|
|
|
60
60
|
/** Paths that are expected to return 401 (don't trigger the interceptor) */
|
|
61
|
-
const AUTH_PATHS = ['/api/auth']
|
|
61
|
+
const AUTH_PATHS = ['/api/auth', '/api/admin-data']
|
|
62
62
|
|
|
63
63
|
let _onUnauthorizedCallback: (() => void) | null = null
|
|
64
64
|
|
|
@@ -118,6 +118,8 @@ export async function getAuthHeaders(): Promise<Record<string, string>> {
|
|
|
118
118
|
const { data: { session } } = await supabase.auth.getSession()
|
|
119
119
|
if (session?.access_token) {
|
|
120
120
|
headers['Authorization'] = `Bearer ${session.access_token}`
|
|
121
|
+
} else {
|
|
122
|
+
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
if (_accountId) {
|
package/netlify.toml
CHANGED