@byline/cli 3.20.4 → 4.0.0

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 (198) hide show
  1. package/README.md +42 -2
  2. package/dist/cli.js +5 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/doctor.d.ts.map +1 -1
  5. package/dist/commands/doctor.js +1 -0
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/init.d.ts +3 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +10 -14
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/setup-checks.d.ts +9 -0
  12. package/dist/commands/setup-checks.d.ts.map +1 -1
  13. package/dist/commands/setup-checks.js +43 -18
  14. package/dist/commands/setup-checks.js.map +1 -1
  15. package/dist/commands/setup.js +1 -1
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/context.d.ts +2 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +6 -0
  20. package/dist/context.js.map +1 -1
  21. package/dist/lib/dependency-version.d.ts +12 -0
  22. package/dist/lib/dependency-version.d.ts.map +1 -0
  23. package/dist/lib/dependency-version.js +101 -0
  24. package/dist/lib/dependency-version.js.map +1 -0
  25. package/dist/lib/planned-writes.d.ts +8 -0
  26. package/dist/lib/planned-writes.d.ts.map +1 -0
  27. package/dist/lib/planned-writes.js +177 -0
  28. package/dist/lib/planned-writes.js.map +1 -0
  29. package/dist/lib/planned-writes.test.d.ts +2 -0
  30. package/dist/lib/planned-writes.test.d.ts.map +1 -0
  31. package/dist/lib/planned-writes.test.js +113 -0
  32. package/dist/lib/planned-writes.test.js.map +1 -0
  33. package/dist/lib/release-policy.d.ts +12 -0
  34. package/dist/lib/release-policy.d.ts.map +1 -0
  35. package/dist/lib/release-policy.js +20 -0
  36. package/dist/lib/release-policy.js.map +1 -0
  37. package/dist/lib/release-policy.test.d.ts +2 -0
  38. package/dist/lib/release-policy.test.d.ts.map +1 -0
  39. package/dist/lib/release-policy.test.js +36 -0
  40. package/dist/lib/release-policy.test.js.map +1 -0
  41. package/dist/lib/route-config.d.ts +40 -0
  42. package/dist/lib/route-config.d.ts.map +1 -0
  43. package/dist/lib/route-config.js +322 -0
  44. package/dist/lib/route-config.js.map +1 -0
  45. package/dist/lib/static-config.d.ts +20 -0
  46. package/dist/lib/static-config.d.ts.map +1 -0
  47. package/dist/lib/static-config.js +444 -0
  48. package/dist/lib/static-config.js.map +1 -0
  49. package/dist/lib/static-config.test.d.ts +2 -0
  50. package/dist/lib/static-config.test.d.ts.map +1 -0
  51. package/dist/lib/static-config.test.js +105 -0
  52. package/dist/lib/static-config.test.js.map +1 -0
  53. package/dist/lib/template-hook-boundary.test.node.d.ts +2 -0
  54. package/dist/lib/template-hook-boundary.test.node.d.ts.map +1 -0
  55. package/dist/lib/template-hook-boundary.test.node.js +30 -0
  56. package/dist/lib/template-hook-boundary.test.node.js.map +1 -0
  57. package/dist/lib/template-path.d.ts +5 -0
  58. package/dist/lib/template-path.d.ts.map +1 -0
  59. package/dist/lib/template-path.js +9 -0
  60. package/dist/lib/template-path.js.map +1 -0
  61. package/dist/lib/workspace-root.d.ts +28 -0
  62. package/dist/lib/workspace-root.d.ts.map +1 -0
  63. package/dist/lib/workspace-root.js +205 -0
  64. package/dist/lib/workspace-root.js.map +1 -0
  65. package/dist/manifest/deps.d.ts +1 -1
  66. package/dist/manifest/deps.d.ts.map +1 -1
  67. package/dist/manifest/deps.js +18 -8
  68. package/dist/manifest/deps.js.map +1 -1
  69. package/dist/phases/deps.d.ts +14 -1
  70. package/dist/phases/deps.d.ts.map +1 -1
  71. package/dist/phases/deps.js +312 -137
  72. package/dist/phases/deps.js.map +1 -1
  73. package/dist/phases/deps.test.d.ts +2 -0
  74. package/dist/phases/deps.test.d.ts.map +1 -0
  75. package/dist/phases/deps.test.js +343 -0
  76. package/dist/phases/deps.test.js.map +1 -0
  77. package/dist/phases/preflight.d.ts.map +1 -1
  78. package/dist/phases/preflight.js +59 -11
  79. package/dist/phases/preflight.js.map +1 -1
  80. package/dist/phases/prompts.d.ts +2 -0
  81. package/dist/phases/prompts.d.ts.map +1 -1
  82. package/dist/phases/prompts.js +51 -2
  83. package/dist/phases/prompts.js.map +1 -1
  84. package/dist/phases/routes-ui.test.d.ts +2 -0
  85. package/dist/phases/routes-ui.test.d.ts.map +1 -0
  86. package/dist/phases/routes-ui.test.js +766 -0
  87. package/dist/phases/routes-ui.test.js.map +1 -0
  88. package/dist/phases/routes.d.ts +4 -1
  89. package/dist/phases/routes.d.ts.map +1 -1
  90. package/dist/phases/routes.js +610 -122
  91. package/dist/phases/routes.js.map +1 -1
  92. package/dist/phases/scaffold-smoke.test.d.ts +2 -0
  93. package/dist/phases/scaffold-smoke.test.d.ts.map +1 -0
  94. package/dist/phases/scaffold-smoke.test.js +168 -0
  95. package/dist/phases/scaffold-smoke.test.js.map +1 -0
  96. package/dist/phases/scaffold.d.ts +4 -1
  97. package/dist/phases/scaffold.d.ts.map +1 -1
  98. package/dist/phases/scaffold.js +275 -72
  99. package/dist/phases/scaffold.js.map +1 -1
  100. package/dist/phases/scaffold.test.d.ts +2 -0
  101. package/dist/phases/scaffold.test.d.ts.map +1 -0
  102. package/dist/phases/scaffold.test.js +178 -0
  103. package/dist/phases/scaffold.test.js.map +1 -0
  104. package/dist/phases/ui.d.ts +4 -1
  105. package/dist/phases/ui.d.ts.map +1 -1
  106. package/dist/phases/ui.js +51 -59
  107. package/dist/phases/ui.js.map +1 -1
  108. package/dist/phases/wire/index.d.ts.map +1 -1
  109. package/dist/phases/wire/index.js +10 -6
  110. package/dist/phases/wire/index.js.map +1 -1
  111. package/dist/phases/wire/shared.d.ts +4 -1
  112. package/dist/phases/wire/shared.d.ts.map +1 -1
  113. package/dist/phases/wire/vite-config.d.ts.map +1 -1
  114. package/dist/phases/wire/vite-config.js +69 -20
  115. package/dist/phases/wire/vite-config.js.map +1 -1
  116. package/dist/phases/wire/vite-config.test.d.ts +2 -0
  117. package/dist/phases/wire/vite-config.test.d.ts.map +1 -0
  118. package/dist/phases/wire/vite-config.test.js +97 -0
  119. package/dist/phases/wire/vite-config.test.js.map +1 -0
  120. package/dist/prompts.d.ts +1 -0
  121. package/dist/prompts.d.ts.map +1 -1
  122. package/dist/prompts.js +7 -2
  123. package/dist/prompts.js.map +1 -1
  124. package/dist/runner.js +1 -1
  125. package/dist/runner.js.map +1 -1
  126. package/dist/templates/byline/admin.config.ts +8 -7
  127. package/dist/templates/byline/clients.server.ts +77 -0
  128. package/dist/templates/byline/collection-types.contract.ts +67 -0
  129. package/dist/templates/byline/collections/index.ts +17 -0
  130. package/dist/templates/byline/collections/server-hooks.ts +7 -0
  131. package/dist/templates/byline/generated/collection-types.ts +11 -0
  132. package/dist/templates/byline/i18n.ts +3 -3
  133. package/dist/templates/byline/locales.ts +9 -9
  134. package/dist/templates/byline/public.ts +12 -0
  135. package/dist/templates/byline/routes.ts +7 -6
  136. package/dist/templates/byline/scripts/generate-types.ts +81 -0
  137. package/dist/templates/byline/server.config.ts +5 -5
  138. package/dist/templates/byline-examples/admin.config.ts +18 -21
  139. package/dist/templates/byline-examples/blocks/photo-block.ts +3 -2
  140. package/dist/templates/byline-examples/clients.server.ts +77 -0
  141. package/dist/templates/byline-examples/collection-types.contract.ts +67 -0
  142. package/dist/templates/byline-examples/collections/docs/hooks.ts +30 -20
  143. package/dist/templates/byline-examples/collections/docs/schema.ts +18 -18
  144. package/dist/templates/byline-examples/collections/index.ts +22 -0
  145. package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +11 -6
  146. package/dist/templates/byline-examples/collections/media/schema.ts +16 -0
  147. package/dist/templates/byline-examples/collections/news/schema.ts +17 -3
  148. package/dist/templates/byline-examples/collections/news-categories/schema.ts +13 -0
  149. package/dist/templates/byline-examples/collections/pages/schema.ts +13 -0
  150. package/dist/templates/byline-examples/collections/server-hooks.ts +8 -0
  151. package/dist/templates/byline-examples/fields/ai-text.ts +1 -1
  152. package/dist/templates/byline-examples/fields/ai-textarea.ts +1 -1
  153. package/dist/templates/byline-examples/fields/lexical-richtext-ai.tsx +3 -3
  154. package/dist/templates/byline-examples/generated/collection-types.ts +139 -0
  155. package/dist/templates/byline-examples/i18n.ts +3 -3
  156. package/dist/templates/byline-examples/locales.ts +9 -9
  157. package/dist/templates/byline-examples/public.ts +12 -0
  158. package/dist/templates/byline-examples/routes.ts +7 -6
  159. package/dist/templates/byline-examples/scripts/backfill-version-locales.ts +1 -1
  160. package/dist/templates/byline-examples/scripts/generate-types.ts +81 -0
  161. package/dist/templates/byline-examples/scripts/import-docs.ts +11 -12
  162. package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.test.node.ts +93 -233
  163. package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.ts +2 -2
  164. package/dist/templates/byline-examples/scripts/lib/strip-leading-h1.test.node.ts +0 -4
  165. package/dist/templates/byline-examples/scripts/re-anchor.ts +3 -3
  166. package/dist/templates/byline-examples/scripts/regenerate-media.ts +6 -5
  167. package/dist/templates/byline-examples/server.config.ts +6 -15
  168. package/dist/templates/host/vite.config.ts +2 -0
  169. package/dist/templates/routes/_byline/admin/collections/$collection/$id/api.tsx +1 -6
  170. package/dist/templates/routes/_byline/admin/collections/$collection/$id/history.tsx +1 -7
  171. package/dist/templates/routes/_byline/admin/collections/$collection/$id/index.tsx +1 -6
  172. package/dist/templates/routes/_byline/route.lazy.tsx +1 -2
  173. package/dist/templates/routes/_byline/route.tsx +1 -1
  174. package/dist/templates/ui-byline/blocks/photo-block/index.tsx +18 -23
  175. package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +1 -1
  176. package/dist/templates/ui-byline/components/admonition/index.tsx +11 -8
  177. package/dist/templates/ui-byline/components/code/code.tsx +6 -4
  178. package/dist/templates/ui-byline/components/link/lang-link.tsx +4 -8
  179. package/dist/templates/ui-byline/components/link/link-field.tsx +3 -2
  180. package/dist/templates/ui-byline/components/link/link-lexical.tsx +9 -8
  181. package/dist/templates/ui-byline/render-blocks.tsx +38 -38
  182. package/dist/templates/ui-byline/types/content.ts +25 -0
  183. package/dist/templates/ui-byline/types/i18n.ts +4 -8
  184. package/dist/test-helpers.d.ts +5 -0
  185. package/dist/test-helpers.d.ts.map +1 -0
  186. package/dist/test-helpers.js +52 -0
  187. package/dist/test-helpers.js.map +1 -0
  188. package/dist/types.d.ts +21 -1
  189. package/dist/types.d.ts.map +1 -1
  190. package/dist/ui-template-contract.test.d.ts +2 -0
  191. package/dist/ui-template-contract.test.d.ts.map +1 -0
  192. package/dist/ui-template-contract.test.js +130 -0
  193. package/dist/ui-template-contract.test.js.map +1 -0
  194. package/dist/workspace-root.test.d.ts +2 -0
  195. package/dist/workspace-root.test.d.ts.map +1 -0
  196. package/dist/workspace-root.test.js +307 -0
  197. package/dist/workspace-root.test.js.map +1 -0
  198. package/package.json +9 -1
@@ -8,20 +8,21 @@
8
8
 
9
9
  /**
10
10
  * Registers Byline's client-side config (collection admin UI configs,
11
- * field editors, i18n, routes) in the current module graph. Imported as
12
- * a side-effect from `src/routes/_byline/route.lazy.tsx` so registration
13
- * fires only when a `_byline/*` URL matches — keeping the Lexical
14
- * editor module graph out of public-route bundles entirely.
11
+ * field editors, i18n, routes) in the current module graph. The `_byline`
12
+ * route registers it from two complementary points: a dynamic import in
13
+ * `route.tsx` covers child loaders, while the side-effect import in
14
+ * `route.lazy.tsx` covers component render and initial hydration. Keeping
15
+ * both imports behind `_byline/*` keeps the admin graph off public routes.
15
16
  *
16
17
  * This is the minimal scaffold: no example collections registered. Add
17
- * collection schemas + admin configs to the `collections` and `admin`
18
- * arrays as you create them.
18
+ * schemas to `collections/index.ts` and presentation configs to `admin`.
19
19
  */
20
20
 
21
21
  import type { ClientConfig } from '@byline/core'
22
22
  import { defineClientConfig } from '@byline/core'
23
23
  import { RichTextField as LexicalRichTextField } from '@byline/richtext-lexical'
24
24
 
25
+ import { collections } from './collections/index.js'
25
26
  import { i18n } from './i18n.js'
26
27
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
27
28
 
@@ -31,7 +32,7 @@ export const config: ClientConfig = {
31
32
  serverURL,
32
33
  i18n,
33
34
  routes,
34
- collections: [],
35
+ collections,
35
36
  admin: [],
36
37
  fields: {
37
38
  richText: { editor: LexicalRichTextField },
@@ -0,0 +1,77 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Typed, server-only access to this application's Byline clients.
11
+ *
12
+ * ```text
13
+ * generated/collection-types.ts
14
+ * `CollectionFieldsByPath`
15
+ * |
16
+ * v
17
+ * collections/index.ts --------------------.
18
+ * `BylineCollections` |
19
+ * v
20
+ * @byline/host-tanstack-start ------> clients.server.ts
21
+ * runtime client getters |
22
+ * v
23
+ * typed `get*BylineClient()` getters
24
+ * ```
25
+ *
26
+ * Why this boundary exists:
27
+ *
28
+ * The TanStack host owns the runtime admin, public, system, and viewer client
29
+ * singletons. It intentionally knows nothing about an application's collection
30
+ * schemas, so its getters cannot provide application-specific collection paths
31
+ * and field shapes. The generated `CollectionFieldsByPath` registry is exported
32
+ * as `BylineCollections` by `collections/index.ts`; this module joins the host
33
+ * getters and app registry once instead of repeating a type assertion at every
34
+ * server call site.
35
+ *
36
+ * Typical server usage:
37
+ *
38
+ * ```ts
39
+ * const publicClient = getPublicBylineClient() // anonymous published reads
40
+ * const viewerClient = getViewerBylineClient() // public or admin-preview reads
41
+ * const adminClient = getAdminBylineClient() // current admin request authority
42
+ * const systemClient = getSystemBylineClient() // trusted hooks and maintenance
43
+ * ```
44
+ *
45
+ * Calls to `client.collection('<collection-path>')` are therefore checked
46
+ * against this application's generated registry and return that collection's
47
+ * canonical generated field shape. `collection-types.contract.ts` separately
48
+ * proves that those generated shapes match the runtime schemas exactly.
49
+ *
50
+ * The `.server.ts` suffix is intentional. Import these getters only from server
51
+ * routes, loaders, actions, scripts, or lifecycle-hook modules; browser code
52
+ * must not cross this boundary.
53
+ *
54
+ * This module does not create client singletons, generate collection types, or
55
+ * configure the host. It only applies the app-specific client type and
56
+ * re-exports the host-owned getters (plus the viewer preview-state helper).
57
+ */
58
+ import type { BylineClient } from '@byline/client'
59
+ import {
60
+ getAdminBylineClient as getHostAdminBylineClient,
61
+ getSystemBylineClient as getHostSystemBylineClient,
62
+ } from '@byline/host-tanstack-start/integrations/byline-client'
63
+ import { getPublicBylineClient as getHostPublicBylineClient } from '@byline/host-tanstack-start/integrations/byline-public-client'
64
+ import {
65
+ getViewerBylineClient as getHostViewerBylineClient,
66
+ isPreviewActive,
67
+ } from '@byline/host-tanstack-start/integrations/byline-viewer-client'
68
+
69
+ import type { BylineCollections } from './collections/index.js'
70
+
71
+ type AppBylineClient = BylineClient<BylineCollections>
72
+
73
+ export const getAdminBylineClient = getHostAdminBylineClient as () => AppBylineClient
74
+ export const getPublicBylineClient = getHostPublicBylineClient as () => AppBylineClient
75
+ export const getSystemBylineClient = getHostSystemBylineClient as () => AppBylineClient
76
+ export const getViewerBylineClient = getHostViewerBylineClient as () => AppBylineClient
77
+ export { isPreviewActive }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Compile-time contract between this application's collection schemas and its
3
+ * generated collection-type projection.
4
+ *
5
+ * ```text
6
+ * collections/index.ts generated/collection-types.ts
7
+ * `typeof collections` `CollectionFields*ByPath`
8
+ * | |
9
+ * v v
10
+ * inferred registry ----------- Exact -------- generated registry
11
+ * |
12
+ * v
13
+ * TypeScript error on drift
14
+ * ```
15
+ *
16
+ * The generated file is deliberately a standalone projection for typed clients
17
+ * and frontend code; it does not import the runtime schemas. This module joins
18
+ * those two app-owned sources at compile time and requires their collection
19
+ * keys, ordinary field shapes, and all-locale field shapes to match exactly.
20
+ *
21
+ * This file has no runtime behavior. Keep it application-owned: `@byline/core`
22
+ * provides the inference and code-generation machinery, but only this
23
+ * application knows its concrete collection tuple and generated output.
24
+ */
25
+
26
+ import type { CollectionFieldData, CollectionFieldDataAllLocales } from '@byline/core'
27
+
28
+ import type { collections } from './collections/index.js'
29
+ import type {
30
+ CollectionFieldsAllLocalesByPath,
31
+ CollectionFieldsByPath,
32
+ } from './generated/collection-types.js'
33
+
34
+ type InferredFieldsByPath = {
35
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldData<Definition>
36
+ }
37
+
38
+ type InferredFieldsAllLocalesByPath = {
39
+ [Definition in (typeof collections)[number] as Definition['path']]: CollectionFieldDataAllLocales<Definition>
40
+ }
41
+
42
+ type IsAny<Value> = 0 extends 1 & Value ? true : false
43
+ type Exact<Left, Right> = IsAny<Left> extends true
44
+ ? IsAny<Right>
45
+ : IsAny<Right> extends true
46
+ ? false
47
+ : (<Value>() => Value extends Left ? 1 : 2) extends <Value>() =>
48
+ Value extends Right ? 1 : 2
49
+ ? (<Value>() => Value extends Right ? 1 : 2) extends <Value>() =>
50
+ Value extends Left ? 1 : 2
51
+ ? true
52
+ : false
53
+ : false
54
+ type Assert<Value extends true> = Value
55
+
56
+ type GeneratedKeysMatchRuntimeRegistry = Assert<
57
+ Exact<keyof CollectionFieldsByPath, keyof InferredFieldsByPath>
58
+ >
59
+ type GeneratedFieldsMatchRuntimeRegistry = Assert<
60
+ Exact<CollectionFieldsByPath, InferredFieldsByPath>
61
+ >
62
+ type GeneratedAllLocalesKeysMatchRuntimeRegistry = Assert<
63
+ Exact<keyof CollectionFieldsAllLocalesByPath, keyof InferredFieldsAllLocalesByPath>
64
+ >
65
+ type GeneratedAllLocalesFieldsMatchRuntimeRegistry = Assert<
66
+ Exact<CollectionFieldsAllLocalesByPath, InferredFieldsAllLocalesByPath>
67
+ >
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { InferCollectionRegistry } from '@byline/core'
10
+
11
+ import type { CollectionFieldsByPath } from '../generated/collection-types.js'
12
+
13
+ /** Add schema-only collection imports here; keep admin presentation separate. */
14
+ export const collections = [] as const
15
+
16
+ export type InferredBylineCollections = InferCollectionRegistry<typeof collections>
17
+ export type BylineCollections = CollectionFieldsByPath
@@ -0,0 +1,7 @@
1
+ /** Server-only lifecycle hook registry. Imported only by server.config.ts. */
2
+ import type { ServerHooksConfig } from '@byline/core'
3
+
4
+ export const serverHooks = {
5
+ collections: {},
6
+ uploads: {},
7
+ } satisfies ServerHooksConfig
@@ -0,0 +1,11 @@
1
+ // Generated by @byline/core/codegen
2
+ // Format version: 1
3
+ // Hash: 4ef42b796dc35fceb394f97eaef885c5795c52031ba91f9ca6ca7e0fd2f20608
4
+
5
+ export type CollectionFieldsByPath = {
6
+ }
7
+
8
+ export type CollectionFieldsAllLocalesByPath = {
9
+ }
10
+
11
+ export type CollectionPath = keyof CollectionFieldsByPath
@@ -12,9 +12,9 @@
12
12
  * translation bundle.
13
13
  *
14
14
  * The locale arrays themselves live in `./locales.ts`, a dependency-free leaf
15
- * module, so the public frontend can import them without pulling in the admin
16
- * translation graph this file depends on. Re-exported here for back-compat
17
- * with existing `~/i18n` importers.
15
+ * module exposed to public frontend code by `./public.ts`, without pulling in
16
+ * the admin translation graph this file depends on. Re-exported here for
17
+ * back-compat with existing `~/i18n` importers.
18
18
  *
19
19
  * `interface` locales govern the CMS admin UI language.
20
20
  * `content` locales govern the languages a document can be published in.
@@ -12,12 +12,12 @@
12
12
  * registry). `byline/i18n.ts` consumes these to assemble the
13
13
  * `defineServerConfig` / `defineClientConfig` payload.
14
14
  *
15
- * Kept separate from `byline/i18n.ts` so a public frontend (or a collection
16
- * schema file loaded outside Vite — e.g. when running seeds via tsx) can
17
- * import the locale arrays without dragging in the admin translation graph
18
- * (`@byline/i18n/admin`) that `i18n.ts` depends on. Importing `byline/i18n.ts`
19
- * would; importing this won't — which keeps the lazy admin/public bundle split
20
- * (`src/routes/_byline/route.lazy.tsx`) intact.
15
+ * Kept separate from `byline/i18n.ts` so `byline/public.ts` can expose the
16
+ * locale arrays to a public frontend (or a collection schema file loaded
17
+ * outside Vite — e.g. when running seeds via tsx) without dragging in the
18
+ * admin translation graph (`@byline/i18n/admin`) that `i18n.ts` depends on.
19
+ * Importing `byline/i18n.ts` would; importing this won't — which keeps the lazy
20
+ * admin/public bundle split (`src/routes/_byline/route.lazy.tsx`) intact.
21
21
  *
22
22
  * `interface` locales govern the CMS admin UI language; `content` locales
23
23
  * govern the languages a document can be published in.
@@ -33,10 +33,10 @@ export interface LocaleDefinition {
33
33
  // (or a third-party plugin merged in via `mergeTranslations(...)`).
34
34
  // `adminTranslations({ locales })` in `byline/i18n.ts` throws at boot if a
35
35
  // requested code is not bundled — `@byline/i18n/admin` ships English and French.
36
- export const interfaceLocales: LocaleDefinition[] = [
36
+ export const interfaceLocales = [
37
37
  { code: 'en', label: 'English' },
38
38
  { code: 'fr', label: 'Français' },
39
- ]
39
+ ] as const satisfies readonly LocaleDefinition[]
40
40
 
41
41
  /** Locales a document can be published in. */
42
42
  export const contentLocales = [
@@ -44,4 +44,4 @@ export const contentLocales = [
44
44
  { code: 'fr', label: 'Français' },
45
45
  { code: 'es', label: 'Español' },
46
46
  { code: 'de', label: 'Deutsch' },
47
- ] as const
47
+ ] as const satisfies readonly LocaleDefinition[]
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /** Client-safe public configuration. Keep this barrel free of admin and server imports. */
10
+ export { contentLocales, interfaceLocales } from './locales.js'
11
+ export { routes } from './routes.js'
12
+ export type { LocaleDefinition } from './locales.js'
@@ -7,17 +7,18 @@
7
7
  */
8
8
 
9
9
  /**
10
- * URL segments for admin and (future) public API routes. Defaults of
11
- * `/admin` and `/api` are applied automatically by `resolveRoutes()` —
12
- * keys only need to be set here when overriding either default.
10
+ * Client-safe URL paths for admin, sign-in, and the future public API.
11
+ * Resolve once at this client-safe configuration boundary so consumers only
12
+ * read canonical paths.
13
13
  */
14
14
 
15
- import type { RoutesConfig } from '@byline/core'
15
+ import { resolveRoutes } from '@byline/core'
16
16
 
17
- export const routes: Partial<RoutesConfig> = {
17
+ export const routes = resolveRoutes({
18
18
  admin: '/admin',
19
19
  api: '/api',
20
- }
20
+ signIn: '/sign-in',
21
+ })
21
22
 
22
23
  /**
23
24
  * Fallback used by both server and admin entry points when no
@@ -0,0 +1,81 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { dirname, resolve } from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ import { emitCollectionTypes } from '@byline/core/codegen'
7
+
8
+ import { collections } from '../collections/index.js'
9
+
10
+ const defaultOutputPath = fileURLToPath(
11
+ new URL('../generated/collection-types.ts', import.meta.url)
12
+ )
13
+
14
+ export type GenerationStatus = 'current' | 'unchanged' | 'written'
15
+
16
+ export function writeCollectionTypes(
17
+ outputPath: string,
18
+ source: string,
19
+ check: boolean
20
+ ): GenerationStatus {
21
+ const existing = existsSync(outputPath) ? readFileSync(outputPath, 'utf8') : undefined
22
+
23
+ if (check) {
24
+ if (existing === undefined) {
25
+ throw new Error(
26
+ `generated collection types are missing: ${outputPath}. Run pnpm byline:generate`
27
+ )
28
+ }
29
+ if (existing !== source) {
30
+ throw new Error(
31
+ `generated collection types are stale: ${outputPath}. Run pnpm byline:generate`
32
+ )
33
+ }
34
+ return 'current'
35
+ }
36
+
37
+ if (existing === source) return 'unchanged'
38
+
39
+ mkdirSync(dirname(outputPath), { recursive: true })
40
+ const temporaryPath = `${outputPath}.${process.pid}.${randomUUID()}.tmp`
41
+ try {
42
+ writeFileSync(temporaryPath, source, 'utf8')
43
+ renameSync(temporaryPath, outputPath)
44
+ } finally {
45
+ rmSync(temporaryPath, { force: true })
46
+ }
47
+ return 'written'
48
+ }
49
+
50
+ function parseArgs(args: readonly string[]): { check: boolean } {
51
+ const unknown = args.filter((arg) => arg !== '--check')
52
+ if (unknown.length > 0) throw new Error(`unknown argument: ${unknown.join(', ')}`)
53
+ if (args.filter((arg) => arg === '--check').length > 1) {
54
+ throw new Error('duplicate argument: --check')
55
+ }
56
+ return { check: args.includes('--check') }
57
+ }
58
+
59
+ export function generateCollectionTypes(
60
+ outputPath = defaultOutputPath,
61
+ check = false
62
+ ): { hash: string; status: GenerationStatus } {
63
+ const { source, hash } = emitCollectionTypes(collections)
64
+ return { hash, status: writeCollectionTypes(outputPath, source, check) }
65
+ }
66
+
67
+ function main(): void {
68
+ const { check } = parseArgs(process.argv.slice(2))
69
+ const { hash, status } = generateCollectionTypes(defaultOutputPath, check)
70
+ console.log(`collection types: ${collections.length} collections ${hash.slice(0, 12)} ${status}`)
71
+ }
72
+
73
+ const entryPath = process.argv[1]
74
+ if (entryPath !== undefined && pathToFileURL(resolve(entryPath)).href === import.meta.url) {
75
+ try {
76
+ main()
77
+ } catch (error) {
78
+ console.error(`collection type generation failed: ${(error as Error).message}`)
79
+ process.exitCode = 1
80
+ }
81
+ }
@@ -14,8 +14,7 @@
14
14
  * read it back with `getBylineCore<AdminStore>()`.
15
15
  *
16
16
  * This is the minimal scaffold: no example collections registered. Add
17
- * collection schemas to the `collections` array as you create them under
18
- * `byline/collections/<name>/schema.ts`.
17
+ * collection schemas to the shared tuple in `byline/collections/index.ts`.
19
18
  */
20
19
 
21
20
  import { type AdminStore, registerAdminAbilities } from '@byline/admin'
@@ -23,7 +22,6 @@ import { JwtSessionProvider } from '@byline/admin/auth'
23
22
  import { type BylineCore, initBylineCore } from '@byline/core'
24
23
  import { pgAdapter } from '@byline/db-postgres'
25
24
  import { createAdminStore } from '@byline/db-postgres/admin'
26
- import { getAdminBylineClient } from '@byline/host-tanstack-start/integrations/byline-client'
27
25
  import {
28
26
  lexicalEditorEmbedServer,
29
27
  lexicalEditorPopulateServer,
@@ -31,13 +29,14 @@ import {
31
29
  } from '@byline/richtext-lexical/server'
32
30
  import { localStorageProvider } from '@byline/storage-local'
33
31
 
32
+ import { getAdminBylineClient } from './clients.server.js'
33
+ import { collections } from './collections/index.js'
34
+ import { serverHooks } from './collections/server-hooks.js'
34
35
  import { i18n } from './i18n.js'
35
36
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
36
37
 
37
38
  const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
38
39
 
39
- const collections: Parameters<typeof pgAdapter>[0]['collections'] = []
40
-
41
40
  // HMR-safe singleton. Vite's program reload re-evaluates this module
42
41
  // without disposing the previous module's resources — every reload
43
42
  // would otherwise allocate a fresh pg `Pool` (max: 20) inside
@@ -93,6 +92,7 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
93
92
  i18n,
94
93
  routes,
95
94
  collections,
95
+ hooks: serverHooks,
96
96
  db,
97
97
  adminStore,
98
98
  // Local filesystem — suitable for development and self-hosted
@@ -8,13 +8,14 @@
8
8
 
9
9
  /**
10
10
  * Registers Byline's client-side config (collection admin UI configs,
11
- * field editors, i18n, routes) in the current module graph. Imported as
12
- * a side-effect from `src/routes/_byline/route.lazy.tsx` so registration
13
- * fires only when a `_byline/*` URL matches — keeping the Lexical
14
- * editor module graph out of public-route bundles entirely.
11
+ * field editors, i18n, routes) in the current module graph. The `_byline`
12
+ * route registers it from two complementary points: a dynamic import in
13
+ * `route.tsx` covers child loaders, while the side-effect import in
14
+ * `route.lazy.tsx` covers component render and initial hydration. Keeping
15
+ * both imports behind `_byline/*` keeps the admin graph off public routes.
15
16
  *
16
- * In TanStack Start with Vite 6 the server entry (`src/server.ts`) and
17
- * the SSR rendering context run in separate Vite environments, so
17
+ * In TanStack Start the server entry (`src/server.ts`) and the SSR rendering
18
+ * context run in separate Vite environments, so
18
19
  * importing this file from `src/server.ts` would NOT propagate the
19
20
  * registration into the SSR render module graph.
20
21
  */
@@ -28,11 +29,12 @@ import { RichTextField as LexicalRichTextField } from '@byline/richtext-lexical'
28
29
  // commented `richText` block below for the exact shape.
29
30
  // import { lexicalEditor } from '@byline/richtext-lexical'
30
31
 
31
- import { Docs, DocsAdmin } from './collections/docs/index.js'
32
- import { Media, MediaAdmin } from './collections/media/index.js'
33
- import { News, NewsAdmin } from './collections/news/index.js'
34
- import { NewsCategories, NewsCategoriesAdmin } from './collections/news-categories/index.js'
35
- import { Pages, PagesAdmin } from './collections/pages/index.js'
32
+ import { DocsAdmin } from './collections/docs/admin.js'
33
+ import { collections } from './collections/index.js'
34
+ import { MediaAdmin } from './collections/media/admin.js'
35
+ import { NewsAdmin } from './collections/news/admin.js'
36
+ import { NewsCategoriesAdmin } from './collections/news-categories/admin.js'
37
+ import { PagesAdmin } from './collections/pages/admin.js'
36
38
  import { i18n } from './i18n.js'
37
39
  import { DEFAULT_SERVER_URL, routes } from './routes.js'
38
40
 
@@ -42,7 +44,7 @@ export const config: ClientConfig = {
42
44
  serverURL,
43
45
  i18n,
44
46
  routes,
45
- collections: [Docs, News, Pages, Media, NewsCategories],
47
+ collections,
46
48
  admin: [DocsAdmin, NewsAdmin, PagesAdmin, MediaAdmin, NewsCategoriesAdmin],
47
49
  fields: {
48
50
  // Default registration — every `type: 'richText'` field gets the full
@@ -58,19 +60,14 @@ export const config: ClientConfig = {
58
60
  // mutations are local to this call. Per-field `editorConfig`
59
61
  // continues to take precedence over whatever is baked in here.
60
62
  //
61
- // import {
62
- // lexicalEditor,
63
- // AdmonitionExtension,
64
- // CodeHighlightExtension,
65
- // TableExtension,
66
- // } from '@byline/richtext-lexical'
63
+ // import { builtInExtensions, lexicalEditor } from '@byline/richtext-lexical/config'
67
64
  //
68
65
  // richText: {
69
66
  // editor: lexicalEditor((c) => {
70
67
  // c.extensions
71
- // .remove(TableExtension)
72
- // .remove(CodeHighlightExtension)
73
- // .remove(AdmonitionExtension)
68
+ // .remove(builtInExtensions.Table)
69
+ // .remove(builtInExtensions.CodeHighlight)
70
+ // .remove(builtInExtensions.Admonition)
74
71
  // c.settings.placeholderText = 'Start writing...'
75
72
  // return c
76
73
  // }),
@@ -53,7 +53,8 @@ export type PhotoBlockFields = BlockFieldData<typeof PhotoBlock>
53
53
 
54
54
  /**
55
55
  * Full block instance shape (`_id`, `_type` + fields) as it appears
56
- * inside a document tree. Use this as the prop type for the photo
57
- * block renderer.
56
+ * inside a document tree. This alias is useful for schema-local helpers;
57
+ * app-facing renderers should import `PhotoBlockData` from
58
+ * `byline/generated/collection-types.ts` and overlay populated relations.
58
59
  */
59
60
  export type PhotoBlockData = BlockData<typeof PhotoBlock>
@@ -0,0 +1,77 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Typed, server-only access to this application's Byline clients.
11
+ *
12
+ * ```text
13
+ * generated/collection-types.ts
14
+ * `CollectionFieldsByPath`
15
+ * |
16
+ * v
17
+ * collections/index.ts --------------------.
18
+ * `BylineCollections` |
19
+ * v
20
+ * @byline/host-tanstack-start ------> clients.server.ts
21
+ * runtime client getters |
22
+ * v
23
+ * typed `get*BylineClient()` getters
24
+ * ```
25
+ *
26
+ * Why this boundary exists:
27
+ *
28
+ * The TanStack host owns the runtime admin, public, system, and viewer client
29
+ * singletons. It intentionally knows nothing about an application's collection
30
+ * schemas, so its getters cannot provide application-specific collection paths
31
+ * and field shapes. The generated `CollectionFieldsByPath` registry is exported
32
+ * as `BylineCollections` by `collections/index.ts`; this module joins the host
33
+ * getters and app registry once instead of repeating a type assertion at every
34
+ * server call site.
35
+ *
36
+ * Typical server usage:
37
+ *
38
+ * ```ts
39
+ * const publicClient = getPublicBylineClient() // anonymous published reads
40
+ * const viewerClient = getViewerBylineClient() // public or admin-preview reads
41
+ * const adminClient = getAdminBylineClient() // current admin request authority
42
+ * const systemClient = getSystemBylineClient() // trusted hooks and maintenance
43
+ * ```
44
+ *
45
+ * Calls such as `systemClient.collection('docs')` are therefore checked against
46
+ * this application's generated registry and return the canonical generated
47
+ * field shape for `docs`. `collection-types.contract.ts` separately proves that
48
+ * those generated shapes still match the runtime collection schemas exactly.
49
+ *
50
+ * The `.server.ts` suffix is intentional. Import these getters only from server
51
+ * routes, loaders, actions, scripts, or lifecycle-hook modules; browser code
52
+ * must not cross this boundary.
53
+ *
54
+ * This module does not create client singletons, generate collection types, or
55
+ * configure the host. It only applies the app-specific client type and
56
+ * re-exports the host-owned getters (plus the viewer preview-state helper).
57
+ */
58
+ import type { BylineClient } from '@byline/client'
59
+ import {
60
+ getAdminBylineClient as getHostAdminBylineClient,
61
+ getSystemBylineClient as getHostSystemBylineClient,
62
+ } from '@byline/host-tanstack-start/integrations/byline-client'
63
+ import { getPublicBylineClient as getHostPublicBylineClient } from '@byline/host-tanstack-start/integrations/byline-public-client'
64
+ import {
65
+ getViewerBylineClient as getHostViewerBylineClient,
66
+ isPreviewActive,
67
+ } from '@byline/host-tanstack-start/integrations/byline-viewer-client'
68
+
69
+ import type { BylineCollections } from './collections/index.js'
70
+
71
+ type AppBylineClient = BylineClient<BylineCollections>
72
+
73
+ export const getAdminBylineClient = getHostAdminBylineClient as () => AppBylineClient
74
+ export const getPublicBylineClient = getHostPublicBylineClient as () => AppBylineClient
75
+ export const getSystemBylineClient = getHostSystemBylineClient as () => AppBylineClient
76
+ export const getViewerBylineClient = getHostViewerBylineClient as () => AppBylineClient
77
+ export { isPreviewActive }