catalyst-core 0.1.0-canary.9 → 0.2.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/README.md +1 -1
- package/bin/catalyst.js +8 -1
- package/changelog.md +6 -0
- package/dist/native/androidProject/app/build.gradle.kts +34 -8
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +270 -78
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +34 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +149 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/OfflineCacheService.kt +374 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +156 -39
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +7 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +6 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +253 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +16 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +7 -3
- package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +7 -2
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +139 -0
- package/dist/native/androidSetup.js +1 -0
- package/dist/native/bridge/WebBridge.js +5 -5
- package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
- package/dist/native/bridge/hooks/index.js +1 -0
- package/dist/native/bridge/hooks/useCamera.js +9 -0
- package/dist/native/bridge/hooks/useDataProtection.js +2 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
- package/dist/native/bridge/hooks/useFilePicker.js +2 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
- package/dist/native/bridge/hooks/useIntent.js +1 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
- package/dist/native/bridge/hooks/useNotification.js +2 -0
- package/dist/native/bridge/hooks/useSafeArea.js +2 -0
- package/dist/native/bridge/hooks/useVideoStream.js +6 -0
- package/dist/native/bridge/hooks/utils.js +1 -0
- package/dist/native/bridge/hooks.js +1 -137
- package/dist/native/bridge/useBaseHook.js +12 -7
- package/dist/native/bridge/utils/NativeBridge.js +41 -6
- package/dist/native/buildAndroid/assets.js +52 -0
- package/dist/native/buildAndroid/build.js +19 -0
- package/dist/native/buildAndroid/config.js +1 -0
- package/dist/native/buildAndroid/index.js +1 -0
- package/dist/native/buildAppAndroid.js +2 -150
- package/dist/native/buildAppIos.js +2 -312
- package/dist/native/buildIos/assets.js +8 -0
- package/dist/native/buildIos/build.js +40 -0
- package/dist/native/buildIos/config.js +71 -0
- package/dist/native/buildIos/index.js +6 -0
- package/dist/native/buildIos/plugins.js +45 -0
- package/dist/native/internalPluginUtils.js +7 -0
- package/dist/native/iosnativeWebView/Package.resolved +36 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
- package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +5 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +6 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +364 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +39 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineCacheService.swift +683 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineURLSchemeHandler.swift +94 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +19 -6
- package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +227 -4
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +14 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +53 -8
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +88 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +30 -11
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +1 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +4 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
- package/dist/native/plugin-bridge/PluginBridge.js +1 -0
- package/dist/native/pluginComposerAndroid.js +28 -0
- package/dist/native/pluginComposerIos.js +16 -0
- package/dist/native/renameAndroidProject.js +5 -5
- package/dist/native/setupEmulatorIos.js +6 -4
- package/dist/native/setupServer.js +2 -3
- package/dist/native/utils.js +4 -1
- package/dist/offline/catalyst-sw.js +1 -0
- package/dist/offline/registerServiceWorker.js +1 -0
- package/dist/otel.js +70 -2
- package/dist/router/ClientRouter.js +1 -1
- package/dist/scripts/build.js +1 -1
- package/dist/scripts/cleanCache.js +1 -0
- package/dist/scripts/devBuild.js +1 -1
- package/dist/scripts/devServe.js +1 -1
- package/dist/scripts/generateOfflineManifest.js +1 -0
- package/dist/scripts/loadEnvironmentVariables.js +1 -2
- package/dist/scripts/plugins.js +1 -0
- package/dist/scripts/registerAliases.js +6 -2
- package/dist/scripts/scriptUtils.js +2 -1
- package/dist/scripts/serve.js +2 -1
- package/dist/scripts/start.js +7 -2
- package/dist/server/expressServer.js +4 -8
- package/dist/server/renderer/document/Body.js +4 -4
- package/dist/server/renderer/document/Head.js +2 -2
- package/dist/server/renderer/extract.js +31 -27
- package/dist/server/renderer/handler.js +47 -24
- package/dist/server/renderer/index.js +1 -1
- package/dist/server/startServer.js +57 -7
- package/dist/server/utils/userAgentUtil.js +9 -3
- package/dist/server/utils/validator.js +1 -1
- package/dist/webpack/babel-plugins/remove-client.plugin.js +2 -2
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +2 -2
- package/dist/webpack/babel.config.client.js +1 -1
- package/dist/webpack/babel.config.ssr.js +1 -1
- package/dist/webpack/base.babel.js +7 -8
- package/dist/webpack/development.client.babel.js +7 -16
- package/dist/webpack/production.client.babel.js +1 -1
- package/dist/webpack/production.ssr.babel.js +5 -1
- package/mcp_v2/knowledge-base.json +206 -100
- package/mcp_v2/lib/helpers.js +42 -8
- package/mcp_v2/lib/seed.js +88 -0
- package/mcp_v2/mcp.js +150 -4
- package/mcp_v2/setup.js +30 -193
- package/mcp_v2/tools/build.js +1 -1
- package/mcp_v2/tools/config.js +16 -4
- package/mcp_v2/tools/conversion.js +11 -11
- package/mcp_v2/tools/github.js +1117 -0
- package/mcp_v2/tools/knowledge.js +16 -3
- package/mcp_v2/tools/sync.js +179 -10
- package/mcp_v2/tools/tasks.js +13 -4
- package/package.json +20 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");function toRoutePath(basePath,routePath,isIndex){if(isIndex||routePath===undefined||routePath===null||routePath===""){return basePath||"/";}const nextPath=String(routePath).startsWith("/")?routePath:`${basePath}/${routePath}`;return`/${nextPath}`.replace(/\/+/g,"/").replace(/\/$/,"")||"/";}function routeRegex(pattern,exact=false){if(pattern==="*"||pattern==="/*")return"^/.*$";if(pattern==="/")return exact?"^/$":"^/.*$";const parts=pattern.split("/").filter(Boolean);const regex=parts.map(part=>{if(part==="*")return".*";if(part.startsWith(":"))return"[^/]+";return part.replace(/[|\\{}()[\]^$+?.]/g,"\\$&");}).join("/");return exact?`^/${regex}$`:`^/${regex}(?:/.*)?$`;}function collectOfflineRoutes(routes,basePath="",parentOffline=false,result=[]){for(const route of Array.isArray(routes)?routes:[]){if(!route||typeof route!=="object")continue;const offline=Object.prototype.hasOwnProperty.call(route,"offline")?route.offline===true:parentOffline;const isIndexRoute=route.index===true||route.path==="";const pattern=toRoutePath(basePath,route.path,isIndexRoute);const regex=routeRegex(pattern,isIndexRoute);if(offline&&!result.some(item=>item.pattern===pattern&&item.regex===regex)){result.push({pattern,regex});}collectOfflineRoutes(route.children,pattern,offline,result);}return result;}function loadAppRoutes(){require("./loadScriptsBeforeServerStarts.js");const emptyModule=()=>null;[".css",".scss",".sass",".less",".png",".jpg",".jpeg",".gif",".svg",".webp"].forEach(ext=>{require.extensions[ext]=emptyModule;});require("@babel/register")({babelrc:false,extensions:[".js",".jsx",".ts",".tsx"],ignore:[/node_modules/],presets:[["@babel/preset-env",{targets:{node:"current"}}],["@babel/preset-react",{runtime:"automatic"}],["@babel/preset-typescript",{isTSX:true,allExtensions:true}]],plugins:["@loadable/babel-plugin"]});const{getRoutes}=require(path.join(process.env.src_path,"src/js/routes/utils.js"));return typeof getRoutes==="function"?getRoutes():[];}function copyIfExists(source,destination){if(fs.existsSync(source))fs.copyFileSync(source,destination);}function main(){const appConfig=require(path.join(process.env.src_path,"config/config.json"));const publicDir=path.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH||appConfig.BUILD_OUTPUT_PATH,"public");const routes=collectOfflineRoutes(loadAppRoutes());fs.mkdirSync(publicDir,{recursive:true});fs.writeFileSync(path.join(publicDir,"catalyst-offline-manifest.json"),`${JSON.stringify({schemaVersion:1,buildId:Date.now().toString(36),routes},null,2)}\n`);copyIfExists(path.join(process.env.src_path,"public/offline.html"),path.join(publicDir,"offline.html"));fs.copyFileSync(path.join(__dirname,"../offline/catalyst-sw.js"),path.join(publicDir,"catalyst-sw.js"));console.log(`Catalyst offline manifest generated: ${routes.length} route(s)`);}main();
|
|
@@ -3,6 +3,5 @@
|
|
|
3
3
|
*/const loadEnvironmentVariables=()=>{if(validateConfigFile(appConfig)){for(let k in appConfig){// below code provides support for object handling present in config.
|
|
4
4
|
// However, for usage on client the client logic in define plugin needs to be checked and updated
|
|
5
5
|
process.env[k]=typeof appConfig[k]==="object"?JSON.stringify(appConfig[k]):appConfig[k];}}// Load Sentry configuration if it exists
|
|
6
|
-
try{const sentryConfig=require("@catalyst/template/config/sentry.config.json");process.env.SENTRY_CONFIG=JSON.stringify(sentryConfig);}catch(error){//
|
|
7
|
-
// console.warn("Warning: Failed to load sentry.config.json")
|
|
6
|
+
try{const sentryConfig=require("@catalyst/template/config/sentry.config.json");process.env.SENTRY_CONFIG=JSON.stringify(sentryConfig);}catch(error){// Sentry config is optional
|
|
8
7
|
}};var _default=exports.default=loadEnvironmentVariables;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const readline=require("node:readline");const{bold,cyan,dim,green,red,yellow}=require("picocolors");const{discoverInternalPlugins,resolveInternalPluginsRoot}=require("../native/internalPluginUtils.js");const CONFIG_PATH=path.join(process.cwd(),"config","config.json");const TICK="x";function detectIndent(rawConfig){const indentMatch=rawConfig.match(/\n([ \t]+)"/);return indentMatch?indentMatch[1]:" ";}function readJsonFile(filePath,label){if(!fs.existsSync(filePath)){throw new Error(`${label} not found at ${filePath}`);}const raw=fs.readFileSync(filePath,"utf8");try{return{raw,json:JSON.parse(raw)};}catch(error){throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);}}function ensureObject(value,label){if(value==null){return{};}if(typeof value!=="object"||Array.isArray(value)){throw new Error(`${label} must be an object`);}return value;}function splitPluginConfig(pluginConfig,plugins){const selectorToPlugin=new Map();const matchesByConfigKey=new Map();const staleToggleKeys=[];plugins.forEach(plugin=>{selectorToPlugin.set(plugin.configKey,plugin);selectorToPlugin.set(plugin.id,plugin);});for(const[key,value]of Object.entries(pluginConfig)){if(typeof value!=="boolean"){throw new Error(`WEBVIEW_CONFIG.plugins.${key} must be boolean`);}const plugin=selectorToPlugin.get(key);if(!plugin){staleToggleKeys.push(key);continue;}const matches=matchesByConfigKey.get(plugin.configKey)||[];matches.push({key,value,plugin});matchesByConfigKey.set(plugin.configKey,matches);}const knownToggles={};for(const plugin of plugins){const matches=matchesByConfigKey.get(plugin.configKey)||[];const uniqueValues=[...new Set(matches.map(entry=>entry.value))];if(uniqueValues.length>1){throw new Error(`Conflicting toggle values for plugin '${plugin.id}' across keys: ${matches.map(entry=>`${entry.key}=${entry.value}`).join(", ")}`);}if(matches.length>0){knownToggles[plugin.configKey]=matches[0].value;}}return{knownToggles,staleToggleKeys};}function createSession({configPath,rawConfig,config,plugins,pluginsRoot}){const webviewConfig=config.WEBVIEW_CONFIG==null?{}:ensureObject(config.WEBVIEW_CONFIG,"WEBVIEW_CONFIG");const pluginConfig=ensureObject(webviewConfig.plugins,"WEBVIEW_CONFIG.plugins");const{knownToggles,staleToggleKeys}=splitPluginConfig(pluginConfig,plugins);return{configPath,config,indent:detectIndent(rawConfig),pluginsRoot,plugins:plugins.map(plugin=>({...plugin,enabled:knownToggles[plugin.configKey]??false})),staleToggleKeys,notice:"",selectedIndex:0};}function buildPersistedPluginConfig(session){const nextConfig={};for(const plugin of session.plugins){nextConfig[plugin.configKey]=plugin.enabled;}return nextConfig;}function renderSession(session){if(typeof console.clear==="function"){console.clear();}console.log(bold("Catalyst Internal Plugins"));console.log(dim(`Config: ${session.configPath}`));console.log(dim(`Catalog: ${session.pluginsRoot}`));console.log("");if(session.staleToggleKeys.length>0){console.log(yellow(`Stale plugin toggle keys will be removed on save: ${session.staleToggleKeys.join(", ")}`));console.log("");}session.plugins.forEach((plugin,index)=>{const isSelected=index===session.selectedIndex;const pointer=isSelected?cyan(">"):" ";const checkbox=plugin.enabled?green(`[${TICK}]`):dim("[ ]");const title=isSelected?bold(plugin.displayName):plugin.displayName;const meta=dim(`(${plugin.configKey})`);console.log(`${pointer} ${checkbox} ${title} ${meta}`);console.log(` ${plugin.description}`);console.log(dim(` category: ${plugin.category} | platforms: ${plugin.platforms.join(", ")}`));console.log(dim(` id: ${plugin.id}`));console.log("");});console.log("Controls: up/down move, space or enter toggle, a enable all, n disable all, s save, q quit");if(session.notice){console.log("");console.log(session.notice);}}function saveSession(session){if(session.config.WEBVIEW_CONFIG==null){session.config.WEBVIEW_CONFIG={};}session.config.WEBVIEW_CONFIG.plugins=buildPersistedPluginConfig(session);fs.writeFileSync(session.configPath,`${JSON.stringify(session.config,null,session.indent)}\n`);}async function runInteractiveSession(session){if(!process.stdin.isTTY||!process.stdout.isTTY){renderSession(session);console.log("");console.log(yellow("Interactive mode requires a TTY. Re-run this command in a terminal session."));return;}const rl=readline.createInterface({input:process.stdin,output:process.stdout});readline.emitKeypressEvents(process.stdin,rl);if(typeof process.stdin.setRawMode==="function"){process.stdin.setRawMode(true);}try{renderSession(session);await new Promise(resolve=>{const finish=()=>{process.stdin.off("keypress",onKeypress);resolve();};const onKeypress=(_,key={})=>{if(key.ctrl&&key.name==="c"){session.notice=yellow("Exited without saving.");renderSession(session);finish();return;}if(key.name==="up"){session.selectedIndex=session.selectedIndex===0?session.plugins.length-1:session.selectedIndex-1;session.notice="";renderSession(session);return;}if(key.name==="down"){session.selectedIndex=session.selectedIndex===session.plugins.length-1?0:session.selectedIndex+1;session.notice="";renderSession(session);return;}if(key.name==="space"||key.name==="return"){const plugin=session.plugins[session.selectedIndex];plugin.enabled=!plugin.enabled;session.notice=green(`${plugin.displayName} is now ${plugin.enabled?"enabled":"disabled"} in the pending config.`);renderSession(session);return;}if(key.name==="a"){session.plugins.forEach(plugin=>{plugin.enabled=true;});session.notice=green("Enabled all discovered plugins.");renderSession(session);return;}if(key.name==="n"){session.plugins.forEach(plugin=>{plugin.enabled=false;});session.notice=yellow("Disabled all discovered plugins.");renderSession(session);return;}if(key.name==="s"){saveSession(session);session.notice=session.staleToggleKeys.length>0?green(`Saved plugin toggles to config/config.json and removed stale keys: ${session.staleToggleKeys.join(", ")}.`):green("Saved plugin toggles to config/config.json.");renderSession(session);finish();return;}if(key.name==="q"){session.notice=yellow("Exited without saving.");renderSession(session);finish();return;}session.notice=red(`Unsupported key: ${key.name||"unknown"}`);renderSession(session);};process.stdin.on("keypress",onKeypress);});}finally{if(typeof process.stdin.setRawMode==="function"){process.stdin.setRawMode(false);}rl.close();}}async function main(){const{raw:rawConfig,json:config}=readJsonFile(CONFIG_PATH,"App config");const catalystCoreRoot=path.dirname(require.resolve("catalyst-core/package.json"));const pluginsRoot=resolveInternalPluginsRoot(catalystCoreRoot);const plugins=discoverInternalPlugins(pluginsRoot);if(plugins.length===0){console.log(yellow(`No internal plugins were discovered at ${pluginsRoot}`));return;}const session=createSession({configPath:CONFIG_PATH,rawConfig,config,plugins,pluginsRoot});await runInteractiveSession(session);}main().catch(error=>{console.error(red(`Plugin manager failed: ${error.message}`));process.exit(1);});
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.catalystResultMap=void 0;var _path=_interopRequireDefault(require("path"));var _moduleAlias=_interopRequireDefault(require("module-alias"));var _package=require("../../package.json");var _validator=require("./validator.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const{_moduleAliases:templateModuleAliases}=require(`${process.env.src_path}/package.json`);const catalystResultMap=exports.catalystResultMap=Object.keys(_package._moduleAliases||[]).reduce((resultMap,aliasName)=>{const aliasPath=_package._moduleAliases[aliasName];if(aliasName?.includes("@catalyst/template")){if(aliasName?.includes("server")&&process.env.NODE_ENV==="production"){resultMap[aliasName]=_path.default.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH);}else{
|
|
2
|
-
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.catalystResultMap=void 0;var _path=_interopRequireDefault(require("path"));var _moduleAlias=_interopRequireDefault(require("module-alias"));var _package=require("../../package.json");var _validator=require("./validator.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const{_moduleAliases:templateModuleAliases}=require(`${process.env.src_path}/package.json`);const catalystResultMap=exports.catalystResultMap=Object.keys(_package._moduleAliases||[]).reduce((resultMap,aliasName)=>{const aliasPath=_package._moduleAliases[aliasName];if(aliasName?.includes("@catalyst/template")){if(aliasName?.includes("server")&&process.env.NODE_ENV==="production"){resultMap[aliasName]=_path.default.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH);}else{// nosemgrep
|
|
2
|
+
resultMap[aliasName]=_path.default.join(process.env.src_path,aliasPath);}return resultMap;}if(aliasName?.includes("@catalyst")&&!aliasName?.includes("@catalyst/template")){if(aliasName?.includes("server")&&process.env.NODE_ENV==="production"){resultMap[aliasName]=_path.default.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH);}else{// nosemgrep
|
|
3
|
+
resultMap[aliasName]=_path.default.join(__dirname,"../",aliasPath);}return resultMap;}return resultMap;},{});_moduleAlias.default.addAliases(catalystResultMap);// resolves module alias imports
|
|
4
|
+
if((0,_validator.validateModuleAlias)(templateModuleAliases)){_moduleAlias.default.addAliases(Object.keys(templateModuleAliases||[]).reduce((resultMap,aliasName)=>{const aliasPath=templateModuleAliases[aliasName];if(aliasPath.includes("server")){if(process.env.NODE_ENV!=="production"){// nosemgrep
|
|
5
|
+
resultMap[aliasName]=_path.default.join(process.env.src_path,aliasPath);}else{resultMap[aliasName]=_path.default.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH);}}else{// nosemgrep
|
|
6
|
+
resultMap[aliasName]=_path.default.join(process.env.src_path,aliasPath);}return resultMap;},{}));}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.arrayToObject=arrayToObject;exports.runBuildCommands=exports.printBundleInformation=void 0;const fs=require("fs");const path=require("path");const util=require("node:util");const{spawnSync}=require("child_process");const{gray,cyan}=require("picocolors");const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);// Function to get file size synchronously
|
|
2
|
-
function getFileSizeSync(filePath){try{const stats=fs.statSync(filePath);return stats.size;}catch(err){console.error(`Error getting bundle information for file ${filePath}: ${err}`);return null;}}const printBundleInformation=()=>{let bundleList=[];const directoryPath=path.join(process.cwd(),`${BUILD_OUTPUT_PATH}/public`);try{const files=fs.readdirSync(directoryPath);files.forEach(file=>{if(!file.includes("txt")&&!file.includes("json")){const filePath
|
|
2
|
+
function getFileSizeSync(filePath){try{const stats=fs.statSync(filePath);return stats.size;}catch(err){console.error(`Error getting bundle information for file ${filePath}: ${err}`);return null;}}const printBundleInformation=()=>{let bundleList=[];const directoryPath=path.join(process.cwd(),`${BUILD_OUTPUT_PATH}/public`);try{const files=fs.readdirSync(directoryPath);files.forEach(file=>{if(!file.includes("txt")&&!file.includes("json")){const filePath=`${directoryPath}${path.sep}${file}`;const fileSize=getFileSizeSync(filePath);if(fileSize!==null){bundleList.push({file,fileSize});}}});}catch(err){console.error("Unable to scan build folder: "+err);}bundleList.sort((a,b)=>b.fileSize-a.fileSize);bundleList.forEach(({file,fileSize})=>{const fileName=`${gray(`${BUILD_OUTPUT_PATH}/public/`)}${cyan(file)}`;const fileSizeInKb=(fileSize/1024).toFixed(2);const size=`\t${fileSizeInKb} kB`.padEnd(16);console.log(util.format(size,fileName));});};exports.printBundleInformation=printBundleInformation;function arrayToObject(array){const obj={};array.forEach(item=>{const[key,value]=item.split("=");if(value)obj[key]=value;});return obj;}const BUILD_FAILURE_MESSAGE="\nBuild Failed!";function shouldFailBuild(result){return result.error||result.signal||result.status!==null&&result.status!==0;}function logBuildFailure(result,failureMessage=BUILD_FAILURE_MESSAGE){console.error(failureMessage);if(result.error){console.error(`Error: ${result.error?.message||result.error}\n`);}if(result.signal){console.error(`Signal: ${result.signal}\n`);}if(result.status!==null){console.error(`Exit code: ${result.status}\n`);}}const runBuildCommands=({commands,cwd,env,failureMessage=BUILD_FAILURE_MESSAGE})=>{const command=commands.join(" && ");// nosemgrep
|
|
3
|
+
const result=spawnSync(command,[],{cwd,stdio:"inherit",shell:true,env});if(shouldFailBuild(result)){logBuildFailure(result,failureMessage);process.exit(result.status||1);}return result;};exports.runBuildCommands=runBuildCommands;
|
package/dist/scripts/serve.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
"use strict";const path=require("path");const{spawnSync}=require("child_process");const{arrayToObject}=require("./scriptUtils");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);/**
|
|
2
2
|
* @description - Serves production build of the application.
|
|
3
|
-
*/function serve(){const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../")
|
|
3
|
+
*/function serve(){const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");// nosemgrep: javascript.lang.security.audit.spawn-shell-true.spawn-shell-true - Production serve keeps shell execution for existing script compatibility.
|
|
4
|
+
spawnSync("node",["-r","./dist/scripts/loadScriptsBeforeServerStarts.js",path.join(process.cwd(),BUILD_OUTPUT_PATH,"startServer.js")],{cwd:dirname,stdio:"inherit",shell:true,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:false,APPLICATION:name||"catalyst_app",...argumentsObject}});}serve();
|
package/dist/scripts/start.js
CHANGED
|
@@ -2,5 +2,10 @@
|
|
|
2
2
|
* @description - starts webpack dev server and node server.
|
|
3
3
|
*/function start(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const command=`
|
|
4
4
|
node ./dist/scripts/checkVersion
|
|
5
|
-
npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/webpack/development.client.babel --no-warnings=ExperimentalWarning --no-warnings=BABEL & npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/server/startServer.js --extensions .js,.ts,.jsx,.tsx --
|
|
6
|
-
`;if(isWindows){
|
|
5
|
+
npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/webpack/development.client.babel --no-warnings=ExperimentalWarning --no-warnings=BABEL & npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/server/startServer.js --extensions .js,.ts,.jsx,.tsx --ignore='__IGNORE__' --no-warnings=ExperimentalWarning --no-warnings=BABEL
|
|
6
|
+
`;if(isWindows){// Windows doesn't support `&` for parallel processes in a single shell command,
|
|
7
|
+
// so we use two non-blocking spawn() calls instead of a single spawnSync().
|
|
8
|
+
// nosemgrep
|
|
9
|
+
spawn(`node ./dist/scripts/checkVersion && start /b npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/webpack/development.client.babel --no-warnings=ExperimentalWarning --no-warnings=BABEL`,[],{cwd:dirname,stdio:"inherit",shell:true,env:{...process.env,src_path:process.cwd(),NODE_ENV:"development",IS_DEV_COMMAND:false,APPLICATION:name||"catalyst_app",BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,...argumentsObject}});// nosemgrep
|
|
10
|
+
spawn(`node ./dist/scripts/checkVersion && npx babel-node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ./dist/server/startServer.js --extensions .js,.ts,.jsx,.tsx --ignore='__IGNORE__' --no-warnings=ExperimentalWarning --no-warnings=BABEL`,[],{cwd:dirname,stdio:"inherit",shell:true,env:{...process.env,src_path:process.cwd(),NODE_ENV:"development",IS_DEV_COMMAND:false,APPLICATION:name||"catalyst_app",BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,...argumentsObject}});}else{// nosemgrep
|
|
11
|
+
spawnSync(command,[],{cwd:dirname,stdio:"inherit",shell:true,env:{...process.env,src_path:process.cwd(),NODE_ENV:"development",IS_DEV_COMMAND:false,APPLICATION:name||"catalyst_app",BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,...argumentsObject}});}}start();
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _path=_interopRequireDefault(require("path"));var _express=_interopRequireDefault(require("express"));var _bodyParser=_interopRequireDefault(require("body-parser"));var _compression=_interopRequireDefault(require("compression"));var _cookieParser=_interopRequireDefault(require("cookie-parser"));var _expressStaticGzip=_interopRequireDefault(require("express-static-gzip"));var _renderer=_interopRequireDefault(require("@catalyst/server/renderer"));var _server=require("@catalyst/template/server/server.js");var _validator=require("@catalyst/server/utils/validator");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const env=process.env.NODE_ENV||"development";const app=(0,_express.default)();// This middleware is being used to extract the body of the request
|
|
2
|
-
app.use(_bodyParser.default.json());//
|
|
3
|
-
app.use(_bodyParser.default.raw({type:"application/*"}));//
|
|
4
|
-
app.use((0,
|
|
5
|
-
if((0,_validator.validateMiddleware)(_server.addMiddlewares))(0,_server.addMiddlewares)(app);// The middleware will attempt to compress response bodies for all request that traverse through the middleware
|
|
6
|
-
app.use((0,_compression.default)());// This endpoint will serve the built assets from the node server. The requests will be made to PUBLIC_STATIC_ASSET_PATH which has been defined in the application config.
|
|
7
|
-
// expressStaticGzip will compress the assets.
|
|
8
|
-
if(env==="production"){app.use(process.env.PUBLIC_STATIC_ASSET_PATH,(0,_expressStaticGzip.default)(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`),{enableBrotli:true,orderPreference:["br","gzip","deflate"]}));}else{app.use(process.env.PUBLIC_STATIC_ASSET_PATH,_express.default.static(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`)));}// This middleware handles document requests.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _path=_interopRequireDefault(require("path"));var _fs=_interopRequireDefault(require("fs"));var _express=_interopRequireDefault(require("express"));var _bodyParser=_interopRequireDefault(require("body-parser"));var _compression=_interopRequireDefault(require("compression"));var _cookieParser=_interopRequireDefault(require("cookie-parser"));var _expressStaticGzip=_interopRequireDefault(require("express-static-gzip"));var _renderer=_interopRequireDefault(require("@catalyst/server/renderer"));var _server=require("@catalyst/template/server/server.js");var _validator=require("@catalyst/server/utils/validator");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const env=process.env.NODE_ENV||"development";const app=(0,_express.default)();const sendBuiltPublicFile=(res,fileName,headers={})=>{const publicFilePath=_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`,fileName);if(!_fs.default.existsSync(publicFilePath)){res.status(404).end();return;}res.set(headers);res.sendFile(publicFilePath);};app.get("/catalyst-sw.js",(_req,res)=>{sendBuiltPublicFile(res,"catalyst-sw.js",{"Content-Type":"application/javascript; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate","Service-Worker-Allowed":"/"});});app.get("/catalyst-offline-manifest.json",(_req,res)=>{sendBuiltPublicFile(res,"catalyst-offline-manifest.json",{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate"});});app.get("/offline.html",(_req,res)=>{sendBuiltPublicFile(res,"offline.html",{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate"});});// This middleware is being used to extract the body of the request
|
|
2
|
+
app.use(_bodyParser.default.json({limit:"100kb"}));// Handles "bytestream array" content types sent as raw application/* bodies
|
|
3
|
+
app.use(_bodyParser.default.raw({type:"application/*",limit:"100kb"}));app.use((0,_cookieParser.default)());if((0,_validator.validateMiddleware)(_server.addMiddlewares))(0,_server.addMiddlewares)(app);app.use((0,_compression.default)());// Serve pre-compressed static assets (brotli preferred) from the build output directory
|
|
4
|
+
if(env==="production"){app.use(process.env.PUBLIC_STATIC_ASSET_PATH,(0,_expressStaticGzip.default)(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`),{enableBrotli:true,orderPreference:["br","gzip","deflate"]}));}else{app.use(process.env.PUBLIC_STATIC_ASSET_PATH,_express.default.static(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`)));}// Catch-all: every non-asset request is handled by the SSR renderer
|
|
9
5
|
app.use("*",_renderer.default);var _default=exports.default=app;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.Body=Body;var _react=_interopRequireDefault(require("react"));var
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.Body=Body;var _react=_interopRequireDefault(require("react"));var _jsxRuntime=require("react/jsx-runtime");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}/**
|
|
2
2
|
* Body component which will be used in page component
|
|
3
3
|
* @param {object} jsx - page jsx code
|
|
4
4
|
* @param {object} initialState - initial state object for redux store
|
|
5
5
|
* @param {object} firstFoldCss - style elements extracted for initial page load
|
|
6
6
|
* @param {object} fetcherData - contains data from executing serverFetcher function
|
|
7
7
|
* @param {object} children - contains any child elements defined within the component
|
|
8
|
-
*/function Body(
|
|
8
|
+
*/const DEFAULT_SAFE_AREA={top:0,right:0,bottom:0,left:0};function Body({jsx="",initialState={},fetcherData={},children,safeArea=DEFAULT_SAFE_AREA,nativeWebView=false}){return/*#__PURE__*/(0,_jsxRuntime.jsxs)("body",{children:[/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable-next-line risxss/catch-potential-xss-react */dangerouslySetInnerHTML:{__html:`window.__SAFE_AREA_INITIAL__ = ${JSON.stringify(safeArea)}; window.__CATALYST_NATIVE_WEBVIEW__ = ${nativeWebView?"true":"false"}`}}),jsx,/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable */dangerouslySetInnerHTML:{__html:`
|
|
9
9
|
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
|
|
10
|
-
window.__ROUTER_INITIAL_DATA__ = ${JSON.stringify(fetcherData)}
|
|
11
|
-
`}}),children]});}
|
|
10
|
+
window.__ROUTER_INITIAL_DATA__ = ${JSON.stringify(fetcherData)}
|
|
11
|
+
`}}),children]});}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.Head=Head;var _react=_interopRequireDefault(require("react"));var
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.Head=Head;var _react=_interopRequireDefault(require("react"));var _jsxRuntime=require("react/jsx-runtime");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}/**
|
|
2
2
|
* Head component which will be used in page rendering
|
|
3
3
|
* @param {boolean} isBot - checks if request is made by bot
|
|
4
4
|
* @param {string} pageCss - includes all styles for page css
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* @param {array} metaTags - user defined function which returns meta tags in array
|
|
7
7
|
* @param {string} publicAssetPath - public asset path for assets
|
|
8
8
|
* @param {object} children - contains any child elements defined within the component
|
|
9
|
-
*/function Head(
|
|
9
|
+
*/function Head({pageCss,preloadJSLinks,metaTags,isBot,publicAssetPath,children}){if(children){return/*#__PURE__*/(0,_jsxRuntime.jsxs)("head",{children:[children,metaTags&&metaTags,pageCss&&/*#__PURE__*/(0,_jsxRuntime.jsx)("style",{dangerouslySetInnerHTML:{__html:pageCss}}),!isBot&&preloadJSLinks]});}return/*#__PURE__*/(0,_jsxRuntime.jsxs)("head",{children:[/*#__PURE__*/(0,_jsxRuntime.jsx)("meta",{charSet:"utf-8"}),/*#__PURE__*/(0,_jsxRuntime.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1"}),publicAssetPath&&/*#__PURE__*/(0,_jsxRuntime.jsx)("link",{rel:"preconnect",href:publicAssetPath}),publicAssetPath&&/*#__PURE__*/(0,_jsxRuntime.jsx)("link",{rel:"dns-prefetch",href:publicAssetPath}),metaTags&&metaTags,pageCss&&/*#__PURE__*/(0,_jsxRuntime.jsx)("style",{dangerouslySetInnerHTML:{__html:pageCss}}),!isBot&&preloadJSLinks]});}
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.cacheAndFetchAssets=void 0;
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* @param {
|
|
5
|
-
|
|
6
|
-
if(!
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.cacheAndFetchAssets=void 0;var _path=_interopRequireDefault(require("path"));var _fs=_interopRequireDefault(require("fs"));var _otel=require("../../otel");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}// Use decimal megabytes (1 MB = 1,000,000 bytes) for all memory logs
|
|
2
|
+
const BYTES_PER_MB=1_000_000;const toMB=bytes=>(bytes/BYTES_PER_MB).toFixed(2);const SSR_SERVICE=process.env.SERVICE_NAME||`pwa-${process.env.APPLICATION}-node-server-otel`;/**
|
|
3
|
+
* Caches CSS file content (shared across all routes)
|
|
4
|
+
* @param {string} assetName - CSS filename
|
|
5
|
+
* @param {string} assetPath - Full path to CSS file
|
|
6
|
+
*/function cacheCSS(assetName,assetPath){if(!process.cssCache){process.cssCache={};}if(!process.cssCache[assetName]){const css=_fs.default.readFileSync(assetPath);process.cssCache[assetName]=css;const cssCacheSize=Object.keys(process.cssCache).length;const cssCacheTotalSizeBytes=Object.values(process.cssCache).reduce((total,cssContent)=>total+Buffer.byteLength(cssContent),0);const memoryUsage=process.memoryUsage();const availableMemoryBytes=(memoryUsage?.heapTotal??0)-(memoryUsage?.heapUsed??0);logger.info(`Last Cached CSS - Asset: ${assetName}, `+`CSS Cache Entries: ${cssCacheSize}, `+`CSS Cache Total Size: ${toMB(cssCacheTotalSizeBytes)} MB, `+`RSS: ${toMB(memoryUsage?.rss??0)} MB, `+`Heap Total: ${toMB(memoryUsage?.heapTotal??0)} MB, `+`Heap Used: ${toMB(memoryUsage?.heapUsed??0)} MB, `+`External: ${toMB(memoryUsage?.external??0)} MB, `+`Array Buffers: ${toMB(memoryUsage?.arrayBuffers??0)} MB, `+`Available Memory: ${toMB(availableMemoryBytes)} MB, `+`Timestamp: ${new Date().toISOString()}`);}return process.cssCache[assetName];}/**
|
|
7
|
+
* Extracts CSS and preload links from cached ChunkExtractor
|
|
8
|
+
* @param {string} routePath - Route path
|
|
9
|
+
* @returns {object} { css: string, preloadJSLinks: array } or null if not cached
|
|
10
|
+
*/function getAssetsFromCachedExtractor(routePath){const isProd=process.env.NODE_ENV==="production";// Only use cached extractor in production
|
|
11
|
+
if(!isProd||!process.extractorCache||!process.extractorCache[routePath]){return null;}const extractor=process.extractorCache[routePath];const linkElements=extractor.getLinkElements({fetchpriority:"low"});// Extractor hasn't run renderToString yet — no chunks collected
|
|
12
|
+
if(!linkElements||linkElements.length===0){return null;}const preloadJSLinks=linkElements.filter(asset=>asset?.props?.as==="script");const cssAssets=linkElements.filter(e=>{const href=e?.props?.href;return href&&href.endsWith(".css");});// Build inline CSS from cached CSS files
|
|
13
|
+
const cssContents=[];for(const cssElement of cssAssets){const assetName=_path.default.basename(cssElement.props.href);// Check if CSS content is cached
|
|
14
|
+
if(process.cssCache&&process.cssCache[assetName]){cssContents.push(process.cssCache[assetName]);}else{// CSS file not in cache yet — fall back to full render path
|
|
15
|
+
return null;}}const cachedCss=cssContents.join("");return{css:cachedCss,preloadJSLinks:preloadJSLinks};}/**
|
|
16
|
+
* Builds inline CSS from link elements by reading and caching CSS files
|
|
17
|
+
* @param {array} linkElements - Link elements from ChunkExtractor
|
|
18
|
+
* @returns {string} Concatenated CSS content
|
|
19
|
+
*/function buildInlineCSS(linkElements){if(!Array.isArray(linkElements)||linkElements.length===0){return"";}const cssContents=[];for(const element of linkElements){const href=element?.props?.href;if(href&&href.endsWith(".css")){const assetName=_path.default.basename(href);const assetPath=_path.default.resolve(process.env.src_path,`${process.env.BUILD_OUTPUT_PATH}/public`,assetName// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - assetName is reduced to a basename and CSS is read from the build output directory.
|
|
20
|
+
);// Read and cache CSS content
|
|
21
|
+
const cssContent=cacheCSS(assetName,assetPath);cssContents.push(cssContent);}}return cssContents.join("");}/**
|
|
22
|
+
* Phase 2: called from renderMarkUp's onAllReady after renderToPipeableStream finishes.
|
|
23
|
+
* Builds the inline CSS (<style> block) and script tags that are appended to the stream.
|
|
24
|
+
* In production, CSS is inlined from disk-cached files; in dev, style tags are used directly.
|
|
25
|
+
* Bots receive no JS — only the fully rendered HTML is needed for crawling.
|
|
26
|
+
*/const cacheAndFetchAssets=exports.cacheAndFetchAssets=(0,_otel.withSyncObservability)(SSR_SERVICE,function cacheAndFetchAssets({webExtractor,res,isBot}){let firstFoldCss="";let firstFoldJS="";const isProd=process.env.NODE_ENV==="production";const{routePath}=res.locals;const linkElements=webExtractor.getLinkElements({fetchpriority:"low"});if(routePath){if(isProd){firstFoldCss=buildInlineCSS(linkElements);if(firstFoldCss?.length)firstFoldCss=`<style>${firstFoldCss}</style>`;}else{firstFoldCss=webExtractor.getStyleTags();}firstFoldJS=!isBot?webExtractor.getScriptTags({fetchpriority:"low"}):"";}return{firstFoldCss,firstFoldJS};},"cacheAndFetchAssets");/**
|
|
27
|
+
* Checks if we have cached assets for this route
|
|
28
|
+
* Called during Phase 1 to determine if we can skip renderToString
|
|
29
|
+
* @param {object} res - Response object
|
|
30
|
+
* @param {object} route - Route configuration
|
|
31
|
+
*/var _default=exports.default=(0,_otel.withSyncObservability)(SSR_SERVICE,function extractAssets(res,route){try{const routePath=route.path;const cached=getAssetsFromCachedExtractor(routePath);if(cached&&(cached.css||cached.preloadJSLinks)){res.locals.pageCss=cached.css;res.locals.preloadJSLinks=cached.preloadJSLinks;return;}logger.info({message:"Cache Missed",uri:routePath});}catch(error){logger.error("Error in extracting assets:"+error);}},"extractAssets");
|
|
@@ -1,24 +1,47 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const matches=
|
|
19
|
-
(0,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(0,_validator.
|
|
23
|
-
|
|
24
|
-
(
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _react=_interopRequireDefault(require("react"));var _extract=_interopRequireWildcard(require("./extract"));var _otel=require("../../otel");var _reactRedux=require("react-redux");var _document=require("./document");var _server=require("react-router-dom/server");var _ServerRouter=_interopRequireDefault(require("@catalyst/router/ServerRouter.js"));var _index=_interopRequireDefault(require("@catalyst/template/src/js/containers/App/index.js"));var _server2=require("@loadable/server");var _server3=require("react-dom/server");var _userAgentUtil=require("@catalyst/server/utils/userAgentUtil");var _router=require("@tata1mg/router");var _validator=require("@catalyst/server/utils/validator");var _document2=_interopRequireDefault(require("@catalyst/template/server/document.js"));var _utils=require("@catalyst/template/src/js/routes/utils.js");var _index2=require("@catalyst/template/server/index.js");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap(),n=new WeakMap();return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f);}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f;})(e,t);}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const storePath=_path.default.resolve(`${process.env.src_path}/src/js/store/index.js`);let createStore;if(_fs.default.existsSync(storePath)){try{const{default:configureStore}=require(`${process.env.src_path}/src/js/store/index.js`);createStore=configureStore;}catch(error){createStore=()=>{return{getState:()=>{}};};}}else{createStore=()=>{return{getState:()=>{}};};}const isProduction=process.env.NODE_ENV==="production";const DEFAULT_SAFE_AREA_INSETS={top:0,right:0,bottom:0,left:0};const parseSafeAreaFromHeaders=req=>{const readEdge=header=>{const raw=req.get(header)??req.headers[header.toLowerCase()];const value=Number(raw);return Number.isFinite(value)&&value>=0?value:null;};const top=readEdge("X-Safe-Area-Top");const right=readEdge("X-Safe-Area-Right");const bottom=readEdge("X-Safe-Area-Bottom");const left=readEdge("X-Safe-Area-Left");const hasAny=[top,right,bottom,left].some(value=>value!==null);if(!hasAny){return null;}return{top:top??0,right:right??0,bottom:bottom??0,left:left??0};};const isNativeWebViewRequest=req=>{const raw=req.get("X-Catalyst-Native-WebView")??req.headers["x-catalyst-native-webview"];return raw==="1"||raw==="true";};// Cache webStats path - computed once at module load
|
|
2
|
+
const webStatsPath=isProduction?_path.default.join(process.env.src_path,`${process.env.BUILD_OUTPUT_PATH}/public/loadable-stats.json`):_path.default.join(__dirname,"../../..",`loadable-stats.json`);// Cache for parsed stats (production only)
|
|
3
|
+
let cachedWebStats=null;const getWebStats=()=>{if(!isProduction){// In development, stats can change; always read fresh.
|
|
4
|
+
return JSON.parse(_fs.default.readFileSync(webStatsPath,"utf-8"));}if(!cachedWebStats){cachedWebStats=JSON.parse(_fs.default.readFileSync(webStatsPath,"utf-8"));logger.info("ChunkExtractor stats cached in memory");}return cachedWebStats;};// Cache for ChunkExtractor instances per route (production only)
|
|
5
|
+
if(!process.extractorCache){process.extractorCache={};}const getOrCreateExtractorForRoute=routePath=>{if(!isProduction){// In dev, always create fresh extractor (HMR compatible)
|
|
6
|
+
return new _server2.ChunkExtractor({stats:getWebStats(),entrypoints:["app"]});}// Production: cache extractor per route
|
|
7
|
+
if(!process.extractorCache[routePath]){process.extractorCache[routePath]=new _server2.ChunkExtractor({stats:getWebStats(),entrypoints:["app"]});logger.info(`ChunkExtractor created and cached for route: ${routePath}`);}return process.extractorCache[routePath];};// Cache routes array - getRoutes() returns same reference, but validate once
|
|
8
|
+
let cachedRoutes=null;const getCachedRoutes=()=>{if(!cachedRoutes){cachedRoutes=(0,_validator.validateGetRoutes)(_utils.getRoutes)?(0,_utils.getRoutes)():[];}return cachedRoutes;};const SSR_SERVICE=process.env.SERVICE_NAME||`pwa-${process.env.APPLICATION}-node-server-otel`;const traceHandlerHook=(fn,spanName)=>typeof fn==="function"?(0,_otel.withSyncObservability)(SSR_SERVICE,fn,spanName):fn;const onRouteMatch=traceHandlerHook(_index2.onRouteMatch,"onRouteMatch");const onFetcherSuccess=traceHandlerHook(_index2.onFetcherSuccess,"onFetcherSuccess");const onFetcherError=traceHandlerHook(_index2.onFetcherError,"onFetcherError");const onAppServerSideSuccess=traceHandlerHook(_index2.onAppServerSideSuccess,"onAppServerSideSuccess");const onAppServerSideError=traceHandlerHook(_index2.onAppServerSideError,"onAppServerSideError");const onRenderError=traceHandlerHook(_index2.onRenderError,"onRenderError");const onRequestError=traceHandlerHook(_index2.onRequestError,"onRequestError");// Phase 1b dry-run: wrapped separately so it appears as a distinct span inside getMatchRoutes.
|
|
9
|
+
const renderToStringWithObservability=(0,_otel.withSyncObservability)(SSR_SERVICE,function dryRunRender(webExtractor,store,context,req,fetcherData){(0,_server3.renderToString)(/*#__PURE__*/(0,_jsxRuntime.jsx)(_server2.ChunkExtractorManager,{extractor:webExtractor,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_reactRedux.Provider,{store:store,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_server.StaticRouter,{context:context,location:req.originalUrl,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_ServerRouter.default,{store:store,intialData:fetcherData})})})}));},"renderToString");// Internal recursive implementation — called directly to avoid creating a new span on each recursion.
|
|
10
|
+
const _getMatchRoutes=(routes,req,res,store,context,fetcherData,basePath="")=>{return routes.reduce((matches,route)=>{const{path}=route;const match=(0,_router.matchPath)({path:`${basePath}/${path}`,caseSensitive:false,end:true},req.baseUrl||"/");if(match){if(!res.locals.pageCss&&!res.locals.preloadJSLinks&&!res.locals.routePath){res.locals.routePath=path;// Phase 1a: try to serve CSS/preloadLinks from the extractor cache (production only).
|
|
11
|
+
// On a cache hit, res.locals.pageCss and preloadJSLinks are populated and
|
|
12
|
+
// the renderToString dry-run below is skipped entirely for this request.
|
|
13
|
+
(0,_extract.default)(res,route);}// Production: reuses the same ChunkExtractor per route (chunks accumulate over time).
|
|
14
|
+
// Dev: always creates a fresh extractor so HMR chunk changes are picked up immediately.
|
|
15
|
+
if(!res.locals.webExtractor){res.locals.webExtractor=getOrCreateExtractorForRoute(path);}if(!res.locals.pageCss&&!res.locals.preloadJSLinks){// Phase 1b: dry-run renderToString solely to let the ChunkExtractor record which
|
|
16
|
+
// chunks this route needs. The output is discarded — the real render happens later
|
|
17
|
+
// in renderMarkUp via renderToPipeableStream.
|
|
18
|
+
renderToStringWithObservability(res.locals.webExtractor,store,context,req,fetcherData);}const wc=route.component;matches.push({route,match,serverSideFunction:wc&&wc.serverSideFunction||(()=>Promise.resolve())});}if(!match&&route.children){const nested=_getMatchRoutes(route.children,req,res,store,context,fetcherData,`${basePath}/${path}`);if(nested.length){matches=matches.concat(nested);}}return matches;},[]);};const getMatchRoutes=(0,_otel.withSyncObservability)(SSR_SERVICE,_getMatchRoutes,"getMatchRoutes");// Preloads chunks required for rendering document
|
|
19
|
+
const getComponent=(store,context,req,fetcherData)=>{return/*#__PURE__*/(0,_jsxRuntime.jsx)("div",{id:"app",children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_reactRedux.Provider,{store:store,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_server.StaticRouter,{context:context,location:req.originalUrl,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_ServerRouter.default,{store:store,intialData:fetcherData})})})});};const tracedResWriteFirstFoldCss=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldCss");const tracedResWriteFirstFoldJS=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldJS");const tracedResEnd=(0,_otel.withSyncObservability)(SSR_SERVICE,res=>res.end(),"res.end");const renderMarkUp=async(errorCode,req,res,metaTags,fetcherData,store,matches,context,webExtractor)=>{const deviceDetails=(0,_userAgentUtil.getUserAgentDetails)(req.headers["user-agent"]||"");const isBot=deviceDetails.googleBot||deviceDetails.aiBot?true:false;const safeArea=parseSafeAreaFromHeaders(req)||{...DEFAULT_SAFE_AREA_INSETS};const nativeWebView=isNativeWebViewRequest(req);/* eslint-disable no-undef */const previousSafeArea=globalThis.__SAFE_AREA_INITIAL__;const previousNativeWebView=globalThis.__CATALYST_NATIVE_WEBVIEW__;globalThis.__SAFE_AREA_INITIAL__=safeArea;globalThis.__CATALYST_NATIVE_WEBVIEW__=nativeWebView;/* eslint-enable no-undef */let state=store.getState();// collectChunks wraps the component tree so the extractor can track which loadable
|
|
20
|
+
// chunks are actually rendered, building the final script/link tag lists.
|
|
21
|
+
const jsx=webExtractor.collectChunks(getComponent(store,context,req,fetcherData));const{IS_DEV_COMMAND,WEBPACK_DEV_SERVER_HOSTNAME,WEBPACK_DEV_SERVER_PORT}=process.env;let publicAssetPath=`${process.env.PUBLIC_STATIC_ASSET_URL}${process.env.PUBLIC_STATIC_ASSET_PATH}`;// serves assets from localhost on running devBuild and devServe command
|
|
22
|
+
if(IS_DEV_COMMAND==="true"){publicAssetPath=`http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/assets/`;}const finalProps={req,res,lang:"en",pageCss:res.locals.pageCss,preloadJSLinks:res.locals.preloadJSLinks,metaTags,isBot,publicAssetPath,jsx,initialState:state,fetcherData,safeArea,nativeWebView};let CompleteDocument=()=>{if((0,_validator.validateCustomDocument)(_document2.default)){return(0,_document2.default)(finalProps);}else{return/*#__PURE__*/(0,_jsxRuntime.jsxs)("html",{lang:finalProps.lang,children:[/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Head,{isBot:finalProps.isBot,pageCss:finalProps.pageCss,metaTags:finalProps.metaTags,preloadJSLinks:finalProps.preloadJSLinks,publicAssetPath:finalProps.publicAssetPath}),/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Body,{jsx:finalProps.jsx,fetcherData:finalProps.fetcherData,initialState:finalProps.initialState,safeArea:finalProps.safeArea,nativeWebView:finalProps.nativeWebView})]});}};const cleanupGlobalThis=()=>{/* eslint-disable no-undef */if(previousSafeArea===undefined){delete globalThis.__SAFE_AREA_INITIAL__;}else{globalThis.__SAFE_AREA_INITIAL__=previousSafeArea;}if(previousNativeWebView===undefined){delete globalThis.__CATALYST_NATIVE_WEBVIEW__;}else{globalThis.__CATALYST_NATIVE_WEBVIEW__=previousNativeWebView;}/* eslint-enable no-undef */};try{let status=errorCode||(matches.length&&matches[0].match.path==="*"?404:200);res.set({"content-type":"text/html; charset=utf-8"});res.status(status);return new Promise((resolve,reject)=>{const{pipe}=(0,_server3.renderToPipeableStream)(/*#__PURE__*/(0,_jsxRuntime.jsx)(CompleteDocument,{}),{onShellReady(){// Start streaming the HTML shell (everything above Suspense boundaries)
|
|
23
|
+
// as soon as it's ready to minimise TTFB.
|
|
24
|
+
res.setHeader("content-type","text/html");pipe(res);},onAllReady(){// All Suspense boundaries have resolved. Append inline CSS and script
|
|
25
|
+
// tags at the end of the stream so the browser can start executing JS.
|
|
26
|
+
const{firstFoldCss,firstFoldJS}=(0,_extract.cacheAndFetchAssets)({webExtractor,res,isBot});tracedResWriteFirstFoldCss(res,firstFoldCss);tracedResWriteFirstFoldJS(res,firstFoldJS);tracedResEnd(res);cleanupGlobalThis();resolve();},onError(error){logger.error({message:`\n Error while renderToPipeableStream : ${error.toString()}`});// function defined by user which needs to run if rendering fails
|
|
27
|
+
(0,_validator.safeCall)(onRenderError,{req,res,store,error});cleanupGlobalThis();reject(error);}});});}catch(error){logger.error({message:`Error in rendering document on server - ${error}`,trace:error.stack,url:req.originalUrl});// function defined by user which needs to run if rendering fails
|
|
28
|
+
(0,_validator.safeCall)(onRenderError,{req,res,store,error});cleanupGlobalThis();return Promise.reject(error);}};const tracedAppServerSideFunction=(0,_otel.withObservability)(SSR_SERVICE,args=>_index.default.serverSideFunction(args),"App.serverSideFunction");const tracedServerDataFetcher=(0,_otel.withObservability)(SSR_SERVICE,_router.serverDataFetcher,"serverDataFetcher");const tracedRenderMarkUp=(0,_otel.withObservability)(SSR_SERVICE,renderMarkUp,"renderMarkUp");const tracedGetMetaData=(0,_otel.withSyncObservability)(SSR_SERVICE,_router.getMetaData,"getMetaData");/**
|
|
29
|
+
* SSR request handler. Execution pipeline per request:
|
|
30
|
+
* 1. Match route → collect loadable chunks (Phase 1 dry-run)
|
|
31
|
+
* 2. App.serverSideFunction (app-level server hook)
|
|
32
|
+
* 3. serverDataFetcher (route-level data fetching)
|
|
33
|
+
* 4. renderMarkUp → renderToPipeableStream (Phase 2 — real render + stream to client)
|
|
34
|
+
*
|
|
35
|
+
* res.headersSent is checked after each async step: if a user hook (onRouteMatch,
|
|
36
|
+
* onAppServerSideSuccess, etc.) has already sent a response (e.g. a redirect), we
|
|
37
|
+
* bail out early without attempting another render.
|
|
38
|
+
*/async function _handler(req,res){try{let context={};let fetcherData={};const store=(0,_validator.validateConfigureStore)(createStore)?createStore({},req,res):null;const routes=getCachedRoutes();// matches: routes that exactly match this URL — used for serverSideFunction execution.
|
|
39
|
+
// allMatches: full nested match tree — used by getMetaData to collect meta tags.
|
|
40
|
+
const matches=getMatchRoutes(routes,req,res,store,context,fetcherData);const allMatches=(0,_router.matchRoutes)(routes,req.baseUrl);let allTags=[];// Extractor is attached to res.locals inside getMatchRoutes; reused for collectChunks in renderMarkUp
|
|
41
|
+
const webExtractor=res.locals.webExtractor;(0,_validator.safeCall)(onRouteMatch,{req,res,matches,store});if(res.headersSent){return Promise.resolve(res);}try{await tracedAppServerSideFunction({store,req,res});(0,_validator.safeCall)(onAppServerSideSuccess,{req,res,store});if(res.headersSent){return Promise.resolve(res);}try{fetcherData=await tracedServerDataFetcher({routes:routes,req,res,url:req.originalUrl},{store});if(res.headersSent){return Promise.resolve(res);}// Check if serverDataFetcher returned an error in the response
|
|
42
|
+
const err=fetcherData?.[req.originalUrl]?.error;allTags=tracedGetMetaData(allMatches,fetcherData);if(err){(0,_validator.safeCall)(onFetcherError,{req,res,store,error:err});if(res.headersSent){return Promise.reject(err);}// TODO: this seems very dependent on developers error handling, we are assuming they attach status_code key in their errors (same in App.serverSideFunction error)
|
|
43
|
+
const statusCode=err.status_code||404;return new Promise((resolve,reject)=>{tracedRenderMarkUp(statusCode,req,res,allTags,fetcherData,store,matches,context,webExtractor).then(resolve).catch(reject);});}else{// function defined by user which needs to run after SSR functions are executed successfully
|
|
44
|
+
(0,_validator.safeCall)(onFetcherSuccess,{req,res,store});if(res.headersSent){return Promise.resolve(res);}return new Promise((resolve,reject)=>{tracedRenderMarkUp(null,req,res,allTags,fetcherData,store,matches,context,webExtractor).then(resolve).catch(reject);});}}catch(error){// TODO: This catch block is kept for any unexpected errors from serverDataFetcher or getMetaData
|
|
45
|
+
logger.error("Error in executing serverFetcher functions: "+error);(0,_validator.safeCall)(onFetcherError,{req,res,store,error});if(res.headersSent){return Promise.reject(error);}return new Promise((resolve,reject)=>{tracedRenderMarkUp(404,req,res,allTags,fetcherData,store,matches,context,webExtractor).then(resolve).catch(reject);});}}catch(error){logger.error("Error in executing serverSideFunction inside App: "+error);// function defined by user which needs to run after app server side function fails
|
|
46
|
+
(0,_validator.safeCall)(onAppServerSideError,{req,res,store,error});if(res.headersSent){return Promise.reject(error);}return new Promise((resolve,reject)=>{tracedRenderMarkUp(error.status_code,req,res,allTags,fetcherData,store,matches,context,webExtractor).then(resolve).catch(reject);});}}catch(error){logger.error("Error in handling document request: "+error.toString());// function defined by user which needs to run when an error occurs in the handler
|
|
47
|
+
(0,_validator.safeCall)(onRequestError,{req,res,error});return Promise.reject(error);}}const handler=(0,_otel.withObservability)(SSR_SERVICE,_handler,"handler");var _default=exports.default=handler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _express=_interopRequireDefault(require("express"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const router=_express.default.Router();
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _express=_interopRequireDefault(require("express"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const router=_express.default.Router();router.use(function rendererMiddleware(req,res,next){let handler=require("./handler").default;if(res.locals.rendererWrapper){logger.debug({message:"Handler wrapped"});res.locals.rendererWrapper(handler)(req,res,next);}else{handler(req,res,next);}});var _default=exports.default=router;
|
|
@@ -1,7 +1,57 @@
|
|
|
1
|
-
"use strict";var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _nodeUtil=_interopRequireDefault(require("node:util"));var _chokidar=_interopRequireDefault(require("chokidar"));var _picocolors=require("picocolors");var _index=require("@catalyst/template/server/index.js");var _validator=require("@catalyst/server/utils/validator.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"use strict";var _fs=_interopRequireDefault(require("fs"));var _net=_interopRequireDefault(require("net"));var _path=_interopRequireDefault(require("path"));var _nodeUtil=_interopRequireDefault(require("node:util"));var _chokidar=_interopRequireDefault(require("chokidar"));var _picocolors=require("picocolors");var _index=require("@catalyst/template/server/index.js");var _validator=require("@catalyst/server/utils/validator.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}/**
|
|
2
|
+
* Entry point for the Catalyst Node.js server process.
|
|
3
|
+
*
|
|
4
|
+
* Dev hot-reload strategy:
|
|
5
|
+
* - loadable-stats.json change → clear require cache (picks up new client chunks)
|
|
6
|
+
* - /server file change → clear require cache + full Express restart
|
|
7
|
+
*
|
|
8
|
+
* EADDRINUSE prevention: restartServer() nulls `serverInstance` before close() so
|
|
9
|
+
* concurrent watcher events only schedule one startServer() on the closing socket.
|
|
10
|
+
*/const env=process.env.NODE_ENV||"development";// Run any app-defined pre-start hook (e.g. seed config, connect to DB)
|
|
11
|
+
(0,_validator.safeCall)(_index.preServerInit);// ─── Process-level error handlers ────────────────────────────────────────────
|
|
12
|
+
process.on("uncaughtException",(err,origin)=>{console.log(process.stderr.fd);console.log(`Caught exception: ${err}\n`+`Exception origin: ${origin}`);});process.on("uncaughtExceptionMonitor",(err,origin)=>{console.log(err,origin);});process.on("unhandledRejection",err=>console.log("unhandledRejection in Catalyst",safeStringify(err)));process.on("SIGINT",function(data){console.log("SIGINT");console.log(data);process.exit(0);});// Parent process can send "shutdown" to trigger a clean exit (used by cluster managers)
|
|
13
|
+
process.on("message",function(msg){if(msg=="shutdown"){console.log("Closing all connections...");setTimeout(function(){console.log("Finished closing connections");process.exit(0);},1500);}});// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
14
|
+
function safeStringify(err){try{return JSON.stringify(err);}catch(e){console.log("error in safeStringify",e);return err;}}// ─── Configuration ────────────────────────────────────────────────────────────
|
|
15
|
+
const port=process.env.NODE_SERVER_PORT??3005;const host=process.env.NODE_SERVER_HOSTNAME??"localhost";// loadable-stats.json is emitted by webpack (@loadable/webpack-plugin) after every
|
|
16
|
+
// successful client-side build. Its presence signals that at least one build has
|
|
17
|
+
// completed and the server can safely start serving SSR responses.
|
|
18
|
+
let statsPath=_path.default.join(__dirname,"../../",`loadable-stats.json`);if(env==="production"){statsPath=_path.default.join(process.env.src_path,`${process.env.BUILD_OUTPUT_PATH}/public/loadable-stats.json`);}// Watcher on loadable-stats.json — used to detect completed webpack rebuilds
|
|
19
|
+
const watcher=_chokidar.default.watch(statsPath,{persistent:true});// Holds the active http.Server instance. Kept at module scope so restartServer()
|
|
20
|
+
// can close the old instance before creating a new one.
|
|
21
|
+
let serverInstance=null;// ─── Cache management ─────────────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Purges all app and framework modules from Node's require cache.
|
|
24
|
+
* Called after every webpack rebuild so that the next require("./expressServer")
|
|
25
|
+
* loads the freshly compiled server bundle instead of the stale cached version.
|
|
26
|
+
* node_modules are intentionally left in cache to avoid re-evaluating them on
|
|
27
|
+
* every hot reload.
|
|
28
|
+
*/const clearServerCache=(filePath="")=>{const projectPath=process.env.src_path;Object.keys(require.cache).forEach(key=>{if(key.startsWith(projectPath)||key.includes("catalyst-core")||key.includes(filePath)){delete require.cache[key];}});};// ─── Server lifecycle ─────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Safely restarts the Express server.
|
|
31
|
+
*
|
|
32
|
+
* Nulls out `serverInstance` before calling close() so that any watcher events
|
|
33
|
+
* that fire concurrently (e.g. an editor writing multiple files on save) hit the
|
|
34
|
+
* `!serverInstance` guard and schedule a single startServer() — rather than each
|
|
35
|
+
* queuing their own close() callback that would all call startServer() once the
|
|
36
|
+
* socket finally closes, causing EADDRINUSE on the second and later attempts.
|
|
37
|
+
*/const restartServer=()=>{if(!serverInstance){startServer();return;}const closing=serverInstance;serverInstance=null;closing.close(()=>startServer());};/**
|
|
38
|
+
* Requires and starts the Express server.
|
|
39
|
+
* expressServer.js is re-required on every call so that, combined with
|
|
40
|
+
* clearServerCache(), hot-reloaded changes are always picked up.
|
|
41
|
+
*/const startServer=()=>{const server=require("./expressServer.js").default;serverInstance=server.listen({port,host},error=>{const{APPLICATION,NODE_SERVER_HOSTNAME,NODE_SERVER_PORT}=process.env;if(error){console.log("An error occured while starting the Application server : ",error);(0,_validator.safeCall)(_index.onServerError);return;}if(env==="development")console.log((0,_picocolors.green)("Compiled successfully!"));console.log(`\nYou can now view ${APPLICATION} in the browser.\n`);console.log(_nodeUtil.default.format("\tLocal:".padEnd(8),(0,_picocolors.cyan)(`http://${NODE_SERVER_HOSTNAME}:${NODE_SERVER_PORT}`)));if(env==="development"){console.log("\nNote that the development build is not optimized.");console.log("To create a production build, use "+(0,_picocolors.cyan)("npm run build"));}console.log("\nFind out more about deployment here:");console.log((0,_picocolors.yellow)("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));});};// ─── Port check ───────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Verifies the target port is free before we attempt to bind.
|
|
44
|
+
* Probing with a temporary server gives a clear, actionable error message
|
|
45
|
+
* instead of a cryptic EADDRINUSE from Express.
|
|
46
|
+
*/const checkPortAvailability=(port,host)=>{return new Promise((resolve,reject)=>{const tester=_net.default.createServer().once("error",err=>{tester.close(()=>{if(err.code==="EADDRINUSE"){reject(new Error(`Port ${port} is already in use on ${host}. Please free the port or set a different NODE_SERVER_PORT.`));}else{reject(err);}});}).once("listening",()=>{tester.close(()=>resolve());}).listen(port,host);});};// ─── Startup ──────────────────────────────────────────────────────────────────
|
|
47
|
+
checkPortAvailability(port,host).then(()=>{if(process.env.NODE_ENV==="development"){if(_fs.default.existsSync(statsPath)){// loadable-stats.json already exists (e.g. dev server restarted mid-session):
|
|
48
|
+
// start immediately and clear the module cache on every subsequent webpack rebuild
|
|
49
|
+
// so SSR always uses the latest client chunks.
|
|
50
|
+
watcher.on("change",()=>{clearServerCache();});startServer();}else{// First boot — webpack hasn't finished the initial build yet.
|
|
51
|
+
// Wait for loadable-stats.json to be created before starting the server,
|
|
52
|
+
// otherwise @loadable/server would fail trying to read chunk metadata.
|
|
53
|
+
watcher.on("add",()=>{startServer();});}// Watch the app's /server directory for source changes.
|
|
54
|
+
// Any modification, addition, or deletion of a server-side file triggers
|
|
55
|
+
// a full server restart so the new code is loaded via a fresh require().
|
|
56
|
+
const serverPath=_path.default.join(process.env.src_path,"server");const serverWatcher=_chokidar.default.watch(serverPath,{persistent:true,ignoreInitial:true,// Don't trigger on initial scan
|
|
57
|
+
ignored:/node_modules/});serverWatcher.on("change",filePath=>{clearServerCache(filePath);restartServer();});serverWatcher.on("add",()=>restartServer());serverWatcher.on("unlink",()=>restartServer());}else{startServer();}}).catch(err=>{console.error(`\n[Catalyst] Server startup failed: ${err.message}\n`);process.exit(1);});
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getUserAgentDetails=void 0;var _uaParserJs=_interopRequireDefault(require("ua-parser-js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const googleBots={"APIs-Google":"APIs-Google",AdSense:"Mediapartners-Google","AdsBot Mobile Web Android":"AdsBot-Google-Mobile","AdsBot Mobile Web":"AdsBot-Google-Mobile",AdsBot:"AdsBot-Google","Mobile AdSense":"Mediapartners-Google","Mobile Apps Android":"AdsBot-Google-Mobile-Apps",Feedfetcher:"FeedFetcher-Google","Google Read Aloud":"Google-Read-Aloud","Duplex on the Web":"DuplexWeb-Google","Google Favicon":"Google Favicon","Web Light":"googleweblight","Amp crawler":"Google-AMPHTM",Googlebot:"Googlebot"}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getUserAgentDetails=void 0;var _uaParserJs=_interopRequireDefault(require("ua-parser-js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const googleBots={"APIs-Google":"APIs-Google",AdSense:"Mediapartners-Google","AdsBot Mobile Web Android":"AdsBot-Google-Mobile","AdsBot Mobile Web":"AdsBot-Google-Mobile",AdsBot:"AdsBot-Google","Mobile AdSense":"Mediapartners-Google","Mobile Apps Android":"AdsBot-Google-Mobile-Apps",Feedfetcher:"FeedFetcher-Google","Google Read Aloud":"Google-Read-Aloud","Duplex on the Web":"DuplexWeb-Google","Google Favicon":"Google Favicon","Web Light":"googleweblight","Amp crawler":"Google-AMPHTM",Googlebot:"Googlebot","Google Inspection Tool":"Google-InspectionTool","Google Agent":"Google-Agent"};// AI Crawler bots - for improved SEO with AI search engines
|
|
2
|
+
const aiCrawlerBots={// OpenAI/ChatGPT bots
|
|
3
|
+
GPTBot:"GPTBot","ChatGPT-User":"ChatGPT-User","OAI-SearchBot":"OAI-SearchBot",// Anthropic/Claude bots
|
|
4
|
+
ClaudeBot:"ClaudeBot","Claude-User":"Claude-User","Claude-SearchBot":"Claude-SearchBot",// Perplexity bots
|
|
5
|
+
PerplexityBot:"PerplexityBot","Perplexity-User":"Perplexity-User",// Microsoft Bing bot (also powers Bing Chat/Copilot)
|
|
6
|
+
Bingbot:"bingbot",// Meta Crawler bot
|
|
7
|
+
MetaBot:"meta-externalagent"};/**
|
|
2
8
|
* check if user agent contains bot data
|
|
3
9
|
* @param {string} ua - user agent
|
|
4
10
|
* @return {string|null}
|
|
5
|
-
*/const getGoogleBot=ua=>{for(let key in googleBots){if(ua.includes(googleBots[key]))return key;}return null;};/**
|
|
11
|
+
*/const getGoogleBot=ua=>{for(let key in googleBots){if(ua.includes(googleBots[key]))return key;}return null;};const getAICrawlerBot=ua=>{for(let key in aiCrawlerBots){if(ua.includes(aiCrawlerBots[key]))return key;}return null;};/**
|
|
6
12
|
* returns object which contains google bot and user-agent info
|
|
7
13
|
* @param {string} ua - user agent
|
|
8
14
|
* @return {object} - returns user agent
|
|
9
|
-
*/const getUserAgentDetails=ua=>{const agentDetails=(0,_uaParserJs.default)(ua);const googleBot=getGoogleBot(ua);return{...agentDetails,googleBot};};exports.getUserAgentDetails=getUserAgentDetails;
|
|
15
|
+
*/const getUserAgentDetails=ua=>{const agentDetails=(0,_uaParserJs.default)(ua);const googleBot=getGoogleBot(ua);const aiBot=getAICrawlerBot(ua);return{...agentDetails,googleBot,aiBot};};exports.getUserAgentDetails=getUserAgentDetails;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const pc=require("picocolors");const handleError=e=>{console.log(pc.red("Failed to start server: "),e);};const safeCall=(fn,...args)=>{try{if(!fn)return;if(typeof fn!=="function"){console.log(pc.red("Invalid lifecycle method defined in server/index.js"));return;}fn(...args);}catch(e){console.log(pc.red(
|
|
1
|
+
"use strict";const pc=require("picocolors");const handleError=e=>{console.log(pc.red("Failed to start server: "),e);};const safeCall=(fn,...args)=>{try{if(!fn)return;if(typeof fn!=="function"){console.log(pc.red("Invalid lifecycle method defined in server/index.js"));return;}fn(...args);}catch(e){console.log(pc.red("Failed to execute lifecycle method: "),fn.name,e);}};const validatePreInitServer=fn=>{try{if(!fn)throw new Error("preServerInit named function should be defined in server/index.js");if(typeof fn!=="function")throw new Error("preServerInit should be function present in server/index.js");return true;}catch(e){handleError(e);}};const validateMiddleware=fn=>{try{if(!fn)throw new Error("addMiddlewares named function not found in server/server.js");if(typeof fn!=="function")throw new Error("addMiddlewares should be function present in server/server.js");return true;}catch(e){handleError(e);}};const validateReducerFunction=fn=>{try{if(!fn)throw new Error("reducer not found in src/js/containers/App/reducer");if(typeof fn!=="function")throw new Error("reducer should present in src/js/containers/App/reducer");return true;}catch(e){handleError(e);}};const validateConfigFile=obj=>{try{if(!obj)throw new Error("config not found in config folder");if(typeof obj!=="object")throw new Error("config object should be exported from config folder inside your project root directory");if(typeof obj==="object"){const requiredConfigKeys={NODE_SERVER_HOSTNAME:"",NODE_SERVER_PORT:"",WEBPACK_DEV_SERVER_HOSTNAME:"",WEBPACK_DEV_SERVER_PORT:"",BUILD_OUTPUT_PATH:"",PUBLIC_STATIC_ASSET_PATH:"",PUBLIC_STATIC_ASSET_URL:"",CLIENT_ENV_VARIABLES:[],ANALYZE_BUNDLE:""};for(let key in requiredConfigKeys){if(!(key in obj))throw new Error(`${key} key not found inside config.json`);}}return true;}catch(e){handleError(e);}};const validatePackageJson=obj=>{try{if(!obj)throw new Error("package.json not found in the project");if(typeof obj!=="object")throw new Error("package.json should be defined in project root directory");return true;}catch(e){handleError(e);}};const validateModuleAlias=obj=>{try{if(!obj)throw new Error("moduleAliases not found in package.json file present in root directory.");if(typeof obj!=="object")throw new Error("moduleAliases named object should be exported from package.json");if(typeof obj==="object"){const requiredModuleAliases={"@api":"api.js","@containers":"src/js/containers","@server":"server","@config":"config","@css":"src/static/css","@routes":"src/js/routes/"};for(let key in requiredModuleAliases){if(key.includes("catalyst"))throw new Error(`Catalyst keyword is restricted for defining aliases`);if(!(key in obj))throw new Error(`${key} module alias not defined inside package.json`);}}return true;}catch(e){handleError(e);}};const validateConfigureStore=fn=>{try{if(!fn)throw new Error("configureStore not found in file src/js/store/index.js");if(typeof fn!=="function")throw new Error("configureStore should be function exported from src/js/store/index.js");return true;}catch(e){handleError(e);}};const validateGetRoutes=fn=>{try{if(!fn)throw new Error("getRoutes not found in file src/js/routes/utils.js");if(typeof fn!=="function")throw new Error("getRoutes should be function exported from src/js/routers/index.js");return true;}catch(e){handleError(e);}};const validateCustomDocument=fn=>{try{if(!fn)throw new Error("document not found in file server/document.js");if(typeof fn!=="function")throw new Error("document should be a react component exported from server/document.js");return true;}catch(e){handleError(e);}};module.exports={safeCall,validateConfigFile,validateConfigureStore,validateCustomDocument,validateGetRoutes,validatePackageJson,validateReducerFunction,validateModuleAlias,validatePreInitServer,validateMiddleware};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=removeClientCodePlugin;/**
|
|
2
2
|
* @description babel plugin used to remove unwanted code from the build.
|
|
3
3
|
* @return babel plugin object
|
|
4
|
-
*/function
|
|
4
|
+
*/function removeClientCodePlugin(){return{name:"ast-transform",// not required
|
|
5
5
|
visitor:{ImportDefaultSpecifier(path){path.parentPath.parent.body=path.parentPath.parent.body.map(astStructure=>{if(astStructure.type==="VariableDeclaration"){if(astStructure.declarations[0]?.init?.type==="ArrowFunctionExpression"&&astStructure.declarations[0]?.init?.body?.body?.length>0){astStructure.declarations[0].init.body.body=astStructure.declarations[0].init.body.body.filter(node=>!(node?.type==="ExpressionStatement"&&node?.expression?.callee?.name==="useEffect"));}}if(astStructure?.type==="FunctionDeclaration"&&astStructure?.body?.body?.length>0){astStructure.body.body=astStructure.body.body.filter(node=>!(node?.type==="ExpressionStatement"&&node?.expression?.callee?.name==="useEffect"));}return astStructure;});}}};}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=removeSsrCodePlugin;/**
|
|
2
2
|
* @description babel plugin used to remove unwanted code from the build.
|
|
3
3
|
* @return babel plugin object
|
|
4
|
-
*/function
|
|
4
|
+
*/function removeSsrCodePlugin(){return{name:"ast-transform",// not required
|
|
5
5
|
visitor:{ImportDefaultSpecifier(path){path.parentPath.parent.body=path.parentPath.parent.body.filter(val=>val?.expression?.left?.property?.name!=="serverSideFunction"&&val?.expression?.left?.property?.name!=="serverFetcher");}}};}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _webpackConfig=_interopRequireDefault(require("@catalyst/template/webpackConfig.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const isCompilerEnabled=!!_webpackConfig.default.reactCompiler;const reactCompilerOptions=typeof _webpackConfig.default.reactCompiler==="object"?_webpackConfig.default.reactCompiler:{target:"
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _webpackConfig=_interopRequireDefault(require("@catalyst/template/webpackConfig.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const isCompilerEnabled=!!_webpackConfig.default.reactCompiler;const reactCompilerOptions=typeof _webpackConfig.default.reactCompiler==="object"?_webpackConfig.default.reactCompiler:{target:"19"};var _default=exports.default={babelrc:false,presets:[["@babel/preset-typescript",{isTSX:true,allExtensions:true}],["@babel/preset-env",{targets:{browsers:"last 2 versions"}}],["@babel/preset-react",{runtime:"automatic"}]],plugins:[...(isCompilerEnabled?[["babel-plugin-react-compiler",reactCompilerOptions]]:[]),"@loadable/babel-plugin"],env:{production:{plugins:[require("./babel-plugins/remove-ssr.plugin").default]},test:{presets:["@babel/preset-react"]},development:{plugins:["react-refresh/babel"]}},ignore:["__TEST__"]};
|