@bravostudioai/react 0.1.32 → 0.1.34

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.
@@ -1 +1 @@
1
- {"version":3,"file":"EncoreApp.js","sources":["../../src/components/EncoreApp.tsx"],"sourcesContent":["\"use client\";\nimport useSWR from \"swr\";\nimport fetcher from \"../lib/fetcher\";\nimport useEncoreState from \"../stores/useEncoreState\";\nimport React, {\n Suspense,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n} from \"react\";\nimport { isLocalMode, setLocalModeOverride } from \"../lib/localMode\";\nimport type { EncoreActionPayload } from \"../contexts/EncoreActionContext\";\nimport DynamicComponent from \"./DynamicComponent\";\nimport { useEncoreRouter } from \"../contexts/EncoreRouterContext\";\nimport { usePusherUpdates } from \"../hooks/usePusherUpdates\";\nimport { useFontLoader } from \"../hooks/useFontLoader\";\nimport { useRepeatingContainers } from \"../hooks/useRepeatingContainers\";\nimport { EncoreContextProviders } from \"./EncoreContextProviders\";\nimport { patchPageData } from \"../lib/dataPatching\";\nimport logger from \"../lib/logger\";\n\n// Simple internal Link component that uses our router context\nconst Link = ({ to, children, style, ...props }: any) => {\n const { navigate } = useEncoreRouter();\n return (\n <a\n href={to}\n onClick={(e) => {\n e.preventDefault();\n navigate(to);\n }}\n style={{ cursor: \"pointer\", ...style }}\n {...props}\n >\n {children}\n </a>\n );\n};\n\n/**\n * Props for the EncoreApp component\n */\nexport type EncoreAppProps = {\n /** Unique identifier for the Encore app */\n appId: string;\n /** Unique identifier for the page to render. If not provided, shows page selector UI */\n pageId?: string;\n /** Optional component identifier for context tracking */\n componentId?: string;\n /** Fallback UI to show during component loading */\n fallback?: React.ReactNode;\n /** Callback fired when the container size changes */\n onSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when the content size changes (including overflow) */\n onContentSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when user interactions trigger actions (button clicks, form submissions, etc.) */\n onAction?: (payload: EncoreActionPayload) => void | Promise<void>;\n /** Data bindings for components with encore:data tags. Maps component IDs to display values */\n data?: Record<string, string | number | any[]>;\n /** Force component loading from \"remote\" CDN or \"local\" filesystem */\n source?: \"remote\" | \"local\";\n /** Control repeating containers (sliders, lists) programmatically by container ID */\n repeatingContainerControls?: Record<\n string,\n { currentIndex?: number; onIndexChange?: (index: number) => void }\n >;\n /** Control input groups (radio button-like behavior). Maps group name to active element */\n inputGroups?: Record<string, string>;\n /** Base URL for the Encore service API */\n baseURL?: string;\n /** Provide app definition directly instead of fetching (for offline/bundled deployments) */\n appDefinition?: any;\n /** Provide page definition directly instead of fetching (for offline/bundled deployments) */\n pageDefinition?: any;\n /** Provide component code directly instead of fetching (for offline/bundled deployments) */\n componentCode?: string;\n};\n\ntype Props = EncoreAppProps;\n\ntype EncoreAssetsById = Record<string, { url?: string }>;\ntype EncoreState = {\n setApp: (app: unknown) => void;\n setAppId: (id: string) => void;\n setPageId: (id: string) => void;\n assetsById: EncoreAssetsById;\n};\n\nconst setAppSelector = (state: EncoreState) => state.setApp;\nconst setAppIdSelector = (state: EncoreState) => state.setAppId;\nconst setPageIdSelector = (state: EncoreState) => state.setPageId;\nconst assetsByIdSelector = (state: EncoreState) => state.assetsById;\n\n/**\n * Main Encore runtime component\n *\n * Loads and renders Encore Studio apps dynamically from the Encore service.\n * Handles data fetching, font loading, real-time updates, and component rendering.\n *\n * @example\n * // Basic usage\n * <EncoreApp appId=\"01ABC123\" pageId=\"01DEF456\" />\n *\n * @example\n * // With data binding\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * data={{\n * \"title-component\": { text: \"Hello World\" }\n * }}\n * />\n *\n * @example\n * // Controlling a slider\n * const [slideIndex, setSlideIndex] = useState(0);\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * repeatingContainerControls={{\n * \"slider-123\": {\n * currentIndex: slideIndex,\n * onIndexChange: setSlideIndex\n * }\n * }}\n * />\n */\nconst EncoreApp = ({\n appId,\n pageId,\n componentId,\n fallback,\n onSizeChange,\n onContentSizeChange,\n onAction,\n data,\n source,\n repeatingContainerControls,\n inputGroups,\n baseURL,\n appDefinition,\n pageDefinition,\n componentCode,\n}: Props) => {\n logger.debug('EncoreApp render', { appId, pageId });\n\n // CRITICAL: Set baseURL BEFORE any hooks that might trigger fetches\n // This must happen synchronously, not in useEffect, because useSWR will fetch immediately\n if (baseURL) {\n const currentBaseURL = useEncoreState.getState().baseURL;\n if (currentBaseURL !== baseURL) {\n useEncoreState.getState().setBaseURL(baseURL);\n }\n }\n\n // Apply source override immediately so hooks below observe correct mode\n if (source) {\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n }\n\n const noRedirect = false;\n\n const setApp = useEncoreState(setAppSelector);\n const setAppId = useEncoreState(setAppIdSelector);\n const setPageId = useEncoreState(setPageIdSelector);\n const assetsById = useEncoreState(assetsByIdSelector);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const contentWrapperRef = useRef<HTMLDivElement | null>(null);\n\n // Monitor content size changes\n useEffect(() => {\n if (!onContentSizeChange) return;\n const element = contentWrapperRef.current;\n if (!element) return;\n\n const notify = () => {\n // Use scroll dimensions to get full size including overflow\n onContentSizeChange({\n width: element.scrollWidth,\n height: element.scrollHeight,\n });\n };\n\n const observer = new ResizeObserver(() => {\n notify();\n });\n\n // Emit initial size\n notify();\n observer.observe(element);\n return () => observer.disconnect();\n }, [onContentSizeChange]);\n\n // State to force DynamicComponent reload when updates are received\n const [reloadKey, setReloadKey] = useState<string | number>(0);\n // Set up Pusher to listen for component updates\n const handleUpdate = useCallback(() => {\n // Increment reloadKey to force DynamicComponent to reload\n setReloadKey((prev) => (typeof prev === \"number\" ? prev + 1 : Date.now()));\n }, []);\n\n // Only enable Pusher in remote mode - it doesn't make sense in local mode\n usePusherUpdates({\n appId,\n pageId: pageId || undefined,\n enabled: !isLocalMode() && !appDefinition,\n onUpdate: handleUpdate,\n });\n\n const useLocalFlag = source === \"local\" || isLocalMode();\n // If appDefinition is provided, disable SWR fetch by setting url to null\n const appUrl = appDefinition\n ? null\n : appId && `/devices/apps/${appId}${useLocalFlag ? \"?useLocal=1\" : \"\"}`;\n\n const appSWR = useSWR(appUrl, fetcher, {\n suspense: !!appUrl, // Only use suspense if we are fetching\n });\n\n const app = appDefinition ? { data: appDefinition } : appSWR;\n\n useEffect(() => {\n setApp(app.data);\n }, [app.data, setApp]);\n\n // Load fonts declared in app.json\n useFontLoader(app?.data);\n\n useEffect(() => {\n setAppId(appId);\n }, [appId, setAppId]);\n\n useEffect(() => {\n if (!pageId) return;\n setPageId(pageId);\n }, [pageId, setPageId]);\n\n // FIXME: Asset data should be embedded into & preloaded by component, not looked up like this\n useEffect(() => {\n if (Object.keys(assetsById).length === 0) return;\n (async () => {\n // Preload images in the browser\n await Promise.allSettled(\n Object.keys(assetsById).map((id) => {\n if (assetsById[id].url) {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = resolve;\n img.onerror = resolve; // tolerate failures to avoid unhandled rejections\n img.src = assetsById[id].url!;\n });\n }\n return Promise.resolve();\n })\n );\n })();\n }, [assetsById]);\n\n const pageUrl = pageDefinition\n ? null\n : appId &&\n pageId &&\n `/devices/apps/${appId}/node/${pageId}${\n useLocalFlag ? \"?useLocal=1\" : \"\"\n }`;\n\n logger.debug('Page data fetch', { pageUrl });\n\n const pageSWR = useSWR(pageUrl, fetcher, { suspense: !!pageUrl });\n const pageData = pageDefinition ? { data: pageDefinition } : pageSWR;\n\n logger.debug('Page data loaded', {\n hasData: !!pageData?.data,\n dataType: typeof pageData?.data\n });\n\n // Memoize the context object to prevent infinite re-renders\n // Only recreate when the actual data changes, not on every render\n const context = useMemo(() => {\n let clientData = pageData.data?.clientData;\n logger.debug('Building context', {\n hasClientData: !!clientData,\n clientDataKeys: Object.keys(clientData || {}).length\n });\n\n // Apply layout heuristics to fix common issues\n if (clientData) {\n patchPageData(clientData);\n }\n\n return {\n nodeData: undefined,\n // Allow overriding specific values by element id.\n // For now, this is used to override TextComponent content when the node has the PROP:TEXT_VAR tag.\n textOverridesById: data,\n // Support for encore:data:array tags - provide array data by component ID\n arrayDataById: data,\n // Support for standalone component data binding (encore:data tags at root level)\n rootData: data,\n };\n }, [pageData.data?.clientData, data]);\n\n // Manage repeating container controls (sliders, lists)\n const repeatingContainerContextValue = useRepeatingContainers(\n repeatingContainerControls\n );\n\n // Sync input groups from props to store\n useEffect(() => {\n if (inputGroups) {\n const setInputGroupValue = useEncoreState.getState().setInputGroupValue;\n Object.entries(inputGroups).forEach(([groupName, elementName]) => {\n setInputGroupValue(groupName, elementName);\n });\n }\n }, [inputGroups]);\n\n // Observe size changes of the dynamic content container and notify consumer\n useEffect(() => {\n if (!onSizeChange) return;\n const element = containerRef.current;\n if (!element) return;\n const notify = (entry: ResizeObserverEntry) => {\n const cr = entry.contentRect;\n onSizeChange({ width: cr.width, height: cr.height });\n };\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n notify(entry);\n }\n });\n // Emit initial size as soon as possible\n const rect = element.getBoundingClientRect();\n onSizeChange({ width: rect.width, height: rect.height });\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n }, [onSizeChange]);\n\n // Per-instance source override\n useEffect(() => {\n if (!source) return;\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n return () => {\n // Clear override when this instance unmounts\n setLocalModeOverride(null);\n };\n }, [source]);\n\n if (!pageId) {\n return (\n <div style={{ padding: \"30px\" }}>\n <div style={{ overflowY: \"auto\" }}>\n {(isLocalMode()\n ? // Local mode: app.json provides pages under app.data.pages\n ((app?.data as any)?.app?.data?.pages || []).map(\n (pg: any) => pg?.id\n )\n : app?.data?.app.pageIds || []\n ).map((p: string) => (\n <Link\n key={p}\n to={`/apps/${appId}/pages/${p}?noRedirect=${noRedirect}`}\n style={{\n fontSize: 20,\n display: \"block\",\n marginBottom: \"10px\",\n }}\n >\n {p}\n </Link>\n ))}\n </div>\n </div>\n );\n }\n return (\n <div\n ref={containerRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n <div\n ref={contentWrapperRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n }}\n >\n <Suspense fallback={fallback || <div />}>\n <EncoreContextProviders\n componentId={componentId}\n onAction={onAction}\n repeatingContainerContextValue={repeatingContainerContextValue}\n bindingContextValue={context}\n >\n <DynamicComponent\n name={`${appId}/draft/components/${pageId}`}\n fallback={fallback}\n reloadKey={reloadKey}\n componentCode={componentCode}\n />\n </EncoreContextProviders>\n </Suspense>\n </div>\n </div>\n );\n};\n\nexport default EncoreApp;\n"],"names":["Link","to","children","style","props","navigate","useEncoreRouter","jsx","e","setAppSelector","state","setAppIdSelector","setPageIdSelector","assetsByIdSelector","EncoreApp","appId","pageId","componentId","fallback","onSizeChange","onContentSizeChange","onAction","data","source","repeatingContainerControls","inputGroups","baseURL","appDefinition","pageDefinition","componentCode","logger","useEncoreState","setLocalModeOverride","noRedirect","setApp","setAppId","setPageId","assetsById","containerRef","useRef","contentWrapperRef","useEffect","element","notify","observer","reloadKey","setReloadKey","useState","handleUpdate","useCallback","prev","usePusherUpdates","isLocalMode","useLocalFlag","appUrl","appSWR","useSWR","fetcher","app","useFontLoader","id","resolve","img","pageUrl","pageSWR","pageData","context","useMemo","clientData","patchPageData","repeatingContainerContextValue","useRepeatingContainers","setInputGroupValue","groupName","elementName","entry","cr","entries","rect","Suspense","EncoreContextProviders","DynamicComponent","pg","p"],"mappings":"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"}
1
+ {"version":3,"file":"EncoreApp.js","sources":["../../src/components/EncoreApp.tsx"],"sourcesContent":["\"use client\";\nimport useSWR from \"swr\";\nimport fetcher from \"../lib/fetcher\";\nimport useEncoreState from \"../stores/useEncoreState\";\nimport React, {\n Suspense,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n} from \"react\";\nimport { isLocalMode, setLocalModeOverride } from \"../lib/localMode\";\nimport type { EncoreActionPayload } from \"../contexts/EncoreActionContext\";\nimport DynamicComponent from \"./DynamicComponent\";\nimport { useEncoreRouter } from \"../contexts/EncoreRouterContext\";\nimport { usePusherUpdates } from \"../hooks/usePusherUpdates\";\nimport { useFontLoader } from \"../hooks/useFontLoader\";\nimport { useRepeatingContainers } from \"../hooks/useRepeatingContainers\";\nimport { EncoreContextProviders } from \"./EncoreContextProviders\";\nimport { patchPageData } from \"../lib/dataPatching\";\nimport logger from \"../lib/logger\";\n\n// Simple internal Link component that uses our router context\nconst Link = ({ to, children, style, ...props }: any) => {\n const { navigate } = useEncoreRouter();\n return (\n <a\n href={to}\n onClick={(e) => {\n e.preventDefault();\n navigate(to);\n }}\n style={{ cursor: \"pointer\", ...style }}\n {...props}\n >\n {children}\n </a>\n );\n};\n\n/**\n * Props for the EncoreApp component\n */\nexport type EncoreAppProps = {\n /** Unique identifier for the Encore app */\n appId: string;\n /** Unique identifier for the page to render. If not provided, shows page selector UI */\n pageId?: string;\n /** Optional component identifier for context tracking */\n componentId?: string;\n /** Fallback UI to show during component loading */\n fallback?: React.ReactNode;\n /** Callback fired when the container size changes */\n onSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when the content size changes (including overflow) */\n onContentSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when user interactions trigger actions (button clicks, form submissions, etc.) */\n onAction?: (payload: EncoreActionPayload) => void | Promise<void>;\n /** Data bindings for components with encore:data tags. Maps component IDs to display values */\n data?: Record<string, string | number | any[]>;\n /** Force component loading from \"remote\" CDN or \"local\" filesystem */\n source?: \"remote\" | \"local\";\n /** Control repeating containers (sliders, lists) programmatically by container ID */\n repeatingContainerControls?: Record<\n string,\n { currentIndex?: number; onIndexChange?: (index: number) => void }\n >;\n /** Control input groups (radio button-like behavior). Maps group name to active element */\n inputGroups?: Record<string, string>;\n /** Base URL for the Encore service API */\n baseURL?: string;\n /** Provide app definition directly instead of fetching (for offline/bundled deployments) */\n appDefinition?: any;\n /** Provide page definition directly instead of fetching (for offline/bundled deployments) */\n pageDefinition?: any;\n /** Provide component code directly instead of fetching (for offline/bundled deployments) */\n componentCode?: string;\n /** Deployment mode: dynamic (default), optimistic, or production */\n mode?: \"dynamic\" | \"optimistic\" | \"production\";\n};\n\ntype Props = EncoreAppProps;\n\ntype EncoreAssetsById = Record<string, { url?: string }>;\ntype EncoreState = {\n setApp: (app: unknown) => void;\n setAppId: (id: string) => void;\n setPageId: (id: string) => void;\n assetsById: EncoreAssetsById;\n};\n\nconst setAppSelector = (state: EncoreState) => state.setApp;\nconst setAppIdSelector = (state: EncoreState) => state.setAppId;\nconst setPageIdSelector = (state: EncoreState) => state.setPageId;\nconst assetsByIdSelector = (state: EncoreState) => state.assetsById;\n\n/**\n * Main Encore runtime component\n *\n * Loads and renders Encore Studio apps dynamically from the Encore service.\n * Handles data fetching, font loading, real-time updates, and component rendering.\n *\n * @example\n * // Basic usage\n * <EncoreApp appId=\"01ABC123\" pageId=\"01DEF456\" />\n *\n * @example\n * // With data binding\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * data={{\n * \"title-component\": { text: \"Hello World\" }\n * }}\n * />\n *\n * @example\n * // Controlling a slider\n * const [slideIndex, setSlideIndex] = useState(0);\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * repeatingContainerControls={{\n * \"slider-123\": {\n * currentIndex: slideIndex,\n * onIndexChange: setSlideIndex\n * }\n * }}\n * />\n */\nconst EncoreApp = ({\n appId,\n pageId,\n componentId,\n fallback,\n onSizeChange,\n onContentSizeChange,\n onAction,\n data,\n source,\n repeatingContainerControls,\n inputGroups,\n baseURL,\n appDefinition,\n pageDefinition,\n componentCode,\n mode,\n}: Props) => {\n logger.debug(\"EncoreApp render\", { appId, pageId, mode });\n\n // CRITICAL: Set baseURL BEFORE any hooks that might trigger fetches\n // This must happen synchronously, not in useEffect, because useSWR will fetch immediately\n if (baseURL) {\n const currentBaseURL = useEncoreState.getState().baseURL;\n if (currentBaseURL !== baseURL) {\n useEncoreState.getState().setBaseURL(baseURL);\n }\n }\n\n // Apply source override immediately so hooks below observe correct mode\n if (source) {\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n }\n\n const noRedirect = false;\n\n const setApp = useEncoreState(setAppSelector);\n const setAppId = useEncoreState(setAppIdSelector);\n const setPageId = useEncoreState(setPageIdSelector);\n const assetsById = useEncoreState(assetsByIdSelector);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const contentWrapperRef = useRef<HTMLDivElement | null>(null);\n\n // Monitor content size changes\n useEffect(() => {\n if (!onContentSizeChange) return;\n const element = contentWrapperRef.current;\n if (!element) return;\n\n const notify = () => {\n // Use scroll dimensions to get full size including overflow\n onContentSizeChange({\n width: element.scrollWidth,\n height: element.scrollHeight,\n });\n };\n\n const observer = new ResizeObserver(() => {\n notify();\n });\n\n // Emit initial size\n notify();\n observer.observe(element);\n return () => observer.disconnect();\n }, [onContentSizeChange]);\n\n // State to force DynamicComponent reload when updates are received\n const [reloadKey, setReloadKey] = useState<string | number>(0);\n // Set up Pusher to listen for component updates\n const handleUpdate = useCallback(() => {\n // Increment reloadKey to force DynamicComponent to reload\n setReloadKey((prev) => (typeof prev === \"number\" ? prev + 1 : Date.now()));\n }, []);\n\n // Only enable Pusher in remote mode - it doesn't make sense in local mode\n usePusherUpdates({\n appId,\n pageId: pageId || undefined,\n enabled: !isLocalMode() && !appDefinition,\n onUpdate: handleUpdate,\n });\n\n const useLocalFlag = source === \"local\" || isLocalMode();\n\n // Determine if we should fetch app definition\n // Production: Do NOT fetch (use appDefinition)\n // Dynamic: Fetch always (ignore appDefinition unless valid) - wait, dynamic assumes no bundled data usually, or if provided, use it? Current logic uses simple check.\n // Optimistic: Fetch always, but use appDefinition as fallback/initial data\n\n // Logic:\n // If we have appDefinition:\n // - Production: Use it, no fetch.\n // - Optimistic: Use it as fallbackData (SWR), and fetch.\n // - Dynamic (Edge Case): User provided data but wants dynamic? Treat same as Optimistic roughly, or just ignore data.\n // But usually Dynamic won't have appDefinition passed unless manually done.\n\n // Refined Logic based on `mode`:\n const isProductionMode = mode === \"production\";\n const isOptimisticMode = mode === \"optimistic\";\n // Default to dynamic if not specified\n const isDynamicMode = !isProductionMode && !isOptimisticMode;\n\n const shouldFetchApp = !isProductionMode && appId;\n const appUrl = shouldFetchApp\n ? `/devices/apps/${appId}${useLocalFlag ? \"?useLocal=1\" : \"\"}`\n : null;\n\n const appSWR = useSWR(appUrl, fetcher, {\n suspense: isDynamicMode && !!appUrl, // Suspense only for Dynamic mode\n fallbackData: isOptimisticMode && appDefinition ? appDefinition : undefined,\n revalidateOnMount: true, // Ensure we fetch fresh data in optimistic mode\n });\n\n const app =\n isProductionMode && appDefinition ? { data: appDefinition } : appSWR;\n\n useEffect(() => {\n setApp(app.data);\n }, [app.data, setApp]);\n\n // Load fonts declared in app.json\n useFontLoader(app?.data);\n\n useEffect(() => {\n setAppId(appId);\n }, [appId, setAppId]);\n\n useEffect(() => {\n if (!pageId) return;\n setPageId(pageId);\n }, [pageId, setPageId]);\n\n // FIXME: Asset data should be embedded into & preloaded by component, not looked up like this\n useEffect(() => {\n if (Object.keys(assetsById).length === 0) return;\n (async () => {\n // Preload images in the browser\n await Promise.allSettled(\n Object.keys(assetsById).map((id) => {\n if (assetsById[id].url) {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = resolve;\n img.onerror = resolve; // tolerate failures to avoid unhandled rejections\n img.src = assetsById[id].url!;\n });\n }\n return Promise.resolve();\n })\n );\n })();\n }, [assetsById]);\n\n const shouldFetchPage = !isProductionMode && appId && pageId;\n const pageUrl = shouldFetchPage\n ? `/devices/apps/${appId}/node/${pageId}${\n useLocalFlag ? \"?useLocal=1\" : \"\"\n }`\n : null;\n\n logger.debug(\"Page data fetch\", { pageUrl, mode });\n\n const pageSWR = useSWR(pageUrl, fetcher, {\n suspense: isDynamicMode && !!pageUrl,\n fallbackData:\n isOptimisticMode && pageDefinition ? pageDefinition : undefined,\n revalidateOnMount: true,\n });\n\n const pageData =\n isProductionMode && pageDefinition ? { data: pageDefinition } : pageSWR;\n\n // Specific logic for Component Code fetching in Optimistic Mode\n // If optimistic, we have componentCode passed in (bundled). We use that initially.\n // BUT we also want to fetch the latest component code in background.\n // DynamicComponent uses 'componentCode' prop if present.\n // We need to fetch the code separately and override the prop passed to DynamicComponent.\n\n // We need to fetch the latest component JS in optimistic mode\n // The DynamicComponent currently logic: if (componentCode) loadAMDModule(code) else fetchDep(name).\n // We want: render with initial componentCode, BUT ALSO fetch new code, and when valid, switch to it.\n\n const [optimisticCode, setOptimisticCode] = useState<string | undefined>(\n componentCode\n );\n\n useEffect(() => {\n // If in optimistic mode, we want to fetch the latest code\n if (isOptimisticMode && appId && pageId && !isLocalMode()) {\n // We can reuse the logic from DynamicComponent/dynamicModules roughly, or just fetch text.\n // The URL logic is in dynamicModules.\n // Let's just rely on DynamicComponent to do the fetching if we pass undefined?\n // No, if we pass undefined it will suspend/show fallback. We want to show OLD code.\n\n // So we renders with `optimisticCode` (initially bundled).\n // We fire a background fetch. on success, updated `optimisticCode`.\n\n const fetchLatestCode = async () => {\n try {\n // Replicating URL logic from dynamicModules for remote mode\n const { CONST_COMPONENTS_CDN_URL } = await import(\"../../constants\");\n const cacheBuster = Math.round(Date.now() / 1000);\n const name = `${appId}/draft/components/${pageId}`;\n const url = `${CONST_COMPONENTS_CDN_URL}/${name}.js?cacheBuster=${cacheBuster}`;\n\n const resp = await fetch(url);\n if (resp.ok) {\n const text = await resp.text();\n // Only update if different?\n // Comparing huge strings might be expensive, but React state update checks equality anyway (ref).\n // Let's just set it.\n setOptimisticCode(text);\n logger.debug(\"Refreshed optimistic component code\");\n }\n } catch (e) {\n logger.warn(\"Failed to background refresh optimistic component\", e);\n }\n };\n\n fetchLatestCode();\n } else {\n // If prop changes (e.g. from HMR or parent), sync it\n setOptimisticCode(componentCode);\n }\n }, [componentCode, isOptimisticMode, appId, pageId]);\n\n // Use the calculated optimistic code or the passed prop depending on mode\n const effectiveComponentCode = isOptimisticMode\n ? optimisticCode\n : componentCode;\n\n logger.debug(\"Page data loaded\", {\n hasData: !!pageData?.data,\n dataType: typeof pageData?.data,\n });\n\n // Memoize the context object to prevent infinite re-renders\n // Only recreate when the actual data changes, not on every render\n const context = useMemo(() => {\n let clientData = pageData.data?.clientData;\n logger.debug(\"Building context\", {\n hasClientData: !!clientData,\n clientDataKeys: Object.keys(clientData || {}).length,\n });\n\n // Apply layout heuristics to fix common issues\n if (clientData) {\n patchPageData(clientData);\n }\n\n return {\n nodeData: undefined,\n // Allow overriding specific values by element id.\n // For now, this is used to override TextComponent content when the node has the PROP:TEXT_VAR tag.\n textOverridesById: data,\n // Support for encore:data:array tags - provide array data by component ID\n arrayDataById: data,\n // Support for standalone component data binding (encore:data tags at root level)\n rootData: data,\n };\n }, [pageData.data?.clientData, data]);\n\n // Manage repeating container controls (sliders, lists)\n const repeatingContainerContextValue = useRepeatingContainers(\n repeatingContainerControls\n );\n\n // Sync input groups from props to store\n useEffect(() => {\n if (inputGroups) {\n const setInputGroupValue = useEncoreState.getState().setInputGroupValue;\n Object.entries(inputGroups).forEach(([groupName, elementName]) => {\n setInputGroupValue(groupName, elementName);\n });\n }\n }, [inputGroups]);\n\n // Observe size changes of the dynamic content container and notify consumer\n useEffect(() => {\n if (!onSizeChange) return;\n const element = containerRef.current;\n if (!element) return;\n const notify = (entry: ResizeObserverEntry) => {\n const cr = entry.contentRect;\n onSizeChange({ width: cr.width, height: cr.height });\n };\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n notify(entry);\n }\n });\n // Emit initial size as soon as possible\n const rect = element.getBoundingClientRect();\n onSizeChange({ width: rect.width, height: rect.height });\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n }, [onSizeChange]);\n\n // Per-instance source override\n useEffect(() => {\n if (!source) return;\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n return () => {\n // Clear override when this instance unmounts\n setLocalModeOverride(null);\n };\n }, [source]);\n\n if (!pageId) {\n return (\n <div style={{ padding: \"30px\" }}>\n <div style={{ overflowY: \"auto\" }}>\n {(isLocalMode()\n ? // Local mode: app.json provides pages under app.data.pages\n ((app?.data as any)?.app?.data?.pages || []).map(\n (pg: any) => pg?.id\n )\n : app?.data?.app.pageIds || []\n ).map((p: string) => (\n <Link\n key={p}\n to={`/apps/${appId}/pages/${p}?noRedirect=${noRedirect}`}\n style={{\n fontSize: 20,\n display: \"block\",\n marginBottom: \"10px\",\n }}\n >\n {p}\n </Link>\n ))}\n </div>\n </div>\n );\n }\n return (\n <div\n ref={containerRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n <div\n ref={contentWrapperRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n }}\n >\n <Suspense fallback={fallback || <div />}>\n <EncoreContextProviders\n componentId={componentId}\n onAction={onAction}\n repeatingContainerContextValue={repeatingContainerContextValue}\n bindingContextValue={context}\n >\n <DynamicComponent\n name={`${appId}/draft/components/${pageId}`}\n fallback={fallback}\n reloadKey={reloadKey}\n componentCode={effectiveComponentCode}\n />\n </EncoreContextProviders>\n </Suspense>\n </div>\n </div>\n );\n};\n\nexport default EncoreApp;\n"],"names":["Link","to","children","style","props","navigate","useEncoreRouter","jsx","e","setAppSelector","state","setAppIdSelector","setPageIdSelector","assetsByIdSelector","EncoreApp","appId","pageId","componentId","fallback","onSizeChange","onContentSizeChange","onAction","data","source","repeatingContainerControls","inputGroups","baseURL","appDefinition","pageDefinition","componentCode","mode","logger","useEncoreState","setLocalModeOverride","noRedirect","setApp","setAppId","setPageId","assetsById","containerRef","useRef","contentWrapperRef","useEffect","element","notify","observer","reloadKey","setReloadKey","useState","handleUpdate","useCallback","prev","usePusherUpdates","isLocalMode","useLocalFlag","isProductionMode","isOptimisticMode","isDynamicMode","appUrl","appSWR","useSWR","fetcher","app","useFontLoader","id","resolve","img","pageUrl","pageSWR","pageData","optimisticCode","setOptimisticCode","CONST_COMPONENTS_CDN_URL","cacheBuster","name","url","resp","text","effectiveComponentCode","context","useMemo","clientData","patchPageData","repeatingContainerContextValue","useRepeatingContainers","setInputGroupValue","groupName","elementName","entry","cr","entries","rect","Suspense","EncoreContextProviders","DynamicComponent","pg","p"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare function runDownload(args: string[]): Promise<void>;
1
+ export declare function runDownload(appId: string, pageId: string, targetPath: string): Promise<void>;
2
2
  //# sourceMappingURL=download.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/download.ts"],"names":[],"mappings":"AA4GA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,iBAoB/C"}
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/download.ts"],"names":[],"mappings":"AA0FA,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,iBAenB"}
@@ -1,2 +1,2 @@
1
- export declare function runGenerate(args: string[]): Promise<void>;
1
+ export declare function runGenerate(appId: string, pageId: string | undefined, outputPath: string | undefined, mode: string): Promise<void>;
2
2
  //# sourceMappingURL=generate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AA8XA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,iBA8D/C"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AA8WA,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,MAAM,iBAyEb"}
@@ -43,7 +43,7 @@ export interface ComponentMetadata {
43
43
  * );
44
44
  * fs.writeFileSync("MyPage.tsx", code);
45
45
  */
46
- export declare function generateComponentCode(appId: string, pageId: string, componentName: string, sliders: SliderInfo[], standaloneComponents: ComponentInfo[], inputGroups: InputGroupInfo[], forms: FormInfo[], selectInputs: SelectInputInfo[], actionButtons: ActionButtonInfo[], isProduction?: boolean, pageMeta?: {
46
+ export declare function generateComponentCode(appId: string, pageId: string, componentName: string, sliders: SliderInfo[], standaloneComponents: ComponentInfo[], inputGroups: InputGroupInfo[], forms: FormInfo[], selectInputs: SelectInputInfo[], actionButtons: ActionButtonInfo[], mode?: string, pageMeta?: {
47
47
  width?: number;
48
48
  height?: number;
49
49
  aspectRatio?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/codegen/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,QAAQ,EACR,eAAe,EACf,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAOjB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,EACjC,YAAY,GAAE,OAAe,EAC7B,QAAQ,CAAC,EAAE;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,MAAM,CAiaR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,GAChC,MAAM,CAwVR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,GAChC,iBAAiB,CAiEnB"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/codegen/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,QAAQ,EACR,eAAe,EACf,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAOjB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,EACjC,IAAI,GAAE,MAAkB,EACxB,QAAQ,CAAC,EAAE;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,MAAM,CAoaR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,GAChC,MAAM,CAwVR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,EAAE,EACrB,oBAAoB,EAAE,aAAa,EAAE,EACrC,WAAW,EAAE,cAAc,EAAE,EAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,EAAE,eAAe,EAAE,EAC/B,aAAa,EAAE,gBAAgB,EAAE,GAChC,iBAAiB,CAiEnB"}
@@ -43,6 +43,8 @@ export type EncoreAppProps = {
43
43
  pageDefinition?: any;
44
44
  /** Provide component code directly instead of fetching (for offline/bundled deployments) */
45
45
  componentCode?: string;
46
+ /** Deployment mode: dynamic (default), optimistic, or production */
47
+ mode?: "dynamic" | "optimistic" | "production";
46
48
  };
47
49
  type Props = EncoreAppProps;
48
50
  /**
@@ -79,6 +81,6 @@ type Props = EncoreAppProps;
79
81
  * }}
80
82
  * />
81
83
  */
82
- declare const EncoreApp: ({ appId, pageId, componentId, fallback, onSizeChange, onContentSizeChange, onAction, data, source, repeatingContainerControls, inputGroups, baseURL, appDefinition, pageDefinition, componentCode, }: Props) => import("react/jsx-runtime").JSX.Element;
84
+ declare const EncoreApp: ({ appId, pageId, componentId, fallback, onSizeChange, onContentSizeChange, onAction, data, source, repeatingContainerControls, inputGroups, baseURL, appDefinition, pageDefinition, componentCode, mode, }: Props) => import("react/jsx-runtime").JSX.Element;
83
85
  export default EncoreApp;
84
86
  //# sourceMappingURL=EncoreApp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EncoreApp.d.ts","sourceRoot":"","sources":["../../../src/components/EncoreApp.tsx"],"names":[],"mappings":"AAIA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AA4B3E;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qDAAqD;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,oGAAoG;IACpG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC;IAC/C,sEAAsE;IACtE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,qFAAqF;IACrF,0BAA0B,CAAC,EAAE,MAAM,CACjC,MAAM,EACN;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CACnE,CAAC;IACF,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,6FAA6F;IAC7F,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,KAAK,GAAG,cAAc,CAAC;AAe5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,QAAA,MAAM,SAAS,GAAI,sMAgBhB,KAAK,4CA+QP,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"EncoreApp.d.ts","sourceRoot":"","sources":["../../../src/components/EncoreApp.tsx"],"names":[],"mappings":"AAIA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AA4B3E;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qDAAqD;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,oGAAoG;IACpG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC;IAC/C,sEAAsE;IACtE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,qFAAqF;IACrF,0BAA0B,CAAC,EAAE,MAAM,CACjC,MAAM,EACN;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CACnE,CAAC;IACF,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,6FAA6F;IAC7F,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,IAAI,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,YAAY,CAAC;CAChD,CAAC;AAEF,KAAK,KAAK,GAAG,cAAc,CAAC;AAe5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,QAAA,MAAM,SAAS,GAAI,4MAiBhB,KAAK,4CAsWP,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "0.1.31";
1
+ export declare const PACKAGE_VERSION = "0.1.34";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const o = "0.1.31";
1
+ const o = "0.1.34";
2
2
  export {
3
3
  o as PACKAGE_VERSION
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"0.1.31\";\n"],"names":["PACKAGE_VERSION"],"mappings":"AAAO,MAAMA,IAAkB;"}
1
+ {"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"0.1.34\";\n"],"names":["PACKAGE_VERSION"],"mappings":"AAAO,MAAMA,IAAkB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bravostudioai/react",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/src/index.d.ts",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "axios": "^1.13.0",
42
+ "commander": "^14.0.2",
42
43
  "dotenv": "^16.0.0",
43
44
  "pusher-js": "^8.4.0",
44
45
  "swr": "^2.3.6",
@@ -88,35 +88,15 @@ async function downloadEncoreFiles({
88
88
  console.log("\n✓ All files downloaded successfully!");
89
89
  }
90
90
 
91
- function printUsage() {
92
- console.log(`
93
- Usage: download-bravo.ts <appId> <pageId> <targetPath>
94
-
95
- Arguments:
96
- appId The Encore app ID
97
- pageId The Encore page ID
98
- targetPath Path where files should be saved
99
-
100
- Environment variables:
101
- APPS_SERVICE_URL Base URL for the apps service
102
-
103
- Example:
104
- download-bravo.ts my-app-id my-page-id ./bravo-files
105
- APPS_SERVICE_URL=https://api.example.com download-bravo.ts my-app-id my-page-id ./bravo-files
106
- `);
107
- }
108
-
109
- export async function runDownload(args: string[]) {
110
- if (args.length < 3 || args.includes("--help") || args.includes("-h")) {
111
- printUsage();
112
- process.exit(args.includes("--help") || args.includes("-h") ? 0 : 1);
113
- }
114
-
115
- const [appId, pageId, targetPath] = args;
116
-
91
+ export async function runDownload(
92
+ appId: string,
93
+ pageId: string,
94
+ targetPath: string
95
+ ) {
117
96
  if (!appId || !pageId || !targetPath) {
118
- console.error("Error: Missing required arguments");
119
- printUsage();
97
+ console.error(
98
+ "Error: Missing required arguments. Usage: download <appId> <pageId> <targetPath>"
99
+ );
120
100
  process.exit(1);
121
101
  }
122
102
 
@@ -84,17 +84,19 @@ async function generateWrapper({
84
84
  pageId,
85
85
  outputPath,
86
86
  cachedAppData,
87
- isProduction,
87
+ mode = "dynamic",
88
88
  usedNames,
89
89
  }: {
90
90
  appId: string;
91
91
  pageId: string;
92
92
  outputPath: string;
93
93
  cachedAppData?: any;
94
- isProduction?: boolean;
94
+ mode?: string;
95
95
  usedNames?: Set<string>;
96
96
  }) {
97
- console.log(`Generating wrapper for app: ${appId}, page: ${pageId}`);
97
+ console.log(
98
+ `Generating wrapper for app: ${appId}, page: ${pageId}, mode: ${mode}`
99
+ );
98
100
 
99
101
  // Determine final output path - we'll update it after we get app/page names
100
102
  let finalOutputPath = outputPath;
@@ -283,6 +285,7 @@ async function generateWrapper({
283
285
  }
284
286
 
285
287
  // Generate component code and README
288
+ const shouldBundleData = mode === "production" || mode === "optimistic";
286
289
  const componentCode = generateComponentCode(
287
290
  appId,
288
291
  pageId,
@@ -293,7 +296,7 @@ async function generateWrapper({
293
296
  forms,
294
297
  selectInputs,
295
298
  actionButtons,
296
- !!isProduction,
299
+ mode,
297
300
  {
298
301
  width: pageData.style?.width,
299
302
  height: pageData.style?.height,
@@ -324,7 +327,7 @@ async function generateWrapper({
324
327
  }
325
328
 
326
329
  // Write files
327
- if (isProduction) {
330
+ if (shouldBundleData) {
328
331
  const componentJsPath = join(tempDir, "component.js");
329
332
  let componentCodeContent = "";
330
333
  try {
@@ -361,46 +364,43 @@ async function generateWrapper({
361
364
  console.log(`✓ Generated wrapper component at: ${indexPath}`);
362
365
  }
363
366
 
364
- function printUsage() {
365
- console.log(`
366
- Usage: generate-wrapper.ts <appId> [pageId] <outputPath>
367
-
368
- Arguments:
369
- appId The Encore app ID
370
- pageId The Encore page ID (optional - if omitted, generates wrappers for ALL pages)
371
- outputPath Path where the generated TSX file(s) should be saved
372
- --production Generate for production (bundles data)
373
- --preseed Alias for --production (bundles data)
374
-
375
- Environment variables:
376
- APPS_SERVICE_URL Base URL for the apps service
377
-
378
- Example:
379
- generate-wrapper.ts 01KA964B1T6KCKSKCNMYSTKRKZ 01KA964B2F42MN4WGCYDTG1Y70 ./src/components/MyEncoreApp.tsx
380
- `);
381
- }
382
-
383
- export async function runGenerate(args: string[]) {
384
- const isProduction =
385
- args.includes("--production") || args.includes("--preseed");
386
- const cleanArgs = args.filter(
387
- (arg) => arg !== "--production" && arg !== "--preseed"
388
- );
389
-
390
- if (
391
- cleanArgs.length < 2 ||
392
- cleanArgs.includes("--help") ||
393
- cleanArgs.includes("-h")
394
- ) {
395
- printUsage();
396
- process.exit(
397
- cleanArgs.includes("--help") || cleanArgs.includes("-h") ? 0 : 1
367
+ export async function runGenerate(
368
+ appId: string,
369
+ pageId: string | undefined,
370
+ outputPath: string | undefined,
371
+ mode: string
372
+ ) {
373
+ if (!appId || (!pageId && !outputPath)) {
374
+ console.error(
375
+ "Error: Missing required arguments. Usage: generate <appId> [pageId] <outputPath>"
398
376
  );
377
+ process.exit(1);
399
378
  }
400
379
 
401
- if (cleanArgs.length === 2) {
402
- const [appId, outputPath] = cleanArgs;
380
+ // If outputPath is missing but we have 2 args logic from caller, handle it.
381
+ // actually caller logic:
382
+ // if 2 args (appId, pageId/output), caller puts output in output and pageId=undefined.
383
+ // so if we are here, we have appId and outputPath. pageId might be undefined.
384
+
385
+ // Wait, if I changed the caller to pass (appId, undefined, outputPath), I need to be sure.
386
+ // arguments: appId, pageId, outputPath.
387
+ // If user ran: generate app out
388
+ // caller: appId=app, pageId=out, output=undefined.
389
+ // caller logic: if (pageId && !output) -> output=pageId, pageId=undefined.
390
+ // caller calls: runGenerate(app, undefined, out, prod).
391
+
392
+ // If user ran: generate app page out
393
+ // caller: appId=app, pageId=page, output=out.
394
+ // caller calls: runGenerate(app, page, out, prod).
395
+
396
+ // So outputPath MUST be defined here effectively if the CLI usage was correct.
397
+ if (!outputPath) {
398
+ console.error("Error: Missing output path.");
399
+ process.exit(1);
400
+ }
403
401
 
402
+ // Default mode: Generate all pages
403
+ if (!pageId) {
404
404
  try {
405
405
  const { pages, appData } = await getAppPages(appId);
406
406
  if (pages.length === 0) {
@@ -417,7 +417,7 @@ export async function runGenerate(args: string[]) {
417
417
  pageId: page.id,
418
418
  outputPath,
419
419
  cachedAppData: appData,
420
- isProduction,
420
+ mode,
421
421
  usedNames,
422
422
  });
423
423
  } catch (error) {
@@ -434,10 +434,9 @@ export async function runGenerate(args: string[]) {
434
434
  return;
435
435
  }
436
436
 
437
- const [appId, pageId, outputPath] = cleanArgs;
438
-
437
+ // Single page generation
439
438
  try {
440
- await generateWrapper({ appId, pageId, outputPath, isProduction }); // Single page generation, no collision context needed unless we wanted global uniqueness but usually used for one-off
439
+ await generateWrapper({ appId, pageId, outputPath, mode });
441
440
  } catch (error) {
442
441
  console.error("\nError:", error instanceof Error ? error.message : error);
443
442
  process.exit(1);
package/src/cli/index.ts CHANGED
@@ -1,35 +1,52 @@
1
-
1
+ import { Command } from "commander";
2
2
  import { runDownload } from "./commands/download";
3
3
  import { runGenerate } from "./commands/generate";
4
4
 
5
- async function main() {
6
- const args = process.argv.slice(2);
7
-
8
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
9
- console.log(`
10
- Usage: encore-lib <command> [options]
11
-
12
- Commands:
13
- download <appId> <pageId> <targetPath> Download Encore page data
14
- generate <appId> [pageId] <outputPath> Generate React wrapper components
15
- `);
16
- process.exit(0);
17
- }
18
-
19
- const command = args[0];
20
- const commandArgs = args.slice(1);
21
-
22
- switch (command) {
23
- case "download":
24
- await runDownload(commandArgs);
25
- break;
26
- case "generate":
27
- await runGenerate(commandArgs);
28
- break;
29
- default:
30
- console.error(`Unknown command: ${command}`);
31
- process.exit(1);
32
- }
33
- }
34
-
35
- main();
5
+ const program = new Command();
6
+
7
+ program
8
+ .name("encore-lib")
9
+ .description("Encore CLI tools")
10
+ // We'll read version from package.json in build or hardcode for now to avoid async import issues in simple CLI
11
+ .version("0.1.32");
12
+
13
+ program
14
+ .command("download")
15
+ .description("Download Encore page data")
16
+ .argument("<appId>", "The Encore app ID")
17
+ .argument("<pageId>", "The Encore page ID")
18
+ .argument("<targetPath>", "Path where files should be saved")
19
+ .action(async (appId, pageId, targetPath) => {
20
+ await runDownload(appId, pageId, targetPath);
21
+ });
22
+
23
+ program
24
+ .command("generate")
25
+ .description("Generate React wrapper components")
26
+ .argument("<appId>", "The Encore app ID")
27
+ .argument("[pageId]", "The Encore page ID (optional)")
28
+ .argument(
29
+ "[outputPath]",
30
+ "Path where the generated TSX file(s) should be saved"
31
+ )
32
+ .option(
33
+ "--mode <mode>",
34
+ "Deployment mode: dynamic (default), optimistic, or production"
35
+ )
36
+ .action(async (appId, pageId, outputPath, options) => {
37
+ // Handle overload: generate <appId> <outputPath> vs generate <appId> <pageId> <outputPath>
38
+ let finalPageId = pageId;
39
+ let finalOutputPath = outputPath;
40
+
41
+ // If only 2 arguments provided (appId and pageId/outputPath), the second one is outputPath
42
+ if (pageId && !outputPath) {
43
+ finalOutputPath = pageId;
44
+ finalPageId = undefined;
45
+ }
46
+
47
+ const mode = options.mode || "dynamic";
48
+
49
+ await runGenerate(appId, finalPageId, finalOutputPath, mode);
50
+ });
51
+
52
+ program.parse();
@@ -67,7 +67,7 @@ export function generateComponentCode(
67
67
  forms: FormInfo[],
68
68
  selectInputs: SelectInputInfo[],
69
69
  actionButtons: ActionButtonInfo[],
70
- isProduction: boolean = false,
70
+ mode: string = "dynamic",
71
71
  pageMeta?: {
72
72
  width?: number;
73
73
  height?: number;
@@ -448,6 +448,8 @@ ${inputGroupMapping.join("\n")}
448
448
  const propsParameter = hasProps ? `props: ${componentName}Props` : "";
449
449
  const propsInterfaceSection = propsInterface ? `${propsInterface}\n\n` : "";
450
450
 
451
+ const shouldBundleData = mode === "production" || mode === "optimistic";
452
+
451
453
  return `/**
452
454
  * ${componentName}
453
455
  *
@@ -458,7 +460,7 @@ ${inputGroupMapping.join("\n")}
458
460
  import { EncoreApp${
459
461
  forms.length > 0 ? ", useEncoreState" : ""
460
462
  } } from "@bravostudioai/react";
461
- ${isProduction ? `import productionData from "./data.json";` : ""}
463
+ ${shouldBundleData ? `import productionData from "./data.json";` : ""}
462
464
 
463
465
  ${itemTypes ? `${itemTypes}\n\n` : ""}${
464
466
  formDataTypes ? `${formDataTypes}\n\n` : ""
@@ -468,8 +470,9 @@ ${inputGroupHandlers.length > 0 ? inputGroupHandlers.join("\n") : ""}
468
470
  <EncoreApp
469
471
  appId="${appId}"
470
472
  pageId="${pageId}"
473
+ mode="${mode}"
471
474
  ${
472
- isProduction
475
+ shouldBundleData
473
476
  ? `appDefinition={productionData.app}
474
477
  pageDefinition={productionData.page}
475
478
  componentCode={productionData.componentCode}`