catalyst-core 0.2.0-beta.3 → 0.3.0-beta.2
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/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/globals.d.ts +7 -0
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidProject/app/build.gradle.kts +45 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
- package/dist/native/androidProject/settings.gradle.kts +10 -0
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +132 -1
- package/dist/native/bridge/hooks/useAI.js +94 -0
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
- package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
- package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
- package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
- package/dist/native/bridge/perf/core/constants.js +103 -20
- package/dist/native/bridge/perf/core/measure.js +179 -30
- package/dist/native/bridge/perf/core/session.js +89 -23
- package/dist/native/bridge/perf/core/store.js +554 -1
- package/dist/native/bridge/perf/core/summary.js +174 -1
- package/dist/native/bridge/perf/core/traceExport.js +240 -1
- package/dist/native/bridge/perf/core/waterfalls.js +146 -1
- package/dist/native/bridge/perf/index.js +359 -38
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +406 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +296 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +450 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +498 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +176 -17
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +40 -8
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +161 -37
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns data used by the Head component for page rendering.
|
|
3
|
+
*/
|
|
4
|
+
export const renderStart = ({
|
|
5
|
+
inlineCss,
|
|
6
|
+
deferredRouteInlineCss,
|
|
7
|
+
jsScripts,
|
|
8
|
+
criticalPreloadLinks,
|
|
9
|
+
deferredPreloadLinks,
|
|
10
|
+
metaTags,
|
|
11
|
+
isBot,
|
|
12
|
+
fetcherData,
|
|
13
|
+
}) => {
|
|
14
|
+
const { IS_DEV_COMMAND, WEBPACK_DEV_SERVER_HOSTNAME, WEBPACK_DEV_SERVER_PORT } = process.env
|
|
15
|
+
|
|
16
|
+
let publicAssetPath = `${process.env.PUBLIC_STATIC_ASSET_URL}${process.env.PUBLIC_STATIC_ASSET_PATH}`
|
|
17
|
+
|
|
18
|
+
if (JSON.parse(IS_DEV_COMMAND)) {
|
|
19
|
+
publicAssetPath = `http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/assets/`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
inlineCss,
|
|
24
|
+
deferredRouteInlineCss,
|
|
25
|
+
jsScripts,
|
|
26
|
+
criticalPreloadLinks,
|
|
27
|
+
deferredPreloadLinks,
|
|
28
|
+
metaTags,
|
|
29
|
+
isBot,
|
|
30
|
+
publicAssetPath,
|
|
31
|
+
fetcherData,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns data used by the Body component for page rendering.
|
|
37
|
+
*/
|
|
38
|
+
export const renderEnd = (initialState = {}, res, jsx, errorCode, fetcherData) => {
|
|
39
|
+
return {
|
|
40
|
+
initialState,
|
|
41
|
+
firstFoldCss: "",
|
|
42
|
+
firstFoldJS: "",
|
|
43
|
+
jsx,
|
|
44
|
+
errorCode,
|
|
45
|
+
fetcherData,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getUserAgentDetails } from "./userAgentUtil.js"
|
|
2
|
+
|
|
3
|
+
// Loaded once at startup when OTEL is enabled; null otherwise.
|
|
4
|
+
let _context, _trace, _IS_BOT_KEY
|
|
5
|
+
if (process.env.OTEL_ENABLE === true) {
|
|
6
|
+
try {
|
|
7
|
+
const { context, trace } = await import("@opentelemetry/api")
|
|
8
|
+
const { IS_BOT_KEY } = await import("../../otel.js")
|
|
9
|
+
_context = context
|
|
10
|
+
_trace = trace
|
|
11
|
+
_IS_BOT_KEY = IS_BOT_KEY
|
|
12
|
+
} catch {
|
|
13
|
+
// otel packages not installed — context injection skipped
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function botDetectionMiddleware(req, res, next) {
|
|
18
|
+
const ua = req.headers["user-agent"] || ""
|
|
19
|
+
const { googleBot, aiBot, statusCakeBot } = getUserAgentDetails(ua)
|
|
20
|
+
const isBot = !!(googleBot || aiBot || statusCakeBot)
|
|
21
|
+
res.locals.is_bot = isBot
|
|
22
|
+
|
|
23
|
+
if (_context && _IS_BOT_KEY) {
|
|
24
|
+
_trace?.getActiveSpan()?.setAttribute("http.response.is_bot", isBot)
|
|
25
|
+
_context.with(_context.active().setValue(_IS_BOT_KEY, isBot), next)
|
|
26
|
+
} else {
|
|
27
|
+
next()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "module"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared helper to bridge ESM server entry files with legacy CommonJS helpers.
|
|
5
|
+
*
|
|
6
|
+
* Usage (in an ESM server file):
|
|
7
|
+
* import { cjsRequire } from "catalyst-core/server/cjsRequire"
|
|
8
|
+
* const legacyHelper = cjsRequire("./legacy/helper")
|
|
9
|
+
*/
|
|
10
|
+
export const cjsRequire = createRequire(import.meta.url)
|
|
@@ -1,15 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import parser from "ua-parser-js"
|
|
2
|
+
|
|
3
|
+
const googleBots = {
|
|
4
|
+
"APIs-Google": "APIs-Google",
|
|
5
|
+
AdSense: "Mediapartners-Google",
|
|
6
|
+
"AdsBot Mobile Web Android": "AdsBot-Google-Mobile",
|
|
7
|
+
"AdsBot Mobile Web": "AdsBot-Google-Mobile",
|
|
8
|
+
AdsBot: "AdsBot-Google",
|
|
9
|
+
"Mobile AdSense": "Mediapartners-Google",
|
|
10
|
+
"Mobile Apps Android": "AdsBot-Google-Mobile-Apps",
|
|
11
|
+
Feedfetcher: "FeedFetcher-Google",
|
|
12
|
+
"Google Read Aloud": "Google-Read-Aloud",
|
|
13
|
+
"Duplex on the Web": "DuplexWeb-Google",
|
|
14
|
+
"Google Favicon": "Google Favicon",
|
|
15
|
+
"Web Light": "googleweblight",
|
|
16
|
+
"Amp crawler": "Google-AMPHTM",
|
|
17
|
+
Googlebot: "Googlebot",
|
|
18
|
+
"Google Inspection Tool": "Google-InspectionTool",
|
|
19
|
+
"Google Agent": "Google-Agent",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// AI Crawler bots - for improved SEO with AI search engines
|
|
23
|
+
const aiCrawlerBots = {
|
|
24
|
+
// OpenAI/ChatGPT bots
|
|
25
|
+
GPTBot: "GPTBot",
|
|
26
|
+
"ChatGPT-User": "ChatGPT-User",
|
|
27
|
+
"OAI-SearchBot": "OAI-SearchBot",
|
|
28
|
+
|
|
29
|
+
// Anthropic/Claude bots
|
|
30
|
+
ClaudeBot: "ClaudeBot",
|
|
31
|
+
"Claude-User": "Claude-User",
|
|
32
|
+
"Claude-SearchBot": "Claude-SearchBot",
|
|
33
|
+
|
|
34
|
+
// Perplexity bots
|
|
35
|
+
PerplexityBot: "PerplexityBot",
|
|
36
|
+
"Perplexity-User": "Perplexity-User",
|
|
37
|
+
|
|
38
|
+
// Microsoft Bing bot (also powers Bing Chat/Copilot)
|
|
39
|
+
Bingbot: "bingbot",
|
|
40
|
+
|
|
41
|
+
// Meta Crawler bot
|
|
42
|
+
MetaBot: "meta-externalagent",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// StatusCake synthetic monitoring user agents.
|
|
46
|
+
// STATUS_CAKE_USER_AGENT_MOBILE matches the mobile pagespeed monitor — kept as a
|
|
47
|
+
// named export so apps that compare UAs directly (mweb's document.js, App/index.js)
|
|
48
|
+
// can import the same constant catalyst uses for its bot detection.
|
|
49
|
+
export const STATUS_CAKE_USER_AGENT_MOBILE = "StatusCake_Pagespeed_Indev"
|
|
50
|
+
const statusCakeBots = {
|
|
51
|
+
"StatusCake Pagespeed Mobile": STATUS_CAKE_USER_AGENT_MOBILE,
|
|
52
|
+
StatusCake: "StatusCake",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
8
56
|
* check if user agent contains bot data
|
|
9
57
|
* @param {string} ua - user agent
|
|
10
58
|
* @return {string|null}
|
|
11
|
-
*/
|
|
59
|
+
*/
|
|
60
|
+
const getGoogleBot = (ua) => {
|
|
61
|
+
for (let key in googleBots) {
|
|
62
|
+
if (ua.includes(googleBots[key])) return key
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const getAICrawlerBot = (ua) => {
|
|
68
|
+
for (let key in aiCrawlerBots) {
|
|
69
|
+
if (ua.includes(aiCrawlerBots[key])) return key
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const getStatusCakeBot = (ua) => {
|
|
75
|
+
for (let key in statusCakeBots) {
|
|
76
|
+
if (ua.includes(statusCakeBots[key])) return key
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
12
82
|
* returns object which contains google bot and user-agent info
|
|
13
83
|
* @param {string} ua - user agent
|
|
14
84
|
* @return {object} - returns user agent
|
|
15
|
-
*/
|
|
85
|
+
*/
|
|
86
|
+
export const getUserAgentDetails = (ua) => {
|
|
87
|
+
const agentDetails = parser(ua)
|
|
88
|
+
const googleBot = getGoogleBot(ua)
|
|
89
|
+
const aiBot = getAICrawlerBot(ua)
|
|
90
|
+
const statusCakeBot = getStatusCakeBot(ua)
|
|
91
|
+
|
|
92
|
+
return { ...agentDetails, googleBot, aiBot, statusCakeBot }
|
|
93
|
+
}
|
|
@@ -1 +1,164 @@
|
|
|
1
|
-
|
|
1
|
+
import pc from "picocolors"
|
|
2
|
+
|
|
3
|
+
const handleError = (e) => {
|
|
4
|
+
console.log(pc.red("Failed to start server: "), e)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const validatePreInitServer = (fn) => {
|
|
8
|
+
try {
|
|
9
|
+
if (!fn) throw new Error("preServerInit named function should be defined in server/index.js")
|
|
10
|
+
if (typeof fn !== "function")
|
|
11
|
+
throw new Error("preServerInit should be function present in server/index.js")
|
|
12
|
+
return true
|
|
13
|
+
} catch (e) {
|
|
14
|
+
handleError(e)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const validateMiddleware = (fn) => {
|
|
19
|
+
try {
|
|
20
|
+
if (!fn) throw new Error("addMiddlewares named function not found in server/server.js")
|
|
21
|
+
if (typeof fn !== "function")
|
|
22
|
+
throw new Error("addMiddlewares should be function present in server/server.js")
|
|
23
|
+
return true
|
|
24
|
+
} catch (e) {
|
|
25
|
+
handleError(e)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const validateReducerFunction = (fn) => {
|
|
30
|
+
try {
|
|
31
|
+
if (!fn) throw new Error("reducer not found in src/js/containers/App/reducer")
|
|
32
|
+
if (typeof fn !== "function")
|
|
33
|
+
throw new Error("reducer should present in src/js/containers/App/reducer")
|
|
34
|
+
return true
|
|
35
|
+
} catch (e) {
|
|
36
|
+
handleError(e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const validateConfigFile = (obj) => {
|
|
41
|
+
try {
|
|
42
|
+
if (!obj) throw new Error("config not found in config folder")
|
|
43
|
+
if (typeof obj !== "object")
|
|
44
|
+
throw new Error(
|
|
45
|
+
"config object should be exported from config folder inside your project root directory"
|
|
46
|
+
)
|
|
47
|
+
if (typeof obj === "object") {
|
|
48
|
+
const requiredConfigKeys = {
|
|
49
|
+
NODE_SERVER_HOSTNAME: "",
|
|
50
|
+
NODE_SERVER_PORT: "",
|
|
51
|
+
WEBPACK_DEV_SERVER_HOSTNAME: "",
|
|
52
|
+
WEBPACK_DEV_SERVER_PORT: "",
|
|
53
|
+
BUILD_OUTPUT_PATH: "",
|
|
54
|
+
PUBLIC_STATIC_ASSET_PATH: "",
|
|
55
|
+
PUBLIC_STATIC_ASSET_URL: "",
|
|
56
|
+
CLIENT_ENV_VARIABLES: [],
|
|
57
|
+
ANALYZE_BUNDLE: "",
|
|
58
|
+
}
|
|
59
|
+
for (let key in requiredConfigKeys) {
|
|
60
|
+
if (!(key in obj)) throw new Error(`${key} key not found inside config.json`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
} catch (e) {
|
|
65
|
+
handleError(e)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const validatePackageJson = (obj) => {
|
|
70
|
+
try {
|
|
71
|
+
if (!obj) throw new Error("package.json not found in the project")
|
|
72
|
+
if (typeof obj !== "object")
|
|
73
|
+
throw new Error("package.json should be defined in project root directory")
|
|
74
|
+
return true
|
|
75
|
+
} catch (e) {
|
|
76
|
+
handleError(e)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const validateModuleAlias = (obj) => {
|
|
81
|
+
try {
|
|
82
|
+
if (!obj) throw new Error("moduleAliases not found in package.json file present in root directory.")
|
|
83
|
+
if (typeof obj !== "object")
|
|
84
|
+
throw new Error("moduleAliases named object should be exported from package.json")
|
|
85
|
+
if (typeof obj === "object") {
|
|
86
|
+
const requiredModuleAliases = {
|
|
87
|
+
"@api": "api.js",
|
|
88
|
+
"@containers": "src/js/containers",
|
|
89
|
+
"@server": "server",
|
|
90
|
+
"@config": "config",
|
|
91
|
+
"@css": "src/static/css",
|
|
92
|
+
"@routes": "src/js/routes/",
|
|
93
|
+
}
|
|
94
|
+
for (let key in requiredModuleAliases) {
|
|
95
|
+
if (key.includes("catalyst"))
|
|
96
|
+
throw new Error(`Catalyst keyword is restricted for defining aliases`)
|
|
97
|
+
if (!(key in obj)) throw new Error(`${key} module alias not defined inside package.json`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true
|
|
101
|
+
} catch (e) {
|
|
102
|
+
handleError(e)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const validateConfigureStore = (fn) => {
|
|
107
|
+
try {
|
|
108
|
+
if (!fn) throw new Error("configureStore not found in file src/js/store/index.js")
|
|
109
|
+
if (typeof fn !== "function")
|
|
110
|
+
throw new Error("configureStore should be function exported from src/js/store/index.js")
|
|
111
|
+
return true
|
|
112
|
+
} catch (e) {
|
|
113
|
+
handleError(e)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const validateGetRoutes = (fn) => {
|
|
118
|
+
try {
|
|
119
|
+
if (!fn) throw new Error("getRoutes not found in file src/js/routes/utils.js")
|
|
120
|
+
if (typeof fn !== "function")
|
|
121
|
+
throw new Error("getRoutes should be function exported from src/js/routers/index.js")
|
|
122
|
+
return true
|
|
123
|
+
} catch (e) {
|
|
124
|
+
handleError(e)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const validateCustomDocument = (fn) => {
|
|
129
|
+
try {
|
|
130
|
+
if (!fn) throw new Error("document not found in file server/document.js")
|
|
131
|
+
if (typeof fn !== "function")
|
|
132
|
+
throw new Error("document should be a react component exported from server/document.js")
|
|
133
|
+
return true
|
|
134
|
+
} catch (e) {
|
|
135
|
+
handleError(e)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Safely call a function, catching and logging any errors.
|
|
141
|
+
* Used for user-defined hooks (onRouteMatch, onFetcherError, etc.)
|
|
142
|
+
* that should never crash the SSR pipeline.
|
|
143
|
+
*/
|
|
144
|
+
const safeCall = (fn, ...args) => {
|
|
145
|
+
if (typeof fn !== "function") return
|
|
146
|
+
try {
|
|
147
|
+
return fn(...args)
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error("Error in user hook:", e)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
validateConfigFile,
|
|
155
|
+
validateConfigureStore,
|
|
156
|
+
validateCustomDocument,
|
|
157
|
+
validateGetRoutes,
|
|
158
|
+
validatePackageJson,
|
|
159
|
+
validateReducerFunction,
|
|
160
|
+
validateModuleAlias,
|
|
161
|
+
validatePreInitServer,
|
|
162
|
+
validateMiddleware,
|
|
163
|
+
safeCall,
|
|
164
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
const FastRefresh = () => {
|
|
4
|
+
return (
|
|
5
|
+
<script
|
|
6
|
+
type="module"
|
|
7
|
+
// eslint-disable-next-line risxss/catch-potential-xss-react
|
|
8
|
+
dangerouslySetInnerHTML={{
|
|
9
|
+
__html: `
|
|
10
|
+
import { injectIntoGlobalHook } from "/@react-refresh";
|
|
11
|
+
injectIntoGlobalHook(window);
|
|
12
|
+
window.$RefreshReg$ = () => {};
|
|
13
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
14
|
+
`,
|
|
15
|
+
}}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default FastRefresh
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { readFileSync, existsSync, statSync } from "fs"
|
|
3
|
+
import { fileURLToPath } from "url"
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
+
const __dirname = path.dirname(__filename)
|
|
7
|
+
|
|
8
|
+
// Cache for loaded aliases
|
|
9
|
+
let aliasCache = null
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load and resolve module aliases from both the consuming app and catalyst-core,
|
|
13
|
+
* using the same algorithm as node-loader.mjs.
|
|
14
|
+
*/
|
|
15
|
+
function loadAliases() {
|
|
16
|
+
if (aliasCache) {
|
|
17
|
+
return aliasCache
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const appRoot = process.env.src_path || process.cwd()
|
|
21
|
+
const appPackageJsonConfig = path.resolve(appRoot, "package.json")
|
|
22
|
+
const catalystPackageJsonConfig = path.resolve(__dirname, "../../package.json")
|
|
23
|
+
|
|
24
|
+
let appModuleAliases = {}
|
|
25
|
+
let catalystModuleAliases = {}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const appPackageJson = JSON.parse(readFileSync(appPackageJsonConfig, "utf8"))
|
|
29
|
+
appModuleAliases = appPackageJson._moduleAliases || {}
|
|
30
|
+
} catch {
|
|
31
|
+
// Silently fail if app package.json doesn't exist or is invalid
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const catalystPackageJson = JSON.parse(readFileSync(catalystPackageJsonConfig, "utf8"))
|
|
36
|
+
catalystModuleAliases = catalystPackageJson._moduleAliases || {}
|
|
37
|
+
} catch {
|
|
38
|
+
// Silently fail if catalyst package.json doesn't exist or is invalid
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Application aliases take precedence over catalyst-core aliases
|
|
42
|
+
const allAliases = { ...catalystModuleAliases, ...appModuleAliases }
|
|
43
|
+
|
|
44
|
+
aliasCache = {}
|
|
45
|
+
for (const [alias, aliasPath] of Object.entries(allAliases)) {
|
|
46
|
+
if (aliasPath && typeof aliasPath === "string") {
|
|
47
|
+
try {
|
|
48
|
+
const resolvedPath = path.resolve(appRoot, ...aliasPath.split("/"))
|
|
49
|
+
aliasCache[alias] = resolvedPath
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(`Failed to resolve alias ${alias}:`, error.message)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return aliasCache
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Try to resolve a path by applying common JS/TS/ESM extensions and index fallbacks,
|
|
61
|
+
* matching the same resolution order as node-loader.mjs.
|
|
62
|
+
*/
|
|
63
|
+
function resolveWithExtensions(basePath) {
|
|
64
|
+
if (existsSync(basePath) && statSync(basePath).isFile()) {
|
|
65
|
+
return basePath
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const extensions = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".scss"]
|
|
69
|
+
|
|
70
|
+
for (const ext of extensions) {
|
|
71
|
+
const candidate = `${basePath}${ext}`
|
|
72
|
+
if (existsSync(candidate)) {
|
|
73
|
+
return candidate
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const currentExt = path.extname(basePath)
|
|
78
|
+
if (currentExt) {
|
|
79
|
+
const withoutExt = basePath.slice(0, -currentExt.length)
|
|
80
|
+
for (const ext of extensions) {
|
|
81
|
+
if (ext === currentExt) continue
|
|
82
|
+
const candidate = `${withoutExt}${ext}`
|
|
83
|
+
if (existsSync(candidate)) {
|
|
84
|
+
return candidate
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const ext of extensions) {
|
|
90
|
+
const candidate = path.join(basePath, `index${ext}`)
|
|
91
|
+
if (existsSync(candidate)) {
|
|
92
|
+
return candidate
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve import path to an absolute file path using the same alias algorithm as node-loader.mjs.
|
|
101
|
+
* Falls back to relative resolution when no alias matches.
|
|
102
|
+
*/
|
|
103
|
+
function resolveToAbsolutePath(importPath, importerId) {
|
|
104
|
+
const aliases = loadAliases()
|
|
105
|
+
|
|
106
|
+
// 1. Try alias resolution (same logic as node-loader resolveAlias)
|
|
107
|
+
for (const [alias, aliasPath] of Object.entries(aliases)) {
|
|
108
|
+
if (importPath === alias || importPath.startsWith(alias + "/")) {
|
|
109
|
+
const remainingPath = importPath === alias ? "" : importPath.slice(alias.length + 1)
|
|
110
|
+
const rawResolved = path.join(aliasPath, remainingPath)
|
|
111
|
+
const finalResolved = resolveWithExtensions(rawResolved)
|
|
112
|
+
if (finalResolved) {
|
|
113
|
+
return path.resolve(finalResolved)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 2. Relative imports — resolve from the importer's directory
|
|
119
|
+
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
120
|
+
const importerDir = path.dirname(importerId)
|
|
121
|
+
const absolutePath = path.resolve(importerDir, importPath)
|
|
122
|
+
const resolved = resolveWithExtensions(absolutePath)
|
|
123
|
+
if (resolved) {
|
|
124
|
+
return resolved
|
|
125
|
+
}
|
|
126
|
+
return absolutePath
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolve import path to a manifest key
|
|
134
|
+
* @param {string} importPath - The import path to resolve
|
|
135
|
+
* @param {string} importerId - The file ID that imports this path
|
|
136
|
+
* @returns {string} - The resolved manifest key (relative to manifest location: build/.vite/)
|
|
137
|
+
*/
|
|
138
|
+
function resolveImportPath(importPath, importerId) {
|
|
139
|
+
try {
|
|
140
|
+
const absolutePath = resolveToAbsolutePath(importPath, importerId)
|
|
141
|
+
|
|
142
|
+
if (absolutePath && process.env.src_path) {
|
|
143
|
+
// Manifest is located at: build/.vite/manifest.json
|
|
144
|
+
const manifestDir = path.join(process.env.src_path, "build", ".vite")
|
|
145
|
+
const relativePath = path.relative(manifestDir, absolutePath)
|
|
146
|
+
return relativePath.replace(/\\/g, "/")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fallback: strip leading ./ and return as-is
|
|
150
|
+
return importPath.replace(/^\.\//, "")
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn(`Error resolving import path ${importPath}:`, error.message)
|
|
153
|
+
return importPath.replace(/^\.\//, "")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Walk an AST node and invoke the visitor for every node.
|
|
159
|
+
*/
|
|
160
|
+
function walk(node, visitor) {
|
|
161
|
+
if (!node || typeof node !== "object") return
|
|
162
|
+
|
|
163
|
+
visitor(node)
|
|
164
|
+
|
|
165
|
+
for (const key of Object.keys(node)) {
|
|
166
|
+
const child = node[key]
|
|
167
|
+
if (Array.isArray(child)) {
|
|
168
|
+
for (const item of child) {
|
|
169
|
+
if (item && typeof item.type === "string") {
|
|
170
|
+
walk(item, visitor)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else if (child && typeof child.type === "string") {
|
|
174
|
+
walk(child, visitor)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Vite plugin to add cacheKey to split calls with manifest keys.
|
|
181
|
+
* Uses Rollup's this.parse() for robust AST-based detection instead of regex.
|
|
182
|
+
*/
|
|
183
|
+
export function injectCacheKeyPlugin() {
|
|
184
|
+
return {
|
|
185
|
+
name: "split-cache-key",
|
|
186
|
+
|
|
187
|
+
async transform(code, id) {
|
|
188
|
+
if (!/\.(js|jsx|ts|tsx)$/.test(id)) return null
|
|
189
|
+
if (id.includes("node_modules")) return null
|
|
190
|
+
if (!code.includes("split")) return null
|
|
191
|
+
|
|
192
|
+
let ast
|
|
193
|
+
try {
|
|
194
|
+
ast = this.parse(code)
|
|
195
|
+
} catch {
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Collect split() call sites that need a cacheKey injected.
|
|
200
|
+
// Process in reverse source order so earlier indices stay valid after splicing.
|
|
201
|
+
const sites = []
|
|
202
|
+
|
|
203
|
+
walk(ast, (node) => {
|
|
204
|
+
// Match: split(<arrow>, <options?>, <cacheKey?>)
|
|
205
|
+
// Also supports: split(<arrow>, <options?>, <rootOptions>, <cacheKey?>)
|
|
206
|
+
if (node.type !== "CallExpression") return
|
|
207
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "split") return
|
|
208
|
+
|
|
209
|
+
const args = node.arguments
|
|
210
|
+
if (args.length === 0) return
|
|
211
|
+
|
|
212
|
+
// First arg must be a function that returns import("...")
|
|
213
|
+
// Supports both: () => import("...") and function() { return import("...") }
|
|
214
|
+
const loader = args[0]
|
|
215
|
+
let importExpr
|
|
216
|
+
|
|
217
|
+
if (loader.type === "ArrowFunctionExpression") {
|
|
218
|
+
// () => import("...")
|
|
219
|
+
importExpr = loader.body
|
|
220
|
+
} else if (loader.type === "FunctionExpression") {
|
|
221
|
+
// function() { return import("...") }
|
|
222
|
+
const stmts = loader.body.body
|
|
223
|
+
if (stmts.length !== 1 || stmts[0].type !== "ReturnStatement") return
|
|
224
|
+
importExpr = stmts[0].argument
|
|
225
|
+
} else {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!importExpr || importExpr.type !== "ImportExpression") return
|
|
230
|
+
if (importExpr.source.type !== "Literal" || typeof importExpr.source.value !== "string")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
const importPath = importExpr.source.value
|
|
234
|
+
|
|
235
|
+
// Already has a cacheKey (3rd or 4th arg) -> skip
|
|
236
|
+
if (args.length >= 4) {
|
|
237
|
+
const fourth = args[3]
|
|
238
|
+
if (fourth?.type === "Literal" && typeof fourth.value === "string") return
|
|
239
|
+
}
|
|
240
|
+
if (args.length >= 3) {
|
|
241
|
+
const third = args[2]
|
|
242
|
+
if (third?.type === "Literal" && typeof third.value === "string") return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sites.push({
|
|
246
|
+
importPath,
|
|
247
|
+
callEnd: node.end, // position of the closing )
|
|
248
|
+
argCount: args.length,
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if (sites.length === 0) return null
|
|
253
|
+
|
|
254
|
+
// Sort descending by position so splicing doesn't shift earlier indices
|
|
255
|
+
sites.sort((a, b) => b.callEnd - a.callEnd)
|
|
256
|
+
|
|
257
|
+
let transformed = code
|
|
258
|
+
for (const { importPath, callEnd, argCount } of sites) {
|
|
259
|
+
let manifestKey = resolveImportPath(importPath, id)
|
|
260
|
+
if (manifestKey && !manifestKey.endsWith(".js")) {
|
|
261
|
+
manifestKey = manifestKey + ".js"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Insert just before the closing ) of the split() call
|
|
265
|
+
let insertion = `, "${manifestKey}"`
|
|
266
|
+
if (argCount === 1) {
|
|
267
|
+
insertion = `, {}, "${manifestKey}"`
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
transformed = transformed.slice(0, callEnd - 1) + insertion + transformed.slice(callEnd - 1)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { code: transformed, map: null }
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync } from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { pathToFileURL } from "url"
|
|
4
|
+
|
|
5
|
+
const emptyConfig = {
|
|
6
|
+
ssrPlugins: [],
|
|
7
|
+
clientPlugins: [],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function loadCustomViteConfig() {
|
|
11
|
+
const configNames = ["buildConfig.js", "webpackConfig.js"]
|
|
12
|
+
const configPath = configNames
|
|
13
|
+
.map((configName) => path.join(process.env.src_path, configName))
|
|
14
|
+
.find((candidatePath) => existsSync(candidatePath))
|
|
15
|
+
|
|
16
|
+
if (!configPath) {
|
|
17
|
+
return emptyConfig
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const configModule = await import(pathToFileURL(configPath).href)
|
|
21
|
+
return configModule.default || configModule
|
|
22
|
+
}
|