@cometchat/skills-cli 2.4.0 → 2.4.1
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/index.js +388 -38
- package/dist/registry/v6/features/catalog.json +599 -135
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -301,7 +301,8 @@ import { readdirSync } from "node:fs";
|
|
|
301
301
|
|
|
302
302
|
// src/utils/version.ts
|
|
303
303
|
function stripRange(versionRange) {
|
|
304
|
-
|
|
304
|
+
const aliased = versionRange.replace(/^npm:.*@/, "");
|
|
305
|
+
return aliased.replace(/^[\^~>=<\s]+/, "").trim();
|
|
305
306
|
}
|
|
306
307
|
function extractMajor(versionRange) {
|
|
307
308
|
if (!versionRange) return null;
|
|
@@ -362,15 +363,9 @@ function detectExpo(root, deps, pkg) {
|
|
|
362
363
|
react_native_version: rnVersion
|
|
363
364
|
};
|
|
364
365
|
}
|
|
365
|
-
function detectBareReactNative(
|
|
366
|
+
function detectBareReactNative(_root, deps) {
|
|
366
367
|
if (!deps["react-native"]) return null;
|
|
367
368
|
if (deps["expo"]) return null;
|
|
368
|
-
const hasNativeIos = pathExists(p(root, "ios")) && (pathExists(p(root, "ios/Podfile")) || pathExists(p(root, "ios/Podfile.properties.json")));
|
|
369
|
-
const hasNativeAndroid = pathExists(p(root, "android")) && pathExists(p(root, "android/build.gradle"));
|
|
370
|
-
const hasRnConfig = pathExists(p(root, "react-native.config.js")) || pathExists(p(root, "react-native.config.ts"));
|
|
371
|
-
if (!hasNativeIos && !hasNativeAndroid && !hasRnConfig) {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
369
|
const rnVersion = extractVersion(deps["react-native"]);
|
|
375
370
|
return {
|
|
376
371
|
framework: "react-native",
|
|
@@ -835,6 +830,207 @@ function detectExistingIntegration(root) {
|
|
|
835
830
|
};
|
|
836
831
|
}
|
|
837
832
|
|
|
833
|
+
// src/detectors/version-conflict.ts
|
|
834
|
+
init_fs();
|
|
835
|
+
function mergedDeps(root) {
|
|
836
|
+
const pkg = readJsonOrNull(p(root, "package.json"));
|
|
837
|
+
if (!pkg) return {};
|
|
838
|
+
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
839
|
+
}
|
|
840
|
+
function matchMajor(text, re) {
|
|
841
|
+
const m = text.match(re);
|
|
842
|
+
return m && m[1] ? parseInt(m[1], 10) : null;
|
|
843
|
+
}
|
|
844
|
+
function androidBuildText(root) {
|
|
845
|
+
return ["build.gradle", "build.gradle.kts", "app/build.gradle", "app/build.gradle.kts"].map((f) => readFileOrNull(p(root, f)) ?? "").join("\n");
|
|
846
|
+
}
|
|
847
|
+
var WEB_FRAMEWORKS = /* @__PURE__ */ new Set(["reactjs", "nextjs", "react-router", "astro"]);
|
|
848
|
+
function detectVersionConflict(root, framework) {
|
|
849
|
+
const deps = mergedDeps(root);
|
|
850
|
+
if (framework && WEB_FRAMEWORKS.has(framework)) {
|
|
851
|
+
const m = extractMajor(deps["@cometchat/chat-uikit-react"]);
|
|
852
|
+
if (m != null && m !== 6) {
|
|
853
|
+
return {
|
|
854
|
+
kind: "kit-major-mismatch",
|
|
855
|
+
installed: `v${m}`,
|
|
856
|
+
expected: "v6",
|
|
857
|
+
detail: `Project has @cometchat/chat-uikit-react v${m} installed, but these skills target v6. Upgrade to ^6 (\`npm i @cometchat/chat-uikit-react@^6\`) before integrating \u2014 v${m} APIs differ from the v6 guidance the skills emit, so the generated code won't compile/run.`
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (framework === "react-native") {
|
|
862
|
+
const m = extractMajor(deps["@cometchat/chat-uikit-react-native"]);
|
|
863
|
+
if (m != null && m !== 5) {
|
|
864
|
+
return {
|
|
865
|
+
kind: "kit-major-mismatch",
|
|
866
|
+
installed: `v${m}`,
|
|
867
|
+
expected: "v5",
|
|
868
|
+
detail: `Project has @cometchat/chat-uikit-react-native v${m}, but these skills target v5 (the current RN UI Kit). Reconcile the installed version before integrating.`
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (framework === "angular") {
|
|
873
|
+
const m = extractMajor(deps["@cometchat/chat-uikit-angular"]);
|
|
874
|
+
if (m != null && m !== 5) {
|
|
875
|
+
return {
|
|
876
|
+
kind: "kit-major-mismatch",
|
|
877
|
+
installed: `v${m}`,
|
|
878
|
+
expected: "v5",
|
|
879
|
+
detail: `Project has @cometchat/chat-uikit-angular v${m}, but these skills target v5. Reconcile the installed version before integrating.`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (framework === "android") {
|
|
884
|
+
const t = androidBuildText(root);
|
|
885
|
+
const hasV5 = /com\.cometchat:chat-uikit-android:5/.test(t);
|
|
886
|
+
const hasV6 = /com\.cometchat:chatuikit-(compose|kotlin)-android:6/.test(t);
|
|
887
|
+
if (hasV5 && hasV6) {
|
|
888
|
+
return {
|
|
889
|
+
kind: "both-versions",
|
|
890
|
+
installed: "V5 + V6",
|
|
891
|
+
detail: "Both the V5 UI Kit (com.cometchat:chat-uikit-android:5.x) and the V6 UI Kit (com.cometchat:chatuikit-{compose,kotlin}-android:6.x) are declared in the Gradle build. They are different SDKs with overlapping class names \u2192 duplicate-class / namespace-collision build failures. Remove the cohort you are NOT integrating before proceeding."
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (framework === "flutter") {
|
|
896
|
+
const pub = readFileOrNull(p(root, "pubspec.yaml")) ?? "";
|
|
897
|
+
const hasV6 = /cometchat_chat_uikit\s*:\s*[\^~]?6\./.test(pub);
|
|
898
|
+
const hasV5 = /cometchat_chat_uikit\s*:\s*[\^~]?5\./.test(pub) || /cometchat_calls_uikit\s*:\s*[\^~]?5\./.test(pub);
|
|
899
|
+
if (hasV6 && hasV5) {
|
|
900
|
+
return {
|
|
901
|
+
kind: "both-versions",
|
|
902
|
+
installed: "V5 + V6",
|
|
903
|
+
detail: "pubspec.yaml declares both V6 (cometchat_chat_uikit ^6.x) and V5 (cometchat_chat_uikit ^5.x / cometchat_calls_uikit ^5.x) CometChat packages. The V5 GetX kit and V6 Bloc kit cannot coexist. Remove the cohort you are NOT integrating before proceeding."
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (framework === "ios") {
|
|
908
|
+
const cocoapods = (readFileOrNull(p(root, "Podfile")) ?? "") + "\n" + (readFileOrNull(p(root, "Podfile.lock")) ?? "");
|
|
909
|
+
const spm = (readFileOrNull(p(root, "Package.swift")) ?? "") + "\n" + (readFileOrNull(p(root, "Package.resolved")) ?? "") + "\n" + (readFileOrNull(p(root, ".swiftpm/Package.resolved")) ?? "");
|
|
910
|
+
const major = matchMajor(cocoapods, /CometChatUIKitSwift[^,\n]*?[\s'"(](\d+)\./) ?? matchMajor(spm, /cometchatuikitswift[\s\S]{0,300}"version"\s*:\s*"(\d+)\./i) ?? matchMajor(spm, /CometChatUIKitSwift[\s\S]{0,200}from:\s*"(\d+)\./);
|
|
911
|
+
if (major != null && major !== 5) {
|
|
912
|
+
return {
|
|
913
|
+
kind: "kit-major-mismatch",
|
|
914
|
+
installed: `v${major}`,
|
|
915
|
+
expected: "v5",
|
|
916
|
+
detail: `Project pins CometChatUIKitSwift v${major}, but these skills target v5. Reconcile the pinned version before integrating.`
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/detectors/coexistence.ts
|
|
924
|
+
init_fs();
|
|
925
|
+
function mergedDeps2(root) {
|
|
926
|
+
const pkg = readJsonOrNull(p(root, "package.json"));
|
|
927
|
+
if (!pkg) return {};
|
|
928
|
+
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
929
|
+
}
|
|
930
|
+
var NPM_COMPETITORS = {
|
|
931
|
+
sendbird: { name: "Sendbird", kind: "chat" },
|
|
932
|
+
"@sendbird/chat": { name: "Sendbird", kind: "chat" },
|
|
933
|
+
"sendbird-uikit": { name: "Sendbird UIKit", kind: "chat" },
|
|
934
|
+
"@sendbird/uikit-react": { name: "Sendbird UIKit", kind: "chat" },
|
|
935
|
+
"@sendbird/uikit-react-native": { name: "Sendbird UIKit", kind: "chat" },
|
|
936
|
+
"@sendbird/calls": { name: "Sendbird Calls", kind: "calling" },
|
|
937
|
+
"stream-chat": { name: "Stream Chat", kind: "chat" },
|
|
938
|
+
"stream-chat-react": { name: "Stream Chat", kind: "chat" },
|
|
939
|
+
"stream-chat-react-native": { name: "Stream Chat", kind: "chat" },
|
|
940
|
+
"stream-chat-expo": { name: "Stream Chat", kind: "chat" },
|
|
941
|
+
"@stream-io/video-react-sdk": { name: "Stream Video", kind: "calling" },
|
|
942
|
+
"@stream-io/video-react-native-sdk": { name: "Stream Video", kind: "calling" },
|
|
943
|
+
"twilio-chat": { name: "Twilio Conversations", kind: "chat" },
|
|
944
|
+
"@twilio/conversations": { name: "Twilio Conversations", kind: "chat" },
|
|
945
|
+
"twilio-video": { name: "Twilio Video", kind: "calling" },
|
|
946
|
+
pubnub: { name: "PubNub", kind: "chat" },
|
|
947
|
+
"@pubnub/chat": { name: "PubNub Chat", kind: "chat" },
|
|
948
|
+
talkjs: { name: "TalkJS", kind: "chat" },
|
|
949
|
+
"@talkjs/react": { name: "TalkJS", kind: "chat" },
|
|
950
|
+
"agora-rtc-sdk-ng": { name: "Agora", kind: "calling" },
|
|
951
|
+
"agora-rtc-react": { name: "Agora", kind: "calling" },
|
|
952
|
+
"react-native-agora": { name: "Agora", kind: "calling" },
|
|
953
|
+
"react-native-onesignal": { name: "OneSignal", kind: "push" },
|
|
954
|
+
"onesignal-cordova-plugin": { name: "OneSignal", kind: "push" },
|
|
955
|
+
"react-onesignal": { name: "OneSignal", kind: "push" }
|
|
956
|
+
};
|
|
957
|
+
var NATIVE_COMPETITORS = [
|
|
958
|
+
// Android (Gradle coordinates) + Flutter (pub names) + iOS (pod names)
|
|
959
|
+
{ pattern: /com\.sendbird\.sdk|sendbird_(sdk|chat_sdk)|\bSendBirdSDK\b|\bSendbirdChatSDK\b/, name: "Sendbird", kind: "chat" },
|
|
960
|
+
{ pattern: /io\.getstream:stream-chat-android|stream_chat_flutter|\bStreamChat\b/, name: "Stream Chat", kind: "chat" },
|
|
961
|
+
{ pattern: /io\.getstream:stream-video-android|stream_video_flutter|\bStreamVideo\b/, name: "Stream Video", kind: "calling" },
|
|
962
|
+
{ pattern: /com\.twilio:conversations|twilio_conversations|\bTwilioConversationsClient\b/, name: "Twilio Conversations", kind: "chat" },
|
|
963
|
+
{ pattern: /com\.twilio:video|twilio_programmable_video|\bTwilioVideo\b/, name: "Twilio Video", kind: "calling" },
|
|
964
|
+
{ pattern: /com\.pubnub:pubnub|pubnub_flutter|(^|\n)\s*pod\s+['"]PubNub['"]/, name: "PubNub", kind: "chat" },
|
|
965
|
+
{ pattern: /io\.agora\.rtc:|agora_rtc_engine|\bAgoraRtcEngine_iOS\b/, name: "Agora", kind: "calling" },
|
|
966
|
+
{ pattern: /com\.onesignal:OneSignal|onesignal_flutter|(^|\n)\s*pod\s+['"]OneSignal/, name: "OneSignal", kind: "push" }
|
|
967
|
+
];
|
|
968
|
+
function dedupeBy(items, key) {
|
|
969
|
+
const seen = /* @__PURE__ */ new Set();
|
|
970
|
+
const out = [];
|
|
971
|
+
for (const it of items) {
|
|
972
|
+
const k = key(it);
|
|
973
|
+
if (seen.has(k)) continue;
|
|
974
|
+
seen.add(k);
|
|
975
|
+
out.push(it);
|
|
976
|
+
}
|
|
977
|
+
return out;
|
|
978
|
+
}
|
|
979
|
+
function nativeManifestText(root) {
|
|
980
|
+
return [
|
|
981
|
+
"build.gradle",
|
|
982
|
+
"build.gradle.kts",
|
|
983
|
+
"app/build.gradle",
|
|
984
|
+
"app/build.gradle.kts",
|
|
985
|
+
"android/app/build.gradle",
|
|
986
|
+
// Flutter / RN-bare android
|
|
987
|
+
"pubspec.yaml",
|
|
988
|
+
"Podfile",
|
|
989
|
+
"ios/Podfile",
|
|
990
|
+
// Flutter / RN ios
|
|
991
|
+
"Package.swift"
|
|
992
|
+
].map((f) => readFileOrNull(p(root, f)) ?? "").join("\n");
|
|
993
|
+
}
|
|
994
|
+
var SERVICE_WORKER_PATHS = [
|
|
995
|
+
"public/sw.js",
|
|
996
|
+
"public/service-worker.js",
|
|
997
|
+
"public/serviceworker.js",
|
|
998
|
+
"public/firebase-messaging-sw.js",
|
|
999
|
+
"static/sw.js",
|
|
1000
|
+
// Astro / SvelteKit static dir
|
|
1001
|
+
"static/service-worker.js",
|
|
1002
|
+
"src/service-worker.ts",
|
|
1003
|
+
"src/service-worker.js",
|
|
1004
|
+
"src/sw.ts",
|
|
1005
|
+
"service-worker.js",
|
|
1006
|
+
"sw.js"
|
|
1007
|
+
];
|
|
1008
|
+
function detectExistingFirebase(root, deps) {
|
|
1009
|
+
if ("firebase" in deps || "@react-native-firebase/app" in deps || "@react-native-firebase/messaging" in deps) {
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
return pathExists(p(root, "google-services.json")) || pathExists(p(root, "app/google-services.json")) || pathExists(p(root, "android/app/google-services.json")) || pathExists(p(root, "GoogleService-Info.plist")) || pathExists(p(root, "ios/GoogleService-Info.plist")) || pathExists(p(root, "ios/Runner/GoogleService-Info.plist")) || pathExists(p(root, "public/firebase-messaging-sw.js"));
|
|
1013
|
+
}
|
|
1014
|
+
function detectCoexistence(root, _framework) {
|
|
1015
|
+
const deps = mergedDeps2(root);
|
|
1016
|
+
const found = [];
|
|
1017
|
+
for (const [dep, meta] of Object.entries(NPM_COMPETITORS)) {
|
|
1018
|
+
if (dep in deps) found.push({ name: meta.name, coordinate: dep, kind: meta.kind });
|
|
1019
|
+
}
|
|
1020
|
+
const nativeText = nativeManifestText(root);
|
|
1021
|
+
if (nativeText.trim()) {
|
|
1022
|
+
for (const c of NATIVE_COMPETITORS) {
|
|
1023
|
+
if (c.pattern.test(nativeText)) {
|
|
1024
|
+
found.push({ name: c.name, coordinate: c.name, kind: c.kind });
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const competing_sdks = dedupeBy(found, (s) => `${s.name}:${s.kind}`);
|
|
1029
|
+
const existing_firebase = detectExistingFirebase(root, deps);
|
|
1030
|
+
const existing_service_workers = SERVICE_WORKER_PATHS.filter((rel) => pathExists(p(root, rel)));
|
|
1031
|
+
return { competing_sdks, existing_firebase, existing_service_workers };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
838
1034
|
// src/detectors/compatibility.ts
|
|
839
1035
|
function computeCompatibility(fw) {
|
|
840
1036
|
const warnings = [];
|
|
@@ -1014,6 +1210,8 @@ async function runDetectors(root) {
|
|
|
1014
1210
|
const package_manager = detectPackageManager(root);
|
|
1015
1211
|
const credentials = detectCredentials(root, fw.env_prefix);
|
|
1016
1212
|
const existing_integration = detectExistingIntegration(root);
|
|
1213
|
+
const version_conflict = detectVersionConflict(root, fw.framework);
|
|
1214
|
+
const coexistence = detectCoexistence(root, fw.framework);
|
|
1017
1215
|
const compatibility = computeCompatibility(fw);
|
|
1018
1216
|
const architecture_context = detectArchitectureContext(root);
|
|
1019
1217
|
const project_name = detectProjectName(root);
|
|
@@ -1038,6 +1236,8 @@ async function runDetectors(root) {
|
|
|
1038
1236
|
package_manager,
|
|
1039
1237
|
credentials,
|
|
1040
1238
|
existing_integration,
|
|
1239
|
+
version_conflict,
|
|
1240
|
+
coexistence,
|
|
1041
1241
|
compatibility,
|
|
1042
1242
|
architecture_context
|
|
1043
1243
|
};
|
|
@@ -1153,6 +1353,32 @@ function printHumanReadable(r) {
|
|
|
1153
1353
|
lines.push(` - ${f}`);
|
|
1154
1354
|
}
|
|
1155
1355
|
}
|
|
1356
|
+
if (r.version_conflict) {
|
|
1357
|
+
lines.push("");
|
|
1358
|
+
lines.push(` \u26A0 VERSION CONFLICT (${r.version_conflict.kind}): ${r.version_conflict.installed}`);
|
|
1359
|
+
lines.push(` ${r.version_conflict.detail}`);
|
|
1360
|
+
lines.push(" \u2192 Reconcile this BEFORE integrating; the dispatcher will stop and ask.");
|
|
1361
|
+
}
|
|
1362
|
+
const co = r.coexistence;
|
|
1363
|
+
if (co && (co.competing_sdks.length > 0 || co.existing_firebase || co.existing_service_workers.length > 0)) {
|
|
1364
|
+
lines.push("");
|
|
1365
|
+
lines.push(" Coexistence (competing stack):");
|
|
1366
|
+
if (co.competing_sdks.length > 0) {
|
|
1367
|
+
lines.push(" \u26A0 Competing chat/calling/push SDK(s) detected:");
|
|
1368
|
+
for (const s of co.competing_sdks) {
|
|
1369
|
+
lines.push(` - ${s.name} (${s.kind}) \u2014 ${s.coordinate}`);
|
|
1370
|
+
}
|
|
1371
|
+
lines.push(" \u2192 Dispatcher will ask: replace it, run alongside (namespaced), or switch fully?");
|
|
1372
|
+
}
|
|
1373
|
+
if (co.existing_firebase) {
|
|
1374
|
+
lines.push(" \u26A0 Existing Firebase setup \u2014 CometChat push is FCM-based; reuse the same app,");
|
|
1375
|
+
lines.push(" don't double-init, and merge (don't overwrite) firebase-messaging-sw.js.");
|
|
1376
|
+
}
|
|
1377
|
+
if (co.existing_service_workers.length > 0) {
|
|
1378
|
+
lines.push(" \u26A0 Existing service worker(s) \u2014 web push must not clobber:");
|
|
1379
|
+
for (const sw of co.existing_service_workers) lines.push(` - ${sw}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1156
1382
|
lines.push("");
|
|
1157
1383
|
lines.push(` Compatibility: ${r.compatibility.supported ? "supported" : "NOT supported"}`);
|
|
1158
1384
|
if (r.compatibility.warnings.length > 0) {
|
|
@@ -3455,10 +3681,10 @@ function checkChatInitBeforeCallsInit(root, family) {
|
|
|
3455
3681
|
for (const f of files) {
|
|
3456
3682
|
const content = readFileOrNull(p(root, f));
|
|
3457
3683
|
if (!content) continue;
|
|
3458
|
-
const
|
|
3459
|
-
const
|
|
3684
|
+
const code = stripComments(content);
|
|
3685
|
+
const chatInit = /CometChat(UIKit)?\.init|CometChat\.init\(/.exec(code);
|
|
3686
|
+
const callsInit = /CometChatCalls\.init|CometChatUIKitCalls\.init/.exec(code);
|
|
3460
3687
|
if (!chatInit || !callsInit) {
|
|
3461
|
-
if (chatInit && !callsInit) return { status: "fail", reason: `${f}: chat init found but no Calls SDK init in same file` };
|
|
3462
3688
|
continue;
|
|
3463
3689
|
}
|
|
3464
3690
|
if (chatInit.index < callsInit.index) return { status: "pass" };
|
|
@@ -3467,6 +3693,9 @@ function checkChatInitBeforeCallsInit(root, family) {
|
|
|
3467
3693
|
return { status: "skip", reason: "no init file containing both Chat + Calls SDK init found" };
|
|
3468
3694
|
}
|
|
3469
3695
|
function checkIncomingCallRootMount(root, family) {
|
|
3696
|
+
if ((family === "web" || family === "native" || family === "angular") && isSessionOnlyMode(root, family)) {
|
|
3697
|
+
return { status: "skip", reason: "session/meeting mode \u2014 no incoming-call entity (joinSession; no ringing surface)" };
|
|
3698
|
+
}
|
|
3470
3699
|
if (pathExists(p(root, ".cometchat/builder.json"))) {
|
|
3471
3700
|
const homeCandidates = [
|
|
3472
3701
|
"src/CometChat/components/CometChatHome/CometChatHome.tsx",
|
|
@@ -3613,14 +3842,20 @@ function checkHangupTeardown(root, family) {
|
|
|
3613
3842
|
}
|
|
3614
3843
|
}
|
|
3615
3844
|
const requiredByFamily = {
|
|
3616
|
-
web: [/CometChatCalls\.(leaveSession|endSession)/, /\.getTracks\(\)\.forEach\([
|
|
3845
|
+
web: [/CometChatCalls\.(leaveSession|endSession)/, /\.getTracks\(\)\.forEach\([\s\S]*?\.stop\(\s*\)\s*\)/],
|
|
3617
3846
|
native: [/CometChatCalls\.(leaveSession|endSession)/, /RNCallKeep\.endCall/],
|
|
3618
|
-
angular: [/CometChatCalls\.(leaveSession|endSession)/, /\.getTracks\(\)\.forEach\([
|
|
3847
|
+
angular: [/CometChatCalls\.(leaveSession|endSession)/, /\.getTracks\(\)\.forEach\([\s\S]*?\.stop\(\s*\)\s*\)/],
|
|
3619
3848
|
android: [/CallSession\.getInstance\(\)\.leaveSession|CometChatCalls\.endSession|CometChatUIKit\.endSession|leaveSession\(\)/, /stopService|stopForeground|finish\(\)/],
|
|
3620
3849
|
ios: [/CallSession\.shared\.leaveSession|CometChatCalls\.(leaveSession|endSession)/, /setActive\(false[^)]*notifyOthersOnDeactivation/],
|
|
3621
3850
|
flutter: [/CallSession\.getInstance\(\)\.leaveSession|CometChatUIKitCalls\.endSession|CometChatCalls\.(leaveSession|endSession)/, /FlutterCallkitIncoming\.endAllCalls|Navigator\.of\(.*\)\.pop|CometChatOngoingCallService\.abort/]
|
|
3622
3851
|
};
|
|
3623
|
-
|
|
3852
|
+
let required = requiredByFamily[family];
|
|
3853
|
+
if (family === "web" || family === "angular") {
|
|
3854
|
+
const ownsMedia = anySourceFileMatches(root, family, /getUserMedia\s*\(/);
|
|
3855
|
+
if (!ownsMedia) {
|
|
3856
|
+
required = [/CometChatCalls\.(leaveSession|endSession)/];
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3624
3859
|
const exts = family === "ios" ? [".swift"] : family === "android" ? [".kt", ".java"] : family === "flutter" ? [".dart"] : [".ts", ".tsx", ".js", ".jsx"];
|
|
3625
3860
|
const dirs = family === "ios" ? ["ios"] : family === "android" ? ["app/src/main"] : family === "flutter" ? ["lib"] : ["src", "app"];
|
|
3626
3861
|
let foundAll = true;
|
|
@@ -3762,6 +3997,28 @@ function gatherSourceFiles(root, family) {
|
|
|
3762
3997
|
const re = SOURCE_EXTS_FOR[family];
|
|
3763
3998
|
return walkFiles(startDir, (name) => re.test(name));
|
|
3764
3999
|
}
|
|
4000
|
+
function anySourceFileMatches(root, family, re) {
|
|
4001
|
+
for (const file of gatherSourceFiles(root, family)) {
|
|
4002
|
+
const content = readFileOrNull(file) ?? "";
|
|
4003
|
+
if (re.test(content)) return true;
|
|
4004
|
+
}
|
|
4005
|
+
return false;
|
|
4006
|
+
}
|
|
4007
|
+
function isSessionOnlyMode(root, family) {
|
|
4008
|
+
const usesSession = anySourceFileMatches(root, family, /CometChatCalls\.(joinSession|startSession)/);
|
|
4009
|
+
if (!usesSession) return false;
|
|
4010
|
+
const usesRinging = anySourceFileMatches(
|
|
4011
|
+
root,
|
|
4012
|
+
family,
|
|
4013
|
+
/CometChatIncomingCall|CometChatOutgoingCall|cometchat-incoming-call|cometchat-outgoing-call|\.initiateCall\b/
|
|
4014
|
+
);
|
|
4015
|
+
return !usesRinging;
|
|
4016
|
+
}
|
|
4017
|
+
function hasChatSdkInPackage(root, family) {
|
|
4018
|
+
const pkg = readFileOrNull(p(root, "package.json")) ?? "";
|
|
4019
|
+
const chatSdkPkg = family === "native" ? "@cometchat/chat-sdk-react-native" : "@cometchat/chat-sdk-javascript";
|
|
4020
|
+
return new RegExp(`"${chatSdkPkg.replace(/[/-]/g, "\\$&")}"`).test(pkg);
|
|
4021
|
+
}
|
|
3765
4022
|
function checkEventListenerCleanup(root, family) {
|
|
3766
4023
|
if (family === "ios" || family === "android" || family === "flutter") {
|
|
3767
4024
|
return { status: "skip", reason: "native families use interface-based listeners, not addEventListener" };
|
|
@@ -3771,17 +4028,20 @@ function checkEventListenerCleanup(root, family) {
|
|
|
3771
4028
|
for (const file of files) {
|
|
3772
4029
|
const content = readFileOrNull(file) ?? "";
|
|
3773
4030
|
if (!/CometChatCalls\.addEventListener/.test(content)) continue;
|
|
4031
|
+
const hasLegacyRemove = /CometChatCalls\.removeEventListener\s*\(/.test(content);
|
|
4032
|
+
const hasAbortSignal = /addEventListener\s*\([\s\S]*?\bsignal\b/.test(content) || /AbortController/.test(content);
|
|
4033
|
+
const capturesReturn = /(?:const|let|var)\s+\w+\s*(?::[^=\n]+)?=\s*CometChatCalls\.addEventListener/.test(content) || /(?:const|let|var)\s+\w+\s*(?::[^=\n]+)?=\s*\[[\s\S]*?CometChatCalls\.addEventListener/.test(content) || /return\s+CometChatCalls\.addEventListener/.test(content);
|
|
4034
|
+
const invokesUnsub = /\b\w*off\w*\s*\(\s*\)/i.test(content) || /=>\s*\w+\s*\(\s*\)/.test(content) || /\.forEach\(\s*\(?\s*\w+\s*\)?\s*=>/.test(content);
|
|
4035
|
+
if (hasLegacyRemove || hasAbortSignal || capturesReturn && invokesUnsub) continue;
|
|
3774
4036
|
const adds = [...content.matchAll(/CometChatCalls\.addEventListener\(\s*["']([\w-]+)["']/g)].map((m) => m[1]);
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
offenders.push(`${file.replace(root + "/", "")}: missing removeEventListener for [${[...new Set(missing)].join(", ")}]`);
|
|
3779
|
-
}
|
|
4037
|
+
offenders.push(
|
|
4038
|
+
`${file.replace(root + "/", "")}: addEventListener (${[...new Set(adds)].join(", ")}) without invoking the returned unsubscribe \u2014 v5 returns an off() fn; call it in teardown`
|
|
4039
|
+
);
|
|
3780
4040
|
}
|
|
3781
4041
|
if (offenders.length === 0) return { status: "pass" };
|
|
3782
4042
|
return {
|
|
3783
4043
|
status: "fail",
|
|
3784
|
-
reason: `${offenders.length} file(s) call addEventListener without
|
|
4044
|
+
reason: `${offenders.length} file(s) call addEventListener without invoking the returned unsubscribe \u2014 listener leak`,
|
|
3785
4045
|
details: offenders.slice(0, 5).join("\n")
|
|
3786
4046
|
};
|
|
3787
4047
|
}
|
|
@@ -3946,7 +4206,7 @@ function checkVirtualBgPlatformGate(root, family) {
|
|
|
3946
4206
|
if (family === "web" || family === "angular") return { status: "skip", reason: "platform supports virtual background" };
|
|
3947
4207
|
const files = gatherSourceFiles(root, family);
|
|
3948
4208
|
for (const file of files) {
|
|
3949
|
-
const content = readFileOrNull(file) ?? "";
|
|
4209
|
+
const content = stripComments(readFileOrNull(file) ?? "");
|
|
3950
4210
|
if (/hideVirtualBackgroundButton\s*[:=]\s*false/i.test(content) || /setHideVirtualBackgroundButton\(\s*false\s*\)/i.test(content)) {
|
|
3951
4211
|
return {
|
|
3952
4212
|
status: "fail",
|
|
@@ -3960,12 +4220,15 @@ function checkGroupAsSessionHelper(root, family) {
|
|
|
3960
4220
|
if (family === "ios" || family === "android" || family === "flutter") {
|
|
3961
4221
|
return { status: "skip", reason: "currently only checked for JS families" };
|
|
3962
4222
|
}
|
|
4223
|
+
if (!hasChatSdkInPackage(root, family)) {
|
|
4224
|
+
return { status: "skip", reason: "no Chat SDK in project \u2014 calls-only session mode; in-call chat uses the Calls SDK's built-in chat, no CometChat group needed" };
|
|
4225
|
+
}
|
|
3963
4226
|
const files = gatherSourceFiles(root, family);
|
|
3964
4227
|
let usesInCallChat = false;
|
|
3965
4228
|
let hasGroupHelper = false;
|
|
3966
4229
|
for (const file of files) {
|
|
3967
4230
|
const content = readFileOrNull(file) ?? "";
|
|
3968
|
-
if (/
|
|
4231
|
+
if (/hideChatButton\s*[:=]\s*false/.test(content)) usesInCallChat = true;
|
|
3969
4232
|
if (/createGroup|ensureCallGroup|ensureGroup|getGroup\s*\(/.test(content)) hasGroupHelper = true;
|
|
3970
4233
|
}
|
|
3971
4234
|
if (!usesInCallChat) return { status: "skip", reason: "in-call chat not wired" };
|
|
@@ -3977,7 +4240,7 @@ function checkCallTokenServerSide(root, family) {
|
|
|
3977
4240
|
}
|
|
3978
4241
|
const files = gatherSourceFiles(root, family);
|
|
3979
4242
|
for (const file of files) {
|
|
3980
|
-
const content = readFileOrNull(file) ?? "";
|
|
4243
|
+
const content = stripComments(readFileOrNull(file) ?? "");
|
|
3981
4244
|
if (/COMETCHAT_AUTH_KEY|cometchat[_.]?auth[_.]?key/i.test(content) && /generateToken|callToken|getCallToken/i.test(content)) {
|
|
3982
4245
|
return {
|
|
3983
4246
|
status: "fail",
|
|
@@ -4086,6 +4349,9 @@ function checkRecordingConsentUi(root, family) {
|
|
|
4086
4349
|
reason: "recording is enabled but no consent/notice UI found in source \u2014 required for two-party-consent jurisdictions"
|
|
4087
4350
|
};
|
|
4088
4351
|
}
|
|
4352
|
+
function stripComments(src) {
|
|
4353
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1");
|
|
4354
|
+
}
|
|
4089
4355
|
function checkNoStaleSdkApis(root, family) {
|
|
4090
4356
|
const exts = family === "ios" ? [".swift"] : family === "android" ? [".kt", ".java"] : family === "flutter" ? [".dart"] : [".ts", ".tsx", ".js", ".jsx"];
|
|
4091
4357
|
const dirs = family === "ios" ? ["ios"] : family === "android" ? ["app/src/main"] : family === "flutter" ? ["lib"] : ["src", "app"];
|
|
@@ -4135,7 +4401,7 @@ function checkNoStaleSdkApis(root, family) {
|
|
|
4135
4401
|
if (isDirectory(ep)) {
|
|
4136
4402
|
if (!entry.startsWith(".") && entry !== "node_modules" && entry !== "Pods" && entry !== "build" && entry !== ".dart_tool") stack.push(ep);
|
|
4137
4403
|
} else if (exts.some((e) => entry.endsWith(e))) {
|
|
4138
|
-
const content = readFileOrNull(ep) ?? "";
|
|
4404
|
+
const content = stripComments(readFileOrNull(ep) ?? "");
|
|
4139
4405
|
for (const { pattern, reason } of banned) {
|
|
4140
4406
|
if (pattern.test(content)) {
|
|
4141
4407
|
violations.push(`${ep.replace(root + "/", "")}: ${reason}`);
|
|
@@ -7009,6 +7275,10 @@ async function getCurrentUserWithLastApp(host, token) {
|
|
|
7009
7275
|
}
|
|
7010
7276
|
return { ...user, last_app };
|
|
7011
7277
|
}
|
|
7278
|
+
function isExtensionEnabled(e) {
|
|
7279
|
+
if (!e) return false;
|
|
7280
|
+
return e.enabled === 1 || e.enabled === true || e.isActive === true || e.status === "enabled";
|
|
7281
|
+
}
|
|
7012
7282
|
async function listInstalledExtensions(host, token, appId) {
|
|
7013
7283
|
const res = await request(
|
|
7014
7284
|
host,
|
|
@@ -7040,7 +7310,7 @@ async function toggleExtension(host, token, appId, extensionId, action) {
|
|
|
7040
7310
|
}
|
|
7041
7311
|
}
|
|
7042
7312
|
async function toggleAiFeature(host, token, appId, featureKey, action) {
|
|
7043
|
-
const path = `/apps/${encodeURIComponent(appId)}/features
|
|
7313
|
+
const path = `/apps/${encodeURIComponent(appId)}/features/${encodeURIComponent(featureKey)}/enabled`;
|
|
7044
7314
|
const res = await request(host, path, {
|
|
7045
7315
|
method: action === "enable" ? "POST" : "DELETE",
|
|
7046
7316
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -7516,6 +7786,8 @@ async function featuresToggle(args, featureName, action) {
|
|
|
7516
7786
|
}
|
|
7517
7787
|
const verb = action === "enable" ? "Enabled" : "Disabled";
|
|
7518
7788
|
const autoWiredNote = match.auto_wired_in_uikit === true ? " The UI Kit's initiateAfterLogin() auto-attaches the decorator, so a browser refresh is all you need." : " Register the extension via UIKitSettingsBuilder.setExtensions([...]) if you haven't already \u2014 the toggle alone isn't enough for non-default extensions. Query the docs MCP for the exact builder syntax.";
|
|
7789
|
+
const codeNote = match.code === "custom-code" ? `This feature needs CUSTOM CLIENT CODE (tier 5). The reference implementation is in the docs (${match.docs_topic ?? "ui-kit/react/extensions"}) \u2014 fetch it via the docs MCP and adapt it; do NOT hand-roll.` : match.code === "steps-in-docs" ? `This feature needs client wiring \u2014 follow the documented steps at ${match.docs_topic ?? "ui-kit/react/extensions"} (query the docs MCP).` : match.code === "stitch-components" ? "This feature is surfaced by COMPOSING existing UI Kit components \u2014 see the kit sample app for the wiring pattern." : "";
|
|
7790
|
+
const settingsNote = match.dashboard_settings ? `Additional dashboard setup required: ${match.dashboard_settings}.` : "";
|
|
7519
7791
|
const result = {
|
|
7520
7792
|
status: action === "enable" ? "enabled" : "disabled",
|
|
7521
7793
|
feature: match,
|
|
@@ -7523,8 +7795,10 @@ async function featuresToggle(args, featureName, action) {
|
|
|
7523
7795
|
next_steps: [
|
|
7524
7796
|
`${verb} ${match.name} on app ${appId}.`,
|
|
7525
7797
|
"Hard-refresh (Cmd+Shift+R / Ctrl+Shift+R) the browser tab running your dev server to pick up the change.",
|
|
7526
|
-
autoWiredNote.trim()
|
|
7527
|
-
|
|
7798
|
+
autoWiredNote.trim(),
|
|
7799
|
+
settingsNote,
|
|
7800
|
+
codeNote
|
|
7801
|
+
].filter(Boolean)
|
|
7528
7802
|
};
|
|
7529
7803
|
if (json) {
|
|
7530
7804
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -8919,7 +9193,7 @@ async function applyFeature(args) {
|
|
|
8919
9193
|
],
|
|
8920
9194
|
"dashboard-only": [
|
|
8921
9195
|
`${feature.name} is configured manually in the dashboard. Disable it there:`,
|
|
8922
|
-
feature.dashboard_path ? ` Dashboard \u2192 ${feature.dashboard_path} \u2192 Disable` : ` Dashboard \u2192
|
|
9196
|
+
feature.dashboard_path ? ` Dashboard \u2192 ${feature.dashboard_path} \u2192 Disable` : ` Dashboard \u2192 Chat & Messaging \u2192 Features \u2192 ${feature.name} \u2192 Disable`
|
|
8923
9197
|
],
|
|
8924
9198
|
"package-install": [
|
|
8925
9199
|
`${feature.name} required installing a package. To remove:`,
|
|
@@ -9075,6 +9349,7 @@ async function applyModeration(args, result, root, state2, explicitAppId, featur
|
|
|
9075
9349
|
result.feature_type = "moderation";
|
|
9076
9350
|
result.next_steps = [
|
|
9077
9351
|
`${feature.name} configured and enabled on app ${appId}. Config keys: ${Object.keys(config2).join(", ")}.`,
|
|
9352
|
+
"\u26A0\uFE0F LEGACY moderation extension. The canonical moderation system is now Rules Management (Dashboard \u2192 Moderation \u2192 Settings \u2192 Rules, or the /moderation/rules REST API) \u2014 rules auto-apply to all messages with no client code. If you use moderation Rules, DISABLE legacy extensions like this one first: running BOTH double-processes every message (delays + perf issues). See docs/moderation/overview.",
|
|
9078
9353
|
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
9079
9354
|
];
|
|
9080
9355
|
return outputResult11(args, result, 0);
|
|
@@ -9230,11 +9505,31 @@ async function applyExtensionToggle(args, result, root, state2, explicitAppId, f
|
|
|
9230
9505
|
return outputResult11(args, result, 1);
|
|
9231
9506
|
}
|
|
9232
9507
|
const remove = args.flags.remove === true || args.flags.remove === "true";
|
|
9508
|
+
const extensionKey = feature.extension_id ?? feature.id;
|
|
9233
9509
|
try {
|
|
9234
|
-
await toggleExtension(host, creds.token, appId,
|
|
9510
|
+
await toggleExtension(host, creds.token, appId, extensionKey, remove ? "disable" : "enable");
|
|
9235
9511
|
} catch (err) {
|
|
9236
9512
|
return handleApiError(args, result, err, feature);
|
|
9237
9513
|
}
|
|
9514
|
+
let lagWarning = null;
|
|
9515
|
+
try {
|
|
9516
|
+
const installed = await listInstalledExtensions(host, creds.token, appId);
|
|
9517
|
+
const match = installed.find((e) => e.id === extensionKey);
|
|
9518
|
+
if (!remove && !match) {
|
|
9519
|
+
result.status = "error";
|
|
9520
|
+
result.error = `The dashboard accepted the request but extension "${extensionKey}" did not appear in this app's extension list afterward \u2014 the extension key is wrong for this feature, or it isn't available on your plan.`;
|
|
9521
|
+
const enabledNow = installed.filter((e) => isExtensionEnabled(e)).map((e) => e.id).join(", ") || "(none)";
|
|
9522
|
+
result.next_steps = [
|
|
9523
|
+
`Extensions currently enabled on this app: ${enabledNow}.`,
|
|
9524
|
+
`Confirm "${feature.name}" is available on your plan (dashboard \u2192 Chat & Messaging \u2192 Features), then retry.`
|
|
9525
|
+
];
|
|
9526
|
+
return outputResult11(args, result, 1);
|
|
9527
|
+
}
|
|
9528
|
+
if (!remove && match && !isExtensionEnabled(match)) {
|
|
9529
|
+
lagWarning = `Note: the toggle was accepted but the dashboard does not yet report "${feature.name}" as enabled. This is usually propagation lag \u2014 re-check in the dashboard (Chat & Messaging \u2192 Features) in a moment; re-run this command if it hasn't flipped.`;
|
|
9530
|
+
}
|
|
9531
|
+
} catch {
|
|
9532
|
+
}
|
|
9238
9533
|
if (state2) {
|
|
9239
9534
|
if (remove) {
|
|
9240
9535
|
state2.applied_features = (state2.applied_features ?? []).filter((f) => f !== feature.id);
|
|
@@ -9247,7 +9542,7 @@ async function applyExtensionToggle(args, result, root, state2, explicitAppId, f
|
|
|
9247
9542
|
summary: `${remove ? "Disabled" : "Enabled"} extension "${feature.name}" via dashboard API on app ${appId}`,
|
|
9248
9543
|
inputs: { feature: feature.id, appId, remove },
|
|
9249
9544
|
decisions: {
|
|
9250
|
-
api_endpoint: `POST /apps/${appId}/extensions { ${remove ? "disabled" : "enabled"}: ["${
|
|
9545
|
+
api_endpoint: `POST /apps/${appId}/extensions { ${remove ? "disabled" : "enabled"}: ["${extensionKey}"] }`,
|
|
9251
9546
|
auto_wired: feature.auto_wired_in_uikit ? "yes" : "no"
|
|
9252
9547
|
},
|
|
9253
9548
|
files_modified: [],
|
|
@@ -9265,6 +9560,7 @@ async function applyExtensionToggle(args, result, root, state2, explicitAppId, f
|
|
|
9265
9560
|
feature.auto_wired_in_uikit ? "The UI Kit's defaultExtensions[] picks this up automatically \u2014 refresh the browser." : "Refresh the chat in the browser. If the feature requires UIKitSettingsBuilder.setExtensions([...]), see the docs link below.",
|
|
9266
9561
|
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
9267
9562
|
];
|
|
9563
|
+
if (lagWarning) result.next_steps = [lagWarning, ...result.next_steps];
|
|
9268
9564
|
return outputResult11(args, result, 0);
|
|
9269
9565
|
}
|
|
9270
9566
|
async function applyAiFeatureToggle(args, result, root, state2, explicitAppId, feature) {
|
|
@@ -9335,7 +9631,7 @@ async function applyAiFeatureToggle(args, result, root, state2, explicitAppId, f
|
|
|
9335
9631
|
inputs: { feature: feature.id, appId, openai_key_written: !remove && needsKeyWrite, remove },
|
|
9336
9632
|
decisions: {
|
|
9337
9633
|
settings_endpoint: !remove && needsKeyWrite ? `PUT /apps/${appId}/ai/settings { openAIKey: "(redacted)" }` : "skipped",
|
|
9338
|
-
toggle_endpoint: `${remove ? "DELETE" : "POST"} /apps/${appId}/features
|
|
9634
|
+
toggle_endpoint: `${remove ? "DELETE" : "POST"} /apps/${appId}/features/${feature.ai_key}/enabled`
|
|
9339
9635
|
},
|
|
9340
9636
|
files_modified: [],
|
|
9341
9637
|
next_actions: [
|
|
@@ -9359,7 +9655,7 @@ function applyDashboardOnly(args, result, feature) {
|
|
|
9359
9655
|
result.status = "manual-action-required";
|
|
9360
9656
|
result.error = `${feature.name} requires config beyond a boolean toggle (third-party API key or multi-field setup).`;
|
|
9361
9657
|
result.next_steps = [
|
|
9362
|
-
feature.dashboard_path ? `In the dashboard at https://app.cometchat.com \u2192 ${feature.dashboard_path}` : `Open https://app.cometchat.com and configure ${feature.name} on the
|
|
9658
|
+
feature.dashboard_path ? `In the dashboard at https://app.cometchat.com \u2192 ${feature.dashboard_path}` : `Open https://app.cometchat.com and configure ${feature.name} on the Chat & Messaging \u2192 Features page.`,
|
|
9363
9659
|
...feature.docs_topic ? [`Docs: https://www.cometchat.com/docs/ui-kit/react/${feature.docs_topic}`] : []
|
|
9364
9660
|
];
|
|
9365
9661
|
return outputResult11(args, result, 1);
|
|
@@ -11794,6 +12090,32 @@ import { spawnSync as spawnSync5 } from "node:child_process";
|
|
|
11794
12090
|
import { tmpdir } from "node:os";
|
|
11795
12091
|
init_config();
|
|
11796
12092
|
init_fs();
|
|
12093
|
+
var BUILDER_TOGGLE_TO_DASHBOARD_FEATURE = {
|
|
12094
|
+
deeperUserEngagement: {
|
|
12095
|
+
messageTranslation: { id: "message-translation", type: "extension" },
|
|
12096
|
+
polls: { id: "polls", type: "extension" },
|
|
12097
|
+
collaborativeWhiteboard: { id: "collaborative-whiteboard", type: "extension" },
|
|
12098
|
+
collaborativeDocument: { id: "collaborative-document", type: "extension" },
|
|
12099
|
+
stickers: { id: "stickers", type: "extension" }
|
|
12100
|
+
},
|
|
12101
|
+
aiUserCopilot: {
|
|
12102
|
+
conversationStarter: { id: "conversation-starter", type: "ai-feature" },
|
|
12103
|
+
conversationSummary: { id: "conversation-summary", type: "ai-feature" },
|
|
12104
|
+
smartReply: { id: "smart-replies", type: "ai-feature" }
|
|
12105
|
+
}
|
|
12106
|
+
};
|
|
12107
|
+
function dashboardSetupNeeded(chatFeatures) {
|
|
12108
|
+
const out = [];
|
|
12109
|
+
if (!chatFeatures) return out;
|
|
12110
|
+
for (const [section, keyMap] of Object.entries(BUILDER_TOGGLE_TO_DASHBOARD_FEATURE)) {
|
|
12111
|
+
const block = chatFeatures[section];
|
|
12112
|
+
if (!block || typeof block !== "object") continue;
|
|
12113
|
+
for (const [builderKey, feat] of Object.entries(keyMap)) {
|
|
12114
|
+
if (block[builderKey] === true) out.push({ ...feat, builderKey, section });
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
return out;
|
|
12118
|
+
}
|
|
11797
12119
|
var HELP20 = `
|
|
11798
12120
|
cometchat builder \u2014 manage Visual Chat Builders for an app
|
|
11799
12121
|
|
|
@@ -12120,6 +12442,11 @@ var PLATFORM_LAYOUTS = {
|
|
|
12120
12442
|
// the JSON settings. SKILL.md §"Visual Builder integration" lists
|
|
12121
12443
|
// these as verbatim copies. ThreadedMessagesVC.swift is included
|
|
12122
12444
|
// because MessagesVC.swift imports it.
|
|
12445
|
+
// BUILD-BREAKER (ENG-35337): the verbatim ThreadedMessagesVC.swift reads
|
|
12446
|
+
// CometChatBuilderSettings.shared.layout.compactMessageComposer, which is
|
|
12447
|
+
// MISSING in CometChatBuilder <= 1.1.2 -> compile failure. Pin
|
|
12448
|
+
// `pod 'CometChatBuilder', '> 1.1.2'` (SPM: from: "1.1.3"). The reference
|
|
12449
|
+
// app's Podfile is unversioned -- that's the trap, not the fix.
|
|
12123
12450
|
files: [
|
|
12124
12451
|
{
|
|
12125
12452
|
zipPath: "CometChatBuilderSwift/BuilderApp/View Controllers/CometChat Components/MessagesVC.swift",
|
|
@@ -12286,7 +12613,9 @@ async function builderExport(args) {
|
|
|
12286
12613
|
}
|
|
12287
12614
|
const layout = PLATFORM_LAYOUTS[platform];
|
|
12288
12615
|
if (!layout) {
|
|
12289
|
-
const
|
|
12616
|
+
const supported = Object.keys(PLATFORM_LAYOUTS).join(", ");
|
|
12617
|
+
const coreSkill = platform === "react-native" ? "native" : platform;
|
|
12618
|
+
const msg = `\`builder export --platform ${platform}\` is not yet implemented (supported: ${supported}). For ${platform}, follow the manual recipe in cometchat-${coreSkill}-core/SKILL.md \xA7"Visual Builder integration".`;
|
|
12290
12619
|
if (json) return emitError(true, msg);
|
|
12291
12620
|
console.error(msg);
|
|
12292
12621
|
return 1;
|
|
@@ -12360,6 +12689,7 @@ async function builderExport(args) {
|
|
|
12360
12689
|
const due = chatFeatures.deeperUserEngagement;
|
|
12361
12690
|
if (due && due.mentionAll === void 0) due.mentionAll = true;
|
|
12362
12691
|
}
|
|
12692
|
+
const needSetup = dashboardSetupNeeded(chatFeatures);
|
|
12363
12693
|
const tmpRoot = mkdtempSync(join18(tmpdir(), "cometchat-builder-export-"));
|
|
12364
12694
|
const tmpZip = join18(tmpRoot, `cometchat-builder-${platform}.zip`);
|
|
12365
12695
|
const tmpExtract = join18(tmpRoot, "extracted");
|
|
@@ -12381,9 +12711,7 @@ async function builderExport(args) {
|
|
|
12381
12711
|
}
|
|
12382
12712
|
const settingsContent = (() => {
|
|
12383
12713
|
if (layout.format === "ts-export") {
|
|
12384
|
-
|
|
12385
|
-
`;
|
|
12386
|
-
return layout.sentinel ? injectSentinel(body2, builderId) : body2;
|
|
12714
|
+
return `export const CometChatSettings: CometChatSettingsInterface = ${JSON.stringify(settings, null, 2)};`;
|
|
12387
12715
|
}
|
|
12388
12716
|
const nowSec = Math.floor(Date.now() / 1e3);
|
|
12389
12717
|
const envelope = {
|
|
@@ -12419,7 +12747,15 @@ async function builderExport(args) {
|
|
|
12419
12747
|
console.error(msg);
|
|
12420
12748
|
return 1;
|
|
12421
12749
|
}
|
|
12422
|
-
|
|
12750
|
+
if (layout.format === "ts-export") {
|
|
12751
|
+
const existing = readFileSync16(settingsPath, "utf8");
|
|
12752
|
+
const constRe = /export const CometChatSettings(?::\s*[A-Za-z_][\w]*)?\s*=\s*\{[\s\S]*?\n\};/;
|
|
12753
|
+
let patched = constRe.test(existing) ? existing.replace(constRe, settingsContent) : existing.replace(/\n*$/, "\n\n") + settingsContent + "\n";
|
|
12754
|
+
if (layout.sentinel) patched = injectSentinel(patched, builderId);
|
|
12755
|
+
writeFileSync10(settingsPath, patched);
|
|
12756
|
+
} else {
|
|
12757
|
+
writeFileSync10(settingsPath, settingsContent);
|
|
12758
|
+
}
|
|
12423
12759
|
primarySettingsFile = settingsPath;
|
|
12424
12760
|
} else {
|
|
12425
12761
|
if (existsSync4(outputAbs) && force) rmSync3(outputAbs, { recursive: true, force: true });
|
|
@@ -12470,7 +12806,12 @@ async function builderExport(args) {
|
|
|
12470
12806
|
platform,
|
|
12471
12807
|
output: outputAbs,
|
|
12472
12808
|
settings_file: primarySettingsFile,
|
|
12473
|
-
builder_name: builderName
|
|
12809
|
+
builder_name: builderName,
|
|
12810
|
+
// Toggled-ON builder features that ALSO need dashboard enablement
|
|
12811
|
+
// (extension / AI key). Empty array = nothing extra to enable. The
|
|
12812
|
+
// dispatcher runs `cometchat apply-feature <id>` for each `extension`
|
|
12813
|
+
// and prompts for an OpenAI key for each `ai-feature`.
|
|
12814
|
+
dashboardSetupNeeded: needSetup
|
|
12474
12815
|
},
|
|
12475
12816
|
null,
|
|
12476
12817
|
2
|
|
@@ -12480,6 +12821,15 @@ async function builderExport(args) {
|
|
|
12480
12821
|
console.log(`\u2713 Builder exported: ${builderId} \u2192 ${outputAbs}`);
|
|
12481
12822
|
console.log(` Settings: ${primarySettingsFile}${layout.sentinel ? " (patched + sentinel)" : " (patched)"}`);
|
|
12482
12823
|
console.log(` Platform: ${platform}`);
|
|
12824
|
+
if (needSetup.length) {
|
|
12825
|
+
console.log("");
|
|
12826
|
+
console.log(" \u26A0 These features are ON in your builder but also need DASHBOARD setup");
|
|
12827
|
+
console.log(" (the builder shows their UI; the server-side capability is separate):");
|
|
12828
|
+
for (const f of needSetup) {
|
|
12829
|
+
const how = f.type === "ai-feature" ? `cometchat apply-feature ${f.id} --openai-key sk-...` : `cometchat apply-feature ${f.id}`;
|
|
12830
|
+
console.log(` \u2022 ${f.id} \u2192 ${how}`);
|
|
12831
|
+
}
|
|
12832
|
+
}
|
|
12483
12833
|
console.log("");
|
|
12484
12834
|
console.log("Resync later by re-running this exact command \u2014 `--force` to replace.");
|
|
12485
12835
|
}
|