unframer 4.1.9 → 4.2.1
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 +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +238 -15
- package/dist/cli.js.map +1 -1
- package/dist/css.d.ts +1 -1
- package/dist/css.d.ts.map +1 -1
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +29 -5
- package/dist/exporter.js.map +1 -1
- package/dist/framer-chunks/chunk-IKQSD2QC.js +10 -0
- package/dist/framer-chunks/fontshare-PZLWRK4B-MHMZIGTX.js +7 -0
- package/dist/framer-chunks/fontshare-SXU5BGFE-OWTMMPGS.js +7 -0
- package/dist/framer-chunks/fontshare-TIA7QUPT-PUDLUTQ7.js +7 -0
- package/dist/framer-chunks/framer-font-RD2SUPQH-Q4MS7WS6.js +7 -0
- package/dist/framer-chunks/google-EGNT223R-P4DUHBW2.js +7 -0
- package/dist/framer-chunks/google-H6SFY4F5-5JSJCGDR.js +7 -0
- package/dist/framer-chunks/google-YSYBFRE6-L7YAHH7V.js +7 -0
- package/dist/framer.d.ts +128 -0
- package/dist/framer.js +15926 -11214
- package/dist/plugin-mcp-dist/lib/framer-client.server.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/framer-client.server.js +9 -0
- package/dist/plugin-mcp-dist/lib/framer-client.server.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/framer.d.ts +8 -1
- package/dist/plugin-mcp-dist/lib/framer.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/framer.js +117 -21
- package/dist/plugin-mcp-dist/lib/framer.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/framer.test.js +79 -65
- package/dist/plugin-mcp-dist/lib/framer.test.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/mcp-handlers.d.ts +12 -12
- package/dist/plugin-mcp-dist/lib/mcp-handlers.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/mcp-handlers.js +247 -54
- package/dist/plugin-mcp-dist/lib/mcp-handlers.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/plugin-websocket.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/plugin-websocket.js +36 -25
- package/dist/plugin-mcp-dist/lib/plugin-websocket.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/react-export.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/react-export.js +79 -61
- package/dist/plugin-mcp-dist/lib/react-export.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/schema.d.ts +8 -0
- package/dist/plugin-mcp-dist/lib/schema.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/schema.js +18 -1
- package/dist/plugin-mcp-dist/lib/schema.js.map +1 -1
- package/dist/plugin-mcp-dist/lib/store.d.ts +1 -1
- package/dist/plugin-mcp-dist/lib/store.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/utils.d.ts +1 -1
- package/dist/plugin-mcp-dist/lib/utils.d.ts.map +1 -1
- package/dist/plugin-mcp-dist/lib/utils.js +11 -7
- package/dist/plugin-mcp-dist/lib/utils.js.map +1 -1
- package/dist/react.d.ts +3 -3
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +0 -2
- package/dist/react.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/src/cli.ts +265 -14
- package/src/exporter.ts +39 -6
- package/src/framer.d.ts +128 -0
- package/src/framer.js +15926 -11214
- package/src/plugin-mcp-dist/lib/framer-client.server.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/framer-client.server.js +9 -0
- package/src/plugin-mcp-dist/lib/framer-client.server.js.map +1 -1
- package/src/plugin-mcp-dist/lib/framer.d.ts +10 -1
- package/src/plugin-mcp-dist/lib/framer.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/framer.js +117 -21
- package/src/plugin-mcp-dist/lib/framer.js.map +1 -1
- package/src/plugin-mcp-dist/lib/framer.test.js +79 -65
- package/src/plugin-mcp-dist/lib/framer.test.js.map +1 -1
- package/src/plugin-mcp-dist/lib/mcp-handlers.d.ts +4 -4
- package/src/plugin-mcp-dist/lib/mcp-handlers.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/mcp-handlers.js +247 -54
- package/src/plugin-mcp-dist/lib/mcp-handlers.js.map +1 -1
- package/src/plugin-mcp-dist/lib/plugin-websocket.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/plugin-websocket.js +36 -25
- package/src/plugin-mcp-dist/lib/plugin-websocket.js.map +1 -1
- package/src/plugin-mcp-dist/lib/react-export.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/react-export.js +79 -61
- package/src/plugin-mcp-dist/lib/react-export.js.map +1 -1
- package/src/plugin-mcp-dist/lib/schema.d.ts +8 -0
- package/src/plugin-mcp-dist/lib/schema.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/schema.js +18 -1
- package/src/plugin-mcp-dist/lib/schema.js.map +1 -1
- package/src/plugin-mcp-dist/lib/utils.d.ts.map +1 -1
- package/src/plugin-mcp-dist/lib/utils.js +11 -7
- package/src/plugin-mcp-dist/lib/utils.js.map +1 -1
- package/src/react.tsx +0 -3
- package/src/styles/framer.css +36 -120
- package/src/version.ts +1 -1
- package/dist/framer-chunks/SqliteDatabase-VAKIICSG-W43ZSXBO.js +0 -97
- package/dist/framer-chunks/chunk-WYG6DFEF.js +0 -121
- package/dist/framer-chunks/default-blog-sqlite-7ZHEY3GT-DXFOTMBP.js +0 -9
- package/dist/framer-chunks/fontshare-4THNDPMZ-L3NZDIOE.js +0 -27099
- package/dist/framer-chunks/fontshare-B2QLD7YB-ZLNQ44LW.js +0 -119
- package/dist/framer-chunks/fontshare-O22OBJ3D-VY7WF3BB.js +0 -825
- package/dist/framer-chunks/framer-font-45AI7UCZ-Z3XHDH5K.js +0 -7
- package/dist/framer-chunks/google-3FCAKCAC-K2ZVMKHN.js +0 -623876
- package/dist/framer-chunks/google-3SZHWBC6-MIC5SCB4.js +0 -1627
- package/dist/framer-chunks/google-GXDJLGJB-356NWSZ7.js +0 -6885
- package/dist/framer-chunks/sqlite-wasm-FGP37EAY-MBPG3MPB.js +0 -11479
- package/dist/framer-chunks/sqlite3-SISQ6ENZ-RRHGROT5.js +0 -9
package/src/cli.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
componentNameToPath,
|
|
22
22
|
dedent,
|
|
23
23
|
isTruthy,
|
|
24
|
+
kebabCase,
|
|
24
25
|
logger,
|
|
25
26
|
sleep,
|
|
26
27
|
spinner,
|
|
@@ -42,7 +43,7 @@ export const cli = goke('unframer')
|
|
|
42
43
|
|
|
43
44
|
let defaultOutDir = 'framer'
|
|
44
45
|
|
|
45
|
-
cli.command('[
|
|
46
|
+
cli.command('[...projectIds]', 'Run unframer with one or more project IDs')
|
|
46
47
|
.option('--outDir <dir>', 'Output directory')
|
|
47
48
|
.option(
|
|
48
49
|
'--external [package]',
|
|
@@ -52,7 +53,7 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
|
|
|
52
53
|
.option('--jsx', 'Output jsx code instead of minified .js code')
|
|
53
54
|
.option('--debug', 'Enable debug logging')
|
|
54
55
|
.option('--metafile', 'Generate meta.json file with build metadata')
|
|
55
|
-
.action(async function main(
|
|
56
|
+
.action(async function main(projectIds, options) {
|
|
56
57
|
const outDir = options.outDir || defaultOutDir
|
|
57
58
|
const jsx = options.jsx ?? true
|
|
58
59
|
const external_ = options.external
|
|
@@ -68,17 +69,35 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
|
|
|
68
69
|
const controller = new AbortController()
|
|
69
70
|
const signal = controller.signal
|
|
70
71
|
const watch = options.watch
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
if (projectIds.length > 0) {
|
|
73
|
+
if (watch && projectIds.length > 1) {
|
|
74
|
+
console.error('--watch is only supported with a single project ID')
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Fetch configs for all project IDs
|
|
79
|
+
const fetchResults = await Promise.all(
|
|
80
|
+
projectIds.map((projectId) => {
|
|
81
|
+
return configFromFetch({
|
|
82
|
+
allExternal,
|
|
83
|
+
externalPackages,
|
|
84
|
+
outDir,
|
|
85
|
+
projectId,
|
|
86
|
+
})
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Merge configs when multiple projects are provided, otherwise use single config directly
|
|
91
|
+
const mergedConfig = fetchResults.length === 1
|
|
92
|
+
? fetchResults[0].config
|
|
93
|
+
: mergeConfigs(fetchResults.map((r) => r.config))
|
|
94
|
+
const cwd = fetchResults[0].cwd
|
|
95
|
+
const websiteUrl = fetchResults[0].websiteUrl
|
|
96
|
+
|
|
78
97
|
const { rebuild, buildContext } = await bundle({
|
|
79
98
|
config: {
|
|
80
99
|
jsx,
|
|
81
|
-
...
|
|
100
|
+
...mergedConfig,
|
|
82
101
|
},
|
|
83
102
|
watch,
|
|
84
103
|
cwd,
|
|
@@ -419,14 +438,15 @@ const hasMcpCommand = cliArgs.includes('mcp')
|
|
|
419
438
|
const hasProjectOption = cliArgs.some((arg) => {
|
|
420
439
|
return arg === '--project' || arg.startsWith('--project=')
|
|
421
440
|
})
|
|
422
|
-
const
|
|
423
|
-
const
|
|
441
|
+
const hasMcpEvalCommand = hasMcpCommand && cliArgs.includes('eval')
|
|
442
|
+
const hasServerApiEnv = Boolean(process.env.FRAMER_PROJECT_URL || process.env.FRAMER_API_KEY)
|
|
443
|
+
const shouldUseServerApi = hasMcpCommand && (hasProjectOption || hasMcpEvalCommand || hasServerApiEnv)
|
|
444
|
+
const mcpMode = shouldUseServerApi
|
|
424
445
|
? 'server-api'
|
|
425
446
|
: config.mode || (config.mcpUrl ? 'plugin' : undefined)
|
|
426
447
|
|
|
427
448
|
if (mcpMode === 'server-api') {
|
|
428
449
|
// Server API mode - use framer-api directly
|
|
429
|
-
// Commands are registered via registerServerApiCommands below
|
|
430
450
|
await registerServerApiCommands()
|
|
431
451
|
} else {
|
|
432
452
|
// Plugin mode - use MCP transport
|
|
@@ -467,9 +487,112 @@ async function registerServerApiCommands() {
|
|
|
467
487
|
// Dynamic import to avoid loading framer-api in plugin mode
|
|
468
488
|
const { connect } = await import('framer-api')
|
|
469
489
|
|
|
490
|
+
// `mcp eval` — run arbitrary framer-api code against a project.
|
|
491
|
+
// Useful for testing API calls before implementing them in MCP tools.
|
|
492
|
+
// The `framer` client is available as a local variable inside the evaluated code.
|
|
493
|
+
cli.command('mcp eval [code]', 'Evaluate arbitrary framer-api code against a project')
|
|
494
|
+
.option(
|
|
495
|
+
'--project <url>',
|
|
496
|
+
'Framer project URL. Also reads FRAMER_PROJECT_URL env var.',
|
|
497
|
+
)
|
|
498
|
+
.action(async (code: string | undefined, options: { project?: string }) => {
|
|
499
|
+
// Read code from argument or stdin
|
|
500
|
+
let evalCode = code
|
|
501
|
+
if (!evalCode && !process.stdin.isTTY) {
|
|
502
|
+
const chunks: Buffer[] = []
|
|
503
|
+
for await (const chunk of process.stdin) {
|
|
504
|
+
chunks.push(chunk)
|
|
505
|
+
}
|
|
506
|
+
evalCode = Buffer.concat(chunks).toString('utf-8').trim()
|
|
507
|
+
}
|
|
508
|
+
if (!evalCode) {
|
|
509
|
+
console.error('No code provided. Pass as argument or pipe via stdin.')
|
|
510
|
+
process.exit(1)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const projectUrl =
|
|
514
|
+
options.project ||
|
|
515
|
+
process.env.FRAMER_PROJECT_URL ||
|
|
516
|
+
loadConfig().framerProjectUrl
|
|
517
|
+
const apiKey =
|
|
518
|
+
process.env.FRAMER_API_KEY || loadConfig().framerApiKey
|
|
519
|
+
|
|
520
|
+
if (!projectUrl) {
|
|
521
|
+
console.error('Project URL required. Use --project option, FRAMER_PROJECT_URL env var, or set during login.')
|
|
522
|
+
process.exit(1)
|
|
523
|
+
}
|
|
524
|
+
if (!apiKey) {
|
|
525
|
+
console.error('API key required. Set FRAMER_API_KEY env var or run `unframer mcp login` first.')
|
|
526
|
+
process.exit(1)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let framerClient: Awaited<ReturnType<typeof connect>> | undefined
|
|
530
|
+
let actionError: unknown
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
spinner.start('Connecting to Framer...')
|
|
534
|
+
framerClient = await connect(projectUrl, apiKey)
|
|
535
|
+
spinner.stop('Connected')
|
|
536
|
+
|
|
537
|
+
// Build an async function body. If the code is a single expression
|
|
538
|
+
// (no semicolons and no newlines), wrap it as a return so the result is captured.
|
|
539
|
+
const isExpression = !evalCode.includes(';') && !evalCode.includes('\n')
|
|
540
|
+
const functionBody = isExpression
|
|
541
|
+
? `return (${evalCode})`
|
|
542
|
+
: evalCode
|
|
543
|
+
|
|
544
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as
|
|
545
|
+
new (arg: string, body: string) => (framer: unknown) => Promise<unknown>
|
|
546
|
+
const fn = new AsyncFunction('framer', functionBody)
|
|
547
|
+
const result = await fn(framerClient)
|
|
548
|
+
|
|
549
|
+
if (result !== undefined) {
|
|
550
|
+
const output = (() => {
|
|
551
|
+
try {
|
|
552
|
+
return JSON.stringify(result, null, 2)
|
|
553
|
+
} catch {
|
|
554
|
+
return String(result)
|
|
555
|
+
}
|
|
556
|
+
})()
|
|
557
|
+
console.log(output)
|
|
558
|
+
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
actionError = error
|
|
561
|
+
} finally {
|
|
562
|
+
if (framerClient) {
|
|
563
|
+
try {
|
|
564
|
+
await framerClient.disconnect()
|
|
565
|
+
} catch (disconnectError) {
|
|
566
|
+
logger.error('Failed disconnecting from Framer:', disconnectError)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (actionError) {
|
|
572
|
+
console.error('Eval failed:', actionError instanceof Error ? actionError.message : actionError)
|
|
573
|
+
if (actionError instanceof Error && actionError.stack) {
|
|
574
|
+
console.error(actionError.stack)
|
|
575
|
+
}
|
|
576
|
+
process.exit(1)
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
|
|
470
580
|
// Import tool definitions and handler from plugin-mcp
|
|
471
581
|
// Note: Run `pnpm --filter plugin-mcp build && pnpm --filter plugin-mcp gen-unframer` first
|
|
472
|
-
|
|
582
|
+
let mcpTools: Awaited<typeof import('./plugin-mcp-dist/lib/mcp-handlers.js')>['mcpTools'] | undefined
|
|
583
|
+
let mcpToolHandler: Awaited<typeof import('./plugin-mcp-dist/lib/mcp-handlers.js')>['mcpToolHandler'] | undefined
|
|
584
|
+
try {
|
|
585
|
+
const imported = await import('./plugin-mcp-dist/lib/mcp-handlers.js')
|
|
586
|
+
mcpTools = imported.mcpTools
|
|
587
|
+
mcpToolHandler = imported.mcpToolHandler
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (hasMcpEvalCommand) {
|
|
590
|
+
// eval command only needs framer-api, not plugin-mcp handlers
|
|
591
|
+
logger.log('Could not load plugin-mcp handlers:', error instanceof Error ? error.message : error)
|
|
592
|
+
} else {
|
|
593
|
+
throw new Error('Failed to load plugin-mcp handlers. Run: pnpm --filter plugin-mcp tsc --incremental && pnpm --filter plugin-mcp gen-unframer', { cause: error })
|
|
594
|
+
}
|
|
595
|
+
}
|
|
473
596
|
if (!mcpTools || !mcpToolHandler) {
|
|
474
597
|
return
|
|
475
598
|
}
|
|
@@ -613,6 +736,7 @@ async function registerServerApiCommands() {
|
|
|
613
736
|
}
|
|
614
737
|
})
|
|
615
738
|
}
|
|
739
|
+
|
|
616
740
|
}
|
|
617
741
|
|
|
618
742
|
export type Config = {
|
|
@@ -662,6 +786,133 @@ type ComponentInstanceInPage = {
|
|
|
662
786
|
webPageId: string
|
|
663
787
|
}
|
|
664
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Merge multiple project configs into a single Config so esbuild bundles all
|
|
791
|
+
* components together and deduplicates shared chunks automatically.
|
|
792
|
+
*
|
|
793
|
+
* - Components maps are merged; on name collision the second project's component
|
|
794
|
+
* is prefixed with the project name to disambiguate.
|
|
795
|
+
* - First project's projectId/projectName/fullFramerProjectId are used as the
|
|
796
|
+
* primary identifiers (framerSiteId in ContextProviders).
|
|
797
|
+
* - Arrays (componentBreakpoints, tokens, locales, framerWebPages, etc.) are concatenated.
|
|
798
|
+
*/
|
|
799
|
+
function mergeConfigs(configs: Config[]): Config {
|
|
800
|
+
if (configs.length === 0) {
|
|
801
|
+
throw new Error('mergeConfigs called with empty array')
|
|
802
|
+
}
|
|
803
|
+
if (configs.length === 1) {
|
|
804
|
+
return configs[0]
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const first = configs[0]
|
|
808
|
+
|
|
809
|
+
// Merge components maps, disambiguating name collisions.
|
|
810
|
+
// Track rename maps per config so we can update breakpoints and instances.
|
|
811
|
+
const mergedComponents: Record<string, string> = {}
|
|
812
|
+
const renameMaps: Map<string, string>[] = []
|
|
813
|
+
|
|
814
|
+
for (const config of configs) {
|
|
815
|
+
const projectSlug = kebabCase(config.projectName || config.projectId || '')
|
|
816
|
+
const renameMap = new Map<string, string>()
|
|
817
|
+
renameMaps.push(renameMap)
|
|
818
|
+
|
|
819
|
+
for (const [name, url] of Object.entries(config.components)) {
|
|
820
|
+
if (mergedComponents[name]) {
|
|
821
|
+
// Name collision: prefix with project slug, ensure uniqueness with numeric suffix
|
|
822
|
+
const base = projectSlug ? `${projectSlug}/${name}` : `${config.projectId}/${name}`
|
|
823
|
+
const disambiguated = uniqueComponentName(base, mergedComponents)
|
|
824
|
+
logger.log(`Component name collision for "${name}", renaming to "${disambiguated}"`)
|
|
825
|
+
renameMap.set(name, disambiguated)
|
|
826
|
+
mergedComponents[disambiguated] = url
|
|
827
|
+
} else {
|
|
828
|
+
mergedComponents[name] = url
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Merge componentBreakpoints, updating componentName for any renamed components
|
|
834
|
+
const mergedBreakpoints = configs.flatMap((config, i) => {
|
|
835
|
+
const renameMap = renameMaps[i]
|
|
836
|
+
return (config.componentBreakpoints || []).map((bp) => {
|
|
837
|
+
const renamed = renameMap.get(bp.componentName)
|
|
838
|
+
if (renamed) {
|
|
839
|
+
return { ...bp, componentName: renamed }
|
|
840
|
+
}
|
|
841
|
+
return bp
|
|
842
|
+
})
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
// Merge tokens, deduplicate by id (keep first occurrence)
|
|
846
|
+
const seenTokenIds = new Set<string>()
|
|
847
|
+
const mergedTokens = configs.flatMap((config) => {
|
|
848
|
+
return (config.tokens || []).filter((token) => {
|
|
849
|
+
if (seenTokenIds.has(token.id)) {
|
|
850
|
+
return false
|
|
851
|
+
}
|
|
852
|
+
seenTokenIds.add(token.id)
|
|
853
|
+
return true
|
|
854
|
+
})
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
// Merge locales, deduplicate by code
|
|
858
|
+
const seenLocaleCodes = new Set<string>()
|
|
859
|
+
const mergedLocales = configs.flatMap((config) => {
|
|
860
|
+
return (config.locales || []).filter((locale) => {
|
|
861
|
+
if (seenLocaleCodes.has(locale.code)) {
|
|
862
|
+
return false
|
|
863
|
+
}
|
|
864
|
+
seenLocaleCodes.add(locale.code)
|
|
865
|
+
return true
|
|
866
|
+
})
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
// Merge framerWebPages
|
|
870
|
+
const mergedWebPages = configs.flatMap((config) => {
|
|
871
|
+
return config.framerWebPages || []
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
// Merge componentInstancesInIndexPage, updating componentPathSlug for renamed components
|
|
875
|
+
const mergedInstances = configs.flatMap((config, i) => {
|
|
876
|
+
const renameMap = renameMaps[i]
|
|
877
|
+
return (config.componentInstancesInIndexPage || []).map((instance) => {
|
|
878
|
+
const renamed = renameMap.get(instance.componentPathSlug)
|
|
879
|
+
if (renamed) {
|
|
880
|
+
return { ...instance, componentPathSlug: renamed }
|
|
881
|
+
}
|
|
882
|
+
return instance
|
|
883
|
+
})
|
|
884
|
+
}).sort((a, b) => {
|
|
885
|
+
return a.pageOrdering - b.pageOrdering
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
const projectNames = configs
|
|
889
|
+
.map((c) => c.projectName)
|
|
890
|
+
.filter(isTruthy)
|
|
891
|
+
spinner.info(`Merging ${configs.length} projects: ${projectNames.join(', ')}`)
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
...first,
|
|
895
|
+
components: mergedComponents,
|
|
896
|
+
componentBreakpoints: mergedBreakpoints,
|
|
897
|
+
tokens: mergedTokens,
|
|
898
|
+
locales: mergedLocales,
|
|
899
|
+
framerWebPages: mergedWebPages,
|
|
900
|
+
componentInstancesInIndexPage: mergedInstances,
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** Generate a unique component name by appending a numeric suffix if the base already exists */
|
|
905
|
+
function uniqueComponentName(base: string, existing: Record<string, string>): string {
|
|
906
|
+
if (!existing[base]) {
|
|
907
|
+
return base
|
|
908
|
+
}
|
|
909
|
+
let index = 2
|
|
910
|
+
while (existing[`${base}-${index}`]) {
|
|
911
|
+
index++
|
|
912
|
+
}
|
|
913
|
+
return `${base}-${index}`
|
|
914
|
+
}
|
|
915
|
+
|
|
665
916
|
export async function configFromFetch({
|
|
666
917
|
projectId,
|
|
667
918
|
externalPackages = [] as string[],
|
package/src/exporter.ts
CHANGED
|
@@ -8,8 +8,6 @@ const require = createRequire(import.meta.url)
|
|
|
8
8
|
|
|
9
9
|
import { Sema } from 'async-sema'
|
|
10
10
|
|
|
11
|
-
import { nodeModulesPolyfillPlugin } from 'esbuild-plugins-node-modules-polyfill'
|
|
12
|
-
|
|
13
11
|
import { transform } from '@babel/core'
|
|
14
12
|
import { exec } from 'child_process'
|
|
15
13
|
import { error } from 'console'
|
|
@@ -219,7 +217,6 @@ export async function bundle({
|
|
|
219
217
|
missingPackages.add(pkg)
|
|
220
218
|
},
|
|
221
219
|
}),
|
|
222
|
-
nodeModulesPolyfillPlugin({}),
|
|
223
220
|
{
|
|
224
221
|
name: 'virtual loader',
|
|
225
222
|
setup(build) {
|
|
@@ -577,17 +574,28 @@ export async function bundle({
|
|
|
577
574
|
?.split('\n')
|
|
578
575
|
.forEach((x) => logger.log(x))
|
|
579
576
|
|
|
577
|
+
function isChunkPath(filePath: string) {
|
|
578
|
+
return (
|
|
579
|
+
filePath.includes('/chunks/') ||
|
|
580
|
+
filePath.includes('\\chunks\\')
|
|
581
|
+
)
|
|
582
|
+
}
|
|
580
583
|
const jsxFiles = buildResult.outputFiles
|
|
581
584
|
.filter(
|
|
582
585
|
(x) =>
|
|
583
586
|
x.path.endsWith('.js') &&
|
|
587
|
+
!isChunkPath(x.path) &&
|
|
584
588
|
fs.existsSync(getFilePaths(x.path, out).jsxPath),
|
|
585
589
|
)
|
|
586
590
|
.map((x) => getFilePaths(x.path, out).jsxPath)
|
|
587
591
|
const outFiles = buildResult.outputFiles
|
|
588
592
|
.map((x) => {
|
|
589
593
|
const paths = getFilePaths(x.path, out)
|
|
590
|
-
if (
|
|
594
|
+
if (
|
|
595
|
+
x.path.endsWith('.js') &&
|
|
596
|
+
!isChunkPath(x.path) &&
|
|
597
|
+
fs.existsSync(paths.jsxPath)
|
|
598
|
+
) {
|
|
591
599
|
return null // Will be handled by jsx files
|
|
592
600
|
}
|
|
593
601
|
return paths.finalJsPath
|
|
@@ -737,8 +745,17 @@ export async function bundle({
|
|
|
737
745
|
formatted = result.content
|
|
738
746
|
}
|
|
739
747
|
} catch (e) {
|
|
748
|
+
spinner.warn(
|
|
749
|
+
`Babel/Biome formatting failed for ${componentName}: ${e instanceof Error ? e.message : e}`,
|
|
750
|
+
)
|
|
751
|
+
logger.log(e instanceof Error ? e.stack : e)
|
|
740
752
|
notifyError(e, 'babel transform and format')
|
|
741
753
|
}
|
|
754
|
+
} else if (tooBig) {
|
|
755
|
+
const sizeMB = (file.text.length / (1024 * 1024)).toFixed(2)
|
|
756
|
+
spinner.warn(
|
|
757
|
+
`Skipping JSX formatting for ${componentName} (${sizeMB} MB exceeds ${(tooBigSize / (1024 * 1024)).toFixed(2)} MB limit)`,
|
|
758
|
+
)
|
|
742
759
|
}
|
|
743
760
|
|
|
744
761
|
const prefix =
|
|
@@ -752,12 +769,24 @@ export async function bundle({
|
|
|
752
769
|
// Always write the temp .js file for type extraction
|
|
753
770
|
await fs.promises.writeFile(paths.tempJsPath, codeJs, 'utf-8')
|
|
754
771
|
|
|
755
|
-
//
|
|
772
|
+
// Write .jsx file if formatting succeeded, or fall back to writing
|
|
773
|
+
// unformatted .js content into the .jsx path when formatting fails
|
|
774
|
+
// (babel error, component too big, etc.) but a stale .jsx already
|
|
775
|
+
// exists on disk. Without this fallback the stale .jsx keeps old
|
|
776
|
+
// chunk imports while the old chunks are deleted, breaking the build.
|
|
756
777
|
if (didFormat && codeJsx !== existing) {
|
|
757
778
|
await fs.promises.writeFile(paths.jsxPath, codeJsx, 'utf-8')
|
|
758
779
|
logger.log(`Updated JSX file for ${componentName}`)
|
|
759
780
|
} else if (didFormat) {
|
|
760
781
|
logger.log(`JSX file unchanged for ${componentName}`)
|
|
782
|
+
} else if (existing && codeJs !== existing) {
|
|
783
|
+
// Formatting failed but .jsx exists with stale content (e.g.
|
|
784
|
+
// old chunk hashes). Overwrite with unformatted JS so imports
|
|
785
|
+
// stay in sync with the current build's chunks.
|
|
786
|
+
await fs.promises.writeFile(paths.jsxPath, codeJs, 'utf-8')
|
|
787
|
+
logger.log(
|
|
788
|
+
`Updated JSX file (unformatted) for ${componentName}`,
|
|
789
|
+
)
|
|
761
790
|
}
|
|
762
791
|
}
|
|
763
792
|
spinner.stop()
|
|
@@ -772,8 +801,12 @@ export async function bundle({
|
|
|
772
801
|
if (file.path.endsWith('.js')) {
|
|
773
802
|
const paths = getFilePaths(file.path, out)
|
|
774
803
|
|
|
775
|
-
if (
|
|
804
|
+
if (
|
|
805
|
+
!isChunkPath(paths.tempJsPath) &&
|
|
806
|
+
fs.existsSync(paths.jsxPath)
|
|
807
|
+
) {
|
|
776
808
|
// Remove temp .js file if .jsx equivalent exists
|
|
809
|
+
// (never applies to chunks which stay as .js)
|
|
777
810
|
logger.log(
|
|
778
811
|
'removing temp JS file with JSX equivalent:',
|
|
779
812
|
path.relative(out, paths.tempJsPath),
|
package/src/framer.d.ts
CHANGED
|
@@ -4859,4 +4859,132 @@ export declare const withVariantFX: <T extends MotionProps>(Component: React_2.C
|
|
|
4859
4859
|
export { }
|
|
4860
4860
|
export declare const combinedCSSRules: string[]
|
|
4861
4861
|
|
|
4862
|
+
export interface FramerMotionGlobalConfig {
|
|
4863
|
+
useManualTiming?: boolean
|
|
4864
|
+
instantAnimations?: boolean
|
|
4865
|
+
skipAnimations?: boolean
|
|
4866
|
+
mix?: unknown
|
|
4867
|
+
WillChange?: new (value: string) => unknown
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
export interface FramerInjectedRuntimeImage {
|
|
4871
|
+
src?: string
|
|
4872
|
+
srcSet?: string
|
|
4873
|
+
}
|
|
4874
|
+
|
|
4875
|
+
export interface FramerInjectedRuntimeRect {
|
|
4876
|
+
width?: number
|
|
4877
|
+
height?: number
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
export interface FramerInjectedRuntime {
|
|
4881
|
+
imagePlaceholderSvg?: string
|
|
4882
|
+
useImageSource?(image: FramerInjectedRuntimeImage, rect?: FramerInjectedRuntimeRect, nodeId?: string): string
|
|
4883
|
+
useImageElement?(image: FramerInjectedRuntimeImage, rect?: FramerInjectedRuntimeRect, nodeId?: string): HTMLImageElement
|
|
4884
|
+
canRenderOptimizedCanvasImage?(src?: string): boolean
|
|
4885
|
+
isOnPageCanvas?: boolean
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4888
|
+
export interface FramerMotionFrameData {
|
|
4889
|
+
delta: number
|
|
4890
|
+
timestamp: number
|
|
4891
|
+
isProcessing: boolean
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4894
|
+
export type FramerMotionFrameCallback = (frameData: FramerMotionFrameData) => void
|
|
4895
|
+
|
|
4896
|
+
export interface FramerMotionFrameStep {
|
|
4897
|
+
schedule(callback: FramerMotionFrameCallback, keepAlive?: boolean, immediate?: boolean): FramerMotionFrameCallback
|
|
4898
|
+
cancel(callback: FramerMotionFrameCallback): void
|
|
4899
|
+
process(frameData: FramerMotionFrameData): void
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
export type FramerMotionFrameStepName =
|
|
4903
|
+
| 'setup'
|
|
4904
|
+
| 'read'
|
|
4905
|
+
| 'resolveKeyframes'
|
|
4906
|
+
| 'preUpdate'
|
|
4907
|
+
| 'update'
|
|
4908
|
+
| 'preRender'
|
|
4909
|
+
| 'render'
|
|
4910
|
+
| 'postRender'
|
|
4911
|
+
|
|
4912
|
+
export type FramerMotionFrameSteps = Record<FramerMotionFrameStepName, FramerMotionFrameStep>
|
|
4913
|
+
|
|
4914
|
+
export interface FramerMotionTime {
|
|
4915
|
+
now(): number
|
|
4916
|
+
set(newTime: number): void
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
export interface FramerMotionGeneratorState<Value = unknown> {
|
|
4920
|
+
value: Value
|
|
4921
|
+
done?: boolean
|
|
4922
|
+
}
|
|
4923
|
+
|
|
4924
|
+
export interface FramerMotionGenerator<Value = unknown> {
|
|
4925
|
+
calculatedDuration?: number | null
|
|
4926
|
+
next(time: number): FramerMotionGeneratorState<Value>
|
|
4927
|
+
velocity?(time: number): number
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
export interface FramerMotionAnimationDriver {
|
|
4931
|
+
now(): number
|
|
4932
|
+
start(autoplay?: boolean): void
|
|
4933
|
+
stop(): void
|
|
4934
|
+
}
|
|
4935
|
+
|
|
4936
|
+
export interface FramerMotionAnimationOptions<Value = unknown> {
|
|
4937
|
+
keyframes: Value[]
|
|
4938
|
+
autoplay?: boolean
|
|
4939
|
+
delay?: number
|
|
4940
|
+
repeat?: number
|
|
4941
|
+
repeatDelay?: number
|
|
4942
|
+
repeatType?: 'loop' | 'reverse' | 'mirror'
|
|
4943
|
+
velocity?: number
|
|
4944
|
+
finalKeyframe?: Value
|
|
4945
|
+
startTime?: number
|
|
4946
|
+
allowFlatten?: boolean
|
|
4947
|
+
motionValue?: { updatedAt?: number }
|
|
4948
|
+
type?: (options: FramerMotionAnimationOptions<Value>) => FramerMotionGenerator<Value>
|
|
4949
|
+
driver?: (update: (timestamp: number) => void) => FramerMotionAnimationDriver
|
|
4950
|
+
onUpdate?(latest: Value): void
|
|
4951
|
+
onPlay?(): void
|
|
4952
|
+
onComplete?(): void
|
|
4953
|
+
onCancel?(): void
|
|
4954
|
+
onStop?(): void
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
export declare class JSAnimation<Value = unknown> implements PromiseLike<void> {
|
|
4958
|
+
constructor(options: FramerMotionAnimationOptions<Value>)
|
|
4959
|
+
options: FramerMotionAnimationOptions<Value>
|
|
4960
|
+
state: 'idle' | 'running' | 'paused' | 'finished'
|
|
4961
|
+
currentTime: number
|
|
4962
|
+
playbackSpeed: number
|
|
4963
|
+
readonly duration: number
|
|
4964
|
+
readonly iterationDuration: number
|
|
4965
|
+
time: number
|
|
4966
|
+
speed: number
|
|
4967
|
+
readonly finished: Promise<void>
|
|
4968
|
+
then<TResult1 = void, TResult2 = never>(
|
|
4969
|
+
onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
|
|
4970
|
+
onrejected?: ((reason: Error) => TResult2 | PromiseLike<TResult2>) | null,
|
|
4971
|
+
): Promise<TResult1 | TResult2>
|
|
4972
|
+
play(): void
|
|
4973
|
+
pause(): void
|
|
4974
|
+
complete(): void
|
|
4975
|
+
finish(): void
|
|
4976
|
+
cancel(): void
|
|
4977
|
+
stop(): void
|
|
4978
|
+
tick(timestamp: number, sample?: boolean): FramerMotionGeneratorState<Value>
|
|
4979
|
+
sample(sampleTime: number): FramerMotionGeneratorState<Value>
|
|
4980
|
+
attachTimeline(timeline: { observe(animation: JSAnimation<Value>): VoidFunction }): VoidFunction
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
export declare const MotionGlobalConfig: FramerMotionGlobalConfig
|
|
4984
|
+
export declare const frameData: FramerMotionFrameData
|
|
4985
|
+
export declare const frameSteps: FramerMotionFrameSteps
|
|
4986
|
+
export declare const time: FramerMotionTime
|
|
4987
|
+
export declare const visualElementStore: WeakMap<object, unknown>
|
|
4988
|
+
export declare function _injectRuntime(injectedRuntime: FramerInjectedRuntime): void
|
|
4989
|
+
|
|
4862
4990
|
export * from 'real-framer-motion'
|