@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.
- package/README.md +42 -2
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +10 -14
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/setup-checks.d.ts +9 -0
- package/dist/commands/setup-checks.d.ts.map +1 -1
- package/dist/commands/setup-checks.js +43 -18
- package/dist/commands/setup-checks.js.map +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -0
- package/dist/context.js.map +1 -1
- package/dist/lib/dependency-version.d.ts +12 -0
- package/dist/lib/dependency-version.d.ts.map +1 -0
- package/dist/lib/dependency-version.js +101 -0
- package/dist/lib/dependency-version.js.map +1 -0
- package/dist/lib/planned-writes.d.ts +8 -0
- package/dist/lib/planned-writes.d.ts.map +1 -0
- package/dist/lib/planned-writes.js +177 -0
- package/dist/lib/planned-writes.js.map +1 -0
- package/dist/lib/planned-writes.test.d.ts +2 -0
- package/dist/lib/planned-writes.test.d.ts.map +1 -0
- package/dist/lib/planned-writes.test.js +113 -0
- package/dist/lib/planned-writes.test.js.map +1 -0
- package/dist/lib/release-policy.d.ts +12 -0
- package/dist/lib/release-policy.d.ts.map +1 -0
- package/dist/lib/release-policy.js +20 -0
- package/dist/lib/release-policy.js.map +1 -0
- package/dist/lib/release-policy.test.d.ts +2 -0
- package/dist/lib/release-policy.test.d.ts.map +1 -0
- package/dist/lib/release-policy.test.js +36 -0
- package/dist/lib/release-policy.test.js.map +1 -0
- package/dist/lib/route-config.d.ts +40 -0
- package/dist/lib/route-config.d.ts.map +1 -0
- package/dist/lib/route-config.js +322 -0
- package/dist/lib/route-config.js.map +1 -0
- package/dist/lib/static-config.d.ts +20 -0
- package/dist/lib/static-config.d.ts.map +1 -0
- package/dist/lib/static-config.js +444 -0
- package/dist/lib/static-config.js.map +1 -0
- package/dist/lib/static-config.test.d.ts +2 -0
- package/dist/lib/static-config.test.d.ts.map +1 -0
- package/dist/lib/static-config.test.js +105 -0
- package/dist/lib/static-config.test.js.map +1 -0
- package/dist/lib/template-hook-boundary.test.node.d.ts +2 -0
- package/dist/lib/template-hook-boundary.test.node.d.ts.map +1 -0
- package/dist/lib/template-hook-boundary.test.node.js +30 -0
- package/dist/lib/template-hook-boundary.test.node.js.map +1 -0
- package/dist/lib/template-path.d.ts +5 -0
- package/dist/lib/template-path.d.ts.map +1 -0
- package/dist/lib/template-path.js +9 -0
- package/dist/lib/template-path.js.map +1 -0
- package/dist/lib/workspace-root.d.ts +28 -0
- package/dist/lib/workspace-root.d.ts.map +1 -0
- package/dist/lib/workspace-root.js +205 -0
- package/dist/lib/workspace-root.js.map +1 -0
- package/dist/manifest/deps.d.ts +1 -1
- package/dist/manifest/deps.d.ts.map +1 -1
- package/dist/manifest/deps.js +18 -8
- package/dist/manifest/deps.js.map +1 -1
- package/dist/phases/deps.d.ts +14 -1
- package/dist/phases/deps.d.ts.map +1 -1
- package/dist/phases/deps.js +312 -137
- package/dist/phases/deps.js.map +1 -1
- package/dist/phases/deps.test.d.ts +2 -0
- package/dist/phases/deps.test.d.ts.map +1 -0
- package/dist/phases/deps.test.js +343 -0
- package/dist/phases/deps.test.js.map +1 -0
- package/dist/phases/preflight.d.ts.map +1 -1
- package/dist/phases/preflight.js +59 -11
- package/dist/phases/preflight.js.map +1 -1
- package/dist/phases/prompts.d.ts +2 -0
- package/dist/phases/prompts.d.ts.map +1 -1
- package/dist/phases/prompts.js +51 -2
- package/dist/phases/prompts.js.map +1 -1
- package/dist/phases/routes-ui.test.d.ts +2 -0
- package/dist/phases/routes-ui.test.d.ts.map +1 -0
- package/dist/phases/routes-ui.test.js +766 -0
- package/dist/phases/routes-ui.test.js.map +1 -0
- package/dist/phases/routes.d.ts +4 -1
- package/dist/phases/routes.d.ts.map +1 -1
- package/dist/phases/routes.js +610 -122
- package/dist/phases/routes.js.map +1 -1
- package/dist/phases/scaffold-smoke.test.d.ts +2 -0
- package/dist/phases/scaffold-smoke.test.d.ts.map +1 -0
- package/dist/phases/scaffold-smoke.test.js +168 -0
- package/dist/phases/scaffold-smoke.test.js.map +1 -0
- package/dist/phases/scaffold.d.ts +4 -1
- package/dist/phases/scaffold.d.ts.map +1 -1
- package/dist/phases/scaffold.js +275 -72
- 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 +178 -0
- package/dist/phases/scaffold.test.js.map +1 -0
- package/dist/phases/ui.d.ts +4 -1
- package/dist/phases/ui.d.ts.map +1 -1
- package/dist/phases/ui.js +51 -59
- package/dist/phases/ui.js.map +1 -1
- package/dist/phases/wire/index.d.ts.map +1 -1
- package/dist/phases/wire/index.js +10 -6
- package/dist/phases/wire/index.js.map +1 -1
- package/dist/phases/wire/shared.d.ts +4 -1
- package/dist/phases/wire/shared.d.ts.map +1 -1
- package/dist/phases/wire/vite-config.d.ts.map +1 -1
- package/dist/phases/wire/vite-config.js +69 -20
- package/dist/phases/wire/vite-config.js.map +1 -1
- package/dist/phases/wire/vite-config.test.d.ts +2 -0
- package/dist/phases/wire/vite-config.test.d.ts.map +1 -0
- package/dist/phases/wire/vite-config.test.js +97 -0
- package/dist/phases/wire/vite-config.test.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +7 -2
- package/dist/prompts.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/templates/byline/admin.config.ts +8 -7
- package/dist/templates/byline/clients.server.ts +77 -0
- package/dist/templates/byline/collection-types.contract.ts +67 -0
- package/dist/templates/byline/collections/index.ts +17 -0
- package/dist/templates/byline/collections/server-hooks.ts +7 -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/routes.ts +7 -6
- package/dist/templates/byline/scripts/generate-types.ts +81 -0
- package/dist/templates/byline/server.config.ts +5 -5
- package/dist/templates/byline-examples/admin.config.ts +18 -21
- package/dist/templates/byline-examples/blocks/photo-block.ts +3 -2
- package/dist/templates/byline-examples/clients.server.ts +77 -0
- package/dist/templates/byline-examples/collection-types.contract.ts +67 -0
- package/dist/templates/byline-examples/collections/docs/hooks.ts +30 -20
- package/dist/templates/byline-examples/collections/docs/schema.ts +18 -18
- package/dist/templates/byline-examples/collections/index.ts +22 -0
- package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +11 -6
- package/dist/templates/byline-examples/collections/media/schema.ts +16 -0
- package/dist/templates/byline-examples/collections/news/schema.ts +17 -3
- 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/collections/server-hooks.ts +8 -0
- package/dist/templates/byline-examples/fields/ai-text.ts +1 -1
- package/dist/templates/byline-examples/fields/ai-textarea.ts +1 -1
- package/dist/templates/byline-examples/fields/lexical-richtext-ai.tsx +3 -3
- 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/routes.ts +7 -6
- package/dist/templates/byline-examples/scripts/backfill-version-locales.ts +1 -1
- package/dist/templates/byline-examples/scripts/generate-types.ts +81 -0
- package/dist/templates/byline-examples/scripts/import-docs.ts +11 -12
- package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.test.node.ts +93 -233
- package/dist/templates/byline-examples/scripts/lib/mdast-to-lexical.ts +2 -2
- package/dist/templates/byline-examples/scripts/lib/strip-leading-h1.test.node.ts +0 -4
- package/dist/templates/byline-examples/scripts/re-anchor.ts +3 -3
- package/dist/templates/byline-examples/scripts/regenerate-media.ts +6 -5
- package/dist/templates/byline-examples/server.config.ts +6 -15
- package/dist/templates/host/vite.config.ts +2 -0
- 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/routes/_byline/route.lazy.tsx +1 -2
- package/dist/templates/routes/_byline/route.tsx +1 -1
- package/dist/templates/ui-byline/blocks/photo-block/index.tsx +18 -23
- package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +1 -1
- package/dist/templates/ui-byline/components/admonition/index.tsx +11 -8
- package/dist/templates/ui-byline/components/code/code.tsx +6 -4
- package/dist/templates/ui-byline/components/link/lang-link.tsx +4 -8
- package/dist/templates/ui-byline/components/link/link-field.tsx +3 -2
- package/dist/templates/ui-byline/components/link/link-lexical.tsx +9 -8
- package/dist/templates/ui-byline/render-blocks.tsx +38 -38
- package/dist/templates/ui-byline/types/content.ts +25 -0
- package/dist/templates/ui-byline/types/i18n.ts +4 -8
- package/dist/test-helpers.d.ts +5 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +52 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui-template-contract.test.d.ts +2 -0
- package/dist/ui-template-contract.test.d.ts.map +1 -0
- package/dist/ui-template-contract.test.js +130 -0
- package/dist/ui-template-contract.test.js.map +1 -0
- package/dist/workspace-root.test.d.ts +2 -0
- package/dist/workspace-root.test.d.ts.map +1 -0
- package/dist/workspace-root.test.js +307 -0
- package/dist/workspace-root.test.js.map +1 -0
- package/package.json +9 -1
|
@@ -24,312 +24,172 @@ describe('mdastToLexical', () => {
|
|
|
24
24
|
expect(state.root.children[0]).toMatchObject({ type: 'paragraph', children: [] })
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
test('
|
|
28
|
-
const
|
|
29
|
-
expect(
|
|
27
|
+
test('maps plain paragraphs and every heading depth', () => {
|
|
28
|
+
const paragraph = convert('hello world').state.root.children[0]
|
|
29
|
+
expect(paragraph).toMatchObject({
|
|
30
30
|
type: 'paragraph',
|
|
31
31
|
children: [{ type: 'text', text: 'hello world', format: 0 }],
|
|
32
32
|
})
|
|
33
|
+
const headings = convert('# h1\n\n## h2\n\n###### h6').state.root.children
|
|
34
|
+
expect(headings.map((node) => (node as { tag: string }).tag)).toEqual(['h1', 'h2', 'h6'])
|
|
33
35
|
})
|
|
34
36
|
|
|
35
|
-
test('
|
|
36
|
-
const { state } = convert('#
|
|
37
|
-
expect(state.root.children).toHaveLength(3)
|
|
37
|
+
test('maps headings and composed inline formatting', () => {
|
|
38
|
+
const { state } = convert('# heading\n\n***both*** and `code`')
|
|
38
39
|
expect(state.root.children[0]).toMatchObject({ type: 'heading', tag: 'h1' })
|
|
39
|
-
|
|
40
|
-
expect(state.root.children[2]).toMatchObject({ type: 'heading', tag: 'h6' })
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('bold + italic compose into a single text node with bitmask format=3', () => {
|
|
44
|
-
const { state } = convert('***both***')
|
|
45
|
-
const paragraph = state.root.children[0] as unknown as {
|
|
40
|
+
const paragraph = state.root.children[1] as unknown as {
|
|
46
41
|
children: Array<{ format: number; text: string }>
|
|
47
42
|
}
|
|
48
|
-
expect(paragraph.children).
|
|
49
|
-
expect(paragraph.children
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
test('inline code carries the IS_CODE bit', () => {
|
|
53
|
-
const { state } = convert('a `b` c')
|
|
54
|
-
const paragraph = state.root.children[0] as unknown as {
|
|
55
|
-
children: Array<{ format: number; text: string }>
|
|
56
|
-
}
|
|
57
|
-
// 'a ', code 'b', ' c'
|
|
58
|
-
expect(paragraph.children.map((c) => ({ text: c.text, format: c.format }))).toEqual([
|
|
59
|
-
{ text: 'a ', format: 0 },
|
|
60
|
-
{ text: 'b', format: 1 << 4 },
|
|
61
|
-
{ text: ' c', format: 0 },
|
|
62
|
-
])
|
|
43
|
+
expect(paragraph.children).toContainEqual(expect.objectContaining({ text: 'both', format: 3 }))
|
|
44
|
+
expect(paragraph.children).toContainEqual(
|
|
45
|
+
expect.objectContaining({ text: 'code', format: 1 << 4 })
|
|
46
|
+
)
|
|
63
47
|
})
|
|
64
48
|
|
|
65
|
-
test('
|
|
49
|
+
test('preserves GFM strikethrough', () => {
|
|
66
50
|
const { state } = convert('a ~~b~~ c')
|
|
67
51
|
const paragraph = state.root.children[0] as unknown as {
|
|
68
52
|
children: Array<{ format: number; text: string }>
|
|
69
53
|
}
|
|
70
|
-
|
|
71
|
-
expect(struck?.format).toBe(1 << 2)
|
|
54
|
+
expect(paragraph.children.find((child) => child.text === 'b')?.format).toBe(1 << 2)
|
|
72
55
|
})
|
|
73
56
|
|
|
74
|
-
test('unordered
|
|
75
|
-
const
|
|
76
|
-
const list = state.root.children[0] as unknown as {
|
|
77
|
-
type: string
|
|
57
|
+
test('maps unordered, ordered, and nested lists', () => {
|
|
58
|
+
const unordered = convert('- a\n - a1\n- b').state.root.children[0] as unknown as {
|
|
78
59
|
listType: string
|
|
79
60
|
tag: string
|
|
80
|
-
children: Array<{
|
|
61
|
+
children: Array<{ children: Array<{ type: string }> }>
|
|
81
62
|
}
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
84
|
-
expect(list.children[0]).toMatchObject({
|
|
85
|
-
type: 'listitem',
|
|
86
|
-
children: [{ type: 'text', text: 'one' }],
|
|
87
|
-
})
|
|
88
|
-
})
|
|
63
|
+
expect(unordered).toMatchObject({ listType: 'bullet', tag: 'ul' })
|
|
64
|
+
expect(unordered.children[0].children.some((child) => child.type === 'list')).toBe(true)
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
const { state } = convert('3. a\n4. b')
|
|
92
|
-
const list = state.root.children[0] as unknown as {
|
|
66
|
+
const ordered = convert('3. a\n4. b').state.root.children[0] as unknown as {
|
|
93
67
|
listType: string
|
|
94
68
|
tag: string
|
|
95
69
|
start: number
|
|
96
70
|
children: Array<{ value: number }>
|
|
97
71
|
}
|
|
98
|
-
expect(
|
|
99
|
-
expect(
|
|
100
|
-
expect(list.children[1].value).toBe(4)
|
|
72
|
+
expect(ordered).toMatchObject({ listType: 'number', tag: 'ol', start: 3 })
|
|
73
|
+
expect(ordered.children.map((child) => child.value)).toEqual([3, 4])
|
|
101
74
|
})
|
|
102
75
|
|
|
103
|
-
test('
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
children: Array<{
|
|
76
|
+
test('maps links and fenced code language aliases', () => {
|
|
77
|
+
const linkParagraph = convert('[label](https://example.com)').state.root
|
|
78
|
+
.children[0] as unknown as {
|
|
79
|
+
children: Array<{ attributes: { url: string; newTab: boolean } }>
|
|
107
80
|
}
|
|
108
|
-
|
|
109
|
-
expect(firstItemChildren.some((c) => c.type === 'list')).toBe(true)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('link emits link node with custom attributes envelope', () => {
|
|
113
|
-
const { state } = convert('[label](https://example.com)')
|
|
114
|
-
const paragraph = state.root.children[0] as unknown as {
|
|
115
|
-
children: Array<{
|
|
116
|
-
type: string
|
|
117
|
-
attributes?: { linkType?: string; url?: string; newTab?: boolean }
|
|
118
|
-
children?: Array<{ text: string }>
|
|
119
|
-
}>
|
|
120
|
-
}
|
|
121
|
-
const link = paragraph.children[0]
|
|
122
|
-
expect(link.type).toBe('link')
|
|
123
|
-
expect(link.attributes).toMatchObject({
|
|
124
|
-
linkType: 'custom',
|
|
81
|
+
expect(linkParagraph.children[0].attributes).toMatchObject({
|
|
125
82
|
url: 'https://example.com',
|
|
126
83
|
newTab: true,
|
|
127
84
|
})
|
|
128
|
-
expect(link.children?.[0]).toMatchObject({ text: 'label' })
|
|
129
|
-
})
|
|
130
85
|
|
|
131
|
-
|
|
132
|
-
const { state } = convert('```ts\nconst x = 1\nconst y = 2\n```')
|
|
133
|
-
const code = state.root.children[0] as unknown as {
|
|
134
|
-
type: string
|
|
86
|
+
const code = convert('```ts\nconst x = 1\n```').state.root.children[0] as unknown as {
|
|
135
87
|
language: string
|
|
136
88
|
children: Array<{ type: string; text?: string }>
|
|
137
89
|
}
|
|
138
|
-
expect(code).
|
|
90
|
+
expect(code.language).toBe('typescript')
|
|
139
91
|
expect(code.children[0]).toMatchObject({ type: 'code-highlight', text: 'const x = 1' })
|
|
140
|
-
expect(code.children[1]).toMatchObject({ type: 'linebreak' })
|
|
141
|
-
expect(code.children[2]).toMatchObject({ type: 'code-highlight', text: 'const y = 2' })
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
test('thematic break emits horizontalrule', () => {
|
|
145
|
-
const { state } = convert('---')
|
|
146
|
-
expect(state.root.children[0]).toMatchObject({ type: 'horizontalrule' })
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
test('blockquote flattens single paragraph into inline children', () => {
|
|
150
|
-
const { state } = convert('> hello')
|
|
151
|
-
const quote = state.root.children[0] as unknown as {
|
|
152
|
-
type: string
|
|
153
|
-
children: Array<{ type: string; text?: string }>
|
|
154
|
-
}
|
|
155
|
-
expect(quote.type).toBe('quote')
|
|
156
|
-
expect(quote.children).toEqual([expect.objectContaining({ type: 'text', text: 'hello' })])
|
|
157
92
|
})
|
|
158
93
|
|
|
159
|
-
test('
|
|
160
|
-
|
|
161
|
-
expect(
|
|
162
|
-
|
|
163
|
-
|
|
94
|
+
test('maps blockquotes, rules, and image warnings', () => {
|
|
95
|
+
expect(convert('> hello').state.root.children[0]).toMatchObject({ type: 'quote' })
|
|
96
|
+
expect(convert('---').state.root.children[0]).toMatchObject({ type: 'horizontalrule' })
|
|
97
|
+
expect(convert('').warnings).toContainEqual(
|
|
98
|
+
expect.objectContaining({ kind: 'dropped-image' })
|
|
99
|
+
)
|
|
164
100
|
})
|
|
165
101
|
|
|
166
|
-
test('GFM
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
expect(warnings).toEqual([])
|
|
170
|
-
|
|
171
|
-
const table = state.root.children[0] as unknown as {
|
|
172
|
-
type: string
|
|
102
|
+
test('maps GFM tables with header state and inline formatting', () => {
|
|
103
|
+
const table = convert('| col |\n| - |\n| **bold** |').state.root
|
|
104
|
+
.children[0] as unknown as {
|
|
173
105
|
children: Array<{
|
|
174
|
-
type: string
|
|
175
106
|
children: Array<{
|
|
176
|
-
type: string
|
|
177
107
|
headerState: number
|
|
178
|
-
children: Array<{
|
|
108
|
+
children: Array<{ children: Array<{ text: string; format: number }> }>
|
|
179
109
|
}>
|
|
180
110
|
}>
|
|
181
111
|
}
|
|
182
|
-
expect(table.
|
|
183
|
-
expect(table.children).
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
expect(headerRow.children[0]).toMatchObject({ type: 'tablecell', headerState: 1 })
|
|
189
|
-
expect(headerRow.children[1]).toMatchObject({ type: 'tablecell', headerState: 1 })
|
|
190
|
-
expect(bodyRow.children[0]).toMatchObject({ type: 'tablecell', headerState: 0 })
|
|
191
|
-
expect(bodyRow.children[1]).toMatchObject({ type: 'tablecell', headerState: 0 })
|
|
192
|
-
|
|
193
|
-
// Inline cell content is wrapped in a paragraph.
|
|
194
|
-
const firstHeaderCell = headerRow.children[0]
|
|
195
|
-
expect(firstHeaderCell.children[0].type).toBe('paragraph')
|
|
196
|
-
expect(firstHeaderCell.children[0].children[0]).toMatchObject({ text: 'a' })
|
|
197
|
-
expect(bodyRow.children[1].children[0].children[0]).toMatchObject({ text: '2' })
|
|
112
|
+
expect(table.children[0].children[0].headerState).toBe(1)
|
|
113
|
+
expect(table.children[1].children[0].headerState).toBe(0)
|
|
114
|
+
expect(table.children[1].children[0].children[0].children[0]).toMatchObject({
|
|
115
|
+
text: 'bold',
|
|
116
|
+
format: 1,
|
|
117
|
+
})
|
|
198
118
|
})
|
|
199
119
|
|
|
200
|
-
test('empty
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
const table = state.root.children[0] as unknown as {
|
|
120
|
+
test('gives empty table cells a structurally valid empty paragraph', () => {
|
|
121
|
+
const table = convert('| a | b |\n| - | - |\n| | 2 |').state.root
|
|
122
|
+
.children[0] as unknown as {
|
|
204
123
|
children: Array<{
|
|
205
124
|
children: Array<{ children: Array<{ type: string; children: unknown[] }> }>
|
|
206
125
|
}>
|
|
207
126
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
test('inline formatting inside cells is preserved', () => {
|
|
213
|
-
const md = '| col |\n| - |\n| **bold** |\n'
|
|
214
|
-
const { state } = convert(md)
|
|
215
|
-
const table = state.root.children[0] as unknown as {
|
|
216
|
-
children: Array<{
|
|
217
|
-
children: Array<{
|
|
218
|
-
children: Array<{ children: Array<{ text: string; format: number }> }>
|
|
219
|
-
}>
|
|
220
|
-
}>
|
|
221
|
-
}
|
|
222
|
-
const bodyCellInline = table.children[1].children[0].children[0].children[0]
|
|
223
|
-
expect(bodyCellInline).toMatchObject({ text: 'bold', format: 1 })
|
|
127
|
+
expect(table.children[1].children[0].children[0]).toMatchObject({
|
|
128
|
+
type: 'paragraph',
|
|
129
|
+
children: [],
|
|
130
|
+
})
|
|
224
131
|
})
|
|
225
132
|
|
|
226
|
-
test('code fence
|
|
133
|
+
test('normalizes common code fence aliases and preserves known names', () => {
|
|
227
134
|
const cases: Array<[string, string]> = [
|
|
228
135
|
['ts', 'typescript'],
|
|
229
136
|
['js', 'javascript'],
|
|
230
137
|
['sh', 'bash'],
|
|
231
138
|
['yml', 'yaml'],
|
|
232
|
-
['tsx', 'tsx'],
|
|
139
|
+
['tsx', 'tsx'],
|
|
233
140
|
]
|
|
234
141
|
for (const [input, expected] of cases) {
|
|
235
|
-
const
|
|
236
|
-
|
|
142
|
+
const code = convert(`\`\`\`${input}\nx\n\`\`\``).state.root.children[0] as unknown as {
|
|
143
|
+
language: string
|
|
144
|
+
}
|
|
237
145
|
expect(code.language).toBe(expected)
|
|
238
146
|
}
|
|
239
147
|
})
|
|
240
148
|
|
|
241
|
-
test('hard-wrapped paragraph
|
|
242
|
-
const
|
|
243
|
-
'In a world where AI produces content fast,\ninto dozens of languages,\nwhy does a CMS matter?'
|
|
244
|
-
const { state } = convert(md)
|
|
245
|
-
const paragraph = state.root.children[0] as unknown as {
|
|
149
|
+
test('collapses hard-wrapped paragraph newlines', () => {
|
|
150
|
+
const paragraph = convert('first line,\nsecond line.').state.root.children[0] as unknown as {
|
|
246
151
|
children: Array<{ text: string }>
|
|
247
152
|
}
|
|
248
|
-
|
|
249
|
-
// <br> nodes and no embedded \n.
|
|
250
|
-
expect(paragraph.children).toHaveLength(1)
|
|
251
|
-
expect(paragraph.children[0].text).toBe(
|
|
252
|
-
'In a world where AI produces content fast, into dozens of languages, why does a CMS matter?'
|
|
253
|
-
)
|
|
153
|
+
expect(paragraph.children[0].text).toBe('first line, second line.')
|
|
254
154
|
})
|
|
255
155
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
expect(adm).toMatchObject({ type: 'admonition', admonitionType: 'note', title: 'Heads up' })
|
|
269
|
-
expect(adm.children).toHaveLength(2)
|
|
270
|
-
expect(adm.children[0]).toMatchObject({ type: 'paragraph' })
|
|
271
|
-
expect(adm.children[0].children[0].text).toBe('First line.')
|
|
272
|
-
expect(adm.children[1].children[0].text).toBe('Second line.')
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
test('inline formatting + links survive inside an admonition body', () => {
|
|
276
|
-
const md = [
|
|
277
|
-
':::warning[Careful]',
|
|
278
|
-
'This is **bold** and a [link](https://x.io).',
|
|
279
|
-
':::',
|
|
280
|
-
].join('\n')
|
|
281
|
-
const { state } = convert(md)
|
|
282
|
-
const adm = state.root.children[0] as unknown as {
|
|
283
|
-
admonitionType: string
|
|
284
|
-
children: Array<{ children: Array<{ type: string; text?: string; format?: number }> }>
|
|
285
|
-
}
|
|
286
|
-
expect(adm.admonitionType).toBe('warning')
|
|
287
|
-
const inline = adm.children[0].children
|
|
288
|
-
expect(inline).toContainEqual(expect.objectContaining({ text: 'bold', format: 1 }))
|
|
289
|
-
expect(inline).toContainEqual(expect.objectContaining({ type: 'link' }))
|
|
290
|
-
})
|
|
156
|
+
test('maps admonitions and ignores colon-bearing prose', () => {
|
|
157
|
+
const admonition = convert(':::warning[Careful]\nThis is **bold**.\n:::').state.root
|
|
158
|
+
.children[0] as unknown as {
|
|
159
|
+
admonitionType: string
|
|
160
|
+
title: string
|
|
161
|
+
children: Array<{ children: Array<{ text?: string; format?: number }> }>
|
|
162
|
+
}
|
|
163
|
+
expect(admonition).toMatchObject({ admonitionType: 'warning', title: 'Careful' })
|
|
164
|
+
expect(admonition.children[0].children).toContainEqual(
|
|
165
|
+
expect.objectContaining({ text: 'bold', format: 1 })
|
|
166
|
+
)
|
|
291
167
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
})
|
|
168
|
+
const prose = convert('Run at 9:30 with a 1:2:3 ratio and note:foo.')
|
|
169
|
+
expect(prose.warnings).toEqual([])
|
|
170
|
+
expect(prose.state.root.children[0]).toMatchObject({ type: 'paragraph' })
|
|
171
|
+
})
|
|
297
172
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
admonitionType: string
|
|
302
|
-
children: Array<{ type: string; children: unknown[] }>
|
|
303
|
-
}
|
|
304
|
-
expect(adm.admonitionType).toBe('danger')
|
|
305
|
-
expect(adm.children).toEqual([expect.objectContaining({ type: 'paragraph', children: [] })])
|
|
306
|
-
})
|
|
173
|
+
test('supports untitled, empty, and source-ordered admonitions', () => {
|
|
174
|
+
const untitled = convert(':::tip\nA quick tip.\n:::').state.root.children[0]
|
|
175
|
+
expect(untitled).toMatchObject({ admonitionType: 'tip', title: '' })
|
|
307
176
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
'heading',
|
|
313
|
-
'admonition',
|
|
314
|
-
'paragraph',
|
|
315
|
-
])
|
|
316
|
-
})
|
|
177
|
+
const empty = convert(':::danger[Stop]\n:::').state.root.children[0] as unknown as {
|
|
178
|
+
children: Array<{ type: string; children: unknown[] }>
|
|
179
|
+
}
|
|
180
|
+
expect(empty.children).toEqual([expect.objectContaining({ type: 'paragraph', children: [] })])
|
|
317
181
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
expect(paragraph.children[0].text).toBe('Run at 9:30 with a 1:2:3 ratio and note:foo.')
|
|
326
|
-
})
|
|
182
|
+
const ordered = convert('# Title\n\n:::note[Note]\nBody.\n:::\n\nAfter.').state.root.children
|
|
183
|
+
expect(ordered.map((node) => (node as { type: string }).type)).toEqual([
|
|
184
|
+
'heading',
|
|
185
|
+
'admonition',
|
|
186
|
+
'paragraph',
|
|
187
|
+
])
|
|
188
|
+
})
|
|
327
189
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
expect(state.root.children[0]).toMatchObject({ type: 'code' })
|
|
333
|
-
})
|
|
190
|
+
test('keeps admonition-looking fences inside code blocks as code', () => {
|
|
191
|
+
const state = convert('```\n:::note[Not an admonition]\nstill code\n:::\n```').state
|
|
192
|
+
expect(state.root.children).toHaveLength(1)
|
|
193
|
+
expect(state.root.children[0]).toMatchObject({ type: 'code' })
|
|
334
194
|
})
|
|
335
195
|
})
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Pure, headless-editor-free conversion. The output JSON is shaped to
|
|
13
13
|
* match what the runtime serializer in
|
|
14
|
-
* `
|
|
14
|
+
* `src/ui/byline/components/richtext-lexical/serialize`
|
|
15
15
|
* expects to render (heading.tag, list.tag + list.listType, code
|
|
16
16
|
* children of type 'code-highlight' interspersed with 'linebreak',
|
|
17
17
|
* link.attributes, etc.).
|
|
@@ -47,7 +47,7 @@ import type {
|
|
|
47
47
|
import type { AdmonitionDirective } from './parse-markdown.js'
|
|
48
48
|
|
|
49
49
|
// Lexical inline format bits — kept in sync with
|
|
50
|
-
//
|
|
50
|
+
// src/ui/byline/components/richtext-lexical/serialize/richtext-node-formats.ts
|
|
51
51
|
const IS_BOLD = 1
|
|
52
52
|
const IS_ITALIC = 1 << 1
|
|
53
53
|
const IS_STRIKETHROUGH = 1 << 2
|
|
@@ -33,10 +33,6 @@ describe('stripLeadingH1IfMatches', () => {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
test('match flattens inline formatting in the H1', () => {
|
|
36
|
-
// mdast-util-to-string drops backticks / emphasis markers from the
|
|
37
|
-
// H1's inline structure. Frontmatter titles are plain prose, so a
|
|
38
|
-
// body H1 of '# Client SDK (`@byline/client`)' compares equal to a
|
|
39
|
-
// frontmatter title of 'Client SDK (@byline/client)'.
|
|
40
36
|
const root = parse('# Client SDK (`@byline/client`)\n\nbody')
|
|
41
37
|
const out = stripLeadingH1IfMatches(root, 'Client SDK (@byline/client)')
|
|
42
38
|
expect(out.children).toHaveLength(1)
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* listed — that list is the outstanding-translation backlog. Re-run as
|
|
17
17
|
* translation progresses; it is idempotent (each doc is its own transaction).
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* pnpm tsx byline/scripts/re-anchor.ts --to fr
|
|
20
|
+
* pnpm tsx byline/scripts/re-anchor.ts --to fr --collection pages
|
|
21
|
+
* pnpm tsx byline/scripts/re-anchor.ts --to fr --dry-run
|
|
22
22
|
*
|
|
23
23
|
* See docs/07-internationalization/index.md.
|
|
24
24
|
*/
|
|
@@ -59,11 +59,12 @@ import {
|
|
|
59
59
|
getCollectionDefinition,
|
|
60
60
|
getServerConfig,
|
|
61
61
|
getWorkflowStatuses,
|
|
62
|
-
type StoredFileValue,
|
|
63
62
|
} from '@byline/core'
|
|
64
63
|
import { extractImageMeta, generateImageVariants, isBypassMimeType } from '@byline/core/image'
|
|
65
64
|
import { uploadField as coreUploadField } from '@byline/core/services'
|
|
66
65
|
|
|
66
|
+
import type { CollectionFieldsByPath, MediaFields } from '../generated/collection-types.js'
|
|
67
|
+
|
|
67
68
|
const COLLECTION_PATH = 'media'
|
|
68
69
|
const FIELD_NAME = 'image'
|
|
69
70
|
|
|
@@ -92,7 +93,7 @@ async function run(): Promise<void> {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const requestContext = createSuperAdminContext({ id: 'regenerate-media-script' })
|
|
95
|
-
const client = createBylineClient({ config, requestContext })
|
|
96
|
+
const client = createBylineClient<CollectionFieldsByPath>({ config, requestContext })
|
|
96
97
|
|
|
97
98
|
const { id: collectionId, version: collectionVersion } =
|
|
98
99
|
await client.resolveCollectionRecord(COLLECTION_PATH)
|
|
@@ -108,7 +109,7 @@ async function run(): Promise<void> {
|
|
|
108
109
|
id: string
|
|
109
110
|
path: string
|
|
110
111
|
status: string
|
|
111
|
-
fields:
|
|
112
|
+
fields: MediaFields
|
|
112
113
|
}[] = []
|
|
113
114
|
const pageSize = 100
|
|
114
115
|
for (let page = 1; ; page++) {
|
|
@@ -123,7 +124,7 @@ async function run(): Promise<void> {
|
|
|
123
124
|
id: d.id,
|
|
124
125
|
path: d.path,
|
|
125
126
|
status: d.status,
|
|
126
|
-
fields: d.fields
|
|
127
|
+
fields: d.fields,
|
|
127
128
|
})
|
|
128
129
|
}
|
|
129
130
|
if (result.docs.length < pageSize) break
|
|
@@ -154,7 +155,7 @@ async function run(): Promise<void> {
|
|
|
154
155
|
let skipped = 0
|
|
155
156
|
|
|
156
157
|
for (const doc of allDocs) {
|
|
157
|
-
const image = doc.fields[FIELD_NAME]
|
|
158
|
+
const image = doc.fields[FIELD_NAME]
|
|
158
159
|
if (image == null || !image.storagePath) {
|
|
159
160
|
console.log(` - skip ${doc.id} (${doc.path}) — no image value`)
|
|
160
161
|
skipped += 1
|
|
@@ -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,14 @@ import {
|
|
|
29
28
|
import { migrate, postgresSearch } from '@byline/search-postgres'
|
|
30
29
|
import { localStorageProvider } from '@byline/storage-local'
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 './clients.server.js'
|
|
32
|
+
import { collections } from './collections/index.js'
|
|
33
|
+
import { serverHooks } from './collections/server-hooks.js'
|
|
41
34
|
import { i18n } from './i18n.js'
|
|
42
35
|
import { DEFAULT_SERVER_URL, routes } from './routes.js'
|
|
43
36
|
|
|
44
37
|
const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
|
|
45
38
|
|
|
46
|
-
const collections = [Docs, News, Pages, Media, NewsCategories]
|
|
47
|
-
|
|
48
39
|
// HMR-safe singleton. Vite's program reload re-evaluates this module
|
|
49
40
|
// without disposing the previous module's resources — every reload
|
|
50
41
|
// would otherwise allocate a fresh pg `Pool` (max: 20) inside
|
|
@@ -149,6 +140,7 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
|
|
|
149
140
|
i18n,
|
|
150
141
|
routes,
|
|
151
142
|
collections,
|
|
143
|
+
hooks: serverHooks,
|
|
152
144
|
db,
|
|
153
145
|
adminStore,
|
|
154
146
|
// Site-wide default storage provider — used by any upload collection
|
|
@@ -173,8 +165,7 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
|
|
|
173
165
|
}),
|
|
174
166
|
// S3-compatible alternative (AWS S3 / Cloudflare R2 / MinIO). Replace
|
|
175
167
|
// the `localStorageProvider` block above with the call below and add
|
|
176
|
-
// the corresponding `BYLINE_STORAGE_S3_*` entries to your `.env.local
|
|
177
|
-
// (see `apps/webapp/.env.local.example`).
|
|
168
|
+
// the corresponding `BYLINE_STORAGE_S3_*` entries to your `.env.local`.
|
|
178
169
|
//
|
|
179
170
|
// On AWS with an IAM role / instance profile, omit `accessKeyId` and
|
|
180
171
|
// `secretAccessKey` so the SDK resolves credentials via its default
|
|
@@ -214,7 +205,7 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
|
|
|
214
205
|
//
|
|
215
206
|
// We pass `getAdminBylineClient` (not the public client) because
|
|
216
207
|
// admin server fns are the only call sites that read documents in
|
|
217
|
-
// the admin
|
|
208
|
+
// the admin host today — the populate phase inherits whichever
|
|
218
209
|
// actor the request resolved. A future public-facing host would
|
|
219
210
|
// register its own client whose `requestContext` factory resolves
|
|
220
211
|
// an end-user actor instead. See `@byline/host-tanstack-start/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { devtools } from '@tanstack/devtools-vite'
|
|
2
|
+
import { bylineClientHookBoundary } from '@byline/host-tanstack-start/vite'
|
|
2
3
|
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
3
4
|
|
|
4
5
|
import tailwindcss from '@tailwindcss/vite'
|
|
@@ -158,6 +159,7 @@ const config = defineConfig({
|
|
|
158
159
|
},
|
|
159
160
|
plugins: [
|
|
160
161
|
browserAsyncHooksAlias(),
|
|
162
|
+
bylineClientHookBoundary(),
|
|
161
163
|
devtools(),
|
|
162
164
|
nitro({
|
|
163
165
|
preset: 'node',
|
|
@@ -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/')
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
* Both call `defineClientConfig` idempotently.
|
|
25
25
|
*
|
|
26
26
|
* If you also want to use the Byline UI components on your public site,
|
|
27
|
-
* import the same stylesheets from your
|
|
28
|
-
* instead (e.g. `_front-end/route.tsx`).
|
|
27
|
+
* import the same stylesheets from your public layout route as well.
|
|
29
28
|
*/
|
|
30
29
|
|
|
31
30
|
import { createLazyFileRoute, Outlet } from '@tanstack/react-router'
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Pathless route definition for everything Byline-rendered (the admin
|
|
11
11
|
* shell and the sign-in page). The `_byline` prefix doesn't contribute
|
|
12
|
-
* to URL paths — `/_byline/admin/...` resolves to `/admin/...` and
|
|
12
|
+
* to URL paths — by default, `/_byline/admin/...` resolves to `/admin/...` and
|
|
13
13
|
* `/_byline/sign-in` resolves to `/sign-in`.
|
|
14
14
|
*
|
|
15
15
|
* This file is intentionally light. The router needs the route definition
|