@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
|
@@ -5,16 +5,14 @@ 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 '~/blocks/photo-block'
|
|
9
|
-
import type { MediaFields } from '~/collections/media/schema'
|
|
10
|
-
|
|
11
8
|
import { ResponsiveImage } from '@/ui/byline/components/responsive-image'
|
|
12
9
|
import { LexicalRichText } from '@/ui/byline/components/richtext-lexical'
|
|
10
|
+
import type { PopulatedPhotoBlockData } from '@/ui/byline/types/content'
|
|
13
11
|
import type { Locale } from '@/ui/byline/types/i18n'
|
|
14
12
|
|
|
15
13
|
interface Props {
|
|
16
14
|
id: string
|
|
17
|
-
block:
|
|
15
|
+
block: PopulatedPhotoBlockData
|
|
18
16
|
lng: Locale
|
|
19
17
|
constrainedLayout?: boolean
|
|
20
18
|
className?: string
|
|
@@ -29,27 +27,26 @@ export function PhotoBlock({
|
|
|
29
27
|
}: Props): React.JSX.Element | null {
|
|
30
28
|
const { photo, alt, caption, display } = block
|
|
31
29
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// document: <MediaFields> }`. Bail out if the relation is missing or
|
|
36
|
-
// unpopulated.
|
|
37
|
-
if (photo == null || (photo as { _resolved?: boolean })._resolved !== true) {
|
|
38
|
-
return null
|
|
39
|
-
}
|
|
40
|
-
const media = (photo as unknown as { document: MediaFields }).document
|
|
30
|
+
// Missing, unresolved, and cycle-suppressed relations have no document.
|
|
31
|
+
const media = photo?.document?.fields
|
|
32
|
+
if (media == null) return null
|
|
41
33
|
|
|
42
34
|
const Comp = display === 'full_width' ? 'div' : Container
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
const captionRoot =
|
|
37
|
+
caption != null && !Array.isArray(caption) && typeof caption === 'object'
|
|
38
|
+
? caption.root
|
|
39
|
+
: undefined
|
|
40
|
+
const captionChildren =
|
|
41
|
+
captionRoot != null && !Array.isArray(captionRoot) && typeof captionRoot === 'object'
|
|
42
|
+
? captionRoot.children
|
|
43
|
+
: undefined
|
|
47
44
|
|
|
48
45
|
return (
|
|
49
46
|
<Comp
|
|
50
47
|
id={id}
|
|
51
48
|
className={cx(
|
|
52
|
-
'px-0
|
|
49
|
+
'px-0',
|
|
53
50
|
{
|
|
54
51
|
'lg:max-w-[920px] xl:max-w-[920px] 2xl:max-w-[920px] mx-auto': display === 'default',
|
|
55
52
|
},
|
|
@@ -65,12 +62,10 @@ export function PhotoBlock({
|
|
|
65
62
|
className="photo-block"
|
|
66
63
|
imgClassName="photo-block--photo"
|
|
67
64
|
/>
|
|
68
|
-
{
|
|
69
|
-
<Container
|
|
70
|
-
className=
|
|
71
|
-
|
|
72
|
-
<div className="photo-block--caption">
|
|
73
|
-
<LexicalRichText lng={lng} nodes={captionDoc?.root?.children} />
|
|
65
|
+
{captionChildren != null && (
|
|
66
|
+
<Container className={cx('py-2', { 'px-0': constrainedLayout })}>
|
|
67
|
+
<div className="photo-block--caption [&_p]:m-0 muted text-[1rem]">
|
|
68
|
+
<LexicalRichText lng={lng} nodes={captionChildren} />
|
|
74
69
|
</div>
|
|
75
70
|
</Container>
|
|
76
71
|
)}
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
'use
|
|
1
|
+
'use client'
|
|
2
2
|
|
|
3
3
|
import type React from 'react'
|
|
4
4
|
|
|
@@ -28,13 +28,16 @@ export function AdmonitionSerializer({
|
|
|
28
28
|
options: SerializeOptions
|
|
29
29
|
}): React.JSX.Element {
|
|
30
30
|
const intent = intentMap[node.admonitionType as keyof typeof intentMap]
|
|
31
|
+
// Admonitions are ElementNodes, so their body is stored directly in children.
|
|
31
32
|
return (
|
|
32
|
-
<
|
|
33
|
-
{node
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
<div className="not-prose pt-6 pb-8">
|
|
34
|
+
<Alert title={node.title} intent={intent} close={false} className="not-prose my-0">
|
|
35
|
+
{node.children != null ? (
|
|
36
|
+
serialize({ nodes: node.children, lng, options })
|
|
37
|
+
) : (
|
|
38
|
+
<span>Content not found for admonition.</span>
|
|
39
|
+
)}
|
|
40
|
+
</Alert>
|
|
41
|
+
</div>
|
|
39
42
|
)
|
|
40
43
|
}
|
|
@@ -10,14 +10,16 @@ interface CodeProps extends CodeIntrinsicProps {
|
|
|
10
10
|
className?: string
|
|
11
11
|
title?: string
|
|
12
12
|
code: string
|
|
13
|
-
language?: string
|
|
13
|
+
language?: string | null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function Code({ code, className, language
|
|
16
|
+
export function Code({ code, className, language }: CodeProps): React.JSX.Element {
|
|
17
|
+
// Prism calls `.toLowerCase()` on this value, so null and empty legacy values need a fallback.
|
|
18
|
+
const resolvedLanguage = language != null && language.length > 0 ? language : 'typescript'
|
|
17
19
|
return (
|
|
18
|
-
<Highlight theme={themes.oneDark} code={code} language={
|
|
20
|
+
<Highlight theme={themes.oneDark} code={code} language={resolvedLanguage}>
|
|
19
21
|
{({ tokens, getLineProps, getTokenProps }) => (
|
|
20
|
-
<div className="code scroller group overflow-y-auto rounded border border-theme-600 relative">
|
|
22
|
+
<div className="code scroller group overflow-y-auto rounded border border-theme-600 relative mb-4">
|
|
21
23
|
<CopyButton
|
|
22
24
|
variant="outlined"
|
|
23
25
|
intent="primary"
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Locale-aware Link STUB.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* pass-through — it accepts the `lng` prop but does NOT prefix URLs.
|
|
4
|
+
* This stub is a thin pass-through: it accepts the `lng` prop but does not
|
|
5
|
+
* prefix URLs. A localized app can replace the body with a wrapper that
|
|
6
|
+
* injects its locale path parameter into TanStack Router's `<Link>`.
|
|
8
7
|
*
|
|
9
8
|
* If your app is single-locale, this is fine as-is.
|
|
10
9
|
*
|
|
11
|
-
* If you have a multi-locale strategy, replace the body with your own
|
|
12
|
-
* Link wrapper. The reference implementation lives at
|
|
13
|
-
* apps/webapp/src/i18n/components/lang-link.tsx
|
|
14
|
-
* in https://github.com/Byline-CMS/bylinecms.dev.
|
|
10
|
+
* If you have a multi-locale strategy, replace the body with your own Link wrapper.
|
|
15
11
|
*/
|
|
16
12
|
|
|
17
13
|
import type React from 'react'
|
|
@@ -84,14 +84,15 @@ function getHref(args: LinkFieldAttributes): string {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const hrefIsLocal = ['tel:', 'mailto:', '/'].some((prefix) => href.startsWith(prefix))
|
|
87
|
-
|
|
87
|
+
// Relative custom hrefs are valid but cannot be passed to URL without a base.
|
|
88
|
+
if (!hrefIsLocal && /^[a-z][a-z\d+.-]*:/i.test(href)) {
|
|
88
89
|
try {
|
|
89
90
|
const objectURL = new URL(href)
|
|
90
91
|
if (objectURL.origin === publicWebsiteUrl) {
|
|
91
92
|
href = objectURL.href.replace(publicWebsiteUrl, '')
|
|
92
93
|
}
|
|
93
94
|
} catch (e) {
|
|
94
|
-
console.error(`Failed to format url: ${href}`, e)
|
|
95
|
+
console.error(`Failed to format url: ${href}`, e)
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -7,8 +7,6 @@ import cx from 'classnames'
|
|
|
7
7
|
import { LangLink } from '@/ui/byline/components/link/lang-link'
|
|
8
8
|
import type { Locale } from '@/ui/byline/types/i18n'
|
|
9
9
|
|
|
10
|
-
// import { getPublicWebsiteUrl } from '@/utils/utils.framework.ts'
|
|
11
|
-
|
|
12
10
|
interface BaseLinkAttributes {
|
|
13
11
|
newTab?: boolean
|
|
14
12
|
nofollow?: boolean
|
|
@@ -134,13 +132,16 @@ function getHref(args: LinkAttributes): string {
|
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
if (!isLocalHref(href)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
// Relative custom hrefs are valid but cannot be passed to URL without a base.
|
|
136
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(href)) {
|
|
137
|
+
try {
|
|
138
|
+
const objectURL = new URL(href)
|
|
139
|
+
if (objectURL.origin === publicWebsiteUrl) {
|
|
140
|
+
href = objectURL.href.replace(publicWebsiteUrl, '')
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error(`Failed to format url: ${href}`, e)
|
|
141
144
|
}
|
|
142
|
-
} catch (e) {
|
|
143
|
-
console.error(`Failed to format url: ${href}`, e) // eslint-disable-line no-console
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -1,45 +1,13 @@
|
|
|
1
|
-
import type { BlocksUnion } from '@byline/core'
|
|
2
1
|
import { Section } from '@byline/ui/react'
|
|
3
2
|
|
|
4
|
-
import { PhotoBlock as PhotoBlockDef } from '~/blocks/photo-block'
|
|
5
|
-
import { RichTextBlock as RichTextBlockDef } from '~/blocks/richtext-block'
|
|
6
|
-
|
|
7
3
|
import { PhotoBlock } from '@/ui/byline/blocks/photo-block'
|
|
8
4
|
import { RichTextBlock } from '@/ui/byline/blocks/richtext-block'
|
|
9
|
-
import { toKebabCase } from '@/ui/utils/to-kebab-case'
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
const Blocks = [PhotoBlockDef, RichTextBlockDef] as const
|
|
13
|
-
|
|
14
|
-
export type AnyBlock = BlocksUnion<typeof Blocks>
|
|
15
|
-
|
|
16
|
-
// Mapped type ensures every AnyBlock['_type'] has a registered component.
|
|
17
|
-
// TypeScript errors here if Blocks gains a new type without a matching entry.
|
|
18
|
-
type BlockRegistry = {
|
|
19
|
-
[K in AnyBlock['_type']]: React.ComponentType<{
|
|
20
|
-
id: string
|
|
21
|
-
block: Extract<AnyBlock, { _type: K }>
|
|
22
|
-
lng: Locale
|
|
23
|
-
constrainedLayout?: boolean
|
|
24
|
-
}>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const blockComponents: BlockRegistry = {
|
|
28
|
-
photoBlock: PhotoBlock,
|
|
29
|
-
richTextBlock: RichTextBlock,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Loose alias for the call site — BlockRegistry enforces correctness at
|
|
33
|
-
// definition time; TypeScript can't infer the correlation during the map loop.
|
|
34
|
-
type AnyBlockComponent = React.ComponentType<{
|
|
35
|
-
id: string
|
|
36
|
-
block: AnyBlock
|
|
37
|
-
lng: Locale
|
|
38
|
-
constrainedLayout?: boolean
|
|
39
|
-
}>
|
|
5
|
+
import { toKebabCase } from '@/ui/byline/utils/to-kebab-case'
|
|
6
|
+
import type { PopulatedContentBlock } from '@/ui/byline/types/content'
|
|
7
|
+
import type { Locale } from '@/ui/byline/types/i18n'
|
|
40
8
|
|
|
41
9
|
interface Props {
|
|
42
|
-
blocks:
|
|
10
|
+
blocks: PopulatedContentBlock[] | undefined | null
|
|
43
11
|
lng: Locale
|
|
44
12
|
constrainedLayout?: boolean
|
|
45
13
|
}
|
|
@@ -54,13 +22,45 @@ export function RenderBlocks({
|
|
|
54
22
|
return (
|
|
55
23
|
<>
|
|
56
24
|
{blocks.map((block) => {
|
|
57
|
-
const
|
|
25
|
+
const content = renderBlock(block, lng, constrainedLayout)
|
|
26
|
+
if (content == null) return null
|
|
27
|
+
|
|
58
28
|
return (
|
|
59
29
|
<Section className={toKebabCase(block._type)} key={block._id}>
|
|
60
|
-
|
|
30
|
+
{content}
|
|
61
31
|
</Section>
|
|
62
32
|
)
|
|
63
33
|
})}
|
|
64
34
|
</>
|
|
65
35
|
)
|
|
66
36
|
}
|
|
37
|
+
|
|
38
|
+
function renderBlock(
|
|
39
|
+
block: PopulatedContentBlock,
|
|
40
|
+
lng: Locale,
|
|
41
|
+
constrainedLayout: boolean
|
|
42
|
+
): React.JSX.Element | null {
|
|
43
|
+
switch (block._type) {
|
|
44
|
+
case 'photoBlock':
|
|
45
|
+
return (
|
|
46
|
+
<PhotoBlock id={block._id} block={block} lng={lng} constrainedLayout={constrainedLayout} />
|
|
47
|
+
)
|
|
48
|
+
case 'richTextBlock':
|
|
49
|
+
return (
|
|
50
|
+
<RichTextBlock
|
|
51
|
+
id={block._id}
|
|
52
|
+
block={block}
|
|
53
|
+
lng={lng}
|
|
54
|
+
constrainedLayout={constrainedLayout}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
default:
|
|
58
|
+
return reportUnsupportedBlock(block)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function reportUnsupportedBlock(block: never): null {
|
|
63
|
+
const type = (block as { _type?: unknown })._type
|
|
64
|
+
console.error(`Unsupported content block type: "${String(type)}"`)
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WithPopulated } from '@byline/client'
|
|
2
|
+
|
|
3
|
+
import type { DocsFields, MediaFields, PagesFields } from '~/generated/collection-types.js'
|
|
4
|
+
|
|
5
|
+
type ContentBlockOf<Fields extends { content?: unknown }> =
|
|
6
|
+
NonNullable<Fields['content']> extends Array<infer Block> ? Block : never
|
|
7
|
+
type ContentBlock = ContentBlockOf<DocsFields> | ContentBlockOf<PagesFields>
|
|
8
|
+
type PhotoBlockData = Extract<ContentBlock, { _type: 'photoBlock' }>
|
|
9
|
+
type PopulatePhotoBlock<Block> = Block extends PhotoBlockData
|
|
10
|
+
? WithPopulated<Block, 'photo', MediaFields>
|
|
11
|
+
: Block
|
|
12
|
+
type PopulatePhotoBlockContent<Content> =
|
|
13
|
+
Content extends Array<infer Block> ? Array<PopulatePhotoBlock<Block>> : Content
|
|
14
|
+
|
|
15
|
+
/** Photo block shape returned by reads that populate the `photo` relation. */
|
|
16
|
+
export type PopulatedPhotoBlockData = WithPopulated<PhotoBlockData, 'photo', MediaFields>
|
|
17
|
+
|
|
18
|
+
export type PopulatedContentBlock = PopulatePhotoBlock<ContentBlock>
|
|
19
|
+
|
|
20
|
+
/** Overlay populated photo blocks while preserving each consumer's content union. */
|
|
21
|
+
export type WithPopulatedPhotoBlockContent<Fields> = {
|
|
22
|
+
[Key in keyof Fields]: Key extends 'content'
|
|
23
|
+
? PopulatePhotoBlockContent<Fields[Key]>
|
|
24
|
+
: Fields[Key]
|
|
25
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Locale` type used throughout the ui/byline tree as a `lng` prop type.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* stub keeps the type as `string` so the install builds without
|
|
7
|
-
* requiring you to wire up an i18n config.
|
|
4
|
+
* This portable default stays `string` so the scaffold builds without an
|
|
5
|
+
* application-specific i18n module.
|
|
8
6
|
*
|
|
9
|
-
* If your app has its own i18n setup, replace this
|
|
10
|
-
* re-
|
|
11
|
-
*
|
|
12
|
-
* export type { Locale } from '@/i18n/i18n-config'
|
|
7
|
+
* If your app has its own i18n setup, replace this alias with its narrower
|
|
8
|
+
* locale union or re-export that setup's `Locale` type from this file.
|
|
13
9
|
*/
|
|
14
10
|
export type Locale = string
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Context } from './context.js';
|
|
2
|
+
import type { Answers } from './types.js';
|
|
3
|
+
export declare function createTestContext(answers?: Answers): Context;
|
|
4
|
+
export declare function createTestContextAt(cwd: string, answers?: Answers): Context;
|
|
5
|
+
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AA6BzC,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,OAAY,GAAG,OAAO,CAGhE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY,GAAG,OAAO,CAe/E"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mkdtempSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Context } from './context.js';
|
|
5
|
+
import { StateStore } from './state.js';
|
|
6
|
+
const noop = () => { };
|
|
7
|
+
const logger = { info: noop, warn: noop, error: noop, success: noop, step: noop, raw: noop };
|
|
8
|
+
const prompter = {
|
|
9
|
+
async text({ defaultValue }) {
|
|
10
|
+
return defaultValue ?? '';
|
|
11
|
+
},
|
|
12
|
+
async password() {
|
|
13
|
+
return 'password';
|
|
14
|
+
},
|
|
15
|
+
async select({ options }) {
|
|
16
|
+
const first = options[0];
|
|
17
|
+
if (!first)
|
|
18
|
+
throw new Error('test prompt has no options');
|
|
19
|
+
return first.value;
|
|
20
|
+
},
|
|
21
|
+
async confirm({ defaultValue }) {
|
|
22
|
+
return defaultValue ?? true;
|
|
23
|
+
},
|
|
24
|
+
spinner: () => ({ start: noop, stop: noop }),
|
|
25
|
+
intro: noop,
|
|
26
|
+
outro: noop,
|
|
27
|
+
note: noop,
|
|
28
|
+
cancel(message) {
|
|
29
|
+
throw new Error(message);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export function createTestContext(answers = {}) {
|
|
33
|
+
const cwd = mkdtempSync(join(tmpdir(), 'byline-cli-test-'));
|
|
34
|
+
return createTestContextAt(cwd, answers);
|
|
35
|
+
}
|
|
36
|
+
export function createTestContextAt(cwd, answers = {}) {
|
|
37
|
+
const state = new StateStore(cwd);
|
|
38
|
+
state.patchAnswers(answers);
|
|
39
|
+
return new Context({
|
|
40
|
+
cwd,
|
|
41
|
+
apply: true,
|
|
42
|
+
dryRun: false,
|
|
43
|
+
yes: true,
|
|
44
|
+
reset: false,
|
|
45
|
+
resetConfirmed: false,
|
|
46
|
+
cliFlags: {},
|
|
47
|
+
logger,
|
|
48
|
+
prompter,
|
|
49
|
+
state,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=test-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.js","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAKvC,MAAM,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;AACrB,MAAM,MAAM,GAAW,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;AACpG,MAAM,QAAQ,GAAa;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE;QACzB,OAAO,YAAY,IAAI,EAAE,CAAA;IAC3B,CAAC;IACD,KAAK,CAAC,QAAQ;QACZ,OAAO,UAAU,CAAA;IACnB,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE;QACtB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACxB,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;QACzD,OAAO,KAAK,CAAC,KAAK,CAAA;IACpB,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE;QAC5B,OAAO,YAAY,IAAI,IAAI,CAAA;IAC7B,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5C,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;IACV,MAAM,CAAC,OAAO;QACZ,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;CACF,CAAA;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAY,EAAE;IACrD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC3D,OAAO,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,OAAO,GAAY,EAAE;IACpE,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAA;IACjC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC3B,OAAO,IAAI,OAAO,CAAC;QACjB,GAAG;QACH,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,KAAK;QACZ,cAAc,EAAE,KAAK;QACrB,QAAQ,EAAE,EAAE;QACZ,MAAM;QACN,QAAQ;QACR,KAAK;KACN,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type DbStrategy = 'existing' | 'docker';
|
|
|
6
6
|
export interface FileWrite {
|
|
7
7
|
path: string;
|
|
8
8
|
contents: string;
|
|
9
|
-
mode?: 'create' | 'overwrite' | 'patch';
|
|
9
|
+
mode?: 'create' | 'overwrite' | 'patch' | 'delete';
|
|
10
10
|
before?: string;
|
|
11
11
|
}
|
|
12
12
|
export interface ShellCommand {
|
|
@@ -18,7 +18,26 @@ export interface Plan {
|
|
|
18
18
|
writes: FileWrite[];
|
|
19
19
|
commands: ShellCommand[];
|
|
20
20
|
notes: string[];
|
|
21
|
+
preconditions?: PlanPrecondition[];
|
|
21
22
|
}
|
|
23
|
+
export interface TreeSnapshotEntry {
|
|
24
|
+
path: string;
|
|
25
|
+
type: 'directory' | 'file' | 'other';
|
|
26
|
+
contents?: string;
|
|
27
|
+
}
|
|
28
|
+
export type PlanPrecondition = {
|
|
29
|
+
type: 'file';
|
|
30
|
+
path: string;
|
|
31
|
+
contents: string | null;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'tree';
|
|
34
|
+
path: string;
|
|
35
|
+
entries: TreeSnapshotEntry[];
|
|
36
|
+
} | {
|
|
37
|
+
type: 'value';
|
|
38
|
+
key: string;
|
|
39
|
+
value: string;
|
|
40
|
+
};
|
|
22
41
|
export interface PhaseResult {
|
|
23
42
|
state: PhaseState;
|
|
24
43
|
notes?: string[];
|
|
@@ -38,6 +57,7 @@ export interface Answers {
|
|
|
38
57
|
dbName?: string;
|
|
39
58
|
dbUser?: string;
|
|
40
59
|
adminPath?: string;
|
|
60
|
+
signInPath?: string;
|
|
41
61
|
uiDir?: string;
|
|
42
62
|
examples?: boolean;
|
|
43
63
|
importDocs?: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,MAAM,OAAO,GACf,WAAW,GACX,SAAS,GACT,MAAM,GACN,IAAI,GACJ,SAAS,GACT,KAAK,GACL,MAAM,GACN,UAAU,GACV,YAAY,GACZ,WAAW,GACX,MAAM,GACN,QAAQ,GACR,IAAI,GACJ,QAAQ,CAAA;AAEZ,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAEnE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;AAE5D,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAA;AAE9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-template-contract.test.d.ts","sourceRoot":"","sources":["../src/ui-template-contract.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { ModuleKind, ModuleResolutionKind, Project, ScriptTarget, SyntaxKind } from 'ts-morph';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
const templates = resolve(dirname(fileURLToPath(import.meta.url)), 'templates');
|
|
6
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
7
|
+
const generated = project.addSourceFileAtPath(resolve(templates, 'byline-examples/generated/collection-types.ts'));
|
|
8
|
+
const contentTypes = project.addSourceFileAtPath(resolve(templates, 'ui-byline/types/content.ts'));
|
|
9
|
+
const renderer = project.addSourceFileAtPath(resolve(templates, 'ui-byline/render-blocks.tsx'));
|
|
10
|
+
describe('public block renderer templates', () => {
|
|
11
|
+
it('covers every generated Docs and Pages block discriminant', () => {
|
|
12
|
+
const blockNames = new Set([
|
|
13
|
+
...contentBlockNames('DocsFields'),
|
|
14
|
+
...contentBlockNames('PagesFields'),
|
|
15
|
+
]);
|
|
16
|
+
const expectedTypes = [...blockNames].map(blockDiscriminant).sort();
|
|
17
|
+
const switchStatement = renderer
|
|
18
|
+
.getDescendantsOfKind(SyntaxKind.SwitchStatement)
|
|
19
|
+
.find((node) => node.getExpression().getText() === 'block._type');
|
|
20
|
+
const renderedTypes = switchStatement
|
|
21
|
+
?.getCaseBlock()
|
|
22
|
+
.getClauses()
|
|
23
|
+
.flatMap((clause) => {
|
|
24
|
+
if (!clause.isKind(SyntaxKind.CaseClause))
|
|
25
|
+
return [];
|
|
26
|
+
const expression = clause.getExpression();
|
|
27
|
+
return expression.isKind(SyntaxKind.StringLiteral) ? [expression.getLiteralValue()] : [];
|
|
28
|
+
})
|
|
29
|
+
.sort();
|
|
30
|
+
expect(renderedTypes).toEqual(expectedTypes);
|
|
31
|
+
});
|
|
32
|
+
it('combines both consumers and preserves consumer-specific content in the overlay', () => {
|
|
33
|
+
expect(contentTypes.getTypeAliasOrThrow('ContentBlock').getTypeNodeOrThrow().getText()).toBe('ContentBlockOf<DocsFields> | ContentBlockOf<PagesFields>');
|
|
34
|
+
expect(contentTypes.getTypeAlias('WithPopulatedPhotoBlockContent')).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
it('typechecks the populated union and consumer-specific overlay contract', () => {
|
|
37
|
+
const typeProject = new Project({
|
|
38
|
+
useInMemoryFileSystem: true,
|
|
39
|
+
compilerOptions: {
|
|
40
|
+
strict: true,
|
|
41
|
+
noEmit: true,
|
|
42
|
+
module: ModuleKind.ESNext,
|
|
43
|
+
moduleResolution: ModuleResolutionKind.Bundler,
|
|
44
|
+
target: ScriptTarget.ES2024,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
typeProject.createSourceFile('/client.ts', `
|
|
48
|
+
export interface PopulatedRelation<T> { document?: T }
|
|
49
|
+
export type WithPopulated<F, K extends keyof F, Target> = {
|
|
50
|
+
[P in keyof F]: P extends K ? PopulatedRelation<Target> : F[P]
|
|
51
|
+
}
|
|
52
|
+
`);
|
|
53
|
+
typeProject.createSourceFile('/generated.ts', generated.getFullText().replace(/import type \{[\s\S]*?\} from '@byline\/core'/, `type JsonValue = unknown
|
|
54
|
+
type RelatedDocumentValue = { documentId: string }
|
|
55
|
+
type StoredFileValue = { key: string }`));
|
|
56
|
+
typeProject.createSourceFile('/content.ts', contentTypes
|
|
57
|
+
.getFullText()
|
|
58
|
+
.replace("from '@byline/client'", "from './client.js'")
|
|
59
|
+
.replace("from '~/generated/collection-types.js'", "from './generated.js'"));
|
|
60
|
+
typeProject.createSourceFile('/contract.ts', `
|
|
61
|
+
import type { WithPopulated } from './client.js'
|
|
62
|
+
import type { DocsFields, MediaFields, PagesFields } from './generated.js'
|
|
63
|
+
import type {
|
|
64
|
+
PopulatedContentBlock,
|
|
65
|
+
WithPopulatedPhotoBlockContent,
|
|
66
|
+
} from './content.js'
|
|
67
|
+
|
|
68
|
+
type Equal<Left, Right> =
|
|
69
|
+
(<Value>() => Value extends Left ? 1 : 2) extends
|
|
70
|
+
(<Value>() => Value extends Right ? 1 : 2) ? true : false
|
|
71
|
+
type Assert<Value extends true> = Value
|
|
72
|
+
type ConsumerBlock =
|
|
73
|
+
| NonNullable<DocsFields['content']>[number]
|
|
74
|
+
| NonNullable<PagesFields['content']>[number]
|
|
75
|
+
type PhotoBlock = Extract<ConsumerBlock, { _type: 'photoBlock' }>
|
|
76
|
+
type ExpectedBlock =
|
|
77
|
+
| Exclude<ConsumerBlock, PhotoBlock>
|
|
78
|
+
| WithPopulated<PhotoBlock, 'photo', MediaFields>
|
|
79
|
+
type PagesOnlyBlock = { _id: string; _type: 'pagesOnlyBlock'; heading: string }
|
|
80
|
+
type ExtendedPages = Omit<PagesFields, 'content'> & {
|
|
81
|
+
content?: Array<NonNullable<PagesFields['content']>[number] | PagesOnlyBlock>
|
|
82
|
+
}
|
|
83
|
+
type PopulatedExtendedBlock = NonNullable<
|
|
84
|
+
WithPopulatedPhotoBlockContent<ExtendedPages>['content']
|
|
85
|
+
>[number]
|
|
86
|
+
|
|
87
|
+
type PopulatedUnionContract = Assert<Equal<PopulatedContentBlock, ExpectedBlock>>
|
|
88
|
+
type ConsumerOverlayContract = Assert<
|
|
89
|
+
Equal<Extract<PopulatedExtendedBlock, { _type: 'pagesOnlyBlock' }>, PagesOnlyBlock>
|
|
90
|
+
>
|
|
91
|
+
`);
|
|
92
|
+
expect(typeProject.getPreEmitDiagnostics().map((diagnostic) => diagnostic.getMessageText())).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
it('reports unsupported runtime blocks and returns null', () => {
|
|
95
|
+
const fallback = renderer.getFunctionOrThrow('reportUnsupportedBlock');
|
|
96
|
+
expect(fallback
|
|
97
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
98
|
+
.some((call) => call.getExpression().getText() === 'console.error')).toBe(true);
|
|
99
|
+
expect(fallback
|
|
100
|
+
.getDescendantsOfKind(SyntaxKind.ReturnStatement)
|
|
101
|
+
.some((statement) => statement.getExpression()?.isKind(SyntaxKind.NullKeyword))).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
function contentBlockNames(collection) {
|
|
105
|
+
const content = generated
|
|
106
|
+
.getTypeAliasOrThrow(collection)
|
|
107
|
+
.getDescendantsOfKind(SyntaxKind.PropertySignature)
|
|
108
|
+
.find((property) => property.getName() === 'content');
|
|
109
|
+
const matches = content
|
|
110
|
+
?.getTypeNodeOrThrow()
|
|
111
|
+
.getText()
|
|
112
|
+
.matchAll(/\b([A-Z]\w*BlockData)\b/g);
|
|
113
|
+
return [...(matches ?? [])].map((match) => match[1]).filter((name) => name !== undefined);
|
|
114
|
+
}
|
|
115
|
+
function blockDiscriminant(blockName) {
|
|
116
|
+
const type = generated.getTypeAliasOrThrow(blockName);
|
|
117
|
+
const discriminant = type
|
|
118
|
+
.getDescendantsOfKind(SyntaxKind.PropertySignature)
|
|
119
|
+
.find((property) => property.getName() === '_type')
|
|
120
|
+
?.getTypeNodeOrThrow();
|
|
121
|
+
if (!discriminant?.isKind(SyntaxKind.LiteralType)) {
|
|
122
|
+
throw new Error(`Generated block ${blockName} has no literal _type`);
|
|
123
|
+
}
|
|
124
|
+
const literal = discriminant.getLiteral();
|
|
125
|
+
if (!literal.isKind(SyntaxKind.StringLiteral)) {
|
|
126
|
+
throw new Error(`Generated block ${blockName} has a non-string _type`);
|
|
127
|
+
}
|
|
128
|
+
return literal.getLiteralValue();
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=ui-template-contract.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-template-contract.test.js","sourceRoot":"","sources":["../src/ui-template-contract.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-root.test.d.ts","sourceRoot":"","sources":["../src/workspace-root.test.ts"],"names":[],"mappings":""}
|