experience-ui-bundle-2gp-deploy
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePackage an existing UI Bundle (2GP)
将现有UI Bundle打包为2GP包
How to take a UI Bundle that already exists in the current project
(under , where is the package
directory from — commonly ) and ship
it as a second-generation package (2GP), then install / upgrade / uninstall
it in another org.
<packageDir>/uiBundles/<name>/<packageDir>sfdx-project.jsonforce-app/main/defaultThis is reference knowledge, not a runbook to execute top-to-bottom. The
user already has a project and a built (or buildable) bundle. Read their intent
and apply only the matching part:
Answer only what was asked. Give the commands for the one part the user
needs plus the org each targets and any genuine caveat — nothing else. Do not
restate the other parts, re-explain the flavor table, or replay the full
build→create→install→promote sequence when the user asked about a single step. A
debug question wants the fix, not a packaging tutorial; an install question wants
the line and the subscriber-vs-Dev-Hub distinction, not
Part 1 and Part 2. Brevity is correctness here.
sf package install| The user wants to… | Go to |
|---|---|
| Decide managed vs. unlocked | Choose a flavor |
| Make the bundle packageable / wire a CustomApplication | Part 1 |
| Create the package or a new version | Part 2 |
| Install / upgrade / uninstall / promote | Part 3 |
| Debug a failure | Part 4 + Troubleshooting |
This skill is for packaging and cross-org distribution (). For
plain source deploy of a bundle into one org (), use
experience-ui-bundle-deploy instead. Never or
here — the project and bundle exist.
/ / are placeholders; substitute
the user's real bundle name and their everywhere they appear
below. Resolve deterministically — never guess in a
multi-package project — with:
sf package …sf project deploy …sf project generatesf template generate ui-bundleMyReactAppforce-appforce-app/main/default<packageDir><packageDir>[0]bash
packageDir="$(scripts/find-bundle-package-dir.sh <bundleName>)" # walks packageDirectories; picks the entry whose tree contains uiBundles/<bundleName>/如何将当前项目中已存在的UI Bundle
(位于,其中是中定义的包目录——通常为)发布为第二代包(2GP),然后在另一个组织中进行安装/升级/卸载操作。
<packageDir>/uiBundles/<name>/<packageDir>sfdx-project.jsonforce-app/main/default本内容为参考知识,并非需要从上到下执行的操作手册。用户已拥有项目和已构建(或可构建)的包。请理解用户意图,仅提供匹配需求的部分:
仅回答用户提出的问题。提供用户所需步骤的命令、目标组织以及真实注意事项——无需其他内容。当用户询问单个步骤时,请勿重述其他部分、重新解释包类型表格,或重复完整的构建→创建→安装→推广流程。调试问题需要解决方案,而非打包教程;安装问题需要命令以及订阅组织与Dev Hub的区别说明,而非第一部分和第二部分内容。简洁即为正确。
sf package install本技能适用于打包与跨组织分发(命令)。若需将包普通源部署到单个组织(命令),请使用experience-ui-bundle-deploy技能。请勿在此处使用或——项目和包已存在。 / / 为占位符,请在以下内容中替换为用户实际的包名称和。请确定的准确路径——在多包项目中切勿猜测,可使用以下命令:
sf package …sf project deploy …sf project generatesf template generate ui-bundleMyReactAppforce-appforce-app/main/default<packageDir><packageDir>[0]bash
packageDir="$(scripts/find-bundle-package-dir.sh <bundleName>)" # 遍历packageDirectories;选择包含uiBundles/<bundleName>/的条目Step 0 — Confirm the orgs (do this before touching any org)
步骤0 — 确认组织(执行任何操作前完成)
Do not assume the default org. Ask the user, or read , then
restate what you'll use:
sf org list- Dev Hub () — where the package is created, versions are built, and source is deployed. Always required.
devhub - Subscriber () — the org you install into. Only required for install / upgrade / uninstall.
subscriber
bash
sf org list # connected orgs + default Dev Hub
sf org list --json | jq -r '.result.nonScratchOrgs[]?.alias'Rules:
- Create-only task (package or version) → one Dev Hub is enough; don't ask for a subscriber.
- Install task → confirm both, and confirm which is which. Installing into the Dev Hub by mistake is a common, messy error.
Substitute the real aliases for / everywhere below.
devhubsubscriberID legend (packaging): package · installable version
(SubscriberPackageVersionId) · Package2Version · version-create
request · install request · uninstall request.
0Ho…04t…05i…08c…0Hf…06y…ID legend (runtime, useful when debugging a broken subscriber): UI
Bundle row · UIBundleApplication junction · CustomApplication /
TabSet · ManagedContentSpace (workspace) · ManagedContentChannel
(WEB_APP). A missing App Launcher tile after install almost always traces back
to one of these being absent or misprovisioned.
9YE…9YF…02u…0Zu…0ap…请勿默认使用默认组织。请询问用户,或执行命令,然后说明将使用的组织:
sf org list- Dev Hub () — 用于创建包、构建版本和部署源码。必须使用。
devhub - 订阅组织 () — 安装包的目标组织。仅在安装/升级/卸载时需要。
subscriber
bash
sf org list # 已连接组织 + 默认Dev Hub
sf org list --json | jq -r '.result.nonScratchOrgs[]?.alias'规则:
- 仅创建任务(包或版本)→ 仅需一个Dev Hub;无需询问订阅组织。
- 安装任务 → 确认两个组织,并明确区分二者。错误地安装到Dev Hub是常见且棘手的问题。
请在以下内容中用实际别名替换 / 。
devhubsubscriberID标识(打包相关): 包 · 可安装版本(SubscriberPackageVersionId) · Package2Version · 版本创建请求 · 安装请求 · 卸载请求。
0Ho…04t…05i…08c…0Hf…06y…ID标识(运行时,调试故障订阅组织时有用): UI Bundle记录 · UIBundleApplication关联记录 · CustomApplication / TabSet · ManagedContentSpace(工作区) · ManagedContentChannel(WEB_APP)。安装后App Launcher tile缺失几乎总是因为上述某一项不存在或配置错误。
9YE…9YF…02u…0Zu…0ap…Prerequisite — the 2GP toggle everyone forgets
前提条件 — 所有人都会忘记的2GP开关
2GP needs a manual Setup toggle on the Dev Hub that no CLI command or
metadata deploy can flip. Setup → Dev Hub, both on:
- Enable Dev Hub, and
- Enable Unlocked Packages and Second-Generation Managed Packages ← the real gate.
Until #2 is on, returns and any query
returns . There is no CLI workaround —
flip the toggle. Verify before starting:
sf package createNOT_FOUNDPackage2sObject type 'Package2' is not supportedbash
undefined2GP需要在Dev Hub中手动设置开关,此开关无法通过CLI命令或元数据部署开启。设置 → Dev Hub,需同时开启以下两项:
- 启用Dev Hub,以及
- 启用解锁包和第二代托管包 ← 关键开关。
在开启第2项之前,会返回,任何查询都会返回。没有CLI解决方法——必须手动开启开关。开始前请验证:
sf package createNOT_FOUNDPackage2sObject type 'Package2' is not supportedbash
undefinedclean "0 records" = 2GP ON; "sObject type 'Package2' is not supported" = toggle OFF
返回"0 records" = 2GP已开启; 返回"sObject type 'Package2' is not supported" = 开关未开启
sf data query --target-org devhub --use-tooling-api --query "SELECT Id FROM Package2 LIMIT 1"
sf org display --target-org devhub --json | jq '.result.isDevHub'
---sf data query --target-org devhub --use-tooling-api --query "SELECT Id FROM Package2 LIMIT 1"
sf org display --target-org devhub --json | jq '.result.isDevHub'
---Choose a flavor
选择包类型
All three are 2GP (same CLI). Pick before creating — it drives the
namespace, how the bundle is named on install, and coexistence.
sf package| Managed | Unlocked — namespaced | Unlocked — org-dependent | |
|---|---|---|---|
| Namespace | required | required | none (empty |
| Source visibility | hidden (IP-protected) | visible / editable | visible / editable |
| Installs as | | | bare |
| Coexists with a local same-name bundle | yes (ns-filtered) | yes (ns-filtered) | no — collides |
| Upgrade behavior | clean replace (locked) | replace, overwrites subscriber edits | replace, overwrites subscriber edits |
| Rollback risk on failed upgrade | yes | yes | none |
| Typical use | ISV / AppExchange distribution | org-agnostic sharing, source open | package depends on metadata already in the target org |
Namespaced flavors (managed, unlocked-namespaced) need a namespace registered
and linked to this Dev Hub (App Launcher → Namespace Registries). No
registered namespace? Use org-dependent unlocked — it needs none.
以下三种均为2GP包(使用相同的 CLI)。创建前需选择类型,它会决定命名空间、包安装后的名称以及共存规则。
sf package| 托管包 | 解锁包(带命名空间) | 解锁包(依赖组织) | |
|---|---|---|---|
| 命名空间 | 必填 | 必填 | 无(空值 |
| 源可见性 | 隐藏(受IP保护) | 可见/可编辑 | 可见/可编辑 |
| 安装后名称 | | | 纯 |
| 与本地同名包共存 | 是(按命名空间过滤) | 是(按命名空间过滤) | 否——冲突 |
| 升级行为 | 干净替换(锁定) | 替换,覆盖订阅方编辑内容 | 替换,覆盖订阅方编辑内容 |
| 升级失败回滚风险 | 是 | 是 | 无 |
| 典型用途 | ISV/AppExchange分发 | 跨组织共享、源码开放 | 包依赖目标组织中已有的元数据 |
带命名空间的包类型(托管、带命名空间的解锁包)需要已注册并关联到当前Dev Hub的命名空间(App Launcher → 命名空间注册表)。若无已注册的命名空间,请使用依赖组织的解锁包——无需命名空间。
How linking works (namespace ⇄ Dev Hub)
命名空间与Dev Hub的关联方式
The namespace lives in a separate Developer Edition (DE) org that owns it;
the Dev Hub borrows it via a linked registration. Concretely:
- Sign up a DE org and register a namespace on it (Setup → Package Manager → Namespace Registrations).
- In the Dev Hub, App Launcher → Namespace Registries → Link Namespace, log in with the DE org's credentials to link the namespace to this Dev Hub.
- Set in
namespaceto the linked namespace slug. If the value here isn't linked to the target Dev Hub,sfdx-project.jsonfails with a namespace error (see Troubleshooting).sf package version create
One DE org can carry multiple namespaces, and one Dev Hub can link multiple DE
orgs — so a single Dev Hub can build packages under several namespaces. The
namespace is locked in at version-create time and travels with every UI
Bundle row inside the built version; you cannot change it later.
命名空间属于单独的开发者版(DE)组织;Dev Hub通过关联注册表“借用”该命名空间。具体步骤:
- 注册一个DE组织,并在其上注册命名空间(设置 → 包管理器 → 命名空间注册表)。
- 在Dev Hub中,进入App Launcher → 命名空间注册表 → 关联命名空间,使用DE组织的凭据登录,将命名空间关联到当前Dev Hub。
- 在中设置
sfdx-project.json为已关联的命名空间标识。若此处的值未关联到目标Dev Hub,namespace会因命名空间错误失败(详见故障排除)。sf package version create
一个DE组织可包含多个命名空间,一个Dev Hub可关联多个DE组织——因此单个Dev Hub可在多个命名空间下构建包。命名空间在版本创建时锁定,并随包内的每个UI Bundle记录一同存在;后续无法修改。
Runtime model — why the flavor matters
运行时模型 — 包类型的重要性
You do not have to explain this to answer a routine question. Reach for it when
the user asks why: why managed hides source, why namespaced installs are
, why some URLs look different, or why an unlocked upgrade wiped
their edits.
ns__Name- Origin isolation. Every installed UI Bundle renders from its own origin on
, distinct from
*.salesforce.appcore UI. Tiers:salesforce.com- — 1st-party core UI
salesforce.com - — 2nd-party AFS-hosted bundles (no namespace)
*.salesforce.app - — 3rd-party / namespaced (managed + unlocked-namespaced) Because each namespace gets its own subdomain, two bundles from different packages can coexist without cross-origin bleed.
<ns>.salesforce.app
- IP protection is a managed-only property. For managed packages,
returns null in subscriber orgs — the compiled
getSourceZip()is stored as opaque content and never handed back. For unlocked (namespaced or org-dependent), the served binary is fully readable by the subscriber.dist/ - Install semantics. Managed and unlocked-namespaced install as and can coexist with a local same-name bundle. Org-dependent unlocked has no namespace — it installs as bare
ns__Nameand collides with a local bundle of the same developer name.Name - Delta upgrade. On of a newer
sf package install, the platform compares content-index hashes of each incoming04t…asset against what's already stored and skips any asset whose hash is unchanged — a patch that touches one bundle re-writes only that bundle's changed files. Developer-owned artifacts (dist/,dist/, ISV base permission sets) are replaced; subscriber-owned state (subscriber-created permission sets, custom metadata, provisioned domain) is preserved.ui-bundle.json - Kill switch. Setup → Security → Multi-Framework Domains → disable a provisioned domain. Immediate 404; metadata stays installed; reversible.
常规问题无需解释此部分。当用户询问原因时可使用:比如为什么托管包会隐藏源码、为什么带命名空间的包安装后名称为、为什么某些URL不同,或为什么解锁包升级会覆盖编辑内容。
ns__Name- 源隔离。每个已安装的UI Bundle从上的独立源渲染,与
*.salesforce.app核心UI区分开。层级:salesforce.com- — 第一方核心UI
salesforce.com - — 第二方AFS托管的包(无命名空间)
*.salesforce.app - — 第三方/带命名空间的包(托管+带命名空间的解锁包) 由于每个命名空间拥有独立子域名,不同包中的两个包可共存而不会出现跨源泄露。
<ns>.salesforce.app
- IP保护仅为托管包特性。对于托管包,在订阅组织中返回null——编译后的
getSourceZip()存储为不透明内容,不会返回给用户。对于解锁包(带命名空间或依赖组织),订阅方可完全读取提供的二进制文件。dist/ - 安装语义。托管包和带命名空间的解锁包安装后名称为,可与本地同名包共存。依赖组织的解锁包无命名空间——安装后名称为纯
ns__Name,会与本地同名包冲突。Name - 增量升级。当使用安装新版本
sf package install时,平台会比较每个传入04t…资源的内容索引哈希与已存储的哈希,跳过哈希未变化的资源——仅修改一个包的补丁只会重写该包的变更文件。开发者拥有的工件(dist/、dist/、ISV基础权限集)会被替换;订阅方拥有的状态(订阅方创建的权限集、自定义元数据、已配置的域名)会被保留。ui-bundle.json - 紧急关闭开关。设置 → 安全 → 多框架域名 → 禁用已配置的域名。立即返回404;元数据仍保留;可恢复。
Part 1 — make the existing bundle packageable
第一部分 — 使现有包可打包
Prepare the project so it can be packaged and used in-org. Apply only what the
request needs.
准备项目使其可打包并在组织中使用。仅执行请求所需的步骤。
1a. Set API version + namespace in sfdx-project.json
sfdx-project.json1a. 在sfdx-project.json
中设置API版本 + 命名空间
sfdx-project.jsonThe here decides which flavor you can build (see table above), so set
it deliberately — there is no safe default. Substitute the user's real registered
namespace for ; use for org-dependent.
namespace<ns>""bash
undefined此处的决定了可构建的包类型(见上表),请谨慎设置——无安全默认值。将替换为用户实际已注册的命名空间;依赖组织的包使用。
namespace<ns>""bash
undefinednamespaced (managed / unlocked-namespaced): <ns> MUST be registered & linked to this Dev Hub
带命名空间(托管/带命名空间的解锁包):<ns>必须已注册并关联到当前Dev Hub
node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace='<ns>'; fs.writeFileSync(f,JSON.stringify(j,null,2))"
node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace='<ns>'; fs.writeFileSync(f,JSON.stringify(j,null,2))"
org-dependent unlocked: no namespace
依赖组织的解锁包:无命名空间
node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace=''; fs.writeFileSync(f,JSON.stringify(j,null,2))"
cat sfdx-project.json # confirm namespace + sourceApiVersion before packaging
- Managed / namespaced-unlocked → `namespace` = a registered, linked namespace.
- Org-dependent unlocked → leave `namespace` as `""`.
- Setting a namespace that isn't registered to this Dev Hub fails the build later
(see [Troubleshooting](#troubleshooting)).node -e "const fs=require('fs'),f='sfdx-project.json',j=JSON.parse(fs.readFileSync(f)); j.sourceApiVersion='68.0'; j.namespace=''; fs.writeFileSync(f,JSON.stringify(j,null,2))"
cat sfdx-project.json # 打包前确认命名空间 + sourceApiVersion
- 托管/带命名空间的解锁包 → `namespace` = 已注册并关联的命名空间。
- 依赖组织的解锁包 → 将`namespace`设为`""`。
- 设置未注册到当前Dev Hub的命名空间会导致后续构建失败(详见[故障排除](#故障排除))。1b. Build the bundle — dist/
must exist before packaging
dist/1b. 构建包 — 打包前必须存在dist/
目录
dist/bash
cd force-app/main/default/uiBundles/MyReactApp # the real bundle dir
npm install --no-audit --no-fund
npm run build
cd -Package or deploy before exists and the app installs but renders
blank — the bundle ships with its built assets. Always build first.
dist/bash
cd force-app/main/default/uiBundles/MyReactApp # 实际包目录
npm install --no-audit --no-fund
npm run build
cd -若在目录存在前打包或部署,包会安装但渲染空白——包需随构建后的资产一同发布。请始终先构建包。
dist/1c. Wire a CustomApplication (only if the bundle must be launchable as a Salesforce app)
1c. 关联CustomApplication(仅当包需作为Salesforce应用启动时)
Skip this step when the bundle is already referenced another way (embedded in a
FlexiPage, Experience Cloud site, etc.). Otherwise read
(where is the
absolute path to this skill's own directory), replace every with
the real bundle developer name, and write the result to the user's project
under . Author with the bundle's
developer name — inside the same package no prefix is needed; cross-namespace
it resolves as (namespaced) or (no namespace).
<SKILL_DIR>/assets/CustomApplication.app-meta.xml<SKILL_DIR>MyReactApp<packageDir>/applications/<uiBundle>ns__Namec__NameThe three fields the App Launcher tile actually cares about — installed
subscribers won't see a broken tile if they're set correctly:
- — required for the App Launcher to render it
<uiType>Lightning</uiType> - — standard navigation container
<navType>Standard</navType> - — desktop form factor (validation is install-time only, so a missing/wrong value passes deploy but hides the tile)
<formFactors>Large</formFactors>
bash
mkdir -p force-app/main/default/applications若包已通过其他方式引用(嵌入到FlexiPage、Experience Cloud站点等),请跳过此步骤。否则请读取(为当前技能目录的绝对路径),将所有替换为实际包的开发者名称,并将结果写入用户项目的目录下。需填写包的开发者名称——同一包内无需前缀;跨命名空间时解析为(带命名空间)或(无命名空间)。
<SKILL_DIR>/assets/CustomApplication.app-meta.xml<SKILL_DIR>MyReactApp<packageDir>/applications/<uiBundle>ns__Namec__NameApp Launcher tile实际关注的三个字段——正确设置后,订阅方安装后不会看到损坏的tile:
- — App Launcher渲染所需
<uiType>Lightning</uiType> - — 标准导航容器
<navType>Standard</navType> - — 桌面设备类型(仅在安装时验证,缺失/错误值会通过部署但隐藏tile)
<formFactors>Large</formFactors>
bash
mkdir -p force-app/main/default/applicationsthen write the substituted template to:
然后将替换后的模板写入:
force-app/main/default/applications/<BundleName>.app-meta.xml
force-app/main/default/applications/<BundleName>.app-meta.xml
undefinedundefined1d. Deploy source to the Dev Hub (so metadata exists before package create
)
package create1d. 将源码部署到Dev Hub(以便package create
前存在元数据)
package createbash
sf project deploy start --source-dir force-app --target-org devhub --api-version 68.0 --wait 30bash
sf project deploy start --source-dir force-app --target-org devhub --api-version 68.0 --wait 301e. Grant app visibility via a permission set (only if 1c added a CustomApplication and the app must be reachable without a manual Setup click)
1e. 通过权限集授予应用可见性(仅当1c添加了CustomApplication且应用无需手动设置即可访问时)
Read , replace
with the real bundle name (both in and the label),
write the result into the user's project, then deploy and assign:
<SKILL_DIR>/assets/PermissionSet.permissionset-meta.xmlMyReactApp<application>bash
mkdir -p force-app/main/default/permissionsets读取,将替换为实际包名称(和标签中均需替换),将结果写入用户项目,然后部署并分配:
<SKILL_DIR>/assets/PermissionSet.permissionset-meta.xmlMyReactApp<application>bash
mkdir -p force-app/main/default/permissionsetswrite the substituted template to:
将替换后的模板写入:
force-app/main/default/permissionsets/<BundleName>_Access.permissionset-meta.xml
force-app/main/default/permissionsets/<BundleName>_Access.permissionset-meta.xml
sf project deploy start --source-dir force-app/main/default/permissionsets/MyReactApp_Access.permissionset-meta.xml --target-org devhub --api-version 68.0 --wait 30
sf org assign permset --name MyReactApp_Access --target-org devhub
---sf project deploy start --source-dir force-app/main/default/permissionsets/MyReactApp_Access.permissionset-meta.xml --target-org devhub --api-version 68.0 --wait 30
sf org assign permset --name MyReactApp_Access --target-org devhub
---Part 2 — create the package (Dev Hub only)
第二部分 — 创建包(仅在Dev Hub中执行)
No subscriber org involved. runs once (registers the
container); you build installable versions repeatedly after. Pick
the one flavor you chose above:
sf package create0Ho…04t…bash
undefined无需涉及订阅组织。仅需运行一次(注册容器);后续可重复构建可安装的版本。选择之前确定的包类型:
sf package create0Ho…04t…bash
undefinedmanaged
托管包
sf package create --name MyReactApp --package-type Managed --path force-app --target-dev-hub devhub
sf package create --name MyReactApp --package-type Managed --path force-app --target-dev-hub devhub
unlocked, namespaced (namespace comes from sfdx-project.json)
解锁包(带命名空间) (命名空间来自sfdx-project.json)
sf package create --name MyReactApp --package-type Unlocked --path force-app --target-dev-hub devhub
sf package create --name MyReactApp --package-type Unlocked --path force-app --target-dev-hub devhub
unlocked, org-dependent (no namespace)
解锁包(依赖组织)(无命名空间)
sf package create --name MyReactApp --package-type Unlocked --org-dependent --path force-app --target-dev-hub devhub
Then build a version:
```bash
sf package version create --package MyReactApp --installation-key-bypass --wait 20 --target-dev-hub devhubsf package create --name MyReactApp --package-type Unlocked --org-dependent --path force-app --target-dev-hub devhub
然后构建版本:
```bash
sf package version create --package MyReactApp --installation-key-bypass --wait 20 --target-dev-hub devhuba specific/patch version instead:
构建特定/补丁版本:
sf package version create --package MyReactApp --version-number 1.0.1 --wait 20 --target-dev-hub devhub
`--version-number 1.0.0.NEXT` auto-bumps the build number; a fixed `1.0.1` pins
it. `--installation-key-bypass` builds an unprotected version (no key to
install); omit it and pass `--installation-key <key>` to gate installs.sf package version create --package MyReactApp --version-number 1.0.1 --wait 20 --target-dev-hub devhub
`--version-number 1.0.0.NEXT`会自动递增构建号;固定值`1.0.1`会锁定版本号。`--installation-key-bypass`构建无保护版本(安装无需密钥);若需设置安装密钥,请省略此参数并添加`--installation-key <key>`。Robust version-create (survives a slow Dev Hub queue)
可靠的版本创建(应对缓慢的Dev Hub队列)
--wait08c…bash
REQ=$(sf package version create --package MyReactApp --installation-key-bypass \
--skip-validation --target-dev-hub devhub --json | jq -r '.result.Id')
echo "request: $REQ"
while :; do
J=$(sf package version create report -i "$REQ" --target-dev-hub devhub --json)
ST=$(echo "$J" | jq -r '.result[0].Status'); echo "status: $ST"
case "$ST" in
Success) echo "$J" | jq -r '.result[0].SubscriberPackageVersionId'; break;;
Error) echo "$J" | jq -r '.result[0].Error[]? // "build failed"'; break;;
esac
sleep 30
done--skip-validationsf package version create report -i 08c… --target-dev-hub devhub--wait08c…bash
REQ=$(sf package version create --package MyReactApp --installation-key-bypass \\
--skip-validation --target-dev-hub devhub --json | jq -r '.result.Id')
echo "request: $REQ"
while :; do
J=$(sf package version create report -i "$REQ" --target-dev-hub devhub --json)
ST=$(echo "$J" | jq -r '.result[0].Status'); echo "status: $ST"
case "$ST" in
Success) echo "$J" | jq -r '.result[0].SubscriberPackageVersionId'; break;;
Error) echo "$J" | jq -r '.result[0].Error[]? // "build failed"'; break;;
esac
sleep 30
done--skip-validationsf package version create report -i 08c… --target-dev-hub devhubPart 3 — install / upgrade / uninstall / promote
第三部分 — 安装/升级/卸载/推广
Confirm the subscriber alias first (Step 0). Everything here hits the
subscriber — except promote, which runs on the Dev Hub.
bash
undefined请先确认订阅组织别名(步骤0)。以下操作均针对订阅组织——除推广操作需在Dev Hub中执行。
bash
undefinedfresh install
全新安装
sf package install --package 04t… --target-org subscriber --wait 10
sf package install --package 04t… --target-org subscriber --wait 10
add --installation-key <key> if the version was built with one
若版本构建时设置了密钥,请添加--installation-key <key>
add --publish-wait 10 to wait for the version to finish publishing
添加--publish-wait 10以等待版本完成发布
upgrade (newer version over the old)
升级(用新版本覆盖旧版本)
sf package install --package 04t…v2 --target-org subscriber --upgrade-type Mixed --wait 10
sf package install --package 04t…v2 --target-org subscriber --upgrade-type Mixed --wait 10
--upgrade-type: Mixed (default) | DeprecateOnly | Delete (destructive — care)
--upgrade-type: Mixed(默认) | DeprecateOnly | Delete(破坏性操作——谨慎使用)
uninstall
卸载
sf package uninstall --package 04t… --target-org subscriber --wait 20
sf package uninstall --package 04t… --target-org subscriber --wait 20
promote a managed version to released/immutable — runs on the DEV HUB, irreversible
推广托管版本为已发布/不可变版本——在DEV HUB中执行,不可逆
sf package version promote --package 04t… --target-dev-hub devhub
**Beta can't upgrade beta.** A `--skip-validation` (beta) v0.2 over a beta v0.1
fails with *"Cannot upgrade beta package."* Either **promote** v0.1 (managed) or
**uninstall** v0.1 first, then install v0.2.
**Unlocked upgrades overwrite subscriber edits** to the bundle. Org-dependent has
no rollback on a failed upgrade; namespaced flavors do.sf package version promote --package 04t… --target-dev-hub devhub
**Beta版本无法升级Beta版本**。使用`--skip-validation`构建的beta v0.2升级beta v0.1会失败,提示*"Cannot upgrade beta package."*。请**推广**v0.1(托管包)或先**卸载**v0.1,再安装v0.2。
**解锁包升级会覆盖订阅方对包的编辑内容**。依赖组织的包升级失败无回滚机制;带命名空间的包类型有回滚机制。Robust install (confirm it actually landed)
可靠的安装(确认包已成功安装)
sf package install --waitbash
sf package install --package 04t… --target-org subscriber --wait 20 --no-prompt
sf package installed list --target-org subscriber --json \
| jq -r '.result[]? | select(.SubscriberPackageVersionId=="04t…") | .SubscriberPackageVersionId'Prints nothing → still processing server-side; poll
a few minutes before concluding it failed.
sf package installed listsf package install --waitbash
sf package install --package 04t… --target-org subscriber --wait 20 --no-prompt
sf package installed list --target-org subscriber --json \\
| jq -r '.result[]? | select(.SubscriberPackageVersionId=="04t…") | .SubscriberPackageVersionId'无输出 → 服务器端仍在处理;请轮询几分钟后再判断是否失败。
sf package installed listPart 4 — debug / inspect
第四部分 — 调试/检查
Mostly read-only. Reach for these to diagnose a failure or inspect state.
bash
undefined大多为只读操作。用于诊断失败问题或检查状态。
bash
undefinedDev Hub state
Dev Hub状态
sf org display --target-org devhub --json | jq '{isDevHub:.result.isDevHub, user:.result.username, instance:.result.instanceUrl, api:.result.apiVersion}'
sf org display --target-org devhub --json | jq '{isDevHub:.result.isDevHub, user:.result.username, instance:.result.instanceUrl, api:.result.apiVersion}'
Is 2GP on? (the #1 root cause)
2GP是否开启? (最常见的根本原因)
sf data query --target-org devhub --use-tooling-api --query "SELECT Id, Name, NamespacePrefix, ContainerOptions FROM Package2"
sf data query --target-org devhub --use-tooling-api --query "SELECT Id, Name, NamespacePrefix, ContainerOptions FROM Package2"
Packages & versions on the Dev Hub
Dev Hub中的包和版本
sf package list --target-dev-hub devhub
sf package version list --packages MyReactApp --target-dev-hub devhub --verbose
sf package list --target-dev-hub devhub
sf package version list --packages MyReactApp --target-dev-hub devhub --verbose
Version-create failures — status + Error[]
版本创建失败——状态 + Error[]
sf package version create list --target-dev-hub devhub
sf package version create report -i 08c… --target-dev-hub devhub
sf data query --target-org devhub --use-tooling-api
--query "SELECT Id, Status, Package2Id, Error FROM Package2VersionCreateRequest ORDER BY CreatedDate DESC LIMIT 5"
--query "SELECT Id, Status, Package2Id, Error FROM Package2VersionCreateRequest ORDER BY CreatedDate DESC LIMIT 5"
sf package version create list --target-dev-hub devhub
sf package version create report -i 08c… --target-dev-hub devhub
sf data query --target-org devhub --use-tooling-api \
--query "SELECT Id, Status, Package2Id, Error FROM Package2VersionCreateRequest ORDER BY CreatedDate DESC LIMIT 5"
A version's details
版本详情
sf package version report --package 04t… --target-dev-hub devhub
sf package version report --package 04t… --target-dev-hub devhub
What's installed in the subscriber
订阅组织中已安装的包
sf package installed list --target-org subscriber --json
| jq -r '.result[]? | "(.SubscriberPackageName) (.SubscriberPackageVersionNumber) (.SubscriberPackageVersionId)"'
| jq -r '.result[]? | "(.SubscriberPackageName) (.SubscriberPackageVersionNumber) (.SubscriberPackageVersionId)"'
sf package installed list --target-org subscriber --json \
| jq -r '.result[]? | "\(.SubscriberPackageName) \(.SubscriberPackageVersionNumber) \(.SubscriberPackageVersionId)"'
Install/uninstall stuck IN_PROGRESS
安装/卸载卡在IN_PROGRESS状态
sf package install report --request-id 0Hf… --target-org subscriber
sf package uninstall report --request-id 06y… --target-org subscriber
sf package install report --request-id 0Hf… --target-org subscriber
sf package uninstall report --request-id 06y… --target-org subscriber
Deploy failures (before you can even package)
部署失败(甚至无法打包)
sf project deploy start --source-dir force-app --target-org devhub --dry-run --wait 30
sf project deploy report --target-org devhub
sf project deploy start --source-dir force-app --target-org devhub --dry-run --wait 30
sf project deploy report --target-org devhub
Bundle renders blank — confirm built assets shipped
包渲染空白——确认构建后的资产已发布
ls -la force-app/main/default/uiBundles/MyReactApp/dist
---ls -la force-app/main/default/uiBundles/MyReactApp/dist
---Troubleshooting
故障排除
| Symptom | Cause / fix |
|---|---|
| 2GP toggle OFF — Setup → Dev Hub → enable "Unlocked & Second-Gen Managed Packages" (manual, no CLI fix). |
| Same — 2GP not provisioned. Enable toggle, re-auth. |
| Cached CLI login — re-auth. Trust the |
| Build queued. Use the async submit + |
| Still IN_PROGRESS server-side. Confirm with |
| "Cannot upgrade beta package" | Beta can't upgrade beta. Promote v0.1 (managed) or uninstall it first, then install v0.2. |
| Namespace error on managed/namespaced build | Namespace not registered/linked to this Dev Hub (App Launcher → Namespace Registries), or switch to org-dependent unlocked (no namespace). |
| App installs but renders blank | Bundle not built before deploy/package — |
| Installed into the wrong org | Wrong alias confirmed in Step 0. Re-check |
| Org-dependent bundle collides with a local one | Both use a bare (null-prefix) name. Use a namespaced flavor, or rename. |
| 症状 | 原因/解决方案 |
|---|---|
| 2GP开关未开启 — 设置 → Dev Hub → 启用“解锁包和第二代托管包”(手动操作,无CLI解决方法)。 |
| 同上 — 未配置2GP。开启开关,重新授权。 |
开启后 | CLI登录缓存 — 重新授权。请信任 |
| 构建处于队列中。使用异步提交 + |
| 服务器端仍处于IN_PROGRESS状态。使用 |
| "Cannot upgrade beta package" | Beta版本无法升级Beta版本。推广v0.1(托管包)或先卸载v0.1,再安装v0.2。 |
| 托管/带命名空间构建时出现命名空间错误 | 命名空间未注册/未关联到当前Dev Hub(App Launcher → 命名空间注册表),或切换为依赖组织的解锁包(无命名空间)。 |
| 包安装后渲染空白 | 部署/打包前未构建包 — 执行 |
| 安装到错误的组织 | 步骤0中确认的别名错误。重新检查 |
| 依赖组织的包与本地包冲突 | 二者均使用无前缀名称。使用带命名空间的包类型,或重命名。 |
Notes
注意事项
- Confirm orgs first. Dev Hub always; subscriber only for install/upgrade/ uninstall. Don't ask for a subscriber on a create-only task.
- Order for a full run: build bundle → deploy source → (once) →
package create(each release) →package version create→install(managed only).promote - For internal Salesforce packaging questions, the authoritative channel is #packaging.
- Authoritative external docs (for deeper reference):
- Second-Generation Managed Packaging Developer Guide — https://developer.salesforce.com/docs/atlas.en-us.pkg2_dev.meta/pkg2_dev/sfdx_dev_dev2gp.htm (managed / AppExchange flavor: workflow, components, distribution, push upgrades, 1GP→2GP gaps).
- Unlocked packages share the same CLI; see the "Unlocked Packages" section of the same guide for the unlocked-namespaced and org-dependent flavors.
sf package
- 先确认组织。始终需要Dev Hub;仅在安装/升级/卸载时需要订阅组织。仅创建任务无需询问订阅组织。
- 完整流程顺序:构建包 → 部署源码 → (仅一次) →
package create(每次发布) →package version create→install(仅托管包)。promote - 内部Salesforce打包问题,请咨询权威渠道**#packaging**。
- 权威外部文档(深入参考):
- 第二代托管包开发者指南 — https://developer.salesforce.com/docs/atlas.en-us.pkg2_dev.meta/pkg2_dev/sfdx_dev_dev2gp.htm (托管/AppExchange类型:工作流、组件、分发、推送升级、1GP→2GP差异)。
- 解锁包使用相同的CLI;请查看同一指南中的“解锁包”章节,了解带命名空间和依赖组织的解锁包类型。",
sf package