Skip to content

Commit

Permalink
feat: stop using prebuild in eas preview recipe (#139)
Browse files Browse the repository at this point in the history
* feat: stop using prebuild in eas preview recipe

* change prebuild declaration in interface to async

* add detox expo plugin only if its not present yet
  • Loading branch information
km1chno authored Nov 28, 2024
1 parent 3ece6e1 commit 2fd1d1b
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 81 deletions.
83 changes: 71 additions & 12 deletions src/extensions/expo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { execSync } from 'child_process'
import { CycliToolbox, ProjectContext, Platform, Environment } from '../types'

module.exports = (toolbox: CycliToolbox) => {
const prebuild = (
const prebuild = async (
context: ProjectContext,
{ cleanAfter }: { cleanAfter: boolean }
) => {
Expand All @@ -10,7 +11,7 @@ module.exports = (toolbox: CycliToolbox) => {

toolbox.print.info('⚙️ Running expo prebuild to setup app configuration.')

toolbox.interactive.spawnSubprocess(
await toolbox.interactive.spawnSubprocess(
'Expo prebuild',
`npx expo prebuild --${context.packageManager}`,
{ alwaysPrintStderr: true }
Expand All @@ -23,24 +24,46 @@ module.exports = (toolbox: CycliToolbox) => {
}

const buildConfigure = async (): Promise<void> => {
toolbox.interactive.spawnSubprocess(
await toolbox.interactive.spawnSubprocess(
'EAS Build configuration',
'npx eas-cli build:configure -p all'
'npx eas-cli build:configure -p all',
{
onError: async () => {
await toolbox.interactive.actionPrompt(
[
'EAS Build configuration failed. If the reason is missing configuration in dynamic file,',
'please update it manually before proceeding.\n',
].join(' ')
)
return true
},
}
)

toolbox.interactive.step('Created default EAS Build configuration.')
}

const updateConfigure = async () => {
toolbox.interactive.spawnSubprocess(
await toolbox.interactive.spawnSubprocess(
'EAS Update configuration',
'npx eas-cli update:configure'
'npx eas-cli update:configure',
{
onError: async () => {
await toolbox.interactive.actionPrompt(
[
'EAS Update configuration failed. If the reason is missing configuration in dynamic file,',
'please update it manually before proceeding.\n',
].join(' ')
)
return true
},
}
)

toolbox.interactive.step('Created default EAS Update configuration.')
}

const credentialsConfigureBuild = ({
const credentialsConfigureBuild = async ({
platform,
environment,
}: {
Expand All @@ -49,17 +72,50 @@ module.exports = (toolbox: CycliToolbox) => {
}) => {
const platformName = platform === 'android' ? 'Android' : 'iOS'

toolbox.interactive.spawnSubprocess(
await toolbox.interactive.spawnSubprocess(
`EAS Credentials configuration for ${platformName}`,
`npx eas-cli credentials:configure-build -p ${platform} -e ${environment}`
)

toolbox.interactive.step(`Configured EAS Credentials for ${platformName}.`)
}

const login = async (): Promise<void> => {
await toolbox.interactive.spawnSubprocess('EAS Login', 'npx eas-cli login')
toolbox.interactive.step(`Successfully logged in to EAS.`)
}

const whoami = (): string | undefined => {
let username: string | undefined = undefined

try {
username = execSync('npx eas-cli whoami', {
stdio: ['ignore', 'pipe', 'ignore'],
})
.toString()
.trim()
} catch (error: unknown) {}

return username
}

const whoamiWithForcedLogin = async (): Promise<string | undefined> => {
const username = whoami()

if (!username) {
await login()
return whoami()
}

return username
}

toolbox.expo = {
prebuild,
eas: {
login,
whoami,
whoamiWithForcedLogin,
buildConfigure,
updateConfigure,
credentialsConfigureBuild,
Expand All @@ -72,17 +128,20 @@ export interface ExpoExtension {
prebuild: (
context: ProjectContext,
{ cleanAfter }: { cleanAfter: boolean }
) => void
) => Promise<void>
eas: {
buildConfigure: () => void
updateConfigure: () => void
login: () => Promise<void>
whoami: () => string | undefined
whoamiWithForcedLogin: () => Promise<string | undefined>
buildConfigure: () => Promise<void>
updateConfigure: () => Promise<void>
credentialsConfigureBuild: ({
platform,
environment,
}: {
platform: Platform
environment: Environment
}) => void
}) => Promise<void>
}
}
}
52 changes: 44 additions & 8 deletions src/extensions/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
intro as clackIntro,
isCancel,
log as clackLog,
text,
} from '@clack/prompts'
import { ConfirmPrompt, MultiSelectPrompt, SelectPrompt } from '@clack/core'
import { execSync } from 'child_process'
Expand Down Expand Up @@ -223,6 +224,21 @@ module.exports = (toolbox: CycliToolbox) => {
return Boolean(confirmed)
}

const textInput = async (
message: string,
placeholder?: string,
initialValue?: string,
validate?: (input: string) => string | void
): Promise<string> => {
const input = await text({ message, placeholder, initialValue, validate })

if (isCancel(input)) {
throw CYCLI_ERROR_CANCEL
}

return input as string
}

const multiselect = async (
message: string,
hint: string,
Expand Down Expand Up @@ -450,11 +466,17 @@ module.exports = (toolbox: CycliToolbox) => {
info(`${S_UR}\n${S_DL}${S_VBAR.repeat(width - 1)}`, color)
}

const spawnSubprocess = (
const spawnSubprocess = async (
processName: string,
command: string,
{ alwaysPrintStderr = false }: { alwaysPrintStderr?: boolean } = {}
) => {
{
alwaysPrintStderr = false,
onError,
}: {
alwaysPrintStderr?: boolean
onError?: (stderr: string) => Promise<boolean>
} = {}
): Promise<void> => {
vspace()
sectionHeader(`Running ${processName}...`, { color: 'dim' })

Expand All @@ -469,18 +491,22 @@ module.exports = (toolbox: CycliToolbox) => {
throw Error(`Failed to execute command ${COLORS.bold(command)}.`)
}

error(`${processName} exiter with error status code ${e.status}.`)

if (
'stderr' in e &&
(typeof e.stderr === 'string' || e.stderr instanceof Buffer)
) {
const stderr = e.stderr.toString()
if (!alwaysPrintStderr && stderr) {
// In no specific onError callback was provided, just print stderr to console.
if (!onError && !alwaysPrintStderr && stderr) {
info(stderr, 'red')
} else if (onError && (await onError(stderr))) {
// If onError returns true, the error was handled so we don't throw exception.
return
}
}

error(`${processName} exited with error status code ${e.status}.`)

throw Error(
`Failed to execute command ${COLORS.bold(
command
Expand All @@ -495,6 +521,7 @@ module.exports = (toolbox: CycliToolbox) => {
toolbox.interactive = {
actionPrompt,
confirm,
textInput,
multiselect,
surveyStep,
surveyWarning,
Expand Down Expand Up @@ -530,6 +557,12 @@ export interface InteractiveExtension {
message: string,
options: { type: 'normal' | 'warning' }
) => Promise<boolean>
textInput: (
message: string,
placeholder?: string,
initialValue?: string,
validate?: (input: string) => string | void
) => Promise<string>
actionPrompt: (message: string) => Promise<void>
surveyStep: (message: string) => void
surveyWarning: (message: string) => void
Expand All @@ -551,7 +584,10 @@ export interface InteractiveExtension {
spawnSubprocess: (
processName: string,
command: string,
options?: { alwaysPrintStderr?: boolean }
) => void
options?: {
alwaysPrintStderr?: boolean
onError?: (stderr: string) => Promise<boolean>
}
) => Promise<void>
}
}
Loading

0 comments on commit 2fd1d1b

Please sign in to comment.