catalyst-core-internal 0.1.0 → 0.1.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/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +10 -2
- package/dist/native/bridge/hooks.js +4 -4
- package/dist/native/bridge/useBaseHook.js +3 -4
- package/dist/native/bridge/utils/NativeBridge.js +4 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +2 -13
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -36
- package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +0 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +4 -14
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +21 -9
- package/mcp_v2/conversion-tasks.json +371 -0
- package/mcp_v2/knowledge-base.json +1450 -0
- package/mcp_v2/lib/helpers.js +145 -0
- package/mcp_v2/mcp.js +366 -0
- package/mcp_v2/package.json +13 -0
- package/mcp_v2/schema.sql +88 -0
- package/mcp_v2/setup.js +262 -0
- package/mcp_v2/tools/build.js +449 -0
- package/mcp_v2/tools/config.js +262 -0
- package/mcp_v2/tools/conversion.js +492 -0
- package/mcp_v2/tools/debug.js +62 -0
- package/mcp_v2/tools/knowledge.js +213 -0
- package/mcp_v2/tools/sync.js +21 -0
- package/mcp_v2/tools/tasks.js +844 -0
- package/package.json +1 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +0 -199
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +0 -212
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +0 -121
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { makeProjectHelpers, findCatalystRoot } = require('../lib/helpers');
|
|
4
|
+
|
|
5
|
+
let _db;
|
|
6
|
+
|
|
7
|
+
function init(db) {
|
|
8
|
+
_db = db;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ─── Master flows ────────────────────────────────────────────────────────────
|
|
12
|
+
// These are fixed — they describe how the framework works.
|
|
13
|
+
// Dynamic sections are filled in at call time from the project's config.
|
|
14
|
+
|
|
15
|
+
const MASTER_FLOWS = {
|
|
16
|
+
|
|
17
|
+
web_dev: [
|
|
18
|
+
{ step: 1, cmd: 'catalyst start', label: 'Start dev server', detail: 'Starts webpack-dev-server + SSR Node server on NODE_SERVER_PORT. Hot reload enabled.' },
|
|
19
|
+
{ step: 2, cmd: null, label: 'Browser / WebView loads', detail: 'Web: browser hits localhost. Universal: WebView loads http://<LOCAL_IP>:<port>. IP auto-detected by catalyst at startup.' },
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
web_build: [
|
|
23
|
+
{ step: 1, cmd: 'catalyst build', label: 'Production build', detail: 'Runs webpack in production mode. Outputs to build/ (or OUTPUT_PATH from config). Includes SSR bundle + client chunks.' },
|
|
24
|
+
{ step: 2, cmd: 'catalyst serve', label: 'Serve built app (local)', detail: 'Serves from build/ — same as production but local. Use for smoke-testing the build before deploy.' },
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
web_prod: [
|
|
28
|
+
{ step: 1, cmd: 'catalyst build', label: 'Build', detail: 'Production webpack build. Set BUILD_ENV=production in env or config.' },
|
|
29
|
+
{ step: 2, cmd: 'NODE_ENV=production BUILD_ENV=production pm2-runtime ./ecosystem.config.js --wait-ready --listen-timeout 15000', label: 'Serve via PM2 (production)', detail: 'ecosystem.config.js controls process name, memory limit, restart policy. --wait-ready waits for app to signal ready before PM2 marks it live.' },
|
|
30
|
+
{ step: 3, cmd: null, label: 'PM2 process config', detail: 'Key fields in ecosystem.config.js: name, autorestart, max_memory_restart (default 1000M), kill_timeout (3000ms). Logs routed to stdout/stderr for container compatibility.' },
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
web_staging: [
|
|
34
|
+
{ step: 1, cmd: 'catalyst build', label: 'Build', detail: 'Same webpack build — staging is config-only, not a different build target.' },
|
|
35
|
+
{ step: 2, cmd: 'NODE_ENV=production BUILD_ENV=staging pm2-runtime ./ecosystem.config.js --wait-ready', label: 'Serve via PM2 (staging)', detail: 'BUILD_ENV=staging changes API URLs and feature flags. NODE_ENV=production keeps Node in production mode for performance.' },
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
android_debug: [
|
|
39
|
+
{ step: 1, cmd: null, label: 'Pre-check: config', detail: 'Reads config/config.json → WEBVIEW_CONFIG.android. Requires: sdkPath, emulatorName, buildType="debug" (or "Debug").' },
|
|
40
|
+
{ step: 2, cmd: 'catalyst build', label: 'Build web assets', detail: 'Compiles web app to build/. Android build embeds these as the WebView content.' },
|
|
41
|
+
{ step: 3, cmd: 'npm run build:android', label: 'Android debug build', detail: 'Runs buildAppAndroid.js: validates ADB + emulator path from sdkPath, launches emulator (emulatorName), copies web assets to androidProject/, runs Gradle debug build, installs APK.' },
|
|
42
|
+
{ step: 4, cmd: null, label: 'WebView startup', detail: 'App launches → WebView loads http://<LOCAL_IP>:<port> → bridge initialises → hooks available.' },
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
android_release: [
|
|
46
|
+
{ step: 1, cmd: null, label: 'Pre-check: config', detail: 'Requires WEBVIEW_CONFIG.android.buildType="release". keystoreConfig must be present with real passwords (not placeholder values).' },
|
|
47
|
+
{ step: 2, cmd: null, label: 'Pre-check: keystore fields', detail: 'Required keystoreConfig fields: keyAlias, storePassword, keyPassword, organizationInfo (companyName, city, state, countryCode).' },
|
|
48
|
+
{ step: 3, cmd: 'catalyst build', label: 'Build web assets', detail: 'Production webpack build. Assets bundled into AAB.' },
|
|
49
|
+
{ step: 4, cmd: 'npm run build:android:release', label: 'Android release build', detail: 'Skips emulator validation. Runs Gradle release build. Calls buildAndroidAAB() — renames project, creates/verifies keystore, signs AAB. Output goes to ./deployment/.' },
|
|
50
|
+
{ step: 5, cmd: null, label: 'AAB output', detail: 'Signed .aab in ./deployment/. Upload to Play Console. Not directly installable — use bundletool to test on device.' },
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
ios_debug: [
|
|
54
|
+
{ step: 1, cmd: null, label: 'Pre-check: config', detail: 'Reads WEBVIEW_CONFIG.ios. Requires: appBundleId, simulatorName, buildType (default "debug").' },
|
|
55
|
+
{ step: 2, cmd: 'catalyst build', label: 'Build web assets', detail: 'Compiles web app. iOS build embeds these.' },
|
|
56
|
+
{ step: 3, cmd: 'npm run build:ios', label: 'iOS simulator build', detail: 'Runs buildAppIos.js: generates ConfigConstants.swift from config (bundleId, URL, port, protocol), launches simulator (simulatorName), cleans Xcode artifacts, compiles with xcodebuild, installs .app, launches.' },
|
|
57
|
+
{ step: 4, cmd: null, label: 'GoogleSignIn (if set)', detail: 'If WEBVIEW_CONFIG.googleSignIn.enabled: injects clientId + iosClientId into Info.plist and Info-Release.plist. GoogleService-Info.plist must exist in project.' },
|
|
58
|
+
],
|
|
59
|
+
|
|
60
|
+
ios_release: [
|
|
61
|
+
{ step: 1, cmd: null, label: 'Pre-check: config', detail: 'buildType must be "Release" (capital R — case-sensitive). appBundleId required. If googleSignIn enabled, GoogleService-Info.plist must be present.' },
|
|
62
|
+
{ step: 2, cmd: 'catalyst build', label: 'Build web assets', detail: 'Production build.' },
|
|
63
|
+
{ step: 3, cmd: 'npm run build:ios', label: 'iOS release build', detail: 'Same script — build type is driven by WEBVIEW_CONFIG.ios.buildType. Generates ConfigConstants.swift with production URL and bundleId. Xcode compiles Release scheme.' },
|
|
64
|
+
{ step: 4, cmd: null, label: 'Archive + export IPA', detail: 'Post-build: archive via Xcode Organizer or xcodebuild -exportArchive. Requires Apple distribution certificate + provisioning profile configured in Xcode.' },
|
|
65
|
+
{ step: 5, cmd: null, label: 'App Store submission', detail: 'Upload IPA via Transporter or Xcode Organizer. TestFlight for beta before release.' },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ─── Architecture diagrams ───────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const MASTER_DIAGRAMS = {
|
|
72
|
+
|
|
73
|
+
universal_app: {
|
|
74
|
+
title: 'Universal App Architecture',
|
|
75
|
+
description: 'How a Catalyst web app runs inside a native WebView container.',
|
|
76
|
+
layers: [
|
|
77
|
+
{ layer: 'web', label: 'React App (Web Layer)', detail: 'Standard React/SSR app. Runs identically on browser and inside WebView. No React Native code.' },
|
|
78
|
+
{ layer: 'server', label: 'Node SSR Server', detail: 'Express server runs inside the native app process (via Node-on-Mobile or localhost tunnel). Serves HTML, handles API proxying.' },
|
|
79
|
+
{ layer: 'bridge', label: 'WebBridge (JS ↔ Native)', detail: 'window.WebBridge injected by native shell. Hooks call WebBridge.postMessage(). Native shell receives, executes platform API, calls back via WebBridge.onMessage().' },
|
|
80
|
+
{ layer: 'native', label: 'Native Shell (Android / iOS)', detail: 'Android: WebViewActivity + JavascriptInterface. iOS: WKWebView + WKScriptMessageHandler. Handles camera, haptics, file picker, notifications, security checks.' },
|
|
81
|
+
],
|
|
82
|
+
flow: [
|
|
83
|
+
'User action in React component',
|
|
84
|
+
'→ Catalyst hook called (e.g. useCamera, useHapticFeedback)',
|
|
85
|
+
'→ hook checks isNative flag (set by WebBridge at init)',
|
|
86
|
+
'→ [native path] WebBridge.postMessage({ command, payload })',
|
|
87
|
+
'→ Native shell receives, executes platform API',
|
|
88
|
+
'→ Native calls back → hook resolves with { data, isNative: true }',
|
|
89
|
+
'→ [web path] hook falls back to browser API (navigator.vibrate, <input type=file>, etc.)',
|
|
90
|
+
],
|
|
91
|
+
configurable_today: [
|
|
92
|
+
'port — WebView loads this port from the local server',
|
|
93
|
+
'useHttps — switches WebView URL to https (requires cert setup)',
|
|
94
|
+
'LOCAL_IP — override auto-detected IP for device builds',
|
|
95
|
+
'accessControl.allowedUrls — whitelist for outbound requests from WebView',
|
|
96
|
+
'googleSignIn.enabled / clientId / iosClientId',
|
|
97
|
+
'splashScreen — duration, backgroundColor, logo asset',
|
|
98
|
+
'notifications.enabled — push notification permission prompt',
|
|
99
|
+
'android.buildOptimisation — enables build caching',
|
|
100
|
+
'android.keystoreConfig — signing for release builds',
|
|
101
|
+
'ios.appBundleId — bundle identifier for App Store',
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
request_lifecycle: {
|
|
106
|
+
title: 'Request Lifecycle (Tri-Transport)',
|
|
107
|
+
description: 'How a data fetch travels from a React component to the API and back.',
|
|
108
|
+
layers: [
|
|
109
|
+
{ layer: 'component', label: 'React Component', detail: 'Calls useDataFetching (or fetch/axios). No transport awareness needed.' },
|
|
110
|
+
{ layer: 'hook', label: 'useDataFetching hook', detail: 'Determines transport based on runtime context: isNative + server reachability.' },
|
|
111
|
+
{ layer: 'transport', label: 'Transport Selection', detail: '3 options: (1) Localhost Server — Node server running in-process, fastest. (2) Native Bridge — postMessage to native, native makes HTTP call. (3) Cloudflare proxy — fallback for external domains not in whitelist.' },
|
|
112
|
+
{ layer: 'api', label: 'API / Backend', detail: 'Response flows back through same transport. Hook normalises response shape regardless of transport used.' },
|
|
113
|
+
],
|
|
114
|
+
flow: [
|
|
115
|
+
'Component calls useDataFetching({ url, method, body })',
|
|
116
|
+
'→ hook checks: is window.WebBridge available? (isNative)',
|
|
117
|
+
'→ [native] checks if url is in WEBVIEW_CONFIG.accessControl.allowedUrls',
|
|
118
|
+
'→ [allowed] sends via localhost Node server (fastest, avoids CORS)',
|
|
119
|
+
'→ [not allowed] routes via native bridge postMessage → native HTTP client',
|
|
120
|
+
'→ [web] standard fetch — browser handles CORS normally',
|
|
121
|
+
'→ response normalised → component receives { data, loading, error }',
|
|
122
|
+
],
|
|
123
|
+
known_pitfalls: [
|
|
124
|
+
'Localhost server transport requires allowedUrls to include localhost entry',
|
|
125
|
+
'Intent (bridge message) size limit: check KB for limit — large payloads silently fail',
|
|
126
|
+
'Cloudflare routing adds latency — prefer localhost server for internal APIs',
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
build_pipeline: {
|
|
131
|
+
title: 'Build Pipeline (Framework Internals)',
|
|
132
|
+
description: 'What happens inside catalyst build and how the native build consumes it.',
|
|
133
|
+
layers: [
|
|
134
|
+
{ layer: 'web', label: 'Webpack (catalyst build)', detail: 'Produces: build/public/ (client chunks), build/server.js (SSR bundle). Loadable-stats.json for code-splitting.' },
|
|
135
|
+
{ layer: 'prepare', label: 'catalyst-core prepare', detail: 'Compiles catalyst-core/src/ → dist/ via Babel. Copies src/native/ → dist/native/ (androidProject, iosnativeWebView, assets). This is the framework build, not the app build.' },
|
|
136
|
+
{ layer: 'copy', label: 'Dev copy workflow', detail: 'After prepare: copy catalyst-core/dist/ → test-app/node_modules/catalyst-core/. Also copy package.json (required for Node 20 exports map — ERR_PACKAGE_PATH_NOT_EXPORTED fix).' },
|
|
137
|
+
{ layer: 'native', label: 'Native build consumes dist/', detail: 'buildAppAndroid.js and buildAppIos.js resolve catalyst-core via require.resolve("catalyst-core/package.json"). Load androidProject and iosnativeWebView from dist/native/.' },
|
|
138
|
+
],
|
|
139
|
+
flow: [
|
|
140
|
+
'[Framework dev] Edit catalyst-core/src/',
|
|
141
|
+
'→ npm run prepare (in catalyst-core)',
|
|
142
|
+
'→ dist/ updated',
|
|
143
|
+
'→ Copy dist/ → test-app/node_modules/catalyst-core/',
|
|
144
|
+
'→ Copy package.json → test-app/node_modules/catalyst-core/package.json',
|
|
145
|
+
'',
|
|
146
|
+
'[App build] In app project root:',
|
|
147
|
+
'→ catalyst build (webpack, outputs build/)',
|
|
148
|
+
'→ npm run build:android / build:ios',
|
|
149
|
+
'→ native build script reads WEBVIEW_CONFIG from config/config.json',
|
|
150
|
+
'→ copies build/ assets into native project',
|
|
151
|
+
'→ compiles native project (Gradle / xcodebuild)',
|
|
152
|
+
'→ installs on emulator/simulator or produces APK/AAB/IPA',
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
bridge_architecture: {
|
|
157
|
+
title: 'Native Bridge Architecture',
|
|
158
|
+
description: 'How JS hooks communicate with native platform APIs through the bridge.',
|
|
159
|
+
layers: [
|
|
160
|
+
{ layer: 'js', label: 'JS Hook Layer', detail: 'useCamera, useFilePicker, useHapticFeedback, useNetworkStatus, useSafeArea, useDeviceInfo, useNotification, useDataProtection, useLocation, useStorage. All check isNative at runtime.' },
|
|
161
|
+
{ layer: 'bridge', label: 'WebBridge Interface', detail: 'Android: JavascriptInterface (@JavascriptInterface annotated methods). iOS: WKScriptMessageHandler. Both expose postMessage(jsonString) and trigger JS callback via evaluateJavascript.' },
|
|
162
|
+
{ layer: 'android', label: 'Android Bridge', detail: 'WebViewActivity hosts WebView. Bridge file: src/native/androidProject/app/src/main/java/.../WebAppInterface.java. 11 native commands + 14 callback events implemented.' },
|
|
163
|
+
{ layer: 'ios', label: 'iOS Bridge', detail: 'WKWebView with WKUserContentController. Bridge file: src/native/iosnativeWebView/. ConfigConstants.swift generated at build time from WEBVIEW_CONFIG (bundleId, URL, port).' },
|
|
164
|
+
],
|
|
165
|
+
flow: [
|
|
166
|
+
'JS: hook.execute() called',
|
|
167
|
+
'→ hook serialises command + payload to JSON',
|
|
168
|
+
'→ window.WebBridge.postMessage(JSON.stringify({ command, requestId, payload }))',
|
|
169
|
+
'→ Native receives in JavascriptInterface (Android) / messageHandler (iOS)',
|
|
170
|
+
'→ Native dispatches to command handler (camera, filepicker, haptic, etc.)',
|
|
171
|
+
'→ Native executes platform API',
|
|
172
|
+
'→ Native calls back: webView.evaluateJavascript("WebBridgeCallback(requestId, result)")',
|
|
173
|
+
'→ JS resolves hook promise → { data, isNative: true, isWeb: false }',
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
routing: {
|
|
178
|
+
title: 'Routing Architecture',
|
|
179
|
+
description: 'How routes are defined, loaded, and rendered in a Catalyst app.',
|
|
180
|
+
layers: [
|
|
181
|
+
{ layer: 'definition', label: 'routes.js / routes.jsx', detail: 'Central route definitions. Each route: { path, component, exact, data (for RouterDataProvider), preload }.' },
|
|
182
|
+
{ layer: 'provider', label: 'RouterDataProvider', detail: 'Wraps app. Fetches route-level data server-side (SSR) or client-side on navigation. Passes data as props to route component.' },
|
|
183
|
+
{ layer: 'shell', label: 'App Shell', detail: 'Persistent layout wrapper. Renders header/footer/nav outside route transitions. Route component renders inside shell outlet.' },
|
|
184
|
+
{ layer: 'ssr', label: 'SSR + Hydration', detail: 'Server renders full HTML from routes. Client hydrates. RouterDataProvider re-fetches data on client transitions after hydration.' },
|
|
185
|
+
],
|
|
186
|
+
flow: [
|
|
187
|
+
'Request arrives at Node server',
|
|
188
|
+
'→ Router matches path to route definition',
|
|
189
|
+
'→ RouterDataProvider fetches route.data() server-side',
|
|
190
|
+
'→ App Shell rendered with matched route component + fetched data',
|
|
191
|
+
'→ HTML streamed to client',
|
|
192
|
+
'→ Client hydrates React tree',
|
|
193
|
+
'→ Client-side navigation: RouterDataProvider intercepts link clicks',
|
|
194
|
+
'→ Fetches next route data client-side',
|
|
195
|
+
'→ Route component swaps inside shell (no full page reload)',
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
function getProjectContext(root) {
|
|
203
|
+
const { readJson, fileExists } = makeProjectHelpers(root);
|
|
204
|
+
const config = readJson('config/config.json') || {};
|
|
205
|
+
const pkg = readJson('package.json') || {};
|
|
206
|
+
const wv = config.WEBVIEW_CONFIG || {};
|
|
207
|
+
const scripts = pkg.scripts || {};
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
wv,
|
|
211
|
+
scripts,
|
|
212
|
+
hasAndroid: !!wv.android,
|
|
213
|
+
hasIos: !!wv.ios,
|
|
214
|
+
androidBuildType: (wv.android?.buildType || 'debug').toLowerCase(),
|
|
215
|
+
iosBuildType: (wv.ios?.buildType || 'debug').toLowerCase(),
|
|
216
|
+
hasKeystore: !!wv.android?.keystoreConfig,
|
|
217
|
+
hasGoogleSignIn: !!wv.googleSignIn?.enabled,
|
|
218
|
+
hasEcosystem: fileExists('ecosystem.config.js'),
|
|
219
|
+
port: wv.port,
|
|
220
|
+
useHttps: !!wv.useHttps,
|
|
221
|
+
accessControl: wv.accessControl || {},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getKnownErrors(keywords) {
|
|
226
|
+
if (!keywords || !keywords.length) return [];
|
|
227
|
+
// Score known_errors rows by keyword overlap
|
|
228
|
+
const rows = _db.prepare(`
|
|
229
|
+
SELECT title, content, tags FROM framework_knowledge
|
|
230
|
+
WHERE section = 'known_errors'
|
|
231
|
+
`).all();
|
|
232
|
+
|
|
233
|
+
const tokens = keywords.map(k => k.toLowerCase());
|
|
234
|
+
return rows
|
|
235
|
+
.map(r => {
|
|
236
|
+
const blob = [r.title, r.content, r.tags].join(' ').toLowerCase();
|
|
237
|
+
const score = tokens.filter(t => blob.includes(t)).length;
|
|
238
|
+
return { ...r, score };
|
|
239
|
+
})
|
|
240
|
+
.filter(r => r.score > 0)
|
|
241
|
+
.sort((a, b) => b.score - a.score)
|
|
242
|
+
.slice(0, 3)
|
|
243
|
+
.map(({ title, content }) => ({ title, content }));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── Tool: get_build_flow ────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function handle_get_build_flow({ platform, mode, symptom } = {}) {
|
|
249
|
+
const catalystRoot = findCatalystRoot();
|
|
250
|
+
if (!catalystRoot) {
|
|
251
|
+
return { error: 'No catalyst-core project found. Run from inside a Catalyst app.' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const ctx = getProjectContext(catalystRoot.dir);
|
|
255
|
+
const p = (platform || 'web').toLowerCase();
|
|
256
|
+
const m = (mode || 'dev').toLowerCase(); // dev | build | production | staging | release
|
|
257
|
+
|
|
258
|
+
let flow_key;
|
|
259
|
+
let warnings = [];
|
|
260
|
+
let notes = [];
|
|
261
|
+
|
|
262
|
+
// ── Web ──────────────────────────────────────────────────────────────────
|
|
263
|
+
if (p === 'web') {
|
|
264
|
+
if (m === 'production' || m === 'prod') {
|
|
265
|
+
flow_key = 'web_prod';
|
|
266
|
+
if (!ctx.hasEcosystem) warnings.push('ecosystem.config.js not found — PM2 serve step will fail. Create one or use `catalyst serve` for local serving.');
|
|
267
|
+
} else if (m === 'staging' || m === 'stag') {
|
|
268
|
+
flow_key = 'web_staging';
|
|
269
|
+
if (!ctx.hasEcosystem) warnings.push('ecosystem.config.js not found — staging serve step will fail.');
|
|
270
|
+
} else if (m === 'build') {
|
|
271
|
+
flow_key = 'web_build';
|
|
272
|
+
} else {
|
|
273
|
+
flow_key = 'web_dev';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
notes.push(`Port: ${ctx.port || 'not set in WEBVIEW_CONFIG'}`);
|
|
277
|
+
notes.push(`useHttps: ${ctx.useHttps}`);
|
|
278
|
+
if (ctx.accessControl.allowedUrls) {
|
|
279
|
+
notes.push(`accessControl.allowedUrls: ${ctx.accessControl.allowedUrls.join(', ')}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Android ──────────────────────────────────────────────────────────────
|
|
284
|
+
else if (p === 'android') {
|
|
285
|
+
if (!ctx.hasAndroid) {
|
|
286
|
+
warnings.push('WEBVIEW_CONFIG.android block missing from config/config.json. Android build will fail immediately.');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const isRelease = m === 'release' || ctx.androidBuildType === 'release';
|
|
290
|
+
flow_key = isRelease ? 'android_release' : 'android_debug';
|
|
291
|
+
|
|
292
|
+
if (isRelease) {
|
|
293
|
+
if (!ctx.hasKeystore) {
|
|
294
|
+
warnings.push('WEBVIEW_CONFIG.android.keystoreConfig missing — release build will fail at AAB signing step. Add keystoreConfig with keyAlias, storePassword, keyPassword, organizationInfo.');
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
if (!ctx.wv.android?.sdkPath) {
|
|
298
|
+
warnings.push('WEBVIEW_CONFIG.android.sdkPath not set — ADB and emulator validation will fail.');
|
|
299
|
+
}
|
|
300
|
+
if (!ctx.wv.android?.emulatorName) {
|
|
301
|
+
warnings.push('WEBVIEW_CONFIG.android.emulatorName not set — emulator launch step will fail.');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
notes.push(`buildType detected: ${ctx.androidBuildType}`);
|
|
306
|
+
if (ctx.wv.android?.sdkPath) notes.push(`sdkPath: ${ctx.wv.android.sdkPath}`);
|
|
307
|
+
if (ctx.wv.android?.emulatorName) notes.push(`emulatorName: ${ctx.wv.android.emulatorName}`);
|
|
308
|
+
if (ctx.wv.android?.buildOptimisation) notes.push('buildOptimisation: enabled');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── iOS ──────────────────────────────────────────────────────────────────
|
|
312
|
+
else if (p === 'ios') {
|
|
313
|
+
if (!ctx.hasIos) {
|
|
314
|
+
warnings.push('WEBVIEW_CONFIG.ios block missing from config/config.json. iOS build will fail immediately.');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const isRelease = m === 'release' || ctx.iosBuildType === 'release';
|
|
318
|
+
flow_key = isRelease ? 'ios_release' : 'ios_debug';
|
|
319
|
+
|
|
320
|
+
if (!ctx.wv.ios?.appBundleId) {
|
|
321
|
+
warnings.push('WEBVIEW_CONFIG.ios.appBundleId not set — Xcode build will use fallback "com.debug.webview".');
|
|
322
|
+
}
|
|
323
|
+
if (!ctx.wv.ios?.simulatorName && !isRelease) {
|
|
324
|
+
warnings.push('WEBVIEW_CONFIG.ios.simulatorName not set — simulator launch step will fail.');
|
|
325
|
+
}
|
|
326
|
+
if (ctx.hasGoogleSignIn && !ctx.wv.ios?.simulatorName) {
|
|
327
|
+
warnings.push('googleSignIn.enabled=true but iosClientId may be missing — check WEBVIEW_CONFIG.googleSignIn.iosClientId.');
|
|
328
|
+
}
|
|
329
|
+
if (isRelease && ctx.iosBuildType !== 'release') {
|
|
330
|
+
warnings.push('ios.buildType is not "Release" (case-sensitive). Release builds require exactly "Release" — not "release".');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
notes.push(`buildType detected: ${ctx.wv.ios?.buildType || 'not set (defaults to debug)'}`);
|
|
334
|
+
if (ctx.wv.ios?.appBundleId) notes.push(`appBundleId: ${ctx.wv.ios.appBundleId}`);
|
|
335
|
+
if (ctx.wv.ios?.simulatorName) notes.push(`simulatorName: ${ctx.wv.ios.simulatorName}`);
|
|
336
|
+
if (ctx.hasGoogleSignIn) notes.push('googleSignIn: enabled');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
else {
|
|
340
|
+
return { error: `Unknown platform "${platform}". Use: web | android | ios` };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── Related errors (debug assist) ────────────────────────────────────────
|
|
344
|
+
const errorKeywords = [p, flow_key.replace('_', ' ')];
|
|
345
|
+
if (symptom) errorKeywords.push(...symptom.toLowerCase().split(/\s+/));
|
|
346
|
+
const related_errors = getKnownErrors(errorKeywords);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
platform: p,
|
|
350
|
+
mode: flow_key,
|
|
351
|
+
project_root: catalystRoot.dir,
|
|
352
|
+
catalyst_version: catalystRoot.catalystVersion,
|
|
353
|
+
steps: MASTER_FLOWS[flow_key],
|
|
354
|
+
project_config: notes,
|
|
355
|
+
warnings: warnings.length ? warnings : undefined,
|
|
356
|
+
related_errors: related_errors.length ? related_errors : undefined,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── Tool: get_architecture_diagram ─────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
function handle_get_architecture_diagram({ feature, symptom } = {}) {
|
|
363
|
+
const catalystRoot = findCatalystRoot();
|
|
364
|
+
if (!catalystRoot) {
|
|
365
|
+
return { error: 'No catalyst-core project found. Run from inside a Catalyst app.' };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const ctx = getProjectContext(catalystRoot.dir);
|
|
369
|
+
|
|
370
|
+
// Match feature to a diagram key
|
|
371
|
+
const f = (feature || '').toLowerCase();
|
|
372
|
+
let diagram_key = null;
|
|
373
|
+
|
|
374
|
+
if (/universal|webview|native.*app|app.*arch/i.test(f)) diagram_key = 'universal_app';
|
|
375
|
+
else if (/request|fetch|data.*fetch|transport|api.*flow/i.test(f)) diagram_key = 'request_lifecycle';
|
|
376
|
+
else if (/build.*pipe|pipeline|webpack|compile|prepare/i.test(f)) diagram_key = 'build_pipeline';
|
|
377
|
+
else if (/bridge|postmessage|js.*native|native.*js/i.test(f)) diagram_key = 'bridge_architecture';
|
|
378
|
+
else if (/rout|navigation|page.*flow/i.test(f)) diagram_key = 'routing';
|
|
379
|
+
|
|
380
|
+
if (!diagram_key) {
|
|
381
|
+
// Fall back: search KB for the feature
|
|
382
|
+
const rows = _db.prepare(`
|
|
383
|
+
SELECT title, content, section FROM framework_knowledge
|
|
384
|
+
WHERE title LIKE ? OR content LIKE ?
|
|
385
|
+
ORDER BY id LIMIT 5
|
|
386
|
+
`).all(`%${feature}%`, `%${feature}%`);
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
feature,
|
|
390
|
+
matched_diagram: null,
|
|
391
|
+
note: `No master diagram for "${feature}". Here are related KB entries:`,
|
|
392
|
+
kb_matches: rows,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const diagram = MASTER_DIAGRAMS[diagram_key];
|
|
397
|
+
|
|
398
|
+
// ── Annotate with project-specific context ────────────────────────────────
|
|
399
|
+
const project_context = [];
|
|
400
|
+
|
|
401
|
+
if (diagram_key === 'universal_app') {
|
|
402
|
+
project_context.push(`Port: ${ctx.port || 'not configured'}`);
|
|
403
|
+
project_context.push(`useHttps: ${ctx.useHttps}`);
|
|
404
|
+
project_context.push(`Android configured: ${ctx.hasAndroid}`);
|
|
405
|
+
project_context.push(`iOS configured: ${ctx.hasIos}`);
|
|
406
|
+
if (ctx.hasGoogleSignIn) project_context.push('googleSignIn: enabled');
|
|
407
|
+
const { readJson } = makeProjectHelpers(catalystRoot.dir);
|
|
408
|
+
const splash = (readJson('config/config.json') || {}).splashScreen;
|
|
409
|
+
if (splash) project_context.push(`splashScreen: configured (duration: ${splash.duration || 'default'})`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (diagram_key === 'request_lifecycle') {
|
|
413
|
+
project_context.push(`allowedUrls: ${(ctx.accessControl.allowedUrls || []).join(', ') || 'none configured'}`);
|
|
414
|
+
project_context.push(`accessControl.enabled: ${ctx.accessControl.enabled ?? 'not set'}`);
|
|
415
|
+
project_context.push(`port: ${ctx.port || 'not set'}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (diagram_key === 'build_pipeline') {
|
|
419
|
+
project_context.push(`catalyst version: ${catalystRoot.catalystVersion}`);
|
|
420
|
+
project_context.push(`android build type: ${ctx.androidBuildType || 'not configured'}`);
|
|
421
|
+
project_context.push(`iOS build type: ${ctx.iosBuildType || 'not configured'}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (diagram_key === 'routing') {
|
|
425
|
+
const { grepSrc } = makeProjectHelpers(catalystRoot.dir);
|
|
426
|
+
const routeFiles = grepSrc('RouterDataProvider|createBrowserRouter|<Route');
|
|
427
|
+
project_context.push(`RouterDataProvider found in: ${routeFiles.length ? routeFiles.slice(0,3).join(', ') : 'not detected'}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Related errors ────────────────────────────────────────────────────────
|
|
431
|
+
const errorKeywords = [diagram_key.replace(/_/g, ' ')];
|
|
432
|
+
if (symptom) errorKeywords.push(...symptom.toLowerCase().split(/\s+/));
|
|
433
|
+
const related_errors = getKnownErrors(errorKeywords);
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
feature,
|
|
437
|
+
matched_diagram: diagram_key,
|
|
438
|
+
title: diagram.title,
|
|
439
|
+
description: diagram.description,
|
|
440
|
+
layers: diagram.layers,
|
|
441
|
+
flow: diagram.flow,
|
|
442
|
+
configurable_today: diagram.configurable_today || undefined,
|
|
443
|
+
known_pitfalls: diagram.known_pitfalls || undefined,
|
|
444
|
+
project_context,
|
|
445
|
+
related_errors: related_errors.length ? related_errors : undefined,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
module.exports = { init, handle_get_build_flow, handle_get_architecture_diagram };
|