afv-library/skills/experience-ui-bundle-deploy/references/license-checks.md

2.8 KiB

License pre-checks

Two license gates guard steps that fail with cryptic errors when the org lacks the right UserLicense. Both are derived from the self-reg profile — there is no separate license config, so the requirement can't drift from the profile the flow actually assigns. Both only apply when self-registration is configured.

Source of truth: reference org-setup.mjs checkDeployLicense (956-992), readProfileUserLicense (938-944), checkSelfRegLicense (895-926), and evaluateLicenseRows (org-setup-utils.mjs ~224+).

Gate 1 — deploy license (pre-deploy, HARD block)

Runs before sf project deploy start (lines 1478-1483). The profile isn't in the org yet pre-deploy, so querying Profile would only report "profile missing" — instead, read the required license NAME straight from the local profile source:

<packageDir>/main/default/profiles/<selfRegProfile>.profile-meta.xml<userLicense>...</userLicense>.

  • If the profile file or <userLicense> element is absent → nothing to gate on; let deploy proceed (line 960).
  • Otherwise query the org:
    SELECT LicenseDefinitionKey, Name, Status, TotalLicenses, UsedLicenses
    FROM UserLicense WHERE Name = '<licenseName-escaped>'
    
  • Zero rows → block the deploy with a message NAMING the missing license (required license "<name>" is not present in the org — add it before deploying). Query/parse failure → block. Otherwise run the seat/status check.

This is a hard block (a StepError) because deploying without the license fails anyway, but with an unhelpful error.

Gate 2 — self-reg license (pre-selfReg, SOFT skip)

Runs before the self-reg step (lines 1598-1601). Query the license via the profile:

SELECT UserLicense.LicenseDefinitionKey, UserLicense.Name, UserLicense.Status,
       UserLicense.TotalLicenses, UserLicense.UsedLicenses
FROM Profile WHERE Name = '<selfRegProfile>'

A query failure, zero rows, inactive license, or no free seats is a soft skip: warn with the specific reason and record the step skipped. Self-registration being unavailable is not a setup failure.

Seat / status math (evaluateLicenseRows)

Done client-side because SOQL can't compare two fields. The license is satisfied only when it is Active and either UsedLicenses < TotalLicenses or TotalLicenses === -1 (the documented "unlimited" sentinel — treat it as always having free seats). Report the specific reason (missing / inactive / no seats) so the user knows exactly what to fix.

SOQL-safety of the profile name

  • In Gate 2 the profile name is validate-and-fail: a ', \, or control char throws a config error rather than being escaped (developer config, not user input).
  • In Gate 1 the license NAME read from the profile XML is escaped for the SOQL literal (escapeSoqlString).