@salesforce/afv-skills 1.58.0 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-bot-upgrade/SKILL.md +3 -1
  17. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  18. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  19. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  20. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  21. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  26. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  27. package/skills/agentforce-generate/SKILL.md +21 -1
  28. package/skills/agentforce-generate/references/known-issues.md +51 -1
  29. package/skills/agentforce-generate/references/legacy-nga-regression-eval.md +547 -0
  30. package/skills/agentforce-generate/references/upgrade-legacy-agent-to-agentscript.md +577 -0
  31. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-analyze.py +404 -0
  32. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-run-trials.sh +58 -0
  33. package/skills/agentforce-persona-generate/SKILL.md +505 -0
  34. package/skills/agentforce-persona-generate/assets/checklist-template.md +100 -0
  35. package/skills/agentforce-persona-generate/assets/persona-encoding-template.md +252 -0
  36. package/skills/agentforce-persona-generate/assets/persona-template.md +229 -0
  37. package/skills/agentforce-persona-generate/assets/sample-dialog-template.md +50 -0
  38. package/skills/agentforce-persona-generate/assets/workshop-outline-template.md +97 -0
  39. package/skills/agentforce-persona-generate/examples/concierge-persona.md +189 -0
  40. package/skills/agentforce-persona-generate/examples/concierge-sample-dialog.md +70 -0
  41. package/skills/agentforce-persona-generate/examples/drover-persona.md +194 -0
  42. package/skills/agentforce-persona-generate/examples/drover-sample-dialog.md +62 -0
  43. package/skills/agentforce-persona-generate/examples/steady-hand-persona.md +188 -0
  44. package/skills/agentforce-persona-generate/examples/steady-hand-sample-dialog.md +68 -0
  45. package/skills/agentforce-persona-generate/examples/yt-persona.md +195 -0
  46. package/skills/agentforce-persona-generate/examples/yt-sample-dialog.md +66 -0
  47. package/skills/agentforce-persona-generate/references/attributions.md +36 -0
  48. package/skills/agentforce-persona-generate/references/interaction-design-notes.md +30 -0
  49. package/skills/agentforce-persona-generate/references/persona-encoding-guide.md +566 -0
  50. package/skills/agentforce-persona-generate/references/persona-framework.md +796 -0
  51. package/skills/agentforce-persona-generate/references/refinement-mappings.md +31 -0
  52. package/skills/agentforce-persona-generate/references/scoring-rubric.md +58 -0
  53. package/skills/agentforce-persona-generate/references/voice-catalog.json +2327 -0
  54. package/skills/agentforce-persona-generate/references/workshop-question-policy.md +90 -0
  55. package/skills/agentforce-persona-generate/references/workshop-scenario-seeds.md +119 -0
  56. package/skills/agentforce-persona-generate/scripts/parse_agent_yaml.py +223 -0
  57. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  58. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  59. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  60. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  61. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  62. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  63. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  64. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  65. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  66. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  67. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  68. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  69. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  70. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  71. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  72. package/skills/data360-schema-get/SKILL.md +3 -3
  73. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  74. package/skills/dx-app-analytics-query/SKILL.md +8 -8
  75. package/skills/dx-org-manage/SKILL.md +1 -0
  76. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  77. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  78. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  79. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  80. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  81. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  82. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  83. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  84. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  85. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  86. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  87. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  88. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  89. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  90. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  91. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +12 -12
  92. package/skills/experience-ui-bundle-app-coordinate/references/phase-completion-validation.md +3 -3
  93. package/skills/experience-ui-bundle-app-coordinate/references/phase-execution-pattern.md +9 -7
  94. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.mjs +132 -0
  95. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.mjs +62 -0
  96. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.mjs +71 -0
  97. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.mjs +95 -0
  98. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.mjs +78 -0
  99. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.mjs +21 -0
  100. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.mjs +28 -0
  101. package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +3 -1
  102. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.mjs +40 -0
  103. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +7 -5
  104. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.mjs +70 -0
  105. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.mjs +76 -0
  106. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.mjs +115 -0
  107. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +7 -3
  108. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +1 -1
  109. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +1 -1
  110. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.mjs +43 -0
  111. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.mjs +131 -0
  112. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.mjs +152 -0
  113. package/skills/experience-ui-bundle-mfa-configure/references/branding.md +10 -6
  114. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  115. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  116. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  117. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  118. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  119. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  120. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  121. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  122. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  123. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  124. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  125. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  126. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  127. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  128. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  129. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  130. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  131. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  132. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  133. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  134. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  135. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  136. package/skills/marketing-message-test/SKILL.md +194 -0
  137. package/skills/marketing-message-test/references/api-contract.md +333 -0
  138. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  139. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  140. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  141. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  142. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  143. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  144. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  145. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  146. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  147. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  148. package/skills/platform-models-api-configure/SKILL.md +5 -5
  149. package/skills/platform-soql-query/SKILL.md +1 -0
  150. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  151. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  152. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  153. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  154. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  155. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  156. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  157. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  158. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  159. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  160. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  161. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  162. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  163. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  164. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  165. package/skills/tableau-next-asset-create/README.md +65 -0
  166. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  167. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  168. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  169. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  170. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  171. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  172. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  173. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  174. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  175. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  176. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  177. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  178. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  179. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  180. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  181. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  182. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  183. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  184. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  185. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  186. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  187. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  188. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  189. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  190. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  191. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  192. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  193. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  194. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  195. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  196. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  197. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  198. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  199. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  200. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  201. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  202. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  203. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  204. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  205. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  206. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  207. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  208. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  209. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  210. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  211. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  212. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  213. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  214. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  215. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  216. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  217. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  218. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  219. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  220. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  221. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  222. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  223. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  224. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  225. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  226. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  227. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  228. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.sh +0 -42
  229. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh +0 -26
  230. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.sh +0 -35
  231. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.sh +0 -47
  232. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.sh +0 -33
  233. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.sh +0 -20
  234. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh +0 -15
  235. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.sh +0 -30
  236. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.sh +0 -37
  237. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.sh +0 -66
  238. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.sh +0 -58
  239. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh +0 -30
  240. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +0 -73
  241. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +0 -118
  242. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // Resolve the existing path of a UIBundle's meta XML file.
3
+ //
4
+ // UIBundle metadata may live at either the nested bundle-style path
5
+ // (uiBundles/{appName}/{appName}.uibundle-meta.xml) or, in older/hand-authored
6
+ // projects, the flat path (uiBundles/{appName}.uibundle-meta.xml). This script
7
+ // performs the deterministic file-existence check and prints whichever path
8
+ // already exists, preferring the nested layout.
9
+ //
10
+ // Always invoke via `node` (never as a bare executable) so this works on
11
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
12
+ //
13
+ // Usage: node scripts/resolve-uibundle-path.mjs <appName>
14
+ //
15
+ // Exit 0: existing meta XML found — its path is printed to stdout.
16
+ // Exit 1: appName argument missing, or no .uibundle-meta.xml found for it.
17
+
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+
21
+ const APP = process.argv[2] ?? "";
22
+
23
+ if (!APP) {
24
+ console.error("ERROR: appName argument is required");
25
+ process.exit(1);
26
+ }
27
+
28
+ const NESTED = path.join("uiBundles", APP, `${APP}.uibundle-meta.xml`);
29
+ const FLAT = path.join("uiBundles", `${APP}.uibundle-meta.xml`);
30
+
31
+ if (fs.existsSync(NESTED) && fs.statSync(NESTED).isFile()) {
32
+ console.log(NESTED);
33
+ process.exit(0);
34
+ } else if (fs.existsSync(FLAT) && fs.statSync(FLAT).isFile()) {
35
+ console.log(FLAT);
36
+ process.exit(0);
37
+ } else {
38
+ console.error(`ERROR: no .uibundle-meta.xml found for ${APP}`);
39
+ process.exit(1);
40
+ }
@@ -10,6 +10,8 @@ metadata:
10
10
  - "experience-ui-bundle-metadata-generate"
11
11
  - "experience-ui-bundle-salesforce-data-access"
12
12
  cliTools:
13
+ - tool: ["node"]
14
+ semver: ">=18.0.0"
13
15
  - tool: ["npm"]
14
16
  semver: ">=8.0.0"
15
17
  - tool: ["npx"]
@@ -22,7 +24,7 @@ metadata:
22
24
 
23
25
  ## Resolve the Bundle Directory
24
26
 
25
- **MUST** run `scripts/resolve-ui-bundle.sh [project-root]` before applying any rule below or writing any file — an ad-hoc `find`/`ls` is not a substitute; it does not enforce the exit-code gate below. It reads `sfdx-project.json`'s `packageDirectories[0].path` (does not assume `force-app` — the source path is configurable) and looks under `<sourceDir>/main/default/uiBundles/`:
27
+ **MUST** run `node scripts/resolve-ui-bundle.mjs [project-root]` before applying any rule below or writing any file — an ad-hoc `find`/`ls` is not a substitute; it does not enforce the exit-code gate below. Always invoke via `node` (never as a bare executable) so this works on Windows as well as macOS/Linux. It reads `sfdx-project.json`'s `packageDirectories[0].path` (does not assume `force-app` — the source path is configurable) and looks under `<sourceDir>/main/default/uiBundles/`:
26
28
 
27
29
  - **Exit 0**, bundle path printed to stdout: exactly one bundle directory found — that is the bundle directory. Use that exact directory name; never substitute a different name (e.g. a generic "AcmePortal" example from a prompt template) for the one actually printed.
28
30
  - **Exit 2**, candidates printed to stderr: multiple `uiBundles/*` subdirectories exist — do not guess, and do not write to any of them, or to a bundle name not in the printed list. Ask the user which app/bundle they mean before editing or running any command.
@@ -32,10 +34,10 @@ Run all `npm`/lint/build/dev commands from inside the resolved bundle directory,
32
34
 
33
35
  ## Preconditions
34
36
 
35
- Before applying any rule below, confirm this is an existing, scaffolded UI bundle: **MUST** run `scripts/check-preconditions.sh <bundle-dir>` with the directory `resolve-ui-bundle.sh` printed.
37
+ Before applying any rule below, confirm this is an existing, scaffolded UI bundle: **MUST** run `node scripts/check-preconditions.mjs <bundle-dir>` with the directory `resolve-ui-bundle.mjs` printed.
36
38
 
37
39
  - **Single-bundle (exit 0) case**: run it once, on that bundle.
38
- - **Multi-bundle (exit 2) case**: do not run it yet — first ask the user which app/bundle they mean, per the Resolve step above. Once the user names the bundle, run `check-preconditions.sh` on that one bundle only. Never run it against multiple candidates speculatively before the user has chosen — that means touching/inspecting bundles the user didn't ask about.
40
+ - **Multi-bundle (exit 2) case**: do not run it yet — first ask the user which app/bundle they mean, per the Resolve step above. Once the user names the bundle, run `check-preconditions.mjs` on that one bundle only. Never run it against multiple candidates speculatively before the user has chosen — that means touching/inspecting bundles the user didn't ask about.
39
41
  - **Exit 0**: the bundle has `src/appLayout.tsx`, `src/routes.tsx`, and `src/components/ui/` — proceed.
40
42
  - **Exit 1**, missing pieces listed: this is a fresh SFDX project, a non-UI-bundle React project, or a partially-scaffolded bundle — **stop**. Do not fall back to generic React knowledge (e.g. `react-router-dom`, a hardcoded basename, or raw HTML), and do not hand-write `appLayout.tsx`/`routes.tsx`/a page/a component to "fill in" the missing scaffold, even for a casual, vague, or urgent-sounding request ("just change the header", "make the background blue"). Tell the user the bundle isn't scaffolded yet and direct them to `experience-ui-bundle-app-coordinate` (or `experience-ui-bundle-metadata-generate`) to scaffold it first. If, after the user names their intended bundle, that one turns out to be unscaffolded, stop and redirect for it — do not silently switch to scaffolding a different candidate instead.
41
43
 
@@ -80,7 +82,7 @@ When making any change that affects navigation, header, footer, sidebar, theme,
80
82
 
81
83
  `index.html` lives at the bundle root (not under `src/`), but it is still in scope for this skill whenever branding or the app name changes — the leftover `<title>React App</title>` / `Vite + React` boilerplate is a common ship-blocker that `npm run lint`/`npm run build` never catches.
82
84
 
83
- Before finishing, confirm: Did I update `appLayout.tsx` with real nav items and branding? Then run `scripts/verify-rules.sh` to check for residual boilerplate (see Verification below).
85
+ Before finishing, confirm: Did I update `appLayout.tsx` with real nav items and branding? Then run `node scripts/verify-rules.mjs` to check for residual boilerplate (see Verification below).
84
86
 
85
87
  | What | Where |
86
88
  |------|-------|
@@ -191,7 +193,7 @@ Before completing, run all of the following from the resolved UI bundle director
191
193
  2. `npm run build` — must succeed.
192
194
  3. `npm run dev` (or the project's dev-server script) — confirm the app starts cleanly so the change is verified at runtime, not just at build time.
193
195
 
194
- **`lint`/`build` alone do not catch the highest-risk rules in this skill** — a wrong `react-router-dom` import, a hardcoded basename, an inline `style={{}}`, or a stray `lightning/*` import can all lint and build clean while breaking at runtime. After any change that touches routing, layout, styling, or module imports, run `scripts/verify-rules.sh <files-or-dirs-you-edited>`:
196
+ **`lint`/`build` alone do not catch the highest-risk rules in this skill** — a wrong `react-router-dom` import, a hardcoded basename, an inline `style={{}}`, or a stray `lightning/*` import can all lint and build clean while breaking at runtime. After any change that touches routing, layout, styling, or module imports, run `node scripts/verify-rules.mjs <files-or-dirs-you-edited>`:
195
197
 
196
198
  - **Exit 0**: no violations found.
197
199
  - **Exit 1**, violations listed by rule and file: fix every one before considering the task complete, even if lint and build passed.
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ // Verify a resolved UI bundle directory is actually scaffolded before any
3
+ // rule in this skill applies.
4
+ // Usage: node check-preconditions.mjs <bundle-dir>
5
+ // <bundle-dir> is the path printed by resolve-ui-bundle.mjs.
6
+ //
7
+ // Always invoke via `node` (never as a bare executable) so this works on
8
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash. Quote
9
+ // <bundle-dir> so Windows paths and spaces survive the shell.
10
+ //
11
+ // Exit 0 if the bundle is scaffolded (src/appLayout.tsx, src/routes.tsx, and
12
+ // src/components/ui/ all present).
13
+ // Exit 1 with the missing pieces listed if the bundle is a fresh SFDX
14
+ // project, a non-UI-bundle React project, or only partially scaffolded —
15
+ // the caller must stop and redirect the user to
16
+ // experience-ui-bundle-app-coordinate (or experience-ui-bundle-metadata-generate)
17
+ // instead of falling back to generic React knowledge.
18
+
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+
22
+ const BUNDLE_DIR = process.argv[2] ?? "";
23
+
24
+ function fail(message) {
25
+ console.error(message);
26
+ process.exit(1);
27
+ }
28
+
29
+ if (!BUNDLE_DIR) {
30
+ fail("ERROR: check-preconditions.mjs requires a valid bundle directory argument");
31
+ }
32
+
33
+ let isDir = false;
34
+ try {
35
+ isDir = fs.statSync(BUNDLE_DIR).isDirectory();
36
+ } catch {
37
+ isDir = false;
38
+ }
39
+
40
+ if (!isDir) {
41
+ fail("ERROR: check-preconditions.mjs requires a valid bundle directory argument");
42
+ }
43
+
44
+ const missing = [];
45
+ if (!fs.existsSync(path.join(BUNDLE_DIR, "src", "appLayout.tsx"))) {
46
+ missing.push("src/appLayout.tsx");
47
+ }
48
+ if (!fs.existsSync(path.join(BUNDLE_DIR, "src", "routes.tsx"))) {
49
+ missing.push("src/routes.tsx");
50
+ }
51
+ const uiDir = path.join(BUNDLE_DIR, "src", "components", "ui");
52
+ if (!fs.existsSync(uiDir) || !fs.statSync(uiDir).isDirectory()) {
53
+ missing.push("src/components/ui/");
54
+ }
55
+
56
+ if (missing.length > 0) {
57
+ console.error(`ERROR: ${BUNDLE_DIR} is not a scaffolded UI bundle. Missing:`);
58
+ for (const item of missing) {
59
+ console.error(` - ${item}`);
60
+ }
61
+ console.error(
62
+ "Stop here. Tell the user the bundle isn't scaffolded yet and direct them to experience-ui-bundle-app-coordinate (or experience-ui-bundle-metadata-generate) to scaffold it first.",
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log(
68
+ `SUCCESS: ${BUNDLE_DIR} is scaffolded (src/appLayout.tsx, src/routes.tsx, src/components/ui/ present)`,
69
+ );
70
+ process.exit(0);
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // Resolve the single UI bundle directory for this project.
3
+ // Usage: node resolve-ui-bundle.mjs [project-root] (default: current directory)
4
+ //
5
+ // Reads sfdx-project.json's packageDirectories[0].path, looks under
6
+ // <that-path>/main/default/uiBundles/, and prints the resolved bundle
7
+ // directory to stdout on success.
8
+ //
9
+ // Always invoke via `node` (never as a bare executable) so this works on
10
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash. Quote the
11
+ // project-root argument so paths with spaces or Windows backslashes stay
12
+ // a single argv.
13
+ //
14
+ // Exit 0: exactly one bundle directory found — printed to stdout.
15
+ // Exit 1: sfdx-project.json missing/invalid, or no uiBundles/* subdirectory
16
+ // found at all (not a scaffolded SFDX project yet).
17
+ // Exit 2: multiple uiBundles/* subdirectories found — caller MUST ask the
18
+ // user which one they mean rather than guessing; candidate names are
19
+ // printed to stderr.
20
+
21
+ import fs from "node:fs";
22
+ import path from "node:path";
23
+
24
+ const PROJECT_ROOT = path.resolve(process.argv[2] ?? ".");
25
+ const SFDX_PROJECT = path.join(PROJECT_ROOT, "sfdx-project.json");
26
+
27
+ function fail(code, message) {
28
+ console.error(message);
29
+ process.exit(code);
30
+ }
31
+
32
+ if (!fs.existsSync(SFDX_PROJECT) || !fs.statSync(SFDX_PROJECT).isFile()) {
33
+ fail(1, `ERROR: sfdx-project.json not found at ${SFDX_PROJECT}`);
34
+ }
35
+
36
+ let sourcePath = "";
37
+ try {
38
+ const data = JSON.parse(fs.readFileSync(SFDX_PROJECT, "utf8"));
39
+ const dirs = data?.packageDirectories ?? [];
40
+ sourcePath = dirs[0]?.path ?? "";
41
+ } catch {
42
+ sourcePath = "";
43
+ }
44
+
45
+ if (!sourcePath) {
46
+ fail(1, "ERROR: sfdx-project.json is invalid or has no packageDirectories[0].path");
47
+ }
48
+
49
+ const BUNDLES_DIR = path.join(PROJECT_ROOT, sourcePath, "main", "default", "uiBundles");
50
+
51
+ if (!fs.existsSync(BUNDLES_DIR) || !fs.statSync(BUNDLES_DIR).isDirectory()) {
52
+ fail(1, `ERROR: No uiBundles/ directory found at ${BUNDLES_DIR}`);
53
+ }
54
+
55
+ const bundleDirs = fs
56
+ .readdirSync(BUNDLES_DIR, { withFileTypes: true })
57
+ .filter((entry) => entry.isDirectory())
58
+ .map((entry) => path.join(BUNDLES_DIR, entry.name))
59
+ .sort();
60
+
61
+ if (bundleDirs.length === 0) {
62
+ fail(1, `ERROR: ${BUNDLES_DIR} exists but contains no bundle subdirectories`);
63
+ }
64
+
65
+ if (bundleDirs.length > 1) {
66
+ console.error(
67
+ "AMBIGUOUS: Multiple UI bundle directories found — ask the user which one they mean before editing or running any command:",
68
+ );
69
+ for (const dir of bundleDirs) {
70
+ console.error(` - ${dir}`);
71
+ }
72
+ process.exit(2);
73
+ }
74
+
75
+ console.log(bundleDirs[0]);
76
+ process.exit(0);
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ // Verification of the hard, checkable rules in this skill that
3
+ // `npm run lint` / `npm run build` do not catch on their own — a wrong
4
+ // react-router-dom import, a hardcoded basename, an inline style prop, a
5
+ // stray lightning/*-or-@wire import, or leftover template boilerplate can
6
+ // all lint and build clean while breaking at runtime or shipping unfinished
7
+ // branding.
8
+ //
9
+ // Usage: node verify-rules.mjs <file-or-dir> [<file-or-dir> ...]
10
+ // Pass the specific files you edited, or the bundle's src/ directory (and
11
+ // its index.html) to check everything.
12
+ //
13
+ // Always invoke via `node` (never as a bare executable, and never via a
14
+ // shell-specific tool like grep/sed) so this works on Windows
15
+ // cmd/PowerShell as well as macOS/Linux/Git Bash. Quote path arguments so
16
+ // Windows paths and spaces survive the shell.
17
+ //
18
+ // Exit 0 if no violations are found.
19
+ // Exit 1 with every violation listed (rule + matching file paths) if any
20
+ // are found — fix them before considering the task complete, even if lint
21
+ // and build passed.
22
+
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+
26
+ const INCLUDE_EXT = new Set([".ts", ".tsx", ".js", ".jsx", ".html"]);
27
+ const EXCLUDE_DIRS = new Set(["node_modules", "dist", "build"]);
28
+
29
+ const RULES = [
30
+ {
31
+ label: 'import from "react-router-dom" (must be "react-router")',
32
+ pattern: /from\s+['"]react-router-dom['"]/,
33
+ },
34
+ {
35
+ label: "a hardcoded basename literal (must derive from <base href> at runtime)",
36
+ pattern: /basename:\s*['"][/A-Za-z0-9_-]+['"]/,
37
+ },
38
+ {
39
+ label: "an inline style={{...}} prop (Tailwind utility classes only)",
40
+ pattern: /style=\{\{/,
41
+ },
42
+ {
43
+ label: "a forbidden lightning/* import or @wire usage (LWC-only)",
44
+ pattern: /from\s+['"]lightning\/|@wire/,
45
+ },
46
+ {
47
+ label: "leftover boilerplate branding (<title>React App</title> / Vite + React)",
48
+ pattern: /<title>[^<]*React App[^<]*<\/title>|Vite\s*\+\s*React/,
49
+ },
50
+ ];
51
+
52
+ const targets = process.argv.slice(2);
53
+
54
+ if (targets.length === 0) {
55
+ console.error("ERROR: verify-rules.mjs requires at least one file or directory argument");
56
+ process.exit(1);
57
+ }
58
+
59
+ for (const p of targets) {
60
+ if (!fs.existsSync(p)) {
61
+ console.error(`ERROR: path does not exist: ${p}`);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ function collectFiles(target, out) {
67
+ const stat = fs.statSync(target);
68
+ if (stat.isDirectory()) {
69
+ const base = path.basename(target);
70
+ if (EXCLUDE_DIRS.has(base)) return;
71
+ for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
72
+ collectFiles(path.join(target, entry.name), out);
73
+ }
74
+ return;
75
+ }
76
+ if (stat.isFile() && INCLUDE_EXT.has(path.extname(target))) {
77
+ out.push(target);
78
+ }
79
+ }
80
+
81
+ const files = [];
82
+ for (const t of targets) {
83
+ collectFiles(t, files);
84
+ }
85
+
86
+ let found = false;
87
+
88
+ for (const rule of RULES) {
89
+ const hits = [];
90
+ for (const file of files) {
91
+ let content;
92
+ try {
93
+ content = fs.readFileSync(file, "utf8");
94
+ } catch {
95
+ continue;
96
+ }
97
+ if (rule.pattern.test(content)) {
98
+ hits.push(file);
99
+ }
100
+ }
101
+ if (hits.length > 0) {
102
+ found = true;
103
+ console.error(`ERROR: ${rule.label} found in:`);
104
+ for (const hit of hits) {
105
+ console.error(` - ${hit}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ if (found) {
111
+ process.exit(1);
112
+ }
113
+
114
+ console.log(`SUCCESS: No hard-rule violations found in: ${targets.join(" ")}`);
115
+ process.exit(0);
@@ -13,6 +13,8 @@ metadata:
13
13
  cliTools:
14
14
  - tool: ["jq"]
15
15
  semver: ">=1.6.0"
16
+ - tool: ["node"]
17
+ semver: ">=18.0.0"
16
18
  - tool: ["sf"]
17
19
  semver: ">=2.0.0"
18
20
  ---
@@ -32,9 +34,11 @@ The frameworks this skill supports are exactly the reference files under `<skill
32
34
  **Detect the framework deterministically — run the script:**
33
35
 
34
36
  ```sh
35
- bash <skill_dir>/scripts/detect-framework.sh [<ROOT>]
37
+ node <skill_dir>/scripts/detect-framework.mjs [<ROOT>]
36
38
  ```
37
39
 
40
+ Always invoke via `node` (never as a bare executable) so this works on Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
41
+
38
42
  `ROOT` defaults to the current directory; pass the bundle or project root when editing/configuring an existing bundle. The script prints exactly one token and sets a matching exit code — branch on it:
39
43
 
40
44
  - `react` / `angular` (exit 0) — open `<skill_dir>/references/<framework>-metadata-generate.md` and use it.
@@ -49,8 +53,8 @@ The reference file gives you the exact `--template` flag, the entry-file layout,
49
53
  - Pass `--output-dir` to use a different location for template generation. If you do, pass that same path to the verification script in step 1 below.
50
54
 
51
55
  After generation:
52
- 1. **Verify the scaffold is complete** — run `bash <skill_dir>/scripts/verify-bundle-location.sh <BundleName> [<CustomOutputDir>] [<framework>]` from the project root and follow any error output. This checks both the bundle's location AND that `package.json`, `src/`, and an entry `index.html` exist — if any are missing, the scaffold step was skipped; go back and run `sf template generate ui-bundle` before continuing. Pass `<CustomOutputDir>` only if you used `--output-dir` during scaffolding (pass `""` to skip it while still supplying a framework); pass `<framework>` (`react` or `angular`) so the remediation hint uses the right template.
53
- 2. **Verify API version** — run `bash <skill_dir>/scripts/check-api-version.sh` from the project root to ensure `sourceApiVersion` in `sfdx-project.json` is 67.0 or higher. The script will automatically update it if needed.
56
+ 1. **Verify the scaffold is complete** — run `node <skill_dir>/scripts/verify-bundle-location.mjs <BundleName> [<CustomOutputDir>] [<framework>]` from the project root and follow any error output. This checks both the bundle's location AND that `package.json`, `src/`, and an entry `index.html` exist — if any are missing, the scaffold step was skipped; go back and run `sf template generate ui-bundle` before continuing. Pass `<CustomOutputDir>` only if you used `--output-dir` during scaffolding (pass `""` to skip it while still supplying a framework); pass `<framework>` (`react` or `angular`) so the remediation hint uses the right template.
57
+ 2. **Verify API version** — run `node <skill_dir>/scripts/check-api-version.mjs` from the project root to ensure `sourceApiVersion` in `sfdx-project.json` is 67.0 or higher. The script will automatically update it if needed.
54
58
  3. Replace all default boilerplate — the framework reference file lists the exact stock `<title>` and placeholder strings to replace
55
59
  4. Populate the home page with real content (landing section, banners, hero, navigation)
56
60
  5. Update navigation and placeholders (see the `experience-ui-bundle-frontend-generate` skill)
@@ -12,7 +12,7 @@ Always pass `--template angularbasic` to scaffold an Angular bundle:
12
12
  sf template generate ui-bundle -n CoffeeBoutique --template angularbasic
13
13
  ```
14
14
 
15
- Pass `--output-dir` to generate at a different location. If you do, pass that same path to `verify-bundle-location.sh` in the verification step.
15
+ Pass `--output-dir` to generate at a different location. If you do, pass that same path to `verify-bundle-location.mjs` in the verification step.
16
16
 
17
17
  ## What the scaffold produces
18
18
 
@@ -12,7 +12,7 @@ Always pass `--template reactbasic` to scaffold a React/Vite bundle:
12
12
  sf template generate ui-bundle -n CoffeeBoutique --template reactbasic
13
13
  ```
14
14
 
15
- Pass `--output-dir` to generate at a different location. If you do, pass that same path to `verify-bundle-location.sh` in the verification step.
15
+ Pass `--output-dir` to generate at a different location. If you do, pass that same path to `verify-bundle-location.mjs` in the verification step.
16
16
 
17
17
  ## What the scaffold produces
18
18
 
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ // Checks that sourceApiVersion in sfdx-project.json is 67.0 or higher.
3
+ // If lower, updates it to "67.0".
4
+ //
5
+ // Usage: node check-api-version.mjs
6
+ //
7
+ // Always invoke via `node` (never as a bare executable) so this works on
8
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash. Parses and rewrites
9
+ // sfdx-project.json with native JSON (no jq dependency).
10
+
11
+ import fs from "node:fs";
12
+
13
+ const PROJECT_FILE = "sfdx-project.json";
14
+
15
+ if (!fs.existsSync(PROJECT_FILE) || !fs.statSync(PROJECT_FILE).isFile()) {
16
+ console.log(`ERROR: ${PROJECT_FILE} not found in current directory`);
17
+ process.exit(1);
18
+ }
19
+
20
+ let data;
21
+ try {
22
+ data = JSON.parse(fs.readFileSync(PROJECT_FILE, "utf8"));
23
+ } catch {
24
+ console.log(`ERROR: ${PROJECT_FILE} is not valid JSON`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const version = data.sourceApiVersion || "0";
29
+ const major = Number.parseInt(String(version).split(".")[0], 10);
30
+ const isBelow = Number.isNaN(major) || major < 67;
31
+
32
+ if (isBelow) {
33
+ console.log(`WARNING: sourceApiVersion is ${version} (< 67.0)`);
34
+ console.log(`Updating ${PROJECT_FILE} to set sourceApiVersion to "67.0"`);
35
+
36
+ data.sourceApiVersion = "67.0";
37
+ fs.writeFileSync(PROJECT_FILE, `${JSON.stringify(data, null, 2)}\n`);
38
+
39
+ console.log("OK: Updated sourceApiVersion to 67.0");
40
+ process.exit(0);
41
+ }
42
+
43
+ console.log(`OK: sourceApiVersion is ${version} (>= 67.0)`);
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // detect-framework.mjs — deterministic UI Bundle framework detector.
4
+ //
5
+ // Usage: node detect-framework.mjs [ROOT]
6
+ // ROOT app or uiBundle root to inspect (default: current directory)
7
+ //
8
+ // Always invoke via `node` (never as a bare executable) so this works on
9
+ // Windows cmd/PowerShell as well as macOS/Linux/Git Bash.
10
+ //
11
+ // Prints exactly one of the following tokens to stdout, nothing else, and sets
12
+ // a matching exit code so callers can branch without parsing:
13
+ // react (exit 0) — React signals only
14
+ // angular (exit 0) — Angular signals only
15
+ // ambiguous (exit 2) — both React and Angular signals present
16
+ // unknown (exit 3) — neither framework detected
17
+ //
18
+ // Step 0 of the skill acts on the result: a single framework (exit 0) proceeds;
19
+ // `ambiguous` asks the user to disambiguate; `unknown` TERMINATES the workflow
20
+ // (no supported framework found — do not guess).
21
+
22
+ import fs from "node:fs";
23
+ import path from "node:path";
24
+
25
+ const ROOT = process.argv[2] ?? ".";
26
+
27
+ let angular = false;
28
+ let react = false;
29
+
30
+ // Resolve ROOT to an absolute path when possible (for the angular.json walk-up).
31
+ let absRoot = "";
32
+ try {
33
+ if (fs.statSync(ROOT).isDirectory()) {
34
+ absRoot = fs.realpathSync(ROOT);
35
+ }
36
+ } catch {
37
+ absRoot = "";
38
+ }
39
+
40
+ // 1) angular.json at or above ROOT → Angular workspace.
41
+ if (absRoot) {
42
+ let dir = absRoot;
43
+ for (;;) {
44
+ if (fs.existsSync(path.join(dir, "angular.json"))) {
45
+ angular = true;
46
+ }
47
+ const parent = path.dirname(dir);
48
+ if (parent === dir) break;
49
+ dir = parent;
50
+ }
51
+ }
52
+
53
+ // Recursively collect file paths under `root`, skipping node_modules directories.
54
+ function walk(root) {
55
+ const results = [];
56
+ const stack = [root];
57
+ while (stack.length > 0) {
58
+ const current = stack.pop();
59
+ let entries;
60
+ try {
61
+ entries = fs.readdirSync(current, { withFileTypes: true });
62
+ } catch {
63
+ continue;
64
+ }
65
+ for (const entry of entries) {
66
+ if (entry.isDirectory()) {
67
+ if (entry.name === "node_modules") continue;
68
+ stack.push(path.join(current, entry.name));
69
+ } else if (entry.isFile()) {
70
+ results.push(path.join(current, entry.name));
71
+ }
72
+ }
73
+ }
74
+ return results;
75
+ }
76
+
77
+ const files = walk(ROOT);
78
+
79
+ // 2) package.json dependencies (excluding node_modules).
80
+ for (const file of files) {
81
+ if (path.basename(file) !== "package.json") continue;
82
+ let content = "";
83
+ try {
84
+ content = fs.readFileSync(file, "utf8");
85
+ } catch {
86
+ continue;
87
+ }
88
+ if (content.includes('"@angular/core"')) angular = true;
89
+ if (/"react"\s*:/.test(content)) react = true;
90
+ }
91
+
92
+ // 3) Source-file signatures (excluding node_modules).
93
+ function hasFile(matches) {
94
+ return files.some(matches);
95
+ }
96
+
97
+ // Angular: component files, routing module, or @Component-decorated classes.
98
+ if (hasFile((f) => f.endsWith(".component.ts"))) angular = true;
99
+ if (hasFile((f) => path.basename(f) === "app.routes.ts")) angular = true;
100
+
101
+ for (const file of files) {
102
+ if (!file.endsWith(".ts")) continue;
103
+ let content = "";
104
+ try {
105
+ content = fs.readFileSync(file, "utf8");
106
+ } catch {
107
+ continue;
108
+ }
109
+ if (content.includes("@Component")) {
110
+ angular = true;
111
+ break;
112
+ }
113
+ }
114
+
115
+ // React: JSX/TSX source files.
116
+ if (hasFile((f) => f.endsWith(".tsx"))) react = true;
117
+ if (hasFile((f) => f.endsWith(".jsx"))) react = true;
118
+
119
+ if (angular && react) {
120
+ console.log("ambiguous");
121
+ process.exit(2);
122
+ } else if (angular) {
123
+ console.log("angular");
124
+ process.exit(0);
125
+ } else if (react) {
126
+ console.log("react");
127
+ process.exit(0);
128
+ } else {
129
+ console.log("unknown");
130
+ process.exit(3);
131
+ }