catalyst-core 0.3.0-beta.1 → 0.3.0-beta.3
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/dist/globals.d.ts +7 -0
- package/dist/native/androidProject/app/build.gradle.kts +45 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
- package/dist/native/androidProject/settings.gradle.kts +10 -0
- package/dist/native/androidSetup.js +8 -6
- package/dist/native/bridge/constants/NativeInterfaces.js +15 -1
- package/dist/native/bridge/hooks/index.js +7 -0
- package/dist/native/bridge/hooks/useAI.js +94 -0
- package/dist/native/buildAndroid/assets.js +1 -1
- package/dist/native/buildAndroid/index.js +32 -0
- package/dist/native/buildIos/assets.js +1 -1
- package/dist/offline/catalyst-sw.js +1 -0
- package/dist/scripts/build.js +15 -11
- package/dist/scripts/generateOfflineManifest.js +1 -0
- package/dist/scripts/scriptUtils.js +1 -0
- package/dist/scripts/serve.js +24 -22
- package/dist/scripts/start.js +1 -3
- package/dist/server/expressServer.js +47 -0
- package/dist/server/renderer/document/Head.jsx +2 -0
- package/dist/server/renderer/extract.js +1 -0
- package/dist/server/renderer/handler.jsx +29 -11
- package/dist/vite/inject-cache-key-plugin.js +7 -0
- package/dist/vite/loadCustomViteConfig.js +1 -0
- package/dist/vite/manifest-categorization-plugin.js +2 -0
- package/dist/vite/node-loader.mjs +6 -0
- package/dist/vite/vite.config.js +83 -1
- package/mcp_v2/knowledge-base.json +52 -4
- package/mcp_v2/mcp.js +36 -4
- package/mcp_v2/tools/build.js +46 -18
- package/mcp_v2/tools/github.js +11 -0
- package/package.json +3 -3
package/dist/vite/vite.config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineConfig, transformWithEsbuild } from "vite"
|
|
2
2
|
import react from "@vitejs/plugin-react"
|
|
3
3
|
import svgr from "vite-plugin-svgr"
|
|
4
|
-
import { builtinModules } from "node:module"
|
|
4
|
+
import { builtinModules, createRequire } from "node:module"
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Treat all .scss imports as CSS modules (webpack compat).
|
|
@@ -113,6 +113,7 @@ try {
|
|
|
113
113
|
const packageJson = JSON.parse(packageJsonContent)
|
|
114
114
|
_moduleAliases = packageJson._moduleAliases || {}
|
|
115
115
|
} catch (error) {
|
|
116
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - packageJsonConfig is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
116
117
|
console.warn(`Failed to read or parse package.json from ${packageJsonConfig}:`, error.message)
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -122,6 +123,7 @@ try {
|
|
|
122
123
|
catalyst_moduleAliases = catalystPackageJson._moduleAliases || {}
|
|
123
124
|
} catch (error) {
|
|
124
125
|
console.warn(
|
|
126
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - catalystPackageJsonConfig is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
125
127
|
`Failed to read or parse catalyst package.json from ${catalystPackageJsonConfig}:`,
|
|
126
128
|
error.message
|
|
127
129
|
)
|
|
@@ -131,6 +133,52 @@ const allAliases = { ..._moduleAliases, ...catalyst_moduleAliases }
|
|
|
131
133
|
|
|
132
134
|
import { imageUrl, fontUrl } from "./scssParams.js"
|
|
133
135
|
|
|
136
|
+
// Tailwind v4's zero-config scanner (`@tailwindcss/postcss`) walks up from
|
|
137
|
+
// `dirname(from)` — the directory of the file PostCSS is currently compiling —
|
|
138
|
+
// unless given an explicit `base`. Vite passes each CSS file's own resolved
|
|
139
|
+
// path as `from`, so for catalyst-core's conventional layout (global
|
|
140
|
+
// stylesheet under `src/static/css`, components under `src/js`, no shared
|
|
141
|
+
// parent short of the app root) the scanner never reaches component files and
|
|
142
|
+
// silently drops every class actually used in JSX. Webpack's postcss-loader
|
|
143
|
+
// doesn't populate `from` the same way, which is why this only regresses
|
|
144
|
+
// under Vite. We resolve the app's own postcss.config.js ourselves and patch
|
|
145
|
+
// in `base: process.env.src_path` so the scan root covers the whole app.
|
|
146
|
+
// Only the `{ pluginName: options }` shorthand can be patched this way —
|
|
147
|
+
// array-form configs (`[require(...)()]`) have already closed over their
|
|
148
|
+
// options — so anything else is left alone and Vite falls back to its normal
|
|
149
|
+
// auto-loaded postcss.config.js resolution.
|
|
150
|
+
const resolvePostcssPlugins = () => {
|
|
151
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - process.env.src_path is the app root set by the framework's own build tooling, not request/user input.
|
|
152
|
+
const configPath = path.join(process.env.src_path, "postcss.config.js")
|
|
153
|
+
if (!fs.existsSync(configPath)) return null
|
|
154
|
+
|
|
155
|
+
const appRequire = createRequire(configPath)
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const userConfig = appRequire(configPath)
|
|
159
|
+
const pluginsConfig = userConfig?.plugins
|
|
160
|
+
|
|
161
|
+
if (!pluginsConfig || Array.isArray(pluginsConfig) || typeof pluginsConfig !== "object") {
|
|
162
|
+
return null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return Object.entries(pluginsConfig).map(([name, options]) => {
|
|
166
|
+
const plugin = appRequire(name)
|
|
167
|
+
const finalOptions =
|
|
168
|
+
name === "@tailwindcss/postcss" && (!options || options.base === undefined)
|
|
169
|
+
? { ...options, base: process.env.src_path }
|
|
170
|
+
: options
|
|
171
|
+
return plugin(finalOptions)
|
|
172
|
+
})
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - configPath is a build-time-resolved local path, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
175
|
+
console.warn(`Failed to patch postcss config at ${configPath}:`, error.message)
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const postcssPlugins = resolvePostcssPlugins()
|
|
181
|
+
|
|
134
182
|
const alias = () => {
|
|
135
183
|
if (!allAliases || typeof allAliases !== "object") {
|
|
136
184
|
return {}
|
|
@@ -139,9 +187,11 @@ const alias = () => {
|
|
|
139
187
|
return Object.keys(allAliases).reduce((moduleEnvMap, alias) => {
|
|
140
188
|
if (allAliases[alias] && typeof allAliases[alias] === "string") {
|
|
141
189
|
try {
|
|
190
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - process.env.src_path is the app root and allAliases comes from the developer's own package.json _moduleAliases config, not request/user input.
|
|
142
191
|
const aliasPath = path.join(process.env.src_path, ...allAliases[alias].split("/"))
|
|
143
192
|
moduleEnvMap[alias] = aliasPath
|
|
144
193
|
} catch (error) {
|
|
194
|
+
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - alias is a key from the developer's own package.json _moduleAliases config, not attacker-controlled; console.warn here does no printf-style substitution.
|
|
145
195
|
console.warn(`Failed to configure alias ${alias}:`, error.message)
|
|
146
196
|
}
|
|
147
197
|
}
|
|
@@ -177,6 +227,37 @@ export const getClientEnvVariables = () => {
|
|
|
177
227
|
process.env["PUBLIC_STATIC_ASSET_URL"]
|
|
178
228
|
)
|
|
179
229
|
|
|
230
|
+
// Derive AI_PUBLIC_CONFIG: only the browser block from AI_CONFIG, no API keys
|
|
231
|
+
let aiConfig = {}
|
|
232
|
+
try {
|
|
233
|
+
const parsedConfig = JSON.parse(process.env.AI_CONFIG || "{}")
|
|
234
|
+
if (parsedConfig && typeof parsedConfig === "object" && !Array.isArray(parsedConfig)) {
|
|
235
|
+
aiConfig = parsedConfig
|
|
236
|
+
} else {
|
|
237
|
+
console.warn("[catalyst-core] AI_CONFIG must be a JSON object, ignoring it")
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.warn(`[catalyst-core] Invalid AI_CONFIG JSON, ignoring: ${e.message}`)
|
|
241
|
+
}
|
|
242
|
+
const aiPublicConfig = aiConfig.browser ? { browser: aiConfig.browser } : {}
|
|
243
|
+
envVarDefinitions[`process.env.AI_PUBLIC_CONFIG`] = JSON.stringify(JSON.stringify(aiPublicConfig))
|
|
244
|
+
|
|
245
|
+
// Detect which catalyst-* AI packages are installed for the app, including
|
|
246
|
+
// hoisted/workspace installs — not just a direct app-local node_modules copy.
|
|
247
|
+
// Results are injected as __CATALYST_PACKAGES__.* literals so dead code
|
|
248
|
+
// referencing missing packages can be branched around at runtime.
|
|
249
|
+
const appRequire = createRequire(path.join(process.env.src_path || process.cwd(), "package.json"))
|
|
250
|
+
let aiPackageInstalled = false
|
|
251
|
+
try {
|
|
252
|
+
appRequire.resolve("catalyst-ai")
|
|
253
|
+
aiPackageInstalled = true
|
|
254
|
+
} catch {
|
|
255
|
+
aiPackageInstalled = false
|
|
256
|
+
}
|
|
257
|
+
envVarDefinitions["__CATALYST_PACKAGES__"] = JSON.stringify({
|
|
258
|
+
ai: aiPackageInstalled,
|
|
259
|
+
})
|
|
260
|
+
|
|
180
261
|
return envVarDefinitions
|
|
181
262
|
}
|
|
182
263
|
|
|
@@ -377,6 +458,7 @@ export default defineConfig({
|
|
|
377
458
|
localsConvention: "camelCase",
|
|
378
459
|
generateScopedName: "[name]__[local]___[hash:base64:5]",
|
|
379
460
|
},
|
|
461
|
+
...(postcssPlugins ? { postcss: { plugins: postcssPlugins } } : {}),
|
|
380
462
|
preprocessorOptions: {
|
|
381
463
|
scss: {
|
|
382
464
|
additionalData: (content) =>
|
|
@@ -170,15 +170,63 @@
|
|
|
170
170
|
{
|
|
171
171
|
"section": "build_system",
|
|
172
172
|
"title": "iOS Build Pipeline",
|
|
173
|
-
"content": "
|
|
173
|
+
"content": "npm run buildApp:ios generates ConfigConstants.swift and plugin metadata from WEBVIEW_CONFIG, syncs selected public/ios assets into the package-owned Xcode project, detects a physical device or launches a simulator, cleans Xcode artifacts, compiles with the Debug configuration, and installs and launches the .app. It does not bundle the full Catalyst web client and does not create an App Store archive. The generated project is located under the resolved catalyst-core package at dist/native/iosnativeWebView/iosnativeWebView.xcodeproj.",
|
|
174
174
|
"layer": "Build",
|
|
175
175
|
"source": "static",
|
|
176
|
-
"tags": ["ios", "build"]
|
|
176
|
+
"tags": ["ios", "build", "buildApp:ios", "debug", "xcode"],
|
|
177
|
+
"github_files": [
|
|
178
|
+
"packages/catalyst-core/src/native/buildAppIos.js",
|
|
179
|
+
"packages/catalyst-core/src/native/buildIos/build.js"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"section": "build_system",
|
|
184
|
+
"title": "Catalyst iOS Production Release Flow (0.2.x and 0.3.x)",
|
|
185
|
+
"content": "Catalyst 0.2.x and 0.3.x use the same iOS production-release flow: (1) deploy the production web application and verify its HTTPS URL, (2) finalize WEBVIEW_CONFIG, bundle ID, app version/build, plugins, permissions, capabilities, icons, splash assets, access-control rules, and privacy declarations, (3) run npm run buildApp:ios from the consuming app root to prepare the package-owned native project and perform its Debug build, (4) open the resolved catalyst-core package's dist/native/iosnativeWebView/iosnativeWebView.xcodeproj, (5) confirm the iosnativeWebView scheme's Archive action uses Release, configure Release signing and capabilities, choose a generic iOS device destination, and run Product -> Archive, (6) Validate App and upload with Distribute App -> TestFlight & App Store, then (7) finish TestFlight testing, App Store Connect metadata, build selection, and App Review submission. buildApp:ios does not create the final App Store artifact.",
|
|
186
|
+
"layer": "Build",
|
|
187
|
+
"source": "static",
|
|
188
|
+
"tags": [
|
|
189
|
+
"ios",
|
|
190
|
+
"release",
|
|
191
|
+
"production",
|
|
192
|
+
"0.2.x",
|
|
193
|
+
"0.3.x",
|
|
194
|
+
"buildApp:ios",
|
|
195
|
+
"xcode",
|
|
196
|
+
"archive",
|
|
197
|
+
"app-store",
|
|
198
|
+
"testflight"
|
|
199
|
+
],
|
|
200
|
+
"github_files": [
|
|
201
|
+
"docs/content/08-Deployment/03-iOS-Production-Release.md",
|
|
202
|
+
"packages/catalyst-core/src/native/iosnativeWebView/iosnativeWebView.xcodeproj/xcshareddata/xcschemes/iosnativeWebView.xcscheme"
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"section": "build_system",
|
|
207
|
+
"title": "Generated iOS Project Signing and Persistence",
|
|
208
|
+
"content": "The generated Xcode project is package-owned state. Its shared xcconfig is regenerated with development-oriented code-signing defaults, and the bundled push entitlement can use aps-environment=development. Before an App Store archive, review the Release team, distribution signing, provisioning, capabilities, and entitlements in Xcode. Run the final npm run buildApp:ios before release-only Xcode changes: later Catalyst generation, package upgrades, or dependency installation can replace or regenerate native config constants, shared Xcode configuration, plist and entitlement content, generated plugin code/resources, selected project-file entries, and assets. Do not treat manual edits inside the generated project as durable app configuration.",
|
|
209
|
+
"layer": "Build",
|
|
210
|
+
"source": "static",
|
|
211
|
+
"tags": [
|
|
212
|
+
"ios",
|
|
213
|
+
"release",
|
|
214
|
+
"signing",
|
|
215
|
+
"provisioning",
|
|
216
|
+
"entitlements",
|
|
217
|
+
"push-notifications",
|
|
218
|
+
"generated-project",
|
|
219
|
+
"regeneration"
|
|
220
|
+
],
|
|
221
|
+
"github_files": [
|
|
222
|
+
"packages/catalyst-core/src/native/buildIos/config.js",
|
|
223
|
+
"packages/catalyst-core/src/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements"
|
|
224
|
+
]
|
|
177
225
|
},
|
|
178
226
|
{
|
|
179
227
|
"section": "build_system",
|
|
180
228
|
"title": "Legacy 0.2.x Build NPM Scripts",
|
|
181
|
-
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild \u2014 dev web build. devServe \u2014 dev server. buildApp \u2014 builds native
|
|
229
|
+
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild \u2014 dev web build. devServe \u2014 dev server. buildApp \u2014 builds native apps. buildApp:android \u2014 Android native app build. buildApp:ios \u2014 iOS native project preparation and Debug build. buildApp:android:release \u2014 Android release build. setupEmulator \u2014 sets up both emulators. setupEmulator:android \u2014 sets up Android AVD. setupEmulator:ios \u2014 sets up iOS simulator. There is no separate buildApp:ios:release flow: run buildApp:ios, then create the production archive from the generated Xcode project's Release Archive action. NOTE: build:android / build:android:release / build:ios do NOT exist in catalyst \u2014 common mistake. Missing any script = that npm run command fails.",
|
|
182
230
|
"layer": "Build",
|
|
183
231
|
"source": "static",
|
|
184
232
|
"tags": ["build", "scripts", "package.json", "native"]
|
|
@@ -434,7 +482,7 @@
|
|
|
434
482
|
{
|
|
435
483
|
"section": "config_structure",
|
|
436
484
|
"title": "WEBVIEW_CONFIG Required Fields",
|
|
437
|
-
"content": "WEBVIEW_CONFIG field reference (from buildAppAndroid.js + buildAppIos.js source):\n\nTOP-LEVEL (both platforms):\n- port (string, required) \u2014 web server port e.g. \"3005\"\n- LOCAL_IP (string, required) \u2014 machine LAN IP e.g. \"192.168.0.11\". NOT localhost \u2014 emulator cannot resolve it\n- appInfo (string, required) \u2014 build identifier e.g. \"android-5Feb2026-v2.1.0\". Missing = iOS build breaks\n- useHttps (boolean, optional) \u2014 default false\n- cachePattern (string, optional) \u2014 comma-separated globs e.g. \"*.css\"\n- accessControl.enabled (boolean, optional) \u2014 default false\n- accessControl.allowedUrls (array, required if enabled) \u2014 URL patterns\n\nANDROID (WEBVIEW_CONFIG.android):\n- sdkPath (string, REQUIRED) \u2014 abs path to Android SDK. Build exits immediately without it\n- emulatorName (string, REQUIRED for debug) \u2014 AVD name. Run: emulator -list-avds\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- packageName (string, optional) \u2014 e.g. com.company.app. Derived from appName if missing\n- buildType (string, optional) \u2014 \"debug\" or \"release\". Default \"debug\"\n- keystore or keystoreConfig (required for release buildType only)\n- buildOptimisation (boolean, optional) \u2014 default false\n- cachePattern (string, optional)\n\nIOS (WEBVIEW_CONFIG.ios):\n- appBundleId (string, optional) \u2014 default \"com.debug.webview\". Must set for distribution\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- simulatorName (string, optional) \u2014 auto-detected if missing. e.g. \"iPhone 17 Pro\"\n- buildType (string, optional
|
|
485
|
+
"content": "WEBVIEW_CONFIG field reference (from buildAppAndroid.js + buildAppIos.js source):\n\nTOP-LEVEL (both platforms):\n- port (string, required) \u2014 web server port e.g. \"3005\"\n- LOCAL_IP (string, required) \u2014 machine LAN IP e.g. \"192.168.0.11\". NOT localhost \u2014 emulator cannot resolve it\n- appInfo (string, required) \u2014 build identifier e.g. \"android-5Feb2026-v2.1.0\". Missing = iOS build breaks\n- useHttps (boolean, optional) \u2014 default false\n- cachePattern (string, optional) \u2014 comma-separated globs e.g. \"*.css\"\n- accessControl.enabled (boolean, optional) \u2014 default false\n- accessControl.allowedUrls (array, required if enabled) \u2014 URL patterns\n\nANDROID (WEBVIEW_CONFIG.android):\n- sdkPath (string, REQUIRED) \u2014 abs path to Android SDK. Build exits immediately without it\n- emulatorName (string, REQUIRED for debug) \u2014 AVD name. Run: emulator -list-avds\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- packageName (string, optional) \u2014 e.g. com.company.app. Derived from appName if missing\n- buildType (string, optional) \u2014 \"debug\" or \"release\". Default \"debug\"\n- keystore or keystoreConfig (required for release buildType only)\n- buildOptimisation (boolean, optional) \u2014 default false\n- cachePattern (string, optional)\n\nIOS (WEBVIEW_CONFIG.ios):\n- appBundleId (string, optional) \u2014 default \"com.debug.webview\". Must set for distribution\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- simulatorName (string, optional) \u2014 auto-detected if missing. e.g. \"iPhone 17 Pro\"\n- buildType (string, optional) \u2014 may label copied Debug output but does not select the Xcode build configuration or create an App Store archive\n- deviceUDID (string, optional) \u2014 physical device builds only\n- developmentTeam (string, required if deviceUDID set) \u2014 Apple Developer Team ID\n- cachePattern (string, optional)\n\nFor iOS production, run buildApp:ios to prepare the generated project, then archive the iosnativeWebView scheme using its Release Archive action in Xcode.",
|
|
438
486
|
"layer": "Config",
|
|
439
487
|
"source": "static",
|
|
440
488
|
"tags": ["config", "WEBVIEW_CONFIG", "android", "ios", "required", "fields"]
|
package/mcp_v2/mcp.js
CHANGED
|
@@ -62,21 +62,53 @@ if (projectInfo.notInstalled) {
|
|
|
62
62
|
process.exit(1)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
const dbMissing = !fs.existsSync(DB_PATH)
|
|
66
|
+
const dbEmpty = !dbMissing && fs.statSync(DB_PATH).size === 0
|
|
67
|
+
|
|
68
|
+
if (dbMissing || dbEmpty) {
|
|
69
|
+
try {
|
|
70
|
+
require("./setup")
|
|
71
|
+
} catch (setupErr) {
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
jsonrpc: "2.0",
|
|
75
|
+
error: {
|
|
76
|
+
code: -32000,
|
|
77
|
+
message: `context.db ${dbEmpty ? "is empty" : "not found"} at ${DB_PATH}. Auto-setup failed: ${setupErr.message}. Run manually: node ${path.join(MCP_DIR, "setup.js")}`,
|
|
78
|
+
},
|
|
79
|
+
id: null,
|
|
80
|
+
}) + "\n"
|
|
81
|
+
)
|
|
82
|
+
// process.exit(1) instead of exitCode+return: this is top-level script code
|
|
83
|
+
// (not inside a function), and ESLint here rejects top-level return even
|
|
84
|
+
// though Node's CommonJS wrapper allows it. Matches the exit pattern used
|
|
85
|
+
// by the other startup-validation failures above (L49, L62).
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let db
|
|
91
|
+
try {
|
|
92
|
+
db = new Database(DB_PATH, { readonly: false })
|
|
93
|
+
} catch (e) {
|
|
94
|
+
const isVersionMismatch =
|
|
95
|
+
e.code === "ERR_DLOPEN_FAILED" ||
|
|
96
|
+
e.message.includes("compiled against a different Node.js version") ||
|
|
97
|
+
e.message.includes("NODE_MODULE_VERSION")
|
|
66
98
|
process.stderr.write(
|
|
67
99
|
JSON.stringify({
|
|
68
100
|
jsonrpc: "2.0",
|
|
69
101
|
error: {
|
|
70
102
|
code: -32000,
|
|
71
|
-
message:
|
|
103
|
+
message: isVersionMismatch
|
|
104
|
+
? `Native module mismatch for Node ${process.version}. Run: npm rebuild better-sqlite3`
|
|
105
|
+
: `Failed to open context.db: ${e.message}`,
|
|
72
106
|
},
|
|
73
107
|
id: null,
|
|
74
108
|
}) + "\n"
|
|
75
109
|
)
|
|
76
110
|
process.exit(1)
|
|
77
111
|
}
|
|
78
|
-
|
|
79
|
-
const db = new Database(DB_PATH, { readonly: false })
|
|
80
112
|
const CONVERSION_TASKS = JSON.parse(fs.readFileSync(TASKS_PATH, "utf8"))
|
|
81
113
|
|
|
82
114
|
// ── Module init ───────────────────────────────────────────────────────────────
|
package/mcp_v2/tools/build.js
CHANGED
|
@@ -149,19 +149,19 @@ const MASTER_FLOWS = {
|
|
|
149
149
|
step: 1,
|
|
150
150
|
cmd: null,
|
|
151
151
|
label: "Pre-check: config",
|
|
152
|
-
detail:
|
|
152
|
+
detail: "Reads WEBVIEW_CONFIG and WEBVIEW_CONFIG.ios. Confirm the development URL, appBundleId, simulatorName, plugins, and selected public/ios assets.",
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
step: 2,
|
|
156
|
-
cmd: "
|
|
157
|
-
label: "
|
|
158
|
-
detail: "
|
|
156
|
+
cmd: "npm run buildApp:ios",
|
|
157
|
+
label: "iOS simulator build",
|
|
158
|
+
detail: "Generates native configuration and plugin code in the package-owned Xcode project, syncs selected assets, compiles with Xcode's Debug configuration, installs the .app, and launches it.",
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
step: 3,
|
|
162
|
-
cmd:
|
|
163
|
-
label: "
|
|
164
|
-
detail: "
|
|
162
|
+
cmd: null,
|
|
163
|
+
label: "Remote web application",
|
|
164
|
+
detail: "The iOS shell loads the configured remote URL. buildApp:ios does not bundle the full Catalyst web client, so the target web application must be reachable.",
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
step: 4,
|
|
@@ -175,27 +175,44 @@ const MASTER_FLOWS = {
|
|
|
175
175
|
{
|
|
176
176
|
step: 1,
|
|
177
177
|
cmd: null,
|
|
178
|
+
label: "Deploy the production web application",
|
|
179
|
+
detail: "Deploy the web application first and confirm its production URL is reachable over HTTPS. The iOS shell loads this remote URL; it does not embed the full web client bundle.",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
step: 2,
|
|
183
|
+
cmd: null,
|
|
178
184
|
label: "Pre-check: config",
|
|
179
|
-
detail:
|
|
185
|
+
detail: "Finalize WEBVIEW_CONFIG, appBundleId, app name, version, incremented build number, access-control rules, plugins, permissions, capabilities, icons, splash assets, and privacy declarations.",
|
|
180
186
|
},
|
|
181
|
-
{ step: 2, cmd: "catalyst build", label: "Build web assets", detail: "Production build." },
|
|
182
187
|
{
|
|
183
188
|
step: 3,
|
|
184
189
|
cmd: "npm run buildApp:ios",
|
|
185
|
-
label: "
|
|
186
|
-
detail: "
|
|
190
|
+
label: "Prepare the native project",
|
|
191
|
+
detail: "Run from the consuming Catalyst app root. This generates native configuration and plugin code, syncs selected assets, and performs a Debug native build. It does not create an App Store archive.",
|
|
187
192
|
},
|
|
188
193
|
{
|
|
189
194
|
step: 4,
|
|
190
195
|
cmd: null,
|
|
191
|
-
label: "
|
|
192
|
-
detail: "
|
|
196
|
+
label: "Open the generated Xcode project",
|
|
197
|
+
detail: "Open node_modules/catalyst-core/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj (or the resolved package equivalent). Confirm the iosnativeWebView scheme's Archive action uses Release.",
|
|
193
198
|
},
|
|
194
199
|
{
|
|
195
200
|
step: 5,
|
|
196
201
|
cmd: null,
|
|
197
|
-
label: "
|
|
198
|
-
detail: "
|
|
202
|
+
label: "Configure signing and archive",
|
|
203
|
+
detail: "Select the correct Apple Developer team, distribution signing, capabilities, entitlements, bundle ID, version, and build. Choose a generic iOS device destination, then Product → Archive.",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
step: 6,
|
|
207
|
+
cmd: null,
|
|
208
|
+
label: "Validate and upload",
|
|
209
|
+
detail: "In Xcode Organizer, run Validate App, then Distribute App → TestFlight & App Store and upload the build.",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
step: 7,
|
|
213
|
+
cmd: null,
|
|
214
|
+
label: "Complete the release",
|
|
215
|
+
detail: "Wait for App Store Connect processing, test through TestFlight, complete store metadata and privacy disclosures, select the processed build, and submit it for App Review.",
|
|
199
216
|
},
|
|
200
217
|
],
|
|
201
218
|
}
|
|
@@ -495,6 +512,7 @@ function getProjectContext(root) {
|
|
|
495
512
|
iosBuildType: (wv.ios?.buildType || "debug").toLowerCase(),
|
|
496
513
|
hasKeystore: !!wv.android?.keystoreConfig,
|
|
497
514
|
hasGoogleSignIn: !!wv.googleSignIn?.enabled,
|
|
515
|
+
hasNotifications: !!wv.notifications?.enabled,
|
|
498
516
|
hasEcosystem: fileExists("ecosystem.config.js"),
|
|
499
517
|
port: wv.port,
|
|
500
518
|
useHttps: !!wv.useHttps,
|
|
@@ -618,7 +636,7 @@ function handle_get_build_flow({ platform, mode, symptom } = {}) {
|
|
|
618
636
|
)
|
|
619
637
|
}
|
|
620
638
|
|
|
621
|
-
const isRelease = m === "release"
|
|
639
|
+
const isRelease = m === "release"
|
|
622
640
|
flow_key = isRelease ? "ios_release" : "ios_debug"
|
|
623
641
|
|
|
624
642
|
if (!ctx.wv.ios?.appBundleId) {
|
|
@@ -634,13 +652,23 @@ function handle_get_build_flow({ platform, mode, symptom } = {}) {
|
|
|
634
652
|
"googleSignIn.enabled=true but iosClientId may be missing — check WEBVIEW_CONFIG.googleSignIn.iosClientId."
|
|
635
653
|
)
|
|
636
654
|
}
|
|
637
|
-
if (isRelease && ctx.
|
|
655
|
+
if (isRelease && !ctx.useHttps) {
|
|
638
656
|
warnings.push(
|
|
639
|
-
|
|
657
|
+
"WEBVIEW_CONFIG.useHttps is not enabled — the App Store flow requires a reachable HTTPS production web application."
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
if (isRelease && ctx.hasNotifications) {
|
|
661
|
+
warnings.push(
|
|
662
|
+
"Push notifications are enabled — review the generated aps-environment entitlement and Release provisioning before archiving."
|
|
640
663
|
)
|
|
641
664
|
}
|
|
642
665
|
|
|
643
666
|
notes.push(`buildType detected: ${ctx.wv.ios?.buildType || "not set (defaults to debug)"}`)
|
|
667
|
+
if (isRelease) {
|
|
668
|
+
notes.push(
|
|
669
|
+
"ios.buildType does not select the App Store build configuration; buildApp:ios still performs a Debug build. Archive the generated project with the scheme's Release action."
|
|
670
|
+
)
|
|
671
|
+
}
|
|
644
672
|
if (ctx.wv.ios?.appBundleId) notes.push(`appBundleId: ${ctx.wv.ios.appBundleId}`)
|
|
645
673
|
if (ctx.wv.ios?.simulatorName) notes.push(`simulatorName: ${ctx.wv.ios.simulatorName}`)
|
|
646
674
|
if (ctx.hasGoogleSignIn) notes.push("googleSignIn: enabled")
|
package/mcp_v2/tools/github.js
CHANGED
|
@@ -161,6 +161,7 @@ function safeReadText(filePath, maxBytes = 8000) {
|
|
|
161
161
|
|
|
162
162
|
function runGit(root, command) {
|
|
163
163
|
try {
|
|
164
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - command is always one of the fixed git/node/npm strings hardcoded at call sites, never external input.
|
|
164
165
|
return execSync(command, {
|
|
165
166
|
cwd: root,
|
|
166
167
|
encoding: "utf8",
|
|
@@ -183,6 +184,7 @@ function slugify(input) {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
function ensureFallbackDir(root) {
|
|
187
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter letting a local developer's coding agent point this tool at any project directory it already has filesystem access to (see mcp.js inputSchema). Not a network-facing input; the caller already has direct fs access via other tools. FALLBACK_DIR is a hardcoded literal.
|
|
186
188
|
const dir = path.join(root, FALLBACK_DIR)
|
|
187
189
|
fs.mkdirSync(dir, { recursive: true })
|
|
188
190
|
return dir
|
|
@@ -406,7 +408,9 @@ function appendSection(sections, heading, content) {
|
|
|
406
408
|
}
|
|
407
409
|
|
|
408
410
|
function isPathInside(root, candidate) {
|
|
411
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - this is the containment-check helper itself (used by callers below to reject paths outside root); root/candidate here are the values being validated, not yet trusted for file access.
|
|
409
412
|
const resolvedRoot = path.resolve(root)
|
|
413
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - this is the containment-check helper itself; candidate is the value being validated, not yet trusted for file access.
|
|
410
414
|
const resolvedCandidate = path.resolve(candidate)
|
|
411
415
|
const relative = path.relative(resolvedRoot, resolvedCandidate)
|
|
412
416
|
return relative === "" || (relative && !relative.startsWith("..") && !path.isAbsolute(relative))
|
|
@@ -543,6 +547,7 @@ function normalizeImages(images, root) {
|
|
|
543
547
|
continue
|
|
544
548
|
}
|
|
545
549
|
|
|
550
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - raw is an MCP tool argument (attachment path) and root the project_path argument; the isPathInside() check on the next line rejects anything that resolves outside root before it is ever read.
|
|
546
551
|
const absolutePath = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(root, raw)
|
|
547
552
|
if (!isPathInside(root, absolutePath)) {
|
|
548
553
|
localFiles.push({ path: raw, exists: false, rejected: true, reason: "outside_project_root" })
|
|
@@ -789,6 +794,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
789
794
|
const candidates = Array.isArray(explicitFiles) ? explicitFiles : []
|
|
790
795
|
for (const file of candidates) {
|
|
791
796
|
if (typeof file !== "string") continue
|
|
797
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - file is an MCP tool argument (explicit related-files list) and root the project_path argument; the isPathInside() check on the next line rejects anything that resolves outside root before it is ever read.
|
|
792
798
|
const absolute = path.isAbsolute(file) ? path.resolve(file) : path.resolve(root, file)
|
|
793
799
|
if (!isPathInside(root, absolute)) continue
|
|
794
800
|
const content = safeReadText(absolute, 12000)
|
|
@@ -802,6 +808,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
802
808
|
const words = normalizeSearchText(query).split(/\s+/).filter(Boolean).slice(0, 5)
|
|
803
809
|
if (words.length === 0) return files
|
|
804
810
|
|
|
811
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "src" is a hardcoded literal.
|
|
805
812
|
const srcDir = path.join(root, "src")
|
|
806
813
|
const matches = []
|
|
807
814
|
function walk(dir) {
|
|
@@ -813,6 +820,7 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
813
820
|
}
|
|
814
821
|
for (const entry of entries) {
|
|
815
822
|
if (entry.name === "node_modules" || entry.name === ".git") continue
|
|
823
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - entry.name comes from fs.readdirSync(dir) above, i.e. actual filenames already on disk under srcDir, not request input.
|
|
816
824
|
const full = path.join(dir, entry.name)
|
|
817
825
|
if (entry.isDirectory()) {
|
|
818
826
|
walk(full)
|
|
@@ -831,7 +839,9 @@ function collectRelatedFiles(root, query, explicitFiles) {
|
|
|
831
839
|
|
|
832
840
|
function gatherGithubIssueContext(args = {}) {
|
|
833
841
|
const root = getProjectRoot(args)
|
|
842
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "package.json" is a hardcoded literal.
|
|
834
843
|
const pkg = safeReadJson(path.join(root, "package.json"))
|
|
844
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the project_path MCP tool argument, an intentional/documented parameter (see mcp.js inputSchema); "config/config.json" is a hardcoded literal.
|
|
835
845
|
const configJson = safeReadJson(path.join(root, "config", "config.json"))
|
|
836
846
|
const branch = runGit(root, "git rev-parse --abbrev-ref HEAD")
|
|
837
847
|
const commit = runGit(root, "git rev-parse --short HEAD")
|
|
@@ -874,6 +884,7 @@ function generateIssueMarkdown(args = {}) {
|
|
|
874
884
|
const built = buildIssueBody(args, { enrich: true })
|
|
875
885
|
const labels = resolveIssueLabels(args, built.body)
|
|
876
886
|
const dir = ensureFallbackDir(root)
|
|
887
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - dir is the FALLBACK_DIR under the project_path MCP argument; the filename is a generated ISO timestamp plus slugify(title), which strips to [a-z0-9-] only, so it cannot contain path separators.
|
|
877
888
|
const filePath = path.join(dir, `${new Date().toISOString().replace(/[:.]/g, "-")}-${slugify(title)}.md`)
|
|
878
889
|
|
|
879
890
|
const markdown = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catalyst-core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.jsx",
|
|
6
6
|
"description": "Web framework that provides great performance out of the box",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lint": "eslint .",
|
|
32
32
|
"lint-staged": "lint-staged",
|
|
33
33
|
"prettify": "prettier . --write",
|
|
34
|
-
"prepare": "
|
|
34
|
+
"prepare": "node ../../scripts/safe-rimraf.js ./dist && cp -r ./src ./dist && babel src/native --out-dir dist/native --config-file ./babel.native.config.cjs --ignore '**/.build/**' && babel src/sentry.js --out-file dist/sentry.cjs --config-file ./babel.native.config.cjs",
|
|
35
35
|
"prepublishOnly": "npm run prepare",
|
|
36
36
|
"test:fixture": "sh ../../scripts/test-catalyst-core.sh"
|
|
37
37
|
},
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react-refresh": "^0.14.0",
|
|
52
52
|
"react-router-dom": "^6.16.0",
|
|
53
53
|
"redux": "^4.2.1",
|
|
54
|
-
"sanitize-html": "^2.
|
|
54
|
+
"sanitize-html": "^2.17.6",
|
|
55
55
|
"sass": "^1.89.2",
|
|
56
56
|
"ua-parser-js": "^1.0.37",
|
|
57
57
|
"vite": "^6.4.3",
|