mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c8cc1b22b | ||
|
|
2d85d997b0 | ||
|
|
5463339b27 | ||
|
|
fc11a7398e | ||
|
|
0b5a981e89 | ||
|
|
ee1f333cd2 | ||
|
|
c79556ccfa | ||
|
|
d4984a7171 | ||
|
|
5c43819594 |
No files matched your search
@@ -57,7 +57,7 @@ This means that it will most likely produce an AAB and you will not be able to i
|
|||||||
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||||
|
|
||||||
if (ctx.workflow === Workflow.MANAGED) {
|
if (ctx.workflow === Workflow.MANAGED) {
|
||||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
|
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
|
||||||
@@ -91,7 +91,7 @@ export async function prepareAndroidBuildAsync(
|
|||||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||||
? false
|
? false
|
||||||
: ctx.buildProfile.autoIncrement,
|
: ctx.buildProfile.autoIncrement,
|
||||||
nonInteractive: ctx.nonInteractive,
|
projectId: ctx.projectId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
prepareJobAsync: async (
|
prepareJobAsync: async (
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import slash from 'slash';
|
|||||||
|
|
||||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||||
import { getUsername } from '../../project/projectUtils';
|
import { getUsername } from '../../project/projectUtils';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
import { getVcsClient } from '../../vcs';
|
import { getVcsClient } from '../../vcs';
|
||||||
import { BuildContext } from '../context';
|
import { BuildContext } from '../context';
|
||||||
|
|
||||||
@@ -24,10 +23,7 @@ export async function prepareJobAsync(
|
|||||||
ctx: BuildContext<Platform.ANDROID>,
|
ctx: BuildContext<Platform.ANDROID>,
|
||||||
jobData: JobData
|
jobData: JobData
|
||||||
): Promise<Job> {
|
): Promise<Job> {
|
||||||
const username = getUsername(
|
const username = getUsername(ctx.exp, ctx.user);
|
||||||
ctx.exp,
|
|
||||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
|
||||||
);
|
|
||||||
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
|
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
|
||||||
const projectRootDirectory =
|
const projectRootDirectory =
|
||||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ export async function syncProjectConfigurationAsync({
|
|||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
localAutoIncrement,
|
localAutoIncrement,
|
||||||
nonInteractive,
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
localAutoIncrement?: AndroidVersionAutoIncrement;
|
localAutoIncrement?: AndroidVersionAutoIncrement;
|
||||||
nonInteractive: boolean;
|
projectId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||||
@@ -29,7 +29,7 @@ export async function syncProjectConfigurationAsync({
|
|||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
|
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
|
||||||
if (isExpoUpdatesInstalled(projectDir)) {
|
if (isExpoUpdatesInstalled(projectDir)) {
|
||||||
await syncUpdatesConfigurationAsync(projectDir, exp, { nonInteractive });
|
await syncUpdatesConfigurationAsync(projectDir, exp, projectId);
|
||||||
}
|
}
|
||||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { Analytics, BuildEvent } from '../analytics/events';
|
|||||||
import { CredentialsContext } from '../credentials/context';
|
import { CredentialsContext } from '../credentials/context';
|
||||||
import { BuildResourceClass } from '../graphql/generated';
|
import { BuildResourceClass } from '../graphql/generated';
|
||||||
import { getExpoConfig } from '../project/expoConfig';
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||||
import { resolveWorkflowAsync } from '../project/workflow';
|
import { resolveWorkflowAsync } from '../project/workflow';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
import { Actor } from '../user/User';
|
||||||
import { createAndroidContextAsync } from './android/build';
|
import { createAndroidContextAsync } from './android/build';
|
||||||
import { BuildContext, CommonContext } from './context';
|
import { BuildContext, CommonContext } from './context';
|
||||||
import { createIosContextAsync } from './ios/build';
|
import { createIosContextAsync } from './ios/build';
|
||||||
@@ -30,6 +30,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
projectDir,
|
projectDir,
|
||||||
resourceClass,
|
resourceClass,
|
||||||
message,
|
message,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
buildProfileName: string;
|
buildProfileName: string;
|
||||||
buildProfile: BuildProfile<T>;
|
buildProfile: BuildProfile<T>;
|
||||||
@@ -42,25 +44,22 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
projectDir: string;
|
projectDir: string;
|
||||||
resourceClass: BuildResourceClass;
|
resourceClass: BuildResourceClass;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
actor: Actor;
|
||||||
|
projectId: string;
|
||||||
}): Promise<BuildContext<T>> {
|
}): Promise<BuildContext<T>> {
|
||||||
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
|
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
|
||||||
|
|
||||||
const user = await ensureLoggedInAsync({ nonInteractive });
|
|
||||||
const projectName = exp.slug;
|
const projectName = exp.slug;
|
||||||
const projectId = await getProjectIdAsync(exp, {
|
|
||||||
env: buildProfile.env,
|
|
||||||
nonInteractive,
|
|
||||||
});
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, platform);
|
const workflow = await resolveWorkflowAsync(projectDir, platform);
|
||||||
const accountId = account.id;
|
const accountId = account.id;
|
||||||
const runFromCI = getenv.boolish('CI', false);
|
const runFromCI = getenv.boolish('CI', false);
|
||||||
|
|
||||||
const credentialsCtx = new CredentialsContext({
|
const credentialsCtx = new CredentialsContext({
|
||||||
exp,
|
projectInfo: { exp, projectId },
|
||||||
nonInteractive,
|
nonInteractive,
|
||||||
projectDir,
|
projectDir,
|
||||||
user,
|
user: actor,
|
||||||
env: buildProfile.env,
|
env: buildProfile.env,
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
});
|
});
|
||||||
@@ -99,7 +98,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
projectId,
|
projectId,
|
||||||
projectName,
|
projectName,
|
||||||
trackingCtx,
|
trackingCtx,
|
||||||
user,
|
user: actor,
|
||||||
workflow,
|
workflow,
|
||||||
message,
|
message,
|
||||||
runFromCI,
|
runFromCI,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function createIosContextAsync(
|
|||||||
const { buildProfile } = ctx;
|
const { buildProfile } = ctx;
|
||||||
|
|
||||||
if (ctx.workflow === Workflow.MANAGED) {
|
if (ctx.workflow === Workflow.MANAGED) {
|
||||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNodeEnvVariable(ctx);
|
checkNodeEnvVariable(ctx);
|
||||||
@@ -80,7 +80,7 @@ export async function prepareIosBuildAsync(
|
|||||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||||
? false
|
? false
|
||||||
: ctx.buildProfile.autoIncrement,
|
: ctx.buildProfile.autoIncrement,
|
||||||
nonInteractive: ctx.nonInteractive,
|
projectId: ctx.projectId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
prepareJobAsync: async (
|
prepareJobAsync: async (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import slash from 'slash';
|
|||||||
|
|
||||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||||
import { getUsername } from '../../project/projectUtils';
|
import { getUsername } from '../../project/projectUtils';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
import { getVcsClient } from '../../vcs';
|
import { getVcsClient } from '../../vcs';
|
||||||
import { BuildContext } from '../context';
|
import { BuildContext } from '../context';
|
||||||
|
|
||||||
@@ -26,10 +25,7 @@ export async function prepareJobAsync(
|
|||||||
): Promise<Job> {
|
): Promise<Job> {
|
||||||
const projectRootDirectory =
|
const projectRootDirectory =
|
||||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||||
const username = getUsername(
|
const username = getUsername(ctx.exp, ctx.user);
|
||||||
ctx.exp,
|
|
||||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
|
||||||
);
|
|
||||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||||
if (jobData.credentials) {
|
if (jobData.credentials) {
|
||||||
const targetNames = Object.keys(jobData.credentials);
|
const targetNames = Object.keys(jobData.credentials);
|
||||||
|
|||||||
@@ -13,20 +13,20 @@ export async function syncProjectConfigurationAsync({
|
|||||||
exp,
|
exp,
|
||||||
targets,
|
targets,
|
||||||
localAutoIncrement,
|
localAutoIncrement,
|
||||||
nonInteractive,
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
targets: Target[];
|
targets: Target[];
|
||||||
localAutoIncrement?: IosVersionAutoIncrement;
|
localAutoIncrement?: IosVersionAutoIncrement;
|
||||||
nonInteractive: boolean;
|
projectId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||||
|
|
||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
if (isExpoUpdatesInstalled(projectDir)) {
|
if (isExpoUpdatesInstalled(projectDir)) {
|
||||||
await syncUpdatesConfigurationAsync(projectDir, exp, { nonInteractive });
|
await syncUpdatesConfigurationAsync(projectDir, exp, projectId);
|
||||||
}
|
}
|
||||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||||
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
||||||
} from '../update/ios/UpdatesModule';
|
} from '../update/ios/UpdatesModule';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
|
||||||
import { easCliVersion } from '../utils/easCli';
|
import { easCliVersion } from '../utils/easCli';
|
||||||
import { getVcsClient } from '../vcs';
|
import { getVcsClient } from '../vcs';
|
||||||
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
|
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
|
||||||
@@ -49,10 +48,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
|||||||
(await vcsClient.getLastCommitMessageAsync()) ?? undefined
|
(await vcsClient.getLastCommitMessageAsync()) ?? undefined
|
||||||
),
|
),
|
||||||
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
|
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
|
||||||
username: getUsername(
|
username: getUsername(ctx.exp, ctx.user),
|
||||||
ctx.exp,
|
|
||||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
|
||||||
),
|
|
||||||
message: ctx.message,
|
message: ctx.message,
|
||||||
...(ctx.platform === Platform.IOS && {
|
...(ctx.platform === Platform.IOS && {
|
||||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||||
} from '../submit/submit';
|
} from '../submit/submit';
|
||||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||||
|
import { Actor } from '../user/User';
|
||||||
import { printJsonOnlyOutput } from '../utils/json';
|
import { printJsonOnlyOutput } from '../utils/json';
|
||||||
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
||||||
import { getVcsClient } from '../vcs';
|
import { getVcsClient } from '../vcs';
|
||||||
@@ -87,7 +88,12 @@ const platformToGraphQLResourceClassMapping: Record<
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFlags): Promise<void> {
|
export async function runBuildAndSubmitAsync(
|
||||||
|
projectDir: string,
|
||||||
|
flags: BuildFlags,
|
||||||
|
actor: Actor,
|
||||||
|
projectId: string
|
||||||
|
): Promise<void> {
|
||||||
await getVcsClient().ensureRepoExistsAsync();
|
await getVcsClient().ensureRepoExistsAsync();
|
||||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||||
|
|
||||||
@@ -134,6 +140,8 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
|
|||||||
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
|
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
|
||||||
],
|
],
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
});
|
});
|
||||||
if (maybeBuild) {
|
if (maybeBuild) {
|
||||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||||
@@ -227,6 +235,8 @@ async function prepareAndStartBuildAsync({
|
|||||||
buildProfile,
|
buildProfile,
|
||||||
resourceClass,
|
resourceClass,
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
flags: BuildFlags;
|
flags: BuildFlags;
|
||||||
@@ -234,6 +244,8 @@ async function prepareAndStartBuildAsync({
|
|||||||
buildProfile: ProfileData<'build'>;
|
buildProfile: ProfileData<'build'>;
|
||||||
resourceClass: BuildResourceClass;
|
resourceClass: BuildResourceClass;
|
||||||
easJsonCliConfig: EasJson['cli'];
|
easJsonCliConfig: EasJson['cli'];
|
||||||
|
actor: Actor;
|
||||||
|
projectId: string;
|
||||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||||
const buildCtx = await createBuildContextAsync({
|
const buildCtx = await createBuildContextAsync({
|
||||||
buildProfileName: buildProfile.profileName,
|
buildProfileName: buildProfile.profileName,
|
||||||
@@ -247,6 +259,8 @@ async function prepareAndStartBuildAsync({
|
|||||||
localBuildOptions: flags.localBuildOptions,
|
localBuildOptions: flags.localBuildOptions,
|
||||||
easJsonCliConfig,
|
easJsonCliConfig,
|
||||||
message: flags.message,
|
message: flags.message,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (moreBuilds) {
|
if (moreBuilds) {
|
||||||
@@ -318,6 +332,7 @@ async function prepareAndStartSubmissionAsync({
|
|||||||
env: buildProfile.env,
|
env: buildProfile.env,
|
||||||
credentialsCtx: buildCtx.credentialsCtx,
|
credentialsCtx: buildCtx.credentialsCtx,
|
||||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||||
|
actor: buildCtx.user,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (moreBuilds) {
|
if (moreBuilds) {
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
|
||||||
import * as PackageManagerUtils from '@expo/package-manager';
|
|
||||||
import { Command } from '@oclif/core';
|
import { Command } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import path from 'path';
|
|
||||||
import semver from 'semver';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalyticsEvent,
|
AnalyticsEvent,
|
||||||
@@ -12,21 +6,61 @@ import {
|
|||||||
initAsync as initAnalyticsAsync,
|
initAsync as initAnalyticsAsync,
|
||||||
logEvent,
|
logEvent,
|
||||||
} from '../analytics/rudderstackClient';
|
} from '../analytics/rudderstackClient';
|
||||||
import { learnMore } from '../log';
|
|
||||||
import { findProjectRootAsync } from '../project/projectUtils';
|
|
||||||
import { getUserAsync } from '../user/User';
|
import { getUserAsync } from '../user/User';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
import ActorContextField from './context/ActorContextField';
|
||||||
import { easCliVersion } from '../utils/easCli';
|
import ContextField from './context/ContextField';
|
||||||
import { setVcsClient } from '../vcs';
|
import ProjectDirContextField from './context/ProjectDirContextField';
|
||||||
import GitClient from '../vcs/clients/git';
|
import ProjectIdContextField from './context/ProjectIdContextField';
|
||||||
|
|
||||||
|
export const EASCommandProjectDirContext = {
|
||||||
|
projectDir: new ProjectDirContextField(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EASCommandProjectIdContext = {
|
||||||
|
projectId: new ProjectIdContextField(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EASCommandLoggedInContext = {
|
||||||
|
actor: new ActorContextField(),
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContextInput<
|
||||||
|
T extends {
|
||||||
|
[name: string]: any;
|
||||||
|
} = object
|
||||||
|
> = {
|
||||||
|
[P in keyof T]: ContextField<T[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContextOutput<
|
||||||
|
T extends {
|
||||||
|
[name: string]: any;
|
||||||
|
} = object
|
||||||
|
> = {
|
||||||
|
[P in keyof T]: T[P];
|
||||||
|
};
|
||||||
|
|
||||||
export default abstract class EasCommand extends Command {
|
export default abstract class EasCommand extends Command {
|
||||||
/**
|
static contextDefinition: ContextInput = {};
|
||||||
* When user data is unavailable locally, determines if the command will
|
|
||||||
* force the user to log in
|
protected async getContextAsync<
|
||||||
*/
|
C extends {
|
||||||
protected requiresAuthentication = true;
|
[name: string]: any;
|
||||||
protected mustBeRunInsideProject = true;
|
} = object
|
||||||
|
>(
|
||||||
|
commandClass: { contextDefinition: ContextInput<C> },
|
||||||
|
{ nonInteractive }: { nonInteractive: boolean }
|
||||||
|
): Promise<ContextOutput<C>> {
|
||||||
|
const contextDefinition = commandClass.contextDefinition;
|
||||||
|
|
||||||
|
const contextValuePairs = await Promise.all(
|
||||||
|
Object.keys(contextDefinition).map(async contextKey => {
|
||||||
|
return [contextKey, await contextDefinition[contextKey].getValueAsync({ nonInteractive })];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.fromEntries(contextValuePairs);
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract runAsync(): Promise<any>;
|
protected abstract runAsync(): Promise<any>;
|
||||||
|
|
||||||
@@ -34,24 +68,14 @@ export default abstract class EasCommand extends Command {
|
|||||||
async run(): Promise<any> {
|
async run(): Promise<any> {
|
||||||
await initAnalyticsAsync();
|
await initAnalyticsAsync();
|
||||||
|
|
||||||
if (this.mustBeRunInsideProject) {
|
// this is needed for logEvent call below as it identifies the user in the analytics system
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
await this.applyCliConfigAsync(projectDir);
|
|
||||||
await this.ensureEasCliIsNotInDependenciesAsync(projectDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.requiresAuthentication) {
|
|
||||||
const { flags } = await this.parse();
|
|
||||||
const nonInteractive = (flags as any)['non-interactive'] ?? false;
|
|
||||||
await ensureLoggedInAsync({ nonInteractive });
|
|
||||||
} else {
|
|
||||||
await getUserAsync();
|
await getUserAsync();
|
||||||
}
|
|
||||||
logEvent(AnalyticsEvent.ACTION, {
|
logEvent(AnalyticsEvent.ACTION, {
|
||||||
// id is assigned by oclif in constructor based on the filepath:
|
// id is assigned by oclif in constructor based on the filepath:
|
||||||
// commands/submit === submit, commands/build/list === build:list
|
// commands/submit === submit, commands/build/list === build:list
|
||||||
action: `eas ${this.id}`,
|
action: `eas ${this.id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.runAsync();
|
return this.runAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,63 +84,4 @@ export default abstract class EasCommand extends Command {
|
|||||||
await flushAnalyticsAsync();
|
await flushAnalyticsAsync();
|
||||||
return super.finally(err);
|
return super.finally(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async applyCliConfigAsync(projectDir: string): Promise<void> {
|
|
||||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
|
||||||
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
|
||||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
|
||||||
throw new Error(
|
|
||||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (config?.requireCommit) {
|
|
||||||
setVcsClient(new GitClient());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise<void> {
|
|
||||||
let printCliVersionWarning = false;
|
|
||||||
|
|
||||||
const consoleWarn = (msg?: string): void => {
|
|
||||||
if (msg) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn(chalk.yellow(msg));
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (await this.isEasCliInDependenciesAsync(projectDir)) {
|
|
||||||
printCliVersionWarning = true;
|
|
||||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
|
||||||
if (maybeRepoRoot !== projectDir && (await this.isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
|
||||||
printCliVersionWarning = true;
|
|
||||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (printCliVersionWarning) {
|
|
||||||
consoleWarn(
|
|
||||||
`It's recommended to use the ${chalk.bold(
|
|
||||||
'"cli.version"'
|
|
||||||
)} field in eas.json to enforce the ${chalk.bold('eas-cli')} version for your project.`
|
|
||||||
);
|
|
||||||
consoleWarn(
|
|
||||||
learnMore('https://github.com/expo/eas-cli#enforcing-eas-cli-version-for-your-project')
|
|
||||||
);
|
|
||||||
consoleWarn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async isEasCliInDependenciesAsync(dir: string): Promise<boolean> {
|
|
||||||
const packageJsonPath = path.join(dir, 'package.json');
|
|
||||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
||||||
return (
|
|
||||||
packageJson?.dependencies?.['eas-cli'] !== undefined ||
|
|
||||||
packageJson?.devDependencies?.['eas-cli'] !== undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
||||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||||
import { getUserAsync } from '../../user/User';
|
import { getUserAsync } from '../../user/User';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
import EasCommand from '../EasCommand';
|
import EasCommand from '../EasCommand';
|
||||||
|
|
||||||
jest.mock('../../user/User');
|
jest.mock('../../user/User');
|
||||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn() }));
|
|
||||||
jest.mock('../../analytics/rudderstackClient', () => {
|
jest.mock('../../analytics/rudderstackClient', () => {
|
||||||
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
||||||
return {
|
return {
|
||||||
@@ -32,19 +30,13 @@ beforeEach(() => {
|
|||||||
.mocked(getUserAsync)
|
.mocked(getUserAsync)
|
||||||
.mockReset()
|
.mockReset()
|
||||||
.mockImplementation(async () => mockJester);
|
.mockImplementation(async () => mockJester);
|
||||||
jest
|
|
||||||
.mocked(ensureLoggedInAsync)
|
|
||||||
.mockReset()
|
|
||||||
.mockImplementation(async () => mockJester);
|
|
||||||
jest.mocked(initAsync).mockReset();
|
jest.mocked(initAsync).mockReset();
|
||||||
jest.mocked(flushAsync).mockReset();
|
jest.mocked(flushAsync).mockReset();
|
||||||
jest.mocked(logEvent).mockReset();
|
jest.mocked(logEvent).mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
const createTestEasCommand = (authValue: boolean): typeof EasCommand => {
|
const createTestEasCommand = (): typeof EasCommand => {
|
||||||
class TestEasCommand extends EasCommand {
|
class TestEasCommand extends EasCommand {
|
||||||
override requiresAuthentication = authValue;
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {}
|
async runAsync(): Promise<void> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,37 +53,29 @@ describe(EasCommand.name, () => {
|
|||||||
//
|
//
|
||||||
// See https://github.com/oclif/command/blob/master/src/command.ts#L80
|
// See https://github.com/oclif/command/blob/master/src/command.ts#L80
|
||||||
// and look for "Config.load"
|
// and look for "Config.load"
|
||||||
it('ensures the user is logged in', async () => {
|
it('ensures the user data is read', async () => {
|
||||||
const TestEasCommand = createTestEasCommand(true);
|
const TestEasCommand = createTestEasCommand();
|
||||||
await TestEasCommand.run();
|
|
||||||
|
|
||||||
expect(ensureLoggedInAsync).toHaveBeenCalled();
|
|
||||||
}, 15_000);
|
|
||||||
|
|
||||||
it('ensures the user data is read from cache', async () => {
|
|
||||||
const TestEasCommand = createTestEasCommand(false);
|
|
||||||
await TestEasCommand.run();
|
await TestEasCommand.run();
|
||||||
|
|
||||||
expect(jest.mocked(getUserAsync)).toBeCalledTimes(1);
|
expect(jest.mocked(getUserAsync)).toBeCalledTimes(1);
|
||||||
expect(jest.mocked(ensureLoggedInAsync)).not.toBeCalled();
|
}, 15_000);
|
||||||
});
|
|
||||||
|
|
||||||
it('initializes analytics', async () => {
|
it('initializes analytics', async () => {
|
||||||
const TestEasCommand = createTestEasCommand(true);
|
const TestEasCommand = createTestEasCommand();
|
||||||
await TestEasCommand.run();
|
await TestEasCommand.run();
|
||||||
|
|
||||||
expect(initAsync).toHaveBeenCalled();
|
expect(initAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('flushes analytics', async () => {
|
it('flushes analytics', async () => {
|
||||||
const TestEasCommand = createTestEasCommand(true);
|
const TestEasCommand = createTestEasCommand();
|
||||||
await TestEasCommand.run();
|
await TestEasCommand.run();
|
||||||
|
|
||||||
expect(flushAsync).toHaveBeenCalled();
|
expect(flushAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs events', async () => {
|
it('logs events', async () => {
|
||||||
const TestEasCommand = createTestEasCommand(true);
|
const TestEasCommand = createTestEasCommand();
|
||||||
await TestEasCommand.run();
|
await TestEasCommand.run();
|
||||||
|
|
||||||
expect(logEvent).toHaveBeenCalledWith('action', {
|
expect(logEvent).toHaveBeenCalledWith('action', {
|
||||||
@@ -102,7 +86,7 @@ describe(EasCommand.name, () => {
|
|||||||
|
|
||||||
describe('after exceptions', () => {
|
describe('after exceptions', () => {
|
||||||
it('flushes analytics', async () => {
|
it('flushes analytics', async () => {
|
||||||
const TestEasCommand = createTestEasCommand(true);
|
const TestEasCommand = createTestEasCommand();
|
||||||
try {
|
try {
|
||||||
await TestEasCommand.run().then(() => {
|
await TestEasCommand.run().then(() => {
|
||||||
throw new Error('foo');
|
throw new Error('foo');
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Errors } from '@oclif/core';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import nullthrows from 'nullthrows';
|
||||||
|
|
||||||
|
import { ApiV2Error } from '../../api';
|
||||||
|
import Log, { learnMore } from '../../log';
|
||||||
|
import { promptAsync } from '../../prompts';
|
||||||
|
import { Actor, getUserAsync, loginAsync } from '../../user/User';
|
||||||
|
import { retryUsernamePasswordAuthWithOTPAsync } from '../../user/otp';
|
||||||
|
import ContextField, { ContextOptions } from './ContextField';
|
||||||
|
|
||||||
|
export default class ActorContextField extends ContextField<Actor> {
|
||||||
|
async getValueAsync({ nonInteractive }: ContextOptions): Promise<Actor> {
|
||||||
|
return await ActorContextField.ensureLoggedInAsync({ nonInteractive });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ensureLoggedInAsync({
|
||||||
|
nonInteractive,
|
||||||
|
}: {
|
||||||
|
nonInteractive: boolean;
|
||||||
|
}): Promise<Actor> {
|
||||||
|
let user: Actor | undefined;
|
||||||
|
try {
|
||||||
|
user = await getUserAsync();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
Log.warn('An Expo user account is required to proceed.');
|
||||||
|
await this.showLoginPromptAsync({ nonInteractive, printNewLine: true });
|
||||||
|
user = await getUserAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullthrows(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async showLoginPromptAsync({
|
||||||
|
nonInteractive = false,
|
||||||
|
printNewLine = false,
|
||||||
|
} = {}): Promise<void> {
|
||||||
|
if (nonInteractive) {
|
||||||
|
Errors.error(
|
||||||
|
`Either log in with ${chalk.bold('eas login')} or set the ${chalk.bold(
|
||||||
|
'EXPO_TOKEN'
|
||||||
|
)} environment variable if you're using EAS CLI on CI (${learnMore(
|
||||||
|
'https://docs.expo.dev/accounts/programmatic-access/',
|
||||||
|
{ dim: false }
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (printNewLine) {
|
||||||
|
Log.newLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.log('Log in to EAS');
|
||||||
|
|
||||||
|
const { username, password } = await promptAsync([
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'username',
|
||||||
|
message: 'Email or username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'password',
|
||||||
|
message: 'Password',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
await loginAsync({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {
|
||||||
|
await retryUsernamePasswordAuthWithOTPAsync(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
e.expoApiV2ErrorMetadata as any
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface ContextOptions {
|
||||||
|
nonInteractive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default abstract class ContextField<T> {
|
||||||
|
abstract getValueAsync(options: ContextOptions): Promise<T>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||||
|
import * as PackageManagerUtils from '@expo/package-manager';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
|
import pkgDir from 'pkg-dir';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
|
import { learnMore } from '../../log';
|
||||||
|
import { easCliVersion } from '../../utils/easCli';
|
||||||
|
import { getVcsClient, setVcsClient } from '../../vcs';
|
||||||
|
import GitClient from '../../vcs/clients/git';
|
||||||
|
import ContextField from './ContextField';
|
||||||
|
|
||||||
|
export default class ProjectDirContextField extends ContextField<string> {
|
||||||
|
async getValueAsync(): Promise<string> {
|
||||||
|
return await ProjectDirContextField.findProjectDirAndVerifyProjectSetupAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async applyCliConfigAsync(projectDir: string): Promise<void> {
|
||||||
|
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||||
|
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
||||||
|
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||||
|
throw new Error(
|
||||||
|
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (config?.requireCommit) {
|
||||||
|
setVcsClient(new GitClient());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise<void> {
|
||||||
|
let printCliVersionWarning = false;
|
||||||
|
|
||||||
|
const consoleWarn = (msg?: string): void => {
|
||||||
|
if (msg) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(chalk.yellow(msg));
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (await this.isEasCliInDependenciesAsync(projectDir)) {
|
||||||
|
printCliVersionWarning = true;
|
||||||
|
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||||
|
if (maybeRepoRoot !== projectDir && (await this.isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||||
|
printCliVersionWarning = true;
|
||||||
|
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printCliVersionWarning) {
|
||||||
|
consoleWarn(
|
||||||
|
`It's recommended to use the ${chalk.bold(
|
||||||
|
'"cli.version"'
|
||||||
|
)} field in eas.json to enforce the ${chalk.bold('eas-cli')} version for your project.`
|
||||||
|
);
|
||||||
|
consoleWarn(
|
||||||
|
learnMore('https://github.com/expo/eas-cli#enforcing-eas-cli-version-for-your-project')
|
||||||
|
);
|
||||||
|
consoleWarn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async isEasCliInDependenciesAsync(dir: string): Promise<boolean> {
|
||||||
|
const packageJsonPath = path.join(dir, 'package.json');
|
||||||
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||||
|
return (
|
||||||
|
packageJson?.dependencies?.['eas-cli'] !== undefined ||
|
||||||
|
packageJson?.devDependencies?.['eas-cli'] !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the project root directory.
|
||||||
|
*/
|
||||||
|
private static async findProjectRootAsync(): Promise<string> {
|
||||||
|
const projectRootDir = await pkgDir();
|
||||||
|
if (!projectRootDir) {
|
||||||
|
throw new Error('Run this command inside a project directory.');
|
||||||
|
} else {
|
||||||
|
let vcsRoot;
|
||||||
|
try {
|
||||||
|
vcsRoot = path.normalize(await getVcsClient().getRootPathAsync());
|
||||||
|
} catch {}
|
||||||
|
if (vcsRoot && vcsRoot.startsWith(projectRootDir) && vcsRoot !== projectRootDir) {
|
||||||
|
throw new Error(
|
||||||
|
`package.json is outside of the current git repository (project root: ${projectRootDir}, git root: ${vcsRoot}).`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return projectRootDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
||||||
|
const projectDir = await this.findProjectRootAsync();
|
||||||
|
await this.applyCliConfigAsync(projectDir);
|
||||||
|
await this.ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||||
|
return projectDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
|
||||||
|
import { ExpoConfig } from '@expo/config-types';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
import Log from '../../log';
|
||||||
|
import { ora } from '../../ora';
|
||||||
|
import { getExpoConfig } from '../../project/expoConfig';
|
||||||
|
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
|
import { toAppPrivacy } from '../../project/projectUtils';
|
||||||
|
import { Actor } from '../../user/User';
|
||||||
|
import ActorContextField from './ActorContextField';
|
||||||
|
import ContextField, { ContextOptions } from './ContextField';
|
||||||
|
import ProjectDirContextField from './ProjectDirContextField';
|
||||||
|
|
||||||
|
export default class ProjectIdContextField extends ContextField<string> {
|
||||||
|
async getValueAsync({ nonInteractive }: ContextOptions): Promise<string> {
|
||||||
|
const projectDir = await ProjectDirContextField['findProjectDirAndVerifyProjectSetupAsync']();
|
||||||
|
const exp = getExpoConfig(projectDir);
|
||||||
|
return await ProjectIdContextField.getProjectIdAsync(exp, { nonInteractive });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an EAS project ID to the appropriate field in the app config.
|
||||||
|
*/
|
||||||
|
private static async saveProjectIdToAppConfigAsync(
|
||||||
|
projectDir: string,
|
||||||
|
projectId: string
|
||||||
|
): Promise<void> {
|
||||||
|
const exp = getExpoConfig(projectDir);
|
||||||
|
const result = await modifyConfigAsync(projectDir, {
|
||||||
|
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (result.type) {
|
||||||
|
case 'success':
|
||||||
|
break;
|
||||||
|
case 'warn': {
|
||||||
|
Log.warn();
|
||||||
|
Log.warn(
|
||||||
|
`Warning: Your project uses dynamic app configuration, and the EAS project ID can't automatically be added to it.`
|
||||||
|
);
|
||||||
|
Log.warn(
|
||||||
|
chalk.dim(
|
||||||
|
'https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Log.warn();
|
||||||
|
Log.warn(
|
||||||
|
`To complete the setup process, set the ${chalk.bold(
|
||||||
|
'extra.eas.projectId'
|
||||||
|
)} in your ${chalk.bold(getProjectConfigDescription(projectDir))}:`
|
||||||
|
);
|
||||||
|
Log.warn();
|
||||||
|
Log.warn(chalk.bold(JSON.stringify({ expo: { extra: { eas: { projectId } } } }, null, 2)));
|
||||||
|
Log.warn();
|
||||||
|
throw new Error(result.message);
|
||||||
|
}
|
||||||
|
case 'fail':
|
||||||
|
throw new Error(result.message);
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected result type from modifyConfigAsync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the EAS project ID from the app config. If the project ID is not set in the config,
|
||||||
|
* use the owner/slug to identify an EAS project on the server (asking for confirmation first),
|
||||||
|
* and attempt to save the EAS project ID to the appropriate field in the app config. If unable to
|
||||||
|
* save to the app config, throw an error.
|
||||||
|
*/
|
||||||
|
private static async getProjectIdAsync(
|
||||||
|
exp: ExpoConfig,
|
||||||
|
options: { nonInteractive: boolean }
|
||||||
|
): Promise<string> {
|
||||||
|
const localProjectId = exp.extra?.eas?.projectId;
|
||||||
|
if (localProjectId) {
|
||||||
|
return localProjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectDir = await ProjectDirContextField['findProjectRootAsync']();
|
||||||
|
if (!projectDir) {
|
||||||
|
throw new Error('This command must be run inside a project directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.warn('EAS project not configured.');
|
||||||
|
|
||||||
|
const getAccountNameForEASProjectSync = (exp: ExpoConfig, user: Actor): string => {
|
||||||
|
if (exp.owner) {
|
||||||
|
return exp.owner;
|
||||||
|
}
|
||||||
|
switch (user.__typename) {
|
||||||
|
case 'User':
|
||||||
|
return user.username;
|
||||||
|
case 'Robot':
|
||||||
|
throw new Error(
|
||||||
|
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actor = await ActorContextField['ensureLoggedInAsync']({
|
||||||
|
nonInteractive: options.nonInteractive,
|
||||||
|
});
|
||||||
|
const projectId = await fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
|
||||||
|
{
|
||||||
|
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||||
|
projectName: exp.slug,
|
||||||
|
privacy: toAppPrivacy(exp.privacy),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nonInteractive: options.nonInteractive,
|
||||||
|
},
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
|
||||||
|
const spinner = ora(`Linking local project to EAS project ${projectId}`).start();
|
||||||
|
try {
|
||||||
|
await this.saveProjectIdToAppConfigAsync(projectDir, projectId);
|
||||||
|
spinner.succeed(`Linked local project to EAS project ${projectId}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
spinner.fail();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-12
@@ -1,16 +1,20 @@
|
|||||||
import { ApiV2Error } from '../../api';
|
import { ApiV2Error } from '../../../api';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../../prompts';
|
||||||
import { loginAsync } from '../User';
|
import { loginAsync } from '../../../user/User';
|
||||||
import { showLoginPromptAsync } from '../actions';
|
import {
|
||||||
import { UserSecondFactorDeviceMethod, retryUsernamePasswordAuthWithOTPAsync } from '../otp';
|
UserSecondFactorDeviceMethod,
|
||||||
|
retryUsernamePasswordAuthWithOTPAsync,
|
||||||
|
} from '../../../user/otp';
|
||||||
|
import ActorContextField from '../ActorContextField';
|
||||||
|
|
||||||
jest.mock('../../prompts');
|
jest.mock('../../../prompts');
|
||||||
jest.mock('../../api', () => ({
|
jest.mock('../../../api', () => ({
|
||||||
ApiV2Error: jest.requireActual('../../api').ApiV2Error,
|
ApiV2Error: jest.requireActual('../../../api').ApiV2Error,
|
||||||
|
getExpoApiBaseUrl: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../otp');
|
jest.mock('../../../user/otp');
|
||||||
jest.mock('../User', () => ({
|
jest.mock('../../../user/User', () => ({
|
||||||
loginAsync: jest.fn(),
|
loginAsync: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ beforeEach(() => {
|
|||||||
jest.mocked(loginAsync).mockReset();
|
jest.mocked(loginAsync).mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(showLoginPromptAsync, () => {
|
describe(ActorContextField['showLoginPromptAsync'], () => {
|
||||||
it('prompts for OTP when 2FA is enabled', async () => {
|
it('prompts for OTP when 2FA is enabled', async () => {
|
||||||
jest
|
jest
|
||||||
.mocked(promptAsync)
|
.mocked(promptAsync)
|
||||||
@@ -53,7 +57,7 @@ describe(showLoginPromptAsync, () => {
|
|||||||
})
|
})
|
||||||
.mockImplementation(async () => {});
|
.mockImplementation(async () => {});
|
||||||
|
|
||||||
await showLoginPromptAsync();
|
await ActorContextField['showLoginPromptAsync']();
|
||||||
|
|
||||||
expect(retryUsernamePasswordAuthWithOTPAsync).toHaveBeenCalledWith('hello', 'world', {
|
expect(retryUsernamePasswordAuthWithOTPAsync).toHaveBeenCalledWith('hello', 'world', {
|
||||||
secondFactorDevices: [
|
secondFactorDevices: [
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { vol } from 'memfs';
|
||||||
|
import pkgDir from 'pkg-dir';
|
||||||
|
|
||||||
|
import ProjectDirContextField from '../ProjectDirContextField';
|
||||||
|
|
||||||
|
jest.mock('@expo/config');
|
||||||
|
jest.mock('fs');
|
||||||
|
jest.mock('pkg-dir');
|
||||||
|
|
||||||
|
jest.mock('../../../prompts');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(ProjectDirContextField['findProjectRootAsync'], () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vol.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if not inside the project directory', async () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./README.md': '1',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
await expect(ProjectDirContextField['findProjectRootAsync']()).rejects.toThrow(
|
||||||
|
'Run this command inside a project directory.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the root directory of the project', async () => {
|
||||||
|
jest.mocked(pkgDir).mockResolvedValue('/app');
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./README.md': '1',
|
||||||
|
'./package.json': '2',
|
||||||
|
'./src/index.ts': '3',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
const projectRoot = await ProjectDirContextField['findProjectRootAsync']();
|
||||||
|
expect(projectRoot).toBe('/app');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { getConfig, modifyConfigAsync } from '@expo/config';
|
||||||
|
import { vol } from 'memfs';
|
||||||
|
|
||||||
|
import { Role } from '../../../graphql/generated';
|
||||||
|
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
|
import ActorContextField from '../ActorContextField';
|
||||||
|
import ProjectDirContextField from '../ProjectDirContextField';
|
||||||
|
import ProjectIdContextField from '../ProjectIdContextField';
|
||||||
|
|
||||||
|
jest.mock('@expo/config');
|
||||||
|
jest.mock('fs');
|
||||||
|
|
||||||
|
jest.mock('../ActorContextField');
|
||||||
|
jest.mock('../ProjectDirContextField');
|
||||||
|
jest.mock('../../../user/User');
|
||||||
|
jest.mock('../../../ora', () => ({
|
||||||
|
ora: () => ({
|
||||||
|
start: () => ({ succeed: () => {}, fail: () => {} }),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
jest.mock('../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||||
|
|
||||||
|
describe(ProjectIdContextField.name, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.mocked(ActorContextField['ensureLoggedInAsync']).mockResolvedValue({
|
||||||
|
__typename: 'User',
|
||||||
|
id: 'user_id',
|
||||||
|
username: 'notnotbrent',
|
||||||
|
primaryAccount: {
|
||||||
|
id: 'account_id_1',
|
||||||
|
name: 'notnotbrent',
|
||||||
|
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
|
||||||
|
},
|
||||||
|
accounts: [
|
||||||
|
{
|
||||||
|
id: 'account_id_1',
|
||||||
|
name: 'notnotbrent',
|
||||||
|
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'account_id_2',
|
||||||
|
name: 'dominik',
|
||||||
|
users: [{ role: Role.ViewOnly, actor: { id: 'user_id' } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isExpoAdmin: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./README.md': '1',
|
||||||
|
'./package.json': '2',
|
||||||
|
'./src/index.ts': '3',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets the project ID from app config if exists', async () => {
|
||||||
|
jest.mocked(getConfig).mockReturnValue({
|
||||||
|
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||||
|
} as any);
|
||||||
|
jest.mocked(ProjectDirContextField['findProjectRootAsync']).mockResolvedValue('/app');
|
||||||
|
await expect(
|
||||||
|
new ProjectIdContextField().getValueAsync({ nonInteractive: false })
|
||||||
|
).resolves.toEqual('1234');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
||||||
|
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||||
|
jest.mocked(ProjectDirContextField['findProjectRootAsync']).mockResolvedValue('/app');
|
||||||
|
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||||
|
type: 'success',
|
||||||
|
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
||||||
|
});
|
||||||
|
jest
|
||||||
|
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||||
|
.mockImplementation(async () => '2345');
|
||||||
|
|
||||||
|
const projectId = await new ProjectIdContextField().getValueAsync({ nonInteractive: false });
|
||||||
|
expect(projectId).toBe('2345');
|
||||||
|
|
||||||
|
expect(modifyConfigAsync).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', {
|
||||||
|
extra: { eas: { projectId: '2345' } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if writing the ID back to the config fails', async () => {
|
||||||
|
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||||
|
jest.mocked(ProjectDirContextField['findProjectRootAsync']).mockResolvedValue('/app');
|
||||||
|
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||||
|
type: 'fail',
|
||||||
|
config: null,
|
||||||
|
});
|
||||||
|
jest
|
||||||
|
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||||
|
.mockImplementation(async () => '4567');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
new ProjectIdContextField().getValueAsync({ nonInteractive: false })
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand from '../../commandUtils/EasCommand';
|
||||||
|
import ActorContextField from '../../commandUtils/context/ActorContextField';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { showLoginPromptAsync } from '../../user/actions';
|
|
||||||
|
|
||||||
export default class AccountLogin extends EasCommand {
|
export default class AccountLogin extends EasCommand {
|
||||||
static override description = 'log in with your Expo account';
|
static override description = 'log in with your Expo account';
|
||||||
static override aliases = ['login'];
|
static override aliases = ['login'];
|
||||||
|
|
||||||
protected override mustBeRunInsideProject = false;
|
|
||||||
protected override requiresAuthentication = false;
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
await showLoginPromptAsync();
|
await ActorContextField['showLoginPromptAsync']();
|
||||||
Log.log('Logged in');
|
Log.log('Logged in');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,6 @@ export default class AccountLogout extends EasCommand {
|
|||||||
static override description = 'log out';
|
static override description = 'log out';
|
||||||
static override aliases = ['logout'];
|
static override aliases = ['logout'];
|
||||||
|
|
||||||
protected override mustBeRunInsideProject = false;
|
|
||||||
protected override requiresAuthentication = false;
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
await logoutAsync();
|
await logoutAsync();
|
||||||
Log.log('Logged out');
|
Log.log('Logged out');
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ export default class AccountView extends EasCommand {
|
|||||||
static override description = 'show the username you are logged in as';
|
static override description = 'show the username you are logged in as';
|
||||||
static override aliases = ['whoami'];
|
static override aliases = ['whoami'];
|
||||||
|
|
||||||
protected override mustBeRunInsideProject = false;
|
|
||||||
protected override requiresAuthentication = false;
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const user = await getUserAsync();
|
const user = await getUserAsync();
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ export default class AnalyticsView extends EasCommand {
|
|||||||
|
|
||||||
static override args = [{ name: 'STATUS', options: ['on', 'off'] }];
|
static override args = [{ name: 'STATUS', options: ['on', 'off'] }];
|
||||||
|
|
||||||
protected override requiresAuthentication = false;
|
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||||
if (status) {
|
if (status) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import { getDefaultBranchNameAsync } from '../../branch/utils';
|
import { getDefaultBranchNameAsync } from '../../branch/utils';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -11,12 +11,7 @@ import {
|
|||||||
UpdateBranch,
|
UpdateBranch,
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
@@ -66,18 +61,23 @@ export default class BranchCreate extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
args: { name },
|
args: { name },
|
||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(BranchCreate);
|
} = await this.parse(BranchCreate);
|
||||||
|
const { projectId } = await this.getContextAsync(BranchCreate, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
@@ -14,12 +14,7 @@ import {
|
|||||||
GetBranchInfoQueryVariables,
|
GetBranchInfoQueryVariables,
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { toggleConfirmAsync } from '../../prompts';
|
import { toggleConfirmAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
@@ -81,6 +76,10 @@ async function deleteBranchOnAppAsync({
|
|||||||
export default class BranchDelete extends EasCommand {
|
export default class BranchDelete extends EasCommand {
|
||||||
static override description = 'delete a branch';
|
static override description = 'delete a branch';
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
static override args = [
|
static override args = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -98,15 +97,14 @@ export default class BranchDelete extends EasCommand {
|
|||||||
args: { name: branchName },
|
args: { name: branchName },
|
||||||
flags,
|
flags,
|
||||||
} = await this.parse(BranchDelete);
|
} = await this.parse(BranchDelete);
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
|
||||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||||
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
|
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
const { projectId } = await this.getContextAsync(BranchDelete, { nonInteractive });
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
if (!branchName) {
|
if (!branchName) {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { listAndRenderBranchesOnAppAsync } from '../../branch/queries';
|
import { listAndRenderBranchesOnAppAsync } from '../../branch/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
|
|
||||||
export default class BranchList extends EasCommand {
|
export default class BranchList extends EasCommand {
|
||||||
@@ -14,19 +12,21 @@ export default class BranchList extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BranchList);
|
const { flags } = await this.parse(BranchList);
|
||||||
|
const { projectId } = await this.getContextAsync(BranchList, {
|
||||||
|
nonInteractive: flags['non-interactive'],
|
||||||
|
});
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
|
|
||||||
if (paginatedQueryOptions.json) {
|
if (paginatedQueryOptions.json) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, {
|
|
||||||
nonInteractive: flags['non-interactive'],
|
|
||||||
});
|
|
||||||
await listAndRenderBranchesOnAppAsync({ projectId, paginatedQueryOptions });
|
await listAndRenderBranchesOnAppAsync({ projectId, paginatedQueryOptions });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -12,12 +12,7 @@ import {
|
|||||||
UpdateBranch,
|
UpdateBranch,
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
@@ -67,17 +62,21 @@ export default class BranchRename extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(BranchRename);
|
} = await this.parse(BranchRename);
|
||||||
|
const { projectId } = await this.getContextAsync(BranchRename, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
if (!currentName) {
|
if (!currentName) {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import {
|
import {
|
||||||
EasPaginatedQueryFlags,
|
EasPaginatedQueryFlags,
|
||||||
getLimitFlagWithCustomValues,
|
getLimitFlagWithCustomValues,
|
||||||
getPaginatedQueryOptions,
|
getPaginatedQueryOptions,
|
||||||
} from '../../commandUtils/pagination';
|
} from '../../commandUtils/pagination';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { listAndRenderUpdateGroupsOnBranchAsync } from '../../update/queries';
|
import { listAndRenderUpdateGroupsOnBranchAsync } from '../../update/queries';
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
|
|
||||||
@@ -28,22 +26,25 @@ export default class BranchView extends EasCommand {
|
|||||||
limit: getLimitFlagWithCustomValues({ defaultTo: 25, limit: 50 }),
|
limit: getLimitFlagWithCustomValues({ defaultTo: 25, limit: 50 }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
args: { name: branchName },
|
args: { name: branchName },
|
||||||
flags,
|
flags,
|
||||||
} = await this.parse(BranchView);
|
} = await this.parse(BranchView);
|
||||||
const { 'non-interactive': nonInteractive } = flags;
|
const { 'non-interactive': nonInteractive } = flags;
|
||||||
|
const { projectId } = await this.getContextAsync(BranchView, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
|
|
||||||
if (paginatedQueryOptions.json) {
|
if (paginatedQueryOptions.json) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
if (!branchName) {
|
if (!branchName) {
|
||||||
if (nonInteractive) {
|
if (nonInteractive) {
|
||||||
throw new Error('Branch name may not be empty.');
|
throw new Error('Branch name may not be empty.');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -14,12 +14,7 @@ import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
|||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { appPlatformEmojis } from '../../platform';
|
import { appPlatformEmojis } from '../../platform';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { confirmAsync, selectAsync } from '../../prompts';
|
import { confirmAsync, selectAsync } from '../../prompts';
|
||||||
|
|
||||||
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
|
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
|
||||||
@@ -133,15 +128,19 @@ export default class BuildCancel extends EasCommand {
|
|||||||
...EASNonInteractiveFlag,
|
...EASNonInteractiveFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
args: { BUILD_ID: buildIdFromArg },
|
args: { BUILD_ID: buildIdFromArg },
|
||||||
flags: { 'non-interactive': nonInteractive },
|
flags: { 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(BuildCancel);
|
} = await this.parse(BuildCancel);
|
||||||
|
const { projectId } = await this.getContextAsync(BuildCancel, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
if (buildIdFromArg) {
|
if (buildIdFromArg) {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import chalk from 'chalk';
|
|||||||
|
|
||||||
import { cleanUpOldEasBuildGradleScriptAsync } from '../../build/android/syncProjectConfiguration';
|
import { cleanUpOldEasBuildGradleScriptAsync } from '../../build/android/syncProjectConfiguration';
|
||||||
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getExpoConfig } from '../../project/expoConfig';
|
||||||
import { findProjectRootAsync, isExpoUpdatesInstalled } from '../../project/projectUtils';
|
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
|
||||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
|
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
|
||||||
@@ -26,8 +29,16 @@ export default class BuildConfigure extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BuildConfigure);
|
const { flags } = await this.parse(BuildConfigure);
|
||||||
|
const { projectId, projectDir } = await this.getContextAsync(BuildConfigure, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
Log.log(
|
Log.log(
|
||||||
'💡 The following process will configure your iOS and/or Android project to be compatible with EAS Build. These changes only apply to your local project files and you can safely revert them at any time.'
|
'💡 The following process will configure your iOS and/or Android project to be compatible with EAS Build. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||||
@@ -35,7 +46,6 @@ export default class BuildConfigure extends EasCommand {
|
|||||||
|
|
||||||
await getVcsClient().ensureRepoExistsAsync();
|
await getVcsClient().ensureRepoExistsAsync();
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const expoUpdatesIsInstalled = isExpoUpdatesInstalled(projectDir);
|
const expoUpdatesIsInstalled = isExpoUpdatesInstalled(projectDir);
|
||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
@@ -60,14 +70,14 @@ export default class BuildConfigure extends EasCommand {
|
|||||||
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
|
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
await syncAndroidUpdatesConfigurationAsync(projectDir, exp, { nonInteractive: false });
|
await syncAndroidUpdatesConfigurationAsync(projectDir, exp, projectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
|
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||||
if (workflow === Workflow.GENERIC) {
|
if (workflow === Workflow.GENERIC) {
|
||||||
await syncIosUpdatesConfigurationAsync(projectDir, exp, { nonInteractive: false });
|
await syncIosUpdatesConfigurationAsync(projectDir, exp, projectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import path from 'path';
|
|||||||
|
|
||||||
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||||
import { UserInputResourceClass } from '../../build/types';
|
import { UserInputResourceClass } from '../../build/types';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { StatuspageServiceName } from '../../graphql/generated';
|
import { StatuspageServiceName } from '../../graphql/generated';
|
||||||
import Log, { link } from '../../log';
|
import Log, { link } from '../../log';
|
||||||
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
|
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
|
||||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
|
||||||
import { selectAsync } from '../../prompts';
|
import { selectAsync } from '../../prompts';
|
||||||
import uniq from '../../utils/expodash/uniq';
|
import uniq from '../../utils/expodash/uniq';
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
@@ -96,6 +99,12 @@ export default class Build extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags: rawFlags } = await this.parse(Build);
|
const { flags: rawFlags } = await this.parse(Build);
|
||||||
|
|
||||||
@@ -104,7 +113,9 @@ export default class Build extends EasCommand {
|
|||||||
}
|
}
|
||||||
const flags = this.sanitizeFlags(rawFlags);
|
const flags = this.sanitizeFlags(rawFlags);
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
const { actor, projectId, projectDir } = await this.getContextAsync(Build, {
|
||||||
|
nonInteractive: flags.nonInteractive,
|
||||||
|
});
|
||||||
|
|
||||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||||
|
|
||||||
@@ -118,7 +129,7 @@ export default class Build extends EasCommand {
|
|||||||
|
|
||||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||||
|
|
||||||
await runBuildAndSubmitAsync(projectDir, flagsWithPlatform);
|
await runBuildAndSubmitAsync(projectDir, flagsWithPlatform, actor, projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sanitizeFlags(
|
private sanitizeFlags(
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import path from 'path';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
|
||||||
import { getTmpDirectory } from '../../utils/paths';
|
import { getTmpDirectory } from '../../utils/paths';
|
||||||
import { getVcsClient } from '../../vcs';
|
import { getVcsClient } from '../../vcs';
|
||||||
|
|
||||||
@@ -62,8 +65,18 @@ export default class BuildInspect extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BuildInspect);
|
const { flags } = await this.parse(BuildInspect);
|
||||||
|
const { actor, projectId, projectDir } = await this.getContextAsync(BuildInspect, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
||||||
const tmpWorkingdir = path.join(getTmpDirectory(), uuidv4());
|
const tmpWorkingdir = path.join(getTmpDirectory(), uuidv4());
|
||||||
|
|
||||||
@@ -81,9 +94,10 @@ export default class BuildInspect extends EasCommand {
|
|||||||
await vcs.makeShallowCopyAsync(tmpWorkingdir);
|
await vcs.makeShallowCopyAsync(tmpWorkingdir);
|
||||||
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
||||||
} else {
|
} else {
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
try {
|
try {
|
||||||
await runBuildAndSubmitAsync(projectDir, {
|
await runBuildAndSubmitAsync(
|
||||||
|
projectDir,
|
||||||
|
{
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
wait: true,
|
wait: true,
|
||||||
clearCache: false,
|
clearCache: false,
|
||||||
@@ -99,7 +113,10 @@ export default class BuildInspect extends EasCommand {
|
|||||||
workingdir: tmpWorkingdir,
|
workingdir: tmpWorkingdir,
|
||||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
actor,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
if (!flags.verbose) {
|
if (!flags.verbose) {
|
||||||
Log.log(chalk.green('Build successful'));
|
Log.log(chalk.green('Build successful'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|||||||
|
|
||||||
import { BUILDS_LIMIT, listAndRenderBuildsOnAppAsync } from '../../build/queries';
|
import { BUILDS_LIMIT, listAndRenderBuildsOnAppAsync } from '../../build/queries';
|
||||||
import { BuildDistributionType, BuildStatus } from '../../build/types';
|
import { BuildDistributionType, BuildStatus } from '../../build/types';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import {
|
import {
|
||||||
EasPaginatedQueryFlags,
|
EasPaginatedQueryFlags,
|
||||||
@@ -15,12 +15,7 @@ import {
|
|||||||
BuildStatus as GraphQLBuildStatus,
|
BuildStatus as GraphQLBuildStatus,
|
||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
|
|
||||||
export default class BuildList extends EasCommand {
|
export default class BuildList extends EasCommand {
|
||||||
@@ -60,6 +55,10 @@ export default class BuildList extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BuildList);
|
const { flags } = await this.parse(BuildList);
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
@@ -70,6 +69,9 @@ export default class BuildList extends EasCommand {
|
|||||||
distribution: buildDistribution,
|
distribution: buildDistribution,
|
||||||
'non-interactive': nonInteractive,
|
'non-interactive': nonInteractive,
|
||||||
} = flags;
|
} = flags;
|
||||||
|
const { projectId } = await this.getContextAsync(BuildList, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
@@ -77,10 +79,6 @@ export default class BuildList extends EasCommand {
|
|||||||
const platform = toAppPlatform(requestedPlatform);
|
const platform = toAppPlatform(requestedPlatform);
|
||||||
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
|
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
|
||||||
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
|
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
await listAndRenderBuildsOnAppAsync({
|
await listAndRenderBuildsOnAppAsync({
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import EasCommand from '../../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../../commandUtils/EasCommand';
|
||||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||||
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
||||||
@@ -14,11 +18,7 @@ import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/
|
|||||||
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
||||||
import { getExpoConfig } from '../../../project/expoConfig';
|
import { getExpoConfig } from '../../../project/expoConfig';
|
||||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||||
import {
|
import { getDisplayNameForProjectIdAsync } from '../../../project/projectUtils';
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../../project/projectUtils';
|
|
||||||
import {
|
import {
|
||||||
ensureVersionSourceIsRemoteAsync,
|
ensureVersionSourceIsRemoteAsync,
|
||||||
getBuildVersionName,
|
getBuildVersionName,
|
||||||
@@ -42,10 +42,17 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
public async runAsync(): Promise<void> {
|
public async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BuildVersionSetView);
|
const { flags } = await this.parse(BuildVersionSetView);
|
||||||
|
const { actor, projectId, projectDir } = await this.getContextAsync(BuildVersionSetView, {
|
||||||
const projectDir = await findProjectRootAsync();
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const platform = await selectPlatformAsync(flags.platform);
|
const platform = await selectPlatformAsync(flags.platform);
|
||||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||||
@@ -57,9 +64,6 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exp = getExpoConfig(projectDir, { env: profile.env });
|
const exp = getExpoConfig(projectDir, { env: profile.env });
|
||||||
|
|
||||||
// this command is always interactive (see prompt below)
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||||
@@ -68,7 +72,8 @@ export default class BuildVersionSetView extends EasCommand {
|
|||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
profile,
|
profile,
|
||||||
platform
|
platform,
|
||||||
|
actor
|
||||||
);
|
);
|
||||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import chalk from 'chalk';
|
|||||||
|
|
||||||
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
import { updateNativeVersionsAsync as updateAndroidNativeVersionsAsync } from '../../../build/android/version';
|
||||||
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
import { updateNativeVersionsAsync as updateIosNativeVersionsAsync } from '../../../build/ios/version';
|
||||||
import EasCommand from '../../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../../commandUtils/EasCommand';
|
||||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||||
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
||||||
import Log from '../../../log';
|
import Log from '../../../log';
|
||||||
@@ -22,7 +26,6 @@ import { getExpoConfig } from '../../../project/expoConfig';
|
|||||||
import { resolveXcodeBuildContextAsync } from '../../../project/ios/scheme';
|
import { resolveXcodeBuildContextAsync } from '../../../project/ios/scheme';
|
||||||
import { resolveTargetsAsync } from '../../../project/ios/target';
|
import { resolveTargetsAsync } from '../../../project/ios/target';
|
||||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
|
||||||
import {
|
import {
|
||||||
ensureVersionSourceIsRemoteAsync,
|
ensureVersionSourceIsRemoteAsync,
|
||||||
getBuildVersionName,
|
getBuildVersionName,
|
||||||
@@ -56,9 +59,17 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
public async runAsync(): Promise<void> {
|
public async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(BuildVersionSyncView);
|
const { flags } = await this.parse(BuildVersionSyncView);
|
||||||
const projectDir = await findProjectRootAsync();
|
const { actor, projectId, projectDir } = await this.getContextAsync(BuildVersionSyncView, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||||
@@ -74,9 +85,6 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
for (const profileInfo of buildProfiles) {
|
for (const profileInfo of buildProfiles) {
|
||||||
const exp = getExpoConfig(projectDir, { env: profileInfo.profile.env });
|
const exp = getExpoConfig(projectDir, { env: profileInfo.profile.env });
|
||||||
|
|
||||||
// this command is always non-interactive
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
|
|
||||||
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
||||||
const platformDisplayName = appPlatformDisplayNames[toAppPlatform(profileInfo.platform)];
|
const platformDisplayName = appPlatformDisplayNames[toAppPlatform(profileInfo.platform)];
|
||||||
|
|
||||||
@@ -84,7 +92,8 @@ export default class BuildVersionSyncView extends EasCommand {
|
|||||||
projectDir,
|
projectDir,
|
||||||
exp,
|
exp,
|
||||||
profileInfo.profile,
|
profileInfo.profile,
|
||||||
profileInfo.platform
|
profileInfo.platform,
|
||||||
|
actor
|
||||||
);
|
);
|
||||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { formatGraphQLBuild } from '../../build/utils/formatBuild';
|
import { formatGraphQLBuild } from '../../build/utils/formatBuild';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasJsonOnlyFlag } from '../../commandUtils/flags';
|
import { EasJsonOnlyFlag } from '../../commandUtils/flags';
|
||||||
import { BuildFragment } from '../../graphql/generated';
|
import { BuildFragment } from '../../graphql/generated';
|
||||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
export default class BuildView extends EasCommand {
|
export default class BuildView extends EasCommand {
|
||||||
@@ -22,20 +17,22 @@ export default class BuildView extends EasCommand {
|
|||||||
...EasJsonOnlyFlag,
|
...EasJsonOnlyFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
args: { BUILD_ID: buildId },
|
args: { BUILD_ID: buildId },
|
||||||
flags,
|
flags,
|
||||||
} = await this.parse(BuildView);
|
} = await this.parse(BuildView);
|
||||||
|
const { projectId } = await this.getContextAsync(BuildView, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
if (flags.json) {
|
if (flags.json) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
|
|
||||||
// this command is always non-interactive
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const spinner = ora().start('Fetching the build…');
|
const spinner = ora().start('Fetching the build…');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import { BranchNotFoundError } from '../../branch/utils';
|
import { BranchNotFoundError } from '../../branch/utils';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -11,12 +11,7 @@ import {
|
|||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import formatFields from '../../utils/formatFields';
|
import formatFields from '../../utils/formatFields';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
@@ -75,19 +70,22 @@ export default class ChannelCreate extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
args: { name: channelName },
|
args: { name: channelName },
|
||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelCreate);
|
} = await this.parse(ChannelCreate);
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelCreate, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
if (!channelName) {
|
if (!channelName) {
|
||||||
const validationMessage = 'Channel name may not be empty.';
|
const validationMessage = 'Channel name may not be empty.';
|
||||||
if (nonInteractive) {
|
if (nonInteractive) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import { selectChannelOnAppAsync } from '../../channel/queries';
|
import { selectChannelOnAppAsync } from '../../channel/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
} from '../../graphql/generated';
|
} from '../../graphql/generated';
|
||||||
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { toggleConfirmAsync } from '../../prompts';
|
import { toggleConfirmAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
@@ -31,19 +29,22 @@ export default class ChannelDelete extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
args: { name: nameArg },
|
args: { name: nameArg },
|
||||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelDelete);
|
} = await this.parse(ChannelDelete);
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelDelete, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
let channelId, channelName;
|
let channelId, channelName;
|
||||||
if (nameArg) {
|
if (nameArg) {
|
||||||
const { id, name } = await ChannelQuery.viewUpdateChannelAsync({
|
const { id, name } = await ChannelQuery.viewUpdateChannelAsync({
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import gql from 'graphql-tag';
|
|||||||
|
|
||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||||
import { selectChannelOnAppAsync } from '../../channel/queries';
|
import { selectChannelOnAppAsync } from '../../channel/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import {
|
import {
|
||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||||
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
|
|
||||||
export async function updateChannelBranchMappingAsync({
|
export async function updateChannelBranchMappingAsync({
|
||||||
@@ -67,19 +65,22 @@ export default class ChannelEdit extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
args,
|
args,
|
||||||
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
|
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ChannelEdit);
|
} = await this.parse(ChannelEdit);
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelEdit, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (json) {
|
if (json) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
const existingChannel = args.name
|
const existingChannel = args.name
|
||||||
? await ChannelQuery.viewUpdateChannelAsync({ appId: projectId, channelName: args.name })
|
? await ChannelQuery.viewUpdateChannelAsync({ appId: projectId, channelName: args.name })
|
||||||
: await selectChannelOnAppAsync({
|
: await selectChannelOnAppAsync({
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { CHANNELS_LIMIT, listAndRenderChannelsOnAppAsync } from '../../channel/queries';
|
import { CHANNELS_LIMIT, listAndRenderChannelsOnAppAsync } from '../../channel/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import {
|
import {
|
||||||
EasPaginatedQueryFlags,
|
EasPaginatedQueryFlags,
|
||||||
getLimitFlagWithCustomValues,
|
getLimitFlagWithCustomValues,
|
||||||
getPaginatedQueryOptions,
|
getPaginatedQueryOptions,
|
||||||
} from '../../commandUtils/pagination';
|
} from '../../commandUtils/pagination';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
|
|
||||||
export default class ChannelList extends EasCommand {
|
export default class ChannelList extends EasCommand {
|
||||||
@@ -19,18 +17,21 @@ export default class ChannelList extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(ChannelList);
|
const { flags } = await this.parse(ChannelList);
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelList, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
await listAndRenderChannelsOnAppAsync({
|
await listAndRenderChannelsOnAppAsync({
|
||||||
projectId,
|
projectId,
|
||||||
paginatedQueryOptions,
|
paginatedQueryOptions,
|
||||||
|
|||||||
@@ -2,18 +2,13 @@ import { Flags } from '@oclif/core';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import { BranchMapping, getBranchMapping } from '../../channel/utils';
|
import { BranchMapping, getBranchMapping } from '../../channel/utils';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { UpdateBranch } from '../../graphql/generated';
|
import { UpdateBranch } from '../../graphql/generated';
|
||||||
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||||
import { ChannelQuery, UpdateChannelByNameObject } from '../../graphql/queries/ChannelQuery';
|
import { ChannelQuery, UpdateChannelByNameObject } from '../../graphql/queries/ChannelQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync, selectAsync } from '../../prompts';
|
import { promptAsync, selectAsync } from '../../prompts';
|
||||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||||
import { updateChannelBranchMappingAsync } from './edit';
|
import { updateChannelBranchMappingAsync } from './edit';
|
||||||
@@ -316,6 +311,10 @@ export default class ChannelRollout extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
args: { channel: channelName },
|
args: { channel: channelName },
|
||||||
@@ -327,13 +326,13 @@ export default class ChannelRollout extends EasCommand {
|
|||||||
'non-interactive': nonInteractive,
|
'non-interactive': nonInteractive,
|
||||||
},
|
},
|
||||||
} = await this.parse(ChannelRollout);
|
} = await this.parse(ChannelRollout);
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelRollout, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const channel = await ChannelQuery.viewUpdateChannelAsync({
|
const channel = await ChannelQuery.viewUpdateChannelAsync({
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import {
|
|||||||
listAndRenderBranchesAndUpdatesOnChannelAsync,
|
listAndRenderBranchesAndUpdatesOnChannelAsync,
|
||||||
selectChannelOnAppAsync,
|
selectChannelOnAppAsync,
|
||||||
} from '../../channel/queries';
|
} from '../../channel/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { enableJsonOutput } from '../../utils/json';
|
import { enableJsonOutput } from '../../utils/json';
|
||||||
|
|
||||||
export default class ChannelView extends EasCommand {
|
export default class ChannelView extends EasCommand {
|
||||||
@@ -27,6 +25,10 @@ export default class ChannelView extends EasCommand {
|
|||||||
...EasPaginatedQueryFlags,
|
...EasPaginatedQueryFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
args: { name: channelName },
|
args: { name: channelName },
|
||||||
@@ -34,14 +36,13 @@ export default class ChannelView extends EasCommand {
|
|||||||
} = await this.parse(ChannelView);
|
} = await this.parse(ChannelView);
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||||
|
const { projectId } = await this.getContextAsync(ChannelView, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
if (!channelName) {
|
if (!channelName) {
|
||||||
const validationMessage = 'A channel name is required to view a specific channel.';
|
const validationMessage = 'A channel name is required to view a specific channel.';
|
||||||
if (nonInteractive) {
|
if (nonInteractive) {
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectDirContext } from '../commandUtils/EasCommand';
|
||||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { appPlatformEmojis } from '../platform';
|
import { appPlatformEmojis } from '../platform';
|
||||||
import { getExpoConfig } from '../project/expoConfig';
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
import { findProjectRootAsync } from '../project/projectUtils';
|
|
||||||
import { selectAsync } from '../prompts';
|
import { selectAsync } from '../prompts';
|
||||||
|
|
||||||
export default class Config extends EasCommand {
|
export default class Config extends EasCommand {
|
||||||
@@ -25,17 +24,20 @@ export default class Config extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override requiresAuthentication = false;
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(Config);
|
const { flags } = await this.parse(Config);
|
||||||
|
const { projectDir } = await this.getContextAsync(Config, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
const { platform: maybePlatform, profile: maybeProfile } = flags as {
|
const { platform: maybePlatform, profile: maybeProfile } = flags as {
|
||||||
platform?: Platform;
|
platform?: Platform;
|
||||||
profile?: string;
|
profile?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
|
|
||||||
const accessor = new EasJsonAccessor(projectDir);
|
const accessor = new EasJsonAccessor(projectDir);
|
||||||
const profileName =
|
const profileName =
|
||||||
maybeProfile ??
|
maybeProfile ??
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../commandUtils/EasCommand';
|
||||||
import { SelectPlatform } from '../credentials/manager/SelectPlatform';
|
import { SelectPlatform } from '../credentials/manager/SelectPlatform';
|
||||||
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
|
|
||||||
export default class Credentials extends EasCommand {
|
export default class Credentials extends EasCommand {
|
||||||
static override description = 'manage credentials';
|
static override description = 'manage credentials';
|
||||||
@@ -10,8 +15,20 @@ export default class Credentials extends EasCommand {
|
|||||||
platform: Flags.enum({ char: 'p', options: ['android', 'ios'] }),
|
platform: Flags.enum({ char: 'p', options: ['android', 'ios'] }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(Credentials);
|
const { flags } = await this.parse(Credentials);
|
||||||
await new SelectPlatform(flags.platform).runAsync();
|
const { actor, projectId, projectDir } = await this.getContextAsync(Credentials, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exp = getExpoConfig(projectDir);
|
||||||
|
|
||||||
|
await new SelectPlatform(actor, { projectId, exp }, flags.platform).runAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import AppStoreApi from '../../credentials/ios/appstore/AppStoreApi';
|
import AppStoreApi from '../../credentials/ios/appstore/AppStoreApi';
|
||||||
import { createContextAsync } from '../../devices/context';
|
import { createContextAsync } from '../../devices/context';
|
||||||
import DeviceManager from '../../devices/manager';
|
import DeviceManager from '../../devices/manager';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
|
|
||||||
export default class DeviceCreate extends EasCommand {
|
export default class DeviceCreate extends EasCommand {
|
||||||
static override description = 'register new Apple Devices to use for internal distribution';
|
static override description = 'register new Apple Devices to use for internal distribution';
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
// this command is interactive by design
|
// this command is interactive by design
|
||||||
const user = await ensureLoggedInAsync({ nonInteractive: false });
|
const { actor, projectId } = await this.getContextAsync(DeviceCreate, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const ctx = await createContextAsync({ appStore: new AppStoreApi(), user });
|
const ctx = await createContextAsync({ appStore: new AppStoreApi(), user: actor, projectId });
|
||||||
const manager = new DeviceManager(ctx);
|
const manager = new DeviceManager(ctx);
|
||||||
await manager.createAsync();
|
await manager.createAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Device, DeviceStatus } from '@expo/apple-utils';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { chooseDevicesToDeleteAsync } from '../../credentials/ios/actions/DeviceUtils';
|
import { chooseDevicesToDeleteAsync } from '../../credentials/ios/actions/DeviceUtils';
|
||||||
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
|
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
|
||||||
import {
|
import {
|
||||||
@@ -16,12 +16,7 @@ import formatDevice from '../../devices/utils/formatDevice';
|
|||||||
import { AppleDevice, Maybe } from '../../graphql/generated';
|
import { AppleDevice, Maybe } from '../../graphql/generated';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getOwnerAccountForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||||
|
|
||||||
export default class DeviceDelete extends EasCommand {
|
export default class DeviceDelete extends EasCommand {
|
||||||
@@ -32,15 +27,18 @@ export default class DeviceDelete extends EasCommand {
|
|||||||
udid: Flags.string({ multiple: true }),
|
udid: Flags.string({ multiple: true }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
flags: { 'apple-team-id': appleTeamIdentifier, udid: udids },
|
flags: { 'apple-team-id': appleTeamIdentifier, udid: udids },
|
||||||
} = await this.parse(DeviceDelete);
|
} = await this.parse(DeviceDelete);
|
||||||
|
const { projectId } = await this.getContextAsync(DeviceDelete, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
// this command is interactive by design
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
if (!appleTeamIdentifier) {
|
if (!appleTeamIdentifier) {
|
||||||
|
|||||||
@@ -2,18 +2,13 @@ import { Flags } from '@oclif/core';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
||||||
import { AppleTeamQuery } from '../../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
import { AppleTeamQuery } from '../../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
||||||
import formatDevice from '../../devices/utils/formatDevice';
|
import formatDevice from '../../devices/utils/formatDevice';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { Ora, ora } from '../../ora';
|
import { Ora, ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getOwnerAccountForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
|
|
||||||
export default class BuildList extends EasCommand {
|
export default class BuildList extends EasCommand {
|
||||||
@@ -23,14 +18,16 @@ export default class BuildList extends EasCommand {
|
|||||||
'apple-team-id': Flags.string(),
|
'apple-team-id': Flags.string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let appleTeamIdentifier = (await this.parse(BuildList)).flags['apple-team-id'];
|
let appleTeamIdentifier = (await this.parse(BuildList)).flags['apple-team-id'];
|
||||||
|
const { projectId } = await this.getContextAsync(BuildList, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
|
|
||||||
// this command is interactive by design
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
let spinner: Ora;
|
let spinner: Ora;
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
||||||
import formatDevice from '../../devices/utils/formatDevice';
|
import formatDevice from '../../devices/utils/formatDevice';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getOwnerAccountForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
|
|
||||||
export default class DeviceView extends EasCommand {
|
export default class DeviceView extends EasCommand {
|
||||||
static override description = 'view a device for your project';
|
static override description = 'view a device for your project';
|
||||||
|
|
||||||
static override args = [{ name: 'UDID' }];
|
static override args = [{ name: 'UDID' }];
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { UDID } = (await this.parse(DeviceView)).args;
|
const { UDID } = (await this.parse(DeviceView)).args;
|
||||||
|
|
||||||
@@ -31,12 +30,9 @@ If you are not sure what is the UDID of the device you are looking for, run:
|
|||||||
);
|
);
|
||||||
throw new Error('Device UDID is missing');
|
throw new Error('Device UDID is missing');
|
||||||
}
|
}
|
||||||
|
const { projectId } = await this.getContextAsync(DeviceView, {
|
||||||
const projectDir = await findProjectRootAsync();
|
nonInteractive: true,
|
||||||
const exp = getExpoConfig(projectDir);
|
});
|
||||||
|
|
||||||
// this command is non-interactive by design
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const spinner = ora().start(`Fetching device details for ${UDID}…`);
|
const spinner = ora().start(`Fetching device details for ${UDID}…`);
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import { Platform } from '@expo/eas-build-job';
|
import { Platform } from '@expo/eas-build-job';
|
||||||
import envinfo from 'envinfo';
|
import envinfo from 'envinfo';
|
||||||
|
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectDirContext } from '../commandUtils/EasCommand';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { findProjectRootAsync } from '../project/projectUtils';
|
|
||||||
import { resolveWorkflowAsync } from '../project/workflow';
|
import { resolveWorkflowAsync } from '../project/workflow';
|
||||||
import { easCliVersion } from '../utils/easCli';
|
import { easCliVersion } from '../utils/easCli';
|
||||||
|
|
||||||
export default class Diagnostics extends EasCommand {
|
export default class Diagnostics extends EasCommand {
|
||||||
static override description = 'display environment info';
|
static override description = 'display environment info';
|
||||||
|
|
||||||
protected override requiresAuthentication = false;
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
|
const { projectDir } = await this.getContextAsync(Diagnostics, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
const info = await envinfo.run(
|
const info = await envinfo.run(
|
||||||
{
|
{
|
||||||
System: ['OS', 'Shell'],
|
System: ['OS', 'Shell'],
|
||||||
@@ -38,12 +43,11 @@ export default class Diagnostics extends EasCommand {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Log.log(info.trimEnd());
|
Log.log(info.trimEnd());
|
||||||
await this.printWorkflowAsync();
|
await this.printWorkflowAsync(projectDir);
|
||||||
Log.newLine();
|
Log.newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async printWorkflowAsync(): Promise<void> {
|
private async printWorkflowAsync(projectDir: string): Promise<void> {
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const androidWorkflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
const androidWorkflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||||
const iosWorkflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
const iosWorkflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import chalk from 'chalk';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import { CredentialsContext } from '../../credentials/context';
|
import { CredentialsContext } from '../../credentials/context';
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { createMetadataContextAsync } from '../../metadata/context';
|
import { createMetadataContextAsync } from '../../metadata/context';
|
||||||
import { downloadMetadataAsync } from '../../metadata/download';
|
import { downloadMetadataAsync } from '../../metadata/download';
|
||||||
import { handleMetadataError } from '../../metadata/errors';
|
import { handleMetadataError } from '../../metadata/errors';
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
|
|
||||||
export default class MetadataPull extends EasCommand {
|
export default class MetadataPull extends EasCommand {
|
||||||
static override description = 'generate the local store configuration from the app stores';
|
static override description = 'generate the local store configuration from the app stores';
|
||||||
@@ -24,21 +26,29 @@ export default class MetadataPull extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
||||||
|
|
||||||
const { flags } = await this.parse(MetadataPull);
|
const { flags } = await this.parse(MetadataPull);
|
||||||
const projectDir = await findProjectRootAsync();
|
const { actor, projectId, projectDir } = await this.getContextAsync(MetadataPull, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||||
|
|
||||||
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
||||||
await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
||||||
|
|
||||||
const credentialsCtx = new CredentialsContext({
|
const credentialsCtx = new CredentialsContext({
|
||||||
exp,
|
projectInfo: { exp, projectId },
|
||||||
projectDir,
|
projectDir,
|
||||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
user: actor,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import { getConfig } from '@expo/config';
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import { CredentialsContext } from '../../credentials/context';
|
import { CredentialsContext } from '../../credentials/context';
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { createMetadataContextAsync } from '../../metadata/context';
|
import { createMetadataContextAsync } from '../../metadata/context';
|
||||||
import { handleMetadataError } from '../../metadata/errors';
|
import { handleMetadataError } from '../../metadata/errors';
|
||||||
import { uploadMetadataAsync } from '../../metadata/upload';
|
import { uploadMetadataAsync } from '../../metadata/upload';
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
|
|
||||||
export default class MetadataPush extends EasCommand {
|
export default class MetadataPush extends EasCommand {
|
||||||
static override description = 'sync the local store configuration to the app stores';
|
static override description = 'sync the local store configuration to the app stores';
|
||||||
@@ -22,21 +24,29 @@ export default class MetadataPush extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
||||||
|
|
||||||
const { flags } = await this.parse(MetadataPush);
|
const { flags } = await this.parse(MetadataPush);
|
||||||
const projectDir = await findProjectRootAsync();
|
const { actor, projectId, projectDir } = await this.getContextAsync(MetadataPush, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||||
|
|
||||||
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
||||||
await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
||||||
|
|
||||||
const credentialsCtx = new CredentialsContext({
|
const credentialsCtx = new CredentialsContext({
|
||||||
exp,
|
projectInfo: { exp, projectId },
|
||||||
projectDir,
|
projectDir,
|
||||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
user: actor,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
import openBrowserAsync from 'better-opn';
|
import openBrowserAsync from 'better-opn';
|
||||||
|
|
||||||
import { getProjectDashboardUrl } from '../build/utils/url';
|
import { getProjectDashboardUrl } from '../build/utils/url';
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../commandUtils/EasCommand';
|
||||||
import { ora } from '../ora';
|
import { ora } from '../ora';
|
||||||
import { getExpoConfig } from '../project/expoConfig';
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
import {
|
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||||
findProjectRootAsync,
|
|
||||||
getOwnerAccountForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../project/projectUtils';
|
|
||||||
|
|
||||||
export default class Open extends EasCommand {
|
export default class Open extends EasCommand {
|
||||||
static override description = 'open the project page in a web browser';
|
static override description = 'open the project page in a web browser';
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const projectDir = await findProjectRootAsync();
|
// this command is interactive by nature (only really run by humans in a terminal)
|
||||||
|
const { projectId, projectDir } = await this.getContextAsync(Open, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
const exp = getExpoConfig(projectDir);
|
const exp = getExpoConfig(projectDir);
|
||||||
|
|
||||||
// this command is interactive by nature (only really run by humans in a terminal)
|
|
||||||
// this ensures the project exists before opening the browser
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const projectName = exp.slug;
|
const projectName = exp.slug;
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
|
|
||||||
|
import ActorContextField from '../../../commandUtils/context/ActorContextField';
|
||||||
|
import ProjectDirContextField from '../../../commandUtils/context/ProjectDirContextField';
|
||||||
|
import ProjectIdContextField from '../../../commandUtils/context/ProjectIdContextField';
|
||||||
import { jester } from '../../../credentials/__tests__/fixtures-constants';
|
import { jester } from '../../../credentials/__tests__/fixtures-constants';
|
||||||
import { AppMutation } from '../../../graphql/mutations/AppMutation';
|
import { AppMutation } from '../../../graphql/mutations/AppMutation';
|
||||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
import { findProjectRootAsync, saveProjectIdToAppConfigAsync } from '../../../project/projectUtils';
|
|
||||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||||
import { ensureLoggedInAsync } from '../../../user/actions';
|
|
||||||
import ProjectInit from '../init';
|
import ProjectInit from '../init';
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('fs');
|
||||||
jest.mock('@expo/config');
|
jest.mock('@expo/config');
|
||||||
jest.mock('../../../project/projectUtils');
|
|
||||||
jest.mock('../../../prompts');
|
jest.mock('../../../prompts');
|
||||||
jest.mock('../../../user/actions');
|
jest.mock('../../../user/actions');
|
||||||
jest.mock('../../../graphql/mutations/AppMutation');
|
jest.mock('../../../graphql/mutations/AppMutation');
|
||||||
@@ -22,6 +22,7 @@ jest.mock('../../../ora', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
jest.mock('../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
jest.mock('../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||||
|
jest.mock('../../../commandUtils/context/ProjectIdContextField');
|
||||||
|
|
||||||
let originalProcessArgv: string[];
|
let originalProcessArgv: string[];
|
||||||
|
|
||||||
@@ -72,15 +73,14 @@ function mockTestProject(options: {
|
|||||||
const mockManifest = { exp: appJSON.expo };
|
const mockManifest = { exp: appJSON.expo };
|
||||||
|
|
||||||
jest.mocked(getConfig).mockReturnValue(mockManifest as any);
|
jest.mocked(getConfig).mockReturnValue(mockManifest as any);
|
||||||
jest.mocked(findProjectRootAsync).mockResolvedValue('/test-project');
|
jest.spyOn(ProjectDirContextField.prototype, 'getValueAsync').mockResolvedValue('/test-project');
|
||||||
|
jest.spyOn(ActorContextField.prototype, 'getValueAsync').mockResolvedValue(jester);
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandOptions = { root: '/test-project' } as any;
|
const commandOptions = { root: '/test-project' } as any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
|
||||||
jest.mocked(ensureLoggedInAsync).mockResolvedValue(jester);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(ProjectInit.name, () => {
|
describe(ProjectInit.name, () => {
|
||||||
@@ -93,20 +93,23 @@ describe(ProjectInit.name, () => {
|
|||||||
describe('interactive', () => {
|
describe('interactive', () => {
|
||||||
it('is no-op if already configured for id', async () => {
|
it('is no-op if already configured for id', async () => {
|
||||||
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prompts to overwrite when different', async () => {
|
it('prompts to overwrite when different', async () => {
|
||||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||||
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '12345');
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).toHaveBeenCalledWith(
|
||||||
|
'/test-project',
|
||||||
|
'12345'
|
||||||
|
);
|
||||||
expect(confirmAsync).toHaveBeenCalled();
|
expect(confirmAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('aborts when prompt to overwrite is declined', async () => {
|
it('aborts when prompt to overwrite is declined', async () => {
|
||||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||||
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
expect(confirmAsync).toHaveBeenCalled();
|
expect(confirmAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -116,7 +119,10 @@ describe(ProjectInit.name, () => {
|
|||||||
await new ProjectInit(['--id', '12345', '--force'], {
|
await new ProjectInit(['--id', '12345', '--force'], {
|
||||||
root: '/test-project',
|
root: '/test-project',
|
||||||
} as any).run();
|
} as any).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '12345');
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).toHaveBeenCalledWith(
|
||||||
|
'/test-project',
|
||||||
|
'12345'
|
||||||
|
);
|
||||||
expect(confirmAsync).not.toHaveBeenCalled();
|
expect(confirmAsync).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -132,7 +138,7 @@ describe(ProjectInit.name, () => {
|
|||||||
'1234'
|
'1234'
|
||||||
)}. Use --force flag to overwrite.`
|
)}. Use --force flag to overwrite.`
|
||||||
);
|
);
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -144,7 +150,10 @@ describe(ProjectInit.name, () => {
|
|||||||
|
|
||||||
it('configures', async () => {
|
it('configures', async () => {
|
||||||
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '1234');
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).toHaveBeenCalledWith(
|
||||||
|
'/test-project',
|
||||||
|
'1234'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -157,7 +166,7 @@ describe(ProjectInit.name, () => {
|
|||||||
|
|
||||||
it('does not configure', async () => {
|
it('does not configure', async () => {
|
||||||
await new ProjectInit([], commandOptions).run();
|
await new ProjectInit([], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,7 +184,10 @@ describe(ProjectInit.name, () => {
|
|||||||
it('prompts for confirmation to link', async () => {
|
it('prompts for confirmation to link', async () => {
|
||||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||||
await new ProjectInit([], commandOptions).run();
|
await new ProjectInit([], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '123456');
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).toHaveBeenCalledWith(
|
||||||
|
'/test-project',
|
||||||
|
'123456'
|
||||||
|
);
|
||||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,7 +196,7 @@ describe(ProjectInit.name, () => {
|
|||||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrow(
|
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrow(
|
||||||
'Project ID configuration canceled. Re-run the command to select a different account/project.'
|
'Project ID configuration canceled. Re-run the command to select a different account/project.'
|
||||||
);
|
);
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -194,7 +206,10 @@ describe(ProjectInit.name, () => {
|
|||||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||||
jest.mocked(AppMutation.createAppAsync).mockResolvedValue('0129');
|
jest.mocked(AppMutation.createAppAsync).mockResolvedValue('0129');
|
||||||
await new ProjectInit([], commandOptions).run();
|
await new ProjectInit([], commandOptions).run();
|
||||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '0129');
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).toHaveBeenCalledWith(
|
||||||
|
'/test-project',
|
||||||
|
'0129'
|
||||||
|
);
|
||||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,7 +218,7 @@ describe(ProjectInit.name, () => {
|
|||||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
||||||
'Project ID configuration canceled for @jester/testing-123.'
|
'Project ID configuration canceled for @jester/testing-123.'
|
||||||
);
|
);
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -229,7 +244,7 @@ describe(ProjectInit.name, () => {
|
|||||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
||||||
`You don't have permission to create a new project on the other account and no matching project already exists on the account.`
|
`You don't have permission to create a new project on the other account and no matching project already exists on the account.`
|
||||||
);
|
);
|
||||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
expect(ProjectIdContextField['saveProjectIdToAppConfigAsync']).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||||
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
|
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import formatFields from '../../utils/formatFields';
|
import formatFields from '../../utils/formatFields';
|
||||||
|
|
||||||
async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
||||||
@@ -34,12 +32,14 @@ async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
|||||||
export default class ProjectInfo extends EasCommand {
|
export default class ProjectInfo extends EasCommand {
|
||||||
static override description = 'information about the current project';
|
static override description = 'information about the current project';
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
static override contextDefinition = {
|
||||||
const projectDir = await findProjectRootAsync();
|
...EASCommandProjectIdContext,
|
||||||
const exp = getExpoConfig(projectDir);
|
};
|
||||||
|
|
||||||
// this command is non-interactive by design
|
async runAsync(): Promise<void> {
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
const { projectId } = await this.getContextAsync(ProjectInfo, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
const { app } = await projectInfoByIdAsync(projectId);
|
const { app } = await projectInfoByIdAsync(projectId);
|
||||||
if (!app) {
|
if (!app) {
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ import nullthrows from 'nullthrows';
|
|||||||
import terminalLink from 'terminal-link';
|
import terminalLink from 'terminal-link';
|
||||||
|
|
||||||
import { getProjectDashboardUrl } from '../../build/utils/url';
|
import { getProjectDashboardUrl } from '../../build/utils/url';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
|
import ProjectIdContextField from '../../commandUtils/context/ProjectIdContextField';
|
||||||
import { AppPrivacy, Role } from '../../graphql/generated';
|
import { AppPrivacy, Role } from '../../graphql/generated';
|
||||||
import { AppMutation } from '../../graphql/mutations/AppMutation';
|
import { AppMutation } from '../../graphql/mutations/AppMutation';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getExpoConfig } from '../../project/expoConfig';
|
||||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
import {
|
import { toAppPrivacy } from '../../project/projectUtils';
|
||||||
findProjectRootAsync,
|
|
||||||
saveProjectIdToAppConfigAsync,
|
|
||||||
toAppPrivacy,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { confirmAsync, promptAsync } from '../../prompts';
|
import { confirmAsync, promptAsync } from '../../prompts';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
import { Actor } from '../../user/User';
|
||||||
|
|
||||||
type InitializeMethodOptions = {
|
type InitializeMethodOptions = {
|
||||||
force: boolean;
|
force: boolean;
|
||||||
@@ -43,19 +43,24 @@ export default class ProjectInit extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
private static async saveProjectIdAndLogSuccessAsync(
|
private static async saveProjectIdAndLogSuccessAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
projectId: string
|
projectId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await saveProjectIdToAppConfigAsync(projectDir, projectId);
|
await ProjectIdContextField['saveProjectIdToAppConfigAsync'](projectDir, projectId);
|
||||||
Log.withTick(`Project successfully linked (ID: ${chalk.bold(projectId)}) (modified app.json)`);
|
Log.withTick(`Project successfully linked (ID: ${chalk.bold(projectId)}) (modified app.json)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async initializeWithExplicitIDAsync(
|
private static async initializeWithExplicitIDAsync(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
projectDir: string,
|
||||||
{ force, nonInteractive }: InitializeMethodOptions
|
{ force, nonInteractive }: InitializeMethodOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
const exp = getExpoConfig(projectDir);
|
||||||
const existingProjectId = exp.extra?.eas?.projectId;
|
const existingProjectId = exp.extra?.eas?.projectId;
|
||||||
|
|
||||||
@@ -97,8 +102,10 @@ export default class ProjectInit extends EasCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async initializeWithInteractiveSelectionAsync(): Promise<void> {
|
private static async initializeWithInteractiveSelectionAsync(
|
||||||
const projectDir = await findProjectRootAsync();
|
actor: Actor,
|
||||||
|
projectDir: string
|
||||||
|
): Promise<void> {
|
||||||
const exp = getExpoConfig(projectDir);
|
const exp = getExpoConfig(projectDir);
|
||||||
const existingProjectId = exp.extra?.eas?.projectId;
|
const existingProjectId = exp.extra?.eas?.projectId;
|
||||||
|
|
||||||
@@ -111,7 +118,6 @@ export default class ProjectInit extends EasCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actor = await ensureLoggedInAsync({ nonInteractive: false });
|
|
||||||
const allAccounts = actor.accounts;
|
const allAccounts = actor.accounts;
|
||||||
const accountNamesWhereUserHasSufficientPermissionsToCreateApp = new Set(
|
const accountNamesWhereUserHasSufficientPermissionsToCreateApp = new Set(
|
||||||
allAccounts
|
allAccounts
|
||||||
@@ -215,11 +221,12 @@ export default class ProjectInit extends EasCommand {
|
|||||||
const {
|
const {
|
||||||
flags: { id, force, 'non-interactive': nonInteractive },
|
flags: { id, force, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(ProjectInit);
|
} = await this.parse(ProjectInit);
|
||||||
|
const { actor, projectDir } = await this.getContextAsync(ProjectInit, { nonInteractive });
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
await ProjectInit.initializeWithExplicitIDAsync(id, { force, nonInteractive });
|
await ProjectInit.initializeWithExplicitIDAsync(id, projectDir, { force, nonInteractive });
|
||||||
} else {
|
} else {
|
||||||
await ProjectInit.initializeWithInteractiveSelectionAsync();
|
await ProjectInit.initializeWithInteractiveSelectionAsync(actor, projectDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||||
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
||||||
import {
|
import {
|
||||||
@@ -16,12 +16,9 @@ import {
|
|||||||
SecretTypeToEnvironmentSecretType,
|
SecretTypeToEnvironmentSecretType,
|
||||||
} from '../../graphql/types/EnvironmentSecret';
|
} from '../../graphql/types/EnvironmentSecret';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import {
|
import {
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
getDisplayNameForProjectIdAsync,
|
||||||
getOwnerAccountForProjectIdAsync,
|
getOwnerAccountForProjectIdAsync,
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
} from '../../project/projectUtils';
|
||||||
import { promptAsync, selectAsync } from '../../prompts';
|
import { promptAsync, selectAsync } from '../../prompts';
|
||||||
|
|
||||||
@@ -52,6 +49,10 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
|||||||
...EASNonInteractiveFlag,
|
...EASNonInteractiveFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
flags: {
|
flags: {
|
||||||
@@ -63,10 +64,10 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
|||||||
'non-interactive': nonInteractive,
|
'non-interactive': nonInteractive,
|
||||||
},
|
},
|
||||||
} = await this.parse(EnvironmentSecretCreate);
|
} = await this.parse(EnvironmentSecretCreate);
|
||||||
|
const { projectId } = await this.getContextAsync(EnvironmentSecretCreate, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
const ownerAccount = await getOwnerAccountForProjectIdAsync(projectId);
|
const ownerAccount = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||||
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
||||||
import {
|
import {
|
||||||
@@ -9,8 +9,6 @@ import {
|
|||||||
EnvironmentSecretsQuery,
|
EnvironmentSecretsQuery,
|
||||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||||
|
|
||||||
export default class EnvironmentSecretDelete extends EasCommand {
|
export default class EnvironmentSecretDelete extends EasCommand {
|
||||||
@@ -23,16 +21,19 @@ export default class EnvironmentSecretDelete extends EasCommand {
|
|||||||
...EASNonInteractiveFlag,
|
...EASNonInteractiveFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
flags: { id, 'non-interactive': nonInteractive },
|
flags: { id, 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(EnvironmentSecretDelete);
|
} = await this.parse(EnvironmentSecretDelete);
|
||||||
|
const { projectId } = await this.getContextAsync(EnvironmentSecretDelete, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
let secret: EnvironmentSecretWithScope | undefined;
|
let secret: EnvironmentSecretWithScope | undefined;
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
const validationMessage = 'You must select which secret to delete.';
|
const validationMessage = 'You must select which secret to delete.';
|
||||||
if (nonInteractive) {
|
if (nonInteractive) {
|
||||||
|
|||||||
@@ -2,26 +2,23 @@ import chalk from 'chalk';
|
|||||||
import Table from 'cli-table3';
|
import Table from 'cli-table3';
|
||||||
import dateFormat from 'dateformat';
|
import dateFormat from 'dateformat';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
|
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||||
import { EnvironmentSecretTypeToSecretType } from '../../graphql/types/EnvironmentSecret';
|
import { EnvironmentSecretTypeToSecretType } from '../../graphql/types/EnvironmentSecret';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
|
|
||||||
export default class EnvironmentSecretList extends EasCommand {
|
export default class EnvironmentSecretList extends EasCommand {
|
||||||
static override description = 'list environment secrets available for your current app';
|
static override description = 'list environment secrets available for your current app';
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const projectDir = await findProjectRootAsync();
|
const { projectId } = await this.getContextAsync(EnvironmentSecretList, {
|
||||||
if (!projectDir) {
|
nonInteractive: true,
|
||||||
throw new Error("Run this command inside your project's directory");
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
|
|
||||||
// this command is non-interacive by design
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
|
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
|
||||||
|
|
||||||
const table = new Table({
|
const table = new Table({
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { EasJsonAccessor } from '@expo/eas-json';
|
|||||||
import { Errors, Flags } from '@oclif/core';
|
import { Errors, Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import EasCommand from '../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandLoggedInContext,
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../commandUtils/EasCommand';
|
||||||
import { StatuspageServiceName, SubmissionFragment } from '../graphql/generated';
|
import { StatuspageServiceName, SubmissionFragment } from '../graphql/generated';
|
||||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
@@ -13,8 +17,6 @@ import {
|
|||||||
selectRequestedPlatformAsync,
|
selectRequestedPlatformAsync,
|
||||||
toPlatforms,
|
toPlatforms,
|
||||||
} from '../platform';
|
} from '../platform';
|
||||||
import { getExpoConfig } from '../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
|
|
||||||
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
||||||
import {
|
import {
|
||||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
|
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
|
||||||
@@ -91,15 +93,20 @@ export default class Submit extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandLoggedInContext,
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags: rawFlags } = await this.parse(Submit);
|
const { flags: rawFlags } = await this.parse(Submit);
|
||||||
|
const { actor, projectId, projectDir } = await this.getContextAsync(Submit, {
|
||||||
|
nonInteractive: false,
|
||||||
|
});
|
||||||
|
|
||||||
const flags = this.sanitizeFlags(rawFlags);
|
const flags = this.sanitizeFlags(rawFlags);
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: flags.nonInteractive });
|
|
||||||
|
|
||||||
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasSubmit]);
|
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasSubmit]);
|
||||||
|
|
||||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||||
@@ -121,6 +128,7 @@ export default class Submit extends EasCommand {
|
|||||||
profile: submissionProfile.profile,
|
profile: submissionProfile.profile,
|
||||||
archiveFlags: flagsWithPlatform.archiveFlags,
|
archiveFlags: flagsWithPlatform.archiveFlags,
|
||||||
nonInteractive: flagsWithPlatform.nonInteractive,
|
nonInteractive: flagsWithPlatform.nonInteractive,
|
||||||
|
actor,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (submissionProfiles.length > 1) {
|
if (submissionProfiles.length > 1) {
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import assert from 'assert';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import { getEASUpdateURL } from '../../api';
|
import { getEASUpdateURL } from '../../api';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import { AppPlatform } from '../../graphql/generated';
|
import { AppPlatform } from '../../graphql/generated';
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { RequestedPlatform, appPlatformDisplayNames } from '../../platform';
|
import { RequestedPlatform, appPlatformDisplayNames } from '../../platform';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getExpoConfig } from '../../project/expoConfig';
|
||||||
import {
|
import {
|
||||||
findProjectRootAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
installExpoUpdatesAsync,
|
installExpoUpdatesAsync,
|
||||||
isExpoUpdatesInstalledOrAvailable,
|
isExpoUpdatesInstalledOrAvailable,
|
||||||
} from '../../project/projectUtils';
|
} from '../../project/projectUtils';
|
||||||
@@ -35,13 +36,20 @@ export default class UpdateConfigure extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
Log.log(
|
Log.log(
|
||||||
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||||
);
|
);
|
||||||
const { flags } = await this.parse(UpdateConfigure);
|
const { flags } = await this.parse(UpdateConfigure);
|
||||||
|
const { projectId, projectDir } = await this.getContextAsync(UpdateConfigure, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
const platform = flags.platform as RequestedPlatform;
|
const platform = flags.platform as RequestedPlatform;
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
const exp = getExpoConfig(projectDir);
|
||||||
|
|
||||||
if (!isExpoUpdatesInstalledOrAvailable(projectDir, exp.sdkVersion)) {
|
if (!isExpoUpdatesInstalledOrAvailable(projectDir, exp.sdkVersion)) {
|
||||||
@@ -61,6 +69,7 @@ export default class UpdateConfigure extends EasCommand {
|
|||||||
android: androidWorkflow,
|
android: androidWorkflow,
|
||||||
ios: iosWorkflow,
|
ios: iosWorkflow,
|
||||||
},
|
},
|
||||||
|
projectId,
|
||||||
});
|
});
|
||||||
Log.withTick(`Configured ${chalk.bold('app.json')} for EAS Update`);
|
Log.withTick(`Configured ${chalk.bold('app.json')} for EAS Update`);
|
||||||
|
|
||||||
@@ -69,14 +78,14 @@ export default class UpdateConfigure extends EasCommand {
|
|||||||
[RequestedPlatform.Android, RequestedPlatform.All].includes(platform) &&
|
[RequestedPlatform.Android, RequestedPlatform.All].includes(platform) &&
|
||||||
androidWorkflow === Workflow.GENERIC
|
androidWorkflow === Workflow.GENERIC
|
||||||
) {
|
) {
|
||||||
await syncAndroidUpdatesConfigurationAsync(projectDir, updatedExp, { nonInteractive: true });
|
await syncAndroidUpdatesConfigurationAsync(projectDir, updatedExp, projectId);
|
||||||
Log.withTick(`Configured ${chalk.bold('AndroidManifest.xml')} for EAS Update`);
|
Log.withTick(`Configured ${chalk.bold('AndroidManifest.xml')} for EAS Update`);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
[RequestedPlatform.Ios, RequestedPlatform.All].includes(platform) &&
|
[RequestedPlatform.Ios, RequestedPlatform.All].includes(platform) &&
|
||||||
iosWorkflow === Workflow.GENERIC
|
iosWorkflow === Workflow.GENERIC
|
||||||
) {
|
) {
|
||||||
await syncIosUpdatesConfigurationAsync(projectDir, updatedExp, { nonInteractive: true });
|
await syncIosUpdatesConfigurationAsync(projectDir, updatedExp, projectId);
|
||||||
Log.withTick(`Configured ${chalk.bold('Expo.plist')} for EAS Update`);
|
Log.withTick(`Configured ${chalk.bold('Expo.plist')} for EAS Update`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +99,7 @@ async function configureAppJSONForEASUpdateAsync({
|
|||||||
exp,
|
exp,
|
||||||
platform,
|
platform,
|
||||||
workflows,
|
workflows,
|
||||||
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
@@ -97,9 +107,9 @@ async function configureAppJSONForEASUpdateAsync({
|
|||||||
workflows: {
|
workflows: {
|
||||||
[key in RequestedPlatform.Android | RequestedPlatform.Ios]: Workflow;
|
[key in RequestedPlatform.Android | RequestedPlatform.Ios]: Workflow;
|
||||||
};
|
};
|
||||||
|
projectId: string;
|
||||||
}): Promise<ExpoConfig> {
|
}): Promise<ExpoConfig> {
|
||||||
// this command is non-interactive in the way it was designed
|
// this command is non-interactive in the way it was designed
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
const easUpdateURL = getEASUpdateURL(projectId);
|
const easUpdateURL = getEASUpdateURL(projectId);
|
||||||
const updates = { ...exp.updates, url: easUpdateURL };
|
const updates = { ...exp.updates, url: easUpdateURL };
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import { getEASUpdateURL } from '../../api';
|
|||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||||
import { BranchNotFoundError, getDefaultBranchNameAsync } from '../../branch/utils';
|
import { BranchNotFoundError, getDefaultBranchNameAsync } from '../../branch/utils';
|
||||||
import { getUpdateGroupUrl } from '../../build/utils/url';
|
import { getUpdateGroupUrl } from '../../build/utils/url';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, {
|
||||||
|
EASCommandProjectDirContext,
|
||||||
|
EASCommandProjectIdContext,
|
||||||
|
} from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||||
import fetch from '../../fetch';
|
import fetch from '../../fetch';
|
||||||
@@ -29,9 +32,7 @@ import Log, { learnMore, link } from '../../log';
|
|||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getExpoConfig } from '../../project/expoConfig';
|
||||||
import {
|
import {
|
||||||
findProjectRootAsync,
|
|
||||||
getOwnerAccountForProjectIdAsync,
|
getOwnerAccountForProjectIdAsync,
|
||||||
getProjectIdAsync,
|
|
||||||
installExpoUpdatesAsync,
|
installExpoUpdatesAsync,
|
||||||
isExpoUpdatesInstalledOrAvailable,
|
isExpoUpdatesInstalledOrAvailable,
|
||||||
} from '../../project/projectUtils';
|
} from '../../project/projectUtils';
|
||||||
@@ -169,6 +170,11 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
...EASCommandProjectDirContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(UpdatePublish);
|
const { flags } = await this.parse(UpdatePublish);
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
@@ -185,6 +191,9 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
'non-interactive': nonInteractive,
|
'non-interactive': nonInteractive,
|
||||||
json: jsonFlag,
|
json: jsonFlag,
|
||||||
} = flags;
|
} = flags;
|
||||||
|
const { projectId, projectDir } = await this.getContextAsync(UpdatePublish, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
|
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
@@ -194,7 +203,6 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
// If a group was specified, that means we are republishing it.
|
// If a group was specified, that means we are republishing it.
|
||||||
republish = republish || !!group;
|
republish = republish || !!group;
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir, {
|
const exp = getExpoConfig(projectDir, {
|
||||||
isPublicConfig: true,
|
isPublicConfig: true,
|
||||||
});
|
});
|
||||||
@@ -234,7 +242,6 @@ export default class UpdatePublish extends EasCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
await checkEASUpdateURLIsSetAsync(exp, projectId);
|
await checkEASUpdateURLIsSetAsync(exp, projectId);
|
||||||
|
|
||||||
if (!branchName) {
|
if (!branchName) {
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
import { selectBranchOnAppAsync } from '../../branch/queries';
|
import { selectBranchOnAppAsync } from '../../branch/queries';
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||||
import {
|
import {
|
||||||
EasPaginatedQueryFlags,
|
EasPaginatedQueryFlags,
|
||||||
getLimitFlagWithCustomValues,
|
getLimitFlagWithCustomValues,
|
||||||
getPaginatedQueryOptions,
|
getPaginatedQueryOptions,
|
||||||
} from '../../commandUtils/pagination';
|
} from '../../commandUtils/pagination';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import {
|
import {
|
||||||
listAndRenderUpdateGroupsOnAppAsync,
|
listAndRenderUpdateGroupsOnAppAsync,
|
||||||
listAndRenderUpdateGroupsOnBranchAsync,
|
listAndRenderUpdateGroupsOnBranchAsync,
|
||||||
@@ -34,19 +32,22 @@ export default class UpdateList extends EasCommand {
|
|||||||
...EasNonInteractiveAndJsonFlags,
|
...EasNonInteractiveAndJsonFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(UpdateList);
|
const { flags } = await this.parse(UpdateList);
|
||||||
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||||
|
const { projectId } = await this.getContextAsync(UpdateList, {
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||||
|
|
||||||
if (jsonFlag) {
|
if (jsonFlag) {
|
||||||
enableJsonOutput();
|
enableJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
if (all) {
|
if (all) {
|
||||||
listAndRenderUpdateGroupsOnAppAsync({ projectId, paginatedQueryOptions });
|
listAndRenderUpdateGroupsOnAppAsync({ projectId, paginatedQueryOptions });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||||
import { WebhookType } from '../../graphql/generated';
|
import { WebhookType } from '../../graphql/generated';
|
||||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { prepareInputParamsAsync } from '../../webhooks/input';
|
import { prepareInputParamsAsync } from '../../webhooks/input';
|
||||||
|
|
||||||
export default class WebhookCreate extends EasCommand {
|
export default class WebhookCreate extends EasCommand {
|
||||||
@@ -27,15 +25,17 @@ export default class WebhookCreate extends EasCommand {
|
|||||||
...EASNonInteractiveFlag,
|
...EASNonInteractiveFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const { flags } = await this.parse(WebhookCreate);
|
const { flags } = await this.parse(WebhookCreate);
|
||||||
|
const { projectId } = await this.getContextAsync(WebhookCreate, {
|
||||||
|
nonInteractive: flags['non-interactive'],
|
||||||
|
});
|
||||||
const webhookInputParams = await prepareInputParamsAsync(flags);
|
const webhookInputParams = await prepareInputParamsAsync(flags);
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: flags['non-interactive'] });
|
|
||||||
|
|
||||||
const spinner = ora('Creating a webhook').start();
|
const spinner = ora('Creating a webhook').start();
|
||||||
try {
|
try {
|
||||||
await WebhookMutation.createWebhookAsync(projectId, webhookInputParams);
|
await WebhookMutation.createWebhookAsync(projectId, webhookInputParams);
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ import assert from 'assert';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import nullthrows from 'nullthrows';
|
import nullthrows from 'nullthrows';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||||
import { WebhookFragment } from '../../graphql/generated';
|
import { WebhookFragment } from '../../graphql/generated';
|
||||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||||
|
|
||||||
@@ -29,15 +27,18 @@ export default class WebhookDelete extends EasCommand {
|
|||||||
...EASNonInteractiveFlag,
|
...EASNonInteractiveFlag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
let {
|
let {
|
||||||
args: { ID: webhookId },
|
args: { ID: webhookId },
|
||||||
flags: { 'non-interactive': nonInteractive },
|
flags: { 'non-interactive': nonInteractive },
|
||||||
} = await this.parse(WebhookDelete);
|
} = await this.parse(WebhookDelete);
|
||||||
|
const { projectId } = await this.getContextAsync(WebhookDelete, {
|
||||||
const projectDir = await findProjectRootAsync();
|
nonInteractive,
|
||||||
const exp = getExpoConfig(projectDir);
|
});
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
|
|
||||||
let webhook: WebhookFragment | undefined =
|
let webhook: WebhookFragment | undefined =
|
||||||
webhookId && (await WebhookQuery.byIdAsync(webhookId));
|
webhookId && (await WebhookQuery.byIdAsync(webhookId));
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { Flags } from '@oclif/core';
|
import { Flags } from '@oclif/core';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import EasCommand from '../../commandUtils/EasCommand';
|
import EasCommand, { EASCommandProjectIdContext } from '../../commandUtils/EasCommand';
|
||||||
import { WebhookType } from '../../graphql/generated';
|
import { WebhookType } from '../../graphql/generated';
|
||||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { ora } from '../../ora';
|
import { ora } from '../../ora';
|
||||||
import { getExpoConfig } from '../../project/expoConfig';
|
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import {
|
|
||||||
findProjectRootAsync,
|
|
||||||
getDisplayNameForProjectIdAsync,
|
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../project/projectUtils';
|
|
||||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||||
|
|
||||||
export default class WebhookList extends EasCommand {
|
export default class WebhookList extends EasCommand {
|
||||||
@@ -24,16 +19,18 @@ export default class WebhookList extends EasCommand {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static override contextDefinition = {
|
||||||
|
...EASCommandProjectIdContext,
|
||||||
|
};
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const {
|
const {
|
||||||
flags: { event },
|
flags: { event },
|
||||||
} = await this.parse(WebhookList);
|
} = await this.parse(WebhookList);
|
||||||
|
const { projectId } = await this.getContextAsync(WebhookList, {
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync();
|
|
||||||
const exp = getExpoConfig(projectDir);
|
|
||||||
|
|
||||||
// this command is non-interactive by design
|
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
|
||||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const spinner = ora(`Fetching the list of webhook on project ${projectDisplayName}`).start();
|
const spinner = ora(`Fetching the list of webhook on project ${projectDisplayName}`).start();
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { GradleBuildContext } from '../../../project/android/gradle';
|
|||||||
import {
|
import {
|
||||||
getOwnerAccountForProjectIdAsync,
|
getOwnerAccountForProjectIdAsync,
|
||||||
getProjectConfigDescription,
|
getProjectConfigDescription,
|
||||||
getProjectIdAsync,
|
|
||||||
} from '../../../project/projectUtils';
|
} from '../../../project/projectUtils';
|
||||||
import { promptAsync } from '../../../prompts';
|
import { promptAsync } from '../../../prompts';
|
||||||
import { CredentialsContext } from '../../context';
|
import { CredentialsContext } from '../../context';
|
||||||
@@ -91,9 +90,8 @@ export async function getAppLookupParamsFromContextAsync(
|
|||||||
ctx: CredentialsContext,
|
ctx: CredentialsContext,
|
||||||
gradleContext?: GradleBuildContext
|
gradleContext?: GradleBuildContext
|
||||||
): Promise<AppLookupParams> {
|
): Promise<AppLookupParams> {
|
||||||
ctx.ensureProjectContext();
|
|
||||||
const projectName = ctx.exp.slug;
|
const projectName = ctx.exp.slug;
|
||||||
const projectId = await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
const projectId = ctx.projectId;
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
|
|
||||||
const androidApplicationIdentifier = await getApplicationIdAsync(
|
const androidApplicationIdentifier = await getApplicationIdAsync(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AccountFragment, AndroidKeystoreFragment } from '../../../graphql/generated';
|
import { AccountFragment, AndroidKeystoreFragment } from '../../../graphql/generated';
|
||||||
import Log from '../../../log';
|
import Log from '../../../log';
|
||||||
import { getProjectIdAsync } from '../../../project/projectUtils';
|
|
||||||
import { CredentialsContext } from '../../context';
|
import { CredentialsContext } from '../../context';
|
||||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||||
import { KeystoreWithType, keystoreSchema } from '../credentials';
|
import { KeystoreWithType, keystoreSchema } from '../credentials';
|
||||||
@@ -15,7 +14,7 @@ export class CreateKeystore {
|
|||||||
throw new Error(`New keystore cannot be created in non-interactive mode.`);
|
throw new Error(`New keystore cannot be created in non-interactive mode.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectId = await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
const projectId = ctx.projectId;
|
||||||
const keystore = await this.provideOrGenerateAsync(projectId);
|
const keystore = await this.provideOrGenerateAsync(projectId);
|
||||||
const keystoreFragment = await ctx.android.createKeystoreAsync(this.account, keystore);
|
const keystoreFragment = await ctx.android.createKeystoreAsync(this.account, keystore);
|
||||||
Log.succeed('Created keystore');
|
Log.succeed('Created keystore');
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { EasJson } from '@expo/eas-json';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { getExpoConfig } from '../project/expoConfig';
|
|
||||||
import { confirmAsync } from '../prompts';
|
import { confirmAsync } from '../prompts';
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
import * as AndroidGraphqlClient from './android/api/GraphqlClient';
|
import * as AndroidGraphqlClient from './android/api/GraphqlClient';
|
||||||
@@ -12,6 +11,11 @@ import * as IosGraphqlClient from './ios/api/GraphqlClient';
|
|||||||
import AppStoreApi from './ios/appstore/AppStoreApi';
|
import AppStoreApi from './ios/appstore/AppStoreApi';
|
||||||
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
|
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
|
||||||
|
|
||||||
|
export type CredentialsContextProjectInfo = {
|
||||||
|
exp: ExpoConfig;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class CredentialsContext {
|
export class CredentialsContext {
|
||||||
public readonly android = AndroidGraphqlClient;
|
public readonly android = AndroidGraphqlClient;
|
||||||
public readonly appStore = new AppStoreApi();
|
public readonly appStore = new AppStoreApi();
|
||||||
@@ -22,58 +26,30 @@ export class CredentialsContext {
|
|||||||
public readonly easJsonCliConfig?: EasJson['cli'];
|
public readonly easJsonCliConfig?: EasJson['cli'];
|
||||||
|
|
||||||
private shouldAskAuthenticateAppStore: boolean = true;
|
private shouldAskAuthenticateAppStore: boolean = true;
|
||||||
private resolvedExp?: ExpoConfig;
|
|
||||||
|
|
||||||
constructor(
|
private projectInfo: CredentialsContextProjectInfo;
|
||||||
private options: {
|
|
||||||
exp?: ExpoConfig;
|
constructor(options: {
|
||||||
|
projectInfo: CredentialsContextProjectInfo;
|
||||||
easJsonCliConfig?: EasJson['cli'];
|
easJsonCliConfig?: EasJson['cli'];
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
projectDir: string;
|
projectDir: string;
|
||||||
user: Actor;
|
user: Actor;
|
||||||
env?: Env;
|
env?: Env;
|
||||||
}
|
}) {
|
||||||
) {
|
|
||||||
this.easJsonCliConfig = options.easJsonCliConfig;
|
this.easJsonCliConfig = options.easJsonCliConfig;
|
||||||
this.projectDir = options.projectDir;
|
this.projectDir = options.projectDir;
|
||||||
this.user = options.user;
|
this.user = options.user;
|
||||||
this.nonInteractive = options.nonInteractive ?? false;
|
this.nonInteractive = options.nonInteractive ?? false;
|
||||||
|
this.projectInfo = options.projectInfo;
|
||||||
this.resolvedExp = options.exp;
|
|
||||||
if (!this.resolvedExp) {
|
|
||||||
this.resolvedExp =
|
|
||||||
CredentialsContext.getExpoConfigInProject(this.projectDir, { env: options.env }) ??
|
|
||||||
undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getExpoConfigInProject(
|
|
||||||
projectDir: string,
|
|
||||||
{ env }: { env?: Env } = {}
|
|
||||||
): ExpoConfig | null {
|
|
||||||
try {
|
|
||||||
return getExpoConfig(projectDir, { env });
|
|
||||||
} catch {
|
|
||||||
// ignore error, context might be created outside of expo project
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasProjectContext(): boolean {
|
|
||||||
return !!this.resolvedExp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get exp(): ExpoConfig {
|
get exp(): ExpoConfig {
|
||||||
this.ensureProjectContext();
|
return this.projectInfo!.exp;
|
||||||
return this.resolvedExp!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ensureProjectContext(): void {
|
get projectId(): string {
|
||||||
if (this.hasProjectContext) {
|
return this.projectInfo!.projectId;
|
||||||
return;
|
|
||||||
}
|
|
||||||
// trigger getConfig error
|
|
||||||
getExpoConfig(this.options.projectDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
|
async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
IosDistributionType as GraphQLIosDistributionType,
|
IosDistributionType as GraphQLIosDistributionType,
|
||||||
IosAppBuildCredentialsFragment,
|
IosAppBuildCredentialsFragment,
|
||||||
} from '../../../graphql/generated';
|
} from '../../../graphql/generated';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils';
|
||||||
import { CredentialsContext } from '../../context';
|
import { CredentialsContext } from '../../context';
|
||||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||||
import { App, Target } from '../types';
|
import { App, Target } from '../types';
|
||||||
@@ -82,9 +82,8 @@ export async function assignBuildCredentialsAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getAppFromContextAsync(ctx: CredentialsContext): Promise<App> {
|
export async function getAppFromContextAsync(ctx: CredentialsContext): Promise<App> {
|
||||||
ctx.ensureProjectContext();
|
|
||||||
const projectName = ctx.exp.slug;
|
const projectName = ctx.exp.slug;
|
||||||
const projectId = await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
const projectId = ctx.projectId;
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import Log from '../../log';
|
import Log from '../../log';
|
||||||
import { pressAnyKeyToContinueAsync } from '../../prompts';
|
import { pressAnyKeyToContinueAsync } from '../../prompts';
|
||||||
import { CredentialsContext } from '../context';
|
import { Actor } from '../../user/User';
|
||||||
|
import { CredentialsContext, CredentialsContextProjectInfo } from '../context';
|
||||||
|
|
||||||
export interface Action<T = void> {
|
export interface Action<T = void> {
|
||||||
|
actor: Actor;
|
||||||
|
projectInfo: CredentialsContextProjectInfo;
|
||||||
runAsync(ctx: CredentialsContext): Promise<T>;
|
runAsync(ctx: CredentialsContext): Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CredentialsContext } from '../context';
|
|
||||||
import { ActionInfo, IosActionType, Scope } from './Actions';
|
import { ActionInfo, IosActionType, Scope } from './Actions';
|
||||||
|
|
||||||
export const highLevelActions: ActionInfo[] = [
|
export const highLevelActions: ActionInfo[] = [
|
||||||
@@ -47,7 +46,7 @@ export const credentialsJsonActions: ActionInfo[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
export function getPushKeyActions(): ActionInfo[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
value: IosActionType.SetUpPushKey,
|
value: IosActionType.SetUpPushKey,
|
||||||
@@ -57,7 +56,7 @@ export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
|||||||
{
|
{
|
||||||
value: IosActionType.CreatePushKey,
|
value: IosActionType.CreatePushKey,
|
||||||
title: 'Add a new push key',
|
title: 'Add a new push key',
|
||||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.UseExistingPushKey,
|
value: IosActionType.UseExistingPushKey,
|
||||||
@@ -77,7 +76,7 @@ export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
export function getAscApiKeyActions(): ActionInfo[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
value: IosActionType.SetUpAscApiKeyForSubmissions,
|
value: IosActionType.SetUpAscApiKeyForSubmissions,
|
||||||
@@ -92,7 +91,7 @@ export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
|||||||
{
|
{
|
||||||
value: IosActionType.CreateAscApiKeyForSubmissions,
|
value: IosActionType.CreateAscApiKeyForSubmissions,
|
||||||
title: 'Add a new API Key For EAS Submit',
|
title: 'Add a new API Key For EAS Submit',
|
||||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.RemoveAscApiKey,
|
value: IosActionType.RemoveAscApiKey,
|
||||||
@@ -107,7 +106,7 @@ export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBuildCredentialsActions(ctx: CredentialsContext): ActionInfo[] {
|
export function getBuildCredentialsActions(): ActionInfo[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
// This command will be triggered during build to ensure all credentials are ready
|
// This command will be triggered during build to ensure all credentials are ready
|
||||||
@@ -124,7 +123,7 @@ export function getBuildCredentialsActions(ctx: CredentialsContext): ActionInfo[
|
|||||||
{
|
{
|
||||||
value: IosActionType.CreateDistributionCertificate,
|
value: IosActionType.CreateDistributionCertificate,
|
||||||
title: `Distribution Certificate: Add a new one to your account`,
|
title: `Distribution Certificate: Add a new one to your account`,
|
||||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.RemoveDistributionCertificate,
|
value: IosActionType.RemoveDistributionCertificate,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import assert from 'assert';
|
|||||||
|
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { GradleBuildContext, resolveGradleBuildContextAsync } from '../../project/android/gradle';
|
import { GradleBuildContext, resolveGradleBuildContextAsync } from '../../project/android/gradle';
|
||||||
import { getProjectIdAsync } from '../../project/projectUtils';
|
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
import { AssignFcm } from '../android/actions/AssignFcm';
|
import { AssignFcm } from '../android/actions/AssignFcm';
|
||||||
import { AssignGoogleServiceAccountKey } from '../android/actions/AssignGoogleServiceAccountKey';
|
import { AssignGoogleServiceAccountKey } from '../android/actions/AssignGoogleServiceAccountKey';
|
||||||
import {
|
import {
|
||||||
@@ -50,27 +48,23 @@ export class ManageAndroid {
|
|||||||
constructor(private callingAction: Action, private projectDir: string) {}
|
constructor(private callingAction: Action, private projectDir: string) {}
|
||||||
|
|
||||||
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
||||||
const hasProjectContext = !!CredentialsContext.getExpoConfigInProject(this.projectDir);
|
const hasProjectContext = !!this.callingAction.projectInfo;
|
||||||
const buildProfile = hasProjectContext
|
const buildProfile = hasProjectContext
|
||||||
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.ANDROID).runAsync()
|
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.ANDROID).runAsync()
|
||||||
: null;
|
: null;
|
||||||
const ctx = new CredentialsContext({
|
const ctx = new CredentialsContext({
|
||||||
projectDir: process.cwd(),
|
projectDir: process.cwd(),
|
||||||
// this command is interactive by design
|
projectInfo: this.callingAction.projectInfo,
|
||||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
user: this.callingAction.actor,
|
||||||
env: buildProfile?.env,
|
env: buildProfile?.env,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let gradleContext;
|
|
||||||
if (ctx.hasProjectContext) {
|
|
||||||
assert(buildProfile, 'buildProfile must be defined in a project context');
|
assert(buildProfile, 'buildProfile must be defined in a project context');
|
||||||
gradleContext = await this.createProjectContextAsync(ctx, buildProfile);
|
const gradleContext = await this.createProjectContextAsync(ctx, buildProfile);
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
if (ctx.hasProjectContext) {
|
|
||||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
|
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
|
||||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
||||||
appLookupParams
|
appLookupParams
|
||||||
@@ -82,14 +76,11 @@ export class ManageAndroid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copy legacy credentials if user is new to EAS and has legacy credentials
|
// copy legacy credentials if user is new to EAS and has legacy credentials
|
||||||
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(
|
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||||
ctx,
|
|
||||||
appLookupParams
|
|
||||||
);
|
|
||||||
if (canCopyLegacyCredentials) {
|
if (canCopyLegacyCredentials) {
|
||||||
await promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
await promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const { action: chosenAction } = await promptAsync({
|
const { action: chosenAction } = await promptAsync({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'action',
|
name: 'action',
|
||||||
@@ -140,11 +131,6 @@ export class ManageAndroid {
|
|||||||
ctx: CredentialsContext,
|
ctx: CredentialsContext,
|
||||||
buildProfile: BuildProfile<Platform.ANDROID>
|
buildProfile: BuildProfile<Platform.ANDROID>
|
||||||
): Promise<GradleBuildContext | undefined> {
|
): Promise<GradleBuildContext | undefined> {
|
||||||
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
|
|
||||||
|
|
||||||
// ensure the project exists on the EAS server
|
|
||||||
await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
|
||||||
|
|
||||||
return await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
return await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,10 +139,6 @@ export class ManageAndroid {
|
|||||||
action: AndroidActionType,
|
action: AndroidActionType,
|
||||||
gradleContext?: GradleBuildContext
|
gradleContext?: GradleBuildContext
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
assert(
|
|
||||||
ctx.hasProjectContext,
|
|
||||||
'You must be in your project directory in order to perform this action'
|
|
||||||
);
|
|
||||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
|
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx, gradleContext);
|
||||||
if (action === AndroidActionType.CreateKeystore) {
|
if (action === AndroidActionType.CreateKeystore) {
|
||||||
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
|
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import {
|
|||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
|
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
|
||||||
import { ensureActorHasPrimaryAccount, ensureLoggedInAsync } from '../../user/actions';
|
|
||||||
import { CredentialsContext } from '../context';
|
import { CredentialsContext } from '../context';
|
||||||
import {
|
import {
|
||||||
AppStoreApiKeyPurpose,
|
AppStoreApiKeyPurpose,
|
||||||
@@ -58,44 +57,33 @@ export class ManageIos {
|
|||||||
constructor(private callingAction: Action, private projectDir: string) {}
|
constructor(private callingAction: Action, private projectDir: string) {}
|
||||||
|
|
||||||
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
async runAsync(currentActions: ActionInfo[] = highLevelActions): Promise<void> {
|
||||||
const hasProjectContext = !!CredentialsContext.getExpoConfigInProject(this.projectDir);
|
const buildProfile = this.callingAction.projectInfo
|
||||||
const buildProfile = hasProjectContext
|
|
||||||
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.IOS).runAsync()
|
? await new SelectBuildProfileFromEasJson(this.projectDir, Platform.IOS).runAsync()
|
||||||
: null;
|
: null;
|
||||||
const ctx = new CredentialsContext({
|
const ctx = new CredentialsContext({
|
||||||
projectDir: process.cwd(),
|
projectDir: process.cwd(),
|
||||||
// this command is interactive by design
|
projectInfo: this.callingAction.projectInfo,
|
||||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
user: this.callingAction.actor,
|
||||||
env: buildProfile?.env,
|
env: buildProfile?.env,
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
});
|
});
|
||||||
const buildCredentialsActions = getBuildCredentialsActions(ctx);
|
const buildCredentialsActions = getBuildCredentialsActions();
|
||||||
const pushKeyActions = getPushKeyActions(ctx);
|
const pushKeyActions = getPushKeyActions();
|
||||||
const ascApiKeyActions = getAscApiKeyActions(ctx);
|
const ascApiKeyActions = getAscApiKeyActions();
|
||||||
|
|
||||||
await ctx.bestEffortAppStoreAuthenticateAsync();
|
await ctx.bestEffortAppStoreAuthenticateAsync();
|
||||||
|
|
||||||
const getAccountForProjectAsync = async (): Promise<AccountFragment> => {
|
const account = await getOwnerAccountForProjectIdAsync(
|
||||||
const projectId = await getProjectIdAsync(ctx.exp, { nonInteractive: false });
|
this.callingAction.projectInfo.projectId
|
||||||
return await getOwnerAccountForProjectIdAsync(projectId);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const account = ctx.hasProjectContext
|
|
||||||
? await getAccountForProjectAsync()
|
|
||||||
: ensureActorHasPrimaryAccount(ctx.user);
|
|
||||||
|
|
||||||
let app = null;
|
|
||||||
let targets = null;
|
|
||||||
if (ctx.hasProjectContext) {
|
|
||||||
assert(buildProfile, 'buildProfile must be defined in project context');
|
assert(buildProfile, 'buildProfile must be defined in project context');
|
||||||
const projectContext = await this.createProjectContextAsync(ctx, account, buildProfile);
|
const projectContext = await this.createProjectContextAsync(ctx, account, buildProfile);
|
||||||
app = projectContext.app;
|
const app = projectContext.app;
|
||||||
targets = projectContext.targets;
|
const targets = projectContext.targets;
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
if (ctx.hasProjectContext) {
|
|
||||||
assert(targets && app);
|
assert(targets && app);
|
||||||
const iosAppCredentialsMap: IosAppCredentialsMap = {};
|
const iosAppCredentialsMap: IosAppCredentialsMap = {};
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
@@ -104,7 +92,6 @@ export class ManageIos {
|
|||||||
await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(appLookupParams);
|
await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(appLookupParams);
|
||||||
}
|
}
|
||||||
displayIosCredentials(app, iosAppCredentialsMap, targets);
|
displayIosCredentials(app, iosAppCredentialsMap, targets);
|
||||||
}
|
|
||||||
|
|
||||||
const { action: chosenAction } = await promptAsync({
|
const { action: chosenAction } = await promptAsync({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
@@ -139,10 +126,6 @@ export class ManageIos {
|
|||||||
return await this.callingAction.runAsync(ctx);
|
return await this.callingAction.runAsync(ctx);
|
||||||
}
|
}
|
||||||
} else if (actionInfo.scope === Scope.Project) {
|
} else if (actionInfo.scope === Scope.Project) {
|
||||||
assert(
|
|
||||||
ctx.hasProjectContext,
|
|
||||||
'You must be in your project directory in order to perform this action'
|
|
||||||
);
|
|
||||||
await this.runProjectSpecificActionAsync(
|
await this.runProjectSpecificActionAsync(
|
||||||
ctx,
|
ctx,
|
||||||
nullthrows(app, 'app must be defined in project context'),
|
nullthrows(app, 'app must be defined in project context'),
|
||||||
@@ -174,11 +157,6 @@ export class ManageIos {
|
|||||||
app: App;
|
app: App;
|
||||||
targets: Target[];
|
targets: Target[];
|
||||||
}> {
|
}> {
|
||||||
assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.');
|
|
||||||
|
|
||||||
// ensure the project exists on the EAS server
|
|
||||||
await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
|
||||||
|
|
||||||
const app = { account, projectName: ctx.exp.slug };
|
const app = { account, projectName: ctx.exp.slug };
|
||||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { selectPlatformAsync } from '../../platform';
|
import { selectPlatformAsync } from '../../platform';
|
||||||
|
import { Actor } from '../../user/User';
|
||||||
|
import { CredentialsContextProjectInfo } from '../context';
|
||||||
import { ManageAndroid } from './ManageAndroid';
|
import { ManageAndroid } from './ManageAndroid';
|
||||||
import { ManageIos } from './ManageIos';
|
import { ManageIos } from './ManageIos';
|
||||||
|
|
||||||
export class SelectPlatform {
|
export class SelectPlatform {
|
||||||
constructor(private readonly flagPlatform?: string) {}
|
constructor(
|
||||||
|
public readonly actor: Actor,
|
||||||
|
public readonly projectInfo: CredentialsContextProjectInfo,
|
||||||
|
private readonly flagPlatform?: string
|
||||||
|
) {}
|
||||||
|
|
||||||
async runAsync(): Promise<void> {
|
async runAsync(): Promise<void> {
|
||||||
const platform = await selectPlatformAsync(this.flagPlatform);
|
const platform = await selectPlatformAsync(this.flagPlatform);
|
||||||
|
|
||||||
if (platform === 'ios') {
|
if (platform === 'ios') {
|
||||||
return await new ManageIos(new SelectPlatform(platform), process.cwd()).runAsync();
|
return await new ManageIos(this, process.cwd()).runAsync();
|
||||||
}
|
}
|
||||||
return await new ManageAndroid(new SelectPlatform(platform), process.cwd()).runAsync();
|
return await new ManageAndroid(this, process.cwd()).runAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,16 +44,6 @@ describe(AccountResolver, () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('when inside project dir', () => {
|
describe('when inside project dir', () => {
|
||||||
const exp = {
|
|
||||||
name: 'wat',
|
|
||||||
slug: 'wat',
|
|
||||||
extra: {
|
|
||||||
eas: {
|
|
||||||
projectId: '1234',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
id: 'test-project-id',
|
id: 'test-project-id',
|
||||||
@@ -72,7 +62,7 @@ describe(AccountResolver, () => {
|
|||||||
value: true,
|
value: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const resolver = new AccountResolver(exp, user);
|
const resolver = new AccountResolver('1234', user);
|
||||||
const account = await resolver.resolveAccountAsync();
|
const account = await resolver.resolveAccountAsync();
|
||||||
expect(account).toEqual({
|
expect(account).toEqual({
|
||||||
id: user.accounts[1].id,
|
id: user.accounts[1].id,
|
||||||
@@ -89,19 +79,7 @@ describe(AccountResolver, () => {
|
|||||||
account: user.accounts[0],
|
account: user.accounts[0],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const resolver = new AccountResolver(exp, user);
|
const resolver = new AccountResolver('1234', user);
|
||||||
const account = await resolver.resolveAccountAsync();
|
|
||||||
expect(account).toEqual(user.accounts[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when outside project dir', () => {
|
|
||||||
it('asks the user to choose the account from his account list', async () => {
|
|
||||||
jest.mocked(prompts).mockImplementationOnce(async () => ({
|
|
||||||
account: user.accounts[0],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const resolver = new AccountResolver(null, user);
|
|
||||||
const account = await resolver.resolveAccountAsync();
|
const account = await resolver.resolveAccountAsync();
|
||||||
expect(account).toEqual(user.accounts[0]);
|
expect(account).toEqual(user.accounts[0]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,24 @@
|
|||||||
import { ExpoConfig, getConfig } from '@expo/config';
|
|
||||||
|
|
||||||
import AppStoreApi from '../credentials/ios/appstore/AppStoreApi';
|
import AppStoreApi from '../credentials/ios/appstore/AppStoreApi';
|
||||||
import { findProjectRootAsync } from '../project/projectUtils';
|
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
|
|
||||||
export interface DeviceManagerContext {
|
export interface DeviceManagerContext {
|
||||||
appStore: AppStoreApi;
|
appStore: AppStoreApi;
|
||||||
exp: ExpoConfig | null;
|
|
||||||
projectDir: string | null;
|
|
||||||
user: Actor;
|
user: Actor;
|
||||||
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createContextAsync({
|
export async function createContextAsync({
|
||||||
appStore,
|
appStore,
|
||||||
cwd,
|
|
||||||
user,
|
user,
|
||||||
|
projectId,
|
||||||
}: {
|
}: {
|
||||||
appStore: AppStoreApi;
|
appStore: AppStoreApi;
|
||||||
cwd?: string;
|
|
||||||
user: Actor;
|
user: Actor;
|
||||||
|
projectId: string;
|
||||||
}): Promise<DeviceManagerContext> {
|
}): Promise<DeviceManagerContext> {
|
||||||
const projectDir = await findProjectRootAsync({ cwd });
|
|
||||||
let exp: ExpoConfig | null = null;
|
|
||||||
if (projectDir) {
|
|
||||||
const config = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
|
||||||
exp = config.exp;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
appStore,
|
appStore,
|
||||||
projectDir,
|
|
||||||
exp,
|
|
||||||
user,
|
user,
|
||||||
|
projectId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ExpoConfig } from '@expo/config';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
@@ -6,7 +5,7 @@ import { AppleTeamMutation } from '../credentials/ios/api/graphql/mutations/Appl
|
|||||||
import { AppleTeamQuery } from '../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
import { AppleTeamQuery } from '../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
||||||
import { AccountFragment, AppleTeamFragment } from '../graphql/generated';
|
import { AccountFragment, AppleTeamFragment } from '../graphql/generated';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||||
import { Choice, confirmAsync, promptAsync } from '../prompts';
|
import { Choice, confirmAsync, promptAsync } from '../prompts';
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
import DeviceCreateAction from './actions/create/action';
|
import DeviceCreateAction from './actions/create/action';
|
||||||
@@ -38,30 +37,25 @@ export default class DeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async resolveAccountAsync(): Promise<AccountFragment> {
|
private async resolveAccountAsync(): Promise<AccountFragment> {
|
||||||
const resolver = new AccountResolver(this.ctx.exp, this.ctx.user);
|
const resolver = new AccountResolver(this.ctx.projectId, this.ctx.user);
|
||||||
return await resolver.resolveAccountAsync();
|
return await resolver.resolveAccountAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountResolver {
|
export class AccountResolver {
|
||||||
constructor(private exp: ExpoConfig | null, private user: Actor) {}
|
constructor(private projectId: string | null, private user: Actor) {}
|
||||||
|
|
||||||
public async resolveAccountAsync(): Promise<AccountFragment> {
|
public async resolveAccountAsync(): Promise<AccountFragment> {
|
||||||
if (this.exp) {
|
|
||||||
const account = await this.resolveProjectAccountAsync();
|
const account = await this.resolveProjectAccountAsync();
|
||||||
if (account) {
|
if (account) {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return await this.promptForAccountAsync();
|
return await this.promptForAccountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveProjectAccountAsync(): Promise<AccountFragment | undefined> {
|
private async resolveProjectAccountAsync(): Promise<AccountFragment | undefined> {
|
||||||
assert(this.exp, 'expo config is not set');
|
assert(this.projectId, 'expo config is not set');
|
||||||
|
const account = await getOwnerAccountForProjectIdAsync(this.projectId);
|
||||||
// this command is interactive by design
|
|
||||||
const projectId = await getProjectIdAsync(this.exp, { nonInteractive: false });
|
|
||||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
|
||||||
|
|
||||||
const useProjectAccount = await confirmAsync({
|
const useProjectAccount = await confirmAsync({
|
||||||
message: `You're inside the project directory. Would you like to use the ${chalk.underline(
|
message: `You're inside the project directory. Would you like to use the ${chalk.underline(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { getRequestContext } from '../credentials/ios/appstore/authenticate';
|
|||||||
import { getExpoConfig } from '../project/expoConfig';
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
|
||||||
|
|
||||||
export type MetadataContext = {
|
export type MetadataContext = {
|
||||||
/** Submission profile platform to use */
|
/** Submission profile platform to use */
|
||||||
@@ -55,7 +54,7 @@ export async function createMetadataContextAsync(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exp = params.exp ?? getExpoConfig(params.projectDir);
|
const exp = params.exp ?? getExpoConfig(params.projectDir);
|
||||||
const user = await ensureLoggedInAsync({ nonInteractive: params.credentialsCtx.nonInteractive });
|
const user = params.credentialsCtx.user;
|
||||||
const bundleIdentifier =
|
const bundleIdentifier =
|
||||||
submitProfile.bundleIdentifier ?? (await getBundleIdentifierAsync(params.projectDir, exp));
|
submitProfile.bundleIdentifier ?? (await getBundleIdentifierAsync(params.projectDir, exp));
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { jester } from '../../credentials/__tests__/fixtures-constants';
|
||||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||||
|
|
||||||
describe(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync, () => {
|
describe(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync, () => {
|
||||||
@@ -5,7 +6,8 @@ describe(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
|
fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
|
||||||
{ accountName: 'fake', projectName: 'fake' },
|
{ accountName: 'fake', projectName: 'fake' },
|
||||||
{ nonInteractive: true }
|
{ nonInteractive: true },
|
||||||
|
jester
|
||||||
)
|
)
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
`Must configure EAS project by running 'eas init' before this command can be run in non-interactive mode.`
|
`Must configure EAS project by running 'eas init' before this command can be run in non-interactive mode.`
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
import { getConfig, modifyConfigAsync } from '@expo/config';
|
|
||||||
import { vol } from 'memfs';
|
|
||||||
|
|
||||||
import { Role } from '../../graphql/generated';
|
|
||||||
import { Actor, getUserAsync } from '../../user/User';
|
|
||||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
|
||||||
import { findProjectRootAsync, getProjectIdAsync } from '../projectUtils';
|
|
||||||
|
|
||||||
jest.mock('@expo/config');
|
|
||||||
jest.mock('fs');
|
|
||||||
|
|
||||||
jest.mock('../fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
|
||||||
jest.mock('../../prompts');
|
|
||||||
jest.mock('../../user/User');
|
|
||||||
jest.mock('../../ora', () => ({
|
|
||||||
ora: () => ({
|
|
||||||
start: () => ({ succeed: () => {}, fail: () => {} }),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(findProjectRootAsync, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vol.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if not inside the project directory', async () => {
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'./README.md': '1',
|
|
||||||
},
|
|
||||||
'/app'
|
|
||||||
);
|
|
||||||
await expect(findProjectRootAsync({ cwd: '/app' })).rejects.toThrow(
|
|
||||||
'Run this command inside a project directory.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('defaults to process.cwd() if defaultToProcessCwd = true', async () => {
|
|
||||||
const spy = jest.spyOn(process, 'cwd').mockReturnValue('/this/is/fake/process/cwd');
|
|
||||||
try {
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'./README.md': '1',
|
|
||||||
},
|
|
||||||
'/app'
|
|
||||||
);
|
|
||||||
const projectDir = await findProjectRootAsync({ cwd: '/app', defaultToProcessCwd: true });
|
|
||||||
expect(projectDir).toBe('/this/is/fake/process/cwd');
|
|
||||||
} finally {
|
|
||||||
spy.mockRestore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the root directory of the project', async () => {
|
|
||||||
vol.fromJSON(
|
|
||||||
{
|
|
||||||
'./README.md': '1',
|
|
||||||
'./package.json': '2',
|
|
||||||
'./src/index.ts': '3',
|
|
||||||
},
|
|
||||||
'/app'
|
|
||||||
);
|
|
||||||
const projectRoot = await findProjectRootAsync({ cwd: '/app/src' });
|
|
||||||
expect(projectRoot).toBe('/app');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(getProjectIdAsync, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.mocked(getUserAsync).mockImplementation(
|
|
||||||
async (): Promise<Actor> => ({
|
|
||||||
__typename: 'User',
|
|
||||||
id: 'user_id',
|
|
||||||
username: 'notnotbrent',
|
|
||||||
primaryAccount: {
|
|
||||||
id: 'account_id_1',
|
|
||||||
name: 'notnotbrent',
|
|
||||||
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
|
|
||||||
},
|
|
||||||
accounts: [
|
|
||||||
{
|
|
||||||
id: 'account_id_1',
|
|
||||||
name: 'notnotbrent',
|
|
||||||
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'account_id_2',
|
|
||||||
name: 'dominik',
|
|
||||||
users: [{ role: Role.ViewOnly, actor: { id: 'user_id' } }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isExpoAdmin: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets the project ID from app config if exists', async () => {
|
|
||||||
await expect(
|
|
||||||
getProjectIdAsync(
|
|
||||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
|
||||||
{ nonInteractive: false }
|
|
||||||
)
|
|
||||||
).resolves.toEqual('1234');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
|
||||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
|
||||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
|
||||||
type: 'success',
|
|
||||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
|
||||||
});
|
|
||||||
jest
|
|
||||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
|
||||||
.mockImplementation(async () => '2345');
|
|
||||||
|
|
||||||
const projectId = await getProjectIdAsync(
|
|
||||||
{ name: 'test', slug: 'test' },
|
|
||||||
{ nonInteractive: false },
|
|
||||||
{ cwd: '/app' }
|
|
||||||
);
|
|
||||||
expect(projectId).toBe('2345');
|
|
||||||
|
|
||||||
expect(modifyConfigAsync).toHaveBeenCalledTimes(1);
|
|
||||||
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', {
|
|
||||||
extra: { eas: { projectId: '2345' } },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if writing the ID back to the config fails', async () => {
|
|
||||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
|
||||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
|
||||||
type: 'fail',
|
|
||||||
config: null,
|
|
||||||
});
|
|
||||||
jest
|
|
||||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
|
||||||
.mockImplementation(async () => '4567');
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
getProjectIdAsync({ name: 'test', slug: 'test' }, { nonInteractive: false }, { cwd: '/app' })
|
|
||||||
).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -110,7 +110,7 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
|
|||||||
'/app'
|
'/app'
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).rejects.toThrowError(/we can't update this file programmatically/);
|
).rejects.toThrowError(/we can't update this file programmatically/);
|
||||||
});
|
});
|
||||||
it('prompts for the Android package if using app.json', async () => {
|
it('prompts for the Android package if using app.json', async () => {
|
||||||
@@ -126,7 +126,7 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).resolves.toBe('com.expo.notdominik');
|
).resolves.toBe('com.expo.notdominik');
|
||||||
expect(promptAsync).toHaveBeenCalled();
|
expect(promptAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -143,7 +143,7 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureApplicationIdIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).resolves.toBe('com.expo.notdominik');
|
).resolves.toBe('com.expo.notdominik');
|
||||||
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
||||||
expect(appJson).toMatchObject({
|
expect(appJson).toMatchObject({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import env from '../../env';
|
|||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
import { Actor } from '../../user/User';
|
||||||
import { resolveWorkflowAsync } from '../workflow';
|
import { resolveWorkflowAsync } from '../workflow';
|
||||||
import { GradleBuildContext } from './gradle';
|
import { GradleBuildContext } from './gradle';
|
||||||
import * as gradleUtils from './gradleUtils';
|
import * as gradleUtils from './gradleUtils';
|
||||||
@@ -20,7 +20,8 @@ export const INVALID_APPLICATION_ID_MESSAGE = `Invalid format of Android applica
|
|||||||
|
|
||||||
export async function ensureApplicationIdIsDefinedForManagedProjectAsync(
|
export async function ensureApplicationIdIsDefinedForManagedProjectAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||||
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
|
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
|
||||||
@@ -30,7 +31,7 @@ export async function ensureApplicationIdIsDefinedForManagedProjectAsync(
|
|||||||
moduleName: gradleUtils.DEFAULT_MODULE_NAME,
|
moduleName: gradleUtils.DEFAULT_MODULE_NAME,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return await configureApplicationIdAsync(projectDir, exp);
|
return await configureApplicationIdAsync(projectDir, exp, actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +110,11 @@ export async function getApplicationIdAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureApplicationIdAsync(projectDir: string, exp: ExpoConfig): Promise<string> {
|
async function configureApplicationIdAsync(
|
||||||
|
projectDir: string,
|
||||||
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
|
): Promise<string> {
|
||||||
const paths = getConfigFilePaths(projectDir);
|
const paths = getConfigFilePaths(projectDir);
|
||||||
// we can't automatically update app.config.js
|
// we can't automatically update app.config.js
|
||||||
if (paths.dynamicConfigPath) {
|
if (paths.dynamicConfigPath) {
|
||||||
@@ -127,7 +132,7 @@ async function configureApplicationIdAsync(projectDir: string, exp: ExpoConfig):
|
|||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(exp);
|
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(exp, actor);
|
||||||
const { packageName } = await promptAsync({
|
const { packageName } = await promptAsync({
|
||||||
name: 'packageName',
|
name: 'packageName',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -168,14 +173,17 @@ export function warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSuggestedApplicationIdAsync(exp: ExpoConfig): Promise<string | undefined> {
|
async function getSuggestedApplicationIdAsync(
|
||||||
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
|
): Promise<string | undefined> {
|
||||||
// Attempt to use the ios bundle id first since it's convenient to have them aligned.
|
// Attempt to use the ios bundle id first since it's convenient to have them aligned.
|
||||||
const maybeBundleId = IOSConfig.BundleIdentifier.getBundleIdentifier(exp);
|
const maybeBundleId = IOSConfig.BundleIdentifier.getBundleIdentifier(exp);
|
||||||
if (maybeBundleId && isApplicationIdValid(maybeBundleId)) {
|
if (maybeBundleId && isApplicationIdValid(maybeBundleId)) {
|
||||||
return maybeBundleId;
|
return maybeBundleId;
|
||||||
} else {
|
} else {
|
||||||
// the only callsite is heavily interactive
|
// the only callsite is heavily interactive
|
||||||
const username = getUsername(exp, await ensureLoggedInAsync({ nonInteractive: false }));
|
const username = getUsername(exp, actor);
|
||||||
// It's common to use dashes in your node project name, strip them from the suggested package name.
|
// It's common to use dashes in your node project name, strip them from the suggested package name.
|
||||||
const possibleId = `com.${username}.${exp.slug}`.split('-').join('');
|
const possibleId = `com.${username}.${exp.slug}`.split('-').join('');
|
||||||
if (isApplicationIdValid(possibleId)) {
|
if (isApplicationIdValid(possibleId)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ExpoConfig } from '@expo/config';
|
|||||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||||
import { BuildProfile } from '@expo/eas-json';
|
import { BuildProfile } from '@expo/eas-json';
|
||||||
|
|
||||||
|
import { Actor } from '../user/User';
|
||||||
import {
|
import {
|
||||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||||
getApplicationIdAsync,
|
getApplicationIdAsync,
|
||||||
@@ -16,7 +17,8 @@ export async function getApplicationIdentifierAsync(
|
|||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig,
|
exp: ExpoConfig,
|
||||||
buildProfile: BuildProfile,
|
buildProfile: BuildProfile,
|
||||||
platform: Platform
|
platform: Platform,
|
||||||
|
actor: Actor
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (platform === Platform.ANDROID) {
|
if (platform === Platform.ANDROID) {
|
||||||
const profile = buildProfile as BuildProfile<Platform.ANDROID>;
|
const profile = buildProfile as BuildProfile<Platform.ANDROID>;
|
||||||
@@ -24,7 +26,7 @@ export async function getApplicationIdentifierAsync(
|
|||||||
const gradleContext = await resolveGradleBuildContextAsync(projectDir, profile);
|
const gradleContext = await resolveGradleBuildContextAsync(projectDir, profile);
|
||||||
|
|
||||||
if (workflow === Workflow.MANAGED) {
|
if (workflow === Workflow.MANAGED) {
|
||||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(projectDir, exp);
|
await ensureApplicationIdIsDefinedForManagedProjectAsync(projectDir, exp, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getApplicationIdAsync(projectDir, exp, gradleContext);
|
return await getApplicationIdAsync(projectDir, exp, gradleContext);
|
||||||
|
|||||||
+3
-3
@@ -7,7 +7,7 @@ import { AppMutation } from '../graphql/mutations/AppMutation';
|
|||||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||||
import { ora } from '../ora';
|
import { ora } from '../ora';
|
||||||
import { confirmAsync } from '../prompts';
|
import { confirmAsync } from '../prompts';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
import { Actor } from '../user/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. Looks for an existing project on EAS servers. If found, ask the user whether this is the
|
* 1. Looks for an existing project on EAS servers. If found, ask the user whether this is the
|
||||||
@@ -23,7 +23,8 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
|||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
}
|
},
|
||||||
|
actor: Actor
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { accountName, projectName } = projectInfo;
|
const { accountName, projectName } = projectInfo;
|
||||||
const projectFullName = `@${accountName}/${projectName}`;
|
const projectFullName = `@${accountName}/${projectName}`;
|
||||||
@@ -34,7 +35,6 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const actor = await ensureLoggedInAsync({ nonInteractive: options.nonInteractive });
|
|
||||||
const allAccounts = actor.accounts;
|
const allAccounts = actor.accounts;
|
||||||
const accountNamesWhereUserHasSufficientPublishPermissions = new Set(
|
const accountNamesWhereUserHasSufficientPublishPermissions = new Set(
|
||||||
allAccounts
|
allAccounts
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
|
|||||||
'/app'
|
'/app'
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).rejects.toThrowError(/we can't update this file programmatically/);
|
).rejects.toThrowError(/we can't update this file programmatically/);
|
||||||
});
|
});
|
||||||
it('prompts for the bundle identifier if using app.json', async () => {
|
it('prompts for the bundle identifier if using app.json', async () => {
|
||||||
@@ -109,7 +109,7 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).resolves.toBe('com.expo.notdominik');
|
).resolves.toBe('com.expo.notdominik');
|
||||||
expect(promptAsync).toHaveBeenCalled();
|
expect(promptAsync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -126,7 +126,7 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any)
|
ensureBundleIdentifierIsDefinedForManagedProjectAsync('/app', {} as any, mockJester)
|
||||||
).resolves.toBe('com.expo.notdominik');
|
).resolves.toBe('com.expo.notdominik');
|
||||||
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
||||||
expect(appJson).toMatchObject({
|
expect(appJson).toMatchObject({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import fs from 'fs-extra';
|
|||||||
import { readAppJson } from '../../build/utils/appJson';
|
import { readAppJson } from '../../build/utils/appJson';
|
||||||
import Log, { learnMore } from '../../log';
|
import Log, { learnMore } from '../../log';
|
||||||
import { promptAsync } from '../../prompts';
|
import { promptAsync } from '../../prompts';
|
||||||
import { ensureLoggedInAsync } from '../../user/actions';
|
import { Actor } from '../../user/User';
|
||||||
import { getProjectConfigDescription, getUsername } from '../projectUtils';
|
import { getProjectConfigDescription, getUsername } from '../projectUtils';
|
||||||
import { resolveWorkflowAsync } from '../workflow';
|
import { resolveWorkflowAsync } from '../workflow';
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@ export const INVALID_BUNDLE_IDENTIFIER_MESSAGE = `Invalid format of iOS bundle i
|
|||||||
|
|
||||||
export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||||
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
|
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
|
||||||
@@ -24,7 +25,7 @@ export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
|||||||
try {
|
try {
|
||||||
return await getBundleIdentifierAsync(projectDir, exp);
|
return await getBundleIdentifierAsync(projectDir, exp);
|
||||||
} catch {
|
} catch {
|
||||||
return await configureBundleIdentifierAsync(projectDir, exp);
|
return await configureBundleIdentifierAsync(projectDir, exp, actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,8 @@ export async function getBundleIdentifierAsync(
|
|||||||
|
|
||||||
async function configureBundleIdentifierAsync(
|
async function configureBundleIdentifierAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const paths = getConfigFilePaths(projectDir);
|
const paths = getConfigFilePaths(projectDir);
|
||||||
// we can't automatically update app.config.js
|
// we can't automatically update app.config.js
|
||||||
@@ -120,7 +122,7 @@ async function configureBundleIdentifierAsync(
|
|||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(exp);
|
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(exp, actor);
|
||||||
const { bundleIdentifier } = await promptAsync({
|
const { bundleIdentifier } = await promptAsync({
|
||||||
name: 'bundleIdentifier',
|
name: 'bundleIdentifier',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -166,14 +168,17 @@ export function isWildcardBundleIdentifier(bundleIdentifier: string): boolean {
|
|||||||
return wildcardRegex.test(bundleIdentifier);
|
return wildcardRegex.test(bundleIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSuggestedBundleIdentifierAsync(exp: ExpoConfig): Promise<string | undefined> {
|
async function getSuggestedBundleIdentifierAsync(
|
||||||
|
exp: ExpoConfig,
|
||||||
|
actor: Actor
|
||||||
|
): Promise<string | undefined> {
|
||||||
// Attempt to use the android package name first since it's convenient to have them aligned.
|
// Attempt to use the android package name first since it's convenient to have them aligned.
|
||||||
const maybeAndroidPackage = AndroidConfig.Package.getPackage(exp);
|
const maybeAndroidPackage = AndroidConfig.Package.getPackage(exp);
|
||||||
if (maybeAndroidPackage && isBundleIdentifierValid(maybeAndroidPackage)) {
|
if (maybeAndroidPackage && isBundleIdentifierValid(maybeAndroidPackage)) {
|
||||||
return maybeAndroidPackage;
|
return maybeAndroidPackage;
|
||||||
} else {
|
} else {
|
||||||
// the only callsite is heavily interactive
|
// the only callsite is heavily interactive
|
||||||
const username = getUsername(exp, await ensureLoggedInAsync({ nonInteractive: false }));
|
const username = getUsername(exp, actor);
|
||||||
// It's common to use dashes in your node project name, strip them from the suggested package name.
|
// It's common to use dashes in your node project name, strip them from the suggested package name.
|
||||||
const possibleId = `com.${username}.${exp.slug}`.split('-').join('');
|
const possibleId = `com.${username}.${exp.slug}`.split('-').join('');
|
||||||
if (isBundleIdentifierValid(possibleId)) {
|
if (isBundleIdentifierValid(possibleId)) {
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
import { ExpoConfig, getConfigFilePaths, getPackageJson, modifyConfigAsync } from '@expo/config';
|
import { ExpoConfig, getConfigFilePaths, getPackageJson } from '@expo/config';
|
||||||
import { Env } from '@expo/eas-build-job';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import pkgDir from 'pkg-dir';
|
|
||||||
import resolveFrom from 'resolve-from';
|
import resolveFrom from 'resolve-from';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
import { AccountFragment, AppPrivacy } from '../graphql/generated';
|
import { AccountFragment, AppPrivacy } from '../graphql/generated';
|
||||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { ora } from '../ora';
|
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
|
||||||
import { expoCommandAsync } from '../utils/expoCli';
|
import { expoCommandAsync } from '../utils/expoCli';
|
||||||
import { getVcsClient } from '../vcs';
|
|
||||||
import { getExpoConfig } from './expoConfig';
|
|
||||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from './fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
|
||||||
|
|
||||||
export function getUsername(exp: ExpoConfig, user: Actor): string | undefined {
|
export function getUsername(exp: ExpoConfig, user: Actor): string | undefined {
|
||||||
switch (user.__typename) {
|
switch (user.__typename) {
|
||||||
@@ -34,144 +27,6 @@ export function getUsername(exp: ExpoConfig, user: Actor): string | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findProjectRootAsync({
|
|
||||||
cwd,
|
|
||||||
defaultToProcessCwd = false,
|
|
||||||
}: {
|
|
||||||
cwd?: string;
|
|
||||||
defaultToProcessCwd?: boolean;
|
|
||||||
} = {}): Promise<string> {
|
|
||||||
const projectRootDir = await pkgDir(cwd);
|
|
||||||
if (!projectRootDir) {
|
|
||||||
if (!defaultToProcessCwd) {
|
|
||||||
throw new Error('Run this command inside a project directory.');
|
|
||||||
} else {
|
|
||||||
return process.cwd();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let vcsRoot;
|
|
||||||
try {
|
|
||||||
vcsRoot = path.normalize(await getVcsClient().getRootPathAsync());
|
|
||||||
} catch {}
|
|
||||||
if (vcsRoot && vcsRoot.startsWith(projectRootDir) && vcsRoot !== projectRootDir) {
|
|
||||||
throw new Error(
|
|
||||||
`package.json is outside of the current git repository (project root: ${projectRootDir}, git root: ${vcsRoot}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return projectRootDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save an EAS project ID to the appropriate field in the app config.
|
|
||||||
*/
|
|
||||||
export async function saveProjectIdToAppConfigAsync(
|
|
||||||
projectDir: string,
|
|
||||||
projectId: string,
|
|
||||||
options: { env?: Env } = {}
|
|
||||||
): Promise<void> {
|
|
||||||
const exp = getExpoConfig(projectDir, options);
|
|
||||||
const result = await modifyConfigAsync(projectDir, {
|
|
||||||
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (result.type) {
|
|
||||||
case 'success':
|
|
||||||
break;
|
|
||||||
case 'warn': {
|
|
||||||
Log.warn();
|
|
||||||
Log.warn(
|
|
||||||
`Warning: Your project uses dynamic app configuration, and the EAS project ID can't automatically be added to it.`
|
|
||||||
);
|
|
||||||
Log.warn(
|
|
||||||
chalk.dim(
|
|
||||||
'https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
Log.warn();
|
|
||||||
Log.warn(
|
|
||||||
`To complete the setup process, set the ${chalk.bold(
|
|
||||||
'extra.eas.projectId'
|
|
||||||
)} in your ${chalk.bold(getProjectConfigDescription(projectDir))}:`
|
|
||||||
);
|
|
||||||
Log.warn();
|
|
||||||
Log.warn(chalk.bold(JSON.stringify({ expo: { extra: { eas: { projectId } } } }, null, 2)));
|
|
||||||
Log.warn();
|
|
||||||
throw new Error(result.message);
|
|
||||||
}
|
|
||||||
case 'fail':
|
|
||||||
throw new Error(result.message);
|
|
||||||
default:
|
|
||||||
throw new Error('Unexpected result type from modifyConfigAsync');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the EAS project ID from the app config. If the project ID is not set in the config,
|
|
||||||
* use the owner/slug to identify an EAS project on the server (asking for confirmation first),
|
|
||||||
* and attempt to save the EAS project ID to the appropriate field in the app config. If unable to
|
|
||||||
* save to the app config, throw an error.
|
|
||||||
*/
|
|
||||||
export async function getProjectIdAsync(
|
|
||||||
exp: ExpoConfig,
|
|
||||||
options: { env?: Env; nonInteractive: boolean },
|
|
||||||
findProjectRootOptions: {
|
|
||||||
cwd?: string;
|
|
||||||
defaultToProcessCwd?: boolean;
|
|
||||||
} = {}
|
|
||||||
): Promise<string> {
|
|
||||||
const localProjectId = exp.extra?.eas?.projectId;
|
|
||||||
if (localProjectId) {
|
|
||||||
return localProjectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectDir = await findProjectRootAsync(findProjectRootOptions);
|
|
||||||
if (!projectDir) {
|
|
||||||
throw new Error('This command must be run inside a project directory.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.warn('EAS project not configured.');
|
|
||||||
|
|
||||||
const getAccountNameForEASProjectSync = (exp: ExpoConfig, user: Actor): string => {
|
|
||||||
if (exp.owner) {
|
|
||||||
return exp.owner;
|
|
||||||
}
|
|
||||||
switch (user.__typename) {
|
|
||||||
case 'User':
|
|
||||||
return user.username;
|
|
||||||
case 'Robot':
|
|
||||||
throw new Error(
|
|
||||||
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const projectId = await fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
|
|
||||||
{
|
|
||||||
accountName: getAccountNameForEASProjectSync(
|
|
||||||
exp,
|
|
||||||
await ensureLoggedInAsync({ nonInteractive: options.nonInteractive })
|
|
||||||
),
|
|
||||||
projectName: exp.slug,
|
|
||||||
privacy: toAppPrivacy(exp.privacy),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nonInteractive: options.nonInteractive,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const spinner = ora(`Linking local project to EAS project ${projectId}`).start();
|
|
||||||
try {
|
|
||||||
await saveProjectIdToAppConfigAsync(projectDir, projectId, options);
|
|
||||||
spinner.succeed(`Linked local project to EAS project ${projectId}`);
|
|
||||||
} catch (e: any) {
|
|
||||||
spinner.fail();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return projectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toAppPrivacy = (privacy: ExpoConfig['privacy']): AppPrivacy => {
|
export const toAppPrivacy = (privacy: ExpoConfig['privacy']): AppPrivacy => {
|
||||||
if (privacy === 'public') {
|
if (privacy === 'public') {
|
||||||
return AppPrivacy.Public;
|
return AppPrivacy.Public;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '../../../graphql/generated';
|
} from '../../../graphql/generated';
|
||||||
import { SubmissionMutation } from '../../../graphql/mutations/SubmissionMutation';
|
import { SubmissionMutation } from '../../../graphql/mutations/SubmissionMutation';
|
||||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils';
|
||||||
import { createSubmissionContextAsync } from '../../context';
|
import { createSubmissionContextAsync } from '../../context';
|
||||||
import { getRecentBuildsForSubmissionAsync } from '../../utils/builds';
|
import { getRecentBuildsForSubmissionAsync } from '../../utils/builds';
|
||||||
import AndroidSubmitCommand from '../AndroidSubmitCommand';
|
import AndroidSubmitCommand from '../AndroidSubmitCommand';
|
||||||
@@ -78,10 +78,6 @@ describe(AndroidSubmitCommand, () => {
|
|||||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockJester.accounts[0]);
|
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockJester.accounts[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.mocked(getProjectIdAsync).mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('non-interactive mode', () => {
|
describe('non-interactive mode', () => {
|
||||||
it("throws error if didn't provide serviceAccountKeyPath in the submit profile", async () => {
|
it("throws error if didn't provide serviceAccountKeyPath in the submit profile", async () => {
|
||||||
const projectId = uuidv4();
|
const projectId = uuidv4();
|
||||||
@@ -99,6 +95,7 @@ describe(AndroidSubmitCommand, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const command = new AndroidSubmitCommand(ctx);
|
const command = new AndroidSubmitCommand(ctx);
|
||||||
await expect(command.runAsync()).rejects.toThrowError();
|
await expect(command.runAsync()).rejects.toThrowError();
|
||||||
@@ -123,6 +120,7 @@ describe(AndroidSubmitCommand, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const command = new AndroidSubmitCommand(ctx);
|
const command = new AndroidSubmitCommand(ctx);
|
||||||
await command.runAsync();
|
await command.runAsync();
|
||||||
@@ -159,6 +157,7 @@ describe(AndroidSubmitCommand, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const command = new AndroidSubmitCommand(ctx);
|
const command = new AndroidSubmitCommand(ctx);
|
||||||
await command.runAsync();
|
await command.runAsync();
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ describe(getServiceAccountKeyResultAsync, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const source: ServiceAccountSource = {
|
const source: ServiceAccountSource = {
|
||||||
sourceType: ServiceAccountSourceType.path,
|
sourceType: ServiceAccountSourceType.path,
|
||||||
@@ -188,6 +189,7 @@ describe(getServiceAccountKeyResultAsync, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const source: ServiceAccountSource = {
|
const source: ServiceAccountSource = {
|
||||||
sourceType: ServiceAccountSourceType.prompt,
|
sourceType: ServiceAccountSourceType.prompt,
|
||||||
@@ -219,6 +221,7 @@ describe(getServiceAccountKeyResultAsync, () => {
|
|||||||
changesNotSentForReview: false,
|
changesNotSentForReview: false,
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const serviceAccountResult = await getServiceAccountKeyResultAsync(ctx, {
|
const serviceAccountResult = await getServiceAccountKeyResultAsync(ctx, {
|
||||||
sourceType: ServiceAccountSourceType.credentialsService,
|
sourceType: ServiceAccountSourceType.credentialsService,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { CredentialsContext } from '../credentials/context';
|
|||||||
import { getExpoConfig } from '../project/expoConfig';
|
import { getExpoConfig } from '../project/expoConfig';
|
||||||
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||||
import { Actor } from '../user/User';
|
import { Actor } from '../user/User';
|
||||||
import { ensureLoggedInAsync } from '../user/actions';
|
|
||||||
|
|
||||||
export interface SubmissionContext<T extends Platform> {
|
export interface SubmissionContext<T extends Platform> {
|
||||||
accountName: string;
|
accountName: string;
|
||||||
@@ -44,17 +43,22 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
|||||||
projectDir: string;
|
projectDir: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
applicationIdentifier?: string;
|
applicationIdentifier?: string;
|
||||||
|
actor: Actor;
|
||||||
}): Promise<SubmissionContext<T>> {
|
}): Promise<SubmissionContext<T>> {
|
||||||
const { applicationIdentifier, projectDir, nonInteractive } = params;
|
const { applicationIdentifier, projectDir, nonInteractive, actor } = params;
|
||||||
const exp = getExpoConfig(projectDir, { env: params.env });
|
const exp = getExpoConfig(projectDir, { env: params.env });
|
||||||
const { env, ...rest } = params;
|
const { env, ...rest } = params;
|
||||||
const user = await ensureLoggedInAsync({ nonInteractive });
|
|
||||||
const projectName = exp.slug;
|
const projectName = exp.slug;
|
||||||
const account = await getOwnerAccountForProjectIdAsync(params.projectId);
|
const account = await getOwnerAccountForProjectIdAsync(params.projectId);
|
||||||
const accountId = account.id;
|
const accountId = account.id;
|
||||||
let credentialsCtx: CredentialsContext | undefined = params.credentialsCtx;
|
let credentialsCtx: CredentialsContext | undefined = params.credentialsCtx;
|
||||||
if (!credentialsCtx) {
|
if (!credentialsCtx) {
|
||||||
credentialsCtx = new CredentialsContext({ projectDir, user, exp, nonInteractive });
|
credentialsCtx = new CredentialsContext({
|
||||||
|
projectDir,
|
||||||
|
user: actor,
|
||||||
|
projectInfo: { exp, projectId: params.projectId },
|
||||||
|
nonInteractive,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackingCtx = {
|
const trackingCtx = {
|
||||||
@@ -72,7 +76,7 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
|||||||
credentialsCtx,
|
credentialsCtx,
|
||||||
exp,
|
exp,
|
||||||
projectName,
|
projectName,
|
||||||
user,
|
user: actor,
|
||||||
trackingCtx,
|
trackingCtx,
|
||||||
applicationIdentifierOverride: applicationIdentifier,
|
applicationIdentifierOverride: applicationIdentifier,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ async function getIosSubmissionContextAsync(): Promise<SubmissionContext<Platfor
|
|||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ describe(getFromCredentialsServiceAsync, () => {
|
|||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
},
|
},
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
jest
|
jest
|
||||||
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
||||||
@@ -86,6 +87,7 @@ describe(getFromCredentialsServiceAsync, () => {
|
|||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
jest
|
jest
|
||||||
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '../../../credentials/__tests__/fixtures-constants';
|
} from '../../../credentials/__tests__/fixtures-constants';
|
||||||
import { SubmissionMutation } from '../../../graphql/mutations/SubmissionMutation';
|
import { SubmissionMutation } from '../../../graphql/mutations/SubmissionMutation';
|
||||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils';
|
||||||
import { createSubmissionContextAsync } from '../../context';
|
import { createSubmissionContextAsync } from '../../context';
|
||||||
import IosSubmitCommand from '../IosSubmitCommand';
|
import IosSubmitCommand from '../IosSubmitCommand';
|
||||||
|
|
||||||
@@ -54,10 +54,6 @@ describe(IosSubmitCommand, () => {
|
|||||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockJester.accounts[0]);
|
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockJester.accounts[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.mocked(getProjectIdAsync).mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('non-interactive mode', () => {
|
describe('non-interactive mode', () => {
|
||||||
it("throws error if didn't provide appleId and ascAppId in the submit profile", async () => {
|
it("throws error if didn't provide appleId and ascAppId in the submit profile", async () => {
|
||||||
const projectId = uuidv4();
|
const projectId = uuidv4();
|
||||||
@@ -73,6 +69,7 @@ describe(IosSubmitCommand, () => {
|
|||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
},
|
},
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const command = new IosSubmitCommand(ctx);
|
const command = new IosSubmitCommand(ctx);
|
||||||
await expect(command.runAsync()).rejects.toThrowError();
|
await expect(command.runAsync()).rejects.toThrowError();
|
||||||
@@ -98,6 +95,7 @@ describe(IosSubmitCommand, () => {
|
|||||||
ascAppId: '12345678',
|
ascAppId: '12345678',
|
||||||
},
|
},
|
||||||
nonInteractive: false,
|
nonInteractive: false,
|
||||||
|
actor: mockJester,
|
||||||
});
|
});
|
||||||
const command = new IosSubmitCommand(ctx);
|
const command = new IosSubmitCommand(ctx);
|
||||||
await command.runAsync();
|
await command.runAsync();
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ import { ExpoConfig } from '@expo/config';
|
|||||||
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
|
import { AndroidConfig, AndroidManifest } from '@expo/config-plugins';
|
||||||
|
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import { ensureValidVersions } from '../utils';
|
import { ensureValidVersions } from '../utils';
|
||||||
|
|
||||||
export async function syncUpdatesConfigurationAsync(
|
export async function syncUpdatesConfigurationAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig,
|
exp: ExpoConfig,
|
||||||
{ nonInteractive }: { nonInteractive: boolean }
|
projectId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
ensureValidVersions(exp, RequestedPlatform.Android);
|
ensureValidVersions(exp, RequestedPlatform.Android);
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
|
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
|
||||||
|
|
||||||
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectDir);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ExpoConfig } from '@expo/config';
|
|||||||
import { IOSConfig } from '@expo/config-plugins';
|
import { IOSConfig } from '@expo/config-plugins';
|
||||||
|
|
||||||
import { RequestedPlatform } from '../../platform';
|
import { RequestedPlatform } from '../../platform';
|
||||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||||
import { getVcsClient } from '../../vcs';
|
import { getVcsClient } from '../../vcs';
|
||||||
import { ensureValidVersions } from '../utils';
|
import { ensureValidVersions } from '../utils';
|
||||||
@@ -10,10 +10,9 @@ import { ensureValidVersions } from '../utils';
|
|||||||
export async function syncUpdatesConfigurationAsync(
|
export async function syncUpdatesConfigurationAsync(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
exp: ExpoConfig,
|
exp: ExpoConfig,
|
||||||
{ nonInteractive }: { nonInteractive: boolean }
|
projectId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
ensureValidVersions(exp, RequestedPlatform.Ios);
|
ensureValidVersions(exp, RequestedPlatform.Ios);
|
||||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
|
||||||
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
|
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
|
||||||
const expoPlist = await readExpoPlistAsync(projectDir);
|
const expoPlist = await readExpoPlistAsync(projectDir);
|
||||||
const updatedExpoPlist = IOSConfig.Updates.setUpdatesConfig(
|
const updatedExpoPlist = IOSConfig.Updates.setUpdatesConfig(
|
||||||
|
|||||||
@@ -1,82 +1,5 @@
|
|||||||
import { Errors } from '@oclif/core';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import nullthrows from 'nullthrows';
|
|
||||||
|
|
||||||
import { ApiV2Error } from '../api';
|
|
||||||
import { AccountFragment } from '../graphql/generated';
|
import { AccountFragment } from '../graphql/generated';
|
||||||
import Log, { learnMore } from '../log';
|
import { Actor } from './User';
|
||||||
import { promptAsync } from '../prompts';
|
|
||||||
import { Actor, getUserAsync, loginAsync } from './User';
|
|
||||||
import { retryUsernamePasswordAuthWithOTPAsync } from './otp';
|
|
||||||
|
|
||||||
export async function showLoginPromptAsync({
|
|
||||||
nonInteractive = false,
|
|
||||||
printNewLine = false,
|
|
||||||
} = {}): Promise<void> {
|
|
||||||
if (nonInteractive) {
|
|
||||||
Errors.error(
|
|
||||||
`Either log in with ${chalk.bold('eas login')} or set the ${chalk.bold(
|
|
||||||
'EXPO_TOKEN'
|
|
||||||
)} environment variable if you're using EAS CLI on CI (${learnMore(
|
|
||||||
'https://docs.expo.dev/accounts/programmatic-access/',
|
|
||||||
{ dim: false }
|
|
||||||
)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (printNewLine) {
|
|
||||||
Log.newLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.log('Log in to EAS');
|
|
||||||
|
|
||||||
const { username, password } = await promptAsync([
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
name: 'username',
|
|
||||||
message: 'Email or username',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'password',
|
|
||||||
name: 'password',
|
|
||||||
message: 'Password',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
try {
|
|
||||||
await loginAsync({
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {
|
|
||||||
await retryUsernamePasswordAuthWithOTPAsync(
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
e.expoApiV2ErrorMetadata as any
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ensureLoggedInAsync({
|
|
||||||
nonInteractive,
|
|
||||||
}: {
|
|
||||||
nonInteractive: boolean;
|
|
||||||
}): Promise<Actor> {
|
|
||||||
let user: Actor | undefined;
|
|
||||||
try {
|
|
||||||
user = await getUserAsync();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
Log.warn('An Expo user account is required to proceed.');
|
|
||||||
await showLoginPromptAsync({ nonInteractive, printNewLine: true });
|
|
||||||
user = await getUserAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullthrows(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureActorHasPrimaryAccount(user: Actor): AccountFragment {
|
export function ensureActorHasPrimaryAccount(user: Actor): AccountFragment {
|
||||||
if (user.__typename === 'User') {
|
if (user.__typename === 'User') {
|
||||||
|
|||||||
Reference in new issue
Block a user