@salesforce/afv-skills 1.55.0 → 1.56.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-generate/SKILL.md +8 -0
- package/skills/agentforce-generate/references/actions-reference.md +2 -2
- package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
- package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
- package/skills/agentforce-observe/SKILL.md +13 -9
- package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
- package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
- package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
- package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
- package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
- package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
- package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
- package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
- package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
- package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
- package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
- package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
- package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
- package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
- package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
- package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
- package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
- package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
- package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
- package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
- package/skills/dx-org-analyze/SKILL.md +0 -1
- package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
- package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
- package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
- package/skills/dx-org-manage/SKILL.md +6 -50
- package/skills/dx-org-manage/examples/README.md +10 -16
- package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
- package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
- package/skills/dx-org-shape-manage/SKILL.md +4 -3
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
- package/skills/experience-accessibility-validate/SKILL.md +8 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
- package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
- package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
- package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
- package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
- package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
- package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
- package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
- package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
- package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
- package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
- package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
- package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
- package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
- package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
- package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
- package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
- package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
- package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
- package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
- package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
- package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
- package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
- package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
- package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
- package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
- package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
- package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
- package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
- package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
- package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
- package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
- package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
- package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
- package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
- package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
- package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
- package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
- package/skills/mobile-apps-create/SKILL.md +1 -1
- package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
- package/skills/platform-sandbox-configure/SKILL.md +108 -133
- package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
- package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
- package/skills/platform-widget-generate/SKILL.md +26 -18
- package/skills/platform-widget-generate/examples/formulas.json +191 -0
- package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
- package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
- package/skills/sales-call-scoring-configure/SKILL.md +318 -0
- package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
- package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
- package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
- package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
- package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
- package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
- package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
- package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
- package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
- package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
- package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
- package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
- package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
- package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
- package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
- package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
- package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
- package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
- package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
- package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
- package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
- package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
- package/skills/service-de-channel-activate/SKILL.md +6 -5
- package/skills/service-de-channel-create/SKILL.md +3 -3
- package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
- package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
- package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
- package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
- package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
- package/skills/service-helpagent-coordinate/SKILL.md +3 -2
- package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
- package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
- package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
- package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
- package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
- package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
- package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
- package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
- package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
- package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
- package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
- package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
- package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
- package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
- package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
- package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
- package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
- package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
- package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
- package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
- package/skills/dx-org-manage/references/cli_flags.md +0 -67
- package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
- package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
- package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
- package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
- package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
- /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
- /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
const { execSync, execFileSync, spawnSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/** Returns the correct sf binary name for the current OS. */
|
|
6
|
+
function getSfBin() {
|
|
7
|
+
return process.platform === 'win32' ? 'sf.cmd' : 'sf';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* sf org display --json stopped returning a real accessToken as of CLI v2.136.8
|
|
12
|
+
* (the field becomes a redacted hint string). Fall back to
|
|
13
|
+
* `sf org auth show-access-token` on those versions.
|
|
14
|
+
*/
|
|
15
|
+
const ACCESS_TOKEN_VIA_SHOW_SINCE = [2, 136, 8];
|
|
16
|
+
|
|
17
|
+
function getSfCliVersion() {
|
|
18
|
+
try {
|
|
19
|
+
let raw;
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
const r = spawnSync('sf.cmd', ['version', '--json'], {
|
|
22
|
+
encoding: 'utf8', stdio: 'pipe', shell: true, windowsVerbatimArguments: true
|
|
23
|
+
});
|
|
24
|
+
if (r.error) return null;
|
|
25
|
+
raw = r.stdout;
|
|
26
|
+
} else {
|
|
27
|
+
raw = execFileSync(getSfBin(), ['version', '--json'], { encoding: 'utf8', stdio: 'pipe' });
|
|
28
|
+
}
|
|
29
|
+
const { cliVersion } = JSON.parse(raw);
|
|
30
|
+
const m = (cliVersion || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
31
|
+
return m ? m.slice(1).map(Number) : null;
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isSfAtLeast(version, min) {
|
|
38
|
+
if (!version) return true; // unknown version → assume new enough
|
|
39
|
+
for (let i = 0; i < min.length; i++) {
|
|
40
|
+
if ((version[i] || 0) !== (min[i] || 0)) return (version[i] || 0) > (min[i] || 0);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run an sf CLI command with args and return parsed JSON output.
|
|
47
|
+
* Works on both Windows (cmd.exe) and Unix (sh).
|
|
48
|
+
* @param {string[]} args - CLI arguments
|
|
49
|
+
* @returns {any} parsed JSON result
|
|
50
|
+
*/
|
|
51
|
+
function execSfJson(args, options) {
|
|
52
|
+
options = options || {};
|
|
53
|
+
let rawOutput;
|
|
54
|
+
try {
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
// execFileSync cannot run .cmd files without shell:true, but shell:true
|
|
57
|
+
// word-splits args containing spaces. Use spawnSync with shell:true +
|
|
58
|
+
// windowsVerbatimArguments:true and pre-quote space-bearing args so that
|
|
59
|
+
// cmd.exe receives them intact.
|
|
60
|
+
const winArgs = args.map(a =>
|
|
61
|
+
(a.includes(' ') || a.includes("'")) ? `"${a.replace(/"/g, '\\"')}"` : a
|
|
62
|
+
);
|
|
63
|
+
const r = spawnSync('sf.cmd', winArgs, {
|
|
64
|
+
encoding: 'utf8', stdio: 'pipe', shell: true, windowsVerbatimArguments: true,
|
|
65
|
+
cwd: options.cwd
|
|
66
|
+
});
|
|
67
|
+
if (r.error) throw r.error;
|
|
68
|
+
rawOutput = r.stdout;
|
|
69
|
+
} else {
|
|
70
|
+
rawOutput = execFileSync(getSfBin(), args, { encoding: 'utf8', stdio: 'pipe', cwd: options.cwd });
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err.stdout) {
|
|
74
|
+
rawOutput = err.stdout;
|
|
75
|
+
} else {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return JSON.parse(rawOutput);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Execute a command and return the output
|
|
84
|
+
*/
|
|
85
|
+
function exec(command, options = {}) {
|
|
86
|
+
try {
|
|
87
|
+
return execSync(command, {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: options.silent ? 'pipe' : 'inherit',
|
|
90
|
+
...options
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (options.ignoreError) {
|
|
94
|
+
// Return the error output if available (for JSON parsing)
|
|
95
|
+
return error.stdout || error.stderr || '{}';
|
|
96
|
+
}
|
|
97
|
+
const safeCmd = command.replace(/Authorization:\s*Bearer\s+[^\s'"\\]+/gi, 'Authorization: Bearer [REDACTED]');
|
|
98
|
+
console.error(`Error executing command: ${safeCmd}`);
|
|
99
|
+
console.error(error.message);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get org details including instance URL and access token
|
|
106
|
+
*/
|
|
107
|
+
function getOrgDetails(orgAlias) {
|
|
108
|
+
const useShowAccessTokenCommand = isSfAtLeast(getSfCliVersion(), ACCESS_TOKEN_VIA_SHOW_SINCE);
|
|
109
|
+
const result = execSfJson(['org', 'display', '--target-org', orgAlias, '--json']);
|
|
110
|
+
|
|
111
|
+
if (result.status !== 0) {
|
|
112
|
+
console.error('Error: Failed to get org details');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let accessToken;
|
|
117
|
+
if (useShowAccessTokenCommand) {
|
|
118
|
+
const tokenResult = execSfJson(['org', 'auth', 'show-access-token', '--target-org', orgAlias, '--no-prompt', '--json']);
|
|
119
|
+
if (!tokenResult.result || !tokenResult.result.accessToken) {
|
|
120
|
+
throw new Error(`Failed to retrieve access token for org "${orgAlias}": ${tokenResult.message || JSON.stringify(tokenResult)}`);
|
|
121
|
+
}
|
|
122
|
+
accessToken = tokenResult.result.accessToken;
|
|
123
|
+
} else {
|
|
124
|
+
if (!result.result || !result.result.accessToken) {
|
|
125
|
+
throw new Error(`Failed to retrieve access token for org "${orgAlias}": ${result.message || JSON.stringify(result)}`);
|
|
126
|
+
}
|
|
127
|
+
accessToken = result.result.accessToken;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
instanceUrl: result.result.instanceUrl,
|
|
132
|
+
accessToken,
|
|
133
|
+
apiVersion: result.result.apiVersion || '62.0',
|
|
134
|
+
orgId: result.result.id
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Query the org for the TPM Accruals package namespace prefix.
|
|
140
|
+
* Returns the prefix WITH trailing double underscore (e.g. "cgcloud_dev__"),
|
|
141
|
+
* or '' for source-only orgs.
|
|
142
|
+
*
|
|
143
|
+
* Uses the DataCloudExportScheduler Apex class as a probe — its NamespacePrefix
|
|
144
|
+
* is the managed package's prefix regardless of the subscriber org's own
|
|
145
|
+
* namespace. Querying Organization.NamespacePrefix instead would return the
|
|
146
|
+
* subscriber's prefix, which is only ever populated on packaging/scratch orgs
|
|
147
|
+
* and would silently return '' on real cgcloud subscribers.
|
|
148
|
+
*/
|
|
149
|
+
function getOrgNamespace(orgAlias) {
|
|
150
|
+
try {
|
|
151
|
+
const result = execSfJson(['data', 'query', '--query', "SELECT NamespacePrefix FROM ApexClass WHERE Name = 'DataCloudExportScheduler'", '--target-org', orgAlias, '--json']);
|
|
152
|
+
const records =
|
|
153
|
+
result.status === 0 && result.result && result.result.records
|
|
154
|
+
? result.result.records
|
|
155
|
+
: [];
|
|
156
|
+
const namespaced = records.find(r => r.NamespacePrefix);
|
|
157
|
+
if (namespaced) {
|
|
158
|
+
return `${namespaced.NamespacePrefix}__`;
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// Fall through to empty
|
|
162
|
+
}
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Strip a leading namespace prefix from a name (case-insensitive).
|
|
168
|
+
* Returns a lowercased copy suitable for tolerant comparisons.
|
|
169
|
+
*/
|
|
170
|
+
function normalizeName(name, namespace) {
|
|
171
|
+
if (!name) return '';
|
|
172
|
+
let normalized = String(name).toLowerCase();
|
|
173
|
+
if (namespace) {
|
|
174
|
+
const nsLower = String(namespace).toLowerCase();
|
|
175
|
+
if (nsLower && normalized.startsWith(nsLower)) {
|
|
176
|
+
normalized = normalized.slice(nsLower.length);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return normalized;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Walk a directory recursively returning all file paths (sync).
|
|
184
|
+
*/
|
|
185
|
+
function walkFiles(dir) {
|
|
186
|
+
const out = [];
|
|
187
|
+
if (!fs.existsSync(dir)) return out;
|
|
188
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
const full = path.join(dir, entry.name);
|
|
191
|
+
if (entry.isDirectory()) {
|
|
192
|
+
out.push(...walkFiles(full));
|
|
193
|
+
} else {
|
|
194
|
+
out.push(full);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build a map of dataSourceBundleDefinition name -> [dataStreamTemplate developer names]
|
|
202
|
+
* by scanning the dataStreamTemplates folder under a Data Kit's force-app tree.
|
|
203
|
+
*
|
|
204
|
+
* This is needed because deploying a DataStreamBundle does not always create a
|
|
205
|
+
* DataKitDeploymentLog row matching the bundle name itself; instead, rows are
|
|
206
|
+
* created for the underlying DataStreamTemplates contained in the bundle.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} dataKitPath - Path to the Data Kit root (containing force-app/)
|
|
209
|
+
* @returns {Map<string, string[]>}
|
|
210
|
+
*/
|
|
211
|
+
function getBundleStreamMap(dataKitPath) {
|
|
212
|
+
const map = new Map();
|
|
213
|
+
if (!dataKitPath) return map;
|
|
214
|
+
|
|
215
|
+
const templatesDir = path.join(
|
|
216
|
+
dataKitPath,
|
|
217
|
+
'force-app',
|
|
218
|
+
'main',
|
|
219
|
+
'default',
|
|
220
|
+
'dataStreamTemplates'
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (!fs.existsSync(templatesDir)) return map;
|
|
224
|
+
|
|
225
|
+
const files = fs
|
|
226
|
+
.readdirSync(templatesDir)
|
|
227
|
+
.filter(f => f.endsWith('.dataStreamTemplate-meta.xml'));
|
|
228
|
+
|
|
229
|
+
const bundleRegex = /<dataSourceBundleDefinition>\s*([^<\s]+)\s*<\/dataSourceBundleDefinition>/i;
|
|
230
|
+
// <sourceObjectName> is the stable identifier that appears verbatim (or as a prefix
|
|
231
|
+
// for ingest streams with hex suffixes) in DataKitDeploymentLog.ComponentName.
|
|
232
|
+
// E.g. sourceObjectName "tpm_accrualrule_ingest" → log "tpm_accrualrule_ingest_3ADCEDC6".
|
|
233
|
+
// Fall back to <dataSourceObject> if absent, then to the filename stem.
|
|
234
|
+
const srcObjNameRegex = /<sourceObjectName>\s*([^<\s]+)\s*<\/sourceObjectName>/i;
|
|
235
|
+
const srcObjRegex = /<dataSourceObject>\s*([^<\s]+)\s*<\/dataSourceObject>/i;
|
|
236
|
+
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
const fullPath = path.join(templatesDir, file);
|
|
239
|
+
let content;
|
|
240
|
+
try {
|
|
241
|
+
content = fs.readFileSync(fullPath, 'utf8');
|
|
242
|
+
} catch (error) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const match = content.match(bundleRegex);
|
|
247
|
+
if (!match) continue;
|
|
248
|
+
|
|
249
|
+
const bundleName = match[1];
|
|
250
|
+
const srcObjNameMatch = content.match(srcObjNameRegex);
|
|
251
|
+
const srcObjMatch = content.match(srcObjRegex);
|
|
252
|
+
const streamDevName = srcObjNameMatch
|
|
253
|
+
? srcObjNameMatch[1]
|
|
254
|
+
: srcObjMatch
|
|
255
|
+
? srcObjMatch[1]
|
|
256
|
+
: file.replace(/\.dataStreamTemplate-meta\.xml$/, '');
|
|
257
|
+
|
|
258
|
+
if (!map.has(bundleName)) {
|
|
259
|
+
map.set(bundleName, []);
|
|
260
|
+
}
|
|
261
|
+
if (!map.get(bundleName).includes(streamDevName)) {
|
|
262
|
+
map.get(bundleName).push(streamDevName);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return map;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Determine if any record in the deployment log matches one of the given spec names.
|
|
271
|
+
*
|
|
272
|
+
* Matching is namespace-tolerant (prefix is stripped before comparison) and
|
|
273
|
+
* uses strict equality by default. For DataStreamBundle specs we additionally
|
|
274
|
+
* allow `endsWith` so a bundle is recognised as deployed when any of its child
|
|
275
|
+
* stream developer names appears as the suffix of a log entry — which is how
|
|
276
|
+
* DataKitDeploymentLog records bundle children under managed-package prefixes.
|
|
277
|
+
*
|
|
278
|
+
* @param {Array<{ComponentName?: string}>} deploymentLog
|
|
279
|
+
* @param {{type?: string, names: string[]}} spec
|
|
280
|
+
* @param {string} namespace
|
|
281
|
+
*/
|
|
282
|
+
function findMatchingRecords(deploymentLog, spec, namespace) {
|
|
283
|
+
const names = (spec && spec.names) || [];
|
|
284
|
+
if (names.length === 0) return [];
|
|
285
|
+
const normalizedNames = names.map(n => normalizeName(n, namespace)).filter(n => n.length > 0);
|
|
286
|
+
if (normalizedNames.length === 0) return [];
|
|
287
|
+
// DataStreamBundle: deployed stream names carry namespace prefixes and hex
|
|
288
|
+
// suffixes (e.g. cgcloud_Promotion_c_Home, tpm_accrualrule_ingest_71560164).
|
|
289
|
+
// Match when the template dev-name appears as a substring of the log name.
|
|
290
|
+
const allowSubstring = spec && spec.type === 'DataStreamBundle';
|
|
291
|
+
return deploymentLog.filter(record => {
|
|
292
|
+
if (!record.ComponentName) return false;
|
|
293
|
+
const normalizedLog = normalizeName(record.ComponentName, namespace);
|
|
294
|
+
return normalizedNames.some(
|
|
295
|
+
n => n === normalizedLog || (allowSubstring && (normalizedLog.endsWith(n) || normalizedLog.includes(n)))
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Query DataKitDeploymentLog. The componentSpecs param is kept only for
|
|
302
|
+
* informational logging; the query returns recent records that we filter later.
|
|
303
|
+
*
|
|
304
|
+
* @param {string} orgAlias
|
|
305
|
+
* @param {Array<{id: string, names: string[]}>} componentSpecs
|
|
306
|
+
*/
|
|
307
|
+
function getDeploymentLogForComponents(orgAlias, componentSpecs) {
|
|
308
|
+
console.log(`Checking deployment status for ${componentSpecs.length} components...`);
|
|
309
|
+
|
|
310
|
+
const query = `SELECT ComponentName, DeploymentStatus, DeploymentError FROM DataKitDeploymentLog ORDER BY CreatedDate DESC LIMIT 500`;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const result = execSfJson(['data', 'query', '--query', query, '--target-org', orgAlias, '--json']);
|
|
314
|
+
|
|
315
|
+
if (result.status === 0 && result.result && result.result.records) {
|
|
316
|
+
return result.result.records;
|
|
317
|
+
}
|
|
318
|
+
return [];
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.log('No existing deployment log found (this is okay for first deployment)');
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Determine which component specs are already successfully deployed.
|
|
327
|
+
* A spec is considered deployed when at least one matching record in the
|
|
328
|
+
* deployment log has DeploymentStatus = 'Successful'.
|
|
329
|
+
*
|
|
330
|
+
* @param {Array<{ComponentName?: string, DeploymentStatus?: string}>} deploymentLog
|
|
331
|
+
* @param {Array<{id: string, names: string[]}>} componentSpecs
|
|
332
|
+
* @param {string} namespace
|
|
333
|
+
* @returns {Set<string>} set of deployed component ids
|
|
334
|
+
*/
|
|
335
|
+
function getDeployedComponents(deploymentLog, componentSpecs, namespace = '') {
|
|
336
|
+
const deployed = new Set();
|
|
337
|
+
|
|
338
|
+
componentSpecs.forEach(spec => {
|
|
339
|
+
const matches = findMatchingRecords(deploymentLog, spec, namespace);
|
|
340
|
+
const hasSuccess = matches.some(r => r.DeploymentStatus === 'Successful');
|
|
341
|
+
if (hasSuccess) {
|
|
342
|
+
deployed.add(spec.id);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return deployed;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Query the Salesforce server clock via Organization.SystemModstamp.
|
|
351
|
+
*
|
|
352
|
+
* Returns the MAX(CreatedDate) of existing DataKitDeploymentLog rows as a
|
|
353
|
+
* server-side baseline timestamp. Rows created after this point belong to the
|
|
354
|
+
* current deploy. Using MAX(CreatedDate) — a value already on the server —
|
|
355
|
+
* avoids local clock-skew entirely.
|
|
356
|
+
*
|
|
357
|
+
* Returns null if no rows exist yet (caller should omit the WHERE clause).
|
|
358
|
+
*
|
|
359
|
+
* @param {string} orgAlias
|
|
360
|
+
* @returns {string|null} ISO-8601 timestamp or null
|
|
361
|
+
*/
|
|
362
|
+
function getMaxCreatedDate(orgAlias) {
|
|
363
|
+
const result = execSfJson(['data', 'query', '--query', 'SELECT MAX(CreatedDate) maxDate FROM DataKitDeploymentLog', '--target-org', orgAlias, '--json']);
|
|
364
|
+
if (result.status === 0 && result.result && result.result.records && result.result.records.length > 0) {
|
|
365
|
+
return result.result.records[0].maxDate ?? null;
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Wait for data kit deployment to complete.
|
|
372
|
+
*
|
|
373
|
+
* Uses a server-side timestamp boundary (WHERE CreatedDate > deploymentStartTime)
|
|
374
|
+
* rather than an opaque Id comparison to isolate log rows created by this deploy.
|
|
375
|
+
* The timestamp is obtained from the Salesforce server via getMaxCreatedDate() just
|
|
376
|
+
* before the deploy is triggered, making the filter immune to local clock-skew.
|
|
377
|
+
*
|
|
378
|
+
* @param {string} orgAlias
|
|
379
|
+
* @param {Array<{id: string, names: string[]}>} componentSpecs
|
|
380
|
+
* @param {string|null} deploymentStartTime - MAX(CreatedDate) from DataKitDeploymentLog captured before deploy; null = no filter (no prior rows)
|
|
381
|
+
* @param {string} namespace - Optional org namespace prefix (e.g. "cgcloud_dev__")
|
|
382
|
+
*/
|
|
383
|
+
async function waitForDeploymentCompletion(orgAlias, componentSpecs, deploymentStartTime, namespace = '') {
|
|
384
|
+
console.log(`\nWaiting for deployment to complete...`);
|
|
385
|
+
console.log(`Tracking ${componentSpecs.length} components`);
|
|
386
|
+
|
|
387
|
+
const maxWaitTime = 90 * 60 * 1000; // 90 minutes — production orgs typically take 60-90 min
|
|
388
|
+
const pollInterval = 60_000; // 60 seconds
|
|
389
|
+
const startTime = Date.now();
|
|
390
|
+
|
|
391
|
+
const completedComponents = new Set();
|
|
392
|
+
const failedComponents = new Map();
|
|
393
|
+
|
|
394
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
395
|
+
const whereClause = deploymentStartTime ? `WHERE CreatedDate > ${deploymentStartTime}` : '';
|
|
396
|
+
const query = `SELECT ComponentName, DeploymentStatus, DeploymentError FROM DataKitDeploymentLog ${whereClause} ORDER BY CreatedDate DESC LIMIT 500`;
|
|
397
|
+
|
|
398
|
+
let deploymentLog = [];
|
|
399
|
+
try {
|
|
400
|
+
const result = execSfJson(['data', 'query', '--query', query, '--target-org', orgAlias, '--json']);
|
|
401
|
+
if (result.status === 0 && result.result && result.result.records) {
|
|
402
|
+
deploymentLog = result.result.records;
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.warn(`Polling query failed (will retry): ${error.message}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
componentSpecs.forEach(spec => {
|
|
409
|
+
if (completedComponents.has(spec.id) || failedComponents.has(spec.id)) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const matches = findMatchingRecords(deploymentLog, spec, namespace);
|
|
413
|
+
if (matches.length === 0) return;
|
|
414
|
+
|
|
415
|
+
const failure = matches.find(r => r.DeploymentStatus === 'Failure');
|
|
416
|
+
const hasSuccess = matches.some(r => r.DeploymentStatus === 'Successful');
|
|
417
|
+
|
|
418
|
+
if (failure) {
|
|
419
|
+
failedComponents.set(spec.id, failure.DeploymentError || 'Unknown error');
|
|
420
|
+
} else if (hasSuccess) {
|
|
421
|
+
completedComponents.add(spec.id);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const inProgressCount = componentSpecs.length - completedComponents.size - failedComponents.size;
|
|
426
|
+
|
|
427
|
+
console.log(` Status: ${completedComponents.size} succeeded, ${failedComponents.size} failed, ${inProgressCount} in progress`);
|
|
428
|
+
|
|
429
|
+
if (failedComponents.size > 0) {
|
|
430
|
+
console.error('\nERROR: Deployment failed with errors:');
|
|
431
|
+
failedComponents.forEach((error, component) => {
|
|
432
|
+
console.error(` - ${component}: ${error}`);
|
|
433
|
+
});
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (completedComponents.size === componentSpecs.length) {
|
|
438
|
+
console.log(`[OK] Deployment completed successfully!\n`);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.error(`\nERROR: Deployment timed out after ${maxWaitTime / 1000 / 60} minutes`);
|
|
446
|
+
console.error(`Completed: ${completedComponents.size}/${componentSpecs.length}`);
|
|
447
|
+
const pending = componentSpecs.filter(s => !completedComponents.has(s.id)).map(s => s.id);
|
|
448
|
+
if (pending.length > 0) {
|
|
449
|
+
console.error(`Pending: ${pending.join(', ')}`);
|
|
450
|
+
}
|
|
451
|
+
throw new Error(`Deployment timed out after ${maxWaitTime / 1000 / 60} minutes — ${pending.length} component(s) still in progress`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Run `sf project deploy start` with --json, validate via status fields.
|
|
456
|
+
* SF CLI exits 1 even on success when warnings are present.
|
|
457
|
+
*/
|
|
458
|
+
function execDeployJson(deployArgs, options) {
|
|
459
|
+
options = options || {};
|
|
460
|
+
const allArgs = deployArgs.concat(['--json']);
|
|
461
|
+
let rawOutput;
|
|
462
|
+
try {
|
|
463
|
+
if (process.platform === 'win32') {
|
|
464
|
+
const winArgs = allArgs.map(a =>
|
|
465
|
+
(a.includes(' ') || a.includes("'")) ? `"${a.replace(/"/g, '\\"')}"` : a
|
|
466
|
+
);
|
|
467
|
+
const r = spawnSync('sf.cmd', winArgs, {
|
|
468
|
+
encoding: 'utf8', stdio: 'pipe', shell: true, windowsVerbatimArguments: true,
|
|
469
|
+
cwd: options.cwd
|
|
470
|
+
});
|
|
471
|
+
if (r.error) throw r.error;
|
|
472
|
+
rawOutput = r.stdout;
|
|
473
|
+
} else {
|
|
474
|
+
rawOutput = execFileSync(getSfBin(), allArgs, { encoding: 'utf8', stdio: 'pipe', cwd: options.cwd });
|
|
475
|
+
}
|
|
476
|
+
} catch (err) {
|
|
477
|
+
if (err.stdout) {
|
|
478
|
+
rawOutput = err.stdout;
|
|
479
|
+
} else {
|
|
480
|
+
throw err;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const result = JSON.parse(rawOutput);
|
|
484
|
+
if (result.status !== 0) {
|
|
485
|
+
const failures = (((result.result || {}).details) || {}).componentFailures || [];
|
|
486
|
+
const msg = failures.length
|
|
487
|
+
? failures.map(f => f.fullName + ': ' + f.problem).join('\n')
|
|
488
|
+
: (result.message || 'Deploy failed');
|
|
489
|
+
throw new Error(msg);
|
|
490
|
+
}
|
|
491
|
+
if (result.result && result.result.status === 'Failed') {
|
|
492
|
+
const failures2 = ((result.result.details || {}).componentFailures) || [];
|
|
493
|
+
const msg2 = failures2.length
|
|
494
|
+
? failures2.map(f => f.fullName + ': ' + f.problem).join('\n')
|
|
495
|
+
: 'Deploy failed';
|
|
496
|
+
throw new Error(msg2);
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
module.exports = {
|
|
501
|
+
exec,
|
|
502
|
+
execDeployJson,
|
|
503
|
+
getOrgDetails,
|
|
504
|
+
getOrgNamespace,
|
|
505
|
+
getMaxCreatedDate,
|
|
506
|
+
normalizeName,
|
|
507
|
+
walkFiles,
|
|
508
|
+
getBundleStreamMap,
|
|
509
|
+
getDeploymentLogForComponents,
|
|
510
|
+
getDeployedComponents,
|
|
511
|
+
waitForDeploymentCompletion,
|
|
512
|
+
getSfBin,
|
|
513
|
+
execSfJson
|
|
514
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: consumer-goods-sync-management-configure
|
|
3
|
+
description: "Use this skill to set up Salesforce Consumer Goods (CG) Cloud Mobile Sync end-to-end for the Sync Management App managed package — it runs all three acts in order (Act 1 Setup Sync, Act 2 Assign Users, Act 3 Plan & Verify), not just installing the baseline, so a sales rep can see the day's planned visits offline. Use it when asked to \"set up CG Cloud mobile sync\", \"configure Consumer Goods mobile sync\", \"set up sync for Retail Execution\", \"install the sync baseline\", \"assign a user to the sync configuration\", \"verify the sync setup\", or \"why can't my rep see their planned visits offline\" when sync setup or the baseline install is suspected. DO NOT TRIGGER for general CG Cloud mobile-app troubleshooting unrelated to sync setup, for authoring new or custom sync baselines (this installs only shipped baselines discovered at runtime), or for building or customizing the mobile app UI itself — those are out of scope."
|
|
4
|
+
metadata:
|
|
5
|
+
version: "1.0"
|
|
6
|
+
cliTools:
|
|
7
|
+
- tool: ["sf"]
|
|
8
|
+
semver: ">=2.0.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Setting up CG Cloud Mobile Sync means running all three acts in order — Act 1 (Setup Sync), Act 2 (Assign Users), Act 3 (Plan & Verify) — NOT just installing the sync artefacts.** Installing the baseline is one step of Act 1; finishing it is not finishing the setup.
|
|
12
|
+
|
|
13
|
+
Guide an admin/implementer through setting up **Consumer Goods (CG) Cloud Mobile Sync** for a customer org so a sales rep can install the CG Cloud Mobile App and see the day's planned visits **offline**. The moving parts live in the managed package **`Sync Management App`** (its namespace is resolved at runtime — see `ns_prefix` in Session state) plus CG Cloud **core / Retail Execution** settings.
|
|
14
|
+
|
|
15
|
+
This skill automates as much as a **real** transport can trigger — a Headless-360 / generic-core call (SOQL, sObject REST, Connect, Tooling), an `sf` deploy, or a **`global @RestResource /v1/*`** endpoint in the package. A `public` package method is **not** enough: in a managed package `public` is namespace-private (unreachable from a subscriber org), so only `global`/`webservice`/`@RestResource` members are triggerable. Where neither a generic core capability nor an invokable package action can complete a step, the skill **reports what's needed — and who provides it — and stops that step**; it never fakes a capability.
|
|
16
|
+
|
|
17
|
+
## Scope & definition of done — READ THIS FIRST
|
|
18
|
+
|
|
19
|
+
**"Set up CG Cloud mobile sync" / "set up sync for Retail Execution" / "configure Consumer Goods mobile sync" with no narrowing qualifier means run ALL THREE ACTS, in order: Act 1 → Act 2 → Act 3.** It does **not** mean "install the artefacts." If you are about to run only the install for an unqualified request, that is the bug this section prevents — start at Act 1, step 1 (readiness).
|
|
20
|
+
|
|
21
|
+
**You are NOT done after installing artefacts.** Setup is complete only when **Act 3's end state** is reached (the activated config listed, verified across the four objects, and checked against the baseline files). A run that ends after the install is a *partial* run — say so plainly.
|
|
22
|
+
|
|
23
|
+
**Act 1 is not "install-only."** It has hard **readiness gates before** the install — the **namespace gate** (A1.1) and **Retail Execution enablement** (A1.2) — and a **verify after** it (A1.4). Installing the baseline into a non-RE-enabled org produces a config that can never sync, so readiness is checked first; a hard miss is **reported-and-stopped**, not written around. The baseline install *is* the sync configuration (the root `Sync_Config__c` and its values come from the baseline CSVs) — there is no separate "create config" step.
|
|
24
|
+
|
|
25
|
+
**A step the skill can't complete is a stop-*that-step* signal, not stop-the-whole-run.** Report the boundary and **who provides the missing piece** (the CG Cloud platform / Retail Execution, or the org admin), then **continue with every unblocked step.** Only a *hard readiness miss* halts the sequence: the sync metadata absent (neither a package install nor an org namespace resolves), RE cannot be enabled and isn't already on, or — under `Default_IOU` — the target user's missing default mobility IOU.
|
|
26
|
+
|
|
27
|
+
**The only legitimate subset run** is when the admin's own ask is narrow (e.g. "sync's already installed, just assign Chantelle", "just verify what's activated") — see the routing table. When the ask is unqualified or you are unsure, run the full 1→3.
|
|
28
|
+
|
|
29
|
+
## The transport contract — every automatable step is `core` or `Sync Management App`
|
|
30
|
+
|
|
31
|
+
This is the honesty spine. Before running a step, know its transport, and **never fake success for a step no transport can complete:**
|
|
32
|
+
|
|
33
|
+
- **`core`** — a generic Salesforce / Headless-360 capability: direct SOQL via `execute_api` → `GET /query?q=...`; the CG Cloud core `RetailExecutionSettings` get→set org-preference triad; sObject-REST writes (`POST /services/data/vXX.0/sobjects/<Entity>`); Schema/Tooling **describe** reads.
|
|
34
|
+
- **`Sync Management App`** — an action triggered inside the package. The real headless surface is a **`global @RestResource /v1/*`** endpoint, chiefly the three **`/v1/syncconfig/*`** routes: **`/install`** (GET discover + POST install — A1.3), **`/configs`** (GET deployed configs + strategy — A3.1 list), **`/assignment`** (GET/POST/DELETE user→config binding — A2.3). **Not** `Sync Management App`: `public` Apex methods (namespace-private) and Visualforce actions.
|
|
35
|
+
|
|
36
|
+
**When neither transport can complete a step**, report what's missing, name **who provides it**, and stop that step — do not guess an API name or write around it. The genuine non-automatable points are few: the *advanced*-RE pilot GA gate (A1.2, platform-owned, no API) and IOU-membership **creation** (A2.2, a CG Cloud core / RE task the skill only reads). Deep transport rules: `references/transports-and-namespace.md`.
|
|
37
|
+
|
|
38
|
+
## Mandatory pre-write plan — state this before your FIRST write
|
|
39
|
+
|
|
40
|
+
Before you execute a single write (enable RE, install a baseline, create a mapping row), post a short plan that makes your scope decision **explicit and reviewable**:
|
|
41
|
+
|
|
42
|
+
1. **The full journey is three acts** — Act 1 (Setup Sync), Act 2 (Assign Users), Act 3 (Plan & Verify).
|
|
43
|
+
2. **Which acts/sub-acts you will run, and which you will skip** — for every skip, **quote the exact words in the admin's request that narrow the scope** ("the request said 'set up sync'" is NOT a narrowing qualifier); for any step you can't complete, name who provides the missing piece.
|
|
44
|
+
3. **The target org**, confirmed explicitly (never silently inherited), and **the resolved `ns_prefix`** you will use for package objects.
|
|
45
|
+
4. **Where you will start.** For an unqualified request this is Act 1, step 1 (readiness: namespace + RE) — never the install.
|
|
46
|
+
5. **Persist this plan to `report.md`** in the working directory — writing that file is the very first action you take, before any org write. Append each Act's outcome (steps run, transport tag, read-back result, blocked steps) as you complete it, and write a final verdict at the end. This file — not the chat transcript — is the run's deliverable; it must exist even if the run stops early. Contract: "Write the run report to disk" in `references/verify-and-smoke.md`.
|
|
47
|
+
|
|
48
|
+
## Entry point — route on the admin's actual ask
|
|
49
|
+
|
|
50
|
+
**Default rule:** unless the ask *explicitly narrows the scope*, treat it as the full setup and **run Acts 1→2→3 in order.** When in doubt, run the full journey.
|
|
51
|
+
|
|
52
|
+
| Signal | Where to start |
|
|
53
|
+
|---|---|
|
|
54
|
+
| **"Set up CG Cloud mobile sync" / "set up sync for Retail Execution" / no narrowing qualifier / no prior state** | **Run the FULL journey: confirm org + resolve namespace, then Act 1 step 1 (readiness: namespace + RE), then Acts 1→2→3. NOT "just install." Not done until Act 3's end state.** |
|
|
55
|
+
| "Sync's already installed, I just need to assign a user" | Narrow → Act 2. First read back the installed config (Act 3 list step) to confirm a config is deployed before binding a user. |
|
|
56
|
+
| "Just install the Retail Execution baseline" | Narrow → Act 1 A1.3 only, **after** confirming A1.1/A1.2 readiness **and** explicit confirmation of which baseline. GET `/v1/syncconfig/install` to discover, then POST to install. Never hand-roll via raw sObject inserts. Report the skipped steps (A1.4, Acts 2–3) as not-done. |
|
|
57
|
+
| "Just verify / list what's activated" | Act 3. Read-only; no writes. |
|
|
58
|
+
| "Enable Retail Execution" | A1.2 only. CG Cloud **core**, a working `core` write; only *advanced*-RE hits the platform-owned pilot gate (no API). |
|
|
59
|
+
|
|
60
|
+
## Session state (track across the conversation)
|
|
61
|
+
|
|
62
|
+
- `target_org_alias` — the org being configured; confirmed explicitly before any write.
|
|
63
|
+
- `ns_prefix` — namespace prefix (with trailing `__`), **discovered at runtime** by A1.1 (never hardcoded), resolved once and used for every package-object path. Two sources: the installed managed package (`InstalledSubscriberPackage.NamespacePrefix`) or the org's own registered namespace (`Organization.NamespacePrefix`, source-deployed DE/packaging org).
|
|
64
|
+
- `api_version` — the Salesforce REST API version for raw-HTTP URLs. **Never hardcode.** Resolve once via `GET /services/data`, take the highest `version`. `execute_api` calls are versionless.
|
|
65
|
+
- `org_ready` — bool; true when A1.1's namespace gate passes AND A1.2 (RE enabled) passes. A hard miss on either halts the run.
|
|
66
|
+
- `installed_config_id` / `installed_version` — from the baseline install (A1.3). The install *is* the config — no separate "config created" flag.
|
|
67
|
+
- `act1_complete`, `act2_complete`, `act3_complete` — bool per Act; run strictly 1→2→3. For an unqualified request, all three must be `true` (minus reported non-automatable steps) before reporting complete.
|
|
68
|
+
- `resolution_strategy` — the config root's `Business_Area_Resolution_Strategy__c` (`Default_IOU` vs `Custom_User_Field`); decides whether A2.3 is IOU-implicit (via A2.2) or an explicit mapping write.
|
|
69
|
+
- `blocked_steps` — running list of steps the skill could not complete, each with the boundary and who provides the missing piece.
|
|
70
|
+
- `report_path` — the run's **deliverable on disk**: a `report.md` in the working directory. Created **before the first org write** (starting with the pre-write plan) and appended at each Act boundary, so a partial or interrupted run still leaves a faithful record. Reporting only in chat does **not** satisfy this — the record must exist as a file.
|
|
71
|
+
|
|
72
|
+
## Per-sub-act transport map (compact)
|
|
73
|
+
|
|
74
|
+
Single source of truth for which transport each step uses. **Full detail (API names, fallbacks, owners): `references/transports-and-namespace.md`, `references/readiness-and-enablement.md`, `references/sync-management-app-install-backbone.md`, `references/verify-and-smoke.md`.**
|
|
75
|
+
|
|
76
|
+
| Sub-act | Requirement | Transport | Note |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| A1.1 | Verify sync metadata present + resolve namespace (hard prereq) | `core` | **The namespace gate is the only hard stop here** (Tooling `InstalledSubscriberPackage` → else `Organization.NamespacePrefix`). No license read (install isn't license-gated) and **no permission-set-existence check** (perm-set names can be renamed/folded). |
|
|
79
|
+
| A1.2 | Enable Retail Execution | `core` | Read+write via `RetailExecutionSettings` (dispatcher, 5 prefs) or MDAPI `Settings:RetailExecution` (3 booleans). Only *advanced*-RE hits the pilot GA gate (no API); base toggle is unaffected. |
|
|
80
|
+
| A1.3 | Install the sync config (baseline root + sub-configs), with confirmation | `Sync Management App` | `SyncConfigInstallEndpoint` `/v1/syncconfig/install`: GET `discoverConfigs`, POST `installConfig`. Baselines discovered at runtime — never hardcoded. **The install *is* the config.** Never hand-roll via raw inserts. |
|
|
81
|
+
| A1.4 | Verify referenced field-set definitions exist per tracked object | `core` (read-only describe) | Names installed by the CSV; definitions (e.g. `RetailMobilityRelevant`) provisioned by RE, not the package. Missing set → report-and-stop, owner = RE provisioning. |
|
|
82
|
+
| A2.1 | Verify target user exists | `core` | `User` SOQL via `execute_api`. |
|
|
83
|
+
| A2.2 | Confirm user's Mobility IOU membership | `core` read-only precondition (`Default_IOU` only) | Reads `InternalOrgUnitUser` (`IsDfltMobIntrOrgUnit = true`); never writes IOU membership. No row under `Default_IOU` → report-and-stop. Skipped under `Custom_User_Field`. |
|
|
84
|
+
| A2.3 | Assign user to the installed config | `Sync Management App` / IOU-implicit → A2.2 | `SyncConfigAssignmentEndpoint` `/v1/syncconfig/assignment` (GET/POST/DELETE). **Not** `core`: a raw write corrupts the overloaded `Business_Area_Name__c`. |
|
|
85
|
+
| A3.1 | List + verify activated config across the 4 objects | `Sync Management App` (list) + `core` (counts) | `SyncConfigListEndpoint` `/v1/syncconfig/configs`; all four `COUNT()`s in **one batched anon-Apex call** (not a per-object `sf data query` loop). **No `/v1/syncconfig/verify` — record counts ARE the verification.** |
|
|
86
|
+
| A3.2 | Verify against baseline static-resource files | `core` | Compare installed per-object counts to the installed baseline's own declared counts, derived at runtime — never hardcoded. |
|
|
87
|
+
|
|
88
|
+
## Act 1 of 3 — Setup Sync
|
|
89
|
+
|
|
90
|
+
> Readiness gates **before** the install, a cheap **landing check after** it — the full per-object verification is Act 3's, not repeated here. Installing the baseline is the middle sub-step, not the whole act.
|
|
91
|
+
|
|
92
|
+
1. **A1.1 — Verify sync metadata present, resolve namespace.** `core`. Confirm the sync objects exist and resolve `ns_prefix` (package via Tooling `InstalledSubscriberPackage`, else `Organization.NamespacePrefix`). **Hard miss (stop) only when NEITHER resolves.** Installing the package is not license-gated, so there is no license read here — and **do not verify any permission set by name** (perm-set names can be renamed/folded). The real Act-1 readiness gates are exactly two: metadata-present (this step) and RE-enabled (A1.2).
|
|
93
|
+
2. **A1.2 — Enable Retail Execution.** `core` read + `core` write. Read via `RetailExecutionSettings` (dispatcher) or MDAPI `Settings:RetailExecution`; enable via the dispatcher's PATCH setters or `sf project deploy start --metadata "Settings:RetailExecution"` (false→true, re-read to confirm). RE-enabling implies base provisioning. The advanced-pilot toggle (`orgHasAdvncdRetailExecutionPilot`) has no API and gates only *advanced* RE.
|
|
94
|
+
3. **A1.3 — Install the sync configuration into the four objects.** `Sync Management App`. GET `discoverConfigs` (runtime discovery — no config named "Standard"), then **POST only on the admin's explicit OK** after presenting the chosen baseline and its manifest-derived per-object counts. Sub-configs = child baselines; `parentConfigId` comes from the manifest, enforcing parent-before-child. Never hand-roll via raw sObject inserts. On `SUCCESS`, confirm landing with the cheap **aggregate** check only — one **batched anon-Apex call** for all four `core` COUNT()s (never a per-object `sf data query` loop; cold-starts blow the timeout), sum == `InstallResponse.recordsInserted`, no manifest fetch — **not** Act 3's per-object comparison.
|
|
95
|
+
4. **A1.4 — Verify the referenced field-set definitions exist.** `core` read-only describe — **not a deploy.** The sync query-field path throws on a missing set (hard fail at first sync), so verify per tracked object; any missing → report-and-stop, owner = RE provisioning.
|
|
96
|
+
|
|
97
|
+
**Act 1 end state:** package present (gate passed) and RE-enabled (or the miss reported and run halted); the admin-confirmed baseline installed across the four objects after explicit confirmation (`installed_config_id`/`installed_version` captured), landing confirmed by the aggregate `COUNT()==recordsInserted` check (the per-object baseline verification is Act 3's); referenced field-set definitions verified present. Full detail: `references/act1-setup-sync.md`.
|
|
98
|
+
|
|
99
|
+
## Act 2 of 3 — Assign Users to the Sync Configuration
|
|
100
|
+
|
|
101
|
+
> Runs after Act 1 (binds a user to the config Act 1 installed). Detect the config root's resolution strategy before choosing the A2.3 transport.
|
|
102
|
+
|
|
103
|
+
1. **A2.1 — Verify the target user exists.** `core`. `User` SOQL via `execute_api`.
|
|
104
|
+
2. **A2.2 — Confirm the user's Mobility IOU membership.** `core` read-only precondition, **`Default_IOU` only.** Read the default mobility IOU (`IsDfltMobIntrOrgUnit = true`); carry `iouId` into A2.3, or **report-and-stop if none** (the skill does not create IOU membership). **Skip entirely under `Custom_User_Field`** — do not generalize into a blanket "no IOU ⇒ stop."
|
|
105
|
+
3. **A2.3 — Assign the user to the config.** `Sync Management App` (mapping row) / `core` (IOU-implicit). An assignment binds a User (`005`), Role (`00E`), OR Profile (`00e`); a Role/Profile row binds a whole cohort. Pick the config via `GET /v1/syncconfig/configs`, resolve name→Id, and scope by strategy (`Default_IOU` → `iouId`; `Custom_User_Field` → `businessArea` picklist). POST to `/v1/syncconfig/assignment`, then GET read-back — confirm the returned short canonical `clientAppId`. Re-assignment is an **upsert**: GET first; a duplicate `(configPath, businessArea, mappedRecordId)` triple is rejected; move a binding = DELETE the old row then POST. **Never write `Sync_Client_App_Profile_Mapping__c` raw** — the endpoint must derive the overloaded `Business_Area_Name__c`.
|
|
106
|
+
|
|
107
|
+
**Act 2 end state:** target user confirmed; under `Default_IOU`, default mobility IOU confirmed (or reported-and-stopped); the user bound to the installed config, verified by read-back. Full detail: `references/act2-assign-users.md`.
|
|
108
|
+
|
|
109
|
+
## Act 3 of 3 — Plan & Verify
|
|
110
|
+
|
|
111
|
+
> The final act — the **authoritative** verification (Act 1 ran only a cheap aggregate landing check; the per-object comparison happens here, once). For an unqualified setup you must reach this act's end state — do not stop after the install.
|
|
112
|
+
|
|
113
|
+
1. **A3.1 — List + verify the activated config across the four objects.** `Sync Management App` (list) + `core` (counts). List deployed configs + strategy via `GET /v1/syncconfig/configs` (a `core` SOQL list is an equivalent fallback). Get all counts via **one batched anon-Apex `COUNT()` call** across the four sync objects + `Sync_Config_State__c` (custom settings) — not a per-object `sf data query` loop (cold-starts blow the timeout). **No `/v1/syncconfig/verify` — record counts ARE the verification.**
|
|
114
|
+
2. **A3.2 — Verify against the baseline static-resource files.** `core`. Compare installed **per-object** counts to the installed baseline's own declared counts, derived at runtime (identify the baseline from the activated config / version captured in A1.3) — this per-object split is A3.2's job (the aggregate total was Act 1's landing check); it catches a right-total / wrong-distribution install. **Do not hardcode counts** — they vary by baseline.
|
|
115
|
+
|
|
116
|
+
**Act 3 end state:** the activated config listed via `/v1/syncconfig/configs` and verified across the four objects (+ state) through `core` SOQL record counts, each matched against the baseline's own declared count. Full detail: `references/act3-plan-verify.md`.
|
|
117
|
+
|
|
118
|
+
## Definition of done (the whole skill)
|
|
119
|
+
|
|
120
|
+
An unqualified CG Cloud mobile sync setup is **complete only when all three Acts reach their end states**, with any step the skill couldn't complete explicitly reported and attributed to who provides the missing piece: Act 1 (org ready; admin-confirmed baseline installed; field-set definitions verified — or missing set reported-and-stopped) → Act 2 (user exists; under `Default_IOU` the default mobility IOU confirmed — or reported-and-stopped; config bound) → Act 3 (activated config listed + verified against baseline files via `core` SOQL counts). **Reporting "done" after only the install (A1.3) is a defect.** Close with an explicit list of what remains and who provides each missing piece.
|
|
121
|
+
|
|
122
|
+
## Out of scope / deferred
|
|
123
|
+
|
|
124
|
+
Reported to set expectations, not reasons to halt the Act sequence:
|
|
125
|
+
|
|
126
|
+
- Removal / GA-gating of the `orgHasAdvncdRetailExecutionPilot` admission toggle — CG Cloud core (Retail Execution); no API anywhere (A1.2).
|
|
127
|
+
- Authoring **new/custom** baselines beyond those shipped (discovered at runtime) — manual ZIP (manifest + CSV) authoring outside this skill.
|
|
128
|
+
- **Creating** Mobility IOU membership (A2.2) — a CG Cloud core / RE task; the skill only reads it.
|
|
129
|
+
|
|
130
|
+
## Companion references
|
|
131
|
+
|
|
132
|
+
- `references/act1-setup-sync.md`, `references/act2-assign-users.md`, `references/act3-plan-verify.md` — standalone per-Act breakdowns.
|
|
133
|
+
- `references/transports-and-namespace.md` — the transport & namespace rules and the `sf` CLI cookbook.
|
|
134
|
+
- `references/readiness-and-enablement.md` — the namespace gate & RE-enablement detail.
|
|
135
|
+
- `references/sync-management-app-install-backbone.md` — the Sync Management App install backbone.
|
|
136
|
+
- `references/verify-and-smoke.md` — the verify & smoke-test patterns.
|
|
137
|
+
- `examples/scenarios.md` — worked validation scenarios (correct vs. anti-pattern runs).
|