@orion-studios/cms 0.5.6 → 0.5.8
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/README.md +21 -4
- package/dist/analytics/react.d.ts +3 -1
- package/dist/analytics/react.js +5 -0
- package/dist/chunk-DPKXKG2S.js +8 -0
- package/dist/{chunk-AYV6KYDP.js → chunk-NSAZCP4I.js} +20 -1
- package/dist/{chunk-ULE565KD.js → chunk-Z52R6XR7.js} +227 -72
- package/dist/content/index.js +1 -1
- package/dist/forms/react.d.ts +12 -2
- package/dist/forms/react.js +6 -3
- package/dist/server/index.d.ts +93 -21
- package/dist/server/index.js +803 -143
- package/dist/studio/index.js +29 -7
- package/package.json +1 -1
- package/sql/bootstrap.sql +313 -6
- package/sql/migrations/20260829032027_cms_security_and_sync_contracts.sql +390 -0
package/dist/server/index.d.ts
CHANGED
|
@@ -52,8 +52,10 @@ type CmsRoutesOptions = {
|
|
|
52
52
|
allowedOrigins?: string[];
|
|
53
53
|
/** Rate limiter for public submissions. Defaults to in-memory (5/min/IP). */
|
|
54
54
|
rateLimitStore?: RateLimitStore | null;
|
|
55
|
-
/** Token accepted by POST /sync
|
|
55
|
+
/** Token accepted only by POST /sync in addition to admin bearer auth. */
|
|
56
56
|
syncToken?: string;
|
|
57
|
+
/** Token accepted only by POST or GET /cron/publish-due. */
|
|
58
|
+
cronToken?: string;
|
|
57
59
|
/** Extra domains never flagged as email typos. */
|
|
58
60
|
knownGoodEmailDomains?: string[];
|
|
59
61
|
/**
|
|
@@ -63,8 +65,20 @@ type CmsRoutesOptions = {
|
|
|
63
65
|
sendEmail?: EmailSender | null;
|
|
64
66
|
/** Site name used in notification emails. */
|
|
65
67
|
siteName?: string;
|
|
66
|
-
/**
|
|
68
|
+
/** Purpose-specific secret for draft preview grants. */
|
|
67
69
|
previewSecret?: string;
|
|
70
|
+
/** Stable identifier for the active preview signing key. */
|
|
71
|
+
previewKeyId?: string;
|
|
72
|
+
/** Optional prior preview key accepted only through the bounded overlap. */
|
|
73
|
+
previewPreviousSecret?: string;
|
|
74
|
+
previewPreviousKeyId?: string;
|
|
75
|
+
previewPreviousValidUntil?: number;
|
|
76
|
+
/** Stable Supabase project reference bound into preview grants. */
|
|
77
|
+
projectRef?: string;
|
|
78
|
+
/** Purpose-specific secret for analytics visitor and session hashing. */
|
|
79
|
+
analyticsSecret?: string;
|
|
80
|
+
/** Purpose-specific secret for auto-reply recipient hashing. */
|
|
81
|
+
autoReplyHashSecret?: string;
|
|
68
82
|
/** Cloudflare Turnstile secret — when set, public submits must include a valid `_turnstileToken`. */
|
|
69
83
|
turnstileSecret?: string;
|
|
70
84
|
/** Max upload size in bytes (default 15 MB). */
|
|
@@ -120,10 +134,35 @@ declare function createCmsRoutes(options: CmsRoutesOptions): {
|
|
|
120
134
|
};
|
|
121
135
|
|
|
122
136
|
declare const PREVIEW_TOKEN_TTL_MS: number;
|
|
137
|
+
declare const PREVIEW_TOKEN_MAX_USES = 8;
|
|
123
138
|
declare const PREVIEW_SESSION_COOKIE = "orion_preview_session";
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
type PreviewSigningKey = {
|
|
140
|
+
id: string;
|
|
141
|
+
secret: string;
|
|
142
|
+
};
|
|
143
|
+
type PreviewKeyRing = {
|
|
144
|
+
active: PreviewSigningKey;
|
|
145
|
+
previous?: PreviewSigningKey & {
|
|
146
|
+
acceptUntil: number;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
type PreviewGrantClaims = {
|
|
150
|
+
version: 1;
|
|
151
|
+
audience: 'cms-preview';
|
|
152
|
+
operation: 'read-draft';
|
|
153
|
+
keyId: string;
|
|
154
|
+
grantId: string;
|
|
155
|
+
pageId: string;
|
|
156
|
+
userId: string;
|
|
157
|
+
projectRef: string;
|
|
158
|
+
issuedAt: number;
|
|
159
|
+
expiresAt: number;
|
|
160
|
+
};
|
|
161
|
+
declare function createPreviewToken(grant: Omit<PreviewGrantClaims, 'version' | 'audience' | 'operation' | 'keyId' | 'issuedAt' | 'expiresAt'>, keys: PreviewKeyRing | string, ttlMs?: number): string;
|
|
162
|
+
/** Returns bound claims when the token is valid and unexpired, else null. */
|
|
163
|
+
declare function verifyPreviewGrantToken(token: string, keys: PreviewKeyRing | string, now?: number): PreviewGrantClaims | null;
|
|
164
|
+
/** Compatibility helper for callers that only need the signed page binding. */
|
|
165
|
+
declare function verifyPreviewToken(token: string, keys: PreviewKeyRing | string): string | null;
|
|
127
166
|
type PreviewPage = {
|
|
128
167
|
id: string;
|
|
129
168
|
slug: string;
|
|
@@ -136,7 +175,7 @@ type PreviewPage = {
|
|
|
136
175
|
* Site-side helper: verifies a preview token and loads the page's DRAFT
|
|
137
176
|
* layout with the given (service or memory) client.
|
|
138
177
|
*/
|
|
139
|
-
declare function getPreviewPage(client: SupabaseClient, token: string,
|
|
178
|
+
declare function getPreviewPage(client: SupabaseClient, token: string, keys: PreviewKeyRing | string, expectedProjectRef: string): Promise<PreviewPage | null>;
|
|
140
179
|
|
|
141
180
|
/**
|
|
142
181
|
* Pure analytics aggregation: raw event rows in, the full dashboard payload
|
|
@@ -144,8 +183,8 @@ declare function getPreviewPage(client: SupabaseClient, token: string, secret: s
|
|
|
144
183
|
* and memory backends and is trivially testable.
|
|
145
184
|
*
|
|
146
185
|
* Call and email taps are useful interaction counts, but browser events are
|
|
147
|
-
* forgeable.
|
|
148
|
-
*
|
|
186
|
+
* forgeable. Form conversions come from authoritative submission rows. Any
|
|
187
|
+
* other conversion requires a server-verified event.
|
|
149
188
|
*/
|
|
150
189
|
type StoredEvent = {
|
|
151
190
|
id?: number | string;
|
|
@@ -163,6 +202,12 @@ type StoredEvent = {
|
|
|
163
202
|
server_verified?: boolean;
|
|
164
203
|
created_at: string;
|
|
165
204
|
};
|
|
205
|
+
/** Minimal authoritative form record used by aggregate analytics. */
|
|
206
|
+
type StoredFormSubmission = {
|
|
207
|
+
form: string;
|
|
208
|
+
session_key: string;
|
|
209
|
+
created_at: string;
|
|
210
|
+
};
|
|
166
211
|
type AnalyticsKpis = {
|
|
167
212
|
visitors: number;
|
|
168
213
|
identifiedVisitors: number;
|
|
@@ -228,7 +273,7 @@ type AnalyticsSummary = {
|
|
|
228
273
|
declare function aggregateAnalytics(events: StoredEvent[], previousEvents: StoredEvent[], range: {
|
|
229
274
|
from: string;
|
|
230
275
|
to: string;
|
|
231
|
-
}): AnalyticsSummary;
|
|
276
|
+
}, submissions?: StoredFormSubmission[], previousSubmissions?: StoredFormSubmission[]): AnalyticsSummary;
|
|
232
277
|
|
|
233
278
|
/**
|
|
234
279
|
* Server-side analytics ingest: validation, bot filtering, and the
|
|
@@ -301,9 +346,11 @@ declare function geoFrom(request: Request): {
|
|
|
301
346
|
* access token), verified via auth.getUser(). Stateless: no cookie plumbing.
|
|
302
347
|
*/
|
|
303
348
|
type CmsEnv = {
|
|
349
|
+
expectedProjectRef: string;
|
|
304
350
|
supabaseUrl: string;
|
|
305
351
|
serviceRoleKey: string;
|
|
306
352
|
};
|
|
353
|
+
declare function validateCmsEnv(env: CmsEnv): CmsEnv;
|
|
307
354
|
declare function readCmsEnv(): CmsEnv;
|
|
308
355
|
declare function getServiceClient(env?: CmsEnv): SupabaseClient;
|
|
309
356
|
/** Test seam: inject a fake client. */
|
|
@@ -333,14 +380,6 @@ declare function can(user: CmsUser | null, action: CmsAction): boolean;
|
|
|
333
380
|
*/
|
|
334
381
|
declare function isStructuralChange(previous: PageLayout, next: PageLayout): boolean;
|
|
335
382
|
|
|
336
|
-
/**
|
|
337
|
-
* Content-as-code sync: the single implementation used by both the
|
|
338
|
-
* POST /api/cms/sync route and local `npm run sync` scripts.
|
|
339
|
-
*
|
|
340
|
-
* Contract (proven on Currin): create missing pages, refresh metadata always,
|
|
341
|
-
* write layouts only where the page is not builder-owned, upsert globals and
|
|
342
|
-
* forms. Idempotent.
|
|
343
|
-
*/
|
|
344
383
|
type SyncPageInput = {
|
|
345
384
|
slug: string;
|
|
346
385
|
path?: string;
|
|
@@ -358,6 +397,11 @@ type SyncFormInput = {
|
|
|
358
397
|
config?: Record<string, unknown>;
|
|
359
398
|
successMessage?: string;
|
|
360
399
|
};
|
|
400
|
+
type SyncRedirectInput = {
|
|
401
|
+
fromPath: string;
|
|
402
|
+
toPath: string;
|
|
403
|
+
permanent?: boolean;
|
|
404
|
+
};
|
|
361
405
|
type SyncMediaInput = {
|
|
362
406
|
storagePath: string;
|
|
363
407
|
filename?: string;
|
|
@@ -373,8 +417,27 @@ type SyncInput = {
|
|
|
373
417
|
globals?: SyncGlobalInput[];
|
|
374
418
|
forms?: SyncFormInput[];
|
|
375
419
|
media?: SyncMediaInput[];
|
|
420
|
+
redirects?: SyncRedirectInput[];
|
|
421
|
+
};
|
|
422
|
+
type SyncOperation = {
|
|
423
|
+
kind: 'page' | 'global' | 'form' | 'media' | 'redirect';
|
|
424
|
+
key: string;
|
|
425
|
+
action: 'create' | 'update' | 'noop' | 'conflict' | 'blocked-delete';
|
|
426
|
+
};
|
|
427
|
+
type SyncPlan = {
|
|
428
|
+
mode: 'dry-run';
|
|
429
|
+
manifestHash: string;
|
|
430
|
+
targetReceiptHash: string;
|
|
431
|
+
operations: SyncOperation[];
|
|
432
|
+
skipped: Array<{
|
|
433
|
+
slug: string;
|
|
434
|
+
issues: unknown;
|
|
435
|
+
}>;
|
|
376
436
|
};
|
|
377
437
|
type SyncResult = {
|
|
438
|
+
mode: 'apply';
|
|
439
|
+
manifestHash: string;
|
|
440
|
+
targetReceiptHash: string;
|
|
378
441
|
synced: string[];
|
|
379
442
|
skipped: Array<{
|
|
380
443
|
slug: string;
|
|
@@ -383,14 +446,23 @@ type SyncResult = {
|
|
|
383
446
|
globals: number;
|
|
384
447
|
forms: number;
|
|
385
448
|
media: number;
|
|
386
|
-
/** Non-page resources that failed to write (globals/forms). */
|
|
387
449
|
failed: Array<{
|
|
388
|
-
kind: '
|
|
450
|
+
kind: SyncOperation['kind'];
|
|
389
451
|
key: string;
|
|
390
452
|
error: string;
|
|
391
453
|
}>;
|
|
454
|
+
conflicts: SyncOperation[];
|
|
455
|
+
};
|
|
456
|
+
type SyncOptions = {
|
|
457
|
+
mode?: 'dry-run';
|
|
458
|
+
expectedProjectRef?: string;
|
|
459
|
+
} | {
|
|
460
|
+
mode: 'apply';
|
|
461
|
+
expectedManifestHash: string;
|
|
462
|
+
expectedProjectRef?: string;
|
|
463
|
+
expectedTargetReceiptHash: string;
|
|
392
464
|
};
|
|
393
|
-
declare function runContentSync(client: SupabaseClient, registry: BlockRegistry, input: SyncInput): Promise<SyncResult>;
|
|
465
|
+
declare function runContentSync(client: SupabaseClient, registry: BlockRegistry, input: SyncInput, options?: SyncOptions): Promise<SyncPlan | SyncResult>;
|
|
394
466
|
|
|
395
467
|
/**
|
|
396
468
|
* In-memory CMS backend for local development and end-to-end tests.
|
|
@@ -423,4 +495,4 @@ declare function createMemoryCms(): MemoryCms;
|
|
|
423
495
|
/** Process-wide singleton for Next.js dev servers (module state survives HMR via globalThis). */
|
|
424
496
|
declare function getMemoryCms(): MemoryCms;
|
|
425
497
|
|
|
426
|
-
export { type AnalyticsEventType, type AnalyticsKpis, type AnalyticsSummary, type CmsAction, type CmsEnv, type CmsRole, type CmsRoutesOptions, type CmsUser, type EmailMessage, type EmailSender, type IncomingEvent, MEMORY_DEV_TOKEN, type MemoryCms, PREVIEW_SESSION_COOKIE, PREVIEW_TOKEN_TTL_MS, type PreviewPage, type ResendSenderOptions, type StoredEvent, type SyncFormInput, type SyncGlobalInput, type SyncInput, type SyncMediaInput, type SyncPageInput, type SyncResult, aggregateAnalytics, can, createCmsRoutes, createDurableRateLimitStore, createMemoryCms, createPreviewToken, createResendSender, deviceFrom, formatSubmissionText, geoFrom, getMemoryCms, getPreviewPage, getServiceClient, isBotRequest, isStructuralChange, notifySubmission, parseEventBatch, readCmsEnv, resolveUser, runContentSync, sessionKeyFor, setServiceClientForTesting, verifyPreviewToken, visitorKeyFor };
|
|
498
|
+
export { type AnalyticsEventType, type AnalyticsKpis, type AnalyticsSummary, type CmsAction, type CmsEnv, type CmsRole, type CmsRoutesOptions, type CmsUser, type EmailMessage, type EmailSender, type IncomingEvent, MEMORY_DEV_TOKEN, type MemoryCms, PREVIEW_SESSION_COOKIE, PREVIEW_TOKEN_MAX_USES, PREVIEW_TOKEN_TTL_MS, type PreviewGrantClaims, type PreviewPage, type ResendSenderOptions, type StoredEvent, type StoredFormSubmission, type SyncFormInput, type SyncGlobalInput, type SyncInput, type SyncMediaInput, type SyncOperation, type SyncOptions, type SyncPageInput, type SyncPlan, type SyncResult, aggregateAnalytics, can, createCmsRoutes, createDurableRateLimitStore, createMemoryCms, createPreviewToken, createResendSender, deviceFrom, formatSubmissionText, geoFrom, getMemoryCms, getPreviewPage, getServiceClient, isBotRequest, isStructuralChange, notifySubmission, parseEventBatch, readCmsEnv, resolveUser, runContentSync, sessionKeyFor, setServiceClientForTesting, validateCmsEnv, verifyPreviewGrantToken, verifyPreviewToken, visitorKeyFor };
|