@salesforce/afv-skills 1.33.0 → 1.35.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.
- package/package.json +1 -1
- package/skills/agentforce-architecture-analyze/SKILL.md +8 -0
- package/skills/agentforce-bot-upgrade/SKILL.md +0 -2
- package/skills/agentforce-d360-analyze/SKILL.md +8 -0
- package/skills/agentforce-observe/SKILL.md +3 -0
- package/skills/agentforce-test/SKILL.md +3 -0
- package/skills/automation-flow-generate/SKILL.md +1 -0
- package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +239 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +21 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +90 -0
- package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_excerpt.md +31 -0
- package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_to_config.json +50 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +76 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/sop_parsing_patterns.md +157 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/source_format_handling.md +230 -0
- package/skills/commerce-b2b-open-code-components-integrate/SKILL.md +8 -0
- package/skills/commerce-b2b-open-code-components-replace/SKILL.md +2 -0
- package/skills/commerce-b2b-store-create/SKILL.md +4 -1
- package/skills/data360-activate/SKILL.md +12 -0
- package/skills/data360-code-extension-generate/SKILL.md +6 -6
- package/skills/data360-connect/SKILL.md +11 -0
- package/skills/data360-harmonize/SKILL.md +11 -0
- package/skills/data360-orchestrate/SKILL.md +30 -0
- package/skills/data360-prepare/SKILL.md +14 -0
- package/skills/data360-query/SKILL.md +10 -0
- package/skills/data360-schema-get/SKILL.md +7 -0
- package/skills/data360-segment/SKILL.md +11 -0
- package/skills/design-systems-slds-validate/SKILL.md +8 -0
- package/skills/design-systems-slds2-migrate/SKILL.md +3 -0
- package/skills/dx-apexguru-scan/SKILL.md +403 -0
- package/skills/dx-apexguru-scan/examples/README.md +54 -0
- package/skills/dx-apexguru-scan/examples/sample-decoded-summary.json +176 -0
- package/skills/dx-apexguru-scan/examples/sample-full-no-runtime-response.json +26 -0
- package/skills/dx-apexguru-scan/examples/sample-succeeded-response.json +15 -0
- package/skills/dx-apexguru-scan/references/api-reference.md +81 -0
- package/skills/dx-apexguru-scan/references/authentication.md +134 -0
- package/skills/dx-apexguru-scan/references/error-handling.md +56 -0
- package/skills/dx-apexguru-scan/references/violation-catalog.md +28 -0
- package/skills/dx-apexguru-scan/scripts/build-zip.sh +87 -0
- package/skills/dx-apexguru-scan/scripts/decode-report.js +389 -0
- package/skills/dx-apexguru-scan/scripts/resolve-token.sh +151 -0
- package/skills/dx-apexguru-scan/scripts/run-scan.sh +153 -0
- package/skills/dx-apexguru-scan/scripts/scan.sh +96 -0
- package/skills/dx-apexguru-scan/scripts/validate-token.js +121 -0
- package/skills/dx-app-analytics-query/SKILL.md +5 -0
- package/skills/dx-code-analyzer-configure/SKILL.md +5 -5
- package/skills/dx-code-analyzer-custom-rule-create/SKILL.md +3 -1
- package/skills/dx-code-analyzer-run/SKILL.md +10 -2
- package/skills/dx-devops-pipeline-manage/SKILL.md +263 -0
- package/skills/dx-devops-pipeline-manage/examples/common-workflows.md +177 -0
- package/skills/dx-devops-pipeline-manage/references/cli-commands.md +298 -0
- package/skills/dx-devops-pipeline-manage/references/parsing-patterns.md +134 -0
- package/skills/dx-devops-pipeline-manage/scripts/check-activation-ready.sh +34 -0
- package/skills/dx-devops-pipeline-manage/scripts/validate-org-type.sh +17 -0
- package/skills/dx-devops-pipeline-manage/scripts/verify-operation.sh +82 -0
- package/skills/dx-devops-promote/SKILL.md +214 -0
- package/skills/dx-devops-promote/examples/promotion-workflows.md +212 -0
- package/skills/dx-devops-promote/references/cli-commands.md +303 -0
- package/skills/dx-devops-test-failures-analyze/SKILL.md +8 -0
- package/skills/dx-devops-test-pipeline-configure/SKILL.md +7 -0
- package/skills/dx-devops-test-suite-assignments-configure/SKILL.md +9 -0
- package/skills/dx-devops-test-suite-run/SKILL.md +8 -0
- package/skills/dx-org-switch/SKILL.md +3 -0
- package/skills/experience-lwc-base-components-integrate/SKILL.md +176 -0
- package/skills/experience-lwc-base-components-integrate/references/lbc-expert-guidance.md +127 -0
- package/skills/experience-lwc-base-components-integrate/references/lightning-component-index.md +179 -0
- package/skills/experience-lwc-base-components-integrate/references/lightning-components.md +5429 -0
- package/skills/experience-lwc-base-components-integrate/scripts/extract-component-docs.sh +61 -0
- package/skills/experience-lwc-generate/SKILL.md +18 -0
- package/skills/experience-lwc-rtl-validate/SKILL.md +149 -0
- package/skills/experience-lwc-rtl-validate/references/rtl-expert.md +892 -0
- package/skills/experience-lwc-rtl-validate/scripts/scan-rtl-css.sh +206 -0
- package/skills/experience-lwc-typescript-migrate/SKILL.md +207 -0
- package/skills/experience-lwc-typescript-migrate/assets/dts-template.ts +15 -0
- package/skills/experience-lwc-typescript-migrate/assets/type-patterns.ts +44 -0
- package/skills/experience-lwc-typescript-migrate/scripts/find-consumers.sh +128 -0
- package/skills/experience-lwr-site-generate/SKILL.md +1 -1
- package/skills/experience-ui-bundle-agentforce-client-generate/SKILL.md +5 -2
- package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +1 -1
- package/skills/experience-ui-bundle-file-upload-generate/SKILL.md +5 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +323 -0
- package/skills/experience-ui-bundle-localize/references/gotchas.md +249 -0
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +169 -0
- package/skills/experience-ui-bundle-localize/references/interpolation.md +311 -0
- package/skills/experience-ui-bundle-localize/references/label-xml.md +282 -0
- package/skills/experience-ui-bundle-localize/references/verifying.md +219 -0
- package/skills/experience-ui-bundle-localize/scripts/check-i18n-wired.sh +195 -0
- package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +100 -0
- package/skills/experience-ui-bundle-localize/scripts/check-org-api-version.sh +40 -0
- package/skills/experience-ui-bundle-localize/scripts/detect-bundle-type.sh +57 -0
- package/skills/experience-ui-bundle-metadata-generate/SKILL.md +1 -1
- package/skills/experience-ui-bundle-project-generate/SKILL.md +4 -4
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +11 -0
- package/skills/external-diagram-mermaid-generate/SKILL.md +21 -0
- package/skills/integration-connectivity-generate/SKILL.md +17 -0
- package/skills/integration-eventing-cdc-configure/SKILL.md +9 -0
- package/skills/integration-eventing-subscription-configure/SKILL.md +7 -0
- package/skills/mobile-apps-create/SKILL.md +5 -0
- package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +3 -0
- package/skills/mobile-platform-offline-validate/SKILL.md +9 -0
- package/skills/omnistudio-callable-apex-generate/SKILL.md +14 -0
- package/skills/omnistudio-datamapper-generate/SKILL.md +13 -0
- package/skills/omnistudio-datapacks-deploy/SKILL.md +18 -0
- package/skills/omnistudio-dependencies-analyze/SKILL.md +12 -0
- package/skills/omnistudio-epc-catalog-generate/SKILL.md +15 -0
- package/skills/omnistudio-flexcard-generate/SKILL.md +11 -0
- package/skills/omnistudio-integration-procedure-generate/SKILL.md +11 -0
- package/skills/omnistudio-omniscript-generate/SKILL.md +9 -0
- package/skills/platform-agentexchange-partner-offers-configure/SKILL.md +8 -0
- package/skills/platform-agentsetup-categories-fetch/SKILL.md +3 -0
- package/skills/platform-apex-generate/SKILL.md +5 -0
- package/skills/platform-apex-logs-debug/SKILL.md +12 -0
- package/skills/platform-apex-test-generate/SKILL.md +5 -0
- package/skills/platform-apex-test-run/SKILL.md +14 -0
- package/skills/platform-custom-application-generate/SKILL.md +1 -0
- package/skills/platform-custom-field-generate/SKILL.md +1 -1
- package/skills/platform-custom-lightning-type-generate/SKILL.md +3 -0
- package/skills/platform-custom-lightning-type-generate/assets/primitive-types-and-constraints.md +1 -1
- package/skills/platform-custom-tab-generate/SKILL.md +1 -0
- package/skills/platform-data-manage/SKILL.md +15 -0
- package/skills/platform-dataspace-access-configure/SKILL.md +2 -2
- package/skills/platform-flexipage-generate/SKILL.md +3 -0
- package/skills/platform-lightning-app-coordinate/SKILL.md +18 -1
- package/skills/platform-mcp-tool-widget-coordinate/SKILL.md +250 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/action-name-source-prompt.md +74 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/apex-invocable-source-prompt.md +90 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/nested-object-source-prompt.md +191 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/pasted-tool-output-prompt.md +85 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/build-plan-format.md +74 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/mcp-tool-output-discovery.md +184 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/two-clt-modeling.md +128 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/validation-gates.md +181 -0
- package/skills/platform-metadata-api-context-get/SKILL.md +6 -2
- package/skills/platform-metadata-retrieve/SKILL.md +5 -0
- package/skills/platform-models-api-configure/SKILL.md +10 -0
- package/skills/platform-permission-set-generate/SKILL.md +1 -2
- package/skills/platform-policy-rule-generate/SKILL.md +2 -0
- package/skills/platform-report-generate/SKILL.md +1 -1
- package/skills/platform-sharing-rules-generate/SKILL.md +1 -1
- package/skills/platform-soql-query/SKILL.md +13 -0
- package/skills/platform-tracing-agentforce-configure/SKILL.md +5 -1
- package/skills/platform-tracing-configure/SKILL.md +4 -1
- package/skills/platform-trust-archive-manage/SKILL.md +3 -0
- package/skills/platform-validation-rule-generate/SKILL.md +1 -0
- package/skills/platform-widget-generate/SKILL.md +2 -1
- package/skills/sales-agentforce-pipeline-management-configure/SKILL.md +3 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Submit a zip to the ApexGuru SFAP Scan API and poll to completion.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash run-scan.sh <zip-path> <raw-result-out.json> [--org <alias>] [--fast]
|
|
6
|
+
# [--max-polls N] [--interval SEC]
|
|
7
|
+
# The endpoint (prod/stage/dev host) is chosen by resolve-token.sh from the
|
|
8
|
+
# token's environment; customers on a prod org always hit api.salesforce.com.
|
|
9
|
+
#
|
|
10
|
+
# Steps:
|
|
11
|
+
# 1. resolve-token.sh → baseUrl + JWT
|
|
12
|
+
# 2. POST {baseUrl}/scan (multipart file upload) → 202 {scanId, status}
|
|
13
|
+
# 3. GET {baseUrl}/scan/{scanId} every ~15s until SUCCEEDED/FAILED
|
|
14
|
+
# 4. Write the final raw SUCCEEDED body verbatim to <raw-result-out.json>
|
|
15
|
+
# (report stays base64-encoded here; decode-report.js handles it).
|
|
16
|
+
#
|
|
17
|
+
# Progress lines go to STDERR so stdout stays a single clean JSON summary:
|
|
18
|
+
# {"scanId":"...","status":"SUCCEEDED","analysisMode":"static|full",
|
|
19
|
+
# "violationCount":N,"filesScanned":N,"rawResult":"<path>"}
|
|
20
|
+
# On failure: {"error":"...","status":"...","httpStatus":NNN,"hint":"..."} exit 1.
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
|
|
25
|
+
ZIP_PATH="${1:-}"
|
|
26
|
+
RAW_OUT="${2:-}"
|
|
27
|
+
shift 2 2>/dev/null || true
|
|
28
|
+
|
|
29
|
+
FAST=""
|
|
30
|
+
ORG_ALIAS=""
|
|
31
|
+
MAX_POLLS=40 # 40 * 15s = 10 min ceiling
|
|
32
|
+
INTERVAL=15
|
|
33
|
+
while [ $# -gt 0 ]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--org) ORG_ALIAS="${2:-}"; shift 2 ;;
|
|
36
|
+
--fast) FAST="true"; shift ;;
|
|
37
|
+
--max-polls) MAX_POLLS="${2:-40}"; shift 2 ;;
|
|
38
|
+
--interval) INTERVAL="${2:-15}"; shift 2 ;;
|
|
39
|
+
*) shift ;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
err() { # msg httpStatus status hint
|
|
44
|
+
jq -cn --arg m "$1" --argjson h "${2:-0}" --arg s "${3:-}" --arg hint "${4:-}" \
|
|
45
|
+
'{error:$m, httpStatus:$h, status:$s, hint:$hint}'
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[ -n "$ZIP_PATH" ] && [ -f "$ZIP_PATH" ] || err "zip not found: $ZIP_PATH" 0 "" "run build-zip.sh first"
|
|
50
|
+
[ -n "$RAW_OUT" ] || err "missing raw output path" 0 "" "usage: run-scan.sh <zip> <raw-out.json>"
|
|
51
|
+
|
|
52
|
+
# --- 1. token ---
|
|
53
|
+
RESOLVE_ARGS=()
|
|
54
|
+
[ -n "$ORG_ALIAS" ] && RESOLVE_ARGS+=(--org "$ORG_ALIAS")
|
|
55
|
+
CREDS="$(bash "$SCRIPT_DIR/resolve-token.sh" ${RESOLVE_ARGS[@]+"${RESOLVE_ARGS[@]}"})" || {
|
|
56
|
+
echo "$CREDS" >&2; exit 1;
|
|
57
|
+
}
|
|
58
|
+
BASE_URL="$(echo "$CREDS" | jq -r '.baseUrl')"
|
|
59
|
+
# The JWT is a secret: resolve-token.sh returns a 0600 file PATH, never the token
|
|
60
|
+
# itself. Read it into memory and delete the file right away so it never lingers.
|
|
61
|
+
TOKEN_FILE="$(echo "$CREDS" | jq -r '.tokenFile')"
|
|
62
|
+
TOKEN=""
|
|
63
|
+
if [ -n "$TOKEN_FILE" ] && [ -f "$TOKEN_FILE" ]; then
|
|
64
|
+
TOKEN="$(cat "$TOKEN_FILE")"
|
|
65
|
+
rm -f "$TOKEN_FILE"
|
|
66
|
+
fi
|
|
67
|
+
[ -n "$TOKEN" ] || err "could not read resolved token" 0 "" "re-run; if it persists, resolve the token manually via APEXGURU_SFAP_TOKEN"
|
|
68
|
+
|
|
69
|
+
hint_for_status() { # maps HTTP status → actionable hint (see references/error-handling.md)
|
|
70
|
+
case "$1" in
|
|
71
|
+
401) echo "Token bad or expired. Re-authenticate the Salesforce org (or supply a fresh sfap_api JWT) and retry." ;;
|
|
72
|
+
403) echo "Scan owned by a different org. The token's tnk-claim org must match the scan owner." ;;
|
|
73
|
+
404) echo "Unknown scanId, or the scan was archived (~30-day GC). Re-submit." ;;
|
|
74
|
+
400) echo "Malformed zip, no Apex inside, or over size limit (200MB compressed / 1GB decompressed)." ;;
|
|
75
|
+
*) echo "See references/error-handling.md." ;;
|
|
76
|
+
esac
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Auto-enable fast mode if >10 Apex files (unless explicitly disabled)
|
|
80
|
+
if [ -z "$FAST" ]; then
|
|
81
|
+
# grep -c exits non-zero when there are no matches; capture the count without
|
|
82
|
+
# letting that non-zero status trip `set -e`, and default an empty result to 0.
|
|
83
|
+
FILE_COUNT=$(unzip -l "$ZIP_PATH" 2>/dev/null | grep -c '\.cls$' || true)
|
|
84
|
+
FILE_COUNT=${FILE_COUNT:-0}
|
|
85
|
+
if [ "$FILE_COUNT" -gt 10 ]; then
|
|
86
|
+
FAST="true"
|
|
87
|
+
echo "Auto-enabling fast mode (detected $FILE_COUNT Apex files)" >&2
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# --- 2. submit ---
|
|
92
|
+
echo "Submitting scan to $BASE_URL/scan ..." >&2
|
|
93
|
+
SUBMIT_BODY="$(mktemp)"
|
|
94
|
+
FAST_ARG=()
|
|
95
|
+
[ -n "$FAST" ] && FAST_ARG=(-F "fastMode=true")
|
|
96
|
+
|
|
97
|
+
HTTP=$(curl -sS -o "$SUBMIT_BODY" -w '%{http_code}' -X POST "$BASE_URL/scan" \
|
|
98
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
99
|
+
-H "x-apexguru-client: ApexGuru-Skill" \
|
|
100
|
+
-F "file=@${ZIP_PATH};type=application/zip" \
|
|
101
|
+
${FAST_ARG[@]+"${FAST_ARG[@]}"}) || err "network error submitting scan" 0 "" "check connectivity to the API host"
|
|
102
|
+
|
|
103
|
+
if [ "$HTTP" != "202" ] && [ "$HTTP" != "200" ]; then
|
|
104
|
+
MSG="$(jq -r '.message // .error // empty' "$SUBMIT_BODY" 2>/dev/null || true)"
|
|
105
|
+
rm -f "$SUBMIT_BODY"
|
|
106
|
+
err "submit failed: ${MSG:-HTTP $HTTP}" "$HTTP" "" "$(hint_for_status "$HTTP")"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
SCAN_ID="$(jq -r '.scanId // empty' "$SUBMIT_BODY")"
|
|
110
|
+
rm -f "$SUBMIT_BODY"
|
|
111
|
+
[ -n "$SCAN_ID" ] || err "no scanId in submit response" "$HTTP" "" "unexpected API response shape"
|
|
112
|
+
echo "Accepted. scanId=$SCAN_ID (polling every ${INTERVAL}s, up to $MAX_POLLS times)..." >&2
|
|
113
|
+
|
|
114
|
+
# --- 3. poll ---
|
|
115
|
+
POLL_BODY="$(mktemp)"
|
|
116
|
+
STATUS="QUEUED"
|
|
117
|
+
i=0
|
|
118
|
+
while [ "$i" -lt "$MAX_POLLS" ]; do
|
|
119
|
+
i=$((i + 1))
|
|
120
|
+
sleep "$INTERVAL"
|
|
121
|
+
HTTP=$(curl -sS -o "$POLL_BODY" -w '%{http_code}' -X GET "$BASE_URL/scan/$SCAN_ID" \
|
|
122
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
123
|
+
-H "x-apexguru-client: ApexGuru-Skill") || { echo "poll $i: network hiccup, retrying" >&2; continue; }
|
|
124
|
+
|
|
125
|
+
if [ "$HTTP" != "200" ]; then
|
|
126
|
+
MSG="$(jq -r '.message // .error // empty' "$POLL_BODY" 2>/dev/null || true)"
|
|
127
|
+
rm -f "$POLL_BODY"
|
|
128
|
+
err "poll failed: ${MSG:-HTTP $HTTP}" "$HTTP" "$STATUS" "$(hint_for_status "$HTTP")"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
STATUS="$(jq -r '.status // "UNKNOWN"' "$POLL_BODY")"
|
|
132
|
+
echo "poll $i/$MAX_POLLS: $STATUS" >&2
|
|
133
|
+
|
|
134
|
+
case "$STATUS" in
|
|
135
|
+
SUCCEEDED)
|
|
136
|
+
cp "$POLL_BODY" "$RAW_OUT"
|
|
137
|
+
rm -f "$POLL_BODY"
|
|
138
|
+
jq -c '{status, violationCount, filesScanned, rawResult: "'"$RAW_OUT"'"}' "$RAW_OUT"
|
|
139
|
+
exit 0
|
|
140
|
+
;;
|
|
141
|
+
FAILED)
|
|
142
|
+
MSG="$(jq -r '.message // "scan reported FAILED"' "$POLL_BODY")"
|
|
143
|
+
rm -f "$POLL_BODY"
|
|
144
|
+
err "$MSG" "$HTTP" "FAILED" "Inspect the message; re-submit after addressing it."
|
|
145
|
+
;;
|
|
146
|
+
QUEUED|RUNNING) : ;; # keep polling
|
|
147
|
+
*) echo "poll $i: unexpected status '$STATUS', continuing" >&2 ;;
|
|
148
|
+
esac
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
rm -f "$POLL_BODY"
|
|
152
|
+
err "scan did not finish within $((MAX_POLLS * INTERVAL))s (last status: $STATUS)" 0 "$STATUS" \
|
|
153
|
+
"Large projects can take longer — re-run with --max-polls higher, or use --fast."
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-shot ApexGuru performance scan: package → submit+poll → decode+present.
|
|
3
|
+
#
|
|
4
|
+
# This wrapper runs all three steps of the workflow in a single command so the
|
|
5
|
+
# scan cannot be left half-finished (a bare run-scan.sh already prints a
|
|
6
|
+
# violation count, which can read as "done" and tempt an early stop before the
|
|
7
|
+
# report is decoded). The FINAL stdout of this script is the ready-to-present
|
|
8
|
+
# markdown report from decode-report.js --present — print that to the user
|
|
9
|
+
# verbatim.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bash scan.sh <project-root> [--out <report.md>]
|
|
13
|
+
# [--org <alias>] [--fast] [--raw <raw-out.json>]
|
|
14
|
+
#
|
|
15
|
+
# <project-root> folder that contains Apex somewhere beneath it (defaults to
|
|
16
|
+
# CWD if omitted or given as "."). build-zip.sh collects every
|
|
17
|
+
# .cls/.trigger under it, any layout.
|
|
18
|
+
# --out FILE also write the presented markdown to FILE (e.g. report.md).
|
|
19
|
+
# --org ALIAS forwarded to run-scan.sh (which sf org to derive the JWT from).
|
|
20
|
+
# --fast forwarded to run-scan.sh (skip LLM-heavy fix generation).
|
|
21
|
+
# --raw FILE keep the raw SUCCEEDED JSON at FILE (default: a temp file).
|
|
22
|
+
#
|
|
23
|
+
# The endpoint (prod/stage/dev) is chosen from the token's env by resolve-token.sh;
|
|
24
|
+
# customers on a prod org always hit api.salesforce.com.
|
|
25
|
+
#
|
|
26
|
+
# Progress for each step streams to STDERR; only the final presented markdown
|
|
27
|
+
# goes to STDOUT. On any step failure, the failing step's JSON error/hint is
|
|
28
|
+
# surfaced to stderr and the script exits non-zero.
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
|
|
33
|
+
PROJECT_ROOT=""
|
|
34
|
+
OUT_FILE=""
|
|
35
|
+
RAW_OUT=""
|
|
36
|
+
ORG_ALIAS=""
|
|
37
|
+
FAST=""
|
|
38
|
+
|
|
39
|
+
# First non-flag arg is the project root.
|
|
40
|
+
while [ $# -gt 0 ]; do
|
|
41
|
+
case "$1" in
|
|
42
|
+
--out) OUT_FILE="${2:-}"; shift 2 ;;
|
|
43
|
+
--raw) RAW_OUT="${2:-}"; shift 2 ;;
|
|
44
|
+
--org) ORG_ALIAS="${2:-}"; shift 2 ;;
|
|
45
|
+
--fast) FAST="--fast"; shift ;;
|
|
46
|
+
*) [ -z "$PROJECT_ROOT" ] && PROJECT_ROOT="$1"; shift ;;
|
|
47
|
+
esac
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$(pwd)"
|
|
51
|
+
[ "$PROJECT_ROOT" = "." ] && PROJECT_ROOT="$(pwd)"
|
|
52
|
+
|
|
53
|
+
TS="$(date +%Y%m%d-%H%M%S)"
|
|
54
|
+
WORK_DIR="$(mktemp -d)"
|
|
55
|
+
ZIP_PATH="$WORK_DIR/apexguru-${TS}.zip"
|
|
56
|
+
[ -n "$RAW_OUT" ] || RAW_OUT="$WORK_DIR/apexguru-raw-${TS}.json"
|
|
57
|
+
|
|
58
|
+
cleanup() { [ -n "${WORK_DIR:-}" ] && rm -rf "$WORK_DIR" 2>/dev/null || true; }
|
|
59
|
+
trap cleanup EXIT
|
|
60
|
+
|
|
61
|
+
# --- Step 1: package the project's Apex into a zip ---
|
|
62
|
+
echo "[scan] Step 1/3: packaging $PROJECT_ROOT ..." >&2
|
|
63
|
+
ZIP_JSON="$(bash "$SCRIPT_DIR/build-zip.sh" "$PROJECT_ROOT" "$ZIP_PATH")" || {
|
|
64
|
+
echo "$ZIP_JSON" >&2
|
|
65
|
+
echo "[scan] packaging failed — see the error/hint above." >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
echo "[scan] packaged: $(echo "$ZIP_JSON" | jq -r '.humanSize // "?"') → $(echo "$ZIP_JSON" | jq -r '.zip')" >&2
|
|
69
|
+
|
|
70
|
+
# --- Step 2: submit + poll to completion ---
|
|
71
|
+
echo "[scan] Step 2/3: submitting and polling the scan ..." >&2
|
|
72
|
+
RUN_ARGS=("$ZIP_PATH" "$RAW_OUT")
|
|
73
|
+
[ -n "$ORG_ALIAS" ] && RUN_ARGS+=(--org "$ORG_ALIAS")
|
|
74
|
+
[ -n "$FAST" ] && RUN_ARGS+=("$FAST")
|
|
75
|
+
RUN_JSON="$(bash "$SCRIPT_DIR/run-scan.sh" "${RUN_ARGS[@]}")" || {
|
|
76
|
+
echo "$RUN_JSON" >&2
|
|
77
|
+
echo "[scan] scan failed — see the error/hint above." >&2
|
|
78
|
+
exit 1
|
|
79
|
+
}
|
|
80
|
+
echo "[scan] scan complete: $(echo "$RUN_JSON" | jq -r '.violationCount // "?"') violations" >&2
|
|
81
|
+
|
|
82
|
+
# --- Step 3: decode + present (this is the deliverable) ---
|
|
83
|
+
echo "[scan] Step 3/3: decoding and presenting the report ..." >&2
|
|
84
|
+
PRESENTED="$(node "$SCRIPT_DIR/decode-report.js" "$RAW_OUT" --present)" || {
|
|
85
|
+
echo "[scan] decoding failed against $RAW_OUT" >&2
|
|
86
|
+
exit 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Optionally persist the presented markdown (e.g. so it survives temp cleanup).
|
|
90
|
+
if [ -n "$OUT_FILE" ]; then
|
|
91
|
+
printf '%s\n' "$PRESENTED" > "$OUT_FILE"
|
|
92
|
+
echo "[scan] report also written to $OUT_FILE" >&2
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# FINAL stdout: the ready-to-present markdown. Print this to the user verbatim.
|
|
96
|
+
printf '%s\n' "$PRESENTED"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Locally sanity-check an SFAP JWT *before* any network call, so a bad token
|
|
3
|
+
// fails fast with a clear reason instead of a bare HTTP 401 deep in the poll
|
|
4
|
+
// loop. This does NOT verify the signature (only the server can) — it decodes
|
|
5
|
+
// the freely-readable header/body claims to catch the two things that cause
|
|
6
|
+
// almost every 401: missing sfap_api scope, or expired.
|
|
7
|
+
//
|
|
8
|
+
// The token's environment (prod/stage/dev) is detected from its tnk claim and
|
|
9
|
+
// reported as detectedEnv so the caller can route to the matching endpoint.
|
|
10
|
+
//
|
|
11
|
+
// The token is read from STDIN (never argv) so it does not leak into the
|
|
12
|
+
// process list. Usage:
|
|
13
|
+
// printf '%s' "$JWT" | node validate-token.js [--now <epoch>]
|
|
14
|
+
//
|
|
15
|
+
// Output (stdout, single-line JSON):
|
|
16
|
+
// pass: {"ok":true,"tnk":"...","detectedEnv":"prod|stage|dev|null","expiresInSeconds":N,"warnings":[...]}
|
|
17
|
+
// fail: {"ok":false,"error":"...","hint":"..."} (exit 1)
|
|
18
|
+
// A "fail" only happens for things we can PROVE wrong (expired / missing scope).
|
|
19
|
+
// Anything we cannot read is a warning, not a block.
|
|
20
|
+
|
|
21
|
+
function out(obj, code) {
|
|
22
|
+
console.log(JSON.stringify(obj));
|
|
23
|
+
process.exit(code);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
let now = Math.floor(Date.now() / 1000);
|
|
28
|
+
for (let i = 0; i < args.length; i++) {
|
|
29
|
+
if (args[i] === "--now") now = parseInt(args[++i], 10) || now;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Read the token from stdin (keeps it out of argv / process list).
|
|
33
|
+
let token = "";
|
|
34
|
+
try {
|
|
35
|
+
token = require("fs").readFileSync(0, "utf8").trim();
|
|
36
|
+
} catch {
|
|
37
|
+
token = "";
|
|
38
|
+
}
|
|
39
|
+
if (!token) out({ ok: false, error: "no token on stdin", hint: "resolve the token before validating" }, 1);
|
|
40
|
+
|
|
41
|
+
// A JWT is header.body.signature, each a base64url-encoded chunk.
|
|
42
|
+
function decodeSegment(seg) {
|
|
43
|
+
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
|
|
44
|
+
const pad = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
|
|
45
|
+
return JSON.parse(Buffer.from(pad, "base64").toString("utf8"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const parts = token.split(".");
|
|
49
|
+
if (parts.length < 2) {
|
|
50
|
+
// Not a JWT we can read — don't block (token may be an opaque format).
|
|
51
|
+
out({ ok: true, tnk: null, detectedEnv: null, expiresInSeconds: null, warnings: ["token is not a readable JWT — skipping local checks"] }, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let header, body;
|
|
55
|
+
try {
|
|
56
|
+
header = decodeSegment(parts[0]);
|
|
57
|
+
body = decodeSegment(parts[1]);
|
|
58
|
+
} catch {
|
|
59
|
+
out({ ok: true, tnk: null, detectedEnv: null, expiresInSeconds: null, warnings: ["could not decode JWT claims — skipping local checks"] }, 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const warnings = [];
|
|
63
|
+
|
|
64
|
+
// --- 1. tnk / environment. Per the SFAP contract, tnk lives on the HEADER and
|
|
65
|
+
// looks like core/<instance>/<orgId> (e.g. core/prod/00D..., core/stagecomstg2/...,
|
|
66
|
+
// core/falcondeva/...). The environment marker lives ONLY in the <instance>
|
|
67
|
+
// segment — the trailing <orgId> (00D...) is an opaque id that must NOT be
|
|
68
|
+
// scanned for substrings, or a prod org whose id happens to contain "dev"/"stg"
|
|
69
|
+
// would be wrongly rejected. So we match markers against the instance segment
|
|
70
|
+
// and the iss host only, never the whole claim string. The detected env is
|
|
71
|
+
// reported (not rejected) so the caller can route to the matching endpoint. ---
|
|
72
|
+
const tnk = header.tnk || body.tnk || null;
|
|
73
|
+
const iss = body.iss || "";
|
|
74
|
+
// tnk instance segment = the middle of core/<instance>/<orgId>; fall back to the
|
|
75
|
+
// whole tnk only if it isn't in that 3-part shape.
|
|
76
|
+
const tnkParts = tnk ? String(tnk).split("/") : [];
|
|
77
|
+
const tnkInstance = tnkParts.length >= 3 ? tnkParts[1] : tnk || "";
|
|
78
|
+
// iss host (strip scheme/path) so we match the host label, not a full URL.
|
|
79
|
+
const issHost = String(iss).replace(/^https?:\/\//, "").split(/[/?#]/)[0];
|
|
80
|
+
const envHay = `${tnkInstance} ${issHost}`.toLowerCase();
|
|
81
|
+
const looksDev = /falcondeva|falcondev|falcontest|deva|(^|[^a-z])dev([^a-z]|$)/.test(envHay);
|
|
82
|
+
const looksStage = /stg|stage/.test(envHay);
|
|
83
|
+
// Env inferred from the token's own claims (null when we can't tell). Production
|
|
84
|
+
// instances carry no stg/dev marker, so a readable tnk with neither signal is prod.
|
|
85
|
+
const detectedEnv = looksStage && !looksDev
|
|
86
|
+
? "stage"
|
|
87
|
+
: looksDev && !looksStage
|
|
88
|
+
? "dev"
|
|
89
|
+
: tnk && !looksStage && !looksDev
|
|
90
|
+
? "prod"
|
|
91
|
+
: null;
|
|
92
|
+
|
|
93
|
+
if (!tnk) warnings.push("no tnk claim found — could not detect the token's environment; defaulting to prod endpoint");
|
|
94
|
+
|
|
95
|
+
// --- 2. scope must include sfap_api. Scope may be a space-delimited string
|
|
96
|
+
// (scp/scope) or an array. Only fail if we can read it AND sfap_api is absent. ---
|
|
97
|
+
const scopeRaw = body.scp ?? body.scope ?? null;
|
|
98
|
+
if (scopeRaw != null) {
|
|
99
|
+
const scopes = Array.isArray(scopeRaw) ? scopeRaw : String(scopeRaw).split(/\s+/);
|
|
100
|
+
if (!scopes.includes("sfap_api")) {
|
|
101
|
+
out({ ok: false, error: "token scope does not include sfap_api",
|
|
102
|
+
hint: "Re-mint the token with the sfap_api scope. See references/authentication.md." }, 1);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
warnings.push("no scope claim found — could not confirm sfap_api scope");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- 3. expiry. exp is epoch seconds. Fail if already past. ---
|
|
109
|
+
let expiresInSeconds = null;
|
|
110
|
+
if (typeof body.exp === "number") {
|
|
111
|
+
expiresInSeconds = body.exp - now;
|
|
112
|
+
if (expiresInSeconds <= 0) {
|
|
113
|
+
out({ ok: false, error: `token expired ${Math.abs(expiresInSeconds)}s ago`,
|
|
114
|
+
hint: "Re-mint a fresh SFAP token and retry." }, 1);
|
|
115
|
+
}
|
|
116
|
+
if (expiresInSeconds < 60) warnings.push(`token expires in ${expiresInSeconds}s — it may lapse mid-scan`);
|
|
117
|
+
} else {
|
|
118
|
+
warnings.push("no exp claim found — could not confirm the token is unexpired");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
out({ ok: true, tnk, detectedEnv, expiresInSeconds, warnings }, 0);
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
name: dx-app-analytics-query
|
|
3
3
|
description: "ISV App Analytics metadata types — AppAnalyticsQueryRequest and AppAnalyticsSettings. Use this skill when the user asks about retrieving managed package usage data, configuring App Analytics simulation mode, querying subscriber snapshots, or understanding the AppAnalyticsQueryRequest lifecycle (New → Pending → Complete → Expired). TRIGGER when: user mentions App Analytics, AppAnalyticsQueryRequest, AppAnalyticsSettings, package usage data, subscriber analytics, ISV analytics, or simulation mode for app analytics. DO NOT TRIGGER when: the task is about standard Salesforce reports/dashboards (use reporting skills), custom SOQL on Account/Contact (use platform-soql-query), or Data Cloud query/search (use data360-query)."
|
|
4
4
|
metadata:
|
|
5
|
+
cliTools:
|
|
6
|
+
- tool: ["sf"]
|
|
7
|
+
semver: ">=2.0.0"
|
|
8
|
+
relatedSkills:
|
|
9
|
+
- "platform-soql-query"
|
|
5
10
|
version: "1.0"
|
|
6
11
|
minApiVersion: "56.0"
|
|
7
12
|
---
|
|
@@ -7,18 +7,18 @@ metadata:
|
|
|
7
7
|
- "dx-code-analyzer-run"
|
|
8
8
|
- "dx-code-analyzer-custom-rule-create"
|
|
9
9
|
cliTools:
|
|
10
|
-
- tool: ["
|
|
11
|
-
semver: ">=
|
|
10
|
+
- tool: ["curl"]
|
|
11
|
+
semver: ">=7.0.0"
|
|
12
12
|
- tool: ["java"]
|
|
13
13
|
semver: ">=11.0.0"
|
|
14
14
|
- tool: ["node"]
|
|
15
15
|
semver: ">=18.0.0"
|
|
16
|
+
- tool: ["npm"]
|
|
17
|
+
semver: ">=9.0.0"
|
|
16
18
|
- tool: ["python3"]
|
|
17
19
|
semver: ">=3.10.0"
|
|
18
|
-
- tool: ["
|
|
20
|
+
- tool: ["sf"]
|
|
19
21
|
semver: ">=2.0.0"
|
|
20
|
-
- tool: ["npm"]
|
|
21
|
-
semver: ">=9.0.0"
|
|
22
22
|
---
|
|
23
23
|
|
|
24
24
|
# Configuring Code Analyzer Skill
|
|
@@ -7,12 +7,14 @@ metadata:
|
|
|
7
7
|
- "dx-code-analyzer-run"
|
|
8
8
|
- "dx-code-analyzer-configure"
|
|
9
9
|
cliTools:
|
|
10
|
-
- tool: ["
|
|
10
|
+
- tool: ["git"]
|
|
11
11
|
semver: ">=2.0.0"
|
|
12
12
|
- tool: ["node"]
|
|
13
13
|
semver: ">=18.0.0"
|
|
14
14
|
- tool: ["npm"]
|
|
15
15
|
semver: ">=9.0.0"
|
|
16
|
+
- tool: ["sf"]
|
|
17
|
+
semver: ">=2.0.0"
|
|
16
18
|
---
|
|
17
19
|
|
|
18
20
|
# dx-code-analyzer-custom-rule-create: Custom Code Analyzer Rule Authoring
|
|
@@ -7,11 +7,19 @@ metadata:
|
|
|
7
7
|
- "dx-code-analyzer-configure"
|
|
8
8
|
- "dx-code-analyzer-custom-rule-create"
|
|
9
9
|
cliTools:
|
|
10
|
-
- tool: ["
|
|
10
|
+
- tool: ["git"]
|
|
11
11
|
semver: ">=2.0.0"
|
|
12
|
+
- tool: ["java"]
|
|
13
|
+
semver: ">=11.0.0"
|
|
14
|
+
- tool: ["jq"]
|
|
15
|
+
semver: ">=1.6.0"
|
|
12
16
|
- tool: ["node"]
|
|
13
17
|
semver: ">=18.0.0"
|
|
14
|
-
- tool: ["
|
|
18
|
+
- tool: ["npm"]
|
|
19
|
+
semver: ">=9.0.0"
|
|
20
|
+
- tool: ["python3"]
|
|
21
|
+
semver: ">=3.9.0"
|
|
22
|
+
- tool: ["sf"]
|
|
15
23
|
semver: ">=2.0.0"
|
|
16
24
|
---
|
|
17
25
|
|