├── .git2gus └── config.json ├── .github ├── actions │ ├── ctcOpen │ │ └── action.yml │ ├── determineNodeVersions │ │ ├── README.md │ │ ├── action.yml │ │ ├── dist │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── main.d.ts │ │ │ └── package.json │ │ ├── mocks │ │ │ └── sample-schedule.json │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ └── main.ts │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── generateOclifReadme │ │ └── action.yml │ ├── get-json-property │ │ └── action.yml │ ├── getGithubUserInfo │ │ └── action.yml │ ├── getPreReleaseTag │ │ └── action.yml │ ├── gitConfig │ │ └── action.yml │ ├── parse-semver │ │ └── action.yml │ ├── prNotification │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── action.yml │ │ └── notification.js │ ├── retry │ │ └── action.yml │ ├── versionInfo │ │ └── action.yml │ └── yarnInstallWithRetries │ │ └── action.yml └── workflows │ ├── automerge.yml │ ├── create-github-release.yml │ ├── ctcClose.yml │ ├── ctcOpen.yml │ ├── devScriptsUpdate.yml │ ├── externalCompile.yml │ ├── externalNut.yml │ ├── githubRelease.yml │ ├── inclusionTest.yml │ ├── lockFileCheck.yml │ ├── notify-slack-on-pr-open.yml │ ├── npmPublish.yml │ ├── nut.yml │ ├── packUploadMac.yml │ ├── packUploadWindows.yml │ ├── preventTypescriptDep.yml │ ├── publishTypedoc.yml │ ├── stampyUpload.yml │ ├── tarballs.yml │ ├── unitTest.yml │ ├── unitTestsLinux.yml │ ├── unitTestsWindows.yml │ └── validatePR.yml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.md ├── SECURITY.md └── images └── plugin-release.png /.git2gus/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "productTag": "a1aB00000004Bx8IAE", 3 | "defaultBuild": "offcore.tooling.56", 4 | "issueTypeLabels": { 5 | "feature": "USER STORY", 6 | "regression": "BUG P1", 7 | "bug": "BUG P3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/actions/ctcOpen/action.yml: -------------------------------------------------------------------------------- 1 | name: ctcOpen 2 | description: Open a CTC case for a release 3 | 4 | inputs: 5 | SF_CHANGE_CASE_SFDX_AUTH_URL: 6 | description: Auth URL 7 | required: true 8 | SF_CHANGE_CASE_TEMPLATE_ID: 9 | description: Change Case template ID 10 | required: true 11 | SF_CHANGE_CASE_CONFIGURATION_ITEM: 12 | description: Change Case config item 13 | required: true 14 | SVC_CLI_BOT_GITHUB_TOKEN: 15 | description: Github token 16 | required: true 17 | 18 | outputs: 19 | changeCaseId: 20 | description: Id for the change case created 21 | value: ${{ steps.ctc.outputs.ctcId }} 22 | 23 | runs: 24 | using: composite 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: lts/* 31 | cache: npm 32 | 33 | - run: npm install -g @salesforce/change-case-management --omit=dev 34 | shell: bash 35 | 36 | - name: Open CTC case 37 | id: ctc 38 | uses: salesforcecli/github-workflows/.github/actions/retry@main 39 | with: 40 | max_attempts: 5 41 | command: | 42 | CTC_RESULT=$(sfchangecase create --location ${{ github.repositoryUrl }} --release ${{ github.repository }}.$(date +%F) --json) 43 | 44 | STATUS=$(printf '%s' "$CTC_RESULT" | jq -r '.status') 45 | CTC_ID=$(printf '%s' "$CTC_RESULT" | jq -r '.result.id') 46 | 47 | if [[ "$STATUS" == "0" && "$CTC_ID" != "null" ]]; then 48 | echo "Successfully created case with ID: $CTC_ID" 49 | echo "ctcId=$CTC_ID" >> "$GITHUB_OUTPUT" 50 | else 51 | echo "CTC failed to open a case. Result:" 52 | echo "$CTC_RESULT" 53 | exit 1 54 | fi 55 | env: 56 | SF_CHANGE_CASE_SFDX_AUTH_URL: ${{ inputs.SF_CHANGE_CASE_SFDX_AUTH_URL}} 57 | SF_CHANGE_CASE_TEMPLATE_ID: ${{ inputs.SF_CHANGE_CASE_TEMPLATE_ID}} 58 | SF_CHANGE_CASE_CONFIGURATION_ITEM: ${{ inputs.SF_CHANGE_CASE_CONFIGURATION_ITEM}} 59 | 60 | - run: echo "[INFO] Change Case ID is:\ $STEPS_CTC_CTCID" 61 | shell: bash 62 | env: 63 | STEPS_CTC_CTCID: ${{ steps.ctc.outputs.ctcId }} 64 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/README.md: -------------------------------------------------------------------------------- 1 | # Determine Node Versions Action 2 | 3 | This action will fetch the Node.js Release Schedule JSON and find all versions that are "open". The action sets the `nodeVersions` output with an array of Node versions. These versions will be used to run tests against. Example in `.github/workflows/unitTestsLinux.yml` 4 | 5 | ### Installing and building 6 | 7 | This packages uses `ncc` to create a single javascript file for execution. Running the following will bundle a new `dist/index.js` file that needs to be committed to the repo. 8 | 9 | ```shell 10 | cd .github/actions/determineNodeVersions 11 | yarn install 12 | yarn build 13 | ``` 14 | 15 | ### Disabling specific versions 16 | 17 | You can disabled Node versions at the Org or Repo level by setting an environment variable for the versions you wish to disable. For example: 18 | 19 | ```shell 20 | NODE_DISABLE_VERSIONS='18' 21 | NODE_DISABLE_VERSIONS='18,23' 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/action.yml: -------------------------------------------------------------------------------- 1 | name: Determine node versions 2 | description: "Get all the supported node versions" 3 | inputs: 4 | nodeVersionOverride: 5 | description: Set node to a specific Semver version 6 | required: false 7 | nodeDisableVersions: 8 | description: A string of major version(s) to disable (18 or 18,23) 9 | required: false 10 | outputs: 11 | nodeVersions: 12 | description: Node versions to be consumed by a workflow matrix 13 | value: ${{ steps.node-versions.outputs.nodeVersions }} 14 | runs: 15 | using: 'composite' 16 | steps: 17 | # Note: this is not typically how you would run javascript in an action 18 | # This is need so that we can get the correct node version used elsewhere 19 | - name: Set up Node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ inputs.nodeVersionOverride || 'lts/*' }} 23 | - name: Run main script 24 | shell: bash 25 | id: node-versions 26 | run: node "$GITHUB_ACTION_PATH/dist/index.js" 27 | env: 28 | NODE_DISABLE_VERSIONS: ${{ inputs.nodeDisableVersions }} 29 | 30 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/dist/main.d.ts: -------------------------------------------------------------------------------- 1 | export declare const getVersions: () => Promise; 2 | export declare const run: () => Promise; 3 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/mocks/sample-schedule.json: -------------------------------------------------------------------------------- 1 | { 2 | "v0.8": { 3 | "start": "2012-06-25", 4 | "end": "2014-07-31" 5 | }, 6 | "v0.10": { 7 | "start": "2013-03-11", 8 | "end": "2016-10-31" 9 | }, 10 | "v0.12": { 11 | "start": "2015-02-06", 12 | "end": "2016-12-31" 13 | }, 14 | "v4": { 15 | "start": "2015-09-08", 16 | "lts": "2015-10-12", 17 | "maintenance": "2017-04-01", 18 | "end": "2018-04-30", 19 | "codename": "Argon" 20 | }, 21 | "v5": { 22 | "start": "2015-10-29", 23 | "maintenance": "2016-04-30", 24 | "end": "2016-06-30" 25 | }, 26 | "v6": { 27 | "start": "2016-04-26", 28 | "lts": "2016-10-18", 29 | "maintenance": "2018-04-30", 30 | "end": "2019-04-30", 31 | "codename": "Boron" 32 | }, 33 | "v7": { 34 | "start": "2016-10-25", 35 | "maintenance": "2017-04-30", 36 | "end": "2017-06-30" 37 | }, 38 | "v8": { 39 | "start": "2017-05-30", 40 | "lts": "2017-10-31", 41 | "maintenance": "2019-01-01", 42 | "end": "2019-12-31", 43 | "codename": "Carbon" 44 | }, 45 | "v9": { 46 | "start": "2017-10-01", 47 | "maintenance": "2018-04-01", 48 | "end": "2018-06-30" 49 | }, 50 | "v10": { 51 | "start": "2018-04-24", 52 | "lts": "2018-10-30", 53 | "maintenance": "2020-05-19", 54 | "end": "2021-04-30", 55 | "codename": "Dubnium" 56 | }, 57 | "v11": { 58 | "start": "2018-10-23", 59 | "maintenance": "2019-04-22", 60 | "end": "2019-06-01" 61 | }, 62 | "v12": { 63 | "start": "2019-04-23", 64 | "lts": "2019-10-21", 65 | "maintenance": "2020-11-30", 66 | "end": "2022-04-30", 67 | "codename": "Erbium" 68 | }, 69 | "v13": { 70 | "start": "2019-10-22", 71 | "maintenance": "2020-04-01", 72 | "end": "2020-06-01" 73 | }, 74 | "v14": { 75 | "start": "2020-04-21", 76 | "lts": "2020-10-27", 77 | "maintenance": "2021-10-19", 78 | "end": "2023-04-30", 79 | "codename": "Fermium" 80 | }, 81 | "v15": { 82 | "start": "2020-10-20", 83 | "maintenance": "2021-04-01", 84 | "end": "2021-06-01" 85 | }, 86 | "v16": { 87 | "start": "2021-04-20", 88 | "lts": "2021-10-26", 89 | "maintenance": "2022-10-18", 90 | "end": "2023-09-11", 91 | "codename": "Gallium" 92 | }, 93 | "v17": { 94 | "start": "2021-10-19", 95 | "maintenance": "2022-04-01", 96 | "end": "2022-06-01" 97 | }, 98 | "v18": { 99 | "start": "2022-04-19", 100 | "lts": "2022-10-25", 101 | "maintenance": "2023-10-18", 102 | "end": "2025-04-30", 103 | "codename": "Hydrogen" 104 | }, 105 | "v19": { 106 | "start": "2022-10-18", 107 | "maintenance": "2023-04-01", 108 | "end": "2023-06-01" 109 | }, 110 | "v20": { 111 | "start": "2023-04-18", 112 | "lts": "2023-10-24", 113 | "maintenance": "2024-10-22", 114 | "end": "2026-04-30", 115 | "codename": "Iron" 116 | }, 117 | "v21": { 118 | "start": "2023-10-17", 119 | "maintenance": "2024-04-01", 120 | "end": "2024-06-01" 121 | }, 122 | "v22": { 123 | "start": "2024-04-24", 124 | "lts": "2024-10-29", 125 | "maintenance": "2025-10-21", 126 | "end": "2027-04-30", 127 | "codename": "Jod" 128 | }, 129 | "v23": { 130 | "start": "2024-10-16", 131 | "maintenance": "2025-04-01", 132 | "end": "2025-06-01" 133 | }, 134 | "v24": { 135 | "start": "2025-04-22", 136 | "lts": "2025-10-28", 137 | "maintenance": "2026-10-20", 138 | "end": "2028-04-30", 139 | "codename": "" 140 | } 141 | } -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "determinenodeversions", 3 | "version": "1.0.0", 4 | "description": "Determine the active Node versions to test against", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "ncc build src/index.ts -o dist", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "type": "module", 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@actions/core": "^1.11.1" 15 | }, 16 | "devDependencies": { 17 | "@salesforce/dev-scripts": "^10.2.10", 18 | "@vercel/ncc": "^0.38.2", 19 | "typescript": "^5.6.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/src/index.ts: -------------------------------------------------------------------------------- 1 | // The entrypoint for the action 2 | import { run } from './main.js' 3 | 4 | await run() 5 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { setFailed, setOutput, notice } from '@actions/core'; 9 | 10 | type VersionData = { 11 | start: string; 12 | end: string; 13 | lts?: string; 14 | maintenance?: string; 15 | codename?: string; 16 | }; 17 | 18 | type VersionsJson = { 19 | [key: string]: VersionData; 20 | }; 21 | 22 | export const getVersions = async () => { 23 | // Get the node release schedule json 24 | const response = await fetch("https://raw.githubusercontent.com/nodejs/Release/main/schedule.json"); 25 | const json = await response.json() as VersionsJson; 26 | 27 | // This version of node was installed in the composite action before this script runs 28 | // This defaults to 'lts/*' but allows for the user to override it with an env var 29 | // Below we will replace the matching major version with this more specific version 30 | // This will ensure that the unit tests run on the same version that will be shipped 31 | const installedNode = process.versions.node; 32 | 33 | // Comma separated list of major versions to disable 34 | // For example: Set `NODE_DISABLE_VERSIONS` to `18,23` 35 | const disableVersions = process.env.NODE_DISABLE_VERSIONS?.split(',') || []; 36 | 37 | 38 | const today = new Date(); 39 | 40 | // Build an array of versions that the current date is between the start and end dates 41 | const versions = Object.keys(json) 42 | .filter((version) => { 43 | const startDate = new Date(json[version].start); 44 | const endDate = new Date(json[version].end); 45 | return today >= startDate && today <= endDate; 46 | }) 47 | .map((version) => version.replace('v', '')) 48 | // Remove versions that are disabled via env var 49 | .filter((version) => { 50 | if (disableVersions.includes(version)) { 51 | notice(`Node version ${version} is disabled via env var`); 52 | return false; 53 | } 54 | return true; 55 | }) 56 | // Override the matching major version with the installed version 57 | // for example if the installed version is 18.15.1, replace 18 with 18.15.1 58 | .map((version) => { 59 | if (version === installedNode.split('.')[0]) { 60 | console.log(`Node version ${version} is overridden to ${installedNode}`); 61 | return installedNode; 62 | } 63 | return version; 64 | }) 65 | 66 | if (versions.length === 0) { 67 | throw new Error('No versions found'); 68 | } 69 | 70 | console.log('Found versions: ', versions); 71 | return versions; 72 | }; 73 | 74 | export const run = async () => { 75 | try { 76 | const versions = await getVersions(); 77 | 78 | setOutput('nodeVersions', versions) 79 | } catch (error: any) { 80 | setFailed(error.message); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /.github/actions/determineNodeVersions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@salesforce/dev-config/tsconfig-strict-esm", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "baseUrl": "." 7 | }, 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.github/actions/generateOclifReadme/action.yml: -------------------------------------------------------------------------------- 1 | name: generate-oclif-readme 2 | description: Generate oclif readme if it is an oclif plugin 3 | 4 | inputs: 5 | skip-on-empty: 6 | description: Should release be skipped if there are no semantic commits? 7 | required: true 8 | pre-release: 9 | description: Is this release will be a prerelease? 10 | required: true 11 | pre-release-identifier: 12 | description: The name of the prerelease channel (e.g. dev, beta) 13 | multi: 14 | description: Create a different markdown page for each topic. 15 | 16 | runs: 17 | using: composite 18 | steps: 19 | # First we check to see if this is an oclif plugin 20 | # If it is not, we will skip readme generation 21 | - name: Check that oclif config exists 22 | id: is-oclif-plugin 23 | shell: bash 24 | run: echo "bool=$(jq 'has("oclif")' package.json)" >> "$GITHUB_OUTPUT" 25 | 26 | - name: Get the next version for oclif readme 27 | id: next-version 28 | if: ${{ steps.is-oclif-plugin.outputs.bool == 'true' }} 29 | uses: TriPSs/conventional-changelog-action@3a392e9aa44a72686b0fc13259a90d287dd0877c 30 | with: 31 | tag-prefix: "" 32 | skip-version-file: true # Do not update the version in the package.json 33 | skip-commit: true # Do not commit the version bump 34 | git-push: false # Do not push to Github 35 | skip-tag: true # Do not tag release 36 | skip-on-empty: ${{ inputs.skip-on-empty }} 37 | pre-release: ${{ inputs.pre-release }} 38 | pre-release-identifier: ${{ inputs.pre-release-identifier }} 39 | output-file: "false" # No changelog file 40 | 41 | - name: Generate oclif readme 42 | if: ${{ steps.is-oclif-plugin.outputs.bool == 'true' && steps.next-version.outputs.skipped == 'false' }} 43 | shell: bash 44 | run: | 45 | yarn install 46 | yarn tsc 47 | yarn oclif readme \ 48 | --no-aliases \ 49 | --version "$STEPS_NEXT_VERSION_TAG" \ 50 | ${{ inputs.multi == 'true' && '--multi' || '' }} \ 51 | --repository-prefix "<%- repo %>/blob/<%- version %>/<%- commandPath %>" \ 52 | || echo "::warning::'oclif readme' failed. Check the logs." 53 | env: 54 | STEPS_NEXT_VERSION_TAG: ${{ steps.next-version.outputs.tag }} 55 | -------------------------------------------------------------------------------- /.github/actions/get-json-property/action.yml: -------------------------------------------------------------------------------- 1 | name: get-json-property 2 | description: Get a property from a json file with dot notation 3 | 4 | # Examples: 5 | # prop_path: version 6 | # prop_path: devDependencies.@salesforce/dev-scripts 7 | 8 | inputs: 9 | path: 10 | required: true 11 | description: Json file to look up prop (package.json) 12 | prop_path: 13 | required: true 14 | description: dot notation property to find (version) 15 | 16 | outputs: 17 | prop: 18 | description: The value of the prop_path 19 | value: ${{ steps.parse.outputs.prop }} 20 | 21 | runs: 22 | using: "composite" 23 | steps: 24 | - name: Get property from json file 25 | id: parse 26 | uses: actions/github-script@v7 27 | with: 28 | result-encoding: string 29 | script: | 30 | try { 31 | const fs = require('fs') 32 | 33 | var path = process.env.INPUTS_PATH; 34 | var propPath = process.env.INPUTS_PROP_PATH; 35 | 36 | const json = JSON.parse(fs.readFileSync(path)) 37 | 38 | // https://stackoverflow.com/a/43849204 39 | const result = propPath.split('.').reduce((p,c)=>p&&p[c]||null, json) 40 | 41 | if (result) { 42 | core.setOutput('prop', result) 43 | } else { 44 | core.setFailed(`Property '${propPath}' not found in '${path}'`) 45 | } 46 | } catch(err) { 47 | core.setFailed(err) 48 | } 49 | env: 50 | INPUTS_PATH: ${{ inputs.path }} 51 | INPUTS_PROP_PATH: ${{ inputs.prop_path }} 52 | -------------------------------------------------------------------------------- /.github/actions/getGithubUserInfo/action.yml: -------------------------------------------------------------------------------- 1 | name: getGithubUserInfo 2 | description: Looks up Github user info for a Token 3 | 4 | inputs: 5 | SVC_CLI_BOT_GITHUB_TOKEN: 6 | description: Github token 7 | required: true 8 | 9 | outputs: 10 | username: 11 | description: Github username associated with the provided token 12 | value: ${{ steps.user-info.outputs.username }} 13 | email: 14 | description: Github email associated with the provided token 15 | value: ${{ steps.user-info.outputs.email }} 16 | 17 | runs: 18 | using: composite 19 | steps: 20 | - name: Validate token is passed 21 | if: inputs.SVC_CLI_BOT_GITHUB_TOKEN == '' 22 | uses: actions/github-script@v7 23 | with: 24 | script: core.setFailed("You must pass a Github Token with repo write access as SVC_CLI_BOT_GITHUB_TOKEN") 25 | 26 | - name: Get Github user info 27 | id: user-info 28 | shell: bash 29 | run: | 30 | USER_INFO=$(gh api user) 31 | 32 | GH_USERNAME=$(echo $USER_INFO | jq -r '.login') 33 | EMAIL=$(echo $USER_INFO | jq -r '.email') 34 | 35 | echo "username=$(echo $USER_INFO | jq -r '.login')" >> "$GITHUB_OUTPUT" 36 | 37 | if [[ -z "$EMAIL" || $EMAIL == 'null' ]]; then 38 | # Email is empty (likely set to private) 39 | ID=$(echo $USER_INFO | jq -r '.id') 40 | 41 | # https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address 42 | EMAIL="$ID+$GH_USERNAME@users.noreply.github.com" 43 | fi 44 | 45 | echo "email=$EMAIL" >> "$GITHUB_OUTPUT" 46 | env: 47 | GH_TOKEN: ${{ inputs.SVC_CLI_BOT_GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /.github/actions/getPreReleaseTag/action.yml: -------------------------------------------------------------------------------- 1 | name: get prerelease tag 2 | description: read package.json and return the version suffix (ex 'beta' if x.y.z-beta.0) 3 | 4 | outputs: 5 | tag: 6 | value: ${{ steps.parsed.outputs.prerelease }} 7 | description: version suffix (ex 'beta' if x.y.z-beta.0 ), if exists in package.json 8 | version: 9 | value: ${{ steps.package-version.outputs.prop }} 10 | description: version from pjson 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - uses: salesforcecli/github-workflows/.github/actions/get-json-property@main 16 | id: package-version 17 | with: 18 | path: "package.json" 19 | prop_path: "version" 20 | 21 | - name: Echo found version 22 | shell: bash 23 | run: echo "[INFO] Version found:\ $STEPS_PACKAGE_VERSION_PROP" 24 | env: 25 | STEPS_PACKAGE_VERSION_PROP: ${{ steps.package-version.outputs.prop }} 26 | 27 | - name: Parse semver version 28 | uses: salesforcecli/github-workflows/.github/actions/parse-semver@main 29 | id: parsed 30 | with: 31 | input_string: ${{ steps.package-version.outputs.prop }} 32 | 33 | - name: Echo found prerelease tag 34 | shell: bash 35 | run: echo "[INFO] Prerelease tag is:\ $STEPS_PARSED_PRERELEASE" 36 | env: 37 | STEPS_PARSED_PRERELEASE: ${{ steps.parsed.outputs.prerelease }} 38 | -------------------------------------------------------------------------------- /.github/actions/gitConfig/action.yml: -------------------------------------------------------------------------------- 1 | name: gitConfig 2 | description: "Sets git username/email and push behavior" 3 | 4 | inputs: 5 | username: 6 | description: "Github username to set as `user.name`. TIP: Use getGithubUserInfo to look up username from a Github token." 7 | required: true 8 | email: 9 | description: "Github email to set as `user.email`. TIP: Use getGithubUserInfo to look up email from a Github token." 10 | required: true 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - name: Set git config 16 | shell: bash 17 | run: | 18 | git config --global push.default current 19 | git config --global user.name "$INPUTS_USERNAME" 20 | git config --global user.email "$INPUTS_EMAIL" 21 | env: 22 | INPUTS_USERNAME: ${{ inputs.username }} 23 | INPUTS_EMAIL: ${{ inputs.email }} 24 | -------------------------------------------------------------------------------- /.github/actions/parse-semver/action.yml: -------------------------------------------------------------------------------- 1 | name: Parse Semver 2 | 3 | description: Extracts major, minor, patch, prerelease, and full version from a given semver. 4 | 5 | inputs: 6 | input_string: 7 | description: 'The semver version to parse' 8 | required: true 9 | 10 | outputs: 11 | major: 12 | description: 'MAJOR part of the semver' 13 | value: ${{ steps.parse.outputs.major }} 14 | minor: 15 | description: 'MINOR part of the semver' 16 | value: ${{ steps.parse.outputs.minor }} 17 | patch: 18 | description: 'PATCH part of the semver' 19 | value: ${{ steps.parse.outputs.patch }} 20 | prerelease: 21 | description: 'Prerelease part of the semver' 22 | value: ${{ steps.parse.outputs.prerelease }} 23 | fullversion: 24 | description: 'Full representation of the semver' 25 | value: ${{ steps.parse.outputs.fullversion }} 26 | 27 | runs: 28 | using: "composite" 29 | steps: 30 | - name: Parse Semver 31 | id: parse 32 | shell: bash 33 | run: | 34 | FULL_VERSION="$INPUTS_INPUT_STRING" 35 | VERSION="${FULL_VERSION#v}" 36 | 37 | # Filter out non-semver characters 38 | CLEAN_VERSION=$(echo "$VERSION" | sed -E 's/[^0-9a-zA-Z.-]+//g') 39 | 40 | if [[ "$CLEAN_VERSION" != "$VERSION" ]]; then 41 | echo "Semver version includes invalid characters. Exiting..." 42 | exit 1 43 | fi 44 | 45 | # Split version into parts 46 | IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" 47 | 48 | # Check if PATCH contains prerelease info and extract it 49 | if [[ "$PATCH" == *-* ]]; then 50 | PRERELEASE=$(echo "$PATCH" | cut -d- -f2 | cut -d. -f1) 51 | PATCH=$(echo "$PATCH" | cut -d- -f1) 52 | fi 53 | 54 | # Set outputs 55 | echo "major=$MAJOR" >> "$GITHUB_OUTPUT" 56 | echo "minor=$MINOR" >> "$GITHUB_OUTPUT" 57 | echo "patch=$PATCH" >> "$GITHUB_OUTPUT" 58 | echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" 59 | echo "fullversion=$FULL_VERSION" >> "$GITHUB_OUTPUT" 60 | env: 61 | INPUTS_INPUT_STRING: ${{ inputs.input_string }} 62 | 63 | - name: Exit if major, minor, or patch not found 64 | if: ${{ !steps.parse.outputs.major || !steps.parse.outputs.minor || !steps.parse.outputs.patch }} 65 | uses: actions/github-script@v7 66 | with: 67 | script: | 68 | core.setFailed(`Error parsing semver: ${process.env.INPUTS_INPUT_STRING} 69 | Major: ${process.env.STEPS_PARSE_MAJOR} 70 | Minor: ${process.env.STEPS_PARSE_MINOR} 71 | Patch: ${process.env.STEPS_PARSE_PATCH}`) 72 | env: 73 | INPUTS_INPUT_STRING: ${{ inputs.input_string }} 74 | STEPS_PARSE_MAJOR: ${{ steps.parse.outputs.major }} 75 | STEPS_PARSE_MINOR: ${{ steps.parse.outputs.minor }} 76 | STEPS_PARSE_PATCH: ${{ steps.parse.outputs.patch }} -------------------------------------------------------------------------------- /.github/actions/prNotification/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:current-alpine 2 | 3 | WORKDIR /usr/app/ 4 | RUN npm install node-fetch@^3 --production 5 | COPY notification.js ./ 6 | RUN chmod +x ./notification.js 7 | 8 | ENTRYPOINT ["node", "/usr/app/notification.js"] 9 | -------------------------------------------------------------------------------- /.github/actions/prNotification/README.md: -------------------------------------------------------------------------------- 1 | # Pull Request Notification Action 2 | A github action to notify a webhook about Pull Request events. 3 | 4 | Mainly used to notify Slack when Pull Requests are opened. 5 | 6 | ## Example 7 | You can see an example in this very repo at [`notify-slack-on-pr-open.yaml`](../../workflows/notify-slack-on-pr-open.yml) 8 | 9 | ## Usage 10 | Add the following yaml to your github actions workflows folder. 11 | > This uses a Slack Webhook URL but you could use any URL for any 3rd party service. 12 | 13 | ```yaml 14 | name: Slack Pull Request Notification 15 | 16 | on: 17 | pull_request: 18 | types: [opened, reopened] 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Notify Slack on PR open 25 | env: 26 | WEBHOOK_URL : ${{ secrets.SLACK_WEBHOOK_URL }} 27 | PULL_REQUEST_AUTHOR_ICON_URL : ${{ github.event.pull_request.user.avatar_url }} 28 | PULL_REQUEST_AUTHOR_NAME : ${{ github.event.pull_request.user.login }} 29 | PULL_REQUEST_AUTHOR_PROFILE_URL: ${{ github.event.pull_request.user.html_url }} 30 | PULL_REQUEST_BASE_BRANCH_NAME : ${{ github.event.pull_request.base.ref }} 31 | PULL_REQUEST_COMPARE_BRANCH_NAME : ${{ github.event.pull_request.head.ref }} 32 | PULL_REQUEST_NUMBER : ${{ github.event.pull_request.number }} 33 | PULL_REQUEST_REPO: ${{ github.event.pull_request.head.repo.name }} 34 | PULL_REQUEST_TITLE : ${{ github.event.pull_request.title }} 35 | PULL_REQUEST_URL : ${{ github.event.pull_request.html_url }} 36 | uses: salesforcecli/github-workflows/.github/actions/prNotification@main 37 | ``` 38 | 39 | ### Arguments 40 | #### SLACK_WEBHOOK_URL 41 | Set this as a repository or organization secret on Github. You will get the value for this from Slack. 42 | 43 | #### PULL_REQUEST_* 44 | These are pulled straight from [Github's API documentation](https://developer.github.com/v3/pulls/). 45 | -------------------------------------------------------------------------------- /.github/actions/prNotification/action.yml: -------------------------------------------------------------------------------- 1 | name: "Salesforce CLI PR Notifications" 2 | author: "RodEsp" 3 | description: "Notifies a webhook URL about PR events." 4 | runs: 5 | using: "docker" 6 | image: "Dockerfile" 7 | branding: 8 | icon: "git-pull-request" 9 | color: "green" -------------------------------------------------------------------------------- /.github/actions/prNotification/notification.js: -------------------------------------------------------------------------------- 1 | const fetch = async (...args) => await import('node-fetch').then(({default: fetch}) => fetch(...args)); 2 | 3 | const authorIconUrl = process.env.PULL_REQUEST_AUTHOR_ICON_URL; 4 | const authorName = process.env.PULL_REQUEST_AUTHOR_NAME; 5 | const authorProfileUrl = process.env.PULL_REQUEST_AUTHOR_PROFILE_URL; 6 | const baseBranchName = process.env.PULL_REQUEST_BASE_BRANCH_NAME; 7 | const compareBranchName = process.env.PULL_REQUEST_COMPARE_BRANCH_NAME; 8 | const PRNum = process.env.PULL_REQUEST_NUMBER; 9 | const PRRepo = process.env.PULL_REQUEST_REPO; 10 | const PRTitle = process.env.PULL_REQUEST_TITLE; 11 | const PRUrl = process.env.PULL_REQUEST_URL; 12 | const webhookUrl = process.env.WEBHOOK_URL; 13 | 14 | const message = { 15 | blocks: [ 16 | { 17 | type: 'header', 18 | text: { 19 | "type": "plain_text", 20 | "text": `:pr: Pull Request Opened in ${PRRepo}`, 21 | "emoji": true 22 | } 23 | }, 24 | { 25 | "type": "divider" 26 | }, 27 | { 28 | type: 'section', 29 | fields: [ 30 | { 31 | type: 'mrkdwn', 32 | text: `*<${PRUrl}|#${PRNum} ${PRTitle}>*` 33 | } 34 | ] 35 | }, 36 | { 37 | type: 'context', 38 | elements: [ 39 | { 40 | type: 'plain_text', 41 | text: baseBranchName 42 | }, 43 | { 44 | "type": "plain_text", 45 | "text": ":arrow_left:", 46 | "emoji": true 47 | }, 48 | { 49 | type: 'plain_text', 50 | text: compareBranchName 51 | } 52 | ] 53 | }, 54 | { 55 | type: 'context', 56 | elements: [ 57 | { 58 | type: 'mrkdwn', 59 | text: '*Author*' 60 | }, 61 | { 62 | type: 'image', 63 | image_url: authorIconUrl, 64 | alt_text: 'GitHub Icon' 65 | }, 66 | { 67 | type: 'mrkdwn', 68 | text: `<${authorProfileUrl}|${authorName}>` 69 | } 70 | ] 71 | } 72 | ] 73 | }; 74 | 75 | if (authorName !== 'SF-CLI-BOT' && authorName !== 'dependabot[bot]' && authorName !== 'svc-cli-bot') { 76 | fetch(webhookUrl, { 77 | method: 'post', 78 | body: JSON.stringify(message), 79 | headers: { 'Content-Type': 'application/json' } 80 | }).then(res => console.log('Successfully notified slack!', `Response: ${JSON.stringify(message, null, 4)}`)) 81 | .catch(err => console.log('Encountered an error when attempting to notify slack: ', err, `Message payload: ${JSON.stringify(message, null, 4)}`)); 82 | } 83 | -------------------------------------------------------------------------------- /.github/actions/retry/action.yml: -------------------------------------------------------------------------------- 1 | name: retry 2 | description: a wrapper for nick-fields/retry with sensible defaults to make our workflow files cleaner 3 | 4 | inputs: 5 | command: 6 | required: true 7 | description: The command to run 8 | max_attempts: 9 | description: Number of attempts to make before failing the step 10 | default: "3" 11 | retry_wait_seconds: 12 | default: "60" 13 | description: Number of seconds to wait before attempting the next retry 14 | timeout_minutes: 15 | default: "60" 16 | description: Minutes to wait before attempt times out. Must only specify either minutes or seconds 17 | retry_on: 18 | default: "any" 19 | description: Event to retry on. Currently supports [any (default), timeout, error] 20 | 21 | runs: 22 | using: "composite" 23 | steps: 24 | - uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 25 | with: 26 | max_attempts: ${{ inputs.max_attempts }} 27 | retry_wait_seconds: ${{ inputs.retry_wait_seconds }} 28 | command: ${{ inputs.command }} 29 | timeout_minutes: ${{ inputs.timeout_minutes }} 30 | retry_on: ${{ inputs.retry_on }} 31 | -------------------------------------------------------------------------------- /.github/actions/versionInfo/action.yml: -------------------------------------------------------------------------------- 1 | name: version-info 2 | description: gets information about a npm tag or version 3 | 4 | inputs: 5 | version: 6 | required: true 7 | description: an npm version number (like 7.180.8) or an npm tag like latest/latest-rc 8 | npmPackage: 9 | required: false 10 | default: sfdx-cli 11 | description: full npm package name, including namespace if one exists 12 | 13 | outputs: 14 | version: 15 | value: ${{ steps.getNumericalVersion.outputs.version }} 16 | description: guaranteed to be an npm number, even if a tag was passed in 17 | url: 18 | value: https://developer.salesforce.com/${{ steps.getS3Folder.outputs.folder }}/versions/${{ steps.getNumericalVersion.outputs.version }}/${{ steps.getSha.outputs.sha }}/${{ steps.getCli.outputs.cli }}-v${{ steps.getNumericalVersion.outputs.version }}-${{ steps.getSha.outputs.sha }}-linux-x64.tar.xz 19 | description: url where the xz tarball for this version can be downloaded 20 | sha: 21 | description: short sha for the matching version 22 | value: ${{ steps.getSha.outputs.sha }} 23 | 24 | runs: 25 | using: composite 26 | steps: 27 | - id: getSha 28 | shell: bash 29 | run: echo "sha=$(npm view $INPUTS_NPM_PACKAGE@$INPUTS_VERSION --json | jq -r '.gitHead[0:7]')" >> "$GITHUB_OUTPUT" 30 | env: 31 | INPUTS_NPM_PACKAGE: ${{ inputs.npmPackage }} 32 | INPUTS_VERSION: ${{ inputs.version }} 33 | 34 | - id: getNumericalVersion 35 | shell: bash 36 | run: echo "version=$(npm view $INPUTS_NPM_PACKAGE@$INPUTS_VERSION --json | jq -r '.version')" >> "$GITHUB_OUTPUT" 37 | env: 38 | INPUTS_NPM_PACKAGE: ${{ inputs.npmPackage }} 39 | INPUTS_VERSION: ${{ inputs.version }} 40 | 41 | - id: getCli 42 | shell: bash 43 | run: echo "cli=$(npm view $INPUTS_NPM_PACKAGE@$INPUTS_VERSION --json | jq -r '.oclif.bin')" >> "$GITHUB_OUTPUT" 44 | env: 45 | INPUTS_NPM_PACKAGE: ${{ inputs.npmPackage }} 46 | INPUTS_VERSION: ${{ inputs.version }} 47 | 48 | - id: getS3Folder 49 | shell: bash 50 | run: echo "folder=$(npm view $INPUTS_NPM_PACKAGE@$INPUTS_VERSION --json | jq -r '.oclif.update.s3.folder')" >> "$GITHUB_OUTPUT" 51 | env: 52 | INPUTS_NPM_PACKAGE: ${{ inputs.npmPackage }} 53 | INPUTS_VERSION: ${{ inputs.version }} 54 | 55 | - name: Echo info 56 | shell: bash 57 | run: | 58 | echo "[INFO] version-info outputs:" 59 | echo "cli: $STEPS_GETCLI_CLI" 60 | echo "version: $STEPS_GETNUMERICALVERSION_VERSION" 61 | echo "sha: $STEPS_GETSHA_SHA" 62 | echo "folder (xz): https://developer.salesforce.com/$STEPS_GETS3FOLDER_FOLDER/versions/$STEPS_GETNUMERICALVERSION_VERSION/$STEPS_GETSHA_SHA/$STEPS_GETCLI_CLI-v$STEPS_GETNUMERICALVERSION_VERSION-$STEPS_GETSHA_SHA-linux-x64.tar.xz" 63 | env: 64 | STEPS_GETCLI_CLI: ${{ steps.getCli.outputs.cli }} 65 | STEPS_GETNUMERICALVERSION_VERSION: ${{ steps.getNumericalVersion.outputs.version }} 66 | STEPS_GETSHA_SHA: ${{ steps.getSha.outputs.sha }} 67 | STEPS_GETS3FOLDER_FOLDER: ${{ steps.getS3Folder.outputs.folder }} 68 | -------------------------------------------------------------------------------- /.github/actions/yarnInstallWithRetries/action.yml: -------------------------------------------------------------------------------- 1 | name: yarn-install-with-retries 2 | description: "wraps yarn install with retries/timeout to handle network failures" 3 | inputs: 4 | ignore-scripts: 5 | default: 'false' 6 | description: "Skip pre/post install scripts" 7 | runs: 8 | using: composite 9 | steps: 10 | - name: yarn install 11 | uses: salesforcecli/github-workflows/.github/actions/retry@main 12 | with: 13 | command: yarn install --network-timeout 600000 ${{ inputs.ignore-scripts == 'true' && '--ignore-scripts' || '' }} 14 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | SVC_CLI_BOT_GITHUB_TOKEN: 5 | description: override the autogenerated github token 6 | required: false 7 | inputs: 8 | mergeMethod: 9 | required: false 10 | description: merge, squash, or rebase 11 | default: merge 12 | type: string 13 | maxVersionBump: 14 | required: false 15 | description: "['patch', 'minor', 'major'] maximum allowed version bump. You probably don't want to automate major version bumps!" 16 | type: string 17 | default: "minor" 18 | skipCI: 19 | required: false 20 | description: Optionally skip ci builds on merges into main 21 | type: boolean 22 | default: false 23 | 24 | jobs: 25 | dependabot-automerge: 26 | runs-on: "ubuntu-latest" 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 31 | 32 | - uses: actions/setup-node@v4 33 | with: 34 | node-version: lts/* 35 | cache: npm 36 | 37 | - run: npm install -g @salesforce/plugin-release-management --omit=dev 38 | 39 | - name: run automerge command 40 | run: sf-release dependabot:automerge --merge-method "$INPUTS_MERGE_METHOD" --max-version-bump "$INPUTS_MAX_VERSION_BUMP" --owner $GITHUB_REPOSITORY_OWNER --repo $(basename $GITHUB_REPOSITORY) ${{ inputs.skipCI && '--skip-ci' || '' }} 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 43 | INPUTS_MERGE_METHOD: ${{ inputs.mergeMethod }} 44 | INPUTS_MAX_VERSION_BUMP: ${{ inputs.maxVersionBump }} 45 | -------------------------------------------------------------------------------- /.github/workflows/create-github-release.yml: -------------------------------------------------------------------------------- 1 | name: create-github-release 2 | on: 3 | workflow_call: 4 | secrets: 5 | SVC_CLI_BOT_GITHUB_TOKEN: 6 | description: A Github PAT with repo write access. 7 | required: true 8 | 9 | inputs: 10 | prerelease: 11 | type: string 12 | description: "Name to use for the prerelease: beta, dev, etc." 13 | skip-on-empty: 14 | type: boolean 15 | default: true 16 | description: "Should release be skipped if there are no semantic commits?" 17 | generate-readme: 18 | type: boolean 19 | default: true 20 | description: "Generate oclif readme" 21 | readme-multi: 22 | type: boolean 23 | description: "Create a different markdown page for each topic." 24 | default: false 25 | 26 | jobs: 27 | release: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Get Github user info 31 | id: github-user-info 32 | uses: salesforcecli/github-workflows/.github/actions/getGithubUserInfo@main 33 | with: 34 | SVC_CLI_BOT_GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 35 | 36 | - uses: actions/checkout@v4 37 | with: 38 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 39 | 40 | - uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main 41 | id: distTag 42 | 43 | - name: Validate prerelease 44 | if: github.ref_name == 'main' && inputs.prerelease 45 | uses: actions/github-script@v7 46 | with: 47 | script: | 48 | core.setFailed('Do not create a prerelease on "main". You can create a prerelease on a branch and when it is merged it will create a non-prerelease Release. For example: 1.0.1-beta.2 will release as 1.0.1 when merged into main.') 49 | 50 | - name: Determine prerelease name 51 | id: prereleaseTag 52 | # Only run this step if the ref is not main 53 | # This will allow us to merge a prerelease PR into main and have it release as a normal release 54 | if: github.ref_name != 'main' 55 | run: | 56 | if [ -n "$INPUTS_PRERELEASE" ]; then 57 | echo "[INFO] Prerelease input passed in, using: $INPUTS_PRERELEASE" 58 | echo "tag=$INPUTS_PRERELEASE" >> "$GITHUB_OUTPUT" 59 | elif [ -n "$STEPS_DISTTAG_TAG" ]; then 60 | echo "[INFO] Prerelease tag found in package.json, using: $STEPS_DISTTAG_TAG" 61 | echo "tag=$STEPS_DISTTAG_TAG" >> "$GITHUB_OUTPUT" 62 | elif [[ "$GITHUB_REF_NAME" =~ ^prerelease/.* ]]; then 63 | echo "[INFO] Prerelease branch found but no prerelease tag, using default: dev" 64 | echo "tag=dev" >> "$GITHUB_OUTPUT" 65 | fi 66 | env: 67 | INPUTS_PRERELEASE: ${{ inputs.prerelease }} 68 | STEPS_DISTTAG_TAG: ${{ steps.distTag.outputs.tag }} 69 | 70 | - name: Generate oclif readme 71 | if: ${{ inputs.generate-readme }} 72 | uses: salesforcecli/github-workflows/.github/actions/generateOclifReadme@main 73 | with: 74 | skip-on-empty: ${{ inputs.skip-on-empty }} 75 | pre-release: ${{ steps.prereleaseTag.outputs.tag && 'true' || 'false' }} 76 | pre-release-identifier: ${{ steps.prereleaseTag.outputs.tag }} 77 | multi: ${{ inputs.readme-multi }} 78 | 79 | - name: Conventional Changelog Action 80 | id: changelog 81 | uses: TriPSs/conventional-changelog-action@3a392e9aa44a72686b0fc13259a90d287dd0877c 82 | with: 83 | git-user-name: ${{ steps.github-user-info.outputs.username }} 84 | git-user-email: ${{ steps.github-user-info.outputs.email }} 85 | github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 86 | tag-prefix: "" 87 | # Setting 'release-count' to 0 will keep ALL releases in the change log file (no pruning) 88 | release-count: "0" 89 | skip-on-empty: ${{ inputs.skip-on-empty }} 90 | pre-release: ${{ steps.prereleaseTag.outputs.tag && 'true' || 'false' }} 91 | pre-release-identifier: ${{ steps.prereleaseTag.outputs.tag }} 92 | # ternary-ish: https://github.com/actions/runner/issues/409#issuecomment-752775072 93 | output-file: ${{ steps.prereleaseTag.outputs.tag && 'false' || 'CHANGELOG.md' }} # If prerelease, do not write the changelog file 94 | 95 | - name: Create Github Release 96 | uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 97 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 98 | with: 99 | name: ${{ steps.changelog.outputs.tag }} 100 | tag: ${{ steps.changelog.outputs.tag }} 101 | commit: ${{ github.ref_name }} 102 | body: ${{ steps.changelog.outputs.clean_changelog }} 103 | prerelease: ${{ steps.prereleaseTag.outputs.tag && 'true' || 'false' }} 104 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 105 | skipIfReleaseExists: true 106 | -------------------------------------------------------------------------------- /.github/workflows/ctcClose.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | changeCaseId: 5 | description: "Id for the change case created" 6 | type: string 7 | required: true 8 | status: 9 | required: false 10 | description: CTC status, like "Implemented - per plan", "Not Implemented" 11 | default: Implemented - per plan 12 | type: string 13 | 14 | jobs: 15 | ctcClose: 16 | runs-on: static-ip-ubuntu-24-runners 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: npm 24 | - run: npm install -g @salesforce/change-case-management --omit=dev 25 | - id: ctc 26 | run: | 27 | if [ -z "${SF_CHANGE_CASE_SFDX_AUTH_URL}" ] || [ -z "${SF_CHANGE_CASE_TEMPLATE_ID}" ] || [ -z "${SF_CHANGE_CASE_CONFIGURATION_ITEM}" ] ; then 28 | echo "Environment not configured for CTC. Your environment needs SF_CHANGE_CASE_SFDX_AUTH_URL, SF_CHANGE_CASE_TEMPLATE_ID, and SF_CHANGE_CASE_CONFIGURATION_ITEM" 29 | exit 1 30 | else 31 | sfchangecase close --location ${{ github.repositoryUrl }} --release ${{github.repository}}.$(date +%F) --changecaseid "$INPUTS_CHANGE_CASE_ID" --status "$INPUTS_STATUS" 32 | fi 33 | env: 34 | INPUTS_CHANGE_CASE_ID: ${{ inputs.changeCaseId}} 35 | INPUTS_STATUS: ${{ inputs.status}} 36 | SF_CHANGE_CASE_SFDX_AUTH_URL: ${{ secrets.SF_CHANGE_CASE_SFDX_AUTH_URL}} 37 | SF_CHANGE_CASE_TEMPLATE_ID: ${{ secrets.SF_CHANGE_CASE_TEMPLATE_ID}} 38 | SF_CHANGE_CASE_CONFIGURATION_ITEM: ${{ secrets.SF_CHANGE_CASE_CONFIGURATION_ITEM}} 39 | -------------------------------------------------------------------------------- /.github/workflows/ctcOpen.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | githubTag: 5 | description: 'The semver tag of the GitHub Release' 6 | type: string 7 | outputs: 8 | changeCaseId: 9 | description: Id for the change case created 10 | value: ${{ jobs.ctcOpen.outputs.changeCaseId }} 11 | 12 | jobs: 13 | ctcOpen: 14 | outputs: 15 | changeCaseId: ${{ steps.ctc.outputs.ctcId }} 16 | runs-on: static-ip-ubuntu-24-runners 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: npm 24 | 25 | - run: npm install -g @salesforce/change-case-management --omit=dev 26 | 27 | - name: Open CTC case 28 | id: ctc 29 | shell: bash 30 | run: | 31 | # Temp disable exit on error 32 | set +e 33 | if [ -n "$GITHUB_TAG" ]; then 34 | RELEASE_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/$GITHUB_TAG" 35 | else 36 | RELEASE_URL="${{ github.server_url }}/${{ github.repository }}/releases" 37 | fi 38 | CTC_RESULT=$(sfchangecase create --location ${{github.repositoryUrl}} --test-environment $RELEASE_URL --release ${{github.repository}}.$(date +%F) --json) 39 | # Re-enable exit on error 40 | set -e 41 | 42 | STATUS=$(printf '%s' "$CTC_RESULT" | jq -r '.status') 43 | CTC_ID=$(printf '%s' "$CTC_RESULT" | jq -r '.result.id') 44 | 45 | if [[ "$STATUS" == "0" && "$CTC_ID" != "null" ]]; then 46 | echo "Successfully created case with ID: $CTC_ID" 47 | echo "ctcId=$CTC_ID" >> "$GITHUB_OUTPUT" 48 | else 49 | echo "CTC failed to open a case. Result:" 50 | echo "$CTC_RESULT" 51 | exit 1 52 | fi 53 | env: 54 | GITHUB_TAG: ${{ inputs.githubTag }} 55 | SF_CHANGE_CASE_SFDX_AUTH_URL: ${{ secrets.SF_CHANGE_CASE_SFDX_AUTH_URL}} 56 | SF_CHANGE_CASE_TEMPLATE_ID: ${{ secrets.SF_CHANGE_CASE_TEMPLATE_ID}} 57 | SF_CHANGE_CASE_CONFIGURATION_ITEM: ${{ secrets.SF_CHANGE_CASE_CONFIGURATION_ITEM}} 58 | 59 | - run: echo "[INFO] Change Case ID is:\ $STEPS_CTC_ID" 60 | env: 61 | STEPS_CTC_ID: ${{ steps.ctc.outputs.ctcId }} 62 | -------------------------------------------------------------------------------- /.github/workflows/devScriptsUpdate.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | SVC_CLI_BOT_GITHUB_TOKEN: 5 | description: A Github PAT with repo write access. 6 | required: true 7 | 8 | jobs: 9 | # what is the RC version, numerically? 10 | compareVersions: 11 | outputs: 12 | shouldUpdate: ${{ !endsWith(steps.packageVersion.outputs.prop, steps.version-info.outputs.version) }} 13 | 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-node@v4 19 | 20 | - uses: salesforcecli/github-workflows/.github/actions/versionInfo@main 21 | id: version-info 22 | with: 23 | version: latest 24 | npmPackage: "@salesforce/dev-scripts" 25 | 26 | - run: echo "[INFO] dev-scripts latest is:\ $STEPS_VERSION_INFO_VERSION" 27 | env: 28 | STEPS_VERSION_INFO_VERSION: ${{ steps.version-info.outputs.version }} 29 | 30 | - uses: salesforcecli/github-workflows/.github/actions/get-json-property@main 31 | id: packageVersion 32 | with: 33 | path: "package.json" 34 | prop_path: 'devDependencies.@salesforce/dev-scripts' 35 | 36 | - run: echo "[INFO] This repo's dev-scripts version is:\ $STEPS_PACKAGE_VERSION_PROP" 37 | env: 38 | STEPS_PACKAGE_VERSION_PROP: ${{ steps.packageVersion.outputs.prop }} 39 | 40 | - run: echo "[INFO] shouldUpdate value will be:\ ${{ !endsWith(steps.packageVersion.outputs.prop, steps.version-info.outputs.version) }}" 41 | 42 | updateDevScripts: 43 | needs: [compareVersions] 44 | if: ${{ needs.compareVersions.outputs.shouldUpdate == 'true' }} 45 | runs-on: "ubuntu-latest" 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 50 | 51 | - name: Get Github user info 52 | id: github-user-info 53 | uses: salesforcecli/github-workflows/.github/actions/getGithubUserInfo@main 54 | with: 55 | SVC_CLI_BOT_GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 56 | 57 | - uses: salesforcecli/github-workflows/.github/actions/gitConfig@main 58 | with: 59 | username: ${{ steps.github-user-info.outputs.username }} 60 | email: ${{ steps.github-user-info.outputs.email }} 61 | 62 | - uses: actions/setup-node@v4 63 | with: 64 | node-version: lts/* 65 | cache: yarn 66 | # TODO: Do we need this? Isn't that why we use npx? 67 | - run: npm install -g yarn-deduplicate 68 | - run: yarn upgrade @salesforce/dev-scripts@latest 69 | # TODO: What does this mean? Would yarnInstallWithRetries work here? 70 | # this may fail because that's how dev-scripts does things 71 | - run: yarn install --network-timeout 600000 72 | continue-on-error: true 73 | - run: yarn install --network-timeout 600000 74 | - run: npx yarn-deduplicate 75 | - run: yarn install --network-timeout 600000 76 | # clean up any outstanding unmerged devScripts PRs 77 | - run: | 78 | PRs=$(gh pr list --search "in:title devScripts update" --json number | jq '.[] | .number' -r | tr -d '[],"') 79 | for pr in $PRs; do 80 | gh pr close "$pr" -c "will be replaced with new PR" --delete-branch 81 | done 82 | env: 83 | GH_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 84 | # this may fail but we still want a PR with partial fix 85 | - run: | 86 | git checkout -b devScripts$(date +%F) 87 | yarn lint -- --fix 88 | continue-on-error: true 89 | - run: | 90 | git add . 91 | git commit -m 'chore: updates from devScripts' --no-verify 92 | git push origin --no-verify 93 | # sometimes pr can't create immediately 94 | - uses: salesforcecli/github-workflows/.github/actions/retry@main 95 | with: 96 | max_attempts: 5 97 | command: | 98 | gh pr create --title 'refactor: devScripts update' --body 'created via github action [skip-validate-pr]' -l dependencies 99 | env: 100 | GH_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 101 | -------------------------------------------------------------------------------- /.github/workflows/externalCompile.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | # could we get this from pjson? 5 | packageName: 6 | description: "The npm package that this repository publishes. ex: @salesforce/core" 7 | required: true 8 | type: string 9 | externalProjectGitUrl: 10 | description: "The url that will be cloned. This contains the code you want to compile. Ex: https://github.com/salesforcecli/plugin-user" 11 | type: string 12 | required: true 13 | command: 14 | required: false 15 | type: string 16 | default: yarn compile 17 | description: "command to execute (ex: yarn compile, yarn build)" 18 | nodeVersion: 19 | required: false 20 | description: version of node to run tests against. Use things like [lts/-1, lts/*, latest] to avoid hardcoding versions 21 | type: string 22 | default: lts/* 23 | os: 24 | required: false 25 | description: "runs-on property, ex: ubuntu-latest, windows-latest" 26 | type: string 27 | default: "ubuntu-latest" 28 | preBuildCommands: 29 | required: false 30 | description: "commands to run before the build...for example, to delete known module conflicts" 31 | type: string 32 | default: 'echo "no preBuildCommands passed"' 33 | postBuildCommands: 34 | required: false 35 | description: "script to run after the build" 36 | type: string 37 | default: 'echo "no postBuildCommands passed"' 38 | preExternalBuildCommands: 39 | required: false 40 | description: "commands to run before the build of the external repo...for example, to delete known module conflicts" 41 | type: string 42 | default: 'echo "no preExternalBuildCommands passed"' 43 | preSwapCommands: 44 | required: false 45 | description: "commands to run before ANY modifications happen. For example, changes that modify the lockfile like yarn add or remove need to happen before the action manually swaps the dependency under test" 46 | type: string 47 | default: 'echo "no preSwapCommands passed"' 48 | branch: 49 | required: false 50 | description: "branch to clone from the repo. Defaults to 'main'" 51 | type: string 52 | default: "main" 53 | ignoreScripts: 54 | required: false 55 | description: "if true, will run yarn install --ignore-scripts when building package in node_modules" 56 | type: boolean 57 | default: false 58 | 59 | jobs: 60 | external-build: 61 | name: ${{ inputs.command }} 62 | runs-on: ${{ inputs.os }} 63 | steps: 64 | - name: Configure git longpaths if on Windows 65 | if: ${{ runner.os == 'Windows' }} 66 | run: git config --system core.longpaths true 67 | 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: ${{ inputs.nodeVersion }} 71 | 72 | - name: Git clone 73 | uses: salesforcecli/github-workflows/.github/actions/retry@main 74 | with: 75 | max_attempts: 20 76 | command: git clone -b "$INPUTS_BRANCH" --single-branch "$INPUTS_EXTERNAL_PROJECT_GIT_URL" $(pwd) 77 | timeout_minutes: 20 78 | env: 79 | INPUTS_BRANCH: ${{ inputs.branch }} 80 | INPUTS_EXTERNAL_PROJECT_GIT_URL: ${{ inputs.externalProjectGitUrl }} 81 | 82 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 83 | 84 | - run: ${{ inputs.preSwapCommands }} 85 | 86 | - name: Swap this dependency for the version on this branch 87 | run: | 88 | yarn remove "$INPUTS_PACKAGE_NAME" 89 | yarn add ${{ github.repository }}#${{ github.sha }} 90 | npx yarn-deduplicate 91 | yarn install --network-timeout 600000 92 | env: 93 | INPUTS_PACKAGE_NAME: ${{ inputs.packageName }} 94 | 95 | - name: Install/build ${{ inputs.packageName }} in node_modules 96 | working-directory: node_modules/${{ inputs.packageName }} 97 | run: | 98 | yarn install --network-timeout 600000 ${{ inputs.ignoreScripts && '--ignore-scripts' || '' }} 99 | ${{ inputs.preBuildCommands }} 100 | yarn compile 101 | ${{ inputs.postBuildCommands }} 102 | 103 | - name: Run preExternalBuildCommands 104 | run: ${{ inputs.preExternalBuildCommands }} 105 | 106 | - name: Build the external project (where the NUTs are) 107 | run: ${{ inputs.command }} 108 | -------------------------------------------------------------------------------- /.github/workflows/externalNut.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | TESTKIT_AUTH_URL: 5 | required: false 6 | TESTKIT_HUB_USERNAME: 7 | required: false 8 | TESTKIT_JWT_CLIENT_ID: 9 | required: false 10 | TESTKIT_JWT_KEY: 11 | required: false 12 | TESTKIT_HUB_INSTANCE: 13 | required: false 14 | 15 | inputs: 16 | # could we get this from pjson? 17 | packageName: 18 | description: "The npm package that this repository publishes. ex: @salesforce/core" 19 | required: true 20 | type: string 21 | externalProjectGitUrl: 22 | description: "The url that will be cloned. This contains the NUTs you want to run. Ex: https://github.com/salesforcecli/plugin-user" 23 | type: string 24 | required: true 25 | command: 26 | required: false 27 | type: string 28 | default: yarn test:nuts 29 | description: "command to execute (ex: yarn test:nuts)" 30 | nodeVersion: 31 | required: false 32 | description: version of node to run tests against. Use things like [lts/-1, lts/*, latest] to avoid hardcoding versions 33 | type: string 34 | default: lts/* 35 | os: 36 | required: false 37 | description: "runs-on property, ex: ubuntu-latest, windows-latest" 38 | type: string 39 | default: "ubuntu-latest" 40 | sfdxExecutablePath: 41 | required: false 42 | description: "Path to sfdx executable to be used by NUTs, defaults to ''" 43 | type: string 44 | preBuildCommands: 45 | required: false 46 | description: "commands to run before the build...for example, to delete known module conflicts" 47 | type: string 48 | default: 'echo "no preBuildCommands passed"' 49 | postBuildCommands: 50 | required: false 51 | description: "script to run after the build" 52 | type: string 53 | default: 'echo "no postBuildCommands passed"' 54 | preExternalBuildCommands: 55 | required: false 56 | description: "commands to run before the build of the external repo...for example, to delete known module conflicts" 57 | type: string 58 | default: 'echo "no preExternalBuildCommands passed"' 59 | preSwapCommands: 60 | required: false 61 | description: "commands to run before ANY modifications happen. For example, changes that modify the lockfile like yarn add or remove need to happen before the action manually swaps the dependency under test" 62 | type: string 63 | default: 'echo "no preSwapCommands passed"' 64 | useCache: 65 | required: false 66 | type: boolean 67 | default: true 68 | attempts: 69 | required: false 70 | type: number 71 | default: 3 72 | branch: 73 | required: false 74 | description: "branch to clone from the repo. Defaults to 'main'" 75 | type: string 76 | default: "main" 77 | ignoreScripts: 78 | required: false 79 | description: "if true, will run yarn install --ignore-scripts when building package in node_modules" 80 | type: boolean 81 | default: false 82 | 83 | jobs: 84 | external-nut: 85 | name: ${{ inputs.command }} 86 | runs-on: ${{ inputs.os }} 87 | steps: 88 | - name: Configure git longpaths if on Windows 89 | if: ${{ runner.os == 'Windows' }} 90 | run: git config --system core.longpaths true 91 | 92 | - uses: actions/setup-node@v4 93 | with: 94 | node-version: ${{ inputs.nodeVersion }} 95 | 96 | - uses: salesforcecli/github-workflows/.github/actions/retry@main 97 | name: cli install 98 | with: 99 | max_attempts: ${{ inputs.attempts }} 100 | command: npm install -g @salesforce/cli@nightly shx yarn-deduplicate --omit=dev 101 | timeout_minutes: 20 102 | 103 | - uses: salesforcecli/github-workflows/.github/actions/retry@main 104 | name: git clone 105 | with: 106 | max_attempts: 20 107 | command: git clone -b ${{ inputs.branch }} --single-branch ${{ inputs.externalProjectGitUrl }} $(pwd) 108 | timeout_minutes: 20 109 | 110 | - name: Cache node modules 111 | if: inputs.useCache 112 | id: cache-nodemodules 113 | uses: actions/cache@v4 114 | with: 115 | path: node_modules 116 | key: ${{ runner.os }}-externalNuts-${{ env.cache-name }}-${{ inputs.externalProjectGitUrl}}-${{ inputs.branch}}-${{ github.sha }} 117 | env: 118 | cache-name: cache-node-modules 119 | 120 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 121 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 122 | 123 | - name: Run preSwapCommands 124 | run: ${{ inputs.preSwapCommands }} 125 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 126 | 127 | - name: Swap this dependency for the version on this branch 128 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 129 | run: | 130 | yarn remove "$INPUTS_PACKAGE_NAME" 131 | yarn add ${{ github.repository }}#${{ github.sha }} 132 | npx yarn-deduplicate 133 | yarn install --network-timeout 600000 134 | env: 135 | INPUTS_PACKAGE_NAME: ${{ inputs.packageName }} 136 | 137 | - name: Install/build ${{ inputs.packageName }} in node_modules 138 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 139 | working-directory: node_modules/${{ inputs.packageName }} 140 | run: | 141 | yarn install --network-timeout 600000 ${{ inputs.ignoreScripts && '--ignore-scripts' || '' }} 142 | ${{ inputs.preBuildCommands }} 143 | yarn compile 144 | ${{ inputs.postBuildCommands }} 145 | 146 | - name: Run preExternalBuildCommands 147 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 148 | run: ${{ inputs.preExternalBuildCommands }} 149 | 150 | - name: Build the external project (where the NUTs are) 151 | run: yarn compile 152 | 153 | - name: Set optional sf executable path 154 | if: inputs.sfdxExecutablePath 155 | run: echo "TESTKIT_EXECUTABLE_PATH=$INPUTS_SF_EXECUTABLE_PATH" >> $GITHUB_ENV 156 | env: 157 | INPUTS_SF_EXECUTABLE_PATH: ${{ inputs.sfdxExecutablePath }} 158 | 159 | - name: NUTs with ${{ inputs.attempts }} attempts 160 | uses: salesforcecli/github-workflows/.github/actions/retry@main 161 | with: 162 | max_attempts: ${{ inputs.attempts }} 163 | command: ${{ inputs.command }} 164 | retry_on: error 165 | env: 166 | SF_DISABLE_TELEMETRY: true 167 | TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL }} 168 | TESTKIT_HUB_USERNAME: ${{ secrets.TESTKIT_HUB_USERNAME }} 169 | TESTKIT_HUB_INSTANCE: ${{ secrets.TESTKIT_HUB_INSTANCE }} 170 | TESTKIT_JWT_CLIENT_ID: ${{ secrets.TESTKIT_JWT_CLIENT_ID }} 171 | TESTKIT_JWT_KEY: ${{ secrets.TESTKIT_JWT_KEY }} 172 | TESTKIT_SETUP_RETRIES: 2 173 | DEBUG: ${{ vars.DEBUG }} 174 | -------------------------------------------------------------------------------- /.github/workflows/githubRelease.yml: -------------------------------------------------------------------------------- 1 | name: tag and github release 2 | # 3 | # 4 | # 5 | # 6 | # ----------------------------------------------------------------------- 7 | # NOTE: This workflow is deprecated in favor of create-github-release.yml 8 | # ----------------------------------------------------------------------- 9 | # 10 | # 11 | # 12 | # 13 | on: 14 | workflow_call: 15 | secrets: 16 | SVC_CLI_BOT_GITHUB_TOKEN: 17 | description: a github PAT with repo access 18 | 19 | inputs: 20 | prerelease: 21 | type: boolean 22 | required: false 23 | default: false 24 | description: use a prerelease instead of a regular release 25 | 26 | jobs: 27 | release: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Deprecation warning 31 | run: | 32 | echo "::warning::WARNING: This workflow is deprecated in favor of create-github-release.yml\n 33 | Update your worklows to use the new flow that supports prereleases\n 34 | Docs: https://github.com/salesforcecli/github-workflows#prereleases\n 35 | Example: https://github.com/salesforcecli/plugin-source/pull/927/files" 36 | - uses: actions/checkout@v4 37 | with: 38 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 39 | 40 | - uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main 41 | id: distTag 42 | if: inputs.prerelease 43 | 44 | - name: prerelease package.json validation 45 | if: inputs.prerelease && !steps.distTag.outputs.tag 46 | uses: actions/github-script@v7 47 | with: 48 | script: | 49 | core.setFailed('Prerelease requires a dist tag name in your package.json like beta in 1.1.1-beta.0') 50 | 51 | - name: Conventional Changelog Action 52 | id: changelog 53 | uses: TriPSs/conventional-changelog-action@3a392e9aa44a72686b0fc13259a90d287dd0877c 54 | with: 55 | git-user-name: svc-cli-bot 56 | git-user-email: svc_cli_bot@salesforce.com 57 | github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 58 | tag-prefix: "" 59 | # Setting 'release-count' to 0 will keep ALL releases in the change log (no pruning) 60 | release-count: "0" 61 | pre-release: ${{ inputs.prerelease }} 62 | pre-release-identifier: ${{ steps.distTag.outputs.tag }} 63 | # ternary-ish: https://github.com/actions/runner/issues/409#issuecomment-752775072 64 | output-file: ${{ inputs.prerelease && 'false' || 'CHANGELOG.md' }} # If prerelease, do not write the changelog file 65 | - name: Create Github Release 66 | uses: actions/create-release@v1 67 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 70 | with: 71 | tag_name: ${{ steps.changelog.outputs.tag }} 72 | release_name: ${{ steps.changelog.outputs.tag }} 73 | body: ${{ steps.changelog.outputs.clean_changelog }} 74 | prerelease: ${{ inputs.prerelease }} 75 | -------------------------------------------------------------------------------- /.github/workflows/inclusionTest.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | tarballs: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | repository: salesforcecli/cli 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: lts/* 14 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 15 | - run: | 16 | yarn add ${{ github.repository }}#${{ github.sha }} --network-concurrency 1 17 | yarn pack:tarballs -t linux-x64 --no-xz 18 | yarn pack:verify 19 | -------------------------------------------------------------------------------- /.github/workflows/lockFileCheck.yml: -------------------------------------------------------------------------------- 1 | on: workflow_call 2 | 3 | jobs: 4 | lockfile-check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: 'lts/*' 11 | - run: | 12 | npm install -g lockfile-lint 13 | lockfile-lint --path yarn.lock --allowed-hosts npm yarn --validate-https 14 | -------------------------------------------------------------------------------- /.github/workflows/notify-slack-on-pr-open.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Slack Notification 2 | 3 | # NOTE: This workflow in github-workflows is not intended to be used externally 4 | # It is used when Pull Requests are opened on this repository itself 5 | # All of our repositories have their own copy of this workflow 6 | 7 | on: 8 | pull_request: 9 | types: [opened, reopened] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Notify Slack on PR open 16 | env: 17 | WEBHOOK_URL : ${{ secrets.CLI_TEAM_SLACK_WEBHOOK_URL }} 18 | PULL_REQUEST_AUTHOR_ICON_URL : ${{ github.event.pull_request.user.avatar_url }} 19 | PULL_REQUEST_AUTHOR_NAME : ${{ github.event.pull_request.user.login }} 20 | PULL_REQUEST_AUTHOR_PROFILE_URL: ${{ github.event.pull_request.user.html_url }} 21 | PULL_REQUEST_BASE_BRANCH_NAME : ${{ github.event.pull_request.base.ref }} 22 | PULL_REQUEST_COMPARE_BRANCH_NAME : ${{ github.event.pull_request.head.ref }} 23 | PULL_REQUEST_NUMBER : ${{ github.event.pull_request.number }} 24 | PULL_REQUEST_REPO: ${{ github.event.pull_request.head.repo.name }} 25 | PULL_REQUEST_TITLE : ${{ github.event.pull_request.title }} 26 | PULL_REQUEST_URL : ${{ github.event.pull_request.html_url }} 27 | uses: salesforcecli/github-workflows/.github/actions/prNotification@main 28 | -------------------------------------------------------------------------------- /.github/workflows/npmPublish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | NPM_TOKEN: 5 | description: npm token 6 | AWS_ACCESS_KEY_ID: 7 | description: AWS access key id. Only required if sign = true 8 | AWS_SECRET_ACCESS_KEY: 9 | description: AWS secret access key. Only required if sign = true 10 | 11 | inputs: 12 | tag: 13 | required: false 14 | description: tag used to publish to npm 15 | default: latest 16 | type: string 17 | sign: 18 | required: false 19 | description: signs the package using sf-release if set to true 20 | default: false 21 | type: boolean 22 | dryrun: 23 | required: false 24 | description: if true, the job will run but will not publish to npm or push to git 25 | default: false 26 | type: boolean 27 | prerelease: 28 | required: false 29 | description: if true, it will use the version -.0 30 | type: boolean 31 | default: false 32 | nodeVersion: 33 | description: version of node to use. It's better to specify latest, lts/* or lts/-1 than to hardode numbers 34 | type: string 35 | default: lts/* 36 | required: false 37 | ctc: 38 | description: | 39 | Use CTC. Requires environment to contain 40 | SF_CHANGE_CASE_SFDX_AUTH_URL, SF_CHANGE_CASE_TEMPLATE_ID, SF_CHANGE_CASE_CONFIGURATION_ITEM. 41 | Also requires a static ip runner (you can't use ubuntu-latest) 42 | type: boolean 43 | required: false 44 | runsOn: 45 | description: the runner. Only needed if you need a non-public runner (ex, for git checkout from IP restricted private repo) 46 | default: ubuntu-latest 47 | required: false 48 | type: string 49 | githubTag: 50 | description: the github release tag that you want to publish as an npm package 51 | required: true 52 | type: string 53 | jobs: 54 | check-publish: 55 | outputs: 56 | published: ${{ steps.is-published.outputs.published }} 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v4 60 | with: 61 | ref: ${{ inputs.githubTag }} 62 | 63 | - uses: actions/setup-node@v4 64 | with: 65 | node-version: ${{ inputs.nodeVersion }} 66 | 67 | - name: Is published 68 | id: is-published 69 | run: | 70 | RESPONSE=$(npm view .@$INPUTS_GITHUB_TAG version --json --silent || echo "Not published") 71 | 72 | # The response is wrapped in double quotes, so we need to compare it with (escaped) quotes 73 | if [ "$RESPONSE" = "\"$INPUTS_GITHUB_TAG\"" ]; then 74 | echo "published=true" >> "$GITHUB_OUTPUT" 75 | else 76 | echo "published=false" >> "$GITHUB_OUTPUT" 77 | fi 78 | env: 79 | INPUTS_GITHUB_TAG: ${{ inputs.githubTag }} 80 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 81 | 82 | - run: echo "[INFO] Is package published:\ $STEPS_IS_PUBLISHED_PUBLISHED" 83 | env: 84 | STEPS_IS_PUBLISHED_PUBLISHED: ${{ steps.is-published.outputs.published }} 85 | 86 | - name: Fail if published 87 | if: steps.is-published.outputs.published == 'true' 88 | uses: actions/github-script@v7 89 | with: 90 | script: core.setFailed(`The version '${process.env.INPUTS_GITHUB_TAG}' has already been published to npm`) 91 | env: 92 | INPUTS_GITHUB_TAG: ${{ inputs.githubTag }} 93 | 94 | ctc-open: 95 | needs: [check-publish] 96 | if: inputs.ctc && needs.check-publish.outputs.published == 'false' 97 | uses: salesforcecli/github-workflows/.github/workflows/ctcOpen.yml@main 98 | with: 99 | githubTag: ${{ inputs.githubTag }} 100 | secrets: inherit 101 | 102 | npm-publish: 103 | needs: [check-publish, ctc-open] 104 | if: ${{ always() && needs.check-publish.outputs.published == 'false' && (!inputs.ctc || (inputs.ctc && needs.ctc-open.outputs.changeCaseId)) }} 105 | runs-on: ${{ inputs.runsOn }} 106 | steps: 107 | - uses: actions/checkout@v4 108 | with: 109 | ref: ${{ inputs.githubTag }} 110 | 111 | - uses: actions/setup-node@v4 112 | with: 113 | node-version: ${{ inputs.nodeVersion }} 114 | cache: yarn 115 | 116 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 117 | 118 | - run: yarn build 119 | 120 | - run: npm install -g @salesforce/plugin-release-management 121 | 122 | - name: NPM Release 123 | run: | 124 | sf-release npm:package:release \ 125 | --githubtag "$INPUTS_GITHUB_TAG" \ 126 | --npmtag "$INPUTS_TAG" \ 127 | --no-install \ 128 | ${{ inputs.dryrun && '--dryrun' || '' }} \ 129 | ${{ inputs.prerelease && format('--prerelease {0}', github.ref_name) || '' }} \ 130 | ${{ inputs.sign && '--sign' || '' }} 131 | env: 132 | INPUTS_GITHUB_TAG: ${{ inputs.githubTag }} 133 | INPUTS_TAG: ${{ inputs.tag }} 134 | NPM_TOKEN: ${{secrets.NPM_TOKEN}} 135 | AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} 136 | AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} 137 | 138 | ctcCloseSuccess: 139 | needs: [ctc-open, npm-publish] 140 | if: needs.ctc-open.result == 'success' && needs.npm-publish.result == 'success' && needs.ctc-open.outputs.changeCaseId 141 | uses: salesforcecli/github-workflows/.github/workflows/ctcClose.yml@main 142 | secrets: inherit 143 | with: 144 | changeCaseId: ${{needs.ctc-open.outputs.changeCaseId}} 145 | 146 | ctcCloseFail: 147 | needs: [ctc-open, npm-publish] 148 | if: always() && inputs.ctc && needs.ctc-open.outputs.changeCaseId && (needs.ctc-open.result != 'success' || needs.npm-publish.result != 'success') 149 | uses: salesforcecli/github-workflows/.github/workflows/ctcClose.yml@main 150 | secrets: inherit 151 | with: 152 | changeCaseId: ${{ needs.ctc-open.outputs.changeCaseId }} 153 | status: Not Implemented 154 | -------------------------------------------------------------------------------- /.github/workflows/nut.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | SF_CHANGE_CASE_SFDX_AUTH_URL: 5 | description: "SFDX_AUTH_URL for the CTC org. Only useful for the CTC NUTs." 6 | required: false 7 | SF_CHANGE_CASE_TEMPLATE_ID: 8 | required: false 9 | description: "Template ID for the CTC org. Only useful for the CTC NUTs." 10 | SF_CHANGE_CASE_CONFIGURATION_ITEM: 11 | required: false 12 | description: "Configuration Item for the CTC org. Only useful for the CTC NUTs." 13 | ONEGP_TESTKIT_AUTH_URL: 14 | description: "SFDX_AUTH_URL for the 1GP org. Only useful for the 1GP NUTs in packaging" 15 | required: false 16 | TESTKIT_AUTH_URL: 17 | required: false 18 | TESTKIT_HUB_USERNAME: 19 | required: false 20 | TESTKIT_JWT_CLIENT_ID: 21 | required: false 22 | TESTKIT_JWT_KEY: 23 | required: false 24 | TESTKIT_HUB_INSTANCE: 25 | required: false 26 | 27 | inputs: 28 | command: 29 | required: false 30 | type: string 31 | default: yarn test:nuts 32 | description: "command to execute (ex: yarn test:nuts)" 33 | nodeVersion: 34 | required: false 35 | description: version of node to run tests against. Use things like [lts/-1, lts/*, latest] to avoid hardcoding versions 36 | type: string 37 | default: lts/* 38 | os: 39 | required: false 40 | description: "runs-on property, ex: ubuntu-latest, windows-latest" 41 | type: string 42 | default: "ubuntu-latest" 43 | sfdxExecutablePath: 44 | required: false 45 | description: "Path to sfdx executable to be used by NUTs, defaults to ''" 46 | type: string 47 | useCache: 48 | required: false 49 | type: boolean 50 | default: true 51 | retries: 52 | required: false 53 | type: number 54 | default: 3 55 | description: "Number of times to attempt NUTs" 56 | 57 | jobs: 58 | nut: 59 | name: ${{ inputs.command }} 60 | runs-on: ${{ inputs.os }} 61 | steps: 62 | - name: Configure git longpaths if on Windows 63 | if: ${{ runner.os == 'Windows' }} 64 | run: git config --system core.longpaths true 65 | 66 | - uses: actions/checkout@v4 67 | 68 | - uses: google/wireit@setup-github-actions-caching/v2 69 | continue-on-error: true 70 | 71 | - uses: actions/setup-node@v4 72 | with: 73 | node-version: ${{ inputs.nodeVersion }} 74 | cache: yarn 75 | 76 | - name: Cache node modules 77 | id: cache-nodemodules 78 | uses: actions/cache@v4 79 | env: 80 | cache-name: cache-node-modules 81 | with: 82 | path: "**/node_modules" 83 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} 84 | 85 | - name: add CLI as global dependency 86 | uses: salesforcecli/github-workflows/.github/actions/retry@main 87 | with: 88 | max_attempts: ${{ inputs.retries }} 89 | command: npm install @salesforce/cli@nightly -g 90 | 91 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 92 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 93 | 94 | # This is a temporary workaround to ensure wireit is >= 0.14.12 95 | # Once all plugins/libs that use this workflow are updated, this can be removed 96 | # See: https://github.com/google/wireit/issues/1297#issuecomment-2794737569 97 | - name: Install wireit 98 | run: yarn add wireit@^0.14.12 99 | 100 | - run: yarn compile 101 | 102 | - name: Check that oclif config exists 103 | id: is-oclif-plugin 104 | run: echo "bool=$(jq 'if .oclif then true else false end' package.json)" >> "$GITHUB_OUTPUT" 105 | 106 | - run: yarn oclif manifest 107 | if: ${{ steps.is-oclif-plugin.outputs.bool == 'true' }} 108 | 109 | - name: Set optional sf executable path 110 | if: inputs.sfdxExecutablePath 111 | run: echo "TESTKIT_EXECUTABLE_PATH=$INPUTS_SF_EXECUTABLE_PATH" >> $GITHUB_ENV 112 | env: 113 | INPUTS_SF_EXECUTABLE_PATH: ${{ inputs.sfdxExecutablePath }} 114 | 115 | - name: NUTs with ${{ inputs.retries }} attempts 116 | uses: salesforcecli/github-workflows/.github/actions/retry@main 117 | with: 118 | max_attempts: ${{ inputs.retries }} 119 | command: ${{ inputs.command }} 120 | retry_on: error 121 | env: 122 | TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL}} 123 | TESTKIT_HUB_USERNAME: ${{ secrets.TESTKIT_HUB_USERNAME }} 124 | TESTKIT_JWT_CLIENT_ID: ${{ secrets.TESTKIT_JWT_CLIENT_ID }} 125 | TESTKIT_JWT_KEY: ${{ secrets.TESTKIT_JWT_KEY }} 126 | TESTKIT_HUB_INSTANCE: ${{ secrets.TESTKIT_HUB_INSTANCE }} 127 | ONEGP_TESTKIT_AUTH_URL: ${{ secrets.ONEGP_TESTKIT_AUTH_URL }} 128 | SF_CHANGE_CASE_SFDX_AUTH_URL: ${{ secrets.SF_CHANGE_CASE_SFDX_AUTH_URL }} 129 | SF_CHANGE_CASE_TEMPLATE_ID: ${{ secrets.SF_CHANGE_CASE_TEMPLATE_ID}} 130 | SF_CHANGE_CASE_CONFIGURATION_ITEM: ${{ secrets.SF_CHANGE_CASE_CONFIGURATION_ITEM}} 131 | TESTKIT_SETUP_RETRIES: 2 132 | SF_DISABLE_TELEMETRY: true 133 | DEBUG: ${{ vars.DEBUG }} 134 | -------------------------------------------------------------------------------- /.github/workflows/packUploadMac.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | version: 5 | type: string 6 | required: true 7 | description: version for upload. do not include the 'v' 8 | channel: 9 | type: string 10 | required: true 11 | description: channel that the uploaded tarballs get promoted to 12 | nodeVersion: 13 | type: string 14 | default: lts/* 15 | description: node version to use for the tarball build 16 | jobs: 17 | macos: 18 | env: 19 | SF_DISABLE_TELEMETRY: true 20 | runs-on: macos-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ inputs.nodeVersion }} 27 | cache: yarn 28 | 29 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 30 | 31 | - name: Pack for macos 32 | uses: salesforcecli/github-workflows/.github/actions/retry@main 33 | with: 34 | command: yarn pack:macos 35 | 36 | - name: Upload macos 37 | run: yarn upload:macos 38 | env: 39 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 40 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 41 | 42 | - name: Promote macos to ${{ inputs.channel }} channel 43 | run: yarn channel:promote --cli sf --version "$INPUTS_VERSION" --target "$INPUTS_CHANNEL" --platform macos 44 | env: 45 | INPUTS_VERSION: ${{ inputs.version }} 46 | INPUTS_CHANNEL: ${{ inputs.channel }} 47 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 49 | 50 | - name: Upload artifacts to Github release 51 | run: gh release upload "$INPUTS_VERSION" ./dist/macos/sf-*.pkg --clobber --repo "$GIT_REPOSITORY" 52 | env: 53 | INPUTS_VERSION: ${{ inputs.version }} 54 | GH_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/packUploadWindows.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | version: 5 | type: string 6 | required: true 7 | description: version for upload. do not include the 'v' 8 | channel: 9 | type: string 10 | required: true 11 | description: channel that the uploaded tarballs get promoted to 12 | nodeVersion: 13 | type: string 14 | default: lts/* 15 | description: node version to use for the tarball build 16 | 17 | jobs: 18 | win: 19 | runs-on: ubuntu-latest 20 | env: 21 | SF_DISABLE_TELEMETRY: true 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ inputs.nodeVersion }} 28 | cache: yarn 29 | 30 | - name: Set up Homebrew 31 | uses: Homebrew/actions/setup-homebrew@e05416b42376bcda221f9102c4f595f4994016be 32 | 33 | # TODO: It would be nice if we chould ditch homebrew for this install 34 | - run: brew install makensis 35 | 36 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 37 | 38 | - name: Pack for Windows 39 | run: yarn pack:win 40 | 41 | - name: Upload Windows 42 | run: yarn upload:win 43 | env: 44 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 45 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 46 | 47 | - name: Promote win to ${{ inputs.channel }} channel 48 | run: yarn channel:promote --cli sf --version "$INPUTS_VERSION" --target "$INPUTS_CHANNEL" --platform win 49 | env: 50 | INPUTS_VERSION: ${{ inputs.version }} 51 | INPUTS_CHANNEL: ${{ inputs.channel }} 52 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 53 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 54 | 55 | - name: Upload artifacts to Github release 56 | run: gh release upload "$INPUTS_VERSION" ./dist/win32/sf-*.exe --clobber --repo "$GITHUB_REPOSITORY" 57 | env: 58 | INPUTS_VERSION: ${{ inputs.version }} 59 | GH_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /.github/workflows/preventTypescriptDep.yml: -------------------------------------------------------------------------------- 1 | on: workflow_call 2 | 3 | jobs: 4 | prevent-typescript-dependency: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: "lts/*" 11 | - run: | 12 | yarn install --production 13 | if [ -d node_modules/typescript ]; then 14 | echo "ERROR: Typescript dependency found"; 15 | echo "--- yarn why typescript ---" 16 | yarn why typescript; 17 | echo "--- npm ls typescript---" 18 | npm ls typescript 19 | exit 1; 20 | fi 21 | -------------------------------------------------------------------------------- /.github/workflows/publishTypedoc.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | secrets: 4 | SVC_CLI_BOT_GITHUB_TOKEN: 5 | description: A Github PAT with repo write access. 6 | required: true 7 | 8 | jobs: 9 | publish: 10 | runs-on: "ubuntu-latest" 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | cache: yarn 22 | 23 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 24 | 25 | - name: Get Github user info 26 | id: github-user-info 27 | uses: salesforcecli/github-workflows/.github/actions/getGithubUserInfo@main 28 | with: 29 | SVC_CLI_BOT_GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} 30 | 31 | - uses: salesforcecli/github-workflows/.github/actions/gitConfig@main 32 | with: 33 | username: ${{ steps.github-user-info.outputs.username }} 34 | email: ${{ steps.github-user-info.outputs.email }} 35 | 36 | - name: Build docs 37 | run: | 38 | rm -rf docs 39 | git worktree prune 40 | git fetch origin gh-pages:gh-pages 41 | git worktree add docs gh-pages 42 | yarn docs 43 | 44 | - name: Send to git 45 | run: | 46 | cd docs 47 | git add . 48 | git commit -m 'docs: publishing gh-pages [skip ci]' --no-verify 49 | git push origin gh-pages --no-verify 50 | -------------------------------------------------------------------------------- /.github/workflows/stampyUpload.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | version: 5 | type: string 6 | required: true 7 | description: version for upload. do not include the 'v' 8 | 9 | jobs: 10 | upload: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: salesforcecli/github-workflows/.github/actions/versionInfo@main 14 | id: version-info 15 | with: 16 | version: ${{ inputs.version }} 17 | npmPackage: "@salesforce/cli" 18 | 19 | - name: Save filename (without arch/extension) for reuse 20 | id: filename 21 | run: echo "FILEBASE=sf-v$INPUTS_VERSION-$STEPS_VERSION_INFO_SHA" >> "$GITHUB_OUTPUT" 22 | env: 23 | INPUTS_VERSION: ${{ inputs.version }} 24 | STEPS_VERSION_INFO_SHA: ${{ steps.version-info.outputs.sha }} 25 | 26 | - name: Download from s3 27 | run: | 28 | aws s3 cp "s3://dfc-data-production/media/salesforce-cli/sf/versions/$INPUTS_VERSION/$STEPS_VERSION_INFO_SHA/$STEPS_FILENAME_FILEBASE-x86.exe" . 29 | aws s3 cp "s3://dfc-data-production/media/salesforce-cli/sf/versions/$INPUTS_VERSION/$STEPS_VERSION_INFO_SHA/$STEPS_FILENAME_FILEBASE-x64.exe" . 30 | aws s3 cp "s3://dfc-data-production/media/salesforce-cli/sf/versions/$INPUTS_VERSION/$STEPS_VERSION_INFO_SHA/$STEPS_FILENAME_FILEBASE-arm64.exe" . 31 | env: 32 | INPUTS_VERSION: ${{ inputs.version }} 33 | STEPS_VERSION_INFO_SHA: ${{ steps.version-info.outputs.sha }} 34 | STEPS_FILENAME_FILEBASE: ${{ steps.filename.outputs.FILEBASE }} 35 | # workaround for AWS CLI not having its region set (see https://github.com/actions/runner-images/issues/2791) 36 | AWS_EC2_METADATA_DISABLED: true 37 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 38 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 39 | 40 | # Note: we first need to switch AWS identity to the one that can access stampy 41 | - name: Upload to unsigned bucket 42 | run: | 43 | ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account') 44 | TEMP_ROLE=$(aws sts assume-role --role-arn $STAMPY_ARN --role-session-name artifact-signing) 45 | export AWS_ACCESS_KEY_ID=$(echo "${TEMP_ROLE}" | jq -r '.Credentials.AccessKeyId') 46 | export AWS_SECRET_ACCESS_KEY=$(echo "${TEMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') 47 | export AWS_SESSION_TOKEN=$(echo "${TEMP_ROLE}" | jq -r '.Credentials.SessionToken') 48 | aws s3 cp "$STEPS_FILENAME_FILEBASE-x86.exe" "$STAMPY_UNSIGNED_BUCKET/$STEPS_FILENAME_FILEBASE-x86.exe" 49 | aws s3 cp "$STEPS_FILENAME_FILEBASE-x64.exe" "$STAMPY_UNSIGNED_BUCKET/$STEPS_FILENAME_FILEBASE-x64.exe" 50 | aws s3 cp "$STEPS_FILENAME_FILEBASE-arm64.exe" "$STAMPY_UNSIGNED_BUCKET/$STEPS_FILENAME_FILEBASE-arm64.exe" 51 | env: 52 | STEPS_FILENAME_FILEBASE: ${{ steps.filename.outputs.FILEBASE }} 53 | STAMPY_ARN: ${{ secrets.STAMPY_ARN }} 54 | STAMPY_UNSIGNED_BUCKET: ${{ secrets.STAMPY_UNSIGNED_BUCKET }} 55 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 56 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 57 | AWS_EC2_METADATA_DISABLED: true 58 | -------------------------------------------------------------------------------- /.github/workflows/tarballs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | upload: 5 | type: boolean 6 | description: true means test, then upload them AWS 7 | required: false 8 | version: 9 | type: string 10 | required: false 11 | description: version for upload. do not include the 'v' 12 | channel: 13 | type: string 14 | required: false 15 | description: channel that the uploaded tarballs get promoted to 16 | nodeVersion: 17 | type: string 18 | default: lts/* 19 | description: node version to use for the tarball build 20 | 21 | jobs: 22 | tarballs: 23 | env: 24 | SF_DISABLE_TELEMETRY: true 25 | runs-on: ubuntu-22-8core 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: ${{ inputs.nodeVersion }} 32 | cache: yarn 33 | 34 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 35 | 36 | - name: pack tarballs 37 | uses: salesforcecli/github-workflows/.github/actions/retry@main 38 | with: 39 | command: yarn pack:tarballs 40 | retry_on: error 41 | 42 | - run: yarn pack:verify 43 | 44 | - run: yarn test:smoke-unix 45 | 46 | - if: inputs.upload 47 | run: yarn upload:tarballs 48 | env: 49 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 50 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 51 | 52 | - if: inputs.upload && inputs.version && inputs.channel 53 | run: yarn channel:promote --cli sf --version "$INPUTS_VERSION" --target "$INPUTS_CHANNEL" 54 | env: 55 | INPUTS_VERSION: ${{ inputs.version }} 56 | INPUTS_CHANNEL: ${{ inputs.channel }} 57 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 58 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 59 | 60 | - if: inputs.upload 61 | run: | 62 | gh release upload "$INPUTS_VERSION" ./dist/*.gz --clobber --repo "$GITHUB_REPOSITORY" 63 | gh release upload "$INPUTS_VERSION" ./dist/*.xz --clobber --repo "$GITHUB_REPOSITORY" 64 | env: 65 | INPUTS_VERSION: ${{ inputs.version }} 66 | GH_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/unitTest.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | linux-unit-tests: 6 | uses: salesforcecli/github-workflows/.github/workflows/unitTestsLinux.yml@main 7 | windows-unit-tests: 8 | uses: salesforcecli/github-workflows/.github/workflows/unitTestsWindows.yml@main 9 | -------------------------------------------------------------------------------- /.github/workflows/unitTestsLinux.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | skipTsDepCheck: 5 | type: boolean 6 | required: false 7 | default: false 8 | description: skip `prevent-typescript-dependency`. Use it for devDeps that ship TS 9 | 10 | jobs: 11 | determine-node-versions: 12 | outputs: 13 | nodeVersions: ${{ steps.determine-node-versions.outputs.nodeVersions }} 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: salesforcecli/github-workflows/.github/actions/determineNodeVersions@main 17 | id: determine-node-versions 18 | with: 19 | nodeVersionOverride: ${{ vars.NODE_VERSION_OVERRIDE }} # default is 'lts/*' 20 | nodeDisableVersions: ${{ vars.NODE_DISABLE_VERSIONS }} 21 | 22 | prevent-typescript-dependency: 23 | if: ${{ inputs.skipTsDepCheck == false }} 24 | uses: salesforcecli/github-workflows/.github/workflows/preventTypescriptDep.yml@main 25 | 26 | linux-unit-tests: 27 | needs: determine-node-versions 28 | strategy: 29 | matrix: 30 | node_version: ${{ fromJSON(needs.determine-node-versions.outputs.nodeVersions) }} 31 | fail-fast: false 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - uses: actions/setup-node@v4 37 | with: 38 | node-version: ${{ matrix.node_version }} 39 | cache: yarn 40 | 41 | - uses: google/wireit@setup-github-actions-caching/v2 42 | continue-on-error: true 43 | 44 | - name: Cache node modules 45 | id: cache-nodemodules 46 | uses: actions/cache@v4 47 | env: 48 | cache-name: cache-node-modules 49 | with: 50 | path: "**/node_modules" 51 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} 52 | 53 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 54 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 55 | 56 | # This is a temporary workaround to ensure wireit is >= 0.14.12 57 | # Once all plugins/libraries that use this workflow are updated, this can be removed 58 | # See: https://github.com/google/wireit/issues/1297#issuecomment-2794737569 59 | - name: Install wireit 60 | run: yarn add wireit@^0.14.12 61 | 62 | - run: yarn build 63 | 64 | - name: yarn test 65 | uses: salesforcecli/github-workflows/.github/actions/retry@main 66 | with: 67 | command: yarn test 68 | env: 69 | SF_DISABLE_TELEMETRY: true 70 | -------------------------------------------------------------------------------- /.github/workflows/unitTestsWindows.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | determine-node-versions: 6 | outputs: 7 | nodeVersions: ${{ steps.determine-node-versions.outputs.nodeVersions }} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: salesforcecli/github-workflows/.github/actions/determineNodeVersions@main 11 | id: determine-node-versions 12 | with: 13 | nodeVersionOverride: ${{ vars.NODE_VERSION_OVERRIDE }} # default is 'lts/*' 14 | nodeDisableVersions: ${{ vars.NODE_DISABLE_VERSIONS }} 15 | 16 | windows-unit-tests: 17 | needs: determine-node-versions 18 | strategy: 19 | matrix: 20 | node_version: ${{ fromJSON(needs.determine-node-versions.outputs.nodeVersions) }} 21 | fail-fast: false 22 | runs-on: windows-latest 23 | steps: 24 | - run: git config --system core.longpaths true 25 | 26 | - uses: actions/checkout@v4 27 | 28 | - uses: google/wireit@setup-github-actions-caching/v2 29 | continue-on-error: true 30 | 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ matrix.node_version }} 34 | cache: yarn 35 | 36 | - name: Cache node modules 37 | id: cache-nodemodules 38 | uses: actions/cache@v4 39 | env: 40 | cache-name: cache-node-modules 41 | with: 42 | path: "**/node_modules" 43 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} 44 | 45 | - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main 46 | if: ${{ steps.cache-nodemodules.outputs.cache-hit != 'true' }} 47 | 48 | # This is a temporary workaround to ensure wireit is >= 0.14.12 49 | # Once all plugins/libraries that use this workflow are updated, this can be removed 50 | # See: https://github.com/google/wireit/issues/1297#issuecomment-2794737569 51 | - name: Install wireit 52 | run: yarn add wireit@^0.14.12 53 | 54 | - run: yarn build 55 | 56 | - name: yarn test 57 | uses: salesforcecli/github-workflows/.github/actions/retry@main 58 | with: 59 | command: yarn test 60 | env: 61 | SF_DISABLE_TELEMETRY: true 62 | -------------------------------------------------------------------------------- /.github/workflows/validatePR.yml: -------------------------------------------------------------------------------- 1 | on: workflow_call 2 | 3 | jobs: 4 | pr-validation: 5 | if: ${{ !contains(github.event.pull_request.body, '[skip-validate-pr]') && !contains(github.event.pull_request.title, '[skip-validate-pr]') }} 6 | runs-on: "ubuntu-latest" 7 | steps: 8 | - name: Find GUS Work Item in Title 9 | uses: kaisugi/action-regex-match@45cc5bacf016a4c0d2c3c9d0f8b7c2f1b79687b8 10 | id: regex-match-gus-wi-title 11 | with: 12 | text: ${{ github.event.pull_request.title }} 13 | regex: 'W-\d{7,8}' 14 | flags: gmi 15 | 16 | - name: Find GUS Work Item in Body 17 | uses: kaisugi/action-regex-match@45cc5bacf016a4c0d2c3c9d0f8b7c2f1b79687b8 18 | id: regex-match-gus-wi-body 19 | with: 20 | text: ${{ github.event.pull_request.body }} 21 | regex: '@W-\d{7,8}@' 22 | flags: gmi 23 | 24 | # Disabling GHA Run and Github Issue (for now) due to E360 lookup 25 | 26 | # - name: Find Github Action Run 27 | # uses: kaisugi/action-regex-match@45cc5bacf016a4c0d2c3c9d0f8b7c2f1b79687b8 28 | # id: regex-match-gha-run 29 | # with: 30 | # text: ${{ github.event.pull_request.body }} 31 | # regex: 'https:\/\/github\.com\/[\w\.-]+\/[\w\.-]+\/actions\/runs\/' 32 | # flags: gm 33 | 34 | # - name: Find CLI Github Issue 35 | # uses: kaisugi/action-regex-match@45cc5bacf016a4c0d2c3c9d0f8b7c2f1b79687b8 36 | # id: regex-match-cli-gh-issue 37 | # with: 38 | # text: ${{ github.event.pull_request.body }} 39 | # regex: 'forcedotcom\/cli\/issues\/[0-9]+|forcedotcom\/salesforcedx-vscode\/issues\/[0-9]+' 40 | # flags: gm 41 | 42 | - name: Fail if no Work Item references 43 | if: | 44 | github.event.pull_request.user.login != 'dependabot[bot]' && 45 | (github.event.pull_request.user.login != 'SF-CLI-BOT' || github.event.pull_request.user.login != 'svc-cli-bot') && 46 | (steps.regex-match-gus-wi-title.outputs.match == '' || steps.regex-match-gus-wi-body.outputs.match == '') 47 | run: | 48 | echo "::warning::PRs need to reference a GUS Work Item in both the PR title AND body. More details in the logs above. 49 | - PR titles should start with a Work Item followed by a description, ex: W-12345678: My PR title 50 | - PR bodies must include a Work Item wrapped in @s, ex: @W-12345678@ or [@W-12345678@](https://some-url) 51 | - If you absolutely must skip this validation, add [skip-validate-pr] to the PR title or body" 52 | exit 1 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Techical writers will be added as reviewers on markdown changes. 2 | *.md @salesforcecli/cli-docs 3 | 4 | # Comment line immediately above ownership line is reserved for related other information. Please be careful while editing. 5 | #ECCN:Open Source 5D002 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | - Using welcoming and inclusive language 39 | - Being respectful of differing viewpoints and experiences 40 | - Gracefully accepting constructive criticism 41 | - Focusing on what is best for the community 42 | - Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | - The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | - Personal attacks, insulting/derogatory comments, or trolling 49 | - Public or private harassment 50 | - Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | - Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | - Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org 'https://www.contributor-covenant.org/' 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ 106 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Salesforce.com, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Workflows 2 | 3 | Reusable workflows and actions 4 | 5 | > [!IMPORTANT] 6 | > Many of these workflows require a Personal Access Token to function. 7 | > 8 | > - Create a new PAT with Repo access 9 | > - It is recommended that this is a service account user 10 | > - Note: This user/bot will need to have access to push to your repo's default branch. This can be configured in the branch protection rules. 11 | > - Add the PAT as an Actions [Organization secret](https://github.com/organizations/salesforcecli/settings/secrets/actions) 12 | > - Set the `Name` to `SVC_CLI_BOT_GITHUB_TOKEN` 13 | > - Paste in your new PAT as the `Value` 14 | > - Set `Repository Access` to 'Selected Repositories' 15 | > - Click the gear icon to select repos that need access to the PAT 16 | > - This can be edited later 17 | > - Click `Add Secret` 18 | 19 | ## Opinionated publish process for npm 20 | 21 | > github is the source of truth for code AND releases. Get the version/tag/release right on github, then publish to npm based on that. 22 | 23 | ![](./images/plugin-release.png) 24 | 25 | 1. work on a feature branch, commiting with conventional-commits 26 | 2. merge to main 27 | 3. A push to main produces (if your commits have `fix:` or `feat:`) a bumped package.json and a tagged github release via `githubRelease` 28 | 4. A release cause `npmPublish` to run. 29 | 30 | Just need to publish to npm? You could use any public action to do step 4. 31 | Use this repo's `npmPublish` if you need either 32 | 33 | 1. codesigning for Salesforce CLIs 34 | 2. integration with CTC 35 | or if you own other repos that need those features and just want consistency. 36 | 37 | ### githubRelease 38 | 39 | > creates a github release based on conventional commit prefixes. Using commits like `fix: etc` (patch version) and `feat: wow` (minor version). 40 | > A commit whose **body** (not the title) contains `BREAKING CHANGES:` will cause the action to update the packageVersion to the next major version, produce a changelog, tag and release. 41 | 42 | ```yml 43 | name: create-github-release 44 | 45 | on: 46 | push: 47 | branches: [main] 48 | 49 | jobs: 50 | release: 51 | uses: salesforcecli/github-workflows/.github/workflows/create-github-release.yml@main 52 | secrets: inherit 53 | # you can also pass in values for the secrets 54 | # secrets: 55 | # SVC_CLI_BOT_GITHUB_TOKEN: gh_pat00000000 56 | ``` 57 | 58 | ### npmPublish 59 | 60 | > This will verify that the version has not already been published. There are additional params for signing your plugin and integrating with Change Traffic Control (release moratoriums) that you probably only care about if your work for Salesforce. 61 | 62 | example usage 63 | 64 | ```yml 65 | on: 66 | release: 67 | # the result of the githubRelease workflow 68 | types: [published] 69 | 70 | jobs: 71 | my-publish: 72 | uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml 73 | with: 74 | tag: latest 75 | githubTag: ${{ github.event.release.tag_name }} 76 | secrets: inherit 77 | # you can also pass in values for the secrets 78 | # secrets: 79 | # NPM_TOKEN: ^&*$ 80 | ``` 81 | 82 | ### Plugin Signing 83 | 84 | Plugins created by Salesforce teams can be signed automatically with `sign:true` if the repo is in [salesforcecli](https://github.com/salesforcecli) or [forcedotcom](https://github.com/forcedotcom) gitub organization. 85 | 86 | You'll need the CLI team to enable your repo for signing. Ask in https://salesforce-internal.slack.com/archives/C0298EE05PU 87 | 88 | Plugin signing is not available outside of Salesforce. Your users can add your plugin to their allow list (`unsignedPluginAllowList.json`) 89 | 90 | ```yml 91 | on: 92 | release: 93 | # the result of the githubRelease workflow 94 | types: [published] 95 | 96 | jobs: 97 | my-publish: 98 | uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml 99 | with: 100 | sign: true 101 | tag: latest 102 | githubTag: ${{ github.event.release.tag_name }} 103 | secrets: inherit 104 | ``` 105 | 106 | ### Prereleases 107 | 108 | `main` will release to `latest`. Other branches can create github prereleases and publish to other npm dist tags. 109 | 110 | You can create a prerelease one of two ways: 111 | 112 | 1. Create a branch with the `prerelease/**` prefix. Example `prerelease/my-fix` 113 | 1. Once a PR is opened, every commit pushed to this branch will create a prerelease 114 | 2. The default prerelease tag will be `dev`. If another tag is desired, manually set it in your `package.json`. Example: `1.2.3-beta.0` 115 | 1. Manually run the `create-github-release` workflow in the Actions tab 116 | 1. Click `Run workflow` 117 | 1. Select the branch you want to create a prerelease from 118 | 1. Enter the desired prerelease tag: `dev`, `beta`, etc 119 | 120 | > [!NOTE] 121 | > Since conventional commits are used, there is no need to manually remove the prerelease tag from your `package.json`. Once the PR is merged into `main`, conventional commits will bump the version as expected (patch for `fix:`, minor for `feat:`, etc) 122 | 123 | Setup: 124 | 125 | 1. Configure the branch rules for wherever you want to release from 126 | 1. Modify your release and publish workflows like the following 127 | 128 | ```yml 129 | name: create-github-release 130 | 131 | on: 132 | push: 133 | branches: 134 | - main 135 | # point at specific branches, or a naming convention via wildcard 136 | - prerelease/** 137 | tags-ignore: 138 | - "*" 139 | workflow_dispatch: 140 | inputs: 141 | prerelease: 142 | type: string 143 | description: "Name to use for the prerelease: beta, dev, etc. NOTE: If this is already set in the package.json, it does not need to be passed in here." 144 | 145 | jobs: 146 | release: 147 | uses: salesforcecli/github-workflows/.github/workflows/create-github-release.yml@main 148 | secrets: inherit 149 | with: 150 | prerelease: ${{ inputs.prerelease }} 151 | # If this is a push event, we want to skip the release if there are no semantic commits 152 | # However, if this is a manual release (workflow_dispatch), then we want to disable skip-on-empty 153 | # This helps recover from forgetting to add semantic commits ('fix:', 'feat:', etc.) 154 | skip-on-empty: ${{ github.event_name == 'push' }} 155 | ``` 156 | 157 | ```yml 158 | name: publish 159 | 160 | on: 161 | release: 162 | # both release and prereleases 163 | types: [published] 164 | # support manual release in case something goes wrong and needs to be repeated or tested 165 | workflow_dispatch: 166 | inputs: 167 | tag: 168 | description: github tag that needs to publish 169 | type: string 170 | required: true 171 | 172 | jobs: 173 | # parses the package.json version and detects prerelease tag (ex: beta from 4.4.4-beta.0) 174 | getDistTag: 175 | outputs: 176 | tag: ${{ steps.distTag.outputs.tag }} 177 | runs-on: ubuntu-latest 178 | steps: 179 | - uses: actions/checkout@v4 180 | with: 181 | ref: ${{ github.event.release.tag_name || inputs.tag }} 182 | - uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main 183 | id: distTag 184 | 185 | npm: 186 | uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml@main 187 | needs: [getDistTag] 188 | with: 189 | tag: ${{ needs.getDistTag.outputs.tag || 'latest' }} 190 | githubTag: ${{ github.event.release.tag_name || inputs.tag }} 191 | secrets: inherit 192 | ``` 193 | 194 | ### Publishing from multiple long-lived branches 195 | 196 | > In this example `main` publishes to npm on a 1.x.x version and uses `latest`. `some-other-branch` publishes version 2.x.x and uses the `v2` dist tag 197 | 198 | ```yml 199 | name: version, tag and github release 200 | 201 | on: 202 | push: 203 | # add the other branch so that it causes github releases just like main does 204 | branches: [main, some-other-branch] 205 | 206 | jobs: 207 | release: 208 | uses: salesforcecli/github-workflows/.github/workflows/githubRelease.yml@main 209 | secrets: inherit 210 | ``` 211 | 212 | ```yml 213 | on: 214 | release: 215 | # the result of the githubRelease workflow 216 | types: [published] 217 | 218 | jobs: 219 | my-publish: 220 | uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml 221 | with: 222 | # ternary-ish https://github.com/actions/runner/issues/409#issuecomment-752775072 223 | # if the version is 2.x we release it on the `v2` dist tag 224 | tag: ${{ startsWith( github.event.release.tag_name || inputs.tag, '1.') && 'latest' || 'v2'}} 225 | githubTag: ${{ github.event.release.tag_name }} 226 | secrets: inherit 227 | ``` 228 | 229 | ## Opinionated Testing Process 230 | 231 | Write unit tests to tests units of code (a function/method). 232 | 233 | Write not-unit-tests to tests larger parts of code (a command) against real environments/APIs. 234 | 235 | Run the UT first (faster, less expensive for infrastructure/limits). 236 | 237 | ```yml 238 | name: tests 239 | on: 240 | push: 241 | branches-ignore: [main] 242 | workflow_dispatch: 243 | 244 | jobs: 245 | unit-tests: 246 | uses: salesforcecli/github-workflows/.github/workflows/unitTest.yml@main 247 | nuts: 248 | needs: unit-tests 249 | uses: salesforcecli/github-workflows/.github/workflows/nut.yml@main 250 | secrets: inherit 251 | strategy: 252 | matrix: 253 | os: [ubuntu-latest, windows-latest] 254 | fail-fast: false 255 | with: 256 | os: ${{ matrix.os }} 257 | ``` 258 | 259 | ## Other Tooling 260 | 261 | ### nut conditional on commit message 262 | 263 | ```yml 264 | # conditional nuts based on commit message includes a certain string 265 | sandbox-nuts: 266 | needs: [nuts, unit-tests] 267 | if: contains(github.event.push.head_commit.message,'[sb-nuts]') 268 | uses: salesforcecli/github-workflows/.github/workflows/nut.yml@main 269 | secrets: inherit 270 | with: 271 | command: test:nuts:sandbox 272 | os: ubuntu-latest 273 | ``` 274 | 275 | ### externalNut 276 | 277 | > Scenario 278 | > 279 | > 1. you have NUTs on a plugin that uses a library 280 | > 2. you want to check changes to the library against those NUTs 281 | 282 | see https://github.com/forcedotcom/source-deploy-retrieve/blob/> e09d635a7b852196701e71a4b2fba401277da313/.github/workflows/test.yml#L25 for an example 283 | 284 | ### automerge 285 | 286 | > This example calls the automerge job. It'll merge PRs from dependabot that are 287 | > 288 | > 1. up to date with main 289 | > 2. mergeable (per github) 290 | > 3. all checks have completed and none failed (skipped may not have run) 291 | 292 | ```yml 293 | name: automerge 294 | on: 295 | workflow_dispatch: 296 | schedule: 297 | - cron: "56 2,5,8,11 * * *" 298 | 299 | jobs: 300 | automerge: 301 | uses: salesforcecli/github-workflows/.github/workflows/automerge.yml@main 302 | # secrets are needed 303 | secrets: inherit 304 | ``` 305 | 306 | need squash? 307 | 308 | ```yml 309 | automerge: 310 | with: 311 | mergeMethod: squash 312 | ``` 313 | 314 | ### versionInfo 315 | 316 | > requires npm to exist. Use in a workflow that has already done that 317 | > 318 | > given an npmTag (ex: `7.100.0` or `latest`) returns the numeric version (`foo` => `7.100.0`) plus > the xz linux tarball url and the short (7 char) sha. 319 | > 320 | > Intended for releasing CLIs, not for general use on npm packages. 321 | 322 | ```yml 323 | # inside steps 324 | - uses: salesforcecli/github-workflows/.github/actions/versionInfo@main 325 | id: version-info 326 | with: 327 | version: ${{ inputs.version }} 328 | npmPackage: sfdx-cli 329 | - run: echo "version is ${{ steps.version-info.outputs.version }} 330 | - run: echo "sha is ${{ steps.version-info.outputs.sha }} 331 | - run: echo "url is ${{ steps.version-info.outputs.url }} 332 | ``` 333 | 334 | ### validatePR 335 | 336 | > Checks that PRs have a link to a github issue OR a GUS WI in the form of `@W-12456789@` (the `@` are to be compatible with [git2gus](https://github.com/forcedotcom/git2gus)) 337 | 338 | ```yml 339 | name: pr-validation 340 | 341 | on: 342 | pull_request: 343 | types: [opened, reopened, edited] 344 | # only applies to PRs that want to merge to main 345 | branches: [main] 346 | 347 | jobs: 348 | pr-validation: 349 | uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main 350 | ``` 351 | 352 | ### prNotification 353 | 354 | > Mainly used to notify Slack when Pull Requests are opened. 355 | > 356 | > For more info see [.github/actions/prNotification/README.md](.github/actions/prNotification/README.md) 357 | 358 | ```yaml 359 | name: Slack Pull Request Notification 360 | 361 | on: 362 | pull_request: 363 | types: [opened, reopened] 364 | 365 | jobs: 366 | build: 367 | runs-on: ubuntu-latest 368 | steps: 369 | - name: Notify Slack on PR open 370 | env: 371 | WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 372 | PULL_REQUEST_AUTHOR_ICON_URL: ${{ github.event.pull_request.user.avatar_url }} 373 | PULL_REQUEST_AUTHOR_NAME: ${{ github.event.pull_request.user.login }} 374 | PULL_REQUEST_AUTHOR_PROFILE_URL: ${{ github.event.pull_request.user.html_url }} 375 | PULL_REQUEST_BASE_BRANCH_NAME: ${{ github.event.pull_request.base.ref }} 376 | PULL_REQUEST_COMPARE_BRANCH_NAME: ${{ github.event.pull_request.head.ref }} 377 | PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} 378 | PULL_REQUEST_REPO: ${{ github.event.pull_request.head.repo.name }} 379 | PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} 380 | PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }} 381 | uses: salesforcecli/github-workflows/.github/actions/prNotification@main 382 | ``` 383 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. 8 | -------------------------------------------------------------------------------- /images/plugin-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforcecli/github-workflows/9721a3570532fb953e66da4d0025d822b83d74fd/images/plugin-release.png --------------------------------------------------------------------------------