mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1774eafcbf | ||
|
|
41a2d9e04a | ||
|
|
2beb38361a | ||
|
|
ec08ded0b1 | ||
|
|
690470aaf4 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'eas-cli': patch
|
||||
'@expo/build-tools': patch
|
||||
'@expo/turtle-spawn': patch
|
||||
---
|
||||
|
||||
Prevent stdin waits in Expo CLI config, prebuild, and export commands by closing stdin and preserving explicit stdio options when a logger is provided.
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BuildJob } from '@expo/eas-build-job';
|
||||
import { bunyan } from '@expo/logger';
|
||||
import { SpawnOptions } from '@expo/turtle-spawn';
|
||||
|
||||
import {
|
||||
installDependenciesWithNpmCacheFallbackAsync,
|
||||
@@ -17,7 +16,7 @@ export async function prebuildAsync<TJob extends BuildJob>(
|
||||
ctx: BuildContext<TJob>,
|
||||
{ logger, workingDir, options }: { logger: bunyan; workingDir: string; options?: PrebuildOptions }
|
||||
): Promise<void> {
|
||||
const spawnOptions: SpawnOptions = {
|
||||
const spawnOptions = {
|
||||
cwd: workingDir,
|
||||
logger,
|
||||
env: {
|
||||
|
||||
@@ -44,7 +44,6 @@ describe(createEasExportBuildFunction, () => {
|
||||
cwd: buildStep.ctx.workingDirectory,
|
||||
env: expect.objectContaining({ HOME: '/tmp/home' }),
|
||||
logger: buildStep.ctx.logger,
|
||||
stdio: 'pipe',
|
||||
},
|
||||
});
|
||||
expect(buildStep.outputById.export_dir.value).toBe('dist');
|
||||
|
||||
@@ -89,7 +89,6 @@ export function createEasExportBuildFunction(): BuildFunction {
|
||||
cwd: stepsCtx.workingDirectory,
|
||||
env,
|
||||
logger: stepsCtx.logger,
|
||||
stdio: 'pipe',
|
||||
},
|
||||
});
|
||||
outputs.export_dir.set(outputDir);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Platform } from '@expo/config';
|
||||
import { BuildFunction, BuildStepInput, BuildStepInputValueTypeName } from '@expo/steps';
|
||||
import spawn from '@expo/turtle-spawn';
|
||||
|
||||
import { installNodeModules } from './installNodeModules';
|
||||
import { PackageManager, resolvePackageManager } from '../../utils/packageManager';
|
||||
import { resolvePackageManager } from '../../utils/packageManager';
|
||||
import { runExpoCliCommand } from '../../utils/project';
|
||||
|
||||
export function createPrebuildBuildFunction(): BuildFunction {
|
||||
return new BuildFunction({
|
||||
@@ -41,7 +41,6 @@ export function createPrebuildBuildFunction(): BuildFunction {
|
||||
customPrebuildCommand: job.platform ? job.experimental?.prebuildCommand : undefined,
|
||||
clean: inputs.clean.value as boolean,
|
||||
});
|
||||
const argsWithExpo = ['expo', ...prebuildCommandArgs];
|
||||
const options = {
|
||||
cwd: stepCtx.workingDirectory,
|
||||
logger,
|
||||
@@ -51,17 +50,7 @@ export function createPrebuildBuildFunction(): BuildFunction {
|
||||
...(appleTeamId ? { APPLE_TEAM_ID: appleTeamId } : {}),
|
||||
},
|
||||
};
|
||||
if (packageManager === PackageManager.NPM) {
|
||||
await spawn('npx', argsWithExpo, options);
|
||||
} else if (packageManager === PackageManager.YARN) {
|
||||
await spawn('yarn', argsWithExpo, options);
|
||||
} else if (packageManager === PackageManager.PNPM) {
|
||||
await spawn('pnpm', argsWithExpo, options);
|
||||
} else if (packageManager === PackageManager.BUN) {
|
||||
await spawn('bun', argsWithExpo, options);
|
||||
} else {
|
||||
throw new Error(`Unsupported package manager: ${packageManager}`);
|
||||
}
|
||||
await runExpoCliCommand({ packageManager, args: prebuildCommandArgs, options });
|
||||
await installNodeModules(stepCtx, env);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ jest.mock('../packageManager', () => ({
|
||||
|
||||
describe(runExpoCliCommand, () => {
|
||||
describe('Expo SDK >= 46', () => {
|
||||
const logger = {} as any;
|
||||
|
||||
it('spawns expo via "npx" when package manager is npm', () => {
|
||||
const mockExpoConfig = mock<ExpoConfig>();
|
||||
when(mockExpoConfig.sdkVersion).thenReturn('46.0.0');
|
||||
@@ -38,8 +40,16 @@ describe(runExpoCliCommand, () => {
|
||||
when(mockCtx.appConfig).thenReturn(Promise.resolve(expoConfig));
|
||||
const ctx = instance(mockCtx);
|
||||
|
||||
void runExpoCliCommand({ args: ['doctor'], options: {}, packageManager: ctx.packageManager });
|
||||
expect(spawn).toHaveBeenCalledWith('npx', ['expo', 'doctor'], expect.any(Object));
|
||||
void runExpoCliCommand({
|
||||
args: ['doctor'],
|
||||
options: { logger },
|
||||
packageManager: ctx.packageManager,
|
||||
});
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'npx',
|
||||
['expo', 'doctor'],
|
||||
expect.objectContaining({ logger, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
);
|
||||
});
|
||||
|
||||
it('spawns expo via "yarn" when package manager is yarn', () => {
|
||||
@@ -48,12 +58,20 @@ describe(runExpoCliCommand, () => {
|
||||
const expoConfig = instance(mockExpoConfig);
|
||||
|
||||
const mockCtx = mock<BuildContext<Android.Job>>();
|
||||
when(mockCtx.packageManager).thenReturn(PackageManager.NPM);
|
||||
when(mockCtx.packageManager).thenReturn(PackageManager.YARN);
|
||||
when(mockCtx.appConfig).thenReturn(Promise.resolve(expoConfig));
|
||||
const ctx = instance(mockCtx);
|
||||
|
||||
void runExpoCliCommand({ args: ['doctor'], options: {}, packageManager: ctx.packageManager });
|
||||
expect(spawn).toHaveBeenCalledWith('npx', ['expo', 'doctor'], expect.any(Object));
|
||||
void runExpoCliCommand({
|
||||
args: ['doctor'],
|
||||
options: { logger },
|
||||
packageManager: ctx.packageManager,
|
||||
});
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'yarn',
|
||||
['expo', 'doctor'],
|
||||
expect.objectContaining({ logger, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
);
|
||||
});
|
||||
|
||||
it('spawns expo via "pnpm" when package manager is pnpm', () => {
|
||||
@@ -66,8 +84,16 @@ describe(runExpoCliCommand, () => {
|
||||
when(mockCtx.appConfig).thenReturn(Promise.resolve(expoConfig));
|
||||
const ctx = instance(mockCtx);
|
||||
|
||||
void runExpoCliCommand({ args: ['doctor'], options: {}, packageManager: ctx.packageManager });
|
||||
expect(spawn).toHaveBeenCalledWith('pnpm', ['expo', 'doctor'], expect.any(Object));
|
||||
void runExpoCliCommand({
|
||||
args: ['doctor'],
|
||||
options: { logger },
|
||||
packageManager: ctx.packageManager,
|
||||
});
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'pnpm',
|
||||
['expo', 'doctor'],
|
||||
expect.objectContaining({ logger, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
);
|
||||
});
|
||||
|
||||
it('spawns expo via "bun" when package manager is bun', () => {
|
||||
@@ -80,8 +106,30 @@ describe(runExpoCliCommand, () => {
|
||||
when(mockCtx.appConfig).thenReturn(Promise.resolve(expoConfig));
|
||||
const ctx = instance(mockCtx);
|
||||
|
||||
void runExpoCliCommand({ args: ['doctor'], options: {}, packageManager: ctx.packageManager });
|
||||
expect(spawn).toHaveBeenCalledWith('bun', ['expo', 'doctor'], expect.any(Object));
|
||||
void runExpoCliCommand({
|
||||
args: ['doctor'],
|
||||
options: { logger },
|
||||
packageManager: ctx.packageManager,
|
||||
});
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'bun',
|
||||
['expo', 'doctor'],
|
||||
expect.objectContaining({ logger, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
);
|
||||
});
|
||||
|
||||
it('always pipes stdout and stderr while ignoring stdin', () => {
|
||||
void runExpoCliCommand({
|
||||
args: ['doctor'],
|
||||
options: { logger },
|
||||
packageManager: PackageManager.NPM,
|
||||
});
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'npx',
|
||||
['expo', 'doctor'],
|
||||
expect.objectContaining({ logger, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import spawn, { SpawnOptions, SpawnPromise, SpawnResult } from '@expo/turtle-spawn';
|
||||
import { bunyan } from '@expo/logger';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
@@ -50,17 +51,21 @@ export function runExpoCliCommand({
|
||||
}: {
|
||||
packageManager: PackageManager;
|
||||
args: string[];
|
||||
options: SpawnOptions;
|
||||
options: Omit<SpawnOptions, 'stdio' | 'logger'> & { logger: bunyan };
|
||||
}): SpawnPromise<SpawnResult> {
|
||||
const normalizedOptions: SpawnOptions = {
|
||||
...options,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
};
|
||||
const argsWithExpo = ['expo', ...args];
|
||||
if (packageManager === PackageManager.NPM) {
|
||||
return spawn('npx', argsWithExpo, options);
|
||||
return spawn('npx', argsWithExpo, normalizedOptions);
|
||||
} else if (packageManager === PackageManager.YARN) {
|
||||
return spawn('yarn', argsWithExpo, options);
|
||||
return spawn('yarn', argsWithExpo, normalizedOptions);
|
||||
} else if (packageManager === PackageManager.PNPM) {
|
||||
return spawn('pnpm', argsWithExpo, options);
|
||||
return spawn('pnpm', argsWithExpo, normalizedOptions);
|
||||
} else if (packageManager === PackageManager.BUN) {
|
||||
return spawn('bun', argsWithExpo, options);
|
||||
return spawn('bun', argsWithExpo, normalizedOptions);
|
||||
} else {
|
||||
throw new Error(`Unsupported package manager: ${packageManager}`);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ async function getExpoConfigInternalAsync(
|
||||
projectDir,
|
||||
['config', '--json', ...(opts.isPublicConfig ? ['--type', 'public'] : [])],
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: {
|
||||
EXPO_NO_DOTENV: '1',
|
||||
},
|
||||
|
||||
@@ -93,7 +93,10 @@ it('uses the bundled config fallback when the local Expo CLI fails', async () =>
|
||||
expect(spawnExpoCommand).toHaveBeenCalledWith(
|
||||
projectDir,
|
||||
['config', '--json', '--type', 'introspect'],
|
||||
{ env: expect.objectContaining({ EXPO_NO_DOTENV: '1' }) }
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: expect.objectContaining({ EXPO_NO_DOTENV: '1' }),
|
||||
}
|
||||
);
|
||||
expect(getPrebuildConfigAsync).toHaveBeenCalledWith(projectDir, { platforms: ['ios'] });
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ export async function getManagedApplicationTargetEntitlementsAsync(
|
||||
projectDir,
|
||||
['config', '--json', '--type', 'introspect'],
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...env,
|
||||
EXPO_NO_DOTENV: '1',
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { pipeSpawnOutput } from '@expo/logger';
|
||||
|
||||
import spawn from '../index';
|
||||
|
||||
jest.mock('@expo/spawn-async', () => jest.fn(() => ({ child: {} })));
|
||||
jest.mock('@expo/logger', () => ({
|
||||
pipeSpawnOutput: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('spawn', () => {
|
||||
it('defaults stdio to pipe when a logger is provided', () => {
|
||||
const logger = {} as any;
|
||||
|
||||
void spawn('echo', ['hello'], { logger });
|
||||
|
||||
expect(spawnAsync).toHaveBeenCalledWith('echo', ['hello'], { stdio: 'pipe' });
|
||||
expect(pipeSpawnOutput).toHaveBeenCalledWith(logger, {}, { stdio: 'pipe' });
|
||||
});
|
||||
|
||||
it('preserves explicit stdio when a logger is provided', () => {
|
||||
const logger = {} as any;
|
||||
const stdio: ['ignore', 'pipe', 'pipe'] = ['ignore', 'pipe', 'pipe'];
|
||||
|
||||
void spawn('echo', ['hello'], { logger, stdio });
|
||||
|
||||
expect(spawnAsync).toHaveBeenCalledWith('echo', ['hello'], { stdio });
|
||||
expect(pipeSpawnOutput).toHaveBeenCalledWith(logger, {}, { stdio });
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ function spawn(
|
||||
}
|
||||
): SpawnPromise<SpawnResult> {
|
||||
const { logger, ...options } = _options;
|
||||
if (logger) {
|
||||
if (logger && options.stdio === undefined) {
|
||||
options.stdio = 'pipe';
|
||||
}
|
||||
const promise = spawnAsync(command, args, options);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"types": ["node", "jest"],
|
||||
"composite": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
|
||||
Reference in new issue
Block a user