unframer 2.6.6 → 2.7.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/esm/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function terminalMarkdown(markdown: string): string | Promise<string>;
1
2
  export declare const logger: {
2
3
  log(...args: any[]): void;
3
4
  green(...args: any[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM;;;;CAUlB,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAOA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,4BAEhD;AAGD,eAAO,MAAM,MAAM;;;;CAUlB,CAAA"}
package/esm/utils.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import pico from 'picocolors';
2
+ import { marked } from 'marked';
3
+ import { markedTerminal } from 'marked-terminal';
4
+ marked.use(markedTerminal());
5
+ export function terminalMarkdown(markdown) {
6
+ return marked(markdown);
7
+ }
2
8
  const prefix = '[unframer]';
3
9
  export const logger = {
4
10
  log(...args) {
package/esm/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,YAAY,CAAA;AAE7B,MAAM,MAAM,GAAG,YAAY,CAAA;AAC3B,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,GAAG,CAAC,GAAG,IAAI;QACP,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;IAChC,CAAC;IACD,KAAK,CAAC,GAAG,IAAI;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;IACD,KAAK,CAAC,GAAG,IAAI;QACT,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;CACJ,CAAA"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,YAAY,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAA;AAE5B,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAA;AAC3B,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,CAAA;AAC3B,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,GAAG,CAAC,GAAG,IAAI;QACP,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;IAChC,CAAC;IACD,KAAK,CAAC,GAAG,IAAI;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;IACD,KAAK,CAAC,GAAG,IAAI;QACT,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IACtE,CAAC;CACJ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unframer",
3
- "version": "2.6.6",
3
+ "version": "2.7.0",
4
4
  "description": "Import Framer components directly in your React app, type safe and customizable",
5
5
  "sideEffects": false,
6
6
  "repository": "https://github.com/remorses/unframer",
@@ -44,12 +44,14 @@
44
44
  "chokidar": "^3.6.0",
45
45
  "dedent": "^1.5.3",
46
46
  "dprint-node": "^1.0.8",
47
- "esbuild": "^0.21.5",
47
+ "esbuild": "^0.24.0",
48
48
  "esbuild-plugin-polyfill-node": "^0.3.0",
49
49
  "eventemitter3": "^3.1.2",
50
50
  "find-up": "^5.0.0",
51
51
  "fs-extra": "^11.2.0",
52
52
  "json5": "^2.2.3",
53
+ "marked": "^15.0.3",
54
+ "marked-terminal": "^7.2.1",
53
55
  "native-fetch": "^4.0.2",
54
56
  "picocolors": "^1.0.1",
55
57
  "real-framer-motion": "npm:framer-motion@11.2.10",
@@ -69,12 +71,12 @@
69
71
  "@types/fs-extra": "^11.0.4",
70
72
  "@types/node": "^22.7.4",
71
73
  "@types/react": "^18.3.12",
72
- "@xmorse/deployment-utils": "^0.2.17",
74
+ "@xmorse/deployment-utils": "^0.2.18",
73
75
  "concurrently": "^8.2.2",
74
76
  "openai": "^4.52.7",
75
77
  "posthtml": "^0.16.6",
76
- "react": "19.0.0-rc-e4953922-20240919",
77
- "react-dom": "19.0.0-rc-e4953922-20240919",
78
+ "react": "19.0.0",
79
+ "react-dom": "19.0.0",
78
80
  "tiktoken": "^1.0.15",
79
81
  "typescript": "^5.6.2"
80
82
  },
package/src/cli.tsx CHANGED
@@ -1,85 +1,90 @@
1
- import { bundle } from './exporter.js'
1
+ import { setMaxListeners } from 'events'
2
2
  import JSON from 'json5'
3
- import events, { EventEmitter, setMaxListeners } from 'events'
3
+ import { bundle, StyleToken } from './exporter.js'
4
4
 
5
- import chokidar from 'chokidar'
6
- import fs from 'fs-extra'
5
+ import { cac } from 'cac'
7
6
  import findUp from 'find-up'
8
- import tmp from 'tmp'
7
+ import fs from 'fs-extra'
9
8
  import path, { basename } from 'path'
10
- const configNames = ['unframer.config.json', 'unframer.json']
11
- import { cac } from 'cac'
12
- import { logger } from './utils.js'
13
9
  import { BreakpointSizes } from './css.js'
10
+ import { logger } from './utils.js'
11
+ const configNames = ['unframer.config.json', 'unframer.json']
14
12
 
15
13
  export const cli = cac('unframer')
16
14
 
17
- cli.command('', 'Run unframer')
18
- .option('--watch', 'Watch for Framer and unframer.config.json changes')
19
- .action(async function main(options) {
15
+ let defaultOutDir = 'framer'
16
+
17
+ function nameToFolder(name: string) {
18
+ return name
19
+ .replace(/[^a-zA-Z0-9]/g, '-') // Replace non-alphanumeric with dash
20
+ .replace(/-+/g, '-') // Replace multiple dashes with single dash
21
+ .replace(/^-|-$/g, '') // Remove leading/trailing dashes
22
+ .toLowerCase()
23
+ }
24
+
25
+ cli.command('[projectId]', 'Run unframer with optional project ID')
26
+ .option('--outDir <dir>', 'Output directory', { default: defaultOutDir })
27
+ .action(async function main(projectId, options) {
28
+ const outDir = options.outDir
29
+ if (projectId) {
30
+ logger.log(`Fetching config for project ${projectId}`)
31
+ const response = await fetch(
32
+ new URL(
33
+ `/api/plugins/reactExportPlugin/project/${projectId}`,
34
+ process.env.UNFRAMER_SERVER_URL || 'https://unframer.co',
35
+ ).toString(),
36
+ )
37
+ if (!response.ok) {
38
+ console.error(`Failed to fetch Framer config`)
39
+ logger.error('Response: ' + (await response.text()))
40
+ return
41
+ }
42
+ const data = await response.json()
43
+ return processConfig({
44
+ config: {
45
+ outDir,
46
+ components: Object.fromEntries(
47
+ data.components.map((c) => [
48
+ nameToFolder(c.name),
49
+ c.url,
50
+ ]),
51
+ ),
52
+ tokens: data.colorStyles,
53
+ },
54
+ watch: false,
55
+
56
+ configBasename: 'remote config',
57
+ signal: new AbortController().signal,
58
+ })
59
+ }
60
+
20
61
  fixOldUnframerPath()
21
62
  const cwd = process.cwd()
22
- const watch = process.argv.includes('--watch')
23
63
  logger.log(`Looking for ${configNames.join(', ')} in ${cwd}`)
24
64
  const configPath = await findUp(configNames, { cwd })
25
65
  if (!configPath) {
26
66
  logger.log(`No ${configNames.join(', ')} found`)
27
67
  return
28
68
  }
29
- let configBasename = basename(configPath!)
69
+ const configBasename = basename(configPath!)
30
70
  const configContent = fs.readFileSync(configPath, 'utf8')
31
71
  if (!configContent) {
32
72
  logger.log(`No ${configBasename} contents found`)
33
73
  return
34
74
  }
35
- let config = JSON.parse(configContent)
75
+ const config = JSON.parse(configContent)
76
+ if (outDir !== defaultOutDir) {
77
+ config.outDir = outDir
78
+ }
36
79
 
37
80
  let controller = new AbortController()
38
81
  setMaxListeners(0, controller.signal)
39
82
  processConfig({
40
83
  config,
41
- watch,
84
+ watch: false,
42
85
  signal: controller.signal,
43
86
  configBasename,
44
87
  })
45
- if (!watch) {
46
- return
47
- }
48
-
49
- const watcher = chokidar.watch(configPath!, {
50
- persistent: true,
51
- })
52
-
53
- watcher.on('change', async (path) => {
54
- logger.log(`${configBasename} changed`)
55
- console.log()
56
- controller.abort()
57
-
58
- controller = new AbortController()
59
- setMaxListeners(0, controller.signal)
60
-
61
- const newConfig = safeJsonParse(
62
- fs.readFileSync(configPath!, 'utf8'),
63
- )
64
- if (!newConfig) {
65
- logger.log(`Invalid ${configBasename} file`)
66
- return
67
- }
68
- const newNames = getNewNames(config, newConfig)
69
- if (newNames.length) {
70
- logger.log(`New components found: ${newNames.join(', ')}`)
71
- await processConfig({
72
- config: {
73
- ...newConfig,
74
- components: pluck(newConfig.components, newNames),
75
- },
76
- watch,
77
- configBasename,
78
- // signal: controller.signal,
79
- })
80
- }
81
- config = newConfig
82
- })
83
88
  })
84
89
 
85
90
  const defaultConfig = `{
@@ -105,6 +110,9 @@ function fixOldUnframerPath() {
105
110
  }
106
111
  return false
107
112
  }
113
+ const version = require('../package.json').version
114
+
115
+ cli.version(version).help()
108
116
 
109
117
  cli.command('init', 'Init the unframer.config.json config').action(
110
118
  async (options) => {
@@ -118,10 +126,6 @@ cli.command('init', 'Init the unframer.config.json config').action(
118
126
  },
119
127
  )
120
128
 
121
- const version = require('../package.json').version
122
-
123
- cli.version(version).help()
124
-
125
129
  function safeJsonParse(json: string) {
126
130
  try {
127
131
  return JSON.parse(json)
@@ -155,6 +159,7 @@ type Config = {
155
159
  [name: string]: string
156
160
  }
157
161
  breakpoints?: BreakpointSizes
162
+ tokens?: StyleToken[]
158
163
  outDir?: string
159
164
  }
160
165
  async function processConfig({
@@ -181,6 +186,7 @@ async function processConfig({
181
186
  breakpoints,
182
187
  cwd: installDir,
183
188
  watch,
189
+ tokens: config.tokens,
184
190
  signal,
185
191
  })
186
192
  } catch (e: any) {
package/src/css.ts CHANGED
@@ -92,7 +92,7 @@ export function getFontsStyles(_fontsDefs: ComponentFontBundle[]) {
92
92
  str += dedent`
93
93
  @font-face {
94
94
  font-family: '${x.family}';
95
- src: url(${x.url});\n`
95
+ src: url('${x.url}');\n`
96
96
  if (x.style) {
97
97
  str += ` font-style: ${x.style};\n`
98
98
  }
package/src/exporter.ts CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  logFontsUsage,
28
28
  } from './css.js'
29
29
  import dedent from 'dedent'
30
- import { logger } from './utils.js'
30
+ import { logger, terminalMarkdown } from './utils.js'
31
31
  import {
32
32
  esbuildPluginBundleDependencies,
33
33
  resolveRedirect,
@@ -42,10 +42,18 @@ function validateUrl(url: string) {
42
42
  }
43
43
  }
44
44
 
45
+ export type StyleToken = {
46
+ id: string
47
+ name?: string
48
+ lightColor: string
49
+ darkColor: string
50
+ }
51
+
45
52
  export async function bundle({
46
53
  cwd: out = '',
47
54
  watch = false,
48
55
  components = {} as Record<string, string>,
56
+ tokens = [] as StyleToken[],
49
57
  breakpoints = {} as BreakpointSizes,
50
58
  signal = undefined as AbortSignal | undefined,
51
59
  }) {
@@ -88,7 +96,7 @@ export async function bundle({
88
96
  esbuildPluginBundleDependencies({
89
97
  signal,
90
98
  }),
91
- polyfillNode({}),
99
+ polyfillNode({}) as any,
92
100
  {
93
101
  name: 'virtual loader',
94
102
  setup(build) {
@@ -143,6 +151,7 @@ export async function bundle({
143
151
  let codeNew =
144
152
  `// @ts-nocheck\n` +
145
153
  `/* eslint-disable */\n` +
154
+ '/* This file was generated by Unframer, do not edit manually */\n' +
146
155
  dprint.format(resultPathAbs, file.text, {
147
156
  lineWidth: 140,
148
157
  quoteStyle: 'alwaysSingle',
@@ -176,9 +185,12 @@ export async function bundle({
176
185
  JSON.stringify({ type: 'module' }),
177
186
  'utf-8',
178
187
  )
188
+ if (!result?.outputFiles) {
189
+ throw new Error('Failed to generate result')
190
+ }
179
191
 
180
192
  const propControlsData = await Promise.all(
181
- result.outputFiles.map(async (file) => {
193
+ result?.outputFiles.map(async (file) => {
182
194
  try {
183
195
  await sema.acquire()
184
196
  const name = path.basename(file.path).replace(/\.js$/, '')
@@ -217,14 +229,16 @@ export async function bundle({
217
229
  ).finally(() => fs.rmSync(packageJson))
218
230
 
219
231
  const cssString =
232
+ '/* This file was generated by Unframer, do not edit manually */\n' +
220
233
  '/* This css file has all the necessary styles to run all your components */\n' +
234
+ '\n' +
235
+ getStyleTokensCss(tokens) +
221
236
  breakpointsStyles(breakpoints) +
222
237
  '\n\n' +
223
238
  combinedCSSRules
224
239
  .map((x) => (x?.startsWith(' ') ? dedent(x) : x))
225
240
  .join('\n') +
226
241
  getFontsStyles(allFonts)
227
-
228
242
  fs.writeFileSync(path.resolve(out, 'styles.css'), cssString, 'utf-8')
229
243
 
230
244
  logFontsUsage(allFonts)
@@ -263,12 +277,17 @@ export async function bundle({
263
277
  if (watch) {
264
278
  logger.log('waiting for components or config changes')
265
279
  }
266
-
267
- const tokensCss =
268
- "/* This css file contains your color variables, sometimes these get desynced when updated in Framer so it's good that you copy and paste this snippet into your app css */\n" +
269
- '/* Bug: https://www.framer.community/c/bugs/color-style-unlinks-when-copying-component-between-projects-resulting-in-potential-value-discrepancy */\n' +
270
- getTokensCss({ out, result })
271
- fs.writeFileSync(path.resolve(out, 'tokens.css'), tokensCss, 'utf-8')
280
+ if (!tokens?.length) {
281
+ const tokensCss =
282
+ "/* This css file contains your color variables, sometimes these get desynced when updated in Framer so it's good that you copy and paste this snippet into your app css */\n" +
283
+ '/* Bug: https://www.framer.community/c/bugs/color-style-unlinks-when-copying-component-between-projects-resulting-in-potential-value-discrepancy */\n' +
284
+ getTokensCss({ out, result })
285
+ fs.writeFileSync(
286
+ path.resolve(out, 'tokens.css'),
287
+ tokensCss,
288
+ 'utf-8',
289
+ )
290
+ }
272
291
  const res = {
273
292
  components: Object.entries(components).map(([name, v]) => {
274
293
  const propControls = propControlsData.find(
@@ -290,54 +309,158 @@ export async function bundle({
290
309
  if (!watch) {
291
310
  const result = await rebuild()
292
311
  await buildContext.dispose()
312
+ console.log(
313
+ terminalMarkdown(dedent`
314
+ # How to use the Framer components
315
+
316
+ The components are exported to the \`framer\` directory (or the directory you specified in the config).
317
+ Each component has a \`.Responsive\` variant that allows you to specify different variants for different breakpoints.
318
+ The breakpoints are:
319
+ - base: 0-319px
320
+ - sm: 320-767px
321
+ - md: 768-959px
322
+ - lg: 960-1199px
323
+ - xl: 1200-1535px
324
+ - 2xl: 1536px+
325
+
326
+ You can import the components like this:
327
+
328
+ \`\`\`tsx
329
+ import './framer/styles.css'
330
+ import Logos from './framer/logos'
331
+
332
+ export default function App() {
333
+ return (
334
+ <div>
335
+ <Logos.Responsive
336
+ variants={{
337
+ lg: 'Desktop',
338
+ md: 'Tablet',
339
+ base: 'Mobile',
340
+ }}
341
+ />
342
+ </div>
343
+ );
344
+ };
345
+ \`\`\`
346
+
347
+ It's very important to import the \`styles.css\` file to include the necessary styles for the components.
348
+
349
+ You can also use the components without the responsive wrapper:
350
+
351
+ \`\`\`tsx
352
+ import './framer/styles.css'
353
+ import Logos from './framer/logos'
354
+
355
+ export default function App() {
356
+ return (
357
+ <div>
358
+ <Logos variant="Desktop" />
359
+ </div>
360
+ );
361
+ };
362
+ \`\`\`
363
+ `),
364
+ )
293
365
  return result
294
366
  }
295
367
 
296
- // when user press ctrl+c dispose
297
- process.on('SIGINT', async () => {
298
- await buildContext.cancel()
299
- buildContext.dispose()
300
- })
301
- process.on('SIGABRT', async () => {
302
- await buildContext.cancel()
303
- buildContext.dispose()
304
- })
305
- signal?.addEventListener('abort', async () => {
306
- await buildContext.cancel()
307
- buildContext.dispose()
308
- })
368
+ // // when user press ctrl+c dispose
369
+ // process.on('SIGINT', async () => {
370
+ // await buildContext.cancel()
371
+ // buildContext.dispose()
372
+ // })
373
+ // process.on('SIGABRT', async () => {
374
+ // await buildContext.cancel()
375
+ // buildContext.dispose()
376
+ // })
377
+ // signal?.addEventListener('abort', async () => {
378
+ // await buildContext.cancel()
379
+ // buildContext.dispose()
380
+ // })
381
+
382
+ // const res = await rebuild()
383
+
384
+ // /**
385
+ // * Get resolved URLs for all components and also wait for 1 second if it took less time than that
386
+ // */
387
+ // const getResolvedUrls = () =>
388
+ // Promise.all([
389
+ // ...Object.values(components).map((u) => {
390
+ // const url = new URL(u)
391
+ // url.searchParams.set('ts', Date.now().toString())
392
+ // return resolveRedirect({ url: url.toString(), signal })
393
+ // }),
394
+ // new Promise((res) => setTimeout(res, 5000)),
395
+ // ])
396
+ // let prevUrls = await getResolvedUrls()
397
+ // while (!signal?.aborted) {
398
+ // const urls = await getResolvedUrls()
399
+ // const changed = urls
400
+ // .map((x, i) => (x !== prevUrls[i] ? i : null))
401
+ // .filter(Boolean)
402
+ // if (!changed?.length) {
403
+ // continue
404
+ // }
405
+ // const changedNames = Object.keys(components).filter((_, i) =>
406
+ // changed.includes(i),
407
+ // )
408
+ // logger.log(`found new component URLs for ${changedNames.join(', ')}`)
409
+ // prevUrls = urls
410
+ // await rebuild()
411
+ // }
412
+ // return res
413
+ }
309
414
 
310
- const res = await rebuild()
311
-
312
- /**
313
- * Get resolved URLs for all components and also wait for 1 second if it took less time than that
314
- */
315
- const getResolvedUrls = () =>
316
- Promise.all([
317
- ...Object.values(components).map((u) => {
318
- const url = new URL(u)
319
- url.searchParams.set('ts', Date.now().toString())
320
- return resolveRedirect({ url: url.toString(), signal })
321
- }),
322
- new Promise((res) => setTimeout(res, 5000)),
323
- ])
324
- let prevUrls = await getResolvedUrls()
325
- while (!signal?.aborted) {
326
- const urls = await getResolvedUrls()
327
- const changed = urls
328
- .map((x, i) => (x !== prevUrls[i] ? i : null))
329
- .filter(Boolean)
330
- if (!changed?.length) {
331
- continue
332
- }
333
- const changedNames = Object.keys(components).filter((_, i) =>
334
- changed.includes(i),
415
+ export function getDarkModeSelector(opts: {
416
+ darkModeType?: 'class' | 'media'
417
+ content: string
418
+ }) {
419
+ const { darkModeType = 'class', content } = opts
420
+ if (darkModeType === 'media') {
421
+ return (
422
+ '@media (prefers-color-scheme: dark) {\n' +
423
+ ' :root {\n' +
424
+ content +
425
+ '\n' +
426
+ ' }\n' +
427
+ '}'
335
428
  )
336
- logger.log(`found new component URLs for ${changedNames.join(', ')}`)
337
- prevUrls = urls
338
- await rebuild()
339
429
  }
340
- return res
430
+ return '.dark:root {\n' + content + '\n' + '}'
431
+ }
432
+
433
+ export function getStyleTokensCss(
434
+ tokens: StyleToken[],
435
+ darkModeType: 'class' | 'media' = 'class',
436
+ ) {
437
+ if (!tokens?.length) {
438
+ return ''
439
+ }
440
+
441
+ const lightTokens = tokens
442
+ .map(
443
+ (token) =>
444
+ ' --token-' + token.id + ': ' + token.lightColor + ';',
445
+ )
446
+ .join('\n')
447
+
448
+ const darkTokens = tokens
449
+ .map(
450
+ (token) => ' --token-' + token.id + ': ' + token.darkColor + ';',
451
+ )
452
+ .join('\n')
453
+
454
+ return (
455
+ ':root {\n' +
456
+ lightTokens +
457
+ '\n' +
458
+ '}\n\n' +
459
+ getDarkModeSelector({
460
+ darkModeType,
461
+ content: darkTokens,
462
+ })
463
+ )
341
464
  }
342
465
 
343
466
  function decapitalize(str: string) {
@@ -615,6 +738,7 @@ export function propControlsToType(controls: PropertyControls, fileName) {
615
738
  .map((line) => ` ${line}`)
616
739
  .join('\n') + '\n'
617
740
  let t = ''
741
+ t += '/* This file was generated by Unframer, do not edit manually */\n'
618
742
  t += 'import * as React from "react"\n\n'
619
743
  t += 'import { UnframerBreakpoint } from "unframer"\n\n'
620
744
  t += `export interface Props {\n${defaultPropsTypes}${types}\n}\n\n`
@@ -669,17 +793,17 @@ export function parsePropertyControls(code: string) {
669
793
  return propControls.slice(realStart + 1, -1)
670
794
  }
671
795
 
672
- type TokenInfo = {
796
+ type ExtractedTokenInfo = {
673
797
  tokenName: string
674
798
  metadata?: Record<string, any>
675
799
 
676
800
  defaultValue: string
677
801
  }
678
802
 
679
- export function extractTokenInfo(code: string): TokenInfo[] {
803
+ export function extractTokenInfo(code: string): ExtractedTokenInfo[] {
680
804
  const lines = code.split('\n')
681
805
  const tokenLines = lines.filter((line) => line.includes('var(--token'))
682
- const tokens: TokenInfo[] = []
806
+ const tokens: ExtractedTokenInfo[] = []
683
807
 
684
808
  for (const line of tokenLines) {
685
809
  let startIndex = 0