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.
Files changed (167) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/globals.d.ts +7 -0
  6. package/dist/index.jsx +8 -0
  7. package/dist/logger.js +74 -3
  8. package/dist/native/androidProject/app/build.gradle.kts +45 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
  12. package/dist/native/androidProject/settings.gradle.kts +10 -0
  13. package/dist/native/androidSetup.js +300 -8
  14. package/dist/native/bridge/WebBridge.js +481 -59
  15. package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
  16. package/dist/native/bridge/errors.js +250 -26
  17. package/dist/native/bridge/hooks/index.js +132 -1
  18. package/dist/native/bridge/hooks/useAI.js +94 -0
  19. package/dist/native/bridge/hooks/useCamera.js +347 -9
  20. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  21. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  22. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  23. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  24. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  25. package/dist/native/bridge/hooks/useIntent.js +95 -1
  26. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  27. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  28. package/dist/native/bridge/hooks/useNotification.js +318 -2
  29. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  30. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  31. package/dist/native/bridge/hooks/utils.js +27 -1
  32. package/dist/native/bridge/hooks.js +16 -1
  33. package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
  34. package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
  35. package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
  36. package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
  37. package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
  38. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
  39. package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
  40. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
  41. package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
  42. package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
  43. package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
  44. package/dist/native/bridge/perf/core/constants.js +103 -20
  45. package/dist/native/bridge/perf/core/measure.js +179 -30
  46. package/dist/native/bridge/perf/core/session.js +89 -23
  47. package/dist/native/bridge/perf/core/store.js +554 -1
  48. package/dist/native/bridge/perf/core/summary.js +174 -1
  49. package/dist/native/bridge/perf/core/traceExport.js +240 -1
  50. package/dist/native/bridge/perf/core/waterfalls.js +146 -1
  51. package/dist/native/bridge/perf/index.js +359 -38
  52. package/dist/native/bridge/safeArea.js +53 -5
  53. package/dist/native/bridge/standardizedHooks.js +595 -82
  54. package/dist/native/bridge/useBaseHook.js +274 -38
  55. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  56. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  57. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  58. package/dist/native/buildAndroid/assets.js +510 -13
  59. package/dist/native/buildAndroid/build.js +296 -19
  60. package/dist/native/buildAndroid/config.js +87 -1
  61. package/dist/native/buildAndroid/index.js +406 -1
  62. package/dist/native/buildAppAndroid.js +34 -2
  63. package/dist/native/buildAppIos.js +62 -2
  64. package/dist/native/buildIos/assets.js +609 -8
  65. package/dist/native/buildIos/build.js +639 -20
  66. package/dist/native/buildIos/config.js +294 -12
  67. package/dist/native/buildIos/index.js +343 -6
  68. package/dist/native/buildIos/plugins.js +343 -16
  69. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  70. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  71. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  72. package/dist/native/internalPluginUtils.js +232 -7
  73. package/dist/native/package.json +3 -0
  74. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  75. package/dist/native/pluginComposerAndroid.js +407 -23
  76. package/dist/native/pluginComposerIos.js +435 -11
  77. package/dist/native/renameAndroidProject.js +1094 -88
  78. package/dist/native/setupEmulatorIos.js +289 -22
  79. package/dist/native/setupServer.js +160 -19
  80. package/dist/native/terminalProgress.js +199 -11
  81. package/dist/native/utils.js +193 -15
  82. package/dist/offline/catalyst-sw.js +145 -1
  83. package/dist/offline/registerServiceWorker.js +33 -1
  84. package/dist/otel.js +375 -46
  85. package/dist/router/ClientRouter.js +14 -2
  86. package/dist/router/ServerRouter.js +9 -1
  87. package/dist/router.js +1 -1
  88. package/dist/scripts/build.js +112 -3
  89. package/dist/scripts/checkVersion.js +3 -1
  90. package/dist/scripts/cleanCache.js +30 -1
  91. package/dist/scripts/generateOfflineManifest.js +116 -1
  92. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  93. package/dist/scripts/plugins.js +309 -1
  94. package/dist/scripts/preServerInit.js +13 -0
  95. package/dist/scripts/scriptUtils.js +54 -3
  96. package/dist/scripts/serve.js +51 -4
  97. package/dist/scripts/start.js +47 -10
  98. package/dist/scripts/validator.js +149 -1
  99. package/dist/sentry.cjs +127 -0
  100. package/dist/sentry.js +125 -5
  101. package/dist/server/expressServer.js +296 -5
  102. package/dist/server/manifestCache.js +36 -0
  103. package/dist/server/renderer/ChunkExtractor.js +144 -0
  104. package/dist/server/renderer/document/Body.jsx +59 -0
  105. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  106. package/dist/server/renderer/document/Head.jsx +60 -0
  107. package/dist/server/renderer/document/index.jsx +2 -0
  108. package/dist/server/renderer/extract.js +149 -31
  109. package/dist/server/renderer/handler.jsx +450 -0
  110. package/dist/server/renderer/index.js +8 -1
  111. package/dist/server/renderer/render.js +47 -0
  112. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  113. package/dist/server/utils/cjsRequire.js +10 -0
  114. package/dist/server/utils/userAgentUtil.js +87 -9
  115. package/dist/server/utils/validator.js +164 -1
  116. package/dist/vite/FastRefresh.jsx +20 -0
  117. package/dist/vite/inject-cache-key-plugin.js +276 -0
  118. package/dist/vite/loadCustomViteConfig.js +22 -0
  119. package/dist/vite/manifest-categorization-plugin.js +455 -0
  120. package/dist/vite/node-loader.mjs +458 -0
  121. package/dist/vite/scssParams.js +21 -0
  122. package/dist/vite/vite.config.client.js +130 -0
  123. package/dist/vite/vite.config.js +498 -0
  124. package/dist/vite/vite.config.server.js +84 -0
  125. package/dist/web-router/components/MetaTag.jsx +34 -0
  126. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  127. package/dist/web-router/components/Split.jsx +200 -0
  128. package/dist/web-router/components/SplitInview.jsx +106 -0
  129. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  130. package/dist/web-router/context.jsx +12 -0
  131. package/dist/web-router/hooks.jsx +72 -0
  132. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  133. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  134. package/dist/web-router/types/context.d.ts +14 -0
  135. package/dist/web-router/types/hooks.d.ts +1 -0
  136. package/dist/web-router/types/index.d.ts +3 -0
  137. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  138. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  139. package/mcp_v2/conversion-tasks.json +35 -17
  140. package/mcp_v2/knowledge-base.json +176 -17
  141. package/mcp_v2/lib/helpers.js +10 -0
  142. package/mcp_v2/mcp.js +40 -8
  143. package/mcp_v2/package.json +1 -0
  144. package/mcp_v2/tools/build.js +161 -37
  145. package/mcp_v2/tools/conversion.js +106 -5
  146. package/mcp_v2/tools/tasks.js +18 -14
  147. package/package.json +35 -68
  148. package/babel.config.js +0 -32
  149. package/dist/index.js +0 -1
  150. package/dist/scripts/devBuild.js +0 -3
  151. package/dist/scripts/devServe.js +0 -3
  152. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  153. package/dist/scripts/registerAliases.js +0 -6
  154. package/dist/server/renderer/document/Body.js +0 -11
  155. package/dist/server/renderer/document/Head.js +0 -9
  156. package/dist/server/renderer/document/index.js +0 -1
  157. package/dist/server/renderer/handler.js +0 -47
  158. package/dist/server/startServer.js +0 -57
  159. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  160. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  161. package/dist/webpack/babel.config.client.js +0 -1
  162. package/dist/webpack/babel.config.ssr.js +0 -1
  163. package/dist/webpack/base.babel.js +0 -7
  164. package/dist/webpack/development.client.babel.js +0 -10
  165. package/dist/webpack/production.client.babel.js +0 -4
  166. package/dist/webpack/production.ssr.babel.js +0 -8
  167. package/dist/webpack/scssParams.js +0 -9
@@ -0,0 +1,498 @@
1
+ import { defineConfig, transformWithEsbuild } from "vite"
2
+ import react from "@vitejs/plugin-react"
3
+ import svgr from "vite-plugin-svgr"
4
+ import { builtinModules, createRequire } from "node:module"
5
+
6
+ /**
7
+ * Treat all .scss imports as CSS modules (webpack compat).
8
+ * Webpack's css-loader with modules:true scopes every .scss file.
9
+ * Vite only does this for files named .module.scss.
10
+ * This plugin renames the resolved ID so Vite's CSS pipeline treats them as modules.
11
+ */
12
+ function scssModulesPlugin() {
13
+ return {
14
+ name: "vite-plugin-scss-as-modules",
15
+ enforce: "pre",
16
+ async resolveId(source, importer, options) {
17
+ if (!importer) return null
18
+
19
+ if (!source.endsWith(".scss") || source.includes(".module.")) return null
20
+
21
+ const resolved = await this.resolve(source, importer, { ...options, skipSelf: true })
22
+ if (!resolved || resolved.id.includes("node_modules") || resolved.id.includes("src/static/css"))
23
+ return null
24
+
25
+ return resolved.id.replace(/\.scss$/, ".module.scss")
26
+ },
27
+ load(id) {
28
+ const cleanId = id.split("?")[0]
29
+ if (cleanId.endsWith(".module.scss") && !fs.existsSync(cleanId)) {
30
+ const originalPath = cleanId.replace(/\.module\.scss$/, ".scss")
31
+ if (fs.existsSync(originalPath)) {
32
+ this.addWatchFile(originalPath)
33
+ return fs.readFileSync(originalPath, "utf-8")
34
+ }
35
+ }
36
+ return null
37
+ },
38
+ handleHotUpdate({ file, server }) {
39
+ if (file.endsWith(".scss") && !file.endsWith(".module.scss")) {
40
+ const virtualId = file.replace(/\.scss$/, ".module.scss")
41
+ const virtualMod = server.moduleGraph.getModuleById(virtualId)
42
+ if (virtualMod) {
43
+ server.moduleGraph.invalidateModule(virtualMod)
44
+ return [virtualMod]
45
+ }
46
+ }
47
+ },
48
+ }
49
+ }
50
+
51
+ function jsxInJsPlugin() {
52
+ return {
53
+ name: "vite-plugin-jsx-in-js",
54
+ enforce: "pre",
55
+ async transform(code, id) {
56
+ if (!id.endsWith(".js")) return null
57
+ if (id.includes("node_modules")) return null
58
+ if (!/</.test(code)) return null
59
+ return transformWithEsbuild(code, id, { loader: "jsx" })
60
+ },
61
+ }
62
+ }
63
+
64
+ const emptyStoreModuleId = "\0catalyst-empty-store"
65
+ const templateStoreImport = "@catalyst/template/src/js/store/index.js"
66
+
67
+ function optionalTemplateStorePlugin() {
68
+ return {
69
+ name: "vite-plugin-optional-template-store",
70
+ enforce: "pre",
71
+ resolveId(source) {
72
+ const storePath = path.join(process.env.src_path, "src/js/store/index.js")
73
+ const isTemplateStoreImport = source === templateStoreImport || path.resolve(source) === storePath
74
+ if (!isTemplateStoreImport) return null
75
+ if (fs.existsSync(storePath)) return null
76
+
77
+ return emptyStoreModuleId
78
+ },
79
+ load(id) {
80
+ if (id !== emptyStoreModuleId) return null
81
+
82
+ return `
83
+ export default function configureStore(initialState = {}) {
84
+ return {
85
+ getState: () => initialState,
86
+ dispatch: (action) => action,
87
+ subscribe: () => () => {},
88
+ replaceReducer: () => {},
89
+ }
90
+ }
91
+ `
92
+ },
93
+ }
94
+ }
95
+
96
+ import { fileURLToPath } from "url"
97
+ import { dirname } from "path"
98
+ const __filename = fileURLToPath(import.meta.url)
99
+ const __dirname = dirname(__filename)
100
+
101
+ import path from "path"
102
+ import fs from "fs"
103
+
104
+ const packageJsonConfig = path.resolve(process.env.src_path, "package.json")
105
+ const catalystPackageJsonConfig = path.resolve(__dirname, "../../package.json")
106
+
107
+ let packageJsonContent, catalystPackageJsonContent
108
+ let _moduleAliases = {},
109
+ catalyst_moduleAliases = {}
110
+
111
+ try {
112
+ packageJsonContent = fs.readFileSync(packageJsonConfig, "utf8")
113
+ const packageJson = JSON.parse(packageJsonContent)
114
+ _moduleAliases = packageJson._moduleAliases || {}
115
+ } catch (error) {
116
+ console.warn(`Failed to read or parse package.json from ${packageJsonConfig}:`, error.message)
117
+ }
118
+
119
+ try {
120
+ catalystPackageJsonContent = fs.readFileSync(catalystPackageJsonConfig, "utf8")
121
+ const catalystPackageJson = JSON.parse(catalystPackageJsonContent)
122
+ catalyst_moduleAliases = catalystPackageJson._moduleAliases || {}
123
+ } catch (error) {
124
+ console.warn(
125
+ `Failed to read or parse catalyst package.json from ${catalystPackageJsonConfig}:`,
126
+ error.message
127
+ )
128
+ }
129
+
130
+ const allAliases = { ..._moduleAliases, ...catalyst_moduleAliases }
131
+
132
+ import { imageUrl, fontUrl } from "./scssParams.js"
133
+
134
+ // Tailwind v4's zero-config scanner (`@tailwindcss/postcss`) walks up from
135
+ // `dirname(from)` — the directory of the file PostCSS is currently compiling —
136
+ // unless given an explicit `base`. Vite passes each CSS file's own resolved
137
+ // path as `from`, so for catalyst-core's conventional layout (global
138
+ // stylesheet under `src/static/css`, components under `src/js`, no shared
139
+ // parent short of the app root) the scanner never reaches component files and
140
+ // silently drops every class actually used in JSX. Webpack's postcss-loader
141
+ // doesn't populate `from` the same way, which is why this only regresses
142
+ // under Vite. We resolve the app's own postcss.config.js ourselves and patch
143
+ // in `base: process.env.src_path` so the scan root covers the whole app.
144
+ // Only the `{ pluginName: options }` shorthand can be patched this way —
145
+ // array-form configs (`[require(...)()]`) have already closed over their
146
+ // options — so anything else is left alone and Vite falls back to its normal
147
+ // auto-loaded postcss.config.js resolution.
148
+ const resolvePostcssPlugins = () => {
149
+ const configPath = path.join(process.env.src_path, "postcss.config.js")
150
+ if (!fs.existsSync(configPath)) return null
151
+
152
+ const appRequire = createRequire(configPath)
153
+
154
+ try {
155
+ const userConfig = appRequire(configPath)
156
+ const pluginsConfig = userConfig?.plugins
157
+
158
+ if (!pluginsConfig || Array.isArray(pluginsConfig) || typeof pluginsConfig !== "object") {
159
+ return null
160
+ }
161
+
162
+ return Object.entries(pluginsConfig).map(([name, options]) => {
163
+ const plugin = appRequire(name)
164
+ const finalOptions =
165
+ name === "@tailwindcss/postcss" && (!options || options.base === undefined)
166
+ ? { ...options, base: process.env.src_path }
167
+ : options
168
+ return plugin(finalOptions)
169
+ })
170
+ } catch (error) {
171
+ console.warn(`Failed to patch postcss config at ${configPath}:`, error.message)
172
+ return null
173
+ }
174
+ }
175
+
176
+ const postcssPlugins = resolvePostcssPlugins()
177
+
178
+ const alias = () => {
179
+ if (!allAliases || typeof allAliases !== "object") {
180
+ return {}
181
+ }
182
+
183
+ return Object.keys(allAliases).reduce((moduleEnvMap, alias) => {
184
+ if (allAliases[alias] && typeof allAliases[alias] === "string") {
185
+ try {
186
+ const aliasPath = path.join(process.env.src_path, ...allAliases[alias].split("/"))
187
+ moduleEnvMap[alias] = aliasPath
188
+ } catch (error) {
189
+ console.warn(`Failed to configure alias ${alias}:`, error.message)
190
+ }
191
+ }
192
+ return moduleEnvMap
193
+ }, {})
194
+ }
195
+
196
+ export const getClientEnvVariables = () => {
197
+ const clientEnvVars = process.env.CLIENT_ENV_VARIABLES
198
+
199
+ if (!clientEnvVars) {
200
+ return {}
201
+ }
202
+
203
+ // Parse CLIENT_ENV_VARIABLES if it's a JSON string
204
+ const allowedVars = typeof clientEnvVars === "string" ? JSON.parse(clientEnvVars) : clientEnvVars
205
+
206
+ // Create define object with only allowed environment variables
207
+ const envVarDefinitions = {}
208
+
209
+ allowedVars.forEach((varName) => {
210
+ if (process.env[varName] !== undefined) {
211
+ // Define as process.env.VARIABLE_NAME for client-side usage
212
+ envVarDefinitions[`process.env.${varName}`] = JSON.stringify(process.env[varName])
213
+ }
214
+ })
215
+ envVarDefinitions[`process.env.src_path`] = JSON.stringify(process.env["src_path"])
216
+ envVarDefinitions[`process.env.BUILD_OUTPUT_PATH`] = JSON.stringify(process.env["BUILD_OUTPUT_PATH"])
217
+ envVarDefinitions[`process.env.PUBLIC_STATIC_ASSET_PATH`] = JSON.stringify(
218
+ process.env["PUBLIC_STATIC_ASSET_PATH"]
219
+ )
220
+ envVarDefinitions[`process.env.PUBLIC_STATIC_ASSET_URL`] = JSON.stringify(
221
+ process.env["PUBLIC_STATIC_ASSET_URL"]
222
+ )
223
+
224
+ // Derive AI_PUBLIC_CONFIG: only the browser block from AI_CONFIG, no API keys
225
+ let aiConfig = {}
226
+ try {
227
+ const parsedConfig = JSON.parse(process.env.AI_CONFIG || "{}")
228
+ if (parsedConfig && typeof parsedConfig === "object" && !Array.isArray(parsedConfig)) {
229
+ aiConfig = parsedConfig
230
+ } else {
231
+ console.warn("[catalyst-core] AI_CONFIG must be a JSON object, ignoring it")
232
+ }
233
+ } catch (e) {
234
+ console.warn(`[catalyst-core] Invalid AI_CONFIG JSON, ignoring: ${e.message}`)
235
+ }
236
+ const aiPublicConfig = aiConfig.browser ? { browser: aiConfig.browser } : {}
237
+ envVarDefinitions[`process.env.AI_PUBLIC_CONFIG`] = JSON.stringify(JSON.stringify(aiPublicConfig))
238
+
239
+ // Detect which catalyst-* AI packages are installed for the app, including
240
+ // hoisted/workspace installs — not just a direct app-local node_modules copy.
241
+ // Results are injected as __CATALYST_PACKAGES__.* literals so dead code
242
+ // referencing missing packages can be branched around at runtime.
243
+ const appRequire = createRequire(path.join(process.env.src_path || process.cwd(), "package.json"))
244
+ let aiPackageInstalled = false
245
+ try {
246
+ appRequire.resolve("catalyst-ai")
247
+ aiPackageInstalled = true
248
+ } catch {
249
+ aiPackageInstalled = false
250
+ }
251
+ envVarDefinitions["__CATALYST_PACKAGES__"] = JSON.stringify({
252
+ ai: aiPackageInstalled,
253
+ })
254
+
255
+ return envVarDefinitions
256
+ }
257
+
258
+ const isProduction = process.env.NODE_ENV === "production"
259
+
260
+ const getScssResourcesImport = () => {
261
+ const resourcesDir = path.join(process.env.src_path, "src/static/css/resources")
262
+ const indexPath = path.join(resourcesDir, "index.scss")
263
+ const variablesPath = path.join(resourcesDir, "_variables.scss")
264
+
265
+ if (fs.existsSync(indexPath)) {
266
+ return '@import "@css/resources/index.scss"; '
267
+ }
268
+
269
+ if (fs.existsSync(variablesPath)) {
270
+ return '@import "@css/resources/_variables.scss"; '
271
+ }
272
+
273
+ return ""
274
+ }
275
+
276
+ // Pre-bundling upfront avoids Vite discovering deps at runtime which causes
277
+ // cascading full-page reloads and "stuck in loading" on dev server.
278
+ const browserOptimizeDeps = [
279
+ "react",
280
+ "react-dom",
281
+ "react-dom/client",
282
+ "react-redux",
283
+ "react-router-dom",
284
+ "redux",
285
+ "redux-thunk",
286
+ "axios",
287
+ "react-helmet-async",
288
+ "react-google-recaptcha",
289
+ "react-fast-compare",
290
+ "history",
291
+ "lottie-web",
292
+ // Pre-bundle alongside React so esbuild marks react/react-router as external
293
+ // and avoids a duplicate React instance (which causes useContext null errors)
294
+ "@tata1mg/slowboi-react",
295
+ "invariant",
296
+ "shallowequal",
297
+ "prop-types",
298
+ "redux-logger",
299
+ "framer-motion",
300
+ "recharts",
301
+ "react-modal",
302
+ "react-datepicker",
303
+ "react-intersection-observer",
304
+ "react-markdown",
305
+ "react-lottie",
306
+ "isomorphic-dompurify",
307
+ "ua-parser-js",
308
+ "qrcode.react",
309
+ "fast-average-color",
310
+ "react-dfp",
311
+ "web-vitals",
312
+ // Referenced by manualChunks in vite.config.client.js — pre-bundle so the
313
+ // first navigation that pulls them in doesn't trigger a full-page reload.
314
+ "react-loadable-visibility",
315
+ "react-detect-offline",
316
+ "react-side-effect",
317
+ "react-async-script",
318
+ "normalize.css",
319
+ ]
320
+
321
+ // Node-only / instrumentation dependencies that must never be bundled into the
322
+ // SSR output. OpenTelemetry is opt-in (enabled at runtime via OTEL_ENABLE), so
323
+ // these packages may not be installed in the consuming app. They are imported
324
+ // only by dist/otel.js, which is itself dynamically imported at runtime and only
325
+ // when OTEL_ENABLE=true (see server/renderer/handler.jsx). Marking them external
326
+ // lets the bundler skip resolving them; Node resolves them at runtime, but only
327
+ // if/when the otel chunk is actually loaded.
328
+ export const nodeOnlyExternalDeps = [
329
+ "elastic-apm-node",
330
+ "@grpc/grpc-js",
331
+ "@opentelemetry/api",
332
+ "@opentelemetry/core",
333
+ "@opentelemetry/resources",
334
+ "@opentelemetry/semantic-conventions",
335
+ "@opentelemetry/sdk-node",
336
+ "@opentelemetry/sdk-metrics",
337
+ "@opentelemetry/sdk-trace-base",
338
+ "@opentelemetry/sdk-trace-node",
339
+ "@opentelemetry/exporter-trace-otlp-grpc",
340
+ "@opentelemetry/exporter-trace-otlp-http",
341
+ "@opentelemetry/exporter-metrics-otlp-grpc",
342
+ "@opentelemetry/exporter-metrics-otlp-http",
343
+ "@opentelemetry/auto-instrumentations-node",
344
+ "@opentelemetry/instrumentation-http",
345
+ "@opentelemetry/instrumentation-express",
346
+ ]
347
+
348
+ // Predicate form for Rollup's `external`. Also matches the many transitive
349
+ // @opentelemetry/instrumentation-* packages that auto-instrumentations-node pulls
350
+ // in, without having to enumerate each one.
351
+ const nodeBuiltins = new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)])
352
+
353
+ export const isNodeOnlyExternal = (id) =>
354
+ nodeBuiltins.has(id) ||
355
+ id === "elastic-apm-node" ||
356
+ id === "@grpc/grpc-js" ||
357
+ id.startsWith("@opentelemetry/")
358
+
359
+ export default defineConfig({
360
+ // Parallel `vite build` (SSR + client) must use separate dirs or Vite will block on shared `node_modules/.vite`.
361
+ cacheDir: path.join(
362
+ process.env.src_path,
363
+ "node_modules",
364
+ process.env.CATALYST_VITE_CACHE_ID ? `.vite-${process.env.CATALYST_VITE_CACHE_ID}` : ".vite"
365
+ ),
366
+ ssr: {
367
+ // Keep selected React-side packages bundled for SSR (transformed by Vite, NOT pre-bundled)
368
+ // noExternal: ["@tata1mg/slowboi-react"],
369
+ // Ensure Node-only instrumentation and low-level Node deps stay external so
370
+ // the bundler never tries to resolve the optional (opt-in) OTEL packages.
371
+ external: nodeOnlyExternalDeps,
372
+ optimizeDeps: {
373
+ include: [
374
+ "invariant",
375
+ "react-fast-compare",
376
+ "shallowequal",
377
+ "prop-types",
378
+ "redux-thunk",
379
+ "redux-logger",
380
+ ],
381
+ // Prevent pre-bundling React ecosystem packages for SSR to avoid duplicate
382
+ // React instances. Pre-bundled copies in .vite/deps_ssr/ create a separate
383
+ // React instance from node_modules/react used by react-dom/server, causing
384
+ // "Invalid hook call" errors when hooks try to read a null dispatcher.
385
+ exclude: [
386
+ "react",
387
+ "react-dom",
388
+ "react-router-dom",
389
+ "@tata1mg/router",
390
+ // "@tata1mg/slowoi-react",
391
+ "catalyst-core/router/ClientRouter",
392
+ ],
393
+ esbuildOptions: {
394
+ format: "esm",
395
+ target: "node2022",
396
+ loader: {
397
+ ".js": "jsx",
398
+ },
399
+ },
400
+ },
401
+ },
402
+ plugins: [
403
+ optionalTemplateStorePlugin(),
404
+ scssModulesPlugin(),
405
+ jsxInJsPlugin(),
406
+ // `include` extends the default regex so Fast Refresh also wraps .js
407
+ // files (their JSX has already been transformed by jsxInJsPlugin above).
408
+ react({ include: /\.(mdx|js|jsx|ts|tsx)$/ }),
409
+ // Default: SVG → React component. Use `*.svg?url` (or `?raw`) for asset URL / raw source.
410
+ // Keep `*.svg?react` so explicit imports still work. Exclude node_modules so deps keep normal asset handling.
411
+ svgr({
412
+ include: ["**/*.svg", "**/*.svg?react"],
413
+ exclude: "**/node_modules/**",
414
+ }),
415
+ ],
416
+ resolve: {
417
+ alias: alias(),
418
+ // Ensure only one copy of React-related packages is used (prevents
419
+ // duplicate instances from hoisted/linked packages in monorepos)
420
+ dedupe: ["react", "react-dom", "react-router-dom", "@tata1mg/slowboi-react"],
421
+ },
422
+ define: {
423
+ ...getClientEnvVariables(),
424
+ },
425
+
426
+ build: {
427
+ outDir: path.join(process.env.src_path, process.env.BUILD_OUTPUT_PATH || "build"),
428
+ },
429
+
430
+ optimizeDeps: {
431
+ // Explicit entries so the dep scanner crawls the app's real import
432
+ // graph. Without this (SSR middleware mode has no index.html), Vite
433
+ // can't see dynamic `import()` chunks produced by split(), so every
434
+ // first-navigation discovers new deps and triggers a full reload.
435
+ entries: [
436
+ path.join(process.env.src_path, "client/index.js"),
437
+ path.join(process.env.src_path, "src/**/*.{js,jsx,ts,tsx}"),
438
+ ],
439
+ include: browserOptimizeDeps,
440
+ exclude: ["catalyst-core/router/ClientRouter"],
441
+ esbuildOptions: {
442
+ format: "esm",
443
+ target: "node2022",
444
+ loader: {
445
+ ".js": "jsx",
446
+ },
447
+ },
448
+ },
449
+
450
+ css: {
451
+ modules: {
452
+ localsConvention: "camelCase",
453
+ generateScopedName: "[name]__[local]___[hash:base64:5]",
454
+ },
455
+ ...(postcssPlugins ? { postcss: { plugins: postcssPlugins } } : {}),
456
+ preprocessorOptions: {
457
+ scss: {
458
+ additionalData: (content) =>
459
+ `${getScssResourcesImport()}$font_url: "${fontUrl()}"; $url_for: "${imageUrl()}";\n${content}`,
460
+ silenceDeprecations: [
461
+ "import",
462
+ "global-builtin",
463
+ "color-functions",
464
+ "if-function",
465
+ "slash-div",
466
+ ],
467
+ },
468
+ },
469
+ },
470
+ json: {
471
+ stringify: true,
472
+ },
473
+ assetsInclude: [
474
+ "**/*.png",
475
+ "**/*.jpg",
476
+ "**/*.gif",
477
+ "**/*.jpeg",
478
+ "**/*.ico",
479
+ "**/*.svg",
480
+ "**/*.ttf",
481
+ "**/*.eot",
482
+ "**/*.woff2",
483
+ ],
484
+
485
+ // Server configuration
486
+ server: {
487
+ hmr: !isProduction,
488
+ fs: {
489
+ allow: [process.env.src_path, __dirname],
490
+ },
491
+ },
492
+
493
+ // Preview configuration for production preview
494
+ preview: {
495
+ port: process.env.NODE_SERVER_PORT ? parseInt(process.env.NODE_SERVER_PORT) : 3005,
496
+ host: process.env.NODE_SERVER_HOSTNAME || "localhost",
497
+ },
498
+ })
@@ -0,0 +1,84 @@
1
+ import loadEnvironmentVariables from "../scripts/loadEnvironmentVariables.js"
2
+ loadEnvironmentVariables()
3
+ import { defineConfig } from "vite"
4
+ import baseConfig, { getClientEnvVariables, isNodeOnlyExternal } from "./vite.config.js"
5
+ import path from "path"
6
+ import { fileURLToPath } from "url"
7
+ import { dirname } from "path"
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+ import { injectCacheKeyPlugin } from "./inject-cache-key-plugin.js"
11
+ import { loadCustomViteConfig } from "./loadCustomViteConfig.js"
12
+
13
+ export default defineConfig(async () => {
14
+ const customViteConfig = await loadCustomViteConfig()
15
+
16
+ return {
17
+ ...baseConfig,
18
+ mode: "production",
19
+
20
+ // Add cache key injection plugin first to transform split calls
21
+ plugins: [
22
+ injectCacheKeyPlugin(),
23
+ ...(baseConfig.plugins || []),
24
+ ...(customViteConfig?.ssrPlugins || []),
25
+ ],
26
+
27
+ build: {
28
+ ...baseConfig.build,
29
+ outDir: path.join(process.env.src_path, process.env.BUILD_OUTPUT_PATH || "build"),
30
+ target: "es2022",
31
+ minify: "esbuild",
32
+ sourcemap: false,
33
+ manifest: false,
34
+ ssrManifest: false,
35
+
36
+ rollupOptions: {
37
+ // Belt-and-suspenders with ssr.external: ensures the opt-in OTEL /
38
+ // node-only packages (and their transitive @opentelemetry/* deps) are
39
+ // never resolved/bundled, even though they may not be installed.
40
+ external: isNodeOnlyExternal,
41
+ input: {
42
+ // Server entry point for SSR
43
+ server: path.join(__dirname, "../server/renderer/index.js"),
44
+ },
45
+ output: {
46
+ format: "es",
47
+ entryFileNames: (chunkInfo) => {
48
+ return chunkInfo.name === "server"
49
+ ? "server/index.js"
50
+ : "server/assets/[name]-[hash].js"
51
+ },
52
+ chunkFileNames: "server/assets/[name]-[hash].js",
53
+ assetFileNames: (assetInfo) => {
54
+ const extType = assetInfo.name.split(".").pop()
55
+ if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
56
+ return "server/assets/images/[name]-[hash][extname]"
57
+ }
58
+ if (/woff2?|eot|ttf|otf/i.test(extType)) {
59
+ return "server/assets/fonts/[name]-[hash][extname]"
60
+ }
61
+ if (/css/i.test(extType)) {
62
+ return "server/assets/css/[name]-[hash][extname]"
63
+ }
64
+ return "server/assets/[name]-[hash][extname]"
65
+ },
66
+ },
67
+ },
68
+
69
+ chunkSizeWarningLimit: 2000,
70
+ },
71
+
72
+ // Optimization for production
73
+ esbuild: {
74
+ legalComments: "none",
75
+ },
76
+
77
+ // Production-specific define
78
+ define: {
79
+ ...getClientEnvVariables(),
80
+ __DEV__: false,
81
+ "process.env.NODE_ENV": JSON.stringify("production"),
82
+ },
83
+ }
84
+ })
@@ -0,0 +1,34 @@
1
+ import React, { useContext, useState, useEffect } from "react"
2
+ import { HelmetProvider, Helmet } from "react-helmet-async"
3
+ import { OneMgRouterContext } from "../context.jsx"
4
+ import { useRouterData } from "./RouterDataProvider.jsx"
5
+ import { deleteHeadTagsByDataAttribute, getMetaData } from "../utils/metaDataUtils.jsx"
6
+ import { useLocation } from "react-router-dom"
7
+
8
+ /**
9
+ * @description renders meta tags component assigned to setMetaData
10
+ */
11
+ export const MetaTag = () => {
12
+ const { matchedRoutes } = useContext(OneMgRouterContext)
13
+ const routeData = useRouterData()
14
+ const location = useLocation()
15
+ const [metaTags, setMetaTags] = useState([<meta key={0}></meta>])
16
+
17
+ useEffect(() => {
18
+ const mergedMetaTags = getMetaData(matchedRoutes, routeData)
19
+ if (Array.isArray(mergedMetaTags) && mergedMetaTags.length > 0) {
20
+ setMetaTags(mergedMetaTags)
21
+ }
22
+ return () => deleteHeadTagsByDataAttribute("catalyst")
23
+ // eslint-disable-next-line react-hooks/exhaustive-deps
24
+ }, [location])
25
+
26
+ if (Array.isArray(metaTags) && metaTags.length >= 0) {
27
+ return (
28
+ <HelmetProvider>
29
+ <Helmet>{metaTags}</Helmet>
30
+ </HelmetProvider>
31
+ )
32
+ }
33
+ return <></>
34
+ }