@bravostudioai/react 0.1.28 → 0.1.29
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 +553 -0
- package/package.json +1 -1
- package/src/codegen/generator.ts +74 -0
- package/src/codegen/parser.ts +47 -707
- package/src/codegen/propQualification.ts +284 -0
- package/src/components/EncoreApp.tsx +92 -540
- package/src/components/EncoreContextProviders.tsx +60 -0
- package/src/hooks/useFontLoader.ts +84 -0
- package/src/hooks/usePusherUpdates.ts +14 -23
- package/src/hooks/useRepeatingContainers.ts +147 -0
- package/src/index.ts +4 -1
- package/src/lib/dataPatching.ts +78 -0
- package/src/lib/dynamicModules.ts +8 -9
- package/src/lib/fetcher.ts +2 -9
- package/src/lib/logger.ts +53 -0
- package/src/lib/moduleRegistry.ts +3 -1
- package/src/stores/useEncoreState.ts +62 -2
- package/src/version.ts +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context provider composition component
|
|
3
|
+
*
|
|
4
|
+
* Wraps children with all necessary Encore contexts in the correct order.
|
|
5
|
+
* This reduces nesting in the main EncoreApp component.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import EncoreComponentIdContext from "../contexts/EncoreComponentIdContext";
|
|
10
|
+
import EncoreActionContext, {
|
|
11
|
+
type EncoreActionPayload,
|
|
12
|
+
} from "../contexts/EncoreActionContext";
|
|
13
|
+
import EncoreRepeatingContainerContext from "../contexts/EncoreRepeatingContainerContext";
|
|
14
|
+
import EncoreBindingContext from "../contexts/EncoreBindingContext";
|
|
15
|
+
|
|
16
|
+
interface EncoreContextProvidersProps {
|
|
17
|
+
componentId?: string;
|
|
18
|
+
onAction?: (payload: EncoreActionPayload) => void | Promise<void>;
|
|
19
|
+
repeatingContainerContextValue: any;
|
|
20
|
+
bindingContextValue: any;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Composes all Encore context providers
|
|
26
|
+
*
|
|
27
|
+
* Provides a cleaner way to wrap content with multiple contexts
|
|
28
|
+
* instead of deeply nested Provider components.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <EncoreContextProviders
|
|
32
|
+
* componentId={componentId}
|
|
33
|
+
* onAction={onAction}
|
|
34
|
+
* repeatingContainerContextValue={containerContextValue}
|
|
35
|
+
* bindingContextValue={bindingContext}
|
|
36
|
+
* >
|
|
37
|
+
* <DynamicComponent />
|
|
38
|
+
* </EncoreContextProviders>
|
|
39
|
+
*/
|
|
40
|
+
export function EncoreContextProviders({
|
|
41
|
+
componentId,
|
|
42
|
+
onAction,
|
|
43
|
+
repeatingContainerContextValue,
|
|
44
|
+
bindingContextValue,
|
|
45
|
+
children,
|
|
46
|
+
}: EncoreContextProvidersProps) {
|
|
47
|
+
return (
|
|
48
|
+
<EncoreComponentIdContext.Provider value={{ componentId }}>
|
|
49
|
+
<EncoreActionContext.Provider value={{ onAction }}>
|
|
50
|
+
<EncoreRepeatingContainerContext.Provider
|
|
51
|
+
value={repeatingContainerContextValue}
|
|
52
|
+
>
|
|
53
|
+
<EncoreBindingContext.Provider value={bindingContextValue}>
|
|
54
|
+
{children}
|
|
55
|
+
</EncoreBindingContext.Provider>
|
|
56
|
+
</EncoreRepeatingContainerContext.Provider>
|
|
57
|
+
</EncoreActionContext.Provider>
|
|
58
|
+
</EncoreComponentIdContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for loading fonts from app definition
|
|
3
|
+
*
|
|
4
|
+
* Handles FontFace API loading of custom fonts declared in the app JSON.
|
|
5
|
+
* Skips fonts marked as broken and provides debug logging in development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect } from "react";
|
|
9
|
+
import logger from "../lib/logger";
|
|
10
|
+
|
|
11
|
+
interface EncoreFont {
|
|
12
|
+
id?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
broken?: boolean;
|
|
15
|
+
fontName?: { family?: string; postScriptName?: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AppDataWithFonts {
|
|
19
|
+
app?: { fonts?: EncoreFont[] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Loads fonts declared in app definition using the FontFace API
|
|
24
|
+
*
|
|
25
|
+
* @param appData - App definition containing fonts array
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const { data: appData } = useSWR(appUrl, fetcher);
|
|
29
|
+
* useFontLoader(appData);
|
|
30
|
+
*/
|
|
31
|
+
export function useFontLoader(appData: unknown) {
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const fonts: EncoreFont[] =
|
|
34
|
+
(appData as AppDataWithFonts | undefined)?.app?.fonts ?? [];
|
|
35
|
+
|
|
36
|
+
logger.debug('Font loading check', { fontCount: fonts?.length || 0 });
|
|
37
|
+
|
|
38
|
+
if (!fonts || fonts.length === 0) return;
|
|
39
|
+
if (typeof window === "undefined" || !("FontFace" in window)) return;
|
|
40
|
+
|
|
41
|
+
fonts.forEach((f) => {
|
|
42
|
+
try {
|
|
43
|
+
const family = f?.fontName?.family;
|
|
44
|
+
const url = f?.url;
|
|
45
|
+
const postScriptName = f?.fontName?.postScriptName;
|
|
46
|
+
|
|
47
|
+
if (!family || !url) return;
|
|
48
|
+
|
|
49
|
+
if (f.broken) {
|
|
50
|
+
logger.warn('Skipping broken font', {
|
|
51
|
+
font: postScriptName || family,
|
|
52
|
+
url
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const familyName = postScriptName || family;
|
|
58
|
+
const fontFace = new FontFace(familyName, `url(${url})`, {
|
|
59
|
+
weight: "100 900",
|
|
60
|
+
style: "normal",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
fontFace
|
|
64
|
+
.load()
|
|
65
|
+
.then((ff) => {
|
|
66
|
+
document.fonts.add(ff);
|
|
67
|
+
logger.debug('Font loaded', { familyName, url });
|
|
68
|
+
|
|
69
|
+
const isCheckPassed = document.fonts.check(`400 12px "${familyName}"`);
|
|
70
|
+
logger.debug('Font check result', { familyName, isCheckPassed });
|
|
71
|
+
})
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
logger.warn('Failed to load font', {
|
|
74
|
+
font: postScriptName || family,
|
|
75
|
+
url,
|
|
76
|
+
error: err.message
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.warn('Error processing font', err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}, [appData]);
|
|
84
|
+
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
|
|
4
4
|
import Pusher from "pusher-js";
|
|
5
5
|
import { mutate } from "swr";
|
|
6
6
|
import { clearModuleCache } from "../lib/moduleRegistry";
|
|
7
|
+
import logger from "../lib/logger";
|
|
7
8
|
|
|
8
9
|
type PusherConfig = {
|
|
9
10
|
key?: string;
|
|
@@ -51,7 +52,7 @@ export function usePusherUpdates({
|
|
|
51
52
|
|
|
52
53
|
// Skip if Pusher key is not configured
|
|
53
54
|
if (!config.key) {
|
|
54
|
-
|
|
55
|
+
logger.warn('Pusher not configured - real-time updates disabled');
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -63,9 +64,9 @@ export function usePusherUpdates({
|
|
|
63
64
|
|
|
64
65
|
pusherRef.current = pusher;
|
|
65
66
|
|
|
66
|
-
// Channel name format: appId
|
|
67
|
+
// Channel name format: appId (e.g., "01KA23JMNBQ2V9NR7K0VXKT5TF")
|
|
67
68
|
const channelName = `${appId}`;
|
|
68
|
-
|
|
69
|
+
logger.debug('Subscribing to Pusher channel', { channelName });
|
|
69
70
|
|
|
70
71
|
// Subscribe to the channel
|
|
71
72
|
const channel = pusher.subscribe(channelName);
|
|
@@ -73,24 +74,18 @@ export function usePusherUpdates({
|
|
|
73
74
|
|
|
74
75
|
// Handle subscription success
|
|
75
76
|
channel.bind("pusher:subscription_succeeded", () => {
|
|
76
|
-
|
|
77
|
-
`[Pusher] Successfully subscribed to channel: ${channelName}`
|
|
78
|
-
);
|
|
77
|
+
logger.debug('Pusher subscription succeeded', { channelName });
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
// Handle subscription error
|
|
82
81
|
channel.bind("pusher:subscription_error", (status: number) => {
|
|
83
|
-
|
|
84
|
-
`[Pusher] Subscription error for channel ${channelName}:`,
|
|
85
|
-
status
|
|
86
|
-
);
|
|
82
|
+
logger.error('Pusher subscription error', { channelName, status });
|
|
87
83
|
});
|
|
88
84
|
|
|
89
85
|
// Listen for update events
|
|
90
|
-
//
|
|
91
|
-
// Common event names: 'update', 'component-updated', 'app-updated', etc.
|
|
86
|
+
// Common event names: 'update', 'component-updated', 'app-updated'
|
|
92
87
|
const handleUpdate = (data: unknown) => {
|
|
93
|
-
|
|
88
|
+
logger.debug('Pusher update received', { channelName, data });
|
|
94
89
|
|
|
95
90
|
// Clear the module cache for the component to force reload
|
|
96
91
|
const componentName = `${appId}/draft/components/${pageId}`;
|
|
@@ -102,19 +97,15 @@ export function usePusherUpdates({
|
|
|
102
97
|
|
|
103
98
|
// Mutate both URLs to trigger refetch
|
|
104
99
|
mutate(appUrl).catch((err) => {
|
|
105
|
-
|
|
100
|
+
logger.error('Error invalidating app cache', err);
|
|
106
101
|
});
|
|
107
102
|
mutate(pageUrl).catch((err) => {
|
|
108
|
-
|
|
103
|
+
logger.error('Error invalidating page cache', err);
|
|
109
104
|
});
|
|
110
105
|
|
|
111
|
-
// Also invalidate URLs with useLocal query param
|
|
112
|
-
mutate(`${appUrl}?useLocal=1`).catch(() => {
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
mutate(`${pageUrl}?useLocal=1`).catch(() => {
|
|
116
|
-
// Ignore errors for optional URLs
|
|
117
|
-
});
|
|
106
|
+
// Also invalidate URLs with useLocal query param
|
|
107
|
+
mutate(`${appUrl}?useLocal=1`).catch(() => {});
|
|
108
|
+
mutate(`${pageUrl}?useLocal=1`).catch(() => {});
|
|
118
109
|
|
|
119
110
|
// Call the onUpdate callback if provided
|
|
120
111
|
onUpdate?.();
|
|
@@ -128,7 +119,7 @@ export function usePusherUpdates({
|
|
|
128
119
|
|
|
129
120
|
// Cleanup function
|
|
130
121
|
return () => {
|
|
131
|
-
|
|
122
|
+
logger.debug('Unsubscribing from Pusher channel', { channelName });
|
|
132
123
|
|
|
133
124
|
// Unbind all event handlers
|
|
134
125
|
if (channelRef.current) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for managing repeating container controls
|
|
3
|
+
*
|
|
4
|
+
* Handles slider/list index management and synchronization between
|
|
5
|
+
* parent-provided controls and internal component state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback, useMemo, useEffect } from "react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import type { RepeatingContainerControl } from "../contexts/EncoreRepeatingContainerContext";
|
|
11
|
+
|
|
12
|
+
interface ContainerControlProps {
|
|
13
|
+
currentIndex?: number;
|
|
14
|
+
onIndexChange?: (index: number) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Manages repeating container (slider/list) controls
|
|
19
|
+
*
|
|
20
|
+
* @param externalControls - Optional controls passed from parent component
|
|
21
|
+
* @returns Context value for EncoreRepeatingContainerContext
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const containerContextValue = useRepeatingContainers(repeatingContainerControls);
|
|
25
|
+
* <EncoreRepeatingContainerContext.Provider value={containerContextValue}>
|
|
26
|
+
*/
|
|
27
|
+
export function useRepeatingContainers(
|
|
28
|
+
externalControls?: Record<string, ContainerControlProps>
|
|
29
|
+
) {
|
|
30
|
+
const [containerControls, setContainerControls] = useState<
|
|
31
|
+
Map<string, RepeatingContainerControl>
|
|
32
|
+
>(new Map());
|
|
33
|
+
|
|
34
|
+
const [controlPropsMap, setControlPropsMap] = useState<
|
|
35
|
+
Map<string, ContainerControlProps>
|
|
36
|
+
>(new Map());
|
|
37
|
+
|
|
38
|
+
// Update control props from external prop
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (externalControls) {
|
|
41
|
+
setControlPropsMap((prev) => {
|
|
42
|
+
// Check if content actually changed to avoid unnecessary updates
|
|
43
|
+
let changed = false;
|
|
44
|
+
if (prev.size !== Object.keys(externalControls).length) {
|
|
45
|
+
changed = true;
|
|
46
|
+
} else {
|
|
47
|
+
for (const [id, props] of Object.entries(externalControls)) {
|
|
48
|
+
const prevProps = prev.get(id);
|
|
49
|
+
if (!prevProps) {
|
|
50
|
+
changed = true;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
prevProps.currentIndex !== props.currentIndex ||
|
|
55
|
+
prevProps.onIndexChange !== props.onIndexChange
|
|
56
|
+
) {
|
|
57
|
+
changed = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!changed) return prev;
|
|
64
|
+
|
|
65
|
+
const newMap = new Map();
|
|
66
|
+
Object.entries(externalControls).forEach(([id, props]) => {
|
|
67
|
+
newMap.set(id, props);
|
|
68
|
+
});
|
|
69
|
+
return newMap;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}, [externalControls]);
|
|
73
|
+
|
|
74
|
+
const registerContainer = useCallback(
|
|
75
|
+
(id: string, control: RepeatingContainerControl) => {
|
|
76
|
+
setContainerControls((prev) => {
|
|
77
|
+
const next = new Map(prev);
|
|
78
|
+
next.set(id, control);
|
|
79
|
+
return next;
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
[]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const unregisterContainer = useCallback((id: string) => {
|
|
86
|
+
setContainerControls((prev) => {
|
|
87
|
+
const next = new Map(prev);
|
|
88
|
+
next.delete(id);
|
|
89
|
+
return next;
|
|
90
|
+
});
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const getControl = useCallback(
|
|
94
|
+
(id: string) => {
|
|
95
|
+
return containerControls.get(id);
|
|
96
|
+
},
|
|
97
|
+
[containerControls]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const setControlProps = useCallback(
|
|
101
|
+
(
|
|
102
|
+
id: string,
|
|
103
|
+
props:
|
|
104
|
+
| ContainerControlProps
|
|
105
|
+
| ((prev: ContainerControlProps) => ContainerControlProps)
|
|
106
|
+
) => {
|
|
107
|
+
setControlPropsMap((prev) => {
|
|
108
|
+
const next = new Map(prev);
|
|
109
|
+
const current = next.get(id) || {};
|
|
110
|
+
const newProps = typeof props === "function" ? props(current) : props;
|
|
111
|
+
next.set(id, newProps);
|
|
112
|
+
return next;
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
[]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const getControlProps = useCallback(
|
|
119
|
+
(id: string) => {
|
|
120
|
+
return controlPropsMap.get(id);
|
|
121
|
+
},
|
|
122
|
+
[controlPropsMap]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Create context value - changes when controlPropsMap changes
|
|
126
|
+
const contextValue = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
registerContainer,
|
|
129
|
+
unregisterContainer,
|
|
130
|
+
getControl,
|
|
131
|
+
setControlProps,
|
|
132
|
+
getControlProps,
|
|
133
|
+
// Include size to trigger re-renders when it changes
|
|
134
|
+
_propsVersion: controlPropsMap.size,
|
|
135
|
+
}),
|
|
136
|
+
[
|
|
137
|
+
registerContainer,
|
|
138
|
+
unregisterContainer,
|
|
139
|
+
getControl,
|
|
140
|
+
setControlProps,
|
|
141
|
+
getControlProps,
|
|
142
|
+
controlPropsMap.size,
|
|
143
|
+
]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return contextValue;
|
|
147
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
console.log("Encore Lib Loaded");
|
|
2
1
|
import EncoreApp from "./components/EncoreApp";
|
|
3
2
|
import EncoreErrorBoundary from "./components/EncoreErrorBoundary";
|
|
4
3
|
import EncoreLoadingFallback from "./components/EncoreLoadingFallback";
|
|
@@ -17,4 +16,8 @@ export {
|
|
|
17
16
|
PACKAGE_VERSION,
|
|
18
17
|
};
|
|
19
18
|
|
|
19
|
+
// Export types for TypeScript consumers
|
|
20
|
+
export type { EncoreAppProps } from "./components/EncoreApp";
|
|
21
|
+
export type { EncoreActionPayload } from "./contexts/EncoreActionContext";
|
|
22
|
+
|
|
20
23
|
export * from "./codegen";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data patching utilities for fixing layout and component issues
|
|
3
|
+
*
|
|
4
|
+
* Applies heuristic-based fixes to page data to correct common layout problems
|
|
5
|
+
* that occur during the Figma-to-Encore conversion process.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import logger from "./logger";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively patches page data to fix layout issues
|
|
12
|
+
*
|
|
13
|
+
* Current heuristics:
|
|
14
|
+
* 1. Horizontal layout detection - If children widths sum to ~100% or ~375px,
|
|
15
|
+
* force HORIZONTAL layout mode to prevent vertical stacking
|
|
16
|
+
*
|
|
17
|
+
* @param clientData - The client data object from page definition
|
|
18
|
+
* @returns Patched client data (mutates in place)
|
|
19
|
+
*/
|
|
20
|
+
export function patchPageData(clientData: any): any {
|
|
21
|
+
if (!clientData) return clientData;
|
|
22
|
+
|
|
23
|
+
const patchNode = (node: any) => {
|
|
24
|
+
if (!node || typeof node !== "object") return;
|
|
25
|
+
|
|
26
|
+
// Heuristic: If children widths sum to ~100% or ~375px, force HORIZONTAL layout
|
|
27
|
+
if (
|
|
28
|
+
node.children &&
|
|
29
|
+
Array.isArray(node.children) &&
|
|
30
|
+
node.children.length > 1
|
|
31
|
+
) {
|
|
32
|
+
let totalWidth = 0;
|
|
33
|
+
let childrenWithWidth = 0;
|
|
34
|
+
|
|
35
|
+
node.children.forEach((child: any) => {
|
|
36
|
+
if (child.style?.width) {
|
|
37
|
+
// Width might be percentage (sums to 100) or pixels (sums to 375)
|
|
38
|
+
totalWidth += child.style.width;
|
|
39
|
+
childrenWithWidth++;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Check if widths sum to full width (100% or 375px)
|
|
44
|
+
const isFullWidthRow =
|
|
45
|
+
(Math.abs(totalWidth - 100) < 1 || Math.abs(totalWidth - 375) < 5) &&
|
|
46
|
+
childrenWithWidth >= 2;
|
|
47
|
+
|
|
48
|
+
if (isFullWidthRow) {
|
|
49
|
+
if (!node.style) node.style = {};
|
|
50
|
+
if (!node.style.layout) node.style.layout = {};
|
|
51
|
+
|
|
52
|
+
// Only apply if mode is missing or undefined
|
|
53
|
+
if (!node.style.layout.mode) {
|
|
54
|
+
logger.debug('Layout patch applied', {
|
|
55
|
+
nodeId: node.id,
|
|
56
|
+
childrenCount: childrenWithWidth,
|
|
57
|
+
totalWidth
|
|
58
|
+
});
|
|
59
|
+
node.style.layout.mode = "HORIZONTAL";
|
|
60
|
+
node.style.layout.primaryAxisAlignItems = "flex-start";
|
|
61
|
+
node.style.layout.counterAxisAlignItems = "flex-start";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Recurse through children
|
|
67
|
+
if (node.children) {
|
|
68
|
+
if (Array.isArray(node.children)) {
|
|
69
|
+
node.children.forEach(patchNode);
|
|
70
|
+
} else {
|
|
71
|
+
patchNode(node.children);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
patchNode(clientData);
|
|
77
|
+
return clientData;
|
|
78
|
+
}
|
|
@@ -2,6 +2,7 @@ import packages from "./packages";
|
|
|
2
2
|
import { isLocalMode } from "./localMode";
|
|
3
3
|
import { CONST_COMPONENTS_CDN_URL } from "../../constants";
|
|
4
4
|
import { registerModule, getModuleExports, haveModule } from "./moduleRegistry";
|
|
5
|
+
import logger from "./logger";
|
|
5
6
|
|
|
6
7
|
// Initialize registry with default packages
|
|
7
8
|
Object.keys(packages).forEach((p) => {
|
|
@@ -85,7 +86,7 @@ export function loadAMDModule(name: string, code: string) {
|
|
|
85
86
|
try {
|
|
86
87
|
${patchedCode}\n//# sourceURL=dynamic-module://${name}.js
|
|
87
88
|
} catch (error) {
|
|
88
|
-
console.error('[Module
|
|
89
|
+
console.error('[encore-lib] Module evaluation error', {
|
|
89
90
|
moduleName: '${name}',
|
|
90
91
|
error: error.message,
|
|
91
92
|
stack: error.stack,
|
|
@@ -97,18 +98,16 @@ export function loadAMDModule(name: string, code: string) {
|
|
|
97
98
|
`
|
|
98
99
|
);
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
logger.debug('Loading AMD module', { name });
|
|
101
102
|
wrapper(define);
|
|
102
|
-
|
|
103
|
+
logger.debug('Module loaded successfully', { name });
|
|
103
104
|
} catch (e: any) {
|
|
104
|
-
|
|
105
|
-
console.error("[Module Loading Failed]", {
|
|
105
|
+
logger.error('Module loading failed', {
|
|
106
106
|
moduleName: name,
|
|
107
|
-
error: e,
|
|
108
107
|
errorType: e.constructor.name,
|
|
109
108
|
message: e.message,
|
|
110
109
|
stack: e.stack,
|
|
111
|
-
|
|
110
|
+
codePreview: code.slice(0, 200) + (code.length > 200 ? "..." : "")
|
|
112
111
|
});
|
|
113
112
|
reject(e);
|
|
114
113
|
}
|
|
@@ -117,7 +116,7 @@ export function loadAMDModule(name: string, code: string) {
|
|
|
117
116
|
|
|
118
117
|
export async function fetchDep(name: string) {
|
|
119
118
|
// Local mode: map Encore component name to local JSX/JS under /flex-layout
|
|
120
|
-
|
|
119
|
+
logger.debug('Fetching dependency', { name, isLocal: isLocalMode() });
|
|
121
120
|
if (isLocalMode()) {
|
|
122
121
|
// Expecting `${appId}/draft/components/${pageId}`
|
|
123
122
|
const m = name.match(/^([^/]+)\/draft\/components\/([^/]+)$/);
|
|
@@ -160,7 +159,7 @@ export async function fetchDep(name: string) {
|
|
|
160
159
|
// Remote mode: use AMD loader
|
|
161
160
|
const cacheBuster = Math.round(Date.now() / 1000);
|
|
162
161
|
const url = `${CONST_COMPONENTS_CDN_URL}/${name}.js?cacheBuster=${cacheBuster}`;
|
|
163
|
-
|
|
162
|
+
logger.debug('Fetching remote component', { url });
|
|
164
163
|
const text = await fetch(url).then(async (a) => {
|
|
165
164
|
if (!a.ok) {
|
|
166
165
|
if (a.status === 403) {
|
package/src/lib/fetcher.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { isLocalMode } from "./localMode";
|
|
3
3
|
import useEncoreState from "../stores/useEncoreState";
|
|
4
|
+
import logger from "./logger";
|
|
4
5
|
|
|
5
6
|
// Get baseURL from store at runtime instead of build time
|
|
6
7
|
const getAppsServiceUrl = () => {
|
|
@@ -86,19 +87,11 @@ const fetcher = (url: string) => {
|
|
|
86
87
|
// Get baseURL at runtime from store
|
|
87
88
|
const appsServiceUrl = getAppsServiceUrl();
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
"[Fetcher] Requesting:",
|
|
91
|
-
url,
|
|
92
|
-
"BaseURL:",
|
|
93
|
-
appsServiceUrl,
|
|
94
|
-
"Headers:",
|
|
95
|
-
{ "x-app-clientrendered": "disabled" }
|
|
96
|
-
);
|
|
90
|
+
logger.debug('Fetching from Encore service', { url, baseURL: appsServiceUrl });
|
|
97
91
|
|
|
98
92
|
return axios({
|
|
99
93
|
baseURL: appsServiceUrl,
|
|
100
94
|
url,
|
|
101
|
-
// headers: { "x-app-clientrendered": "true" },
|
|
102
95
|
}).then((res) => res.data);
|
|
103
96
|
};
|
|
104
97
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utility for encore-lib
|
|
3
|
+
*
|
|
4
|
+
* Provides conditional logging based on environment.
|
|
5
|
+
* Debug logs only appear in development mode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const isDev = typeof import.meta !== 'undefined'
|
|
9
|
+
? import.meta.env?.DEV || import.meta.env?.MODE === 'development'
|
|
10
|
+
: process.env.NODE_ENV === 'development';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Logger instance with environment-aware methods
|
|
14
|
+
*/
|
|
15
|
+
export const logger = {
|
|
16
|
+
/**
|
|
17
|
+
* Debug-level logging (only in development)
|
|
18
|
+
* Use for detailed diagnostic information
|
|
19
|
+
*/
|
|
20
|
+
debug: (...args: any[]) => {
|
|
21
|
+
if (isDev) {
|
|
22
|
+
console.debug('[encore-lib]', ...args);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Info-level logging (only in development)
|
|
28
|
+
* Use for general informational messages
|
|
29
|
+
*/
|
|
30
|
+
info: (...args: any[]) => {
|
|
31
|
+
if (isDev) {
|
|
32
|
+
console.info('[encore-lib]', ...args);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Warning-level logging (always shown)
|
|
38
|
+
* Use for recoverable issues that need attention
|
|
39
|
+
*/
|
|
40
|
+
warn: (...args: any[]) => {
|
|
41
|
+
console.warn('[encore-lib]', ...args);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Error-level logging (always shown)
|
|
46
|
+
* Use for errors and exceptions
|
|
47
|
+
*/
|
|
48
|
+
error: (...args: any[]) => {
|
|
49
|
+
console.error('[encore-lib]', ...args);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default logger;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import logger from "./logger";
|
|
2
|
+
|
|
1
3
|
const registry: Record<string, any> = {};
|
|
2
4
|
|
|
3
5
|
export function registerModule(name: string, module: any) {
|
|
@@ -19,6 +21,6 @@ export function haveModule(name: string): boolean {
|
|
|
19
21
|
export function clearModuleCache(name: string): void {
|
|
20
22
|
if (name in registry) {
|
|
21
23
|
delete registry[name];
|
|
22
|
-
|
|
24
|
+
logger.debug('Module cache cleared', { name });
|
|
23
25
|
}
|
|
24
26
|
}
|