@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.
- package/__tests__/node-handlers/node-to-jsx/index.ts +164 -0
- package/__tests__/node-handlers/node-to-jsx/url-search-params-expression.ts +59 -0
- package/__tests__/node-handlers/node-to-jsx/utils.ts +217 -0
- package/__tests__/utils/ast-utils.ts +135 -0
- package/__tests__/utils/content-property-sanitiser.ts +156 -0
- package/__tests__/utils/url-search-params.ts +48 -0
- package/dist/cjs/builders/ast-builders.d.ts +1 -0
- package/dist/cjs/builders/ast-builders.d.ts.map +1 -1
- package/dist/cjs/builders/ast-builders.js +9 -1
- package/dist/cjs/builders/ast-builders.js.map +1 -1
- package/dist/cjs/builders/style-builders.d.ts +7 -0
- package/dist/cjs/builders/style-builders.d.ts.map +1 -1
- package/dist/cjs/builders/style-builders.js +35 -1
- package/dist/cjs/builders/style-builders.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/index.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/index.js +19 -3
- package/dist/cjs/node-handlers/node-to-html/index.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/utils.d.ts +2 -1
- package/dist/cjs/node-handlers/node-to-html/utils.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/utils.js +94 -11
- package/dist/cjs/node-handlers/node-to-html/utils.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/index.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/index.js +701 -42
- package/dist/cjs/node-handlers/node-to-jsx/index.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/types.d.ts +40 -2
- package/dist/cjs/node-handlers/node-to-jsx/types.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts +50 -5
- package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/utils.js +492 -35
- package/dist/cjs/node-handlers/node-to-jsx/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/ast-utils.d.ts +70 -4
- package/dist/cjs/utils/ast-utils.d.ts.map +1 -1
- package/dist/cjs/utils/ast-utils.js +414 -28
- package/dist/cjs/utils/ast-utils.js.map +1 -1
- package/dist/cjs/utils/style-utils.d.ts +1 -0
- package/dist/cjs/utils/style-utils.d.ts.map +1 -1
- package/dist/cjs/utils/style-utils.js +155 -2
- package/dist/cjs/utils/style-utils.js.map +1 -1
- package/dist/cjs/utils/url-search-params.d.ts +48 -0
- package/dist/cjs/utils/url-search-params.d.ts.map +1 -0
- package/dist/cjs/utils/url-search-params.js +109 -0
- package/dist/cjs/utils/url-search-params.js.map +1 -0
- package/dist/esm/builders/ast-builders.d.ts +1 -0
- package/dist/esm/builders/ast-builders.d.ts.map +1 -1
- package/dist/esm/builders/ast-builders.js +7 -0
- package/dist/esm/builders/ast-builders.js.map +1 -1
- package/dist/esm/builders/style-builders.d.ts +7 -0
- package/dist/esm/builders/style-builders.d.ts.map +1 -1
- package/dist/esm/builders/style-builders.js +33 -0
- package/dist/esm/builders/style-builders.js.map +1 -1
- package/dist/esm/node-handlers/node-to-html/index.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/index.js +20 -4
- package/dist/esm/node-handlers/node-to-html/index.js.map +1 -1
- package/dist/esm/node-handlers/node-to-html/utils.d.ts +2 -1
- package/dist/esm/node-handlers/node-to-html/utils.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/utils.js +93 -11
- package/dist/esm/node-handlers/node-to-html/utils.js.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/index.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/index.js +703 -44
- package/dist/esm/node-handlers/node-to-jsx/index.js.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/types.d.ts +40 -2
- package/dist/esm/node-handlers/node-to-jsx/types.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/utils.d.ts +50 -5
- package/dist/esm/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/utils.js +486 -33
- package/dist/esm/node-handlers/node-to-jsx/utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/ast-utils.d.ts +70 -4
- package/dist/esm/utils/ast-utils.d.ts.map +1 -1
- package/dist/esm/utils/ast-utils.js +403 -27
- package/dist/esm/utils/ast-utils.js.map +1 -1
- package/dist/esm/utils/style-utils.d.ts +1 -0
- package/dist/esm/utils/style-utils.d.ts.map +1 -1
- package/dist/esm/utils/style-utils.js +153 -1
- package/dist/esm/utils/style-utils.js.map +1 -1
- package/dist/esm/utils/url-search-params.d.ts +48 -0
- package/dist/esm/utils/url-search-params.d.ts.map +1 -0
- package/dist/esm/utils/url-search-params.js +81 -0
- package/dist/esm/utils/url-search-params.js.map +1 -0
- package/package.json +4 -4
- package/src/builders/ast-builders.ts +26 -0
- package/src/builders/style-builders.ts +64 -0
- package/src/node-handlers/node-to-html/index.ts +34 -4
- package/src/node-handlers/node-to-html/utils.ts +111 -11
- package/src/node-handlers/node-to-jsx/index.ts +999 -52
- package/src/node-handlers/node-to-jsx/types.ts +48 -3
- package/src/node-handlers/node-to-jsx/utils.ts +655 -31
- package/src/utils/ast-utils.ts +547 -28
- package/src/utils/style-utils.ts +157 -1
- package/src/utils/url-search-params.ts +122 -0
package/src/utils/style-utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|