持续集成

持续集成 (CI) 是一种软件开发实践,开发人员在其中 定期将他们的代码更改集成到源代码存储库中。确保新代码 不引入错误,自动构建和测试在开发人员签入之前或之后运行 变化。

许多第三方 CI 工具可供您选择。Salesforce DX 轻松集成 添加到这些工具中,以便您可以为 Salesforce 设置持续集成 应用。

  • 使用 CircleCI 进行持续集成 CircleCI
    是一种常用的集成工具,它与您现有的版本控制系统集成,以将增量更新推送到您指定的环境。CircleCI 可以用作基于云的或本地工具。这些说明演示了如何使用 GitHub、CircleCI 和 Dev Hub 组织进行持续集成。
  • 使用 Jenkins 进行持续集成
    Jenkins 是一个开源的、可扩展的自动化服务器,用于实现持续集成和持续交付。您可以轻松地将 Salesforce DX 集成到 Jenkins 框架中,以针对临时组织自动测试 Salesforce 应用程序。
  • 与 Travis CI 的持续集成
    Travis CI 是一种基于云的持续集成 (CI) 服务,用于构建和测试 GitHub 上托管的软件项目。
  • 组织开发模型
    的示例 CI 存储库 通过从您选择的供应商处克隆示例存储库,快速开始使用 CI。每个存储库都有一个示例配置文件和一个包含分步信息的综合 README.md。
  • 包开发模型
    的示例 CI 存储库 通过克隆所选供应商的示例存储库,快速开始使用 CI。每个存储库都有一个示例配置文件和一个包含分步信息的综合 README.md。

使用 CircleCI 进行持续集成

CircleCI 是一种常用的集成工具,可与您现有的集成 版本控制系统,用于将增量更新推送到您指定的环境。CircleCI系列 可用作基于云的或本地工具。这些说明演示了如何使用 GitHub、CircleCI 和 Dev Hub 组织,用于持续集成。

  • 为 CircleCI 配置环境
    在集成现有 CircleCI 框架之前,请配置开发人员中心组织和 CircleCI 项目。
  • 将 CircleCI 连接到您的 DevHub
    授权 CircleCI 通过连接的应用程序将内容推送到您的 Dev Hub。

为 CircleCI 配置环境

在集成现有的 CircleCI 框架之前,请配置开发人员中心组织和 CircleCI 项目。

  1. 使用 CircleCI 设置 GitHub 存储库。您可以按照 CircleCI 网站上的注册步骤访问您的代码 在 GitHub 上。
  2. 安装 Salesforce CLI(如果您还没有)。
  3. 如果尚未操作,请遵循使用 JWT 流为 Dev Hub 组织授权组织。
  4. 加密服务器密钥。
    1. 首先,生成密钥和初始化向量 (iv) 以在本地加密 server.key 文件。CircleCI 使用 key 和 iv 来 在生成环境中解密服务器密钥。在包含 server.key 文件的目录中运行以下命令。 对于值,请输入您自己选择的单词 以创建唯一密钥。<passphrase>openssl enc -aes-256-cbc -k <passphrase> -P -md sha1 -nosalt键和 iv 值显示在输出中。key=****24B2 iv =****DA58
    2. 请注意 key 和 iv 值,稍后需要用到它们。
    3. 使用新生成的密钥加密 server.key 文件,然后 IV 值。在包含 server.key 文件的目录中运行以下命令,将 <key> 和 <iv> 替换为上一步中的值。openssl enc -nosalt -aes-256-cbc -in server.key -out server.key.enc -base64 -K <key> -iv <iv>注意key 和 iv 值仅使用一次,并且不要使用它们来加密超过 server.key 的值。虽然您可以重复使用此对来加密其他 事情,这样做被视为违反安全规定。每次在步骤 a 中运行命令时,都会生成新的键和 iv 值。在 换句话说,你不能重新生成同一对。如果丢失这些值,则必须 生成新的并再次加密。

接下来,将 server.key.enc 的密钥、iv 和内容存储为 CircleCI UI 中受保护的环境变量。这些值被认为是机密的,因此 采取适当的预防措施来保护它们。

将 CircleCI 连接到您的 DevHub

授权 CircleCI 通过连接的应用程序将内容推送到您的开发人员中心。

  1. 确保您已安装 Salesforce CLI。通过运行 sf version 进行检查,并确认您看到的是版本信息。如果你 未安装,请参阅安装 Salesforce CLI。
  2. 确认您可以从包含 server.key 文件的目录中执行基于 JWT 的授权。从包含 您的 server.key(替换 <your_consumer_key> 并在指示的位置<your_username>值)。sf org login jwt --client-id <your_consumer_key> --jwt-key-file server.key --username <your_username> --set-default-dev-hub
  3. 使用页面顶部的 Fork 链接将 sfdx-circleci 存储库分叉到你的 GitHub 帐户中。
  4. 为此项目创建一个本地目录,并将分叉的存储库本地克隆到新的 目录。将 <git_username> 替换为你自己的 GitHub 用户名。git clone https://github.com/<git_username>/sfdx-circleci.git
  5. 从基于 JWT 的授权连接的应用中检索生成的使用者密钥。从 设置,在“快速查找”框中,输入“应用”,然后选择“应用 经理。在已连接旁边的行菜单中选择查看 应用程序。
  6. 在 CircleCI UI 中,您会看到一个名为 sfdx-circleci 的项目。在项目设置中,存储 名为 HUB_CONSUMER_KEY 的 CircleCI 环境变量中的使用者密钥。有关更多信息,请参阅 CircleCI 文档在 项目。
  7. 将用于访问 Dev Hub 的用户名存储在 CircleCI 环境变量中 使用 CircleCI UI 命名HUB_SFDX_USER
  8. 将加密服务器密钥中的密钥和 iv 值存储在 CircleCI 环境变量中 分别命名为 DECRYPTION_KEY和 DECRYPTION_IV。完成环境变量的设置后,您的 项目屏幕如下图所示。注意在包含 server.key 文件的目录中,使用命令删除 server.key。切勿在公共场所存放密钥或证书。rm server.key

你准备好了!现在,当您提交并推送更改时,您的更改将启动一个 CircleCI 构建。

为存储库做贡献

如果您发现任何问题或改进此存储库的机会,请修复它们!随意 为这个项目做贡献,分叉这个仓库,然后将 内容。进行更改后,通过发送拉取请求与社区共享这些更改。 有关参与的详细信息,请参阅如何发送拉取请求 到 GitHub 项目。

报告问题

如果您发现此演示中有任何问题无法修复,请随时在此存储库的问题部分报告。

使用 Jenkins 进行持续集成

Jenkins 是一个开源的、可扩展的自动化服务器,用于实现连续 集成和持续交付。您可以轻松地将 Salesforce DX 集成到 Jenkins 中 框架,用于针对临时组织自动测试 Salesforce 应用程序。

为了集成 Jenkins,我们假设:

  • 您熟悉 Jenkins 的工作原理。您可以通过多种方式配置和使用 Jenkins。我们 专注于将 Salesforce DX 集成到 Jenkins 多分支管道中。
  • 运行 Jenkins 服务器的计算机有权访问版本控制 系统和包含 Salesforce 应用程序的存储库。
  • 为 Jenkins 配置环境
    在将 Dev Hub 和临时组织集成到现有 Jenkins 框架之前,请配置 Jenkins 环境。我们的示例假定你使用的是包开发模型。
  • Jenkinsfile 演练
    示例 Jenkinsfile 演示如何将 Dev Hub 和临时组织集成到 Jenkins 作业中。此示例使用 Jenkins 多分支管道。每个 Jenkins 设置都是不同的。本演练介绍自动测试 Salesforce 应用程序的方法之一。本演练重点介绍了用于创建临时组织、上传代码和运行测试的 Salesforce CLI 命令。
  • 示例 Jenkinsfile
    Jenkinsfile 是一个文本文件,其中包含 Jenkins Pipeline 的定义。此 Jenkinsfile 展示了如何集成 Salesforce CLI 命令,以使用 scratch 组织自动测试 Salesforce 应用程序。

为 Jenkins 配置环境

在将 Dev Hub 和 Scratch 组织集成到现有的 Jenkins 框架之前, 配置 Jenkins 环境。我们的示例假定你正在包中工作 发展模式。

  1. 在开发人员中心组织中,按照基于 JWT 的授权流所述创建连接的应用。此步骤包括获取或创建私钥和数字证书。在保存连接的 应用程序。您需要使用者密钥来设置 Jenkins 环境。也有可用的 用于对数字证书进行签名的私钥文件。
  2. 在运行 Jenkins 服务器的计算机上,执行以下操作。
    1. 下载并安装 Salesforce CLI。
    2. 使用 Jenkins 管理员凭据将私钥文件存储为 Jenkins 机密文件 接口。记下新条目的 ID。稍后在 Jenkinsfile 中引用此凭据条目。
    3. 在 Jenkins 环境中设置以下变量。
      • SF_USERNAME – Dev Hub 组织的用户名,例如 juliet.capulet@myenvhub.com。
      • SF_INSTANCE_URL – 托管 Dev Hub 的 Salesforce 实例的登录 URL 组织。默认值为 https://login.salesforce.com。我们建议您更新 此值设置为 Dev Hub 组织的 My Domain 登录 URL。您可以找到一个组织的 “设置”中“我的域”页面上的“我的域”登录 URL。
      • SF_CONSUMER_KEY – 创建连接后返回的使用者密钥 开发人员中心组织中的应用。
      • SERVER_KEY_CREDENTALS_ID – 您的私钥文件的凭证 ID 存储在 Jenkins 管理员凭据界面中。
      • PACKAGE_NAME – 包的名称,例如“我的包”。
      • PACKAGE_VERSION-软件包的版本,以 04t 开头。
      • TEST_LEVEL – 包的测试级别,例如 RunLocalTests。
      这些环境变量的名称只是建议。您可以使用任何名称作为 只要你在 Jenkinsfile 中指定它。您还可以选择设置SF_AUTOUPDATE_DISABLE 变量以禁用 Salesforce 命令行界面。CLI 自动更新可能会干扰 Jenkins 的执行 工作。true
  3. 设置您的 Salesforce DX 项目,以便您可以创建临时组织。
  4. (可选)将自定义工具插件安装到 Jenkins 控制台中,并创建一个自定义 引用 Salesforce CLI 的工具。Jenkins 演练假定您创建了一个 /usr/local/bin 目录中名为 toolbelt 的自定义工具,该目录是 Salesforce CLI 的安装目录。

Jenkinsfile 演练

示例 Jenkinsfile 展示了如何将 Dev Hub 和 Scratch 组织集成到 Jenkins 作业。此示例使用 Jenkins 多分支管道。每个 Jenkins 设置都是 不同。本演练介绍自动测试 Salesforce 的方法之一 应用。本演练重点介绍了用于创建临时组织的 Salesforce CLI 命令。 上传代码并运行测试。

本演练依赖于 sfdx-jenkins-package Jenkinsfile。我们假设你熟悉 Jenkinsfile、Jenkins Pipeline DSL 和 Groovy 编程语言的结构。这 演练演示如何使用 Salesforce CLI 实现 Jenkins 管道,以及 临时组织。有关所使用的命令,请参阅 CLI 命令参考。

此工作流与 Jenkinsfile 阶段最接近。

  • 定义变量
  • 查看源代码
  • 将所有阶段包装在 withCredentials 命令中
  • 将所有阶段包装在 withEnv 命令中
  • 授权 Dev Hub 组织并创建临时组织
  • 推送源并分配权限集
  • 运行 Apex 测试
  • 删除临时组织
  • 创建程序包
  • 创建临时组织并显示信息
  • 安装包、运行单元测试和删除临时组织

定义变量

使用关键字定义所需的变量 通过 Salesforce CLI 命令。为每个变量分配相应的环境变量 您之前在 Jenkins 环境中设置。def

def SF_CONSUMER_KEY=env.SF_CONSUMER_KEY
def SERVER_KEY_CREDENTALS_ID=env.SERVER_KEY_CREDENTALS_ID
def TEST_LEVEL='RunLocalTests'
def PACKAGE_NAME='0Ho1U000000CaUzSAK'
def PACKAGE_VERSION
def SF_INSTANCE_URL = env.SF_INSTANCE_URL ?: "https://MyDomainName.my.salesforce.com"

定义变量,但不要设置其 价值。你稍后再做。SF_USERNAME

def SF_USERNAME

虽然不是必需的,但我们假设你使用 Jenkins 全局工具配置来 创建指向 CLI 安装目录。在 Jenkinsfile 中,使用 tool 命令将变量的值设置为此自定义工具。toolbelttoolbelt

def toolbelt = tool 'toolbelt'

现在,您可以使用 在 Jenkinsfile 中引用 Salesforce CLI 可执行文件。${toolbelt}/sf

查看源代码

在测试代码之前,请从版本控制中获取相应的版本或分支 系统 (VCS) 存储库。在此示例中,我们使用 Jenkins 命令。我们假设 Jenkins 管理员已经 配置环境以访问正确的 VCS 存储库并检出正确的 分支。checkout scm

stage('checkout source') {
        // when running in multi-branch job, one must issue this command
        checkout scm
  }

将所有阶段包装在 withCredentials 命令中

您之前使用 凭据接口。因此,您必须使用 Jenkinsfile 正文中的命令来访问机密文件。该命令允许您命名凭据条目,然后 从凭据存储中提取,并通过变量提供给包含的代码。 使用时,将所有阶段放在其 代码块。withCredentialswithCredentialswithCredentials

此示例将 JWT 密钥文件的凭据 ID 存储在变量 中。您之前定义了 并将其设置为其 相应的环境变量。该命令从以下位置获取密钥文件的内容 凭据存储并将内容放置在临时位置。位置 存储在变量 中。你 将变量与命令一起使用以指定 私钥安全。SERVER_KEY_CREDENTALS_IDSERVER_KEY_CREDENTALS_IDwithCredentialsserver_key_fileserver_key_fileorg login jwt

withCredentials([file(credentialsId: SERVER_KEY_CREDENTALS_ID, variable: 'server_key_file')])
   # all stages will go here 
}

将所有阶段包装在 withEnv 命令中

运行 Jenkins 作业时,了解文件的存储位置会很有帮助。那里 是需要注意的两个主要目录:工作区目录和主页 目录。工作区目录对于每个作业都是唯一的,而主目录是 所有工作都一样。

该命令将 JWT 密钥文件存储在 作业期间的 Jenkins 工作区。但是,Salesforce CLI 命令将身份验证文件存储在主目录中;这些 身份验证文件在作业持续时间之外保留。withCredentialsauth

运行单个作业时,此设置不会出现问题,但在运行时可能会导致问题 多个作业。因此,如果使用同一个 Dev Hub 运行多个作业,会发生什么情况,或者 其他 Salesforce 用户?当 CLI 尝试以用户身份连接到开发人员中心时,你 经过身份验证时,无法刷新令牌。为什么?CLI 尝试使用 JWT 密钥 其他工作区中不再存在的文件,而不考虑当前作业。withCredentials

如果使用 设置主目录以匹配工作区目录,则每个作业的身份验证文件都是唯一的。 为每个作业创建唯一的身份验证文件也更安全,因为每个作业只能访问 它创建的身份验证文件。withEnv

使用 时,将所有阶段都放在其代码中 块withEnv

withEnv(["HOME=${env.WORKSPACE}"]) {
   # all stages will go here 
}

注意

如果不使用管道或在管道阶段之外运行命令,请添加主页 脚本的环境规范:。export HOME=$WORKSPACE

授权 Dev Hub 组织并创建临时组织

此示例使用两个 阶段:一个阶段用于授权开发人员中心组织,另一个阶段用于创建临时 组织。sfdx-jenkins-package

// -------------------------------------------------------------------------
// Authorize the Dev Hub org with JWT key and give it an alias.
// -------------------------------------------------------------------------

stage('Authorize DevHub') {   
    rc = command "${toolbelt}/sf org login jwt --instance-url ${SF_INSTANCE_URL} --client-id ${SF_CONSUMER_KEY} --username ${SF_USERNAME} --jwt-key-file ${server_key_file} --set-default-dev-hub --alias HubOrg"
    if (rc != 0) {
        error 'Salesforce dev hub org authorization failed.'
    }
}


// -------------------------------------------------------------------------
// Create new scratch org to test your code.
// -------------------------------------------------------------------------

stage('Create Test Scratch Org') {
    rc = command "${toolbelt}/sf org create scratch --target-dev-hub HubOrg --set-default --definition-file config/project-scratch-def.json --alias ciorg --wait 10 --duration-days 1"
    if (rc != 0) {
        error 'Salesforce test scratch org creation failed.'
    }
}

用于授权开发人员中心组织。org login jwt

此步骤只需运行一次,但我们建议您将其添加到 Jenkinsfile 中,并在每次授权时进行授权 运行 Jenkins 作业。这样,您始终可以确保 Jenkins 作业不会中止 由于缺乏授权。授权多个通常没有什么坏处 时间,但请记住,临时组织版本的API调用限制仍然存在 适用。

使用命令的标志 提供有关你正在授权的开发人员中心组织的信息。的值 、 和 标志是 SF_CONSUMER_KEY、HubOrg 和 SF_INSTANCE_URL 环境变量 之前分别定义。标志的值是您在上一节中使用 命令。该标志指定此 HubOrg 是用于创建临时组织的默认 Dev Hub 组织。org login jwt–client-id–username–instance-url–jwt-key-fileserver_key_filewithCredentials–set-default-dev-hub

注意

最佳做法是为每个 Jenkins 作业提供唯一的身份验证文件 使用包装器。但这是可能的 改为在 Jenkins 计算机上授权 Dev Hub。优点是你的 在计算机上为运行的任何 Jenkins 作业集中设置身份验证。这 缺点是安全性:每个作业都可以访问所有经过身份验证的用户,无论 你希望他们或不愿意。withEnv

如果确实要在 Jenkins 上对 Dev Hub 进行身份验证If you do want to auth to auth to your Dev Hub on your Jenkins 机器,请按照下列步骤操作:

  • 在 Jenkins 计算机上,以 Jenkins 用户身份,使用 任何命令。org login
  • 在 Jenkinsfile 中,删除 、 、 和语句。withCredentialswithEnvorg login jwt

使用 CLI 命令创建 一个临时组织。在此示例中,CLI 命令使用 config/project-scratch-def.json 文件(相对于 项目目录)以创建临时组织。该标志将输出指定为 JSON 格式。该标志将新的临时组织设置为 违约。org create scratch–json–set-default

解析命令的 JSON 输出的 Groovy 代码提取自动生成的用户名 组织创建的一部分。使用此用户名,存储在 SF_USERNAME 变量中,用于 使用推送源、分配权限集等的 CLI 命令。org create scratch

推送源并分配权限集

让我们用元数据填充您的新临时组织。此示例使用命令部署源 到组织。源代码包括构成 Salesforce 的所有部分 application:Apex 类和测试类、权限集、布局、触发器、 自定义对象等。project deploy start

// -------------------------------------------------------------------------
// Push source to test scratch org.
// -------------------------------------------------------------------------

stage('Push To Test Scratch Org') {
    rc = command "${toolbelt}/sf project deploy start --target-org ciorg"
    if (rc != 0) {
        error 'Salesforce push to test scratch org failed.'
    }
}

回想一下包含输出的自动生成的用户名的 SF_USERNAME 变量 通过前面的命令 阶段。该代码使用此变量作为标志的参数,以指定 新的 Scratch 组织。org create scratch–target-org

该命令将部署所有 它在项目中找到的与 Salesforce 相关的文件。将 .forceignore 文件添加到存储库以列出 您不希望推送到组织的文件。project deploy start

运行 Apex 测试

现在,您的源代码和测试源代码已推送到临时组织,请运行命令以运行 Apex 测试。apex run test

// -------------------------------------------------------------------------
// Run unit tests in test scratch org.
// -------------------------------------------------------------------------

stage('Run Tests In Test Scratch Org') {
    rc = command "${toolbelt}/sf apex run test --target-org ciorg --wait 10 --result-format tap --code-coverage --test-level ${TEST_LEVEL}"
    if (rc != 0) {
        error 'Salesforce unit test run in test scratch org failed.'
    }
}

您可以为 CLI 指定各种标志 命令。在示例中:apex run test

  • 该标志运行所有测试 在临时组织中,源自已安装的托管软件包的测试除外。 您还可以指定运行 仅本地测试,运行 仅某些 Apex 测试或套件,或运行组织中的所有测试。–test-level ${TEST_LEVEL}RunLocalTestsRunSpecifiedTestsRunAllTestsInOrg
  • 该标志指定 命令输出采用 Test Anything Protocol (TAP) 格式。测试结果 写入文件仍采用 JUnit 和 JSON 格式。–result-format tap
  • 该标志指定用户名 用于访问 Scratch 组织(SF_USERNAME 中的值)。–target-org ciorg

该命令将其测试结果写入 JUnit 格式。apex run test

删除临时组织

Salesforce 保留在临时文件后指定天数内删除临时组织的权利 已创建。您还可以在管道中创建一个阶段,用于显式删除 测试完成后的 scratch 组织。此清理可确保更好地管理您的 资源。org delete scratch

// -------------------------------------------------------------------------
// Delete package install scratch org.
// -------------------------------------------------------------------------

stage('Delete Package Install Scratch Org') {
    rc = command "${toolbelt}/sf org delete scratch --target-org installorg --no-prompt"
    if (rc != 0) {
        error 'Salesforce package install scratch org deletion failed.'
    }
}

创建程序包

现在,让我们创建一个包。如果您不熟悉包装,则可以将包装视为 使用元数据填充的容器。它包含一组相关功能, 自定义项和架构。您可以使用包将元数据从一个 Salesforce 组织移动到 另一个。创建包后,添加元数据并创建新的包版本。

// -------------------------------------------------------------------------
// Create package version.
// -------------------------------------------------------------------------

stage('Create Package Version') {
    if (isUnix()) {
        output = sh returnStdout: true, script: "${toolbelt}/sf package version create --package ${PACKAGE_NAME} --installation-key-bypass --wait 10 --json --target-dev-hub HubOrg"
    } else {
        output = bat(returnStdout: true, script: "${toolbelt}/sf package version create --package ${PACKAGE_NAME} --installation-key-bypass --wait 10 --json --target-dev-hub HubOrg").trim()
        output = output.readLines().drop(1).join(" ")
}

    // Wait 5 minutes for package replication.
    sleep 300

    def jsonSlurper = new JsonSlurperClassic()
    def response = jsonSlurper.parseText(output)

    PACKAGE_VERSION = response.result.SubscriberPackageVersionId

    response = null

    echo ${PACKAGE_VERSION}
}

创建临时组织并显示信息

还记得您之前创建临时组织的时候吗?现在让我们创建一个临时组织进行安装 您的包,并显示有关该暂存组织的信息。

// -------------------------------------------------------------------------
// Create new scratch org to install package to.
// -------------------------------------------------------------------------

stage('Create Package Install Scratch Org') {
    rc = command "${toolbelt}/sf org create scratch --target-dev-hub HubOrg --set-default --definition-file config/project-scratch-def.json --alias installorg --wait 10 --duration-days 1"
    if (rc != 0) {
        error 'Salesforce package install scratch org creation failed.'
    }
}


// -------------------------------------------------------------------------
// Display install scratch org info.
// -------------------------------------------------------------------------

stage('Display Install Scratch Org') {
    rc = command "${toolbelt}/sf org display --target-org installorg"
    if (rc != 0) {
        error 'Salesforce install scratch org display failed.'
    }
}

安装包、运行单元测试和删除临时组织

最后,在临时组织中安装软件包,运行单元测试,然后删除 scratch org. 就是这样!

// -------------------------------------------------------------------------
// Install package in scratch org.
// -------------------------------------------------------------------------

stage('Install Package In Scratch Org') {
    rc = command "${toolbelt}/sf package install --package ${PACKAGE_VERSION} --target-org installorg --wait 10"
    if (rc != 0) {
        error 'Salesforce package install failed.'
    }
}


// -------------------------------------------------------------------------
// Run unit tests in package install scratch org.
// -------------------------------------------------------------------------

stage('Run Tests In Package Install Scratch Org') {
    rc = command "${toolbelt}/sf apex run test --target-org installorg --result-format tap --code-coverage --test-level ${TEST_LEVEL} --wait 10"
    if (rc != 0) {
        error 'Salesforce unit test run in pacakge install scratch org failed.'
    }
}


// -------------------------------------------------------------------------
// Delete package install scratch org.
// -------------------------------------------------------------------------

stage('Delete Package Install Scratch Org') {
    rc = command "${toolbelt}/sf org delete scratch --target-org installorg --no-prompt"
    if (rc != 0) {
        error 'Salesforce package install scratch org deletion failed.'
    }
}

示例 Jenkinsfile

Jenkinsfile 是一个文本文件,其中包含 Jenkins 流水线的定义。此 Jenkinsfile 展示了如何集成 Salesforce CLI 命令以自动测试 Salesforce 使用 Scratch 组织的应用程序。

Jenkinsfile 演练主题使用此 sfdx-jenkins-package Jenkinsfile 作为示例。

#!groovy

import groovy.json.JsonSlurperClassic

node {

    def SF_CONSUMER_KEY=env.SF_CONSUMER_KEY
    def SF_USERNAME=env.SF_USERNAME
    def SERVER_KEY_CREDENTALS_ID=env.SERVER_KEY_CREDENTALS_ID
    def TEST_LEVEL='RunLocalTests'
    def PACKAGE_NAME='0Ho1U000000CaUzSAK'
    def PACKAGE_VERSION
    def SF_INSTANCE_URL = env.SF_INSTANCE_URL ?: "https://login.salesforce.com"

    def toolbelt = tool 'toolbelt'


    // -------------------------------------------------------------------------
    // Check out code from source control.
    // -------------------------------------------------------------------------

    stage('checkout source') {
        checkout scm
    }


    // -------------------------------------------------------------------------
    // Run all the enclosed stages with access to the Salesforce
    // JWT key credentials.
    // -------------------------------------------------------------------------
    
    withEnv(["HOME=${env.WORKSPACE}"]) {
        
        withCredentials([file(credentialsId: SERVER_KEY_CREDENTALS_ID, variable: 'server_key_file')]) {

            // -------------------------------------------------------------------------
            // Authorize the Dev Hub org with JWT key and give it an alias.
            // -------------------------------------------------------------------------

            stage('Authorize DevHub') {
                rc = command "${toolbelt}/sf org login jwt --instance-url ${SF_INSTANCE_URL} --client-id ${SF_CONSUMER_KEY} --username ${SF_USERNAME} --jwt-key-file ${server_key_file} --set-default-dev-hub --alias HubOrg"
                if (rc != 0) {
                    error 'Salesforce dev hub org authorization failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Create new scratch org to test your code.
            // -------------------------------------------------------------------------

            stage('Create Test Scratch Org') {
                rc = command "${toolbelt}/sf org create scratch --target-dev-hub HubOrg --set-default --definition-file config/project-scratch-def.json --alias ciorg --wait 10 --duration-days 1"
                if (rc != 0) {
                    error 'Salesforce test scratch org creation failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Display test scratch org info.
            // -------------------------------------------------------------------------

            stage('Display Test Scratch Org') {
                rc = command "${toolbelt}/sf org display --target-org ciorg"
                if (rc != 0) {
                    error 'Salesforce test scratch org display failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Push source to test scratch org.
            // -------------------------------------------------------------------------

            stage('Push To Test Scratch Org') {
                rc = command "${toolbelt}/sf project deploy start --target-org ciorg"
                if (rc != 0) {
                    error 'Salesforce push to test scratch org failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Run unit tests in test scratch org.
            // -------------------------------------------------------------------------

            stage('Run Tests In Test Scratch Org') {
                rc = command "${toolbelt}/sf apex run test --target-org ciorg --wait 10 --result-format tap --code-coverage --test-level ${TEST_LEVEL}"
                if (rc != 0) {
                    error 'Salesforce unit test run in test scratch org failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Delete test scratch org.
            // -------------------------------------------------------------------------

            stage('Delete Test Scratch Org') {
                rc = command "${toolbelt}/sf org delete scratch --target-org installorg --no-prompt"
                if (rc != 0) {
                    error 'Salesforce test scratch org deletion failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Create package version.
            // -------------------------------------------------------------------------

            stage('Create Package Version') {
                if (isUnix()) {
                    output = sh returnStdout: true, script: "${toolbelt}/sf package version create --package ${PACKAGE_NAME} --installation-key-bypass --wait 10 --json --target-dev-hub HubOrg"
                } else {
                    output = bat(returnStdout: true, script: "${toolbelt}/sf package version create --package ${PACKAGE_NAME} --installation-key-bypass --wait 10 --json --target-dev-hub HubOrg").trim()
                    output = output.readLines().drop(1).join(" ")
                }

                // Wait 5 minutes for package replication.
                sleep 300

                def jsonSlurper = new JsonSlurperClassic()
                def response = jsonSlurper.parseText(output)

                PACKAGE_VERSION = response.result.SubscriberPackageVersionId

                response = null

                echo ${PACKAGE_VERSION}
            }


            // -------------------------------------------------------------------------
            // Create new scratch org to install package to.
            // -------------------------------------------------------------------------

            stage('Create Package Install Scratch Org') {
                rc = command "${toolbelt}/sf org create scratch --target-dev-hub HubOrg --set-default --definition-file config/project-scratch-def.json --alias installorg --wait 10 --duration-days 1"
                if (rc != 0) {
                    error 'Salesforce package install scratch org creation failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Display install scratch org info.
            // -------------------------------------------------------------------------

            stage('Display Install Scratch Org') {
                rc = command "${toolbelt}/sf org display --target-org installorg"
                if (rc != 0) {
                    error 'Salesforce install scratch org display failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Install package in scratch org.
            // -------------------------------------------------------------------------

            stage('Install Package In Scratch Org') {
                rc = command "${toolbelt}/sf package install --package ${PACKAGE_VERSION} --target-org installorg --wait 10"
                if (rc != 0) {
                    error 'Salesforce package install failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Run unit tests in package install scratch org.
            // -------------------------------------------------------------------------

            stage('Run Tests In Package Install Scratch Org') {
                rc = command "${toolbelt}/sf apex run test --target-org installorg --result-format tap --code-coverage --test-level ${TEST_LEVEL} --wait 10"
                if (rc != 0) {
                    error 'Salesforce unit test run in pacakge install scratch org failed.'
                }
            }


            // -------------------------------------------------------------------------
            // Delete package install scratch org.
            // -------------------------------------------------------------------------

            stage('Delete Package Install Scratch Org') {
                rc = command "${toolbelt}/sf org delete scratch --target-org installorg --no-prompt"
                if (rc != 0) {
                    error 'Salesforce package install scratch org deletion failed.'
                }
            }
        }
    }
}

def command(script) {
    if (isUnix()) {
        return sh(returnStatus: true, script: script);
    } else {
        return bat(returnStatus: true, script: script);
    }
}

与 Travis CI 的持续集成

Travis CI 是一种基于云的持续集成 (CI) 服务,用于构建和测试托管在 GitHub 上的软件项目。

有关设置 Travis CI 的帮助,请参阅:

  • 组织开发模型的示例 Travis CI 存储库
  • 包开发模型的示例 Travis CI 存储库

组织开发模型的示例 CI 存储库

通过从您选择的供应商处克隆示例存储库,快速开始使用 CI。 每个存储库都有一个示例配置文件和一个全面的 README.md 分步信息。

这些示例存储库支持组织开发模型。此模型使用 Salesforce CLI、 源代码管理系统和应用程序生命周期中的沙盒。要确定这是否 模型适合您,请完成组织开发模型模块,赢取徽章。

供应商链接到 GitHub 存储库
AppVeyorhttps://github.com/forcedotcom/sfdx-appveyor-org
https://github.com/forcedotcom/sfdx-bamboo-org
位桶https://github.com/forcedotcom/sfdx-bitbucket-org
CircleCI系列https://github.com/forcedotcom/sfdx-circleci-org
GitLab的https://github.com/forcedotcom/sfdx-gitlab-org
詹金斯https://github.com/forcedotcom/sfdx-jenkins-org
特拉维斯CIhttps://github.com/forcedotcom/sfdx-travisci-org

包开发模型的示例 CI 存储库

通过从您选择的供应商处克隆示例存储库,快速开始使用 CI。 每个存储库都有一个示例配置文件和一个全面的 README.md 分步信息。

这些示例存储库支持包开发模型。此模型使用 Salesforce CLI、源代码控制系统、用于开发的临时组织以及用于测试和 分期。要确定此型号是否适合您,请前往并通过以下方式获得徽章 完成包开发模型模块。

供应商链接到 GitHub 存储库
AppVeyorhttps://github.com/forcedotcom/sfdx-appveyor-package
https://github.com/forcedotcom/sfdx-bamboo-package
位桶https://github.com/forcedotcom/sfdx-bitbucket-package
CircleCI系列https://github.com/forcedotcom/sfdx-circleci-package
GitLab的https://github.com/forcedotcom/sfdx-gitlab-packageCI/CD 模板 Salesforce/Apex 应用程序:https://gitlab.com/sfdx/sfdx-cicd-template
詹金斯https://github.com/forcedotcom/sfdx-jenkins-package
特拉维斯CIhttps://github.com/forcedotcom/sfdx-travisci-package

Salesforce DX 疑难解答

以下是一些帮助您解决问题的提示。

  • CLI 版本信息 使用这些命令可查看有关 Salesforce CLI 的版本信息
  • 错误:未找到
    默认开发中心 由于授权问题,当您尝试创建临时组织时,您会看到此错误。
  • 组织授权失败后无法工作
    有时,您尝试使用 Salesforce CLI 或 IDE 授权 Dev Hub 组织或临时组织,但未成功登录到该组织。端口对于杂散授权过程保持打开状态,并且您不能使用 CLI 或 IDE。若要继续,请手动结束该过程。
  • 错误:使用者密钥已被获取
    假设你在已创建连接应用的组织上运行。当您尝试将检索到的源部署到其他组织时,部署失败并显示错误。发生了什么事?project retrieve startThe consumer key is already taken

CLI 版本信息

使用以下命令可查看有关 Salesforce CLI 的版本信息。

sf plugins --core      // Version of the CLI and all installed plug-ins
sf --version     // CLI version

错误:未找到默认开发中心

当您尝试创建临时组织时,由于授权问题,您会看到此错误。

假设你使用该标志成功授权了开发人员中心组织。与组织关联的用户名是默认用户名 Dev Hub 用户名。然后,您可以在不使用该标志的情况下成功创建临时组织。但是,当您尝试创建一个临时组织时 再次使用相同的CLI命令时,您会收到以下错误:–set-default-dev-hub–target-dev-hub

Error (1): No default dev hub found. Use -v or --target-dev-hub to specify an environment.

发生了什么事?

:您不再位于运行 authorization 命令的目录中。这 使用标志的目录很重要。–set-default-dev-hub

如果从项目目录的根目录运行 authorization 命令,则会在本地设置配置变量。价值 仅当从同一项目目录运行命令时才适用。如果更改为 不同的目录并运行,本地 默认 Dev Hub 组织的设置不再适用,并且出现错误。target-dev-huborg create scratch

通过执行下列操作之一来解决问题。

  • 全局设置,以便可以从任何目录运行。target-dev-huborg create scratchsf config set target-dev-hub=<devhubusername> --global
  • 从同一项目目录运行,其中 你已授权开发人员中心组织。org create scratch
  • 使用 标志 with 从任何目录运行它。–target-dev-huborg create scratchsf target-dev-hub --definition-file <file> --target-dev-hub <devhubusername> --alias my-scratch-org
  • 若要检查是否已全局或本地设置配置值,请使用此命令并检查 “位置”列。sf config list

组织授权失败后无法工作

有时,您尝试使用 Salesforce CLI 或 一个 IDE,但您没有成功登录到组织。港口仍然对流浪者开放 授权过程,并且您不能使用 CLI 或 IDE。若要继续,请结束该过程 手动地。

macOS 或 Linux

要从 macOS 或 Linux 上失败的组织授权中恢复,请使用终端终止 在端口 1717 上运行的进程。

  1. 在终端中,运行:lsof -i tcp:1717
  2. 在结果中,找到使用该端口的进程的 ID。
  3. 跑:kill -9 <the process ID>

窗户

要从 Windows 上失败的组织授权中恢复,请使用任务管理器结束节点 过程。

  1. 按 Ctrl+Alt+Delete,然后单击“任务管理器”。
  2. 选择“进程”选项卡。
  3. 找到名为 的进程。Node注意如果您是 Node.js 开发人员,则可以使用此进程运行多个进程 名字。
  4. 选择要结束的进程,然后单击“结束” 过程

错误:使用者密钥已被获取

假设您在一个组织上运行 你已在其中创建已连接的应用。当您尝试将检索到的源部署到 不同的组织,部署失败并显示错误。发生了什么事?

project retrieve startThe consumer key is already taken

连接的应用包括网站或应用用来标识自身的使用者密钥 Salesforce的。使用者密钥在整个 Salesforce 生态系统中必须是唯一的。什么时候 您尝试部署检索到的 (和未更改的) 源文件与 将应用连接到新组织时,由于使用者密钥重复,部署失败。

有几个选项可以解决此问题。

  • 在部署源之前,从项目中删除连接的应用源文件 到新组织。因此,不会创建连接的应用。连接的应用程序 源文件的名称类似于 。force-app/main/default/connectedApps/MyConnApp.connectedApp-meta.xml
  • 更新已连接应用的文件,并将元素的值更改为唯一值。这是 显示元素的示例连接应用文件的代码片段。<consumerKey><consumerKey><?xml version="1.0" encoding="UTF-8"?> <ConnectedApp xmlns="http://soap.sforce.com/2006/04/metadata"> <contactEmail>john@doecompany.com</contactEmail> <contactPhone>5556789</contactPhone> <label>MyConnApp</label> <oauthConfig> <callbackUrl>http://localhost:1717/OauthRedirect</callbackUrl> <consumerKey>3MVG9PG9sFc71i9n55UWbx2</consumerKey> <isAdminApproved>false</isAdminApproved> ...

Salesforce DX 的限制

以下是您在使用 Salesforce DX 时可能遇到的一些已知问题。

有关最新的已知问题,请访问 Trailblazer Community 的已知问题页面。

Salesforce 命令行界面

如果与客户端密码一起使用,则授权失败auth:web:login描述: 如果您使用客户端 ID 和客户端密码运行,则无法使用 Salesforce CLI 发出 命令发送到临时组织,因为授权文件不正确 创建。auth:web:login

解决方法: 在没有客户端的情况下使用基于 Web 的流程 ID 和客户端密钥,或使用基于 JWT 的流程向组织授权。 有关开发说明,请参阅《Salesforce DX 开发人员指南》中的授权 Hub 和 Scratch 组织授权方法。Windows Defender 暂停 CLI 安装描述:在 Windows 上安装 Salesforce CLI 时, 你会看到 Windows Defender 警告。此消息是预期的,因为我们 更新了安装程序的代码签名证书。

解决方法:若要忽略此消息,请单击“运行” 反正。无法使用 Salesforce CLI 导入记录类型描述:运行命令时,我们不支持 RecordType。data:tree:import

解决方法:没有。对 Windows 上 Shell 环境的有限支持描述: Salesforce CLI 在命令提示符 () 和 Powershell 上进行了测试。有已知的 Cygwin 和 Min-GW 环境中的问题,以及 Windows 子系统的问题 Linux (WSL)。将来可能会测试和支持这些环境 释放。现在,请改用受支持的 shell。cmd.exe

解决方法: 没有。该命令不 完成执行force:apex:test:run描述: 在某些情况下,该命令不会完成执行。例子 这些情况包括 Apex 测试或 Apex 测试中的编译错误 当另一个预编译正在进行时触发另一个预编译。force:apex:test:run

解决方法: 通过键入 control-C 停止命令执行。如果命令是 作为持续集成 (CI) 作业的一部分,请尝试设置环境 变量。SFDX_PRECOMPILE_DISABLE=true

Dev Hub 和 Scratch 组织

Salesforce CLI 有时无法识别 Scratch 组织 社区描述:有时(但并非在所有情况下)Salesforce CLI 不承认与社区一起创建临时组织 特征。您无法使用 CLI 打开临时组织,即使 暂存组织在开发人员中心中列出。

解决方法:你可以试试这个 解决方法,尽管它并不能在所有情况下解决问题。删除 暂存组织,然后使用 CLI 创建新的暂存组织。 删除和重新创建临时组织将计入您的每日临时记录 组织限制。拉取社区并部署社区时出错描述:发生此错误是因为临时组织没有 所需的访客许可证。

解决方法:在您的临时组织中 定义文件,如果指定了 Communities 功能,则还要指定 站点功能。

源管理

错误:删除自定义标签后未找到任何结果force:source:status描述:在暂存中删除自定义标签后,该命令将返回错误 组织。force:source:statusNo Results Found

解决方法:选项#1:如果您只有一个或两个划痕 组织,您可以通过其 生成的用户名,请使用此解决方法。在 你的 DX project/.sfdx/org 目录,仅删除 受影响的临时组织所在的文件夹。

选项#2:如果你有 与您的 DX 项目相关的几个临时组织,而您不知道 要删除哪个临时组织的本地数据,请使用此解决方法。删除 Your DX project/.sfdx/org 目录。此目录包含所有 与项目相关的临时组织。当您运行下一个 source-tracking 命令,用于此组织或其他临时组织 (, , 或 ),CLI 将重建 该组织的源跟踪信息。source:pushsource:pullsource:status

删除 目录(在选项 #1 或选项 #2 之后),再次运行。force:source:status错误:名为“Account.PersonAccount”的“RecordType”类型的实体不能是 发现描述: 尽管您可以在临时打开个人帐户 组织,将该功能添加到您的临时组织定义中,运行或导致错误,source:pushsource:pull

解决方法: 没有。force:source:convert 不会将安装后脚本添加到包 .xml描述:如果运行 ,package.xml 不包含帖子 安装脚本。force:source:convert解决方法:若要解决此问题,请选择以下选项之一 方法:

  • 手动将元素添加到元数据目录中的 package.xml 中 产生<postInstallClass>force:source:convert
  • 手动将元素添加到发布组织中的包中,或者 要将软件包部署到的组织。

必须在对象的元数据文件中手动启用源跟踪描述:如果您在标准或自定义设备上启用 Feed 跟踪 对象,然后运行 , Feed 跟踪未启用。force:source:pull

解决方法:在您的 Salesforce 中 DX项目,手动启用标准或自定义的Feed跟踪 对象在其元数据文件 (-meta.xml) 中添加 .<enableFeeds>true</enableFeeds>无法将查找筛选器推送到临时组织描述: 执行命令推送关系源时 字段,有时会收到以下错误:force:source:push

duplicate value found: <unknown> duplicates value on record with id: <unknown> at line num, col num.

解决方法: 没有。

部署

部署时编译可能会增加临时组织中的部署时间描述: 如果您的 Apex 代码部署时间很慢,您的临时组织可能会 有设置 设置为 。enableCompileOnDeploytrue解决方法:要将其关闭,请将其设置为 ( default) 或从 Scratch 组织中删除该设置 定义。

false

{
  "orgName": "ekapner Company",
  "edition": "Developer",
  "features": [],
  "settings": {
    "lightningExperienceSettings": {
          "enableS1DesktopEnabled": true
      },
      "apexSettings": {
          "enableCompileOnDeploy": false
      }
  }
}

托管的第一代软件包

在临时组织中安装软件包时,不会执行任何测试描述: 如果将测试作为连续测试的一部分 集成过程中,当您在 Scratch 组织。

解决方法: 您可以在 软件包已安装。CLI 中用于托管包密码的新术语描述: 使用 CLI 将安装密钥添加到软件包版本或 要安装受密钥保护的软件包版本,密钥的参数名称为 。当您查看 Salesforce 用户界面中的托管包版本,相同的包 属性称为“密码”。在 API 中,对应的字段名称, “password”,保持不变。–installationkey

解决方法: 没有。

托管的第二代软件包

无法为托管软件包指定修补程序版本描述: 由四部分组成的软件包版本号包括一个补丁段,定义 作为 major.minor.patch.build。但是,您不能为 第二代托管包。如果设置了修补程序,则软件包创建将失败 包描述符中的数字。我们计划为 Winter ’20 版本中的托管包。

解决方法: 始终设置 版本号的补丁段,设置为 0。例如,1.2.0.1 是 有效,但 1.2.1.1 无效。受保护的自定义元数据和自定义设置对开发人员可见 临时组织(如果已安装的软件包共享命名空间)描述: 将机密存储在 使用受保护的自定义元数据或受保护的第二代包 自定义设置。您可以使用 相同的命名空间。但是,当您在临时组织中安装这些软件包时, 这些机密对于在 具有共享命名空间的 Scratch 组织。将来,我们可能会添加一个 “package-protected” 关键字来防止访问这些中的包机密 情况。

解决方法: 没有。

解锁套餐

受保护的自定义元数据和自定义设置对开发人员可见 临时组织(如果已安装的软件包共享命名空间)描述: 在解锁的密钥中存储密钥时要小心 使用受保护的自定义元数据或受保护的自定义设置的包。你 可以创建多个具有相同命名空间的解锁包。但是,当 您在临时组织中安装这些软件包,这些密钥对 在临时组织工作的任何开发人员,共享 命名空间。将来,我们可能会在 在这些情况下阻止访问包机密。

解决方法: 没有。

ref