@byline/cli 3.20.4 → 3.21.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.
- package/README.md +14 -0
- package/dist/phases/scaffold.d.ts +2 -0
- package/dist/phases/scaffold.d.ts.map +1 -1
- package/dist/phases/scaffold.js +26 -0
- package/dist/phases/scaffold.js.map +1 -1
- package/dist/phases/scaffold.test.d.ts +2 -0
- package/dist/phases/scaffold.test.d.ts.map +1 -0
- package/dist/phases/scaffold.test.js +29 -0
- package/dist/phases/scaffold.test.js.map +1 -0
- package/dist/templates/byline/admin.config.ts +8 -7
- package/dist/templates/byline/client.server.ts +32 -0
- package/dist/templates/byline/collection-types.contract.ts +42 -0
- package/dist/templates/byline/collections/index.ts +17 -0
- package/dist/templates/byline/generated/collection-types.ts +11 -0
- package/dist/templates/byline/i18n.ts +3 -3
- package/dist/templates/byline/locales.ts +9 -9
- package/dist/templates/byline/public.ts +12 -0
- package/dist/templates/byline/scripts/generate-types.ts +81 -0
- package/dist/templates/byline/server.config.ts +3 -5
- package/dist/templates/byline-examples/admin.config.ts +12 -10
- package/dist/templates/byline-examples/client.server.ts +32 -0
- package/dist/templates/byline-examples/collection-types.contract.ts +42 -0
- package/dist/templates/byline-examples/collections/docs/hooks.ts +11 -11
- package/dist/templates/byline-examples/collections/docs/schema.ts +21 -12
- package/dist/templates/byline-examples/collections/index.ts +22 -0
- package/dist/templates/byline-examples/collections/media/schema.ts +13 -0
- package/dist/templates/byline-examples/collections/news/schema.ts +13 -0
- package/dist/templates/byline-examples/collections/news-categories/schema.ts +13 -0
- package/dist/templates/byline-examples/collections/pages/schema.ts +13 -0
- package/dist/templates/byline-examples/generated/collection-types.ts +139 -0
- package/dist/templates/byline-examples/i18n.ts +3 -3
- package/dist/templates/byline-examples/locales.ts +9 -9
- package/dist/templates/byline-examples/public.ts +12 -0
- package/dist/templates/byline-examples/scripts/generate-types.ts +81 -0
- package/dist/templates/byline-examples/server.config.ts +2 -12
- package/dist/templates/routes/_byline/admin/collections/$collection/$id/api.tsx +1 -6
- package/dist/templates/routes/_byline/admin/collections/$collection/$id/history.tsx +1 -7
- package/dist/templates/routes/_byline/admin/collections/$collection/$id/index.tsx +1 -6
- package/dist/templates/ui-byline/blocks/photo-block/index.tsx +1 -2
- package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +1 -1
- package/dist/templates/ui-byline/render-blocks.tsx +3 -7
- package/package.json +1 -1
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
* config from it. Anything the schema *statically imports* therefore ships to
|
|
18
18
|
* the client. Hook *bodies*, however, only ever run server-side.
|
|
19
19
|
*
|
|
20
|
-
* Search indexing is the canonical reason this matters: it imports
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Search indexing is the canonical reason this matters: it imports the
|
|
21
|
+
* server-only app client boundary from `byline/client.server.ts`. Importing
|
|
22
|
+
* that from the schema would drag server code into the browser bundle (fatal
|
|
23
|
+
* in dev). The schema avoids it by referencing these hooks through a
|
|
24
|
+
* **dynamic import** instead of an inline object:
|
|
25
25
|
*
|
|
26
26
|
* // schema.ts
|
|
27
27
|
* hooks: () => import('./hooks.js')
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
54
|
import { defineHooks } from '@byline/core'
|
|
55
|
-
import { getSystemBylineClient } from '
|
|
55
|
+
import { getSystemBylineClient } from '../../client.server.js'
|
|
56
56
|
|
|
57
57
|
export default defineHooks({
|
|
58
58
|
beforeCreate: async ({ data, collectionPath }) => {
|
|
@@ -62,7 +62,7 @@ export default defineHooks({
|
|
|
62
62
|
console.log(
|
|
63
63
|
`afterCreate: document ${documentId} (version ${documentVersionId}) created in '${collectionPath}'`
|
|
64
64
|
)
|
|
65
|
-
await getSystemBylineClient().collection(
|
|
65
|
+
await getSystemBylineClient().collection('docs').indexDocument(documentId)
|
|
66
66
|
},
|
|
67
67
|
beforeUpdate: async ({ data, collectionPath }) => {
|
|
68
68
|
console.log(`beforeUpdate: updating a document in '${collectionPath}'`, data)
|
|
@@ -71,7 +71,7 @@ export default defineHooks({
|
|
|
71
71
|
console.log(
|
|
72
72
|
`afterUpdate: document ${documentId} (version ${documentVersionId}) in '${collectionPath}' updated`
|
|
73
73
|
)
|
|
74
|
-
await getSystemBylineClient().collection(
|
|
74
|
+
await getSystemBylineClient().collection('docs').indexDocument(documentId)
|
|
75
75
|
},
|
|
76
76
|
beforeStatusChange: async ({ documentId, collectionPath, previousStatus, nextStatus }) => {
|
|
77
77
|
console.log(
|
|
@@ -85,21 +85,21 @@ export default defineHooks({
|
|
|
85
85
|
console.log(
|
|
86
86
|
`afterStatusChange: ${documentId} in '${collectionPath}' ${previousStatus} → ${nextStatus}`
|
|
87
87
|
)
|
|
88
|
-
await getSystemBylineClient().collection(
|
|
88
|
+
await getSystemBylineClient().collection('docs').indexDocument(documentId)
|
|
89
89
|
},
|
|
90
90
|
beforeUnpublish: async ({ documentId, collectionPath }) => {
|
|
91
91
|
console.log(`beforeUnpublish: unpublishing ${documentId} in '${collectionPath}'`)
|
|
92
92
|
},
|
|
93
93
|
afterUnpublish: async ({ documentId, collectionPath }) => {
|
|
94
94
|
console.log(`afterUnpublish: ${documentId} in '${collectionPath}' unpublished`)
|
|
95
|
-
await getSystemBylineClient().collection(
|
|
95
|
+
await getSystemBylineClient().collection('docs').indexDocument(documentId)
|
|
96
96
|
},
|
|
97
97
|
beforeDelete: async ({ documentId, collectionPath }) => {
|
|
98
98
|
console.log(`beforeDelete: deleting ${documentId} in '${collectionPath}'`)
|
|
99
99
|
},
|
|
100
100
|
afterDelete: async ({ documentId, collectionPath }) => {
|
|
101
101
|
console.log(`afterDelete: ${documentId} in '${collectionPath}' deleted`)
|
|
102
|
-
await getSystemBylineClient().collection(
|
|
102
|
+
await getSystemBylineClient().collection('docs').removeFromIndex(documentId)
|
|
103
103
|
},
|
|
104
104
|
// Fires on any structural tree change (place / reorder / re-parent /
|
|
105
105
|
// promote-on-delete) for a `tree: true` collection. The payload carries the
|
|
@@ -41,13 +41,22 @@ export const Docs = defineCollection({
|
|
|
41
41
|
// placement from the source directory layout (see
|
|
42
42
|
// byline/scripts/import-docs.ts --tree).
|
|
43
43
|
tree: true,
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// the
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
//
|
|
44
|
+
// Admin list-view quick-search. The collection list route's search box
|
|
45
|
+
// matches these top-level text-store fields with substring queries (ILIKE, in
|
|
46
|
+
// the Postgres adapter) against `store_text` — a lightweight "find the row I
|
|
47
|
+
// mean" affordance that needs no indexing and no lifecycle hooks. Falls back
|
|
48
|
+
// to `useAsTitle` when omitted; declared explicitly here for guidance.
|
|
49
|
+
listSearch: ['title'],
|
|
50
|
+
// Search-*provider* config (docs/05-reading-and-delivery/07-search.md) —
|
|
51
|
+
// distinct from `listSearch` above. Each key names the part a field plays in
|
|
52
|
+
// the index: `body` fields feed the full-text search vector (`title` is
|
|
53
|
+
// display-only unless listed here, so we include it and boost it into the
|
|
54
|
+
// heaviest weight class); `facets`, `filters`, and `zones` round out the
|
|
55
|
+
// surface. This drives the pluggable SearchProvider (full-text search in
|
|
56
|
+
// @byline/search-postgres) and requires a `search` provider registered in
|
|
57
|
+
// byline/server.config.ts. Unlike `listSearch`, `search` is inert on its own
|
|
58
|
+
// — opting in here is what makes the index / reindex / deindex lifecycle
|
|
59
|
+
// hooks in ./hooks.ts keep this collection's provider index in sync.
|
|
51
60
|
// `content` is a container (blocks) field: naming it walks every text-bearing
|
|
52
61
|
// child (richtext body, image alt + caption) into the searchable body —
|
|
53
62
|
// omit it and document bodies are silently absent from the index.
|
|
@@ -60,11 +69,11 @@ export const Docs = defineCollection({
|
|
|
60
69
|
// a collection *schema* is isomorphic — Byline bundles it into the browser
|
|
61
70
|
// admin too (the admin reads field config from it), so anything the schema
|
|
62
71
|
// *statically imports* ships to the client. Search indexing needs a
|
|
63
|
-
// server-only import
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
72
|
+
// server-only import from the app's `client.server.ts` boundary, which would
|
|
73
|
+
// crash the browser bundle if imported here. The **loader form** — a thunk
|
|
74
|
+
// that dynamically imports a sibling module — keeps `./hooks.ts` and its
|
|
75
|
+
// entire import graph out of the client bundle while running exactly like
|
|
76
|
+
// inline hooks on the server.
|
|
68
77
|
// See ./hooks.ts for the worked example and the full explanation.
|
|
69
78
|
hooks: () => import('./hooks.js'),
|
|
70
79
|
fields: [
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
import { Docs } from './docs/schema.js'
|
|
14
|
+
import { Media } from './media/schema.js'
|
|
15
|
+
import { News } from './news/schema.js'
|
|
16
|
+
import { NewsCategories } from './news-categories/schema.js'
|
|
17
|
+
import { Pages } from './pages/schema.js'
|
|
18
|
+
|
|
19
|
+
export const collections = [Docs, News, Pages, Media, NewsCategories] as const
|
|
20
|
+
|
|
21
|
+
export type InferredBylineCollections = InferCollectionRegistry<typeof collections>
|
|
22
|
+
export type BylineCollections = CollectionFieldsByPath
|
|
@@ -50,6 +50,19 @@ export const Media = defineCollection({
|
|
|
50
50
|
archived: { label: 'Archived', verb: 'Archive' },
|
|
51
51
|
}),
|
|
52
52
|
showStats: true,
|
|
53
|
+
// Admin list-view quick-search. The collection list route's search box
|
|
54
|
+
// matches these top-level text-store fields with substring queries (ILIKE, in
|
|
55
|
+
// the Postgres adapter) against `store_text` — a lightweight "find the row I
|
|
56
|
+
// mean" affordance that needs no indexing and no lifecycle hooks. Falls back
|
|
57
|
+
// to `useAsTitle` when omitted; declared explicitly here for guidance.
|
|
58
|
+
listSearch: ['title'],
|
|
59
|
+
// Search-*provider* config (docs/05-reading-and-delivery/07-search.md) —
|
|
60
|
+
// distinct from `listSearch` above. This drives the pluggable SearchProvider
|
|
61
|
+
// (full-text search in @byline/search-postgres) and offers richer options:
|
|
62
|
+
// per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
|
|
63
|
+
// `listSearch`, `search` is inert on its own — it MUST be paired with index /
|
|
64
|
+
// reindex / deindex document-lifecycle hooks that keep the provider index in
|
|
65
|
+
// sync on create / update / publish / delete (see this collection's hooks).
|
|
53
66
|
search: { body: ['title', 'caption'] },
|
|
54
67
|
fields: [
|
|
55
68
|
{
|
|
@@ -30,6 +30,19 @@ export const News = defineCollection({
|
|
|
30
30
|
archived: { label: 'Archived', verb: 'Archive' },
|
|
31
31
|
}),
|
|
32
32
|
showStats: true,
|
|
33
|
+
// Admin list-view quick-search. The collection list route's search box
|
|
34
|
+
// matches these top-level text-store fields with substring queries (ILIKE, in
|
|
35
|
+
// the Postgres adapter) against `store_text` — a lightweight "find the row I
|
|
36
|
+
// mean" affordance that needs no indexing and no lifecycle hooks. Falls back
|
|
37
|
+
// to `useAsTitle` when omitted; declared explicitly here for guidance.
|
|
38
|
+
listSearch: ['title'],
|
|
39
|
+
// Search-*provider* config (docs/05-reading-and-delivery/07-search.md) —
|
|
40
|
+
// distinct from `listSearch` above. This drives the pluggable SearchProvider
|
|
41
|
+
// (full-text search in @byline/search-postgres) and offers richer options:
|
|
42
|
+
// per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
|
|
43
|
+
// `listSearch`, `search` is inert on its own — it MUST be paired with index /
|
|
44
|
+
// reindex / deindex document-lifecycle hooks that keep the provider index in
|
|
45
|
+
// sync on create / update / publish / delete (see this collection's hooks).
|
|
33
46
|
search: { body: ['title'] },
|
|
34
47
|
useAsTitle: 'title',
|
|
35
48
|
useAsPath: 'title',
|
|
@@ -21,6 +21,19 @@ export const NewsCategories = defineCollection({
|
|
|
21
21
|
// `published` and the form shows only Save / Close.
|
|
22
22
|
workflow: SINGLE_STATUS_WORKFLOW,
|
|
23
23
|
showStats: true,
|
|
24
|
+
// Admin list-view quick-search. The collection list route's search box
|
|
25
|
+
// matches these top-level text-store fields with substring queries (ILIKE, in
|
|
26
|
+
// the Postgres adapter) against `store_text` — a lightweight "find the row I
|
|
27
|
+
// mean" affordance that needs no indexing and no lifecycle hooks. Falls back
|
|
28
|
+
// to `useAsTitle` when omitted; declared explicitly here for guidance.
|
|
29
|
+
listSearch: ['name'],
|
|
30
|
+
// Search-*provider* config (docs/05-reading-and-delivery/07-search.md) —
|
|
31
|
+
// distinct from `listSearch` above. This drives the pluggable SearchProvider
|
|
32
|
+
// (full-text search in @byline/search-postgres) and offers richer options:
|
|
33
|
+
// per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
|
|
34
|
+
// `listSearch`, `search` is inert on its own — it MUST be paired with index /
|
|
35
|
+
// reindex / deindex document-lifecycle hooks that keep the provider index in
|
|
36
|
+
// sync on create / update / publish / delete (see this collection's hooks).
|
|
24
37
|
search: { body: ['name'] },
|
|
25
38
|
useAsTitle: 'name',
|
|
26
39
|
useAsPath: 'name',
|
|
@@ -32,6 +32,19 @@ export const Pages = defineCollection({
|
|
|
32
32
|
archived: { label: 'Archived', verb: 'Archive' },
|
|
33
33
|
}),
|
|
34
34
|
showStats: true,
|
|
35
|
+
// Admin list-view quick-search. The collection list route's search box
|
|
36
|
+
// matches these top-level text-store fields with substring queries (ILIKE, in
|
|
37
|
+
// the Postgres adapter) against `store_text` — a lightweight "find the row I
|
|
38
|
+
// mean" affordance that needs no indexing and no lifecycle hooks. Falls back
|
|
39
|
+
// to `useAsTitle` when omitted; declared explicitly here for guidance.
|
|
40
|
+
listSearch: ['title'],
|
|
41
|
+
// Search-*provider* config (docs/05-reading-and-delivery/07-search.md) —
|
|
42
|
+
// distinct from `listSearch` above. This drives the pluggable SearchProvider
|
|
43
|
+
// (full-text search in @byline/search-postgres) and offers richer options:
|
|
44
|
+
// per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
|
|
45
|
+
// `listSearch`, `search` is inert on its own — it MUST be paired with index /
|
|
46
|
+
// reindex / deindex document-lifecycle hooks that keep the provider index in
|
|
47
|
+
// sync on create / update / publish / delete (see this collection's hooks).
|
|
35
48
|
search: { body: ['title'] },
|
|
36
49
|
useAsTitle: 'title',
|
|
37
50
|
useAsPath: 'title',
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Generated by @byline/core/codegen
|
|
2
|
+
// Format version: 1
|
|
3
|
+
// Hash: a32bba417302b4e2c90703e47a1f326d4db64cabd11194f8eed816e78442178e
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
JsonValue,
|
|
7
|
+
RelatedDocumentValue,
|
|
8
|
+
StoredFileValue,
|
|
9
|
+
} from '@byline/core'
|
|
10
|
+
|
|
11
|
+
export type PhotoBlockData = {
|
|
12
|
+
_id: string
|
|
13
|
+
_type: 'photoBlock'
|
|
14
|
+
display?: 'default' | 'wide' | 'full_width' | undefined
|
|
15
|
+
photo?: RelatedDocumentValue | undefined
|
|
16
|
+
alt: string
|
|
17
|
+
caption?: JsonValue | undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PhotoBlockDataAllLocales = {
|
|
21
|
+
_id: string
|
|
22
|
+
_type: 'photoBlock'
|
|
23
|
+
display?: 'default' | 'wide' | 'full_width' | undefined
|
|
24
|
+
photo?: RelatedDocumentValue | undefined
|
|
25
|
+
alt: string
|
|
26
|
+
caption?: { [locale: string]: JsonValue } | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type RichTextBlockData = {
|
|
30
|
+
_id: string
|
|
31
|
+
_type: 'richTextBlock'
|
|
32
|
+
richText: JsonValue
|
|
33
|
+
constrainedWidth?: boolean | undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type RichTextBlockDataAllLocales = {
|
|
37
|
+
_id: string
|
|
38
|
+
_type: 'richTextBlock'
|
|
39
|
+
richText: { [locale: string]: JsonValue }
|
|
40
|
+
constrainedWidth?: boolean | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type DocsFields = {
|
|
44
|
+
title: string
|
|
45
|
+
summary: string
|
|
46
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
47
|
+
publishedOn: Date
|
|
48
|
+
content?: Array<RichTextBlockData | PhotoBlockData> | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type DocsFieldsAllLocales = {
|
|
52
|
+
title: { [locale: string]: string }
|
|
53
|
+
summary: { [locale: string]: string }
|
|
54
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
55
|
+
publishedOn: Date
|
|
56
|
+
content?: Array<RichTextBlockDataAllLocales | PhotoBlockDataAllLocales> | undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type MediaFields = {
|
|
60
|
+
image: StoredFileValue
|
|
61
|
+
title: string
|
|
62
|
+
altText: string
|
|
63
|
+
caption?: string | undefined
|
|
64
|
+
credit?: string | undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type MediaFieldsAllLocales = {
|
|
68
|
+
image: StoredFileValue
|
|
69
|
+
title: string
|
|
70
|
+
altText: string
|
|
71
|
+
caption?: string | undefined
|
|
72
|
+
credit?: string | undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type NewsCategoriesFields = {
|
|
76
|
+
name: string
|
|
77
|
+
description: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type NewsCategoriesFieldsAllLocales = {
|
|
81
|
+
name: { [locale: string]: string }
|
|
82
|
+
description: { [locale: string]: string }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type NewsFields = {
|
|
86
|
+
title: string
|
|
87
|
+
summary: string
|
|
88
|
+
category: RelatedDocumentValue
|
|
89
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
90
|
+
featured?: boolean | undefined
|
|
91
|
+
content: JsonValue
|
|
92
|
+
publishedOn: Date
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type NewsFieldsAllLocales = {
|
|
96
|
+
title: { [locale: string]: string }
|
|
97
|
+
summary: { [locale: string]: string }
|
|
98
|
+
category: RelatedDocumentValue
|
|
99
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
100
|
+
featured?: boolean | undefined
|
|
101
|
+
content: { [locale: string]: JsonValue }
|
|
102
|
+
publishedOn: Date
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type PagesFields = {
|
|
106
|
+
title: string
|
|
107
|
+
summary: string
|
|
108
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
109
|
+
area: 'root' | 'about' | 'legal'
|
|
110
|
+
content?: Array<RichTextBlockData | PhotoBlockData> | undefined
|
|
111
|
+
publishedOn: Date
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type PagesFieldsAllLocales = {
|
|
115
|
+
title: { [locale: string]: string }
|
|
116
|
+
summary: { [locale: string]: string }
|
|
117
|
+
featureImage?: RelatedDocumentValue | undefined
|
|
118
|
+
area: 'root' | 'about' | 'legal'
|
|
119
|
+
content?: Array<RichTextBlockDataAllLocales | PhotoBlockDataAllLocales> | undefined
|
|
120
|
+
publishedOn: Date
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type CollectionFieldsByPath = {
|
|
124
|
+
docs: DocsFields
|
|
125
|
+
media: MediaFields
|
|
126
|
+
news: NewsFields
|
|
127
|
+
'news-categories': NewsCategoriesFields
|
|
128
|
+
pages: PagesFields
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type CollectionFieldsAllLocalesByPath = {
|
|
132
|
+
docs: DocsFieldsAllLocales
|
|
133
|
+
media: MediaFieldsAllLocales
|
|
134
|
+
news: NewsFieldsAllLocales
|
|
135
|
+
'news-categories': NewsCategoriesFieldsAllLocales
|
|
136
|
+
pages: PagesFieldsAllLocales
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
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
|
|
16
|
-
* translation graph this file depends on. Re-exported here for
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* (`@byline/i18n/admin`) that `i18n.ts` depends on.
|
|
19
|
-
* would; importing this won't — which keeps the lazy
|
|
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
|
|
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'
|
|
@@ -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
|
+
}
|
|
@@ -19,7 +19,6 @@ import { JwtSessionProvider } from '@byline/admin/auth'
|
|
|
19
19
|
import { type BylineCore, initBylineCore } from '@byline/core'
|
|
20
20
|
import { pgAdapter } from '@byline/db-postgres'
|
|
21
21
|
import { createAdminStore } from '@byline/db-postgres/admin'
|
|
22
|
-
import { getAdminBylineClient } from '@byline/host-tanstack-start/integrations/byline-client'
|
|
23
22
|
import {
|
|
24
23
|
lexicalEditorEmbedServer,
|
|
25
24
|
lexicalEditorPopulateServer,
|
|
@@ -29,22 +28,13 @@ import {
|
|
|
29
28
|
import { migrate, postgresSearch } from '@byline/search-postgres'
|
|
30
29
|
import { localStorageProvider } from '@byline/storage-local'
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// admin UI configs (React components, CSS modules) that are not loadable
|
|
35
|
-
// outside Vite (e.g. when running seeds via tsx).
|
|
36
|
-
import { Docs } from './collections/docs/schema.js'
|
|
37
|
-
import { Media } from './collections/media/schema.js'
|
|
38
|
-
import { News } from './collections/news/schema.js'
|
|
39
|
-
import { NewsCategories } from './collections/news-categories/schema.js'
|
|
40
|
-
import { Pages } from './collections/pages/schema.js'
|
|
31
|
+
import { getAdminBylineClient } from './client.server.js'
|
|
32
|
+
import { collections } from './collections/index.js'
|
|
41
33
|
import { i18n } from './i18n.js'
|
|
42
34
|
import { DEFAULT_SERVER_URL, routes } from './routes.js'
|
|
43
35
|
|
|
44
36
|
const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
|
|
45
37
|
|
|
46
|
-
const collections = [Docs, News, Pages, Media, NewsCategories]
|
|
47
|
-
|
|
48
38
|
// HMR-safe singleton. Vite's program reload re-evaluates this module
|
|
49
39
|
// without disposing the previous module's resources — every reload
|
|
50
40
|
// would otherwise allocate a fresh pg `Pool` (max: 20) inside
|
|
@@ -8,9 +8,4 @@
|
|
|
8
8
|
|
|
9
9
|
import { createCollectionApiRoute } from '@byline/host-tanstack-start/routes'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export const Route = createCollectionApiRoute('/_byline/admin/collections/$collection/$id/api', {
|
|
14
|
-
contentLocales,
|
|
15
|
-
defaultContentLocale: i18n.content.defaultLocale,
|
|
16
|
-
})
|
|
11
|
+
export const Route = createCollectionApiRoute('/_byline/admin/collections/$collection/$id/api')
|
|
@@ -8,12 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import { createCollectionHistoryRoute } from '@byline/host-tanstack-start/routes'
|
|
10
10
|
|
|
11
|
-
import { contentLocales, i18n } from '~/i18n'
|
|
12
|
-
|
|
13
11
|
export const Route = createCollectionHistoryRoute(
|
|
14
|
-
'/_byline/admin/collections/$collection/$id/history'
|
|
15
|
-
{
|
|
16
|
-
contentLocales,
|
|
17
|
-
defaultContentLocale: i18n.content.defaultLocale,
|
|
18
|
-
}
|
|
12
|
+
'/_byline/admin/collections/$collection/$id/history'
|
|
19
13
|
)
|
|
@@ -8,9 +8,4 @@
|
|
|
8
8
|
|
|
9
9
|
import { createCollectionEditRoute } from '@byline/host-tanstack-start/routes'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export const Route = createCollectionEditRoute('/_byline/admin/collections/$collection/$id/', {
|
|
14
|
-
contentLocales,
|
|
15
|
-
defaultContentLocale: i18n.content.defaultLocale,
|
|
16
|
-
})
|
|
11
|
+
export const Route = createCollectionEditRoute('/_byline/admin/collections/$collection/$id/')
|
|
@@ -5,8 +5,7 @@ import type React from 'react'
|
|
|
5
5
|
import { Container, FadeInLift } from '@byline/ui/react'
|
|
6
6
|
import cx from 'classnames'
|
|
7
7
|
|
|
8
|
-
import type { PhotoBlockData } from '~/
|
|
9
|
-
import type { MediaFields } from '~/collections/media/schema'
|
|
8
|
+
import type { MediaFields, PhotoBlockData } from '~/generated/collection-types.js'
|
|
10
9
|
|
|
11
10
|
import { ResponsiveImage } from '@/ui/byline/components/responsive-image'
|
|
12
11
|
import { LexicalRichText } from '@/ui/byline/components/richtext-lexical'
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Container } from '@byline/ui/react'
|
|
4
4
|
import cx from 'classnames'
|
|
5
5
|
|
|
6
|
-
import type { RichTextBlockData } from '~/
|
|
6
|
+
import type { RichTextBlockData } from '~/generated/collection-types.js'
|
|
7
7
|
|
|
8
8
|
import { LexicalRichText } from '@/ui/byline/components/richtext-lexical'
|
|
9
9
|
import type { Locale } from '@/ui/byline/types/i18n'
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import type { BlocksUnion } from '@byline/core'
|
|
2
1
|
import { Section } from '@byline/ui/react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { RichTextBlock as RichTextBlockDef } from '~/blocks/richtext-block'
|
|
3
|
+
import type { PhotoBlockData, RichTextBlockData } from '~/generated/collection-types.js'
|
|
6
4
|
|
|
7
5
|
import { PhotoBlock } from '@/ui/byline/blocks/photo-block'
|
|
8
6
|
import { RichTextBlock } from '@/ui/byline/blocks/richtext-block'
|
|
9
7
|
import { toKebabCase } from '@/ui/utils/to-kebab-case'
|
|
10
8
|
import type { Locale } from '@/i18n/i18n-config'
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export type AnyBlock = BlocksUnion<typeof Blocks>
|
|
10
|
+
export type AnyBlock = PhotoBlockData | RichTextBlockData
|
|
15
11
|
|
|
16
12
|
// Mapped type ensures every AnyBlock['_type'] has a registered component.
|
|
17
|
-
// TypeScript errors here if
|
|
13
|
+
// TypeScript errors here if AnyBlock gains a new type without a matching entry.
|
|
18
14
|
type BlockRegistry = {
|
|
19
15
|
[K in AnyBlock['_type']]: React.ComponentType<{
|
|
20
16
|
id: string
|