diff --git a/build/continuous-deploy-fingerprint/index.js b/build/continuous-deploy-fingerprint/index.js index 7f76d65a..0e023b7b 100644 --- a/build/continuous-deploy-fingerprint/index.js +++ b/build/continuous-deploy-fingerprint/index.js @@ -42402,15 +42402,27 @@ exports.getPullRequestFromGitCommitShaAsync = getPullRequestFromGitCommitShaAsyn Object.defineProperty(exports, "__esModule", ({ value: true })); exports.loadProjectConfig = void 0; const exec_1 = __nccwpck_require__(1514); +const io_1 = __nccwpck_require__(7436); /** * Load the Expo app project config in the given directory. * This runs `expo config` command instead of using `@expo/config` directly, * to use the app's own version of the config. */ -async function loadProjectConfig(cwd) { +async function loadProjectConfig(cwd, easEnvironment) { let stdout = ''; + const baseArguments = ['expo', 'config', '--json', '--type', 'public']; + let commandLine; + let args; + if (easEnvironment) { + commandLine = await (0, io_1.which)('eas', true); + args = ['env:exec', easEnvironment, ['npx', ...baseArguments].join(' ')]; + } + else { + commandLine = 'npx'; + args = baseArguments; + } try { - ({ stdout } = await (0, exec_1.getExecOutput)('npx', ['expo', 'config', '--json', '--type', 'public'], { + ({ stdout } = await (0, exec_1.getExecOutput)(commandLine, args, { cwd, silent: true, })); @@ -44536,27 +44548,38 @@ const expo_1 = __nccwpck_require__(2489); const github_1 = __nccwpck_require__(978); const project_1 = __nccwpck_require__(7191); const worker_1 = __nccwpck_require__(8912); +function getInput(name, options) { + const value = (0, core_1.getInput)(name, options); + if (!value) { + if (options?.required) { + throw new Error(`Input ${name} is required.`); + } + return null; + } + return value; +} function collectContinuousDeployFingerprintInput() { function validatePlatformInput(platformInput) { return ['android', 'ios', 'all'].includes(platformInput); } - const platformInput = (0, core_1.getInput)('platform'); + const platformInput = getInput('platform', { required: true }); if (!validatePlatformInput(platformInput)) { throw new Error(`Invalid platform: ${platformInput}. Must be one of "all", "ios", "android".`); } return { - profile: (0, core_1.getInput)('profile'), - branch: (0, core_1.getInput)('branch'), + profile: getInput('profile', { required: true }), + branch: getInput('branch', { required: true }), platform: platformInput, - githubToken: (0, core_1.getInput)('github-token'), - workingDirectory: (0, core_1.getInput)('working-directory'), + githubToken: getInput('github-token', { required: true }), + workingDirectory: getInput('working-directory', { required: true }), + environment: getInput('environment'), }; } exports.collectContinuousDeployFingerprintInput = collectContinuousDeployFingerprintInput; (0, worker_1.executeAction)(continuousDeployFingerprintAction); async function continuousDeployFingerprintAction(input = collectContinuousDeployFingerprintInput()) { const isInPullRequest = (0, github_1.hasPullContext)(); - const config = await (0, project_1.loadProjectConfig)(input.workingDirectory); + const config = await (0, project_1.loadProjectConfig)(input.workingDirectory, input.environment); const projectId = config.extra?.eas?.projectId; if (!projectId) { return (0, core_1.setFailed)(`Missing 'extra.eas.projectId' in app.json or app.config.js.`); @@ -44569,6 +44592,7 @@ async function continuousDeployFingerprintAction(input = collectContinuousDeploy profile: input.profile, workingDirectory: input.workingDirectory, isInPullRequest, + environment: input.environment, }) : null, platformsToRun.has('ios') @@ -44577,6 +44601,7 @@ async function continuousDeployFingerprintAction(input = collectContinuousDeploy profile: input.profile, workingDirectory: input.workingDirectory, isInPullRequest, + environment: input.environment, }) : null, ]); @@ -44584,6 +44609,7 @@ async function continuousDeployFingerprintAction(input = collectContinuousDeploy const updates = await publishEASUpdatesAsync({ cwd: input.workingDirectory, branch: input.branch, + environment: input.environment, }); if (!isInPullRequest) { (0, core_1.info)(`Skipped comment: action was not run from a pull request`); @@ -44616,11 +44642,12 @@ async function continuousDeployFingerprintAction(input = collectContinuousDeploy (0, core_1.setOutput)('update-output', updates); } exports.continuousDeployFingerprintAction = continuousDeployFingerprintAction; -async function buildForPlatformIfNecessaryAsync({ platform, workingDirectory, profile, isInPullRequest, }) { +async function buildForPlatformIfNecessaryAsync({ platform, workingDirectory, profile, isInPullRequest, environment, }) { const humanReadablePlatformName = platform === 'ios' ? 'iOS' : 'Android'; const fingerprintHash = await getFingerprintHashForPlatformAsync({ cwd: workingDirectory, platform, + environment, }); (0, core_1.info)(`${humanReadablePlatformName} fingerprint: ${fingerprintHash}`); (0, core_1.info)(`Looking for ${humanReadablePlatformName} builds with matching runtime version (fingerprint)...`); @@ -44652,10 +44679,27 @@ async function buildForPlatformIfNecessaryAsync({ platform, workingDirectory, pr }; } } -async function getFingerprintHashForPlatformAsync({ cwd, platform, }) { +async function getFingerprintHashForPlatformAsync({ cwd, platform, environment, }) { try { const extraArgs = (0, core_1.isDebug)() ? ['--debug'] : []; - const { stdout } = await (0, exec_1.getExecOutput)('npx', ['expo-updates', 'fingerprint:generate', '--platform', platform, ...extraArgs], { + const baseArguments = [ + 'expo-updates', + 'fingerprint:generate', + '--platform', + platform, + ...extraArgs, + ]; + let commandLine; + let args; + if (environment) { + commandLine = await (0, io_1.which)('eas', true); + args = ['env:exec', environment, ['npx', ...baseArguments].join(' ')]; + } + else { + commandLine = 'npx'; + args = baseArguments; + } + const { stdout } = await (0, exec_1.getExecOutput)(commandLine, args, { cwd, silent: !(0, core_1.isDebug)(), }); @@ -44737,10 +44781,14 @@ async function createEASBuildAsync({ cwd, profile, platform, }) { } return JSON.parse(stdout)[0]; } -async function publishEASUpdatesAsync({ cwd, branch, }) { +async function publishEASUpdatesAsync({ cwd, branch, environment, }) { let stdout; try { - const execOutput = await (0, exec_1.getExecOutput)(await (0, io_1.which)('eas', true), ['update', '--auto', '--branch', branch, '--non-interactive', '--json'], { + const args = ['update', '--auto', '--branch', branch, '--non-interactive', '--json']; + if (environment) { + args.push('--environment', environment); + } + const execOutput = await (0, exec_1.getExecOutput)(await (0, io_1.which)('eas', true), args, { cwd, silent: !(0, core_1.isDebug)(), }); diff --git a/build/preview-build/index.js b/build/preview-build/index.js index 8c33ab1e..0f84b275 100644 --- a/build/preview-build/index.js +++ b/build/preview-build/index.js @@ -88827,7 +88827,7 @@ function collectPreviewBuildActionInput() { exports.collectPreviewBuildActionInput = collectPreviewBuildActionInput; (0, worker_1.executeAction)(previewAction); async function previewAction(input = collectPreviewBuildActionInput()) { - const config = await (0, project_1.loadProjectConfig)(input.workingDirectory); + const config = await (0, project_1.loadProjectConfig)(input.workingDirectory, null); if (!config.extra?.eas?.projectId) { return (0, core_1.setFailed)('Missing "extra.eas.projectId" in app.json or app.config.js. Please run `eas build:configure` first.'); } @@ -90070,15 +90070,27 @@ exports.installPackage = installPackage; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.loadProjectConfig = void 0; const exec_1 = __nccwpck_require__(1514); +const io_1 = __nccwpck_require__(7436); /** * Load the Expo app project config in the given directory. * This runs `expo config` command instead of using `@expo/config` directly, * to use the app's own version of the config. */ -async function loadProjectConfig(cwd) { +async function loadProjectConfig(cwd, easEnvironment) { let stdout = ''; + const baseArguments = ['expo', 'config', '--json', '--type', 'public']; + let commandLine; + let args; + if (easEnvironment) { + commandLine = await (0, io_1.which)('eas', true); + args = ['env:exec', easEnvironment, ['npx', ...baseArguments].join(' ')]; + } + else { + commandLine = 'npx'; + args = baseArguments; + } try { - ({ stdout } = await (0, exec_1.getExecOutput)('npx', ['expo', 'config', '--json', '--type', 'public'], { + ({ stdout } = await (0, exec_1.getExecOutput)(commandLine, args, { cwd, silent: true, })); diff --git a/build/preview/index.js b/build/preview/index.js index 49f66f87..371e751f 100644 --- a/build/preview/index.js +++ b/build/preview/index.js @@ -41877,7 +41877,7 @@ async function previewAction(input = previewInput()) { if (!update) { return (0, core_1.setFailed)(`No update found in command output.`); } - const config = await (0, project_1.loadProjectConfig)(input.workingDirectory); + const config = await (0, project_1.loadProjectConfig)(input.workingDirectory, null); if (!config.extra?.eas?.projectId) { return (0, core_1.setFailed)(`Missing 'extra.eas.projectId' in app.json or app.config.js.`); } @@ -42614,15 +42614,27 @@ exports.getPullRequestFromGitCommitShaAsync = getPullRequestFromGitCommitShaAsyn Object.defineProperty(exports, "__esModule", ({ value: true })); exports.loadProjectConfig = void 0; const exec_1 = __nccwpck_require__(1514); +const io_1 = __nccwpck_require__(7436); /** * Load the Expo app project config in the given directory. * This runs `expo config` command instead of using `@expo/config` directly, * to use the app's own version of the config. */ -async function loadProjectConfig(cwd) { +async function loadProjectConfig(cwd, easEnvironment) { let stdout = ''; + const baseArguments = ['expo', 'config', '--json', '--type', 'public']; + let commandLine; + let args; + if (easEnvironment) { + commandLine = await (0, io_1.which)('eas', true); + args = ['env:exec', easEnvironment, ['npx', ...baseArguments].join(' ')]; + } + else { + commandLine = 'npx'; + args = baseArguments; + } try { - ({ stdout } = await (0, exec_1.getExecOutput)('npx', ['expo', 'config', '--json', '--type', 'public'], { + ({ stdout } = await (0, exec_1.getExecOutput)(commandLine, args, { cwd, silent: true, })); diff --git a/continuous-deploy-fingerprint/README.md b/continuous-deploy-fingerprint/README.md index 1b3c625e..7c2e921d 100644 --- a/continuous-deploy-fingerprint/README.md +++ b/continuous-deploy-fingerprint/README.md @@ -38,6 +38,7 @@ ## Overview `continuous-deploy-fingerprint` is a GitHub Action continuously deploys an Expo project using the expo-updates fingerprint runtime version policy. When run, it performs the following tasks in order, once for each platform: + 1. Check current fingerprint of the project. 2. Check for EAS builds with specified profile matching that fingerprint. 3. If a in-progress or finished EAS build doesn't exist, start one. @@ -55,13 +56,15 @@ This action is customizable through variables defined in the [`action.yml`](action.yml). Here is a summary of all the input options you can use. -| variable | default | description | -| --------------------- | -------------- | ---------------------------------------------------------------------------- | -| **profile** | (required) | The EAS Build profile to use | -| **branch** | (required) | The EAS Update branch on which to publish | -| **working-directory** | - | The relative directory of your Expo app | -| **platform** | `all` | The platform to deploy on (available options are `ios`, `android` and `all`) | -| **github-token** | `github.token` | GitHub token to use when commenting on PR ([read more](#github-tokens)) | +| variable | default | description | +| --------------- | ---------- | ------------------------------------------------------------------------ | +| **profile** | (required) | The EAS Build profile to use | +| **branch** | (required) | The EAS Update branch on which to publish | +| **environment** | - | The environment to use for server-side defined EAS environment variables | + +| **working-directory** | - | The relative directory of your Expo app | +| **platform** | `all` | The platform to deploy on (available options are `ios`, `android` and `all`) | +| **github-token** | `github.token` | GitHub token to use when commenting on PR ([read more](#github-tokens)) | And the action will generate these [outputs](#available-outputs) for other actions to do something based on what this action did. @@ -69,13 +72,13 @@ And the action will generate these [outputs](#available-outputs) for other actio In case you want to reuse this action for other purpose, this action will set the following action outputs. -| output name | description | -| ------------------------ | ------------------------------ | -| **ios-fingerprint** | The iOS fingerprint of the current commit. | -| **android-fingerprint** | The Android fingerprint of the current commit. | -| **android-build-id** | ID for Android EAS Build if one was started. | -| **ios-build-id** | ID for iOS EAS Build if one was started. | -| **update-output** | The output (JSON) from the `eas update` command. | +| output name | description | +| ----------------------- | ------------------------------------------------ | +| **ios-fingerprint** | The iOS fingerprint of the current commit. | +| **android-fingerprint** | The Android fingerprint of the current commit. | +| **android-build-id** | ID for Android EAS Build if one was started. | +| **ios-build-id** | ID for iOS EAS Build if one was started. | +| **update-output** | The output (JSON) from the `eas update` command. | ## Caveats @@ -93,6 +96,7 @@ You can read more about this in the [GitHub Actions documentation][link-actions] ### Continuously deploy after tests on main branch and pull requests This workflow continuously deploys: + - main branch -> production EAS Build profile and EAS Update branch - PR branches -> development EAS Build profile and EAS Update branch diff --git a/continuous-deploy-fingerprint/action.yml b/continuous-deploy-fingerprint/action.yml index e8f97b36..f5602c43 100644 --- a/continuous-deploy-fingerprint/action.yml +++ b/continuous-deploy-fingerprint/action.yml @@ -15,6 +15,9 @@ inputs: branch: description: The EAS Update branch on which to publish. required: true + environment: + description: The environment to use for server-side defined EAS environment variables during command execution + required: false platform: description: The platform on which to publish. Possible values are 'all' | 'android' | 'ios' required: false diff --git a/src/actions/continuous-deploy-fingerprint.ts b/src/actions/continuous-deploy-fingerprint.ts index 9c8e3cc1..644483a0 100644 --- a/src/actions/continuous-deploy-fingerprint.ts +++ b/src/actions/continuous-deploy-fingerprint.ts @@ -1,4 +1,11 @@ -import { getInput, info, isDebug, setFailed, setOutput } from '@actions/core'; +import { + InputOptions, + getInput as getActionInput, + info, + isDebug, + setFailed, + setOutput, +} from '@actions/core'; import { getExecOutput } from '@actions/exec'; import { which } from '@actions/io'; import { ExpoConfig } from '@expo/config'; @@ -13,22 +20,36 @@ import { executeAction } from '../worker'; type BuildRunInfo = { buildInfo: BuildInfo; isNew: boolean; fingerprintHash: string }; type PlatformArg = 'android' | 'ios' | 'all'; +function getInput(name: string, options: { required: true }): string; +function getInput(name: string): string | null; +function getInput(name: string, options?: InputOptions): string | null { + const value = getActionInput(name, options); + if (!value) { + if (options?.required) { + throw new Error(`Input ${name} is required.`); + } + return null; + } + return value; +} + export function collectContinuousDeployFingerprintInput() { function validatePlatformInput(platformInput: string): platformInput is PlatformArg { return ['android', 'ios', 'all'].includes(platformInput); } - const platformInput = getInput('platform'); + const platformInput = getInput('platform', { required: true }); if (!validatePlatformInput(platformInput)) { throw new Error(`Invalid platform: ${platformInput}. Must be one of "all", "ios", "android".`); } return { - profile: getInput('profile'), - branch: getInput('branch'), + profile: getInput('profile', { required: true }), + branch: getInput('branch', { required: true }), platform: platformInput, - githubToken: getInput('github-token'), - workingDirectory: getInput('working-directory'), + githubToken: getInput('github-token', { required: true }), + workingDirectory: getInput('working-directory', { required: true }), + environment: getInput('environment'), }; } @@ -39,7 +60,7 @@ export async function continuousDeployFingerprintAction( ) { const isInPullRequest = hasPullContext(); - const config = await loadProjectConfig(input.workingDirectory); + const config = await loadProjectConfig(input.workingDirectory, input.environment); const projectId = config.extra?.eas?.projectId; if (!projectId) { return setFailed(`Missing 'extra.eas.projectId' in app.json or app.config.js.`); @@ -54,6 +75,7 @@ export async function continuousDeployFingerprintAction( profile: input.profile, workingDirectory: input.workingDirectory, isInPullRequest, + environment: input.environment, }) : null, platformsToRun.has('ios') @@ -62,6 +84,7 @@ export async function continuousDeployFingerprintAction( profile: input.profile, workingDirectory: input.workingDirectory, isInPullRequest, + environment: input.environment, }) : null, ]); @@ -71,6 +94,7 @@ export async function continuousDeployFingerprintAction( const updates = await publishEASUpdatesAsync({ cwd: input.workingDirectory, branch: input.branch, + environment: input.environment, }); if (!isInPullRequest) { @@ -111,17 +135,20 @@ async function buildForPlatformIfNecessaryAsync({ workingDirectory, profile, isInPullRequest, + environment, }: { platform: 'ios' | 'android'; profile: string; workingDirectory: string; isInPullRequest: boolean; + environment: string | null; }): Promise { const humanReadablePlatformName = platform === 'ios' ? 'iOS' : 'Android'; const fingerprintHash = await getFingerprintHashForPlatformAsync({ cwd: workingDirectory, platform, + environment, }); info(`${humanReadablePlatformName} fingerprint: ${fingerprintHash}`); @@ -165,20 +192,37 @@ async function buildForPlatformIfNecessaryAsync({ async function getFingerprintHashForPlatformAsync({ cwd, platform, + environment, }: { cwd: string; platform: 'ios' | 'android'; + environment: string | null; }): Promise { try { const extraArgs = isDebug() ? ['--debug'] : []; - const { stdout } = await getExecOutput( - 'npx', - ['expo-updates', 'fingerprint:generate', '--platform', platform, ...extraArgs], - { - cwd, - silent: !isDebug(), - } - ); + + const baseArguments = [ + 'expo-updates', + 'fingerprint:generate', + '--platform', + platform, + ...extraArgs, + ]; + + let commandLine: string; + let args: string[]; + if (environment) { + commandLine = await which('eas', true); + args = ['env:exec', environment, ['npx', ...baseArguments].join(' ')]; + } else { + commandLine = 'npx'; + args = baseArguments; + } + + const { stdout } = await getExecOutput(commandLine, args, { + cwd, + silent: !isDebug(), + }); const { hash } = JSON.parse(stdout); if (!hash || typeof hash !== 'string') { throw new Error(`Invalid fingerprint output: ${stdout}`); @@ -293,20 +337,22 @@ async function createEASBuildAsync({ async function publishEASUpdatesAsync({ cwd, branch, + environment, }: { cwd: string; branch: string; + environment: string | null; }): Promise { let stdout: string; try { - const execOutput = await getExecOutput( - await which('eas', true), - ['update', '--auto', '--branch', branch, '--non-interactive', '--json'], - { - cwd, - silent: !isDebug(), - } - ); + const args = ['update', '--auto', '--branch', branch, '--non-interactive', '--json']; + if (environment) { + args.push('--environment', environment); + } + const execOutput = await getExecOutput(await which('eas', true), args, { + cwd, + silent: !isDebug(), + }); stdout = execOutput.stdout; } catch (error: unknown) { throw new Error(`Could not create a new EAS Update: ${String(error)}`); diff --git a/src/actions/preview-build.ts b/src/actions/preview-build.ts index 008546c5..8eb7fa9a 100644 --- a/src/actions/preview-build.ts +++ b/src/actions/preview-build.ts @@ -51,7 +51,7 @@ export function collectPreviewBuildActionInput() { executeAction(previewAction); export async function previewAction(input = collectPreviewBuildActionInput()) { - const config = await loadProjectConfig(input.workingDirectory); + const config = await loadProjectConfig(input.workingDirectory, null); if (!config.extra?.eas?.projectId) { return setFailed( 'Missing "extra.eas.projectId" in app.json or app.config.js. Please run `eas build:configure` first.' diff --git a/src/actions/preview.ts b/src/actions/preview.ts index f11f5bc5..87fd8269 100644 --- a/src/actions/preview.ts +++ b/src/actions/preview.ts @@ -53,7 +53,7 @@ export async function previewAction(input = previewInput()) { return setFailed(`No update found in command output.`); } - const config = await loadProjectConfig(input.workingDirectory); + const config = await loadProjectConfig(input.workingDirectory, null); if (!config.extra?.eas?.projectId) { return setFailed(`Missing 'extra.eas.projectId' in app.json or app.config.js.`); } diff --git a/src/project.ts b/src/project.ts index 24f72287..acc88bb9 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,4 +1,5 @@ import { getExecOutput } from '@actions/exec'; +import { which } from '@actions/io'; import { ExpoConfig } from '@expo/config'; /** @@ -6,11 +7,26 @@ import { ExpoConfig } from '@expo/config'; * This runs `expo config` command instead of using `@expo/config` directly, * to use the app's own version of the config. */ -export async function loadProjectConfig(cwd: string): Promise { +export async function loadProjectConfig( + cwd: string, + easEnvironment: string | null +): Promise { let stdout = ''; + const baseArguments = ['expo', 'config', '--json', '--type', 'public']; + + let commandLine: string; + let args: string[]; + if (easEnvironment) { + commandLine = await which('eas', true); + args = ['env:exec', easEnvironment, ['npx', ...baseArguments].join(' ')]; + } else { + commandLine = 'npx'; + args = baseArguments; + } + try { - ({ stdout } = await getExecOutput('npx', ['expo', 'config', '--json', '--type', 'public'], { + ({ stdout } = await getExecOutput(commandLine, args, { cwd, silent: true, }));