experience-ui-bundle-2gp-deploy

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Package an existing UI Bundle (2GP)

将现有UI Bundle打包为2GP包

How to take a UI Bundle that already exists in the current project (under
<packageDir>/uiBundles/<name>/
, where
<packageDir>
is the package directory from
sfdx-project.json
— commonly
force-app/main/default
) and ship it as a second-generation package (2GP), then install / upgrade / uninstall it in another org.
This 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
sf package install
line and the subscriber-vs-Dev-Hub distinction, not Part 1 and Part 2. Brevity is correctness here.
The user wants to…Go to
Decide managed vs. unlockedChoose a flavor
Make the bundle packageable / wire a CustomApplicationPart 1
Create the package or a new versionPart 2
Install / upgrade / uninstall / promotePart 3
Debug a failurePart 4 + Troubleshooting
This skill is for packaging and cross-org distribution (
sf package …
). For plain source deploy of a bundle into one org (
sf project deploy …
), use experience-ui-bundle-deploy instead. Never
sf project generate
or
sf template generate ui-bundle
here — the project and bundle exist.
MyReactApp
/
force-app
/
force-app/main/default
are placeholders; substitute the user's real bundle name and their
<packageDir>
everywhere they appear below. Resolve
<packageDir>
deterministically — never guess
[0]
in a multi-package project — with:
bash
packageDir="$(scripts/find-bundle-package-dir.sh <bundleName>)"   # walks packageDirectories; picks the entry whose tree contains uiBundles/<bundleName>/

如何将当前项目中已存在的UI Bundle (位于
<packageDir>/uiBundles/<name>/
,其中
<packageDir>
sfdx-project.json
中定义的包目录——通常为
force-app/main/default
)发布为第二代包(2GP),然后在另一个组织中进行安装/升级/卸载操作。
本内容为参考知识,并非需要从上到下执行的操作手册。用户已拥有项目和已构建(或可构建)的包。请理解用户意图,仅提供匹配需求的部分:
仅回答用户提出的问题。提供用户所需步骤的命令、目标组织以及真实注意事项——无需其他内容。当用户询问单个步骤时,请勿重述其他部分、重新解释包类型表格,或重复完整的构建→创建→安装→推广流程。调试问题需要解决方案,而非打包教程;安装问题需要
sf package install
命令以及订阅组织与Dev Hub的区别说明,而非第一部分和第二部分内容。简洁即为正确。
用户需求查看章节
选择托管包还是解锁包选择包类型
使包可打包 / 关联CustomApplication第一部分
创建包或新版本第二部分
安装/升级/卸载/推广第三部分
调试失败问题第四部分 + 故障排除
本技能适用于打包与跨组织分发
sf package …
命令)。若需将包普通源部署到单个组织(
sf project deploy …
命令),请使用experience-ui-bundle-deploy技能。请勿在此处使用
sf project generate
sf template generate ui-bundle
——项目和包已存在。
MyReactApp
/
force-app
/
force-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
sf org list
, then restate what you'll use:
  • Dev Hub (
    devhub
    ) — where the package is created, versions are built, and source is deployed. Always required.
  • Subscriber (
    subscriber
    ) — the org you install into. Only required for install / upgrade / uninstall.
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
devhub
/
subscriber
everywhere below.
ID legend (packaging):
0Ho…
package ·
04t…
installable version (SubscriberPackageVersionId) ·
05i…
Package2Version ·
08c…
version-create request ·
0Hf…
install request ·
06y…
uninstall request.
ID legend (runtime, useful when debugging a broken subscriber):
9YE…
UI Bundle row ·
9YF…
UIBundleApplication junction ·
02u…
CustomApplication / TabSet ·
0Zu…
ManagedContentSpace (workspace) ·
0ap…
ManagedContentChannel (WEB_APP). A missing App Launcher tile after install almost always traces back to one of these being absent or misprovisioned.

请勿默认使用默认组织。请询问用户,或执行
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是常见且棘手的问题。
请在以下内容中用实际别名替换
devhub
/
subscriber
ID标识(打包相关):
0Ho…
包 ·
04t…
可安装版本(SubscriberPackageVersionId) ·
05i…
Package2Version ·
08c…
版本创建请求 ·
0Hf…
安装请求 ·
06y…
卸载请求。
ID标识(运行时,调试故障订阅组织时有用):
9YE…
UI Bundle记录 ·
9YF…
UIBundleApplication关联记录 ·
02u…
CustomApplication / TabSet ·
0Zu…
ManagedContentSpace(工作区) ·
0ap…
ManagedContentChannel(WEB_APP)。安装后App Launcher tile缺失几乎总是因为上述某一项不存在或配置错误。

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:
  1. Enable Dev Hub, and
  2. Enable Unlocked Packages and Second-Generation Managed Packages ← the real gate.
Until #2 is on,
sf package create
returns
NOT_FOUND
and any
Package2
query returns
sObject type 'Package2' is not supported
. There is no CLI workaround — flip the toggle. Verify before starting:
bash
undefined
2GP需要在Dev Hub中手动设置开关,此开关无法通过CLI命令或元数据部署开启。设置 → Dev Hub,需同时开启以下两项:
  1. 启用Dev Hub,以及
  2. 启用解锁包和第二代托管包 ← 关键开关。
在开启第2项之前,
sf package create
会返回
NOT_FOUND
,任何
Package2
查询都会返回
sObject type 'Package2' is not supported
。没有CLI解决方法——必须手动开启开关。开始前请验证:
bash
undefined

clean "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
sf package
CLI). Pick before creating — it drives the namespace, how the bundle is named on install, and coexistence.
ManagedUnlocked — namespacedUnlocked — org-dependent
Namespacerequiredrequirednone (empty
""
)
Source visibilityhidden (IP-protected)visible / editablevisible / editable
Installs as
ns__Name
ns__Name
bare
Name
(flat)
Coexists with a local same-name bundleyes (ns-filtered)yes (ns-filtered)no — collides
Upgrade behaviorclean replace (locked)replace, overwrites subscriber editsreplace, overwrites subscriber edits
Rollback risk on failed upgradeyesyesnone
Typical useISV / AppExchange distributionorg-agnostic sharing, source openpackage 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包(使用相同的
sf package
CLI)。创建前需选择类型,它会决定命名空间、包安装后的名称以及共存规则。
托管包解锁包(带命名空间)解锁包(依赖组织)
命名空间必填必填无(空值
""
源可见性隐藏(受IP保护)可见/可编辑可见/可编辑
安装后名称
ns__Name
ns__Name
Name
(无前缀)
与本地同名包共存是(按命名空间过滤)是(按命名空间过滤)否——冲突
升级行为干净替换(锁定)替换,覆盖订阅方编辑内容替换,覆盖订阅方编辑内容
升级失败回滚风险
典型用途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:
  1. Sign up a DE org and register a namespace on it (Setup → Package Manager → Namespace Registrations).
  2. In the Dev Hub, App Launcher → Namespace RegistriesLink Namespace, log in with the DE org's credentials to link the namespace to this Dev Hub.
  3. Set
    namespace
    in
    sfdx-project.json
    to the linked namespace slug. If the value here isn't linked to the target Dev Hub,
    sf package version create
    fails with a namespace error (see Troubleshooting).
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通过关联注册表“借用”该命名空间。具体步骤:
  1. 注册一个DE组织,并在其上注册命名空间(设置 → 包管理器 → 命名空间注册表)。
  2. 在Dev Hub中,进入App Launcher → 命名空间注册表关联命名空间,使用DE组织的凭据登录,将命名空间关联到当前Dev Hub。
  3. sfdx-project.json
    中设置
    namespace
    为已关联的命名空间标识。若此处的值未关联到目标Dev Hub,
    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
ns__Name
, why some URLs look different, or why an unlocked upgrade wiped their edits.
  • Origin isolation. Every installed UI Bundle renders from its own origin on
    *.salesforce.app
    , distinct from
    salesforce.com
    core UI. Tiers:
    • salesforce.com
      — 1st-party core UI
    • *.salesforce.app
      — 2nd-party AFS-hosted bundles (no namespace)
    • <ns>.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.
  • IP protection is a managed-only property. For managed packages,
    getSourceZip()
    returns null in subscriber orgs — the compiled
    dist/
    is stored as opaque content and never handed back. For unlocked (namespaced or org-dependent), the served binary is fully readable by the subscriber.
  • Install semantics. Managed and unlocked-namespaced install as
    ns__Name
    and can coexist with a local same-name bundle. Org-dependent unlocked has no namespace — it installs as bare
    Name
    and collides with a local bundle of the same developer name.
  • Delta upgrade. On
    sf package install
    of a newer
    04t…
    , the platform compares content-index hashes of each incoming
    dist/
    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/
    ,
    ui-bundle.json
    , ISV base permission sets) are replaced; subscriber-owned state (subscriber-created permission sets, custom metadata, provisioned domain) is preserved.
  • Kill switch. Setup → Security → Multi-Framework Domains → disable a provisioned domain. Immediate 404; metadata stays installed; reversible.

常规问题无需解释此部分。当用户询问原因时可使用:比如为什么托管包会隐藏源码、为什么带命名空间的包安装后名称为
ns__Name
、为什么某些URL不同,或为什么解锁包升级会覆盖编辑内容。
  • 源隔离。每个已安装的UI Bundle从
    *.salesforce.app
    上的独立源渲染,与
    salesforce.com
    核心UI区分开。层级:
    • salesforce.com
      — 第一方核心UI
    • *.salesforce.app
      — 第二方AFS托管的包(无命名空间)
    • <ns>.salesforce.app
      — 第三方/带命名空间的包(托管+带命名空间的解锁包) 由于每个命名空间拥有独立子域名,不同包中的两个包可共存而不会出现跨源泄露。
  • IP保护仅为托管包特性。对于托管包
    getSourceZip()
    在订阅组织中返回null——编译后的
    dist/
    存储为不透明内容,不会返回给用户。对于解锁包(带命名空间或依赖组织),订阅方可完全读取提供的二进制文件。
  • 安装语义。托管包和带命名空间的解锁包安装后名称为
    ns__Name
    ,可与本地同名包共存。依赖组织的解锁包无命名空间——安装后名称为纯
    Name
    ,会与本地同名包冲突。
  • 增量升级。当使用
    sf package install
    安装新版本
    04t…
    时,平台会比较每个传入
    dist/
    资源的内容索引哈希与已存储的哈希,跳过哈希未变化的资源——仅修改一个包的补丁只会重写该包的变更文件。开发者拥有的工件(
    dist/
    ui-bundle.json
    、ISV基础权限集)会被替换;订阅方拥有的状态(订阅方创建的权限集、自定义元数据、已配置的域名)会被保留。
  • 紧急关闭开关。设置 → 安全 → 多框架域名 → 禁用已配置的域名。立即返回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

1a. 在
sfdx-project.json
中设置API版本 + 命名空间

The
namespace
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
<ns>
; use
""
for org-dependent.
bash
undefined
此处的
namespace
决定了可构建的包类型(见上表),请谨慎设置——无安全默认值。将
<ns>
替换为用户实际已注册的命名空间;依赖组织的包使用
""
bash
undefined

namespaced (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

1b. 构建包 — 打包前必须存在
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
dist/
exists and the app installs but renders blank — the bundle ships with its built assets. Always build first.
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
<SKILL_DIR>/assets/CustomApplication.app-meta.xml
(where
<SKILL_DIR>
is the absolute path to this skill's own directory), replace every
MyReactApp
with the real bundle developer name, and write the result to the user's project under
<packageDir>/applications/
. Author
<uiBundle>
with the bundle's developer name — inside the same package no prefix is needed; cross-namespace it resolves as
ns__Name
(namespaced) or
c__Name
(no namespace).
The three fields the App Launcher tile actually cares about — installed subscribers won't see a broken tile if they're set correctly:
  • <uiType>Lightning</uiType>
    — required for the App Launcher to render it
  • <navType>Standard</navType>
    — standard navigation container
  • <formFactors>Large</formFactors>
    — desktop form factor (validation is install-time only, so a missing/wrong value passes deploy but hides the tile)
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__Name
(带命名空间)或
c__Name
(无命名空间)。
App Launcher tile实际关注的三个字段——正确设置后,订阅方安装后不会看到损坏的tile:
  • <uiType>Lightning</uiType>
    — App Launcher渲染所需
  • <navType>Standard</navType>
    — 标准导航容器
  • <formFactors>Large</formFactors>
    — 桌面设备类型(仅在安装时验证,缺失/错误值会通过部署但隐藏tile)
bash
mkdir -p force-app/main/default/applications

then write the substituted template to:

然后将替换后的模板写入:

force-app/main/default/applications/<BundleName>.app-meta.xml

force-app/main/default/applications/<BundleName>.app-meta.xml

undefined
undefined

1d. Deploy source to the Dev Hub (so metadata exists before
package create
)

1d. 将源码部署到Dev Hub(以便
package create
前存在元数据)

bash
sf project deploy start --source-dir force-app --target-org devhub --api-version 68.0 --wait 30
bash
sf project deploy start --source-dir force-app --target-org devhub --api-version 68.0 --wait 30

1e. 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
<SKILL_DIR>/assets/PermissionSet.permissionset-meta.xml
, replace
MyReactApp
with the real bundle name (both in
<application>
and the label), write the result into the user's project, then deploy and assign:
bash
mkdir -p force-app/main/default/permissionsets
读取
<SKILL_DIR>/assets/PermissionSet.permissionset-meta.xml
,将
MyReactApp
替换为实际包名称(
<application>
和标签中均需替换),将结果写入用户项目,然后部署并分配:
bash
mkdir -p force-app/main/default/permissionsets

write 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.
sf package create
runs once (registers the
0Ho…
container); you build installable
04t…
versions repeatedly after. Pick the one flavor you chose above:
bash
undefined
无需涉及订阅组织。
sf package create
仅需运行一次(注册
0Ho…
容器);后续可重复构建可安装的
04t…
版本。选择之前确定的包类型:
bash
undefined

managed

托管包

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 devhub
sf 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 devhub

a 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队列)

--wait
can time out while the build sits queued, losing the request handle. Submit async, capture the
08c…
id, poll:
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-validation
is faster but produces a beta version (can't be promoted, and beta can't upgrade beta — see Part 3). Drop it for a releasable build. Resume a queued build anytime:
sf package version create report -i 08c… --target-dev-hub devhub

--wait
可能在构建仍处于队列中时超时,导致丢失请求句柄。请异步提交,捕获
08c…
ID,轮询状态:
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-validation
速度更快,但会生成beta版本(无法推广,且beta版本无法升级beta版本——见第三部分)。若需可发布的版本,请省略此参数。可随时恢复队列中的构建:
sf package version create report -i 08c… --target-dev-hub devhub

Part 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
undefined

fresh 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 --wait
can exit 0 while the request is still IN_PROGRESS — a false success. Verify:
bash
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
sf package installed list
a few minutes before concluding it failed.

sf package install --wait
可能在请求仍处于IN_PROGRESS状态时返回0——假成功。请验证:
bash
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 list
几分钟后再判断是否失败。

Part 4 — debug / inspect

第四部分 — 调试/检查

Mostly read-only. Reach for these to diagnose a failure or inspect state.
bash
undefined
大多为只读操作。用于诊断失败问题或检查状态。
bash
undefined

Dev 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"
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)"'
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

故障排除

SymptomCause / fix
sObject type 'Package2' is not supported
2GP toggle OFF — Setup → Dev Hub → enable "Unlocked & Second-Gen Managed Packages" (manual, no CLI fix).
sf package create
NOT_FOUND
Same — 2GP not provisioned. Enable toggle, re-auth.
isDevHub: false/null
after enabling
Cached CLI login — re-auth. Trust the
Package2
query +
package create
, not the cached flag.
version create
hangs /
--wait
times out
Build queued. Use the async submit +
version create report -i 08c…
poll; resume later with the same id.
install --wait
exits 0 but app missing
Still IN_PROGRESS server-side. Confirm with
sf package installed list
; poll a few minutes.
"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 buildNamespace not registered/linked to this Dev Hub (App Launcher → Namespace Registries), or switch to org-dependent unlocked (no namespace).
App installs but renders blankBundle not built before deploy/package —
npm run build
, confirm
dist/
, redeploy, rebuild the version.
Installed into the wrong orgWrong alias confirmed in Step 0. Re-check
sf org list
;
subscriber
devhub
.
Org-dependent bundle collides with a local oneBoth use a bare (null-prefix) name. Use a namespaced flavor, or rename.
症状原因/解决方案
sObject type 'Package2' is not supported
2GP开关未开启 — 设置 → Dev Hub → 启用“解锁包和第二代托管包”(手动操作,无CLI解决方法)。
sf package create
返回
NOT_FOUND
同上 — 未配置2GP。开启开关,重新授权。
开启后
isDevHub: false/null
CLI登录缓存 — 重新授权。请信任
Package2
查询 +
package create
结果,而非缓存标志。
version create
卡住 /
--wait
超时
构建处于队列中。使用异步提交 +
version create report -i 08c…
轮询;后续可使用同一ID恢复。
install --wait
返回0但应用缺失
服务器端仍处于IN_PROGRESS状态。使用
sf package installed list
确认;轮询几分钟。
"Cannot upgrade beta package"Beta版本无法升级Beta版本。推广v0.1(托管包)或先卸载v0.1,再安装v0.2。
托管/带命名空间构建时出现命名空间错误命名空间未注册/未关联到当前Dev Hub(App Launcher → 命名空间注册表),或切换为依赖组织的解锁包(无命名空间)。
包安装后渲染空白部署/打包前未构建包 — 执行
npm run build
,确认
dist/
目录存在,重新部署,重新构建版本。
安装到错误的组织步骤0中确认的别名错误。重新检查
sf org list
subscriber
devhub
依赖组织的包与本地包冲突二者均使用无前缀名称。使用带命名空间的包类型,或重命名。

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 →
    package create
    (once) →
    package version create
    (each release) →
    install
    promote
    (managed only).
  • For internal Salesforce packaging questions, the authoritative channel is #packaging.
  • Authoritative external docs (for deeper reference):
  • 先确认组织。始终需要Dev Hub;仅在安装/升级/卸载时需要订阅组织。仅创建任务无需询问订阅组织。
  • 完整流程顺序:构建包 → 部署源码 →
    package create
    (仅一次) →
    package version create
    (每次发布) →
    install
    promote
    (仅托管包)。
  • 内部Salesforce打包问题,请咨询权威渠道**#packaging**。
  • 权威外部文档(深入参考):