prev-cli 0.24.18 → 0.24.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +5 -2
  2. package/src/jsx/adapters/html.test.ts +191 -0
  3. package/src/jsx/adapters/html.ts +404 -0
  4. package/src/jsx/adapters/react.test.ts +172 -0
  5. package/src/jsx/adapters/react.tsx +346 -0
  6. package/src/jsx/define-component.ts +129 -0
  7. package/src/jsx/index.ts +47 -0
  8. package/src/jsx/jsx-runtime.test.ts +63 -0
  9. package/src/jsx/jsx-runtime.ts +117 -0
  10. package/src/jsx/migrate.test.ts +72 -0
  11. package/src/jsx/migrate.ts +451 -0
  12. package/src/jsx/schemas/index.ts +4 -0
  13. package/src/jsx/schemas/primitives.ts +107 -0
  14. package/src/jsx/schemas/tokens.ts +60 -0
  15. package/src/jsx/validation.ts +77 -0
  16. package/src/jsx/vnode.ts +159 -0
  17. package/src/primitives/index.ts +8 -0
  18. package/src/primitives/migrate.test.ts +317 -0
  19. package/src/primitives/migrate.ts +364 -0
  20. package/src/primitives/parser.test.ts +265 -0
  21. package/src/primitives/parser.ts +442 -0
  22. package/src/primitives/template-parser.test.ts +297 -0
  23. package/src/primitives/template-parser.ts +374 -0
  24. package/src/primitives/template-renderer.test.ts +359 -0
  25. package/src/primitives/template-renderer.ts +497 -0
  26. package/src/primitives/tokens.css +82 -0
  27. package/src/primitives/types.ts +248 -0
  28. package/src/tokens/defaults.test.ts +137 -0
  29. package/src/tokens/defaults.ts +77 -0
  30. package/src/tokens/defaults.yaml +76 -0
  31. package/src/tokens/resolver.test.ts +229 -0
  32. package/src/tokens/resolver.ts +173 -0
  33. package/src/tokens/utils.test.ts +172 -0
  34. package/src/tokens/utils.ts +104 -0
  35. package/src/tokens/validation.test.ts +118 -0
  36. package/src/tokens/validation.ts +226 -0
@@ -0,0 +1,107 @@
1
+ // src/jsx/schemas/primitives.ts
2
+ // Zod schemas for primitive props - single source of truth
3
+ import { z } from 'zod'
4
+ import {
5
+ SpacingToken,
6
+ AlignToken,
7
+ BackgroundToken,
8
+ RadiusToken,
9
+ ColorToken,
10
+ SizeToken,
11
+ WeightToken,
12
+ FitToken,
13
+ } from './tokens'
14
+
15
+ // Children type - accepts VNode array or single VNode (normalized later)
16
+ const Children = z.any()
17
+
18
+ // Layout primitive: Col - Vertical stack
19
+ export const ColProps = z.object({
20
+ gap: SpacingToken.optional(),
21
+ align: AlignToken.optional(),
22
+ padding: SpacingToken.optional(),
23
+ children: Children.optional(),
24
+ })
25
+ export type ColProps = z.infer<typeof ColProps>
26
+
27
+ // Layout primitive: Row - Horizontal stack
28
+ export const RowProps = z.object({
29
+ gap: SpacingToken.optional(),
30
+ align: AlignToken.optional(),
31
+ padding: SpacingToken.optional(),
32
+ children: Children.optional(),
33
+ })
34
+ export type RowProps = z.infer<typeof RowProps>
35
+
36
+ // Layout primitive: Box - Container with styling
37
+ export const BoxProps = z.object({
38
+ padding: SpacingToken.optional(),
39
+ bg: BackgroundToken.optional(),
40
+ radius: RadiusToken.optional(),
41
+ children: Children.optional(),
42
+ })
43
+ export type BoxProps = z.infer<typeof BoxProps>
44
+
45
+ // Layout primitive: Spacer - Whitespace
46
+ export const SpacerProps = z.object({
47
+ size: SpacingToken.optional(), // If absent, flex grow
48
+ })
49
+ export type SpacerProps = z.infer<typeof SpacerProps>
50
+
51
+ // Layout primitive: Slot - State-dependent placeholder
52
+ export const SlotProps = z.object({
53
+ name: z.string(),
54
+ })
55
+ export type SlotProps = z.infer<typeof SlotProps>
56
+
57
+ // Content primitive: Text
58
+ export const TextProps = z.object({
59
+ children: z.string().optional(),
60
+ size: SizeToken.optional(),
61
+ weight: WeightToken.optional(),
62
+ color: ColorToken.optional(),
63
+ })
64
+ export type TextProps = z.infer<typeof TextProps>
65
+
66
+ // Content primitive: Icon
67
+ export const IconProps = z.object({
68
+ name: z.string(),
69
+ size: SizeToken.optional(),
70
+ color: ColorToken.optional(),
71
+ })
72
+ export type IconProps = z.infer<typeof IconProps>
73
+
74
+ // Content primitive: Image
75
+ export const ImageProps = z.object({
76
+ src: z.string(),
77
+ alt: z.string().optional(),
78
+ fit: FitToken.optional(),
79
+ })
80
+ export type ImageProps = z.infer<typeof ImageProps>
81
+
82
+ // Primitive type enum
83
+ export const PrimitiveType = z.enum([
84
+ 'col',
85
+ 'row',
86
+ 'box',
87
+ 'spacer',
88
+ 'slot',
89
+ 'text',
90
+ 'icon',
91
+ 'image',
92
+ 'component',
93
+ ])
94
+ export type PrimitiveType = z.infer<typeof PrimitiveType>
95
+
96
+ // Props union for validation
97
+ export const PrimitiveProps = z.union([
98
+ ColProps,
99
+ RowProps,
100
+ BoxProps,
101
+ SpacerProps,
102
+ SlotProps,
103
+ TextProps,
104
+ IconProps,
105
+ ImageProps,
106
+ ])
107
+ export type PrimitiveProps = z.infer<typeof PrimitiveProps>
@@ -0,0 +1,60 @@
1
+ // src/jsx/schemas/tokens.ts
2
+ // Zod schemas for design tokens - single source of truth
3
+ import { z } from 'zod'
4
+
5
+ // Spacing tokens
6
+ export const SpacingToken = z.enum(['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'])
7
+ export type SpacingToken = z.infer<typeof SpacingToken>
8
+
9
+ // Alignment tokens
10
+ export const AlignToken = z.enum(['start', 'center', 'end', 'stretch', 'between'])
11
+ export type AlignToken = z.infer<typeof AlignToken>
12
+
13
+ // Background tokens - shadcn-compatible
14
+ export const BackgroundToken = z.enum([
15
+ 'transparent',
16
+ 'background',
17
+ 'card',
18
+ 'primary',
19
+ 'secondary',
20
+ 'muted',
21
+ 'accent',
22
+ 'destructive',
23
+ 'input',
24
+ ])
25
+ export type BackgroundToken = z.infer<typeof BackgroundToken>
26
+
27
+ // Border radius tokens
28
+ export const RadiusToken = z.enum(['none', 'sm', 'md', 'lg', 'xl', 'full'])
29
+ export type RadiusToken = z.infer<typeof RadiusToken>
30
+
31
+ // Color tokens - shadcn-compatible (foreground colors for text)
32
+ export const ColorToken = z.enum([
33
+ 'foreground',
34
+ 'card-foreground',
35
+ 'primary',
36
+ 'primary-foreground',
37
+ 'secondary',
38
+ 'secondary-foreground',
39
+ 'muted',
40
+ 'muted-foreground',
41
+ 'accent',
42
+ 'accent-foreground',
43
+ 'destructive',
44
+ 'destructive-foreground',
45
+ 'border',
46
+ 'ring',
47
+ ])
48
+ export type ColorToken = z.infer<typeof ColorToken>
49
+
50
+ // Typography size tokens
51
+ export const SizeToken = z.enum(['xs', 'sm', 'base', 'lg', 'xl', '2xl'])
52
+ export type SizeToken = z.infer<typeof SizeToken>
53
+
54
+ // Font weight tokens
55
+ export const WeightToken = z.enum(['normal', 'medium', 'semibold', 'bold'])
56
+ export type WeightToken = z.infer<typeof WeightToken>
57
+
58
+ // Image fit tokens
59
+ export const FitToken = z.enum(['cover', 'contain', 'fill'])
60
+ export type FitToken = z.infer<typeof FitToken>
@@ -0,0 +1,77 @@
1
+ // src/jsx/validation.ts
2
+ // Configurable validation for JSX primitives
3
+ import { z } from 'zod'
4
+
5
+ /**
6
+ * Validation mode controls how schema validation behaves:
7
+ * - 'strict': throws on validation errors (useful for development/testing)
8
+ * - 'warn': logs warnings but continues (default in DEV)
9
+ * - 'off': no validation (default in production)
10
+ */
11
+ export type ValidationMode = 'strict' | 'warn' | 'off'
12
+
13
+ // DEV mode flag
14
+ const DEV = process.env.NODE_ENV !== 'production'
15
+
16
+ // Current validation mode - defaults based on environment
17
+ let validationMode: ValidationMode = DEV ? 'warn' : 'off'
18
+
19
+ /**
20
+ * Set the validation mode for JSX primitives
21
+ * @param mode - The validation mode to use
22
+ */
23
+ export function setValidationMode(mode: ValidationMode): void {
24
+ validationMode = mode
25
+ }
26
+
27
+ /**
28
+ * Get the current validation mode
29
+ */
30
+ export function getValidationMode(): ValidationMode {
31
+ return validationMode
32
+ }
33
+
34
+ /**
35
+ * Validate props against a Zod schema based on current mode.
36
+ * In 'strict' mode, throws on error.
37
+ * In 'warn' mode, logs a warning and continues.
38
+ * In 'off' mode, skips validation entirely.
39
+ *
40
+ * @param schema - Zod schema to validate against
41
+ * @param props - Props to validate
42
+ * @param componentName - Component name for error messages
43
+ */
44
+ export function validateProps(
45
+ schema: z.ZodType,
46
+ props: unknown,
47
+ componentName: string
48
+ ): void {
49
+ if (validationMode === 'off') {
50
+ return
51
+ }
52
+
53
+ const result = schema.safeParse(props)
54
+
55
+ if (!result.success) {
56
+ const errorMessage = formatValidationError(componentName, result.error)
57
+
58
+ if (validationMode === 'strict') {
59
+ throw new Error(errorMessage)
60
+ }
61
+
62
+ // warn mode - log and continue
63
+ console.warn(errorMessage)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Format Zod validation error for display
69
+ */
70
+ function formatValidationError(componentName: string, error: z.ZodError): string {
71
+ const issues = error.issues.map((issue) => {
72
+ const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''
73
+ return ` - ${path}${issue.message}`
74
+ })
75
+
76
+ return `[${componentName}] Invalid props:\n${issues.join('\n')}`
77
+ }
@@ -0,0 +1,159 @@
1
+ // src/jsx/vnode.ts
2
+ // Virtual Node representation - immutable tree structure
3
+ import { z } from 'zod'
4
+ import { PrimitiveType } from './schemas/primitives'
5
+
6
+ export interface VNodeType {
7
+ id: string
8
+ type: z.infer<typeof PrimitiveType>
9
+ props: Record<string, unknown>
10
+ children?: VNodeType[]
11
+ componentName?: string
12
+ }
13
+
14
+ // VNode schema - recursive definition for runtime validation
15
+ export const VNode: z.ZodType<VNodeType> = z.lazy(() =>
16
+ z.object({
17
+ id: z.string(),
18
+ type: PrimitiveType,
19
+ props: z.record(z.string(), z.unknown()),
20
+ children: z.array(VNode).optional(),
21
+ componentName: z.string().optional(),
22
+ })
23
+ )
24
+
25
+ // ============================================================
26
+ // Render Context - isolated ID generation for concurrent safety
27
+ // ============================================================
28
+
29
+ /**
30
+ * Render context for isolated ID generation.
31
+ * Each render should use its own context to avoid ID conflicts
32
+ * when multiple renders execute concurrently.
33
+ */
34
+ export interface RenderContext {
35
+ /** Generate unique ID for a node type */
36
+ nextId: (type: string) => string
37
+ }
38
+
39
+ /**
40
+ * Create a new render context with isolated ID counter.
41
+ * Use this for each render to ensure concurrent safety.
42
+ */
43
+ export function createRenderContext(): RenderContext {
44
+ let counter = 0
45
+ return {
46
+ nextId: (type: string) => `${type}-${counter++}`,
47
+ }
48
+ }
49
+
50
+ // Global context for backward compatibility
51
+ let globalContext = createRenderContext()
52
+
53
+ /**
54
+ * Reset ID counter - call at start of each render.
55
+ * @deprecated Use createRenderContext() for concurrent safety
56
+ */
57
+ export function resetIdCounter(): void {
58
+ globalContext = createRenderContext()
59
+ }
60
+
61
+ /**
62
+ * Get the current global render context.
63
+ * @internal
64
+ */
65
+ export function getGlobalContext(): RenderContext {
66
+ return globalContext
67
+ }
68
+
69
+ /**
70
+ * Create an immutable VNode
71
+ * @param type - Node type (col, row, text, etc.)
72
+ * @param props - Node properties
73
+ * @param children - Child nodes
74
+ * @param context - Optional render context for ID generation (uses global if not provided)
75
+ */
76
+ export function createVNode(
77
+ type: VNodeType['type'],
78
+ props: Record<string, unknown>,
79
+ children?: VNodeType[],
80
+ context?: RenderContext
81
+ ): VNodeType {
82
+ const ctx = context ?? globalContext
83
+ const node: VNodeType = {
84
+ id: ctx.nextId(type),
85
+ type,
86
+ props: Object.freeze({ ...props }),
87
+ children: children ? Object.freeze([...children]) as VNodeType[] : undefined,
88
+ }
89
+ return Object.freeze(node) as VNodeType
90
+ }
91
+
92
+ /**
93
+ * Create a component VNode (wraps rendered content)
94
+ * @param componentName - Name of the component
95
+ * @param props - Component properties
96
+ * @param children - Child nodes
97
+ * @param context - Optional render context for ID generation (uses global if not provided)
98
+ */
99
+ export function createComponentVNode(
100
+ componentName: string,
101
+ props: Record<string, unknown>,
102
+ children?: VNodeType[],
103
+ context?: RenderContext
104
+ ): VNodeType {
105
+ const ctx = context ?? globalContext
106
+ const node: VNodeType = {
107
+ id: ctx.nextId('component'),
108
+ type: 'component',
109
+ props: Object.freeze({ ...props }),
110
+ children: children ? Object.freeze([...children]) as VNodeType[] : undefined,
111
+ componentName,
112
+ }
113
+ return Object.freeze(node) as VNodeType
114
+ }
115
+
116
+ /**
117
+ * Normalize children to array, converting strings/numbers to Text nodes
118
+ */
119
+ export function normalizeChildren(children: unknown): VNodeType[] {
120
+ if (children === null || children === undefined) return []
121
+
122
+ const items = Array.isArray(children) ? children.flat(Infinity) : [children]
123
+
124
+ return items
125
+ .filter((item) => item !== null && item !== undefined && item !== false && item !== true)
126
+ .map((item) => {
127
+ // Convert strings and numbers to Text nodes
128
+ if (typeof item === 'string' || typeof item === 'number') {
129
+ return createVNode('text', { content: String(item) })
130
+ }
131
+ return item as VNodeType
132
+ })
133
+ }
134
+
135
+ /**
136
+ * Check structural equality of two VNodes (ignores IDs)
137
+ */
138
+ export function vnodeEquals(a: VNodeType, b: VNodeType): boolean {
139
+ if (a.type !== b.type) return false
140
+ if (a.componentName !== b.componentName) return false
141
+
142
+ // Compare props (shallow)
143
+ const aProps = Object.entries(a.props).filter(([k]) => k !== 'children')
144
+ const bProps = Object.entries(b.props).filter(([k]) => k !== 'children')
145
+ if (aProps.length !== bProps.length) return false
146
+ for (const [key, value] of aProps) {
147
+ if (b.props[key] !== value) return false
148
+ }
149
+
150
+ // Compare children recursively
151
+ const aChildren = a.children || []
152
+ const bChildren = b.children || []
153
+ if (aChildren.length !== bChildren.length) return false
154
+ for (let i = 0; i < aChildren.length; i++) {
155
+ if (!vnodeEquals(aChildren[i], bChildren[i])) return false
156
+ }
157
+
158
+ return true
159
+ }
@@ -0,0 +1,8 @@
1
+ // src/primitives/index.ts
2
+ // Layout and content primitives for renderer-agnostic templates
3
+
4
+ export * from './types'
5
+ export * from './parser'
6
+ export * from './template-parser'
7
+ export * from './template-renderer'
8
+ export * from './migrate'