@elytracms/studio 0.0.79 → 0.0.81

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.
Files changed (107) hide show
  1. package/convex/contract-parity.test.ts +1 -1
  2. package/convex/functions.ts +13 -3
  3. package/convex/http-routes.test.ts +143 -0
  4. package/convex/http.ts +19 -9
  5. package/convex/schema.ts +0 -19
  6. package/convex/users.ts +1 -1
  7. package/dist/components/app-views/default-modules.d.ts +141 -44
  8. package/dist/components/app-views/default-modules.js +229 -65
  9. package/dist/components/app-views/default-modules.js.map +1 -1
  10. package/dist/components/app-views/module.d.ts +38 -0
  11. package/dist/components/app-views/module.js.map +1 -1
  12. package/dist/components/app-views/sidebar.d.ts +5 -2
  13. package/dist/components/app-views/sidebar.js +16 -5
  14. package/dist/components/app-views/sidebar.js.map +1 -1
  15. package/dist/components/app-views/studio-config.d.ts +27 -0
  16. package/dist/components/app-views/studio-config.js.map +1 -1
  17. package/dist/components/builder/canvas-render-context.js +7 -1
  18. package/dist/components/builder/canvas-render-context.js.map +1 -1
  19. package/dist/components/builder/reuse-context.js +7 -1
  20. package/dist/components/builder/reuse-context.js.map +1 -1
  21. package/dist/components/builder/reuse.d.ts +94 -16
  22. package/dist/components/builder/reuse.js +201 -19
  23. package/dist/components/builder/reuse.js.map +1 -1
  24. package/dist/components/cms/bulk-actions.js +3 -1
  25. package/dist/components/cms/bulk-actions.js.map +1 -1
  26. package/dist/components/cms/cell-edit.js +6 -2
  27. package/dist/components/cms/cell-edit.js.map +1 -1
  28. package/dist/components/cms/cms-store.d.ts +40 -13
  29. package/dist/components/cms/cms-store.js +50 -22
  30. package/dist/components/cms/cms-store.js.map +1 -1
  31. package/dist/components/cms/collection-table.js +2 -2
  32. package/dist/components/cms/collection-table.js.map +1 -1
  33. package/dist/components/cms/date-input-core.d.ts +21 -0
  34. package/dist/components/cms/date-input-core.js +20 -0
  35. package/dist/components/cms/date-input-core.js.map +1 -1
  36. package/dist/components/cms/date-input.d.ts +9 -1
  37. package/dist/components/cms/date-input.js +2 -2
  38. package/dist/components/cms/date-input.js.map +1 -1
  39. package/dist/components/cms/detail-editor.js +11 -5
  40. package/dist/components/cms/detail-editor.js.map +1 -1
  41. package/dist/components/cms/editable-cell.js +2 -1
  42. package/dist/components/cms/editable-cell.js.map +1 -1
  43. package/dist/components/cms/field-control.js +4 -2
  44. package/dist/components/cms/field-control.js.map +1 -1
  45. package/dist/components/cms/field-renderer.js +8 -1
  46. package/dist/components/cms/field-renderer.js.map +1 -1
  47. package/dist/components/cms/import-export.js +2 -1
  48. package/dist/components/cms/import-export.js.map +1 -1
  49. package/dist/components/cms/issues-pane.d.ts +4 -2
  50. package/dist/components/cms/issues-pane.js +6 -4
  51. package/dist/components/cms/issues-pane.js.map +1 -1
  52. package/dist/components/cms/scalar-value-editor.js +2 -1
  53. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  54. package/dist/components/components/transfer-dialog.js +10 -12
  55. package/dist/components/components/transfer-dialog.js.map +1 -1
  56. package/dist/components/project-list.d.ts +9 -3
  57. package/dist/components/project-list.js +11 -5
  58. package/dist/components/project-list.js.map +1 -1
  59. package/dist/components/project-switcher.d.ts +3 -1
  60. package/dist/components/project-switcher.js +4 -2
  61. package/dist/components/project-switcher.js.map +1 -1
  62. package/dist/components/redirects/redirects-store.d.ts +21 -0
  63. package/dist/components/redirects/redirects-store.js +17 -4
  64. package/dist/components/redirects/redirects-store.js.map +1 -1
  65. package/dist/components/section-nav.js +102 -4
  66. package/dist/components/section-nav.js.map +1 -1
  67. package/dist/customization.d.ts +7 -0
  68. package/dist/customization.js.map +1 -1
  69. package/dist/lib/data/collection-content.d.ts +6 -1
  70. package/dist/lib/data/collection-content.js +50 -12
  71. package/dist/lib/data/collection-content.js.map +1 -1
  72. package/dist/lib/i18n/messages.de.js +132 -0
  73. package/dist/lib/i18n/messages.de.js.map +1 -1
  74. package/dist/lib/i18n/messages.en.d.ts +103 -0
  75. package/dist/lib/i18n/messages.en.js +146 -0
  76. package/dist/lib/i18n/messages.en.js.map +1 -1
  77. package/dist/lib/locale/binding-fallback.d.ts +8 -1
  78. package/dist/lib/locale/binding-fallback.js +8 -1
  79. package/dist/lib/locale/binding-fallback.js.map +1 -1
  80. package/dist/lib/locale/locale-store.d.ts +41 -9
  81. package/dist/lib/locale/locale-store.js +34 -15
  82. package/dist/lib/locale/locale-store.js.map +1 -1
  83. package/dist/lib/locale/locale-switcher.d.ts +3 -1
  84. package/dist/lib/locale/locale-switcher.js +4 -2
  85. package/dist/lib/locale/locale-switcher.js.map +1 -1
  86. package/dist/lib/locale/use-studio-locale.d.ts +9 -4
  87. package/dist/lib/locale/use-studio-locale.js.map +1 -1
  88. package/dist/lib/persistence/convex-adapter.d.ts +1 -3
  89. package/dist/lib/persistence/convex-adapter.js +0 -55
  90. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  91. package/dist/lib/persona/labels.d.ts +42 -1
  92. package/dist/lib/persona/labels.js +57 -0
  93. package/dist/lib/persona/labels.js.map +1 -1
  94. package/dist/lib/routes/route-issues.d.ts +9 -0
  95. package/dist/lib/routes/route-issues.js +117 -0
  96. package/dist/lib/routes/route-issues.js.map +1 -0
  97. package/dist/lib/workspace/config-issues.d.ts +54 -0
  98. package/dist/lib/workspace/config-issues.js +70 -0
  99. package/dist/lib/workspace/config-issues.js.map +1 -0
  100. package/dist/lib/workspace/index.d.ts +9 -0
  101. package/dist/lib/workspace/index.js +11 -0
  102. package/dist/lib/workspace/index.js.map +1 -1
  103. package/dist/lib/workspace/switcher-source.d.ts +20 -3
  104. package/dist/lib/workspace/switcher-source.js.map +1 -1
  105. package/package.json +2 -2
  106. package/convex/cliTokens.ts +0 -63
  107. package/convex/sync.ts +0 -297
package/convex/sync.ts DELETED
@@ -1,297 +0,0 @@
1
- import { makeFunctionReference } from 'convex/server'
2
- import type { GenericActionCtx, GenericDataModel } from 'convex/server'
3
- import { v } from 'convex/values'
4
- import { verifyCliToken } from '@elytracms/core/operations'
5
- import type { VerifiedCliToken } from '@elytracms/core/operations'
6
- import { roleGrantsCapability } from '@elytracms/core/persistence'
7
- import type {
8
- CliTokenRepository,
9
- OperationCapabilityId,
10
- PersistenceAdapter,
11
- } from '@elytracms/core/persistence'
12
- import { httpAction, internalQuery } from './functions'
13
-
14
- /**
15
- * EC-157 — the builder-sync HTTP endpoint. Serves the contract fixed in
16
- * `@elytracms/core/export-sync`'s `registrySyncEndpoints` from this deployment's
17
- * `.convex.site` origin, so a host repo's `builder check` / `typegen` run against
18
- * the real builder instead of a `fetchImpl` double:
19
- *
20
- * - `GET /api/projects/:projectId/registry-sync/snapshot?mode=&environment=` — the
21
- * typegen/check snapshot: locale config only. Collection schemas, routes/redirects,
22
- * AND the project graph are config-owned (EC-183/EC-207/EC-287, AD-11) — the CLI
23
- * reads them from `elytra.config.ts`, not this snapshot.
24
- *
25
- * ## Package-shipped, deployment-agnostic (EC-223/EC-252)
26
- *
27
- * This module ships inside `@elytracms/studio`; the consumer's `convex/sync.ts` is a
28
- * one-line `export *` shim. It therefore NEVER imports the per-deployment
29
- * `./_generated/api` — it addresses sibling functions by STRING ref via
30
- * `makeFunctionReference` (resolved against the consumer's deployment), exactly like
31
- * `publishing.ts`. That decoupling is also what makes it typecheck under a cross-repo
32
- * `link:` loop: there is no `ApiFromModules` inference to unify across two `convex`
33
- * instances (the bug EC-252 fixes — a copied `sync.ts` collapsed `api.cliTokens.byHash`
34
- * to `{}`).
35
- *
36
- * ## Auth (EC-150 CLI tokens)
37
- *
38
- * The endpoint requires `Authorization: Bearer <ect_…>`. Tokens are verified by
39
- * `verifyCliToken` (`@elytracms/core/operations`): the presented plaintext is SHA-256-hashed
40
- * and looked up via `cliTokens.byHash`; unknown and revoked tokens are rejected with
41
- * a 401. Single-tenant (EC-182): a deployment hosts one project, so a token carries
42
- * no per-project scope — it just needs at least the `viewer` capability the snapshot
43
- * read requires.
44
- *
45
- * ## Error contract
46
- *
47
- * Failures are structured JSON bodies — `{ error: { code, message, issues? } }` —
48
- * matching `RegistrySyncErrorBody` in `@elytracms/core/export-sync`; even unexpected
49
- * exceptions return a structured body, never an opaque crash page. The CLI surfaces
50
- * them as `transport` issues.
51
- */
52
-
53
- type ActionCtx = GenericActionCtx<GenericDataModel>
54
-
55
- // Sibling functions are addressed by STRING ref (resolved against the consumer's
56
- // deployment), never `./_generated/api` — see the module note. The explicit arg +
57
- // return generics keep the call sites typed without `ApiFromModules` inference.
58
- type CliTokenRow = {
59
- tokenId: string
60
- label: string
61
- tokenHash: string
62
- createdAt: string
63
- revokedAt?: string
64
- }
65
- const cliTokensByHash = makeFunctionReference<
66
- 'query',
67
- { tokenHash: string },
68
- CliTokenRow | null
69
- >('cliTokens:byHash')
70
- const syncExportSnapshot = makeFunctionReference<
71
- 'query',
72
- { projectId: string; mode: SnapshotMode; environment: string },
73
- BuilderSnapshot
74
- >('sync:exportSnapshot')
75
-
76
- // --- Structured responses ----------------------------------------------------
77
-
78
- interface SyncErrorIssue {
79
- code: string
80
- message: string
81
- componentId?: string
82
- }
83
-
84
- function jsonResponse(value: unknown, status = 200): Response {
85
- return new Response(JSON.stringify(value), {
86
- status,
87
- headers: { 'content-type': 'application/json' },
88
- })
89
- }
90
-
91
- function errorResponse(
92
- status: number,
93
- code: string,
94
- message: string,
95
- issues?: SyncErrorIssue[],
96
- ): Response {
97
- return jsonResponse(
98
- { error: { code, message, ...(issues && issues.length > 0 ? { issues } : {}) } },
99
- status,
100
- )
101
- }
102
-
103
- // --- Routing -----------------------------------------------------------------
104
-
105
- const PROJECTS_PREFIX = '/api/projects/'
106
-
107
- type SyncEndpoint = 'snapshot'
108
-
109
- interface SyncRoute {
110
- projectId: string
111
- endpoint: SyncEndpoint
112
- }
113
-
114
- /** Parse `/api/projects/:projectId/registry-sync/<rest>` (Convex routes by prefix only). */
115
- function parseSyncRoute(method: string, pathname: string): SyncRoute | undefined {
116
- if (!pathname.startsWith(PROJECTS_PREFIX)) return undefined
117
- const segments = pathname
118
- .slice(PROJECTS_PREFIX.length)
119
- .split('/')
120
- .map((segment) => decodeURIComponent(segment))
121
- const [projectId, scope, ...rest] = segments
122
- if (!projectId || scope !== 'registry-sync') return undefined
123
- const tail = rest.join('/')
124
- if (tail === 'snapshot' && method === 'GET') return { projectId, endpoint: 'snapshot' }
125
- return undefined
126
- }
127
-
128
- // --- Token auth (EC-150) -------------------------------------------------------
129
-
130
- /**
131
- * The capability each endpoint requires; the snapshot read needs `content.read`
132
- * (EC-240c). Capability grants come from the ONE canonical role policy
133
- * (`roleGrantsCapability`), so a token's role is checked the same way handlers
134
- * check members — no role literal at the boundary.
135
- */
136
- const REQUIRED_CAPABILITY: Record<SyncEndpoint, OperationCapabilityId> = {
137
- snapshot: 'content.read',
138
- }
139
-
140
- /**
141
- * A {@link CliTokenRepository} over this deployment's `cliTokens` table, narrowed to
142
- * the verification path. `verifyCliToken` only ever calls `findByHash`; the write
143
- * methods live behind the admin-only operations layer and are not reachable here.
144
- */
145
- function cliTokenRepository(ctx: ActionCtx): CliTokenRepository {
146
- const unsupported = (method: string) =>
147
- Promise.reject(new Error(`cli-token ${method} is not available on the sync endpoint`))
148
- return {
149
- createToken: () => unsupported('createToken'),
150
- listTokens: () => unsupported('listTokens'),
151
- revokeToken: () => unsupported('revokeToken'),
152
- async findByHash(tokenHash) {
153
- const doc = await ctx.runQuery(cliTokensByHash, { tokenHash })
154
- if (!doc) return undefined
155
- return {
156
- id: doc.tokenId,
157
- label: doc.label,
158
- tokenHash: doc.tokenHash,
159
- createdAt: doc.createdAt,
160
- ...(doc.revokedAt ? { revokedAt: doc.revokedAt } : {}),
161
- }
162
- },
163
- }
164
- }
165
-
166
- type AuthResult = { ok: true; verified: VerifiedCliToken } | { ok: false; response: Response }
167
-
168
- /** Resolve and verify the Bearer CLI token, then check it grants the required capability. */
169
- async function authorize(
170
- ctx: ActionCtx,
171
- request: Request,
172
- route: SyncRoute,
173
- ): Promise<AuthResult> {
174
- const header = request.headers.get('authorization')
175
- if (!header || !header.toLowerCase().startsWith('bearer ')) {
176
- return {
177
- ok: false,
178
- response: errorResponse(
179
- 401,
180
- 'unauthorized',
181
- 'Missing CLI token — send "Authorization: Bearer <token>" (issue one with tokens.issue).',
182
- ),
183
- }
184
- }
185
- const token = header.slice('bearer '.length).trim()
186
- // verifyCliToken only reads `adapter.cliTokens`; the rest of the adapter surface
187
- // is deliberately absent here (the assertion documents exactly that contract).
188
- const adapter = { cliTokens: cliTokenRepository(ctx) } as PersistenceAdapter
189
- const verified = await verifyCliToken(adapter, token)
190
- if (verified === null) {
191
- return {
192
- ok: false,
193
- response: errorResponse(401, 'unauthorized', 'Unknown, malformed, or revoked CLI token.'),
194
- }
195
- }
196
- if (!roleGrantsCapability(verified.role, REQUIRED_CAPABILITY[route.endpoint])) {
197
- return {
198
- ok: false,
199
- response: errorResponse(
200
- 403,
201
- 'forbidden',
202
- `This endpoint requires the "${REQUIRED_CAPABILITY[route.endpoint]}" capability (token role "${verified.role}" does not grant it).`,
203
- ),
204
- }
205
- }
206
- return { ok: true, verified }
207
- }
208
-
209
- // --- The HTTP action ------------------------------------------------------------
210
-
211
- export const registrySync = httpAction(async (ctx, request) => {
212
- const pathname = new URL(request.url).pathname
213
- try {
214
- const route = parseSyncRoute(request.method, pathname)
215
- if (!route) {
216
- return errorResponse(
217
- 404,
218
- 'not-found',
219
- `No registry-sync endpoint matches ${request.method} ${pathname}.`,
220
- )
221
- }
222
- const auth = await authorize(ctx, request, route)
223
- if (!auth.ok) return auth.response
224
- const { projectId } = route
225
-
226
- // snapshot
227
- const params = new URL(request.url).searchParams
228
- const mode = params.get('mode') ?? 'published'
229
- if (mode !== 'published' && mode !== 'draft') {
230
- return errorResponse(
231
- 400,
232
- 'invalid-request',
233
- `Invalid mode "${mode}" (expected published|draft).`,
234
- )
235
- }
236
- const environment = params.get('environment') ?? 'production'
237
- const snapshot = await ctx.runQuery(syncExportSnapshot, {
238
- projectId,
239
- mode,
240
- environment,
241
- })
242
- return jsonResponse(snapshot)
243
- } catch (cause) {
244
- // Even unexpected failures keep the structured contract — never an opaque crash.
245
- return errorResponse(
246
- 500,
247
- 'internal',
248
- `Registry sync failed: ${cause instanceof Error ? cause.message : String(cause)}`,
249
- )
250
- }
251
- })
252
-
253
- // --- Typegen/check snapshot -----------------------------------------------------
254
-
255
- type SnapshotMode = 'published' | 'draft'
256
-
257
- interface BuilderSnapshot {
258
- projectId: string
259
- mode: SnapshotMode
260
- environment: string
261
- project: null
262
- localeConfig: { default: string; locales: string[] }
263
- collections: never[]
264
- }
265
-
266
- /**
267
- * Everything `builder typegen`/`builder check` need from live builder state, in the
268
- * `BuilderSnapshot` wire shape of `@elytracms/core/export-sync`'s `createHttpBuilderSource`.
269
- * EC-287: the project graph is retired (structure is code), so the snapshot no
270
- * longer carries a graph — like the CMS schema and routes, it is config-owned and
271
- * read from `elytra.config.ts` in the consuming repo.
272
- */
273
- export const exportSnapshot = internalQuery({
274
- args: {
275
- projectId: v.string(),
276
- mode: v.union(v.literal('published'), v.literal('draft')),
277
- environment: v.string(),
278
- },
279
- handler: async (ctx, { projectId, mode, environment }): Promise<BuilderSnapshot> => {
280
- // `environment` is a vestigial wire-compat field on the CLI snapshot contract
281
- // (EC-179 §2): a deployment IS one environment.
282
- // AD-11 (EC-182/EC-183/EC-207): project identity, locale config, CMS
283
- // collection schemas, AND routes/redirects are all config-owned —
284
- // `builder typegen`/`check` run in the consuming repo and read them from
285
- // `elytra.config.ts`, not the wire. `project`/`localeConfig`/`collections`
286
- // stay in the wire shape as config-sourced placeholders until the CLI reads
287
- // config directly (C1).
288
- return {
289
- projectId,
290
- mode,
291
- environment,
292
- project: null,
293
- localeConfig: { default: 'en', locales: ['en'] },
294
- collections: [],
295
- }
296
- },
297
- })