@teleporthq/teleport-plugin-common 0.42.35 → 0.43.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.
Files changed (91) hide show
  1. package/__tests__/node-handlers/node-to-jsx/index.ts +164 -0
  2. package/__tests__/node-handlers/node-to-jsx/url-search-params-expression.ts +59 -0
  3. package/__tests__/node-handlers/node-to-jsx/utils.ts +217 -0
  4. package/__tests__/utils/ast-utils.ts +135 -0
  5. package/__tests__/utils/content-property-sanitiser.ts +156 -0
  6. package/__tests__/utils/url-search-params.ts +48 -0
  7. package/dist/cjs/builders/ast-builders.d.ts +1 -0
  8. package/dist/cjs/builders/ast-builders.d.ts.map +1 -1
  9. package/dist/cjs/builders/ast-builders.js +9 -1
  10. package/dist/cjs/builders/ast-builders.js.map +1 -1
  11. package/dist/cjs/builders/style-builders.d.ts +7 -0
  12. package/dist/cjs/builders/style-builders.d.ts.map +1 -1
  13. package/dist/cjs/builders/style-builders.js +35 -1
  14. package/dist/cjs/builders/style-builders.js.map +1 -1
  15. package/dist/cjs/node-handlers/node-to-html/index.d.ts.map +1 -1
  16. package/dist/cjs/node-handlers/node-to-html/index.js +19 -3
  17. package/dist/cjs/node-handlers/node-to-html/index.js.map +1 -1
  18. package/dist/cjs/node-handlers/node-to-html/utils.d.ts +2 -1
  19. package/dist/cjs/node-handlers/node-to-html/utils.d.ts.map +1 -1
  20. package/dist/cjs/node-handlers/node-to-html/utils.js +94 -11
  21. package/dist/cjs/node-handlers/node-to-html/utils.js.map +1 -1
  22. package/dist/cjs/node-handlers/node-to-jsx/index.d.ts.map +1 -1
  23. package/dist/cjs/node-handlers/node-to-jsx/index.js +701 -42
  24. package/dist/cjs/node-handlers/node-to-jsx/index.js.map +1 -1
  25. package/dist/cjs/node-handlers/node-to-jsx/types.d.ts +40 -2
  26. package/dist/cjs/node-handlers/node-to-jsx/types.d.ts.map +1 -1
  27. package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts +50 -5
  28. package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  29. package/dist/cjs/node-handlers/node-to-jsx/utils.js +492 -35
  30. package/dist/cjs/node-handlers/node-to-jsx/utils.js.map +1 -1
  31. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  32. package/dist/cjs/utils/ast-utils.d.ts +70 -4
  33. package/dist/cjs/utils/ast-utils.d.ts.map +1 -1
  34. package/dist/cjs/utils/ast-utils.js +414 -28
  35. package/dist/cjs/utils/ast-utils.js.map +1 -1
  36. package/dist/cjs/utils/style-utils.d.ts +1 -0
  37. package/dist/cjs/utils/style-utils.d.ts.map +1 -1
  38. package/dist/cjs/utils/style-utils.js +155 -2
  39. package/dist/cjs/utils/style-utils.js.map +1 -1
  40. package/dist/cjs/utils/url-search-params.d.ts +48 -0
  41. package/dist/cjs/utils/url-search-params.d.ts.map +1 -0
  42. package/dist/cjs/utils/url-search-params.js +109 -0
  43. package/dist/cjs/utils/url-search-params.js.map +1 -0
  44. package/dist/esm/builders/ast-builders.d.ts +1 -0
  45. package/dist/esm/builders/ast-builders.d.ts.map +1 -1
  46. package/dist/esm/builders/ast-builders.js +7 -0
  47. package/dist/esm/builders/ast-builders.js.map +1 -1
  48. package/dist/esm/builders/style-builders.d.ts +7 -0
  49. package/dist/esm/builders/style-builders.d.ts.map +1 -1
  50. package/dist/esm/builders/style-builders.js +33 -0
  51. package/dist/esm/builders/style-builders.js.map +1 -1
  52. package/dist/esm/node-handlers/node-to-html/index.d.ts.map +1 -1
  53. package/dist/esm/node-handlers/node-to-html/index.js +20 -4
  54. package/dist/esm/node-handlers/node-to-html/index.js.map +1 -1
  55. package/dist/esm/node-handlers/node-to-html/utils.d.ts +2 -1
  56. package/dist/esm/node-handlers/node-to-html/utils.d.ts.map +1 -1
  57. package/dist/esm/node-handlers/node-to-html/utils.js +93 -11
  58. package/dist/esm/node-handlers/node-to-html/utils.js.map +1 -1
  59. package/dist/esm/node-handlers/node-to-jsx/index.d.ts.map +1 -1
  60. package/dist/esm/node-handlers/node-to-jsx/index.js +703 -44
  61. package/dist/esm/node-handlers/node-to-jsx/index.js.map +1 -1
  62. package/dist/esm/node-handlers/node-to-jsx/types.d.ts +40 -2
  63. package/dist/esm/node-handlers/node-to-jsx/types.d.ts.map +1 -1
  64. package/dist/esm/node-handlers/node-to-jsx/utils.d.ts +50 -5
  65. package/dist/esm/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  66. package/dist/esm/node-handlers/node-to-jsx/utils.js +486 -33
  67. package/dist/esm/node-handlers/node-to-jsx/utils.js.map +1 -1
  68. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  69. package/dist/esm/utils/ast-utils.d.ts +70 -4
  70. package/dist/esm/utils/ast-utils.d.ts.map +1 -1
  71. package/dist/esm/utils/ast-utils.js +403 -27
  72. package/dist/esm/utils/ast-utils.js.map +1 -1
  73. package/dist/esm/utils/style-utils.d.ts +1 -0
  74. package/dist/esm/utils/style-utils.d.ts.map +1 -1
  75. package/dist/esm/utils/style-utils.js +153 -1
  76. package/dist/esm/utils/style-utils.js.map +1 -1
  77. package/dist/esm/utils/url-search-params.d.ts +48 -0
  78. package/dist/esm/utils/url-search-params.d.ts.map +1 -0
  79. package/dist/esm/utils/url-search-params.js +81 -0
  80. package/dist/esm/utils/url-search-params.js.map +1 -0
  81. package/package.json +4 -4
  82. package/src/builders/ast-builders.ts +26 -0
  83. package/src/builders/style-builders.ts +64 -0
  84. package/src/node-handlers/node-to-html/index.ts +34 -4
  85. package/src/node-handlers/node-to-html/utils.ts +111 -11
  86. package/src/node-handlers/node-to-jsx/index.ts +999 -52
  87. package/src/node-handlers/node-to-jsx/types.ts +48 -3
  88. package/src/node-handlers/node-to-jsx/utils.ts +655 -31
  89. package/src/utils/ast-utils.ts +547 -28
  90. package/src/utils/style-utils.ts +157 -1
  91. package/src/utils/url-search-params.ts +122 -0
@@ -1,6 +1,147 @@
1
1
  import { StringUtils } from '@teleporthq/teleport-shared'
2
2
  import { UIDLStaticValue, UIDLStyleDefinitions, UIDLStyleValue } from '@teleporthq/teleport-types'
3
3
 
4
+ const CSS_FUNCTIONS = [
5
+ 'radial-gradient',
6
+ 'linear-gradient',
7
+ 'conic-gradient',
8
+ 'repeating-linear-gradient',
9
+ 'repeating-radial-gradient',
10
+ 'repeating-conic-gradient',
11
+ ]
12
+
13
+ // CSS `content` property has the strictest value grammar of any
14
+ // property the AI emits: it must be either a quoted string, one of
15
+ // a fixed set of keywords, or a recognised function call. The AI
16
+ // occasionally produces garbage here (e.g. the raw fragment `';`
17
+ // straight out of a hallucinated declaration), and the
18
+ // downstream JSS serializer happily concatenates that into
19
+ // `content: ';;` — a CSS parse error that breaks the entire
20
+ // stylesheet on Next.js dev (the browser stops applying any rule
21
+ // after the malformed one). When we can't recognise the value as
22
+ // valid, we fall back to an empty string literal — losing the
23
+ // decoration but keeping the stylesheet parseable.
24
+ const CONTENT_KEYWORDS = new Set([
25
+ 'none',
26
+ 'normal',
27
+ 'open-quote',
28
+ 'close-quote',
29
+ 'no-open-quote',
30
+ 'no-close-quote',
31
+ 'inherit',
32
+ 'initial',
33
+ 'unset',
34
+ 'revert',
35
+ 'revert-layer',
36
+ ])
37
+
38
+ const CONTENT_FUNCTION_PREFIXES = [
39
+ 'attr(',
40
+ 'counter(',
41
+ 'counters(',
42
+ 'var(',
43
+ 'url(',
44
+ 'image(',
45
+ 'image-set(',
46
+ 'linear-gradient(',
47
+ 'radial-gradient(',
48
+ 'conic-gradient(',
49
+ ]
50
+
51
+ const isProperlyQuotedString = (value: string): boolean => {
52
+ if (value.length < 2) {
53
+ return false
54
+ }
55
+ const first = value.charAt(0)
56
+ const last = value.charAt(value.length - 1)
57
+ if (first !== last) {
58
+ return false
59
+ }
60
+ if (first !== '"' && first !== "'") {
61
+ return false
62
+ }
63
+ // Walk the interior and reject unescaped occurrences of the same
64
+ // quote character — those would close the string early and leak
65
+ // arbitrary CSS into the surrounding stylesheet.
66
+ for (let i = 1; i < value.length - 1; i++) {
67
+ const ch = value.charAt(i)
68
+ if (ch === '\\') {
69
+ i++ // skip the escaped character
70
+ continue
71
+ }
72
+ if (ch === first) {
73
+ return false
74
+ }
75
+ }
76
+ return true
77
+ }
78
+
79
+ export const sanitizeContentValue = (raw: string): string => {
80
+ const trimmed = raw.trim()
81
+ if (trimmed === '') {
82
+ return "''"
83
+ }
84
+ if (CONTENT_KEYWORDS.has(trimmed)) {
85
+ return trimmed
86
+ }
87
+ for (const prefix of CONTENT_FUNCTION_PREFIXES) {
88
+ if (trimmed.startsWith(prefix) && trimmed.endsWith(')')) {
89
+ return trimmed
90
+ }
91
+ }
92
+ if (isProperlyQuotedString(trimmed)) {
93
+ return trimmed
94
+ }
95
+ // Last-resort repair: strip every quote and semicolon (the two
96
+ // characters that break the surrounding rule) and wrap the
97
+ // remainder in single quotes. If nothing legible survives, fall
98
+ // through to an empty string. We deliberately do NOT try to
99
+ // preserve "almost a quoted string" inputs — the AI's malformed
100
+ // emissions don't have a clear repair path and silently keeping
101
+ // them risks injecting CSS the user didn't author.
102
+ const stripped = trimmed.replace(/['";]/g, '').trim()
103
+ return "'" + stripped + "'"
104
+ }
105
+
106
+ const isValidCSSValue = (value: string | number, key?: string): boolean => {
107
+ if (typeof value === 'number') {
108
+ return true
109
+ }
110
+
111
+ // Check for CSS functions that are missing their parentheses (e.g. "radial-gradientfoo")
112
+ for (const fn of CSS_FUNCTIONS) {
113
+ if (value.includes(fn) && !value.includes(`${fn}(`)) {
114
+ return false
115
+ }
116
+ }
117
+
118
+ // The `content` property is treated separately by
119
+ // `sanitizeContentValue` — it has its own grammar (quoted string
120
+ // OR keyword OR function call) that the general colon/paren
121
+ // check below would mis-evaluate for a legitimate quoted string
122
+ // that happens to contain a `:`.
123
+ if (key === 'content') {
124
+ return true
125
+ }
126
+
127
+ // Check for values that contain embedded CSS declarations (e.g. "valueproperty-name: other")
128
+ // A colon in a CSS value normally only appears inside url() or var() or similar functions.
129
+ // If a colon appears outside of parentheses, the value is likely corrupted.
130
+ let parenDepth = 0
131
+ for (let i = 0; i < value.length; i++) {
132
+ if (value[i] === '(') {
133
+ parenDepth++
134
+ } else if (value[i] === ')') {
135
+ parenDepth--
136
+ } else if (value[i] === ':' && parenDepth === 0) {
137
+ // Colons outside parentheses in a CSS value indicate a corrupted/merged declaration
138
+ return false
139
+ }
140
+ }
141
+
142
+ return true
143
+ }
144
+
4
145
  const getContentOfStyleKey = (styleValue: UIDLStyleValue) => {
5
146
  if (styleValue.type === 'static') {
6
147
  return styleValue.content
@@ -16,7 +157,22 @@ const getContentOfStyleKey = (styleValue: UIDLStyleValue) => {
16
157
 
17
158
  export const getContentOfStyleObject = (styleObject: UIDLStyleDefinitions) => {
18
159
  return Object.keys(styleObject).reduce((acc: Record<string, unknown>, key) => {
19
- acc[key] = getContentOfStyleKey(styleObject[key])
160
+ const value = getContentOfStyleKey(styleObject[key])
161
+ if (typeof value === 'string' || typeof value === 'number') {
162
+ if (isValidCSSValue(value, key)) {
163
+ // The `content` CSS property is the only one in the AI's
164
+ // typical output that consistently arrives malformed — the
165
+ // sanitiser repairs or empty-strings it so the surrounding
166
+ // rule stays parseable.
167
+ if (key === 'content' && typeof value === 'string') {
168
+ acc[key] = sanitizeContentValue(value)
169
+ } else {
170
+ acc[key] = value
171
+ }
172
+ }
173
+ } else {
174
+ acc[key] = value
175
+ }
20
176
  return acc
21
177
  }, {})
22
178
  }
@@ -0,0 +1,122 @@
1
+ import * as types from '@babel/types'
2
+ import type { UIDLSearchParamsDefinition } from '@teleporthq/teleport-types'
3
+
4
+ /**
5
+ * Builds the Next.js-style prelude nodes for a page that declares URL search
6
+ * params. Emits:
7
+ *
8
+ * const router = useRouter()
9
+ *
10
+ * The returned `registry` carries per-key defaults so `createDynamicValueExpression`
11
+ * can fall back to the declared default via `router?.query?.<key> ?? '<default>'`.
12
+ */
13
+ export const buildNextJsUrlSearchParamsPrelude = (
14
+ searchParams: UIDLSearchParamsDefinition | undefined
15
+ ): {
16
+ statements: types.Statement[]
17
+ registry: Record<string, { defaultValue?: string }>
18
+ prefixMap: Record<string, string>
19
+ } => {
20
+ if (!searchParams || searchParams.length === 0) {
21
+ return { statements: [], registry: {}, prefixMap: {} }
22
+ }
23
+ const registry: Record<string, { defaultValue?: string }> = {}
24
+ for (const entry of searchParams) {
25
+ registry[entry.key] = {
26
+ defaultValue: entry.defaultValue,
27
+ }
28
+ }
29
+ // const router = useRouter()
30
+ const routerDecl = types.variableDeclaration('const', [
31
+ types.variableDeclarator(
32
+ types.identifier('router'),
33
+ types.callExpression(types.identifier('useRouter'), [])
34
+ ),
35
+ ])
36
+ return {
37
+ statements: [routerDecl],
38
+ registry,
39
+ prefixMap: { urlSearchParams: 'router.query' },
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Builds the static-export prelude for a page that declares URL search params.
45
+ * Emits:
46
+ *
47
+ * const __urlSearchParams =
48
+ * typeof window !== 'undefined'
49
+ * ? Object.fromEntries(new URLSearchParams(window.location.search).entries())
50
+ * : {}
51
+ * useEffect(() => {
52
+ * if (typeof window === 'undefined') return
53
+ * const handler = () =>
54
+ * setUrlSearchParams(
55
+ * Object.fromEntries(new URLSearchParams(window.location.search).entries())
56
+ * )
57
+ * window.addEventListener('popstate', handler)
58
+ * return () => window.removeEventListener('popstate', handler)
59
+ * }, [])
60
+ *
61
+ * For simplicity the snippet stays at the "read on mount" level — the listing
62
+ * renders with the current query params. Implementations that want live
63
+ * re-filtering on popstate can subscribe via the returned hook shape.
64
+ */
65
+ export const buildStaticUrlSearchParamsPrelude = (
66
+ searchParams: UIDLSearchParamsDefinition | undefined
67
+ ): {
68
+ statements: types.Statement[]
69
+ registry: Record<string, { defaultValue?: string }>
70
+ prefixMap: Record<string, string>
71
+ } => {
72
+ if (!searchParams || searchParams.length === 0) {
73
+ return { statements: [], registry: {}, prefixMap: {} }
74
+ }
75
+ const registry: Record<string, { defaultValue?: string }> = {}
76
+ for (const entry of searchParams) {
77
+ registry[entry.key] = {
78
+ defaultValue: entry.defaultValue,
79
+ }
80
+ }
81
+ // const __urlSearchParams = typeof window !== 'undefined'
82
+ // ? Object.fromEntries(new URLSearchParams(window.location.search).entries())
83
+ // : {}
84
+ const decl = types.variableDeclaration('const', [
85
+ types.variableDeclarator(
86
+ types.identifier('__urlSearchParams'),
87
+ types.conditionalExpression(
88
+ types.binaryExpression(
89
+ '!==',
90
+ types.unaryExpression('typeof', types.identifier('window')),
91
+ types.stringLiteral('undefined')
92
+ ),
93
+ types.callExpression(
94
+ types.memberExpression(types.identifier('Object'), types.identifier('fromEntries')),
95
+ [
96
+ types.callExpression(
97
+ types.memberExpression(
98
+ types.newExpression(types.identifier('URLSearchParams'), [
99
+ types.memberExpression(
100
+ types.memberExpression(
101
+ types.identifier('window'),
102
+ types.identifier('location')
103
+ ),
104
+ types.identifier('search')
105
+ ),
106
+ ]),
107
+ types.identifier('entries')
108
+ ),
109
+ []
110
+ ),
111
+ ]
112
+ ),
113
+ types.objectExpression([])
114
+ )
115
+ ),
116
+ ])
117
+ return {
118
+ statements: [decl],
119
+ registry,
120
+ prefixMap: { urlSearchParams: '__urlSearchParams' },
121
+ }
122
+ }