spine-framework 0.3.95 → 0.3.97
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.
|
@@ -22,6 +22,7 @@ import { existsSync, readFileSync, mkdirSync, cpSync, writeFileSync } from 'fs'
|
|
|
22
22
|
import { resolve, dirname, join, basename } from 'path'
|
|
23
23
|
import { fileURLToPath } from 'url'
|
|
24
24
|
import { execSync } from 'child_process'
|
|
25
|
+
import * as readline from 'readline'
|
|
25
26
|
import { createClient } from '@supabase/supabase-js'
|
|
26
27
|
import { runMigrations } from './migrate.js'
|
|
27
28
|
|
|
@@ -388,6 +389,9 @@ async function seedApp(appDir: string, appSlug: string) {
|
|
|
388
389
|
table: string
|
|
389
390
|
conflict: string
|
|
390
391
|
inject_app_id?: boolean
|
|
392
|
+
// When true: only patch design_schema.record_permissions on existing types.
|
|
393
|
+
// The record must have { kind, slug, record_permissions } — no app_id injection.
|
|
394
|
+
permissions_patch?: boolean
|
|
391
395
|
}
|
|
392
396
|
|
|
393
397
|
const seedEntries: SeedEntry[] = Array.isArray(manifest.seed)
|
|
@@ -399,6 +403,18 @@ async function seedApp(appDir: string, appSlug: string) {
|
|
|
399
403
|
{ file: 'seed/link-types.json', table: 'link_types', conflict: 'app_id,slug' },
|
|
400
404
|
]
|
|
401
405
|
|
|
406
|
+
// Always append the permissions patch file if it exists, unless already declared
|
|
407
|
+
const hasPermissionsPatch = seedEntries.some(e => e.permissions_patch)
|
|
408
|
+
if (!hasPermissionsPatch) {
|
|
409
|
+
seedEntries.push({
|
|
410
|
+
file: 'seed/type_permissions.json',
|
|
411
|
+
table: 'types',
|
|
412
|
+
conflict: 'kind,slug',
|
|
413
|
+
inject_app_id: false,
|
|
414
|
+
permissions_patch: true,
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
402
418
|
for (const entry of seedEntries) {
|
|
403
419
|
const filePath = join(appDir, entry.file)
|
|
404
420
|
if (!existsSync(filePath)) {
|
|
@@ -419,6 +435,70 @@ async function seedApp(appDir: string, appSlug: string) {
|
|
|
419
435
|
continue
|
|
420
436
|
}
|
|
421
437
|
|
|
438
|
+
// ── type_permissions.json: targeted record_permissions patch with check-and-choose ──
|
|
439
|
+
if (entry.permissions_patch) {
|
|
440
|
+
console.log(` Patching record_permissions for ${records.length} type(s) from ${entry.file}...`)
|
|
441
|
+
for (const record of records) {
|
|
442
|
+
const { kind, slug, record_permissions: newPerms } = record
|
|
443
|
+
if (!kind || !slug || !newPerms) {
|
|
444
|
+
console.warn(` ⚠ Skipping malformed entry (requires kind, slug, record_permissions)`)
|
|
445
|
+
continue
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Fetch the existing type (match on kind+slug regardless of app_id)
|
|
449
|
+
const { data: existing } = await db
|
|
450
|
+
.from('types')
|
|
451
|
+
.select('id, design_schema')
|
|
452
|
+
.eq('kind', kind)
|
|
453
|
+
.eq('slug', slug)
|
|
454
|
+
.maybeSingle()
|
|
455
|
+
|
|
456
|
+
if (!existing) {
|
|
457
|
+
console.log(` ○ ${kind}/${slug} not found in DB — skipping (install the owning app first)`)
|
|
458
|
+
continue
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const existingSchema = (existing.design_schema ?? {}) as Record<string, any>
|
|
462
|
+
const existingPerms = existingSchema.record_permissions ?? null
|
|
463
|
+
|
|
464
|
+
// Deep-equal check
|
|
465
|
+
if (JSON.stringify(existingPerms) === JSON.stringify(newPerms)) {
|
|
466
|
+
console.log(` ✓ ${kind}/${slug} record_permissions unchanged — skipped`)
|
|
467
|
+
continue
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Show diff and prompt
|
|
471
|
+
console.log(`\n Type: ${kind}/${slug}`)
|
|
472
|
+
console.log(` Existing record_permissions:`)
|
|
473
|
+
console.log(' ' + JSON.stringify(existingPerms, null, 2).replace(/\n/g, '\n '))
|
|
474
|
+
console.log(` New record_permissions:`)
|
|
475
|
+
console.log(' ' + JSON.stringify(newPerms, null, 2).replace(/\n/g, '\n '))
|
|
476
|
+
|
|
477
|
+
const choice = await prompt(' Replace? [R]eplace / [K]eep → ')
|
|
478
|
+
if (choice.trim().toLowerCase() !== 'r') {
|
|
479
|
+
console.log(` ○ Kept existing`)
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Targeted JSON merge: only update record_permissions key in design_schema
|
|
484
|
+
const { error: patchErr } = await db
|
|
485
|
+
.from('types')
|
|
486
|
+
.update({
|
|
487
|
+
design_schema: { ...existingSchema, record_permissions: newPerms },
|
|
488
|
+
})
|
|
489
|
+
.eq('id', existing.id)
|
|
490
|
+
|
|
491
|
+
if (patchErr) {
|
|
492
|
+
console.warn(` ⚠ ${kind}/${slug}: ${patchErr.message}`)
|
|
493
|
+
} else {
|
|
494
|
+
console.log(` ✓ ${kind}/${slug} record_permissions updated`)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
console.log(` ✓ type_permissions patch complete`)
|
|
498
|
+
continue
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Standard seed: upsert whole record ────────────────────────────────────
|
|
422
502
|
const injectAppId = entry.inject_app_id !== false // default true
|
|
423
503
|
console.log(` Seeding ${records.length} record(s) from ${entry.file} → ${entry.table}...`)
|
|
424
504
|
|
|
@@ -437,6 +517,18 @@ async function seedApp(appDir: string, appSlug: string) {
|
|
|
437
517
|
}
|
|
438
518
|
}
|
|
439
519
|
|
|
520
|
+
// ─── PROMPT HELPER ──────────────────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
function prompt(question: string): Promise<string> {
|
|
523
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
524
|
+
return new Promise(resolve => {
|
|
525
|
+
rl.question(question, answer => {
|
|
526
|
+
rl.close()
|
|
527
|
+
resolve(answer)
|
|
528
|
+
})
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
440
532
|
// ─── SPINE CONFIG HELPERS ───────────────────────────────────────────────────
|
|
441
533
|
|
|
442
534
|
interface RegistrationConfig {
|
|
@@ -152,7 +152,7 @@ export const validateToken = createHandler(async (_ctx, _body) => {
|
|
|
152
152
|
|
|
153
153
|
const { data: person } = await adminDb
|
|
154
154
|
.from('people')
|
|
155
|
-
.select('id, email, account_id, role_id, status, data, account:accounts(display_name), role:roles(slug, name)')
|
|
155
|
+
.select('id, email, account_id, role_id, status, data, account:accounts!people_account_id_fkey(display_name), role:roles(slug, name)')
|
|
156
156
|
.filter('data->>invite_token', 'eq', token)
|
|
157
157
|
.in('status', ['invited', 'pending'])
|
|
158
158
|
.single()
|
|
@@ -602,7 +602,7 @@ export const listInvites = createHandler(async (ctx, _body) => {
|
|
|
602
602
|
.select(`
|
|
603
603
|
id, email, full_name, status, created_at, updated_at,
|
|
604
604
|
account_id, role_id,
|
|
605
|
-
account:accounts(id, slug, display_name),
|
|
605
|
+
account:accounts!people_account_id_fkey(id, slug, display_name),
|
|
606
606
|
role:roles(id, slug, name),
|
|
607
607
|
data
|
|
608
608
|
`)
|
|
@@ -87,9 +87,11 @@
|
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
89
|
"record_permissions": {
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
90
|
+
"system_admin": ["create", "read", "update", "delete"],
|
|
91
|
+
"support_admin": ["create", "read", "update"],
|
|
92
|
+
"support": ["create", "read", "update"],
|
|
93
|
+
"member_admin": ["create", "read", "update"],
|
|
94
|
+
"member": ["create", "read", "update"]
|
|
93
95
|
}
|
|
94
96
|
},
|
|
95
97
|
"validation_schema": {},
|
|
@@ -98,9 +98,11 @@
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
"record_permissions": {
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
101
|
+
"system_admin": ["create", "read", "update", "delete"],
|
|
102
|
+
"support_admin": ["create", "read", "update"],
|
|
103
|
+
"support": ["create", "read", "update"],
|
|
104
|
+
"member_admin": ["create", "read", "update"],
|
|
105
|
+
"member": ["create", "read", "update"]
|
|
104
106
|
}
|
|
105
107
|
},
|
|
106
108
|
"validation_schema": {},
|