@salesforce/afv-skills 1.62.0 → 1.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-offline-experimentation-run/SKILL.md +253 -0
  3. package/skills/agentforce-offline-experimentation-run/scripts/build_report.py +796 -0
  4. package/skills/agentforce-offline-experimentation-run/scripts/resolve-token.sh +111 -0
  5. package/skills/agentforce-offline-experimentation-run/scripts/sfap_replay.py +1053 -0
  6. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +21 -11
  7. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +10 -0
  8. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  9. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +44 -1
  10. package/skills/automation-sandbox-post-copy-configure/SKILL.md +132 -124
  11. package/skills/automation-sandbox-post-copy-configure/references/flow_activation_path.md +151 -0
  12. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +55 -9
  13. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +54 -107
  14. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +1 -1
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +82 -5
  16. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +35 -2
  17. package/skills/consumer-goods-promotion-bo-api-deploy/references/preflight-and-seed-queries.md +149 -0
  18. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +17 -1
  19. package/skills/dx-devops-conflict-resolve/SKILL.md +1 -5
  20. package/skills/dx-devops-pipeline-manage/SKILL.md +1 -5
  21. package/skills/dx-devops-project-manage/SKILL.md +1 -5
  22. package/skills/dx-devops-promote/SKILL.md +1 -5
  23. package/skills/dx-devops-request-status/SKILL.md +1 -5
  24. package/skills/dx-lifecycle-center/SKILL.md +120 -0
  25. package/skills/dx-lifecycle-center/examples/workflows.md +175 -0
  26. package/skills/dx-lifecycle-center/references/connect-api.md +123 -0
  27. package/skills/dx-lifecycle-center/references/connect-org.md +184 -0
  28. package/skills/dx-lifecycle-center/references/deploy.md +153 -0
  29. package/skills/dx-lifecycle-center/references/error-handling.md +21 -0
  30. package/skills/dx-lifecycle-center/references/hub-org.md +37 -0
  31. package/skills/dx-lifecycle-center/references/list-orgs.md +99 -0
  32. package/skills/dx-lifecycle-center/references/request-body.md +75 -0
  33. package/skills/dx-lifecycle-center/references/troubleshooting.md +31 -0
  34. package/skills/dx-lifecycle-center/scripts/poll-deploy-status.sh +200 -0
  35. package/skills/dx-lifecycle-center/scripts/resolve-environment.sh +120 -0
  36. package/skills/dx-org-devhub-configure/SKILL.md +1 -3
  37. package/skills/dx-org-shape-manage/SKILL.md +1 -5
  38. package/skills/dx-org-snapshot-manage/SKILL.md +1 -3
  39. package/skills/education-cloud-academic-calendar-generate/SKILL.md +1 -3
  40. package/skills/education-cloud-course-catalog-migrate/SKILL.md +1 -3
  41. package/skills/education-cloud-domain-configure/SKILL.md +1 -3
  42. package/skills/education-cloud-multi-campus-configure/SKILL.md +1 -3
  43. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +1 -5
  44. package/skills/experience-ui-bundle-2gp-deploy/SKILL.md +1 -3
  45. package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -5
  46. package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -3
  47. package/skills/field-service-data-capture-migrate/SKILL.md +1 -3
  48. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +1 -3
  49. package/skills/marketing-message-test/SKILL.md +1 -3
  50. package/skills/nonprofit-fundraising-configure/SKILL.md +240 -0
  51. package/skills/nonprofit-fundraising-configure/references/gotchas.md +26 -0
  52. package/skills/nonprofit-fundraising-configure/references/mcp-invocation.md +678 -0
  53. package/skills/nonprofit-fundraising-configure/references/permission-targeting.md +35 -0
  54. package/skills/nonprofit-volunteer-management-model-get/SKILL.md +166 -0
  55. package/skills/nonprofit-volunteer-management-model-get/references/entities.md +315 -0
  56. package/skills/nonprofit-volunteer-management-model-get/references/relationships.md +175 -0
  57. package/skills/nonprofit-volunteer-management-model-get/references/themes.md +143 -0
  58. package/skills/nonprofit-volunteer-management-records-create/SKILL.md +121 -0
  59. package/skills/nonprofit-volunteer-management-records-create/references/field-guide.md +284 -0
  60. package/skills/nonprofit-volunteer-management-records-create/references/mcp-invocation.md +248 -0
  61. package/skills/nonprofit-volunteer-management-records-create/references/review-format.md +140 -0
  62. package/skills/nonprofit-volunteer-management-records-create/scripts/expand-series.py +632 -0
  63. package/skills/nonprofit-volunteer-management-records-create/scripts/render-review-table.py +419 -0
  64. package/skills/nonprofit-volunteer-management-records-create/scripts/tests/test_expand_series.py +690 -0
  65. package/skills/nonprofit-volunteer-management-records-create/scripts/tests/test_render_review_table.py +421 -0
  66. package/skills/nonprofit-volunteer-management-structure-generate/SKILL.md +119 -0
  67. package/skills/nonprofit-volunteer-management-structure-generate/references/configuration-decisions.md +159 -0
  68. package/skills/nonprofit-volunteer-management-structure-generate/references/discovery-interview.md +137 -0
  69. package/skills/nonprofit-volunteer-management-structure-generate/references/edge-cases.md +48 -0
  70. package/skills/nonprofit-volunteer-management-structure-generate/references/mcp-invocation.md +75 -0
  71. package/skills/nonprofit-volunteer-management-structure-generate/references/plan-output.md +141 -0
  72. package/skills/platform-custom-field-generate/SKILL.md +1 -0
  73. package/skills/platform-custom-field-generate/references/advanced-picklists.md +5 -5
  74. package/skills/platform-custom-metadata-type-generate/SKILL.md +3 -0
  75. package/skills/platform-custom-object-generate/SKILL.md +4 -0
  76. package/skills/platform-custom-setting-generate/SKILL.md +3 -0
  77. package/skills/platform-datamask-run/SKILL.md +1 -5
  78. package/skills/platform-permission-set-generate/SKILL.md +3 -0
  79. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +1 -3
  80. package/skills/platform-sandbox-configure/SKILL.md +1 -3
  81. package/skills/platform-value-set-generate/SKILL.md +3 -0
  82. package/skills/sales-call-scoring-configure/SKILL.md +1 -5
  83. package/skills/service-agentforce-channel-configure/SKILL.md +1 -3
  84. package/skills/service-agentforce-human-escalation-configure/SKILL.md +1 -3
  85. package/skills/service-catalog-template-coordinate/SKILL.md +1 -3
  86. package/skills/service-concierge-portal-generate/SKILL.md +1 -3
  87. package/skills/service-email-to-case-configure/SKILL.md +1 -5
  88. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +1 -3
  89. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +1 -3
  90. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +1 -3
  91. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +1 -3
  92. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +1 -3
  93. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -5
  94. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +1 -3
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +1 -3
  96. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +1 -3
  97. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +1 -3
  98. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +1 -3
  99. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +1 -0
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +1 -0
  101. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +1 -3
  102. package/skills/service-itsm-agentic-setup-uel-user-create/SKILL.md +1 -9
  103. package/skills/service-itsm-incident-mgmt-configure/SKILL.md +1 -5
  104. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -3
  105. package/skills/service-itsm-slack-itservice-configure/SKILL.md +1 -3
  106. package/skills/service-itsm-swarming-configure/SKILL.md +1 -3
  107. package/skills/service-itsm-teams-configure/SKILL.md +1 -3
  108. package/skills/service-itsm-teams-debug/SKILL.md +1 -3
  109. package/skills/service-itsm-teams-employee-agent-configure/SKILL.md +1 -3
  110. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +1 -3
  111. package/skills/service-itsm-teams-itservice-configure/SKILL.md +1 -3
  112. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +1 -3
  113. package/skills/service-omni-agent-users-create/SKILL.md +1 -3
  114. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +1 -3
  115. package/skills/service-omni-attribute-routing-configure/SKILL.md +1 -3
  116. package/skills/service-omni-base-settings-configure/SKILL.md +1 -3
  117. package/skills/service-omni-channel-inventory-analyze/SKILL.md +1 -3
  118. package/skills/service-omni-channel-limits-analyze/SKILL.md +1 -3
  119. package/skills/service-omni-channel-setup-coordinate/SKILL.md +1 -3
  120. package/skills/service-omni-command-center-analyze/SKILL.md +1 -3
  121. package/skills/service-omni-command-center-configure/SKILL.md +1 -3
  122. package/skills/service-omni-permission-set-assign/SKILL.md +1 -3
  123. package/skills/service-omni-presence-status-deploy/SKILL.md +1 -3
  124. package/skills/service-omni-presence-user-config-deploy/SKILL.md +1 -3
  125. package/skills/service-omni-queue-deploy/SKILL.md +1 -3
  126. package/skills/service-omni-queue-members-assign/SKILL.md +1 -3
  127. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +1 -3
  128. package/skills/service-omni-routing-flow-deploy/SKILL.md +1 -3
  129. package/skills/service-omni-service-channel-configure/SKILL.md +1 -3
  130. package/skills/service-omni-sidebar-configure/SKILL.md +1 -3
  131. package/skills/service-omni-skills-based-routing-configure/SKILL.md +1 -3
  132. package/skills/service-omni-supervisor-config-deploy/SKILL.md +1 -3
  133. package/skills/service-omni-supervisor-permset-assign/SKILL.md +1 -3
  134. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +1 -3
  135. package/skills/service-omni-supervisor-users-create/SKILL.md +1 -3
  136. package/skills/service-omni-work-skill-routing-configure/SKILL.md +1 -3
  137. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -47
  138. package/skills/tableau-next-asset-create/README.md +0 -65
@@ -0,0 +1,21 @@
1
+ # Simple Deploy Error Handling
2
+
3
+ Connect errors arrive as `CODE:message`. Read the code, explain the cause in plain language, state the fix, and stop. Never retry in a loop and never fabricate a `requestId` or result.
4
+
5
+ | Code / symptom | Cause | Fix |
6
+ |---|---|---|
7
+ | 403 / no access | User lacks `userHasDevopsSimpleDeployApiAccess`. | Ask an admin for DX Inspector deploy access. |
8
+ | `TARGET_ORG_NAMED_CREDENTIAL_NOT_AUTHENTICATED` | Named credential exists but OAuth was never completed. | Complete the OAuth flow for the environment (`connect-org.md`), then retry. |
9
+ | `TARGET_ORG_SESSION_EXPIRED` | Credential returned 401 or 403. | Reconnect the target org (`connect-org.md`), then retry. |
10
+ | `DEPLOY_TARGET_ORG_CONNECTION_FAILED` with `Bad_OAuth_Token` | The environment's stored OAuth token is stale or revoked (seen live). | Reconnect that environment (`connect-org.md`); try another environment only if the user asks. |
11
+ | `DEPLOY_TARGET_ORG_CONNECTION_FAILED` with an HTML error page | The target org is gone or unreachable (for example an expired scratch org; seen live). The message carries a full HTML page. | Say the target org is unreachable or expired. Do not paste the HTML. |
12
+ | `DEPLOY_TARGET_ORG_CONNECTION_FAILED` | Credential service or target unreachable, or "Target environment does not match the named credential". | Re-resolve the pair from the same `DevopsEnvironment` row (`list-orgs.md`). |
13
+ | `VALIDATION_REQUIRES_METADATA` | Validate run with no `changes`. | Add at least one metadata component. |
14
+ | `QUICK_DEPLOY_NOT_AVAILABLE` / `QUICK_DEPLOY_NOT_DEPLOYABLE` | No successful, still-valid validation for this target. | Re-run validate, then quick deploy the new `0Af` id. |
15
+ | `QUICK_DEPLOY_ALREADY_DONE` / `QUICK_DEPLOY_ALREADY_IN_PROGRESS` | The validation was already used or is running. | Check status of the earlier request instead. |
16
+ | `QUICK_DEPLOY_VALIDATE_ONLY_MUTUALLY_EXCLUSIVE` | Both `isCheckDeploy` and `isQuickDeploy` set. | Send only one. |
17
+ | `VALIDATE_QUICK_DEPLOY_FEATURE_NOT_ENABLED` | Validate/quick deploy kill switch is closed. | Use a plain deploy, or ask the platform team. |
18
+ | 400 with a Zod message | cliaas schema rule (see `request-body.md`): blank `fullName`/`type`/`operation`, bad `testOptions`, `changes` with quick deploy, bad `0Af` id. | Fix the named field and resubmit once. |
19
+ | `DEPLOY_UNKNOWN_ERROR` | Unexpected server error. | Report the message; do not retry blindly. |
20
+ | Status `FAILED` (or `DevopsRequestInfo` `ERROR`) | Deploy ran and failed. | Show the failed `step` and the reason: `errorDetail` (component and test failures, coverage shortfall) when present, else `activityMessage`. Details in `deploy.md`, Step 7. |
21
+ | Status `NOT_FOUND` | Unknown id, or older than ~2 days. | Confirm the id; resubmit only if the user asks. |
@@ -0,0 +1,37 @@
1
+ # Lifecycle Center org (Operation C)
2
+
3
+ The **Lifecycle Center org** is the Lifecycle Center hub that DX Inspector connects to. Its credential is named exactly `ALMDevOpsCenterHub` (case-sensitive). It is a separate connection from the deploy targets.
4
+
5
+ Three rules always hold:
6
+
7
+ 1. It is **never a deploy target**.
8
+ 2. It **never appears in a list** and is not mentioned while listing, whether or not it is connected. It is discussed only when the user asks about it by name.
9
+ 3. Its credential is **never used to deploy**.
10
+
11
+ ## Hub names
12
+
13
+ Make two normalized forms of the request, both lowercase. The **word form** turns hyphens, underscores, and other punctuation into single spaces and splits camel case ("LifecycleCenter" becomes "lifecycle center"). The **compact form** also drops every space. Then "Lifecycle Center org", "lifecycle-center", "lifecycle_center", "LifecycleCenter org", and "lifecyclecenter" all read the same. Work through these in order:
14
+
15
+ 1. **Never a hub name:** "Dev Hub" in any form (including "Dev Hub org"; it is the Salesforce Dev Hub), and plural or general phrases such as "DevOps Center orgs", "DevOps Center environments", or "orgs connected to DevOps Center". These are ordinary requests: route them by rules 2 to 5 in `SKILL.md`.
16
+ 2. **Certain:** the compact form contains `lifecyclecenter`, `lifecyclecentre`, `almdevopscenterhub`, or `devopscenterhub`, and the name is used as an org: it stands alone or is followed by "org", "hub", "connection", "credential", or "status". This covers "connect to lifecycle center", "is lifecyclecenter org connected", "Lifecycle Center hub", "life cycle center", and `ALMDevOpsCenterHub`. Treat the request as Operation C with no clarifying question, even with extra words around it. If the name is followed by another word that makes it a longer name ("LifecycleCenterService", "lifecycle center controller"), it is a component name, not the org.
17
+ 3. **Ambiguous:** the word form contains, as whole words, "hub org", "the hub", "doce hub", or "devops center org" (not followed by "s", "list", or letters that make a longer word, so "organizations" is not a match), or a standalone word "hub" that is not part of "dev hub", or any longer word that ends in "hub". Treat the request as Operation C only after the user confirms (see the first row below).
18
+
19
+ Run this check **before** the other routing rules.
20
+
21
+ ## What to do
22
+
23
+ | The request | Do |
24
+ |---|---|
25
+ | Connect, reconnect, repair, log in to, or authorize the Lifecycle Center org | 1. If the name is in the ambiguous group, ask: "Do you mean the Lifecycle Center org (the Lifecycle Center hub)?" On no, ask which org they mean and treat it as a normal org (Operation B). 2. On yes, or for a certain name, check the credential (`connect-org.md`, Step 0, hub mode). 3. **Configured:** reply only "The Lifecycle Center org is already configured." and stop. No reconnect offer, org details, or login link. 4. **Not configured, or half-created:** run `connect-org.md` Steps 1 to 5 in hub mode, using its sign-in layout (Step 2) and connected layout (Step 5). |
26
+ | Ask whether the Lifecycle Center org is connected or configured | Check the credential the same way. Configured: reply only "The Lifecycle Center org is already configured." Not configured: reply "The Lifecycle Center org is not configured. Want me to connect it?" |
27
+ | Deploy, validate, or quick deploy to the Lifecycle Center org | Make no API call. Ask: "Do you mean the Lifecycle Center org, or a different org?" A different org: ask which and continue at `deploy.md`, Step 2. The Lifecycle Center org: say a deploy cannot go there because it is the Lifecycle Center hub, not a deploy target, and offer to connect that org, or another one, as a regular deploy target (Operation B). Never deploy with the hub credential. |
28
+ | List connected orgs or environments | Never mention the Lifecycle Center org. Follow `list-orgs.md`. |
29
+ | Show the Lifecycle Center org in the list, or asks why it is not listed | Say it is the Lifecycle Center hub, kept separate from deploy targets, and is never part of the deploy list. If they want its status, check the credential and answer as above. |
30
+ | Remove or disconnect it | Say to use **Remove DevOps Center Connection** in DX Inspector. |
31
+
32
+ ## Notes for the connect flow
33
+
34
+ - Hub mode has no `DevopsEnvironment` row: it uses `setupNamedCredential` with the fixed name `ALMDevOpsCenterHub`.
35
+ - The user must sign in as someone with the **DevOps Center User** permission set in the Lifecycle Center org.
36
+ - A failed finish step clears the user's saved DX Inspector selections. Say so before a repair, and again if the finish fails.
37
+ - Never rename `ALMDevOpsCenterHub`. DX Inspector finds the credential by that exact name.
@@ -0,0 +1,99 @@
1
+ # List connected orgs (Operation A)
2
+
3
+ Shows the orgs that can receive a deploy: the **DX Inspector environments** stored in the source org. The same lookup resolves a target the user names (alias, URL, org id, or name) for Deploy and Connect.
4
+
5
+ The targets are the DX Inspector environments, **not** the orgs authorized on this machine. Never build the list from `sf org list` or local aliases: an org can be authorized locally yet not connected, and the reverse.
6
+
7
+ ## 1. Run the resolver
8
+
9
+ ```bash
10
+ bash scripts/resolve-environment.sh <source-org-alias> [identifier]
11
+ ```
12
+
13
+ `<source-org-alias>` is the alias the user named, else the default org from `sf org display --json`. Pass `identifier` exactly as the user gave it. It accepts:
14
+
15
+ | The user gives | Example |
16
+ |---|---|
17
+ | Environment name | `MySandbox` |
18
+ | Local `sf` alias | `my-sandbox` |
19
+ | Org URL (My Domain, setup, or lightning host) | `https://mydomain--mysandbox.sandbox.my.salesforce.com` |
20
+ | Org id (15 or 18 characters) | `00D5g000001AbCd` |
21
+
22
+ The script runs `SELECT Id, Name, NamedCredential, OrgType, IsExpired, OrgUrl, OrgIdentifier FROM DevopsEnvironment ORDER BY Name` and prints one JSON document:
23
+
24
+ - `source`: `alias`, `username`, `orgId`, `instanceUrl` of the source org.
25
+ - `environments[]`: each row with `state` of `selectable`, `expired`, or `unauthenticated`.
26
+ - With an identifier: `matchStatus`, `matches[]`, `match`, and `sourceMatched`.
27
+
28
+ The Lifecycle Center org's own row is never returned (see `hub-org.md`).
29
+
30
+ ## 2. If the script fails
31
+
32
+ It exits `1` and prints one reason on stderr. Say only what the reason supports:
33
+
34
+ | Script says | Tell the user | Then |
35
+ |---|---|---|
36
+ | `DevOps Center not enabled or no access` | "DX Inspector isn't enabled for this org or user, so there are no environments to list or deploy to. An admin needs to enable it." | Stop. Do not run another query, do not fall back to `sf org list`, and do not offer a deploy. |
37
+ | `query failed, not an enablement problem` | The error in plain words (often an expired session). | Tell them to reconnect the source org (in Builder Central use the Reconnect prompt; running the CLI locally, `sf org login web --alias <alias>`), then try again. Never say DX Inspector is disabled. |
38
+ | `unexpected query result` | The environment lookup returned something unreadable. | Report that and stop. Do not guess a list. |
39
+ | `cannot read source org` | The source org is not authorized on this machine. | Ask which org they build in. If it is not authorized, reconnect it the same way as above, then start again. |
40
+ | `jq required` | `jq` is missing from the environment. | Report it and stop. |
41
+
42
+ ## 3. Read the match (only when an identifier was passed)
43
+
44
+ **First check `sourceMatched`.** If it is `true`, the identifier is the source org (this also covers a row with `isSource: true`): say it cannot be its own deploy target and ask for a different org. Ignore `matchStatus` in that case.
45
+
46
+ | `matchStatus` | Do |
47
+ |---|---|
48
+ | `matched` and `state` is `selectable` | Use that row. Confirm it back with name, URL, and org id. |
49
+ | `matched` and `state` is `unauthenticated` | Connect never finished. Offer to finish it (`connect-org.md`, repair path). |
50
+ | `matched` and `state` is `expired` | Retired or replaced by an environment swap. Not deployable and not repairable: pick another or connect a new one. |
51
+ | `ambiguous` | Show `matches` and ask which one. |
52
+ | `not-connected` | Not in DX Inspector. Offer to connect it (`connect-org.md`). If the identifier was an alias, URL, or org id, carry it into the connect flow so only the missing inputs are asked. |
53
+ | `none-requested` | No target named: show the list (section 4). |
54
+
55
+ When the identifier is an alias that is not connected, `sf org display --target-org <alias> --json` gives its URL, which sets the `orgType` by the host table in `connect-org.md`. Use the alias as the suggested environment name (letters only) and still ask the user to confirm the type if the host does not decide it.
56
+
57
+ ## 4. Show the list
58
+
59
+ A numbered table with the columns `#`, `Name`, `Type`, `URL`, `Org id`, `Status`:
60
+
61
+ - One row per entry in `environments[]`, then the **source org** as the last row. A row with `isSource: true` is the source org: show it once, as that last row, never as a selectable environment.
62
+ - Always include the `Org id` column. Write `—` for any empty cell.
63
+ - The source org's `Type` is always `—`: `source` carries no org type, so never infer one.
64
+ - Never show row ids or credential names.
65
+ - If several rows share a name, tell them apart by URL.
66
+
67
+ | Row | Status column |
68
+ |---|---|
69
+ | `state = selectable` | Selectable |
70
+ | `state = expired` | Retired or replaced |
71
+ | `state = unauthenticated` | Not authenticated |
72
+ | The source org | `<alias> (current org, source)`. It is the source, so it is **not** a deploy target. If the user picks it, explain that and ask for another. |
73
+
74
+ Example:
75
+
76
+ | # | Name | Type | URL | Org id | Status |
77
+ |---|---|---|---|---|---|
78
+ | 1 | MySandbox | Sandbox | mydomain--mysandbox.sandbox.my.salesforce.com | 00D5g000001AbCd | Selectable |
79
+ | 2 | OldSandbox | Sandbox | mydomain--old.sandbox.my.salesforce.com | — | Retired or replaced |
80
+ | 3 | my-alias *(current org, source)* | — | mydomain.my.salesforce.com | 00D5g000001XyZw | — |
81
+
82
+ End the list with exactly this sentence and nothing after it:
83
+
84
+ > Want to deploy to an org that isn't listed? I can help you connect a new one.
85
+
86
+ Do not add any other offer, summary, or follow-up.
87
+
88
+ | Situation | Reply instead of the table |
89
+ |---|---|
90
+ | No environments | "No environments are connected to DX Inspector in `<source-org-alias>` yet." Then the closing sentence. |
91
+ | Every environment is expired or unauthenticated | "None of the connected environments can accept a deploy right now: <names and reasons>." Then the closing sentence. |
92
+
93
+ ## 5. Current-org details
94
+
95
+ When the user asks about the current org itself, answer from `source`: alias, username, URL, and org id. Add nothing else.
96
+
97
+ ## Carrying a chosen row forward
98
+
99
+ For Deploy, take `id` → `targetEnvironmentId` and `namedCredential` → `namedCredential` from the **same** row. The API rejects a mismatched pair. Never deploy to a row whose `state` is not `selectable`.
@@ -0,0 +1,75 @@
1
+ # Simple Deploy Request Body
2
+
3
+ > **Scope:** the skill sends metadata only. `datachanges` is documented here for reference but is deferred; do not send it from this skill yet.
4
+
5
+ Source of truth: `ISimpleDeployResource` / `SimpleDeployInputRepresentation` (Connect) and the cliaas `validator/schema.ts`. Connect forwards `requestBody` to cliaas unchanged, so cliaas validation errors surface as a 400.
6
+
7
+ ## Envelope (Connect input)
8
+
9
+ | Field | Type | Notes |
10
+ |---|---|---|
11
+ | `namedCredential` | string | `DevopsEnvironment.NamedCredential`. Must be bound to `targetEnvironmentId`. |
12
+ | `targetEnvironmentId` | string | `DevopsEnvironment.Id`. Scopes validate and quick-deploy tracking. |
13
+ | `requestBody` | object | The deploy payload below. |
14
+ | `headerTargetInstanceURL`, `headerTargetAccessToken` | string | Raw-token alternative to `namedCredential`. **Do not use from this skill.** |
15
+ | `components` | array | Deprecated and ignored. Do not send. |
16
+
17
+ ## `requestBody`
18
+
19
+ | Field | Type | Notes |
20
+ |---|---|---|
21
+ | `changes[]` | `{fullName, type, operation}` | All three required and non-blank. `operation` is `add`, `modify`, or `delete`. Map anything else (dependency, manual, manifest) to `modify`. |
22
+ | `datachanges` | object (deferred) | `{ "objectsList": { "<ObjectApiName>": { "fields": [{"name": "..."}], ... } } }`. Each object needs at least one field. |
23
+ | `isCheckDeploy` | boolean | `true` = validate only (alias `checkOnly`). Default `false`. |
24
+ | `testOptions` | `{level, tests[]}` | `level`: `NoTestRun`, `RunSpecifiedTests`, `RunLocalTests` (default), `RunAllTestsInOrg`. `tests` must list the tests for `RunSpecifiedTests`; send an empty array otherwise. Legacy `testLevel` + `tests` also accepted. |
25
+ | `isQuickDeploy` | boolean | `true` = deploy a prior validation. Default `false`. |
26
+ | `checkDeployIdentifier` | string | `0Af` id (15 or 18 chars). Required with `isQuickDeploy`. |
27
+ | `metadataLookups` | map<string,string> | Optional. |
28
+
29
+ ### `datachanges.objectsList.<Object>` entry
30
+
31
+ `fields[]` (required, `{name, ...}`), plus optional `relatedObjects`, `duplicateCheck` (field names for dedupe), `recordIds`, `filters`, `formats`, `numRecords`, `externalIdField` (upsert/match key, for example `External_Account_Id__c`). Unknown keys pass through. Remove duplicate fields by `name` before sending.
32
+
33
+ ## Mode matrix
34
+
35
+ | Mode | `changes` | `datachanges` | `testOptions` | Other |
36
+ |---|---|---|---|---|
37
+ | deploy | required (this skill) | deferred | optional | The API itself allows data-only; this skill sends metadata. |
38
+ | validate | **required** | ignored (stripped, remembered for quick deploy) | optional | `isCheckDeploy: true`. |
39
+ | quickDeploy | **forbidden** | forbidden | **forbidden** | `isQuickDeploy: true` + `checkDeployIdentifier`. |
40
+
41
+ Test level default: Production targets need tests to complete a deploy, so use `RunLocalTests`; sandbox and scratch-like targets use `NoTestRun`. An explicit user choice always wins.
42
+
43
+ ## Examples
44
+
45
+ Metadata deploy:
46
+
47
+ ```json
48
+ {
49
+ "namedCredential": "QA_Sandbox_1PJSB...",
50
+ "targetEnvironmentId": "1QeSB0000004T3R0AU",
51
+ "requestBody": {
52
+ "changes": [
53
+ {"fullName": "AccountService", "type": "ApexClass", "operation": "modify"},
54
+ {"fullName": "Account.Tier__c", "type": "CustomField", "operation": "add"}
55
+ ],
56
+ "testOptions": {"level": "NoTestRun", "tests": []}
57
+ }
58
+ }
59
+ ```
60
+
61
+ Validate only:
62
+
63
+ ```json
64
+ { "namedCredential": "Prod", "targetEnvironmentId": "1Qe...",
65
+ "requestBody": { "isCheckDeploy": true,
66
+ "changes": [{"fullName": "AccountService", "type": "ApexClass", "operation": "modify"}],
67
+ "testOptions": {"level": "RunLocalTests", "tests": []} } }
68
+ ```
69
+
70
+ Quick deploy:
71
+
72
+ ```json
73
+ { "namedCredential": "Prod", "targetEnvironmentId": "1Qe...",
74
+ "requestBody": { "isQuickDeploy": true, "checkDeployIdentifier": "0AfXXXXXXXXXXXXXXX" } }
75
+ ```
@@ -0,0 +1,31 @@
1
+ # Troubleshooting: DX Inspector org connection
2
+
3
+ Explain each problem to the user in plain language. Never echo raw JSON or tokens. Do not retry a failing call in a loop.
4
+
5
+ | Symptom | Likely cause | Fix |
6
+ |---|---|---|
7
+ | Deploy POST returns `TARGET_ORG_ENVIRONMENT_CREATION_FAILED:Invalid org type` | `orgType` is not valid | Use `PRODUCTION`, `SANDBOX`, or `ORGFARM`. `envName` must contain at least one letter. |
8
+ | Hub POST returns `400 namedCredentialType is not supported.` | Typo or wrong case in the type | Use one of `ORG_TO_ORG`, `ORG_TO_SANDBOX_ORG`, `ORG_TO_ORGFARM_ORG`. |
9
+ | POST returns 403 or "insufficient access" | User lacks DevOps API access in the dev org | Assign the DevOps Center permissions in the dev org. The user needs *Customize Application*. |
10
+ | POST succeeds but `redirectUrl` is empty | Setup or URL generation failed on the server; the errors were swallowed | **Hub:** check status; if the EC or NC exists, re-POST once. **Deploy:** the row already exists, so do not POST again (it would insert a second row). Get a fresh link with `GET /connect/devops/environment/<id>`, or delete the row (below) and start over. If it is still empty, tell the user setup failed server-side and stop. |
11
+ | Login page is `login.salesforce.com` but the target is a sandbox, or the reverse | Wrong `namedCredentialType` | **Deploy:** `DELETE /connect/devops/environment/<id>` (removes the NC, EC and row), then POST again with the correct `orgType`. **Hub:** delete the NC and EC in Setup, then re-run with the correct type. |
12
+ | OAuth page shows an error or loops back | The browser has no dev-org session, or the user denied access | Ask the user to sign in to the dev org in that browser, then give a fresh link (deploy: `GET /connect/devops/environment/<id>`; hub: re-run Step 1). Do not run `sf org open`. |
13
+ | EC principal still `NotConfigured` after the user says they're done | The login didn't finish, or a different Salesforce user completed it | The token is per user, so the same dev-org user must do the login. Give a fresh link (deploy: `GET /connect/devops/environment/<id>`; hub: re-run Step 1). |
14
+ | User can't use the credential | Missing **Access DevOps Center Named Credentials** (`AccessDevOpsCenterNamedCredentials`) | Assign that permission set to the user in the dev org. |
15
+ | Hub PATCH returns `404 Named credential does not exist` | POST never created the NC, or the name differs | Re-check the exact name (case-sensitive) and re-run Step 1. |
16
+ | Hub PATCH returns `DOCE_CONNECTION_UPDATE_FAILED` | The `userinfo` callout failed, usually because the OAuth token is missing or expired | Confirm the principal is `Configured`, then re-run Steps 1–4. The user's saved DX Inspector selections were cleared, so they must pick them again. |
17
+ | Deploy environment PATCH returns `TARGET_ORG_AUTHENTICATION_STATUS_CHECK_FAILED` (401/403), `TARGET_ORG_AUTHENTICATION_TIMEOUT` (408/504), or `TARGET_ORG_UNKNOWN_ERROR` (includes "Environment record not found" and "does not have a named credential") | The login is missing or expired, the callout timed out, or the id is wrong | Confirm the principal is `Configured` and the `id` is the POST's. Give a fresh link and PATCH once. Do not loop. |
18
+ | PATCH `calloutUrl` or `orgUrl` is still a login host | Unexpected `userinfo` response | Stop and report. Do not hand the NC to deploy. |
19
+ | Hub mode: `doceConnectionType` fails | The hub user lacks **DevOps Center User** | Assign the permission set in the Lifecycle Center org, then re-run the check. |
20
+ | Deploy: `TARGET_ORG_SESSION_EXPIRED` or `Bad_OAuth_Token` | The target refresh token was revoked or expired. A stale token is not visible in the environment list; it only shows at deploy time | Repair: fresh link from `GET /connect/devops/environment/<id>`, user logs in, PATCH once. |
21
+ | Environment shows `unauthenticated` (empty `OrgUrl` or `OrgIdentifier`) | The connect never finished: POST ran but login or PATCH did not | Same repair: fresh link, login, PATCH once. Do not POST again. |
22
+ | `Doce Hub Environment` row is missing from the environment list | By design: the resolver drops the hub row (credential `ALMDevOpsCenterHub`, empty `OrgUrl`) because it is the Lifecycle Center hub, not a deploy target | Nothing to repair. To deploy to that org, connect it as a deploy target with the Environment API. |
23
+ | Environment shows `expired` | `IsExpired` is set when an environment is retired or replaced by a swap; it is not an OAuth expiry | Do not repair it. Pick another environment or connect a new one. |
24
+
25
+ ## Starting over
26
+
27
+ **Deploy target:** `DELETE /connect/devops/environment/<id>` (confirm with the user first). It removes the Named Credential, External Credential, and the `DevopsEnvironment` row. Do not delete credentials in Setup: that leaves an orphaned row that still looks selectable.
28
+
29
+ **Hub:** delete both the **Named Credential** and the **External Credential** named `ALMDevOpsCenterHub` in Setup → Named Credentials (and the External Credentials tab), and run the skill again.
30
+
31
+ For the hub, the **Remove DevOps Center Connection** option in DX Inspector does the same teardown.
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env bash
2
+ # Poll a Simple Deploy request until it reaches a terminal state, or check once.
3
+ # Read-only: issues only GET calls and SOQL queries.
4
+ #
5
+ # Two sources are read on every check, and a terminal state from EITHER one ends the poll:
6
+ # 1. the Simple Deploy status endpoint (live `step`, `metadata.deployId`)
7
+ # 2. DevopsRequestInfo, queried by RequestToken (the same record commit polling uses;
8
+ # Status is SUCCESS or ERROR, with Message / ErrorDetails / RequestCompletionDate)
9
+ #
10
+ # Usage:
11
+ # poll-deploy-status.sh --once <requestId> <source-org-alias>
12
+ # poll-deploy-status.sh <requestId> <source-org-alias> [interval-seconds] [timeout-seconds]
13
+ # Env: DETAIL_WAIT=<seconds> how long to wait for the failure detail (default 20; 4 with --once)
14
+ #
15
+ # On failure the component-level errors are read from the DEPLOYMENT_COMPLETED
16
+ # DevopsActivityLog row of the request. Core's DeployDetailFetchHandler fills it in
17
+ # asynchronously (it calls the target org's Metadata API through the deploy's named
18
+ # credential), so no login to the target org is needed here. The script waits briefly
19
+ # for it, and stops waiting early if Core marks it errorDetailUnavailable.
20
+ #
21
+ # Prints one JSON object on stdout when the check ends:
22
+ # outcome (SUCCEEDED|FAILED|NOT_FOUND|IN_PROGRESS|TIMEOUT), status, step, deployId, message,
23
+ # errorMessage, errorDetails, deployJobLogURL, completedAt, decidedBy,
24
+ # activityMessage, errorDetail (null when unavailable), errorDetailUnavailable, response
25
+ # Progress lines go to stderr.
26
+ #
27
+ # NOT_FOUND from the endpoint is tolerated for the first few polls: right after submit
28
+ # the status cache entry may not exist yet. A DevopsRequestInfo row overrides it.
29
+ #
30
+ # Exit codes:
31
+ # 0 deploy succeeded
32
+ # 1 usage error, missing dependency, or both status calls failed
33
+ # 2 deploy failed, or NOT_FOUND after the grace polls
34
+ # 3 timeout reached before a terminal state (poll mode); outcome TIMEOUT carries requestId,
35
+ # step and deployId (when known) so the user can be told what to check
36
+ # 4 still processing (--once mode only)
37
+ set -euo pipefail
38
+
39
+ ONCE=0
40
+ ARGS=()
41
+ while [ $# -gt 0 ]; do
42
+ case "$1" in
43
+ --once) ONCE=1; shift ;;
44
+ *) ARGS+=("$1"); shift ;;
45
+ esac
46
+ done
47
+
48
+ REQUEST_ID="${ARGS[0]:-}"
49
+ SOURCE_ORG="${ARGS[1]:-}"
50
+ INTERVAL="${ARGS[2]:-10}"
51
+ TIMEOUT="${ARGS[3]:-100}"
52
+ API_VERSION="${SF_API_VERSION:-v67.0}"
53
+ API_VERSION="v${API_VERSION#v}" # accept 67.0 or v67.0
54
+ NOT_FOUND_GRACE=3
55
+ DETAIL_WAIT="${DETAIL_WAIT:-20}"
56
+ [ "$ONCE" -eq 1 ] && DETAIL_WAIT="${DETAIL_WAIT_ONCE:-4}"
57
+
58
+ usage() {
59
+ echo "Usage: poll-deploy-status.sh [--once] <requestId> <source-org-alias> [interval-seconds] [timeout-seconds]" >&2
60
+ exit 1
61
+ }
62
+
63
+ [ -n "$REQUEST_ID" ] && [ -n "$SOURCE_ORG" ] || usage
64
+ case "$REQUEST_ID" in *[!A-Za-z0-9-]*) echo "requestId may only contain letters, digits and hyphens" >&2; exit 1 ;; esac
65
+ case "$INTERVAL$TIMEOUT" in *[!0-9]*) echo "interval and timeout must be whole numbers" >&2; exit 1 ;; esac
66
+ [ "$INTERVAL" -ge 1 ] || { echo "interval must be >= 1" >&2; exit 1; }
67
+ for dep in sf jq; do
68
+ command -v "$dep" >/dev/null 2>&1 || { echo "Missing dependency: $dep" >&2; exit 1; }
69
+ done
70
+
71
+ PATH_URL="/services/data/${API_VERSION}/connect/devops/simple-deploy/deploy?requestID=${REQUEST_ID}"
72
+ SOQL="SELECT Id, Status, Message, ErrorDetails, RequestCompletionDate FROM DevopsRequestInfo WHERE RequestToken = '${REQUEST_ID}'"
73
+ START=$(date +%s)
74
+ NOT_FOUND_COUNT=0
75
+ POLLS=0
76
+
77
+ sfq() { env FORCE_COLOR=0 SF_CLI_DISABLE_AUTOUPDATE=true "$@"; }
78
+
79
+ # Failure detail from Core: DEPLOYMENT_COMPLETED DevopsActivityLog.Description (JSON) holds the flat
80
+ # errorMessage plus, once Core's async handler has run, errorDetail {componentFailures, testFailures,
81
+ # errorMessage, ...} or errorDetailUnavailable. Waits up to DETAIL_WAIT seconds for either marker.
82
+ ACT_MSG=""; ACT_DETAIL=null; ACT_UNAVAIL=false
83
+ fetch_failure_detail() {
84
+ [ -n "$DRI_ID" ] || return 0
85
+ local waited=0 q desc
86
+ # No target-org deploy job (failed before the target, e.g. RETRIEVE_METADATA): Core will never add
87
+ # errorDetail, and the flat message already says why, so do not wait.
88
+ [ -n "$DEPLOY_ID" ] || DETAIL_WAIT=0
89
+ while true; do
90
+ q=$(sfq sf data query --target-org "$SOURCE_ORG" --json -q \
91
+ "SELECT Description FROM DevopsActivityLog WHERE ActivityType = 'DEPLOYMENT_COMPLETED' AND DevopsRequestInfoId = '${DRI_ID}' LIMIT 1" 2>/dev/null) || q=""
92
+ desc=$(printf '%s' "$q" | jq -r '.result.records[0].Description // empty' 2>/dev/null || true)
93
+ if [ -n "$desc" ] && printf '%s' "$desc" | jq -e . >/dev/null 2>&1; then
94
+ ACT_MSG=$(printf '%s' "$desc" | jq -r '.errorMessage // empty')
95
+ ACT_DETAIL=$(printf '%s' "$desc" | jq -c '.errorDetail // null')
96
+ ACT_UNAVAIL=$(printf '%s' "$desc" | jq -r 'if .errorDetailUnavailable then "true" else "false" end')
97
+ if [ "$ACT_DETAIL" != "null" ] || [ "$ACT_UNAVAIL" = "true" ]; then return 0; fi
98
+ fi
99
+ [ "$waited" -ge "$DETAIL_WAIT" ] && return 0
100
+ echo "waiting for Core to attach the failure detail (${waited}s/${DETAIL_WAIT}s)" >&2
101
+ sleep 2; waited=$((waited + 2))
102
+ done
103
+ }
104
+
105
+ # Emit the final JSON and exit. $1 outcome, $2 exit code, $3 decidedBy.
106
+ finish() {
107
+ [ "$1" = "FAILED" ] && fetch_failure_detail
108
+ jq -n \
109
+ --arg outcome "$1" --arg decidedBy "$3" --arg requestId "$REQUEST_ID" \
110
+ --arg status "$STATUS" --arg step "$STEP" --arg deployId "$DEPLOY_ID" \
111
+ --arg errorMessage "$ERR_MSG" --arg logUrl "$LOG_URL" \
112
+ --arg message "$DRI_MSG" --arg errorDetails "$DRI_ERR" --arg completedAt "$DRI_DONE" \
113
+ --arg activityMessage "$ACT_MSG" --argjson errorDetail "$ACT_DETAIL" --argjson unavailable "$ACT_UNAVAIL" \
114
+ --argjson response "${RESP_JSON:-null}" \
115
+ '{requestId:$requestId, outcome:$outcome, status:$status, step:$step, deployId:$deployId,
116
+ message:$message, errorMessage:$errorMessage, errorDetails:$errorDetails,
117
+ activityMessage:$activityMessage, errorDetail:$errorDetail, errorDetailUnavailable:$unavailable,
118
+ deployJobLogURL:$logUrl, completedAt:$completedAt, decidedBy:$decidedBy, response:$response}'
119
+ exit "$2"
120
+ }
121
+
122
+ while true; do
123
+ # --- source 1: Simple Deploy status endpoint
124
+ EP_OK=1; RESP_JSON=null
125
+ ERRF=$(mktemp)
126
+ if OUT=$(sfq sf api request rest "$PATH_URL" --method GET --target-org "$SOURCE_ORG" 2>"$ERRF"); then
127
+ RESP_JSON=$(printf '%s' "$OUT" | jq -c '.response // null' 2>/dev/null || echo null)
128
+ else
129
+ EP_OK=0
130
+ echo "Status endpoint call failed: $(printf '%s%s' "$OUT" "$(cat "$ERRF")" | head -c 300)" >&2
131
+ fi
132
+ rm -f "$ERRF"
133
+
134
+ jqr() { printf '%s' "$RESP_JSON" | jq -r "$1" 2>/dev/null || true; }
135
+ STATUS=$(jqr '(.status // empty) | ascii_upcase')
136
+ STEP=$(jqr '.step // empty')
137
+ DEPLOY_ID=$(jqr '.metadata.deployId // empty')
138
+ ERR_MSG=$(jqr '.errorMessage // empty')
139
+ LOG_URL=$(jqr '.metadata.deployJobLogURL // empty')
140
+ LOG_URL="${LOG_URL//\{version\}/${API_VERSION#v}}" # the API returns a literal v{version}
141
+ META=$(jqr '(.metadata.status // empty) | ascii_upcase')
142
+
143
+ # --- source 2: DevopsRequestInfo (same record commit polling reads)
144
+ DRI_ID=""; DRI_STATUS=""; DRI_MSG=""; DRI_ERR=""; DRI_DONE=""
145
+ if DRI=$(sfq sf data query --target-org "$SOURCE_ORG" --json -q "$SOQL" 2>/dev/null); then
146
+ DRI_ID=$(printf '%s' "$DRI" | jq -r '.result.records[0].Id // empty' 2>/dev/null || true)
147
+ DRI_STATUS=$(printf '%s' "$DRI" | jq -r '(.result.records[0].Status // empty) | ascii_upcase' 2>/dev/null || true)
148
+ DRI_MSG=$(printf '%s' "$DRI" | jq -r '.result.records[0].Message // empty' 2>/dev/null || true)
149
+ DRI_ERR=$(printf '%s' "$DRI" | jq -r '.result.records[0].ErrorDetails // empty' 2>/dev/null || true)
150
+ DRI_DONE=$(printf '%s' "$DRI" | jq -r '.result.records[0].RequestCompletionDate // empty' 2>/dev/null || true)
151
+ fi
152
+
153
+ if [ "$EP_OK" -eq 0 ] && [ -z "$DRI_STATUS" ]; then
154
+ echo "Both status sources failed for requestId=${REQUEST_ID}" >&2
155
+ exit 1
156
+ fi
157
+
158
+ # --- decide: a terminal state from either source wins
159
+ case "$DRI_STATUS" in
160
+ SUCCESS) STATUS=SUCCEEDED; finish SUCCEEDED 0 request-info ;;
161
+ ERROR) STATUS=FAILED; finish FAILED 2 request-info ;;
162
+ esac
163
+ case "$META" in
164
+ SUCCEEDED|SUCCEEDEDPARTIAL|SUCCESS) [ "$STATUS" = "FAILED" ] || STATUS=SUCCEEDED ;;
165
+ FAILED|CANCELED|CANCELLED) STATUS=FAILED ;;
166
+ esac
167
+ case "$STATUS" in
168
+ SUCCEEDED|COMPLETED|SUCCESS|SUCCEEDED_PARTIAL) finish SUCCEEDED 0 endpoint ;;
169
+ FAILED|ERROR|CANCELED|CANCELLED) finish FAILED 2 endpoint ;;
170
+ NOT_FOUND)
171
+ NOT_FOUND_COUNT=$((NOT_FOUND_COUNT + 1))
172
+ if [ "$ONCE" -eq 1 ] || [ "$NOT_FOUND_COUNT" -gt "$NOT_FOUND_GRACE" ]; then
173
+ finish NOT_FOUND 2 endpoint
174
+ fi ;;
175
+ QUEUED|STARTED|IN_PROGRESS|INPROGRESS|PENDING|CANCELING|"") ;;
176
+ *)
177
+ echo "Unexpected status '${STATUS}': ${RESP_JSON}" | head -c 500 >&2; echo >&2
178
+ exit 1 ;;
179
+ esac
180
+
181
+ if [ "$ONCE" -eq 1 ]; then
182
+ finish IN_PROGRESS 4 endpoint
183
+ fi
184
+
185
+ ELAPSED=$(( $(date +%s) - START ))
186
+ if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
187
+ echo "Timed out after ${TIMEOUT}s. requestId=${REQUEST_ID} status=${STATUS:-unknown} step=${STEP:-n/a}" >&2
188
+ finish TIMEOUT 3 none
189
+ fi
190
+ REMAINING=$(( TIMEOUT - ELAPSED ))
191
+ POLLS=$((POLLS + 1))
192
+ # Quick early checks (fast deploys finish in seconds), then settle to INTERVAL.
193
+ STEP_SLEEP=$INTERVAL
194
+ if [ "$POLLS" -le 3 ] && [ "$INTERVAL" -gt 3 ]; then STEP_SLEEP=3
195
+ elif [ "$POLLS" -le 6 ] && [ "$INTERVAL" -gt 5 ]; then STEP_SLEEP=5
196
+ fi
197
+ SLEEP=$(( STEP_SLEEP < REMAINING ? STEP_SLEEP : REMAINING ))
198
+ echo "status=${STATUS:-pending} step=${STEP:-n/a} request-info=${DRI_STATUS:-none}; next check in ${SLEEP}s" >&2
199
+ sleep "$SLEEP"
200
+ done
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # Lists the DevOps Center environments of a source org and resolves an optional
3
+ # identifier (alias, username, My Domain URL, 15/18-char org id, or environment name)
4
+ # to one of them. Read-only. Prints one JSON document on stdout.
5
+ #
6
+ # Usage: resolve-environment.sh <source-org-alias> [identifier] [--rows-file <file>]
7
+ # --rows-file read the DevopsEnvironment query result JSON from a file (tests)
8
+ #
9
+ # Exit codes: 0 ok (check .match / .matchStatus), 1 usage or call error.
10
+ set -uo pipefail
11
+
12
+ command -v jq >/dev/null || { echo "jq required" >&2; exit 1; }
13
+ SRC="${1:-}"
14
+ [ -n "$SRC" ] || { echo "usage: resolve-environment.sh <source-org-alias> [identifier] [--rows-file <file>]" >&2; exit 1; }
15
+ shift
16
+ IDENT=""; ROWS_FILE=""
17
+ while [ $# -gt 0 ]; do
18
+ case "$1" in
19
+ --rows-file) [ $# -ge 2 ] || { echo "--rows-file needs a value" >&2; exit 1; }; ROWS_FILE="$2"; shift 2 ;;
20
+ *) IDENT="$1"; shift ;;
21
+ esac
22
+ done
23
+
24
+ sfj() { env FORCE_COLOR=0 SF_CLI_DISABLE_AUTOUPDATE=true "$@" 2>/dev/null; }
25
+
26
+ # Source org (the org the user built in).
27
+ src_json=$(sfj sf org display --target-org "$SRC" --json) \
28
+ || { echo "cannot read source org '$SRC'" >&2; exit 1; }
29
+ source_obj=$(printf '%s' "$src_json" | jq -c --arg a "$SRC" \
30
+ '{alias:$a, username:.result.username, orgId:.result.id, instanceUrl:.result.instanceUrl}')
31
+
32
+ # DevopsEnvironment rows.
33
+ if [ -n "$ROWS_FILE" ]; then
34
+ rows_json=$(cat "$ROWS_FILE")
35
+ else
36
+ rows_json=$(sfj sf data query --target-org "$SRC" --json --query \
37
+ "SELECT Id, Name, NamedCredential, OrgType, IsExpired, OrgUrl, OrgIdentifier FROM DevopsEnvironment ORDER BY Name") \
38
+ || {
39
+ # sf prints its JSON error on stdout; tell "not enabled" apart from an expired session or other failure.
40
+ qmsg=$(printf '%s' "$rows_json" | jq -r '[.name, .message] | map(select(. != null)) | join(": ")' 2>/dev/null)
41
+ case "$qmsg" in
42
+ *INVALID_TYPE*|*"sObject type"*|*"not supported"*|*INSUFFICIENT_ACCESS*)
43
+ echo "DevOps Center not enabled or no access: DevopsEnvironment is unavailable ($qmsg)" >&2 ;;
44
+ *) echo "DevopsEnvironment query failed, not an enablement problem: ${qmsg:-unknown error}" >&2 ;;
45
+ esac
46
+ exit 1
47
+ }
48
+ fi
49
+ printf '%s' "$rows_json" | jq -e '.result.records' >/dev/null 2>&1 \
50
+ || { echo "unexpected query result" >&2; exit 1; }
51
+
52
+ # Identifier -> comparable keys: org id (first 15), host, and a lowercase name.
53
+ # Local alias or username: expand through `sf org display` to its org id and host.
54
+ id15=""; host=""; name=""
55
+ if [ -n "$IDENT" ]; then
56
+ if printf '%s' "$IDENT" | grep -Eq '^00D[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$'; then
57
+ id15="${IDENT:0:15}"
58
+ elif printf '%s' "$IDENT" | grep -Eiq '^https?://' \
59
+ || printf '%s' "$IDENT" | grep -Eiq '^[A-Za-z0-9.-]+\.(salesforce\.com|salesforce-setup\.com|force\.com|pc-rnd\.salesforce\.com)(/.*)?$'; then
60
+ host=$(printf '%s' "$IDENT" | sed -E 's#^[Hh][Tt][Tt][Pp][Ss]?://##; s#[/?].*$##' | tr 'A-Z' 'a-z')
61
+ else
62
+ # Environment name, alias, or username. An authorized alias also yields an org id and host.
63
+ name=$(printf '%s' "$IDENT" | tr 'A-Z' 'a-z')
64
+ exp=$(sfj sf org display --target-org "$IDENT" --json) && {
65
+ id15=$(printf '%s' "$exp" | jq -r '.result.id // empty | .[0:15]')
66
+ host=$(printf '%s' "$exp" | jq -r '.result.instanceUrl // empty' | sed -E 's#^https?://##')
67
+ # A resolved org is matched by identity only: an environment that merely shares its name is another org.
68
+ [ -n "$id15" ] && name=""
69
+ }
70
+ fi
71
+ fi
72
+
73
+ jq -n \
74
+ --argjson source "$source_obj" \
75
+ --argjson rows "$rows_json" \
76
+ --arg ident "$IDENT" --arg id15 "$id15" --arg host "$host" --arg name "$name" '
77
+ # setup and lightning hosts point at the same org as the My Domain host
78
+ def norm: ascii_downcase | sub("^https?://"; "") | sub("[/?].*$"; "")
79
+ | sub("\\.salesforce-setup\\.com$"; ".salesforce.com")
80
+ | sub("\\.lightning\\.force\\.com$"; ".my.salesforce.com");
81
+ # expired = retired or replaced by an environment swap (not an OAuth expiry).
82
+ # unauthenticated = connect never finished: the PATCH fills OrgUrl and OrgIdentifier.
83
+ # The DevOps Center hub row is created with only the fixed hub NC (no OrgUrl) and is the control
84
+ # plane, not a deploy target, so it is dropped before listing or matching (also by its fixed
85
+ # name while it has no OrgUrl, in case its credential was removed).
86
+ # NamedCredential is plain text in the API; strip an <a> wrapper in case a UI layer added one.
87
+ def ncname: ((.NamedCredential // "") | gsub("<[^>]*>"; "") | gsub("^\\s+|\\s+$"; ""));
88
+ def state: if (.IsExpired == true) then "expired"
89
+ elif ((ncname == "") or (.OrgUrl // "") == "" or (.OrgIdentifier // "") == "") then "unauthenticated"
90
+ else "selectable" end;
91
+ [ ($rows.result.records // [])[] | select(ncname != "ALMDevOpsCenterHub" and ((.Name // "") != "Doce Hub Environment" or (.OrgUrl // "") != "")) ] as $recs
92
+ | ($host | if . == "" then "" else norm end) as $h
93
+ | [ $recs[] | {
94
+ id: .Id, name: .Name, orgType: .OrgType,
95
+ orgUrl: (.OrgUrl // null),
96
+ orgId: (.OrgIdentifier // null),
97
+ namedCredential: (ncname | if . == "" then null else . end),
98
+ state: state,
99
+ isSource: ((.OrgIdentifier // "")[0:15] == ($source.orgId // "")[0:15] and (.OrgIdentifier // "") != "")
100
+ } ] as $envs
101
+ | ( if $ident == "" then []
102
+ else [ $envs[] | select(
103
+ ($id15 != "" and ((.orgId // "")[0:15] == $id15))
104
+ or ($h != "" and ((.orgUrl // "") != "") and ((.orgUrl | norm) == $h))
105
+ or ($name != "" and ((.name | ascii_downcase) == $name))) ]
106
+ end ) as $m
107
+ | { source: $source,
108
+ environments: $envs,
109
+ identifier: $ident,
110
+ matchStatus: ( if $ident == "" then "none-requested"
111
+ elif ($m | length) == 0 then "not-connected"
112
+ elif ($m | length) > 1 then "ambiguous"
113
+ else "matched" end ),
114
+ matches: $m,
115
+ match: (if ($m | length) == 1 then $m[0] else null end),
116
+ sourceMatched: ( $ident != "" and
117
+ ( ($id15 != "" and $id15 == ($source.orgId // "")[0:15])
118
+ or ($h != "" and (($source.instanceUrl // "") | norm) == $h)
119
+ or ($name != "" and $name == ($source.alias | ascii_downcase))
120
+ or (($m | length) == 1 and $m[0].isSource) ) ) }'
@@ -16,9 +16,7 @@ metadata:
16
16
  # could falsely gate an admin holding only one. Declare the canonical
17
17
  # System-Administrator perm; the OR alternative stays documented in the
18
18
  # description and body.
19
- accessCheck:
20
- - type: "userPerm"
21
- value: "ModifyAllData"
19
+ accessCheck: "UserPermissions.ModifyAllData"
22
20
  cliTools:
23
21
  - tool: ["jq"]
24
22
  semver: ">=1.6.0"
@@ -10,11 +10,7 @@ metadata:
10
10
  cliTools:
11
11
  - tool: ["sf"]
12
12
  semver: ">=2.0.0"
13
- accessCheck:
14
- - type: "orgPref"
15
- value: "ScratchOrgManagementPref"
16
- - type: "orgPref"
17
- value: "ShapeExportPref"
13
+ accessCheck: "(OrgPreferences.ScratchOrgManagementPref) && (OrgPreferences.ShapeExportPref)"
18
14
  ---
19
15
 
20
16
  # dx-org-shape-manage
@@ -13,9 +13,7 @@ metadata:
13
13
  semver: ">=2.0.0"
14
14
  - tool: ["jq"]
15
15
  semver: ">=1.6"
16
- accessCheck:
17
- - type: "orgPref"
18
- value: "ScratchOrgSnapshotPref"
16
+ accessCheck: "OrgPreferences.ScratchOrgSnapshotPref"
19
17
  ---
20
18
 
21
19
  # dx-org-snapshot-manage