@byline/cli 1.11.2 → 1.12.1

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.
@@ -29,7 +29,6 @@ import { RichTextField as LexicalRichTextField } from '@byline/richtext-lexical'
29
29
  // import { lexicalEditor } from '@byline/richtext-lexical'
30
30
 
31
31
  import { Docs, DocsAdmin } from './collections/docs/index.js'
32
- import { DocsCategories, DocsCategoriesAdmin } from './collections/docs-categories/index.js'
33
32
  import { Media, MediaAdmin } from './collections/media/index.js'
34
33
  import { News, NewsAdmin } from './collections/news/index.js'
35
34
  import { NewsCategories, NewsCategoriesAdmin } from './collections/news-categories/index.js'
@@ -43,8 +42,8 @@ export const config: ClientConfig = {
43
42
  serverURL,
44
43
  i18n,
45
44
  routes,
46
- collections: [Docs, News, Pages, Media, DocsCategories, NewsCategories],
47
- admin: [DocsAdmin, NewsAdmin, PagesAdmin, MediaAdmin, DocsCategoriesAdmin, NewsCategoriesAdmin],
45
+ collections: [Docs, News, Pages, Media, NewsCategories],
46
+ admin: [DocsAdmin, NewsAdmin, PagesAdmin, MediaAdmin, NewsCategoriesAdmin],
48
47
  fields: {
49
48
  // Default registration — every `type: 'richText'` field gets the full
50
49
  // Lexical feature set unless overridden per-field via
@@ -53,16 +52,25 @@ export const config: ClientConfig = {
53
52
  richText: { editor: LexicalRichTextField },
54
53
 
55
54
  // ---------------------------------------------------------------------
56
- // Alternatively — register the editor with site-wide custom settings.
57
- // The `configure` callback receives a deep clone of `defaultEditorConfig`,
58
- // so mutating it is safe. Per-field `editorConfig` continues to take
59
- // precedence over whatever is baked in here.
55
+ // Alternatively — register the editor with site-wide custom settings
56
+ // and an edited extensions list. The `configure` callback receives a
57
+ // fresh seed (default settings + the canonical extensions list), and
58
+ // mutations are local to this call. Per-field `editorConfig`
59
+ // continues to take precedence over whatever is baked in here.
60
+ //
61
+ // import {
62
+ // lexicalEditor,
63
+ // AdmonitionExtension,
64
+ // CodeHighlightExtension,
65
+ // TableExtension,
66
+ // } from '@byline/richtext-lexical'
60
67
  //
61
68
  // richText: {
62
69
  // editor: lexicalEditor((c) => {
63
- // c.settings.options.tablePlugin = false
64
- // c.settings.options.codeHighlightPlugin = false
65
- // c.settings.options.admonitionPlugin = false
70
+ // c.extensions
71
+ // .remove(TableExtension)
72
+ // .remove(CodeHighlightExtension)
73
+ // .remove(AdmonitionExtension)
66
74
  // c.settings.placeholderText = 'Start writing...'
67
75
  // return c
68
76
  // }),
@@ -11,7 +11,6 @@ import { DateTimeFormatter } from '@byline/ui/react'
11
11
 
12
12
  import { SummaryLength } from '~/components/summary-length.js'
13
13
 
14
- import { FeaturedFormatter } from './components/feature-formatter.js'
15
14
  import { Docs } from './schema.js'
16
15
 
17
16
  /**
@@ -34,14 +33,7 @@ const listViewColumns: ColumnDefinition[] = [
34
33
  label: 'Title',
35
34
  sortable: true,
36
35
  align: 'left',
37
- className: 'w-[30%]',
38
- },
39
- {
40
- fieldName: 'featured',
41
- label: 'Featured',
42
- align: 'center',
43
- className: 'w-[10%]',
44
- formatter: { component: FeaturedFormatter },
36
+ className: 'w-[50%]',
45
37
  },
46
38
  {
47
39
  fieldName: 'status',
@@ -54,7 +46,7 @@ const listViewColumns: ColumnDefinition[] = [
54
46
  label: 'Last Updated',
55
47
  sortable: true,
56
48
  align: 'right',
57
- className: 'w-[20%]',
49
+ className: 'w-[35%]',
58
50
  formatter: { component: DateTimeFormatter },
59
51
  },
60
52
  ]
@@ -166,18 +158,13 @@ export const DocsAdmin: CollectionAdminConfig = defineAdmin(Docs, {
166
158
  {
167
159
  name: 'details',
168
160
  label: 'Details',
169
- fields: ['title', 'summary', 'featureImage', 'category', 'featured'],
161
+ fields: ['title', 'summary', 'featureImage'],
170
162
  },
171
163
  {
172
164
  name: 'content',
173
165
  label: 'Content',
174
166
  fields: ['content'],
175
167
  },
176
- {
177
- name: 'reviews',
178
- label: 'Reviews & Links',
179
- fields: ['reviews', 'links'],
180
- },
181
168
  ],
182
169
  },
183
170
  ],
@@ -148,20 +148,6 @@ export const Docs = defineCollection({
148
148
  optional: true,
149
149
  },
150
150
  publishedOnField,
151
- {
152
- name: 'category',
153
- label: 'Category',
154
- type: 'relation',
155
- targetCollection: 'docs-categories',
156
- displayField: 'name',
157
- },
158
- {
159
- name: 'featured',
160
- label: 'Featured',
161
- type: 'checkbox',
162
- optional: true,
163
- helpText: 'Feature this document.',
164
- },
165
151
  {
166
152
  name: 'content',
167
153
  label: 'Content',
@@ -169,35 +155,6 @@ export const Docs = defineCollection({
169
155
  optional: true,
170
156
  blocks: [RichTextBlock, PhotoBlock],
171
157
  },
172
- {
173
- name: 'reviews',
174
- label: 'Reviews',
175
- type: 'array',
176
- optional: true,
177
- fields: [
178
- {
179
- name: 'reviewItem',
180
- label: 'Review Item',
181
- type: 'group',
182
- fields: [
183
- { name: 'rating', label: 'Rating', type: 'integer' },
184
- {
185
- name: 'comment',
186
- label: 'Comments',
187
- type: 'richText',
188
- localized: false,
189
- },
190
- ],
191
- },
192
- ],
193
- },
194
- {
195
- name: 'links',
196
- label: 'Links',
197
- type: 'array',
198
- optional: true,
199
- fields: [{ name: 'link', label: 'Link', type: 'text' }],
200
- },
201
158
  availableLanguagesField(),
202
159
  ],
203
160
  })
@@ -38,7 +38,7 @@ import {
38
38
  import styles from './media-list-view.module.css'
39
39
  import { FormatBadge } from './media-thumbnail'
40
40
 
41
- export function formatNumber(number: number, decimalPlaces: number) {
41
+ function formatNumber(number: number, decimalPlaces: number) {
42
42
  if (typeof number !== 'number' || Number.isNaN(number)) {
43
43
  throw new TypeError('Input must be a valid number')
44
44
  }
@@ -56,6 +56,7 @@ export const Media = defineCollection({
56
56
  name: 'image',
57
57
  label: 'Image',
58
58
  type: 'image',
59
+ helpText: 'Select an image for this media item.',
59
60
  upload: {
60
61
  // Allow common image types. Extend with 'video/*', 'application/pdf'
61
62
  // etc. for a more general media field.
@@ -11,6 +11,7 @@ import { DateTimeFormatter } from '@byline/ui/react'
11
11
 
12
12
  import { SummaryLength } from '~/components/summary-length.js'
13
13
 
14
+ import { FeaturedFormatter } from './components/feature-formatter.js'
14
15
  import { News } from './schema.js'
15
16
 
16
17
  /**
@@ -33,7 +34,14 @@ const listViewColumns: ColumnDefinition[] = [
33
34
  label: 'Title',
34
35
  sortable: true,
35
36
  align: 'left',
36
- className: 'w-[25%]',
37
+ className: 'w-[30%]',
38
+ },
39
+ {
40
+ fieldName: 'featured',
41
+ label: 'Featured',
42
+ align: 'center',
43
+ className: 'w-[10%]',
44
+ formatter: { component: FeaturedFormatter },
37
45
  },
38
46
  {
39
47
  fieldName: 'status',
@@ -201,6 +209,6 @@ export const NewsAdmin: CollectionAdminConfig = defineAdmin(News, {
201
209
  */
202
210
  layout: {
203
211
  main: ['main'],
204
- sidebar: ['availableLanguages', 'publishedOn'],
212
+ sidebar: ['featured', 'availableLanguages', 'publishedOn'],
205
213
  },
206
214
  })
@@ -0,0 +1,10 @@
1
+ import type { FormatterProps } from '@byline/core'
2
+
3
+ export function FeaturedFormatter({ value, record }: FormatterProps) {
4
+ if (value == null || value === false) return null
5
+ return (
6
+ <span title={`"${record.title}" is featured`} className="text-amber-500">
7
+
8
+ </span>
9
+ )
10
+ }
@@ -66,6 +66,13 @@ export const News = defineCollection({
66
66
  displayField: 'title',
67
67
  optional: true,
68
68
  },
69
+ {
70
+ name: 'featured',
71
+ label: 'Featured',
72
+ type: 'checkbox',
73
+ optional: true,
74
+ helpText: 'Feature this document.',
75
+ },
69
76
  {
70
77
  name: 'content',
71
78
  label: 'Content',
@@ -27,7 +27,7 @@ export const NewsCategories = defineCollection({
27
27
  // Demonstration of `orderable: true` — short, finite, naturally ordered.
28
28
  // Editors can drag rows in the list view to set a canonical order; the
29
29
  // value persists on `byline_documents.order_key` without bumping the
30
- // document version. See docs/ORDERABLE.md.
30
+ // document version. See docs/COLLECTIONS.md (Orderable collections).
31
31
  orderable: true,
32
32
  fields: [
33
33
  { name: 'name', label: 'Name', type: 'text', localized: true },
@@ -6,6 +6,15 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ /**
10
+ * **Schema-side helper.** Returns a `GroupField` schema that emits one
11
+ * `checkbox` field per configured content locale. Drop the result into
12
+ * a collection's `fields` array in `<collection>/schema.ts`. Pure data;
13
+ * the only imports are `@byline/core` types and the i18n locale list.
14
+ *
15
+ * See `docs/FIELDS.md` for the schema-vs-admin model.
16
+ */
17
+
9
18
  import type { GroupField } from '@byline/core'
10
19
 
11
20
  import { contentLocales, type LocaleDefinition } from '../i18n.js'
@@ -39,7 +48,7 @@ const builtInValidate = (value: Record<string, boolean> | undefined): string | u
39
48
  !Array.isArray(value) &&
40
49
  Object.values(value).some(Boolean)
41
50
  if (!hasSelection) {
42
- return 'At least one language must be selected.'
51
+ return 'At least one language must be selected'
43
52
  }
44
53
  return undefined
45
54
  }
@@ -6,41 +6,58 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ /**
10
+ * **Schema-side helper.** Returns a `RichTextField` schema — drop into a
11
+ * collection's `fields` array in `<collection>/schema.ts`. Bakes a
12
+ * compact Lexical `editorConfig` (settings only) into the schema. Pure
13
+ * data: no React, no CSS — schema files must stay tsx-loadable for
14
+ * seeds (see `byline/server.config.ts`).
15
+ *
16
+ * **Constraint** — `editorConfig` baked into a schema can only override
17
+ * **settings** (placeholder, toolbar UI flags, `embedRelationsOnSave`).
18
+ * Extension references (TableExtension, AdmonitionExtension, etc.) are
19
+ * not JSON-safe and would break tsx-loaded seeds; per-field extension
20
+ * removal goes through a client-side wrapper component registered via
21
+ * `FieldAdminConfig.editor`.
22
+ *
23
+ * See `docs/FIELDS.md` for the full schema-vs-admin model.
24
+ */
25
+
9
26
  import type { RichTextField } from '@byline/core'
10
- import { defaultEditorConfig, type EditorConfig } from '@byline/richtext-lexical'
27
+ // Import from `/server` (data-only) rather than the package root so this
28
+ // schema helper stays tsx-loadable. The root barrel evaluates `RichTextField`
29
+ // / `EditorField` and their CSS imports, which would break seeds that load
30
+ // any collection schema using this factory.
31
+ import { defaultEditorConfig, type EditorConfig } from '@byline/richtext-lexical/server'
11
32
 
12
33
  type Options = Partial<Omit<RichTextField, 'type' | 'editorConfig'>> & {
13
34
  /**
14
35
  * Optional callback to further customise the compact defaults. Receives a
15
- * mutable copy of the compact config; mutate and return, or return a new
36
+ * mutable copy of the compact settings; mutate and return, or return a new
16
37
  * object. Runs after the compact preset is applied, so callers can re-enable
17
38
  * specific options for a particular field without re-listing the full set.
39
+ *
40
+ * The compact preset only touches `settings`. Do not assign extensions
41
+ * here — schema-side `editorConfig` must remain JSON-safe.
18
42
  */
19
43
  configure?: (config: EditorConfig) => EditorConfig
20
44
  }
21
45
 
22
46
  /**
23
- * Compact preset — disables block-level features (tables, layouts,
24
- * admonitions, code highlight, lists, embeds, inline images, alignment) and
25
- * keeps a slim toolbar suitable for inline body copy like image captions,
26
- * byline strap-lines, or compact form fields.
47
+ * Compact preset — disables secondary toolbar UI (text alignment,
48
+ * inline-code, undo/redo, text style) for inline body copy like image
49
+ * captions, byline strap-lines, or compact form fields. Bold / italic /
50
+ * link editing remain on.
51
+ *
52
+ * To narrow the *extension* set per-field — drop tables, lists, embeds,
53
+ * the floating format toolbar, the table action menu — register a
54
+ * `LexicalRichTextCompact` wrapper component via `FieldAdminConfig.editor`.
55
+ * Extension references aren't safe to bake into schemas, and floating
56
+ * UIs are now extension-presence controlled rather than settings-controlled.
27
57
  */
28
58
  function applyCompactPreset(config: EditorConfig): EditorConfig {
29
59
  const o = config.settings.options
30
60
  o.textAlignment = false
31
- o.tablePlugin = false
32
- o.tableActionMenuPlugin = false
33
- o.tableCellBackgroundColor = false
34
- o.tableCellMerge = false
35
- o.layoutPlugin = false
36
- o.admonitionPlugin = false
37
- o.codeHighlightPlugin = false
38
- o.horizontalRulePlugin = false
39
- o.listPlugin = false
40
- o.checkListPlugin = false
41
- o.inlineImagePlugin = false
42
- o.autoEmbedPlugin = false
43
- o.floatingTextFormatToolbarPlugin = false
44
61
  o.textStyle = false
45
62
  o.inlineCode = false
46
63
  o.undoRedo = false
@@ -48,24 +65,20 @@ function applyCompactPreset(config: EditorConfig): EditorConfig {
48
65
  }
49
66
 
50
67
  /**
51
- * Returns a `RichTextField` with a reduced Lexical feature set baked into
52
- * `editorConfig`. Use this for caption-style or otherwise constrained rich-text
53
- * fields where the full editor surface would be inappropriate.
54
- *
55
- * The compact preset disables tables, layouts, lists, code highlight, inline
56
- * images, embeds, and most secondary toolbar features while keeping bold /
57
- * italic / link editing and the floating link editor.
68
+ * Returns a `RichTextField` with reduced toolbar settings baked into
69
+ * `editorConfig`. Use this for caption-style or otherwise constrained
70
+ * rich-text fields where the full editor toolbar would be inappropriate.
58
71
  *
59
72
  * @example
60
73
  * ```ts
61
74
  * fields: [
62
75
  * lexicalRichTextCompact({ name: 'caption', label: 'Caption' }),
63
- * // Compact + re-enable lists for a specific field:
76
+ * // Compact + custom placeholder for one field:
64
77
  * lexicalRichTextCompact({
65
78
  * name: 'summary',
66
79
  * label: 'Summary',
67
80
  * configure: (c) => {
68
- * c.settings.options.listPlugin = true
81
+ * c.settings.placeholderText = 'One sentence summary…'
69
82
  * return c
70
83
  * },
71
84
  * }),
@@ -6,6 +6,15 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ /**
10
+ * **Schema-side field.** A pre-built `datetime` field definition. Drop
11
+ * the exported `publishedOnField` (the value, not a call) into a
12
+ * collection's `fields` array in `<collection>/schema.ts`. Pure data;
13
+ * safe to import from any schema file.
14
+ *
15
+ * See `docs/FIELDS.md` for the schema-vs-admin model.
16
+ */
17
+
9
18
  import { defineField, type FieldData } from '@byline/core'
10
19
 
11
20
  /**
@@ -10,12 +10,10 @@
10
10
  import 'dotenv/config'
11
11
  import './server.config.js'
12
12
 
13
- import { seedDocsCategories } from './seeds/doc-categories.js'
14
13
  import { seedDocs } from './seeds/docs.js'
15
14
  import { seedNewsCategories } from './seeds/news-categories.js'
16
15
 
17
16
  async function run() {
18
- await seedDocsCategories()
19
17
  await seedNewsCategories()
20
18
  await seedDocs()
21
19
  }
@@ -24,7 +24,6 @@ const sampleDocument = {
24
24
  // targetCollectionId: "cat-123",
25
25
  // targetDocumentId: "electronics-audio"
26
26
  // },
27
- featured: false,
28
27
  publishedOn: new Date('2024-01-15T10:00:00'),
29
28
  content: [
30
29
  {
@@ -165,81 +164,6 @@ const sampleDocument = {
165
164
  },
166
165
  },
167
166
  ],
168
- reviews: [
169
- {
170
- reviewItem: {
171
- rating: 5,
172
- comment: {
173
- root: {
174
- children: [
175
- {
176
- children: [
177
- {
178
- detail: 0,
179
- format: 0,
180
- mode: 'normal',
181
- style: '',
182
- text: 'Some review text here...',
183
- type: 'text',
184
- version: 1,
185
- },
186
- ],
187
- direction: 'ltr',
188
- format: '',
189
- indent: 0,
190
- type: 'paragraph',
191
- version: 1,
192
- textFormat: 0,
193
- textStyle: '',
194
- },
195
- ],
196
- direction: 'ltr',
197
- format: '',
198
- indent: 0,
199
- type: 'root',
200
- version: 1,
201
- },
202
- },
203
- },
204
- },
205
- {
206
- reviewItem: {
207
- rating: 3,
208
- comment: {
209
- root: {
210
- children: [
211
- {
212
- children: [
213
- {
214
- detail: 0,
215
- format: 0,
216
- mode: 'normal',
217
- style: '',
218
- text: 'Some review text here...',
219
- type: 'text',
220
- version: 1,
221
- },
222
- ],
223
- direction: 'ltr',
224
- format: '',
225
- indent: 0,
226
- type: 'paragraph',
227
- version: 1,
228
- textFormat: 0,
229
- textStyle: '',
230
- },
231
- ],
232
- direction: 'ltr',
233
- format: '',
234
- indent: 0,
235
- type: 'root',
236
- version: 1,
237
- },
238
- },
239
- },
240
- },
241
- ],
242
- links: [{ link: 'https://example.com' }, { link: 'https://another-example.com' }],
243
167
  }
244
168
 
245
169
  export async function seedDocs(count = 15) {
@@ -28,7 +28,6 @@ import { localStorageProvider } from '@byline/storage-local'
28
28
  // admin UI configs (React components, CSS modules) that are not loadable
29
29
  // outside Vite (e.g. when running seeds via tsx).
30
30
  import { Docs } from './collections/docs/schema.js'
31
- import { DocsCategories } from './collections/docs-categories/schema.js'
32
31
  import { Media } from './collections/media/schema.js'
33
32
  import { News } from './collections/news/schema.js'
34
33
  import { NewsCategories } from './collections/news-categories/schema.js'
@@ -38,7 +37,7 @@ import { DEFAULT_SERVER_URL, routes } from './routes.js'
38
37
 
39
38
  const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
40
39
 
41
- const collections = [Docs, News, Pages, Media, DocsCategories, NewsCategories]
40
+ const collections = [Docs, News, Pages, Media, NewsCategories]
42
41
 
43
42
  // HMR-safe singleton. Vite's program reload re-evaluates this module
44
43
  // without disposing the previous module's resources — every reload
@@ -132,6 +132,7 @@ const config = defineConfig({
132
132
  browserAsyncHooksAlias(),
133
133
  devtools(),
134
134
  nitro({
135
+ preset: 'node',
135
136
  // @byline/ui ships compiled JS that does `import './foo_module.css'`.
136
137
  // @byline/host-tanstack-start re-exports route factories from
137
138
  // @byline/ui at runtime in the SSR graph. Nitro externalizes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/cli",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "1.11.2",
5
+ "version": "1.12.1",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -61,7 +61,7 @@
61
61
  "scripts": {
62
62
  "build": "tsc -p tsconfig.json && tsc-alias && node ./scripts/copy-templates.js && chmod +x dist/cli.js",
63
63
  "dev": "tsc -w -p tsconfig.json",
64
- "clean": "rimraf node_modules dist .turbo",
64
+ "clean": "node scripts/clean.js node_modules dist .turbo",
65
65
  "lint": "biome check --write --unsafe --diagnostic-level=error",
66
66
  "test": "vitest run --passWithNoTests",
67
67
  "typecheck": "tsc --noEmit"