Compare commits

..
Author SHA1 Message Date
Kudo Chien e7b7eacd4e fix lint errors 2024-12-19 00:20:26 +08:00
Kudo Chien ec43906005 update changelog 2024-12-19 00:19:03 +08:00
Kudo Chien b71f324c83 [eas-cli] support platform version 2024-12-19 00:11:52 +08:00
Expo Bot 4bb550e3bb Upgrade eas-build dependencies (#2776) 2024-12-18 13:12:27 +01:00
Kadi Kraman 6a55c65908 [eas-cli] show deployment errors (#2771)
* Show upload errors

* Add changelog

* Fix changelog

* Show error from request body when applicable
2024-12-13 17:08:53 +00:00
Expo CI ab3495da5e update CHANGELOG.md 2024-12-13 15:25:41 +00:00
10 changed files with 294 additions and 34 deletions

No files matched your search

+12
View File
@@ -8,6 +8,18 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Added support to read platform version from `app.json`. ([#2778](https://github.com/expo/eas-cli/pull/2778) by [@kudo](https://github.com/kudo))
### 🐛 Bug fixes
- Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman))
### 🧹 Chores
## [14.2.0](https://github.com/expo/eas-cli/releases/tag/v14.2.0) - 2024-12-13
### 🎉 New features
- Add `eas deploy --dry-run` flag to output tarball. ([#2761](https://github.com/expo/eas-cli/pull/2761) by [@kitten](https://github.com/kitten))
### 🐛 Bug fixes
+2 -2
View File
@@ -12,7 +12,7 @@
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "10.0.6",
"@expo/config-plugins": "9.0.12",
"@expo/eas-build-job": "1.0.154",
"@expo/eas-build-job": "1.0.156",
"@expo/eas-json": "14.2.0",
"@expo/env": "^0.4.0",
"@expo/json-file": "8.3.3",
@@ -28,7 +28,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.154",
"@expo/steps": "1.0.156",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
+15 -6
View File
@@ -20,7 +20,11 @@ import { getNextVersionCode } from '../../project/android/versions';
import { resolveWorkflowAsync } from '../../project/workflow';
import { Client } from '../../vcs/vcs';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
import {
bumpAppVersionAsync,
ensureStaticConfigExists,
getVersionConfigTarget,
} from '../utils/version';
export enum BumpStrategy {
APP_VERSION,
@@ -53,9 +57,11 @@ export async function bumpVersionAsync({
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
Log.log('Updated versions in app.json');
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
const version = versionGetter(exp);
await updateNativeVersionsAsync({
projectDir,
version: exp.version,
version,
versionCode: exp.android?.versionCode,
});
Log.log('Synchronized versions with build gradle');
@@ -79,7 +85,7 @@ export async function bumpVersionInAppJsonAsync({
if (bumpStrategy === BumpStrategy.APP_VERSION) {
const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0';
await bumpAppVersionAsync({ appVersion, projectDir, exp });
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.ANDROID });
} else {
const versionCode = AndroidConfig.Version.getVersionCode(exp);
const bumpedVersionCode = getNextVersionCode(versionCode);
@@ -118,9 +124,11 @@ export async function maybeResolveVersionsAsync(
return {};
}
} else {
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
const appVersion = versionGetter(exp);
return {
appBuildVersion: String(AndroidConfig.Version.getVersionCode(exp)),
appVersion: exp.version,
appVersion,
};
}
}
@@ -202,6 +210,7 @@ export async function resolveRemoteVersionCodeAsync(
applicationId
);
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
let currentBuildVersion: string;
if (remoteVersions?.buildVersion) {
@@ -230,7 +239,7 @@ export async function resolveRemoteVersionCodeAsync(
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
buildVersion: currentBuildVersion,
runtimeVersion:
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
@@ -254,7 +263,7 @@ export async function resolveRemoteVersionCodeAsync(
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
buildVersion: String(nextBuildVersion),
runtimeVersion:
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
+13 -5
View File
@@ -21,7 +21,11 @@ import uniqBy from '../../utils/expodash/uniqBy';
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
import { Client } from '../../vcs/vcs';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
import {
bumpAppVersionAsync,
ensureStaticConfigExists,
getVersionConfigTarget,
} from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
@@ -48,9 +52,11 @@ export async function bumpVersionAsync({
ensureStaticConfigExists(projectDir);
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
Log.log('Updated versions in app.json');
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
const version = versionGetter(exp);
await updateNativeVersionsAsync({
projectDir,
version: exp.version,
version,
buildNumber: exp.ios?.buildNumber,
targets,
});
@@ -73,7 +79,7 @@ export async function bumpVersionInAppJsonAsync({
Log.addNewLineIfNone();
if (bumpStrategy === BumpStrategy.APP_VERSION) {
const appVersion = IOSConfig.Version.getVersion(exp);
await bumpAppVersionAsync({ appVersion, projectDir, exp });
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
} else {
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
if (isValidBuildNumber(buildNumber)) {
@@ -141,8 +147,10 @@ export async function readShortVersionAsync(
validateShortVersion({ shortVersion, workflow });
return shortVersion;
} else {
validateShortVersion({ shortVersion: exp.version, workflow });
return exp.version;
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
const shortVersion = versionGetter(exp);
validateShortVersion({ shortVersion, workflow });
return shortVersion;
}
}
@@ -0,0 +1,148 @@
import { ExpoConfig } from '@expo/config';
import { Platform } from '@expo/eas-build-job';
import { updateAppJsonConfigAsync } from '../appJson';
import { bumpAppVersionAsync, getVersionConfigTarget } from '../version';
jest.mock('../appJson', () => ({
__esModule: true,
updateAppJsonConfigAsync: jest.fn().mockImplementation(
async (
{
exp,
}: {
projectDir: string;
exp: ExpoConfig;
},
modifyConfig: (config: any) => void
) => {
// a mocked implementation that only mutates the config object without writing to disk
modifyConfig(exp);
}
),
}));
describe(bumpAppVersionAsync, () => {
const name = 'test';
const slug = 'test';
const projectDir = '/app';
const mockUpdateAppJsonConfigAsync = updateAppJsonConfigAsync as jest.MockedFunction<
typeof updateAppJsonConfigAsync
>;
it('should bump expo.version for valid semver', async () => {
const appVersion = '1.0.0';
const exp: ExpoConfig = {
name,
slug,
version: appVersion,
};
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
expect(exp.version).toBe('1.0.1');
});
it('should bump expo.android.version if the expo.android.version exists', async () => {
const exp: ExpoConfig = {
name,
slug,
version: '0.0.0',
android: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '1.0.0',
},
ios: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '2.0.0',
},
};
await bumpAppVersionAsync({ appVersion: '1.0.0', projectDir, exp, platform: Platform.ANDROID });
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
expect(exp.version).toBe('0.0.0');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(exp.android.version).toBe('1.0.1');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(exp.ios.version).toBe('2.0.0');
});
it('should bump expo.ios.version if the expo.ios.version exists', async () => {
const exp: ExpoConfig = {
name,
slug,
version: '0.0.0',
android: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '1.0.0',
},
ios: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '2.0.0',
},
};
await bumpAppVersionAsync({ appVersion: '2.0.0', projectDir, exp, platform: Platform.IOS });
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
expect(exp.version).toBe('0.0.0');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(exp.android.version).toBe('1.0.0');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(exp.ios.version).toBe('2.0.1');
});
});
describe(getVersionConfigTarget, () => {
const exp: ExpoConfig = {
name: 'test',
slug: 'test',
version: '0.0.0',
android: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '1.0.0',
},
ios: {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
version: '2.0.0',
},
};
it('should return the correct config target for the android platform', () => {
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
exp,
platform: Platform.ANDROID,
});
expect(fieldName).toBe('expo.android.version');
expect(versionGetter(exp)).toBe('1.0.0');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(versionUpdater(exp, '3.3.3').android.version).toBe('3.3.3');
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
});
it('should return the correct config target for the ios platform', () => {
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
exp,
platform: Platform.IOS,
});
expect(fieldName).toBe('expo.ios.version');
expect(versionGetter(exp)).toBe('2.0.0');
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
expect(versionUpdater(exp, '3.3.3').ios.version).toBe('3.3.3');
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
});
it('should return the correct config target for common version', () => {
const exp: ExpoConfig = {
name: 'test',
slug: 'test',
version: '0.0.0',
};
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
exp,
platform: Platform.IOS,
});
expect(fieldName).toBe('expo.version');
expect(versionGetter(exp)).toBe('0.0.0');
expect(versionUpdater(exp, '3.3.3').version).toBe('3.3.3');
});
});
+58 -3
View File
@@ -1,4 +1,5 @@
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
import { Platform } from '@expo/eas-build-job';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import semver from 'semver';
@@ -18,21 +19,25 @@ export async function bumpAppVersionAsync({
appVersion,
projectDir,
exp,
platform,
}: {
appVersion: string;
projectDir: string;
exp: ExpoConfig;
platform: Platform;
}): Promise<void> {
const { fieldName, versionUpdater } = getVersionConfigTarget({ exp, platform });
let bumpedAppVersion: string;
if (semver.valid(appVersion)) {
bumpedAppVersion = nullthrows(semver.inc(appVersion, 'patch'));
Log.log(
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold(
`Bumping ${chalk.bold(fieldName)} from ${chalk.bold(appVersion)} to ${chalk.bold(
bumpedAppVersion
)}`
);
} else {
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`);
Log.log(`${chalk.bold(fieldName)} = ${chalk.bold(appVersion)} is not a valid semver`);
bumpedAppVersion = (
await promptAsync({
type: 'text',
@@ -42,6 +47,56 @@ export async function bumpAppVersionAsync({
).bumpedAppVersion;
}
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
config.version = bumpedAppVersion;
versionUpdater(config, bumpedAppVersion);
});
}
/**
* Get the target version field from ExpoConfig based on the platform.
*/
export function getVersionConfigTarget({
exp,
platform,
}: {
exp: ExpoConfig;
platform: Platform;
}): {
fieldName: string;
versionGetter: (config: ExpoConfig) => string | undefined;
versionUpdater: (config: ExpoConfig, version: string) => ExpoConfig;
} {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
if (platform === Platform.ANDROID && typeof exp.android?.version === 'string') {
return {
fieldName: 'expo.android.version',
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
versionGetter: config => config.android?.version,
versionUpdater: (config, version) => {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
config.android = { ...config.android, version };
return config;
},
};
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
} else if (platform === Platform.IOS && typeof exp.ios?.version === 'string') {
return {
fieldName: 'expo.ios.version',
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
versionGetter: config => config.ios?.version,
versionUpdater: (config, version) => {
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
config.ios = { ...config.ios, version };
return config;
},
};
}
return {
fieldName: 'expo.version',
versionGetter: config => config.version,
versionUpdater: (config, version) => {
config.version = version;
return config;
},
};
}
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
import chalk from 'chalk';
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
import { getVersionConfigTarget } from '../../../build/utils/version';
import EasCommand from '../../../commandUtils/EasCommand';
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
@@ -111,6 +112,7 @@ export default class BuildVersionSetView extends EasCommand {
: `What version would you like to initialize it with?`;
Log.log(currentStateMessage);
const { versionGetter } = getVersionConfigTarget({ exp, platform });
const { version } = await promptAsync({
type: platform === Platform.ANDROID ? 'number' : 'text',
name: 'version',
@@ -124,7 +126,7 @@ export default class BuildVersionSetView extends EasCommand {
appId: projectId,
platform: toAppPlatform(platform),
applicationIdentifier,
storeVersion: exp.version ?? '1.0.0',
storeVersion: versionGetter(exp) ?? '1.0.0',
buildVersion: String(version),
runtimeVersion:
(await getRuntimeVersionNullableAsync(projectDir, exp, platform)) ?? undefined,
+5 -4
View File
@@ -109,20 +109,21 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
return retry(error);
}
const body = await response.json().catch(() => null);
const errorMessage = body?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
if (
response.status === 408 ||
response.status === 409 ||
response.status === 429 ||
(response.status >= 500 && response.status <= 599)
) {
const message = `Upload of "${filePath}" failed: ${response.statusText}`;
const text = await response.text().catch(() => null);
return retry(new Error(text ? `${message}\n${text}` : message));
return retry(new Error(errorMessage));
} else if (response.status === 413) {
const message = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
throw new Error(message);
} else if (!response.ok) {
throw new Error(`Upload of "${filePath}" failed: ${response.statusText}`);
throw new Error(errorMessage);
}
return {
+1 -1
View File
@@ -6,7 +6,7 @@
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "1.0.154",
"@expo/eas-build-job": "1.0.156",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
Generated
+37 -12
View File
@@ -1465,10 +1465,10 @@
slugify "^1.3.4"
sucrase "3.35.0"
"@expo/[email protected]4":
version "1.0.154"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.154.tgz#366511196e983bdb4d4687266373a5822a74af82"
integrity sha512-h5VQYX++KZqU2d6PB1xskgB3DXZRpuLNHjciW6RQ67sRzMgsSxMbFAosiW03LV/rYi8yZdR8NHFbsIf1Qk6ZIg==
"@expo/[email protected]6":
version "1.0.156"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.156.tgz#c8e1f34a480eba217e6aa37e5d178f440065a7e7"
integrity sha512-pD3vWO7fIaQ4wmJy/x6L2dVK2rwf1qNzTE/4I5JI4r6vYSKrSCv8EIoE87xDBaRUBMk72ZdzyznJe516pMyTaw==
dependencies:
"@expo/logger" "1.0.117"
joi "^17.13.1"
@@ -1645,12 +1645,12 @@
dependencies:
cross-spawn "^7.0.3"
"@expo/[email protected]4":
version "1.0.154"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.154.tgz#631148dd1fa58a30a70f92be50abdc4e42672ab0"
integrity sha512-PoFT1IDYptmfw3zGwblHOqfU4zFx8ckHALm7OalqceITqn0MTAk85/y5SUuh/AQCHbo/ULTJTnrnBocmDzpkQA==
"@expo/[email protected]6":
version "1.0.156"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.156.tgz#79a043a7dd4f5c098c954ed7a013c407631f515a"
integrity sha512-fa2rMaWqF0DaqqyDXy2Dy5pBbXTPfnMD1ALQid3ApjokMV5Gq+CTyr7sYFGqr1vywK0sftpmKVAOMJzHV95dDg==
dependencies:
"@expo/eas-build-job" "1.0.154"
"@expo/eas-build-job" "1.0.156"
"@expo/logger" "1.0.117"
"@expo/spawn-async" "^1.7.2"
arg "^5.0.2"
@@ -13228,7 +13228,16 @@ string-length@^5.0.1:
char-regex "^2.0.0"
strip-ansi "^7.0.1"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13384,7 +13393,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13405,6 +13414,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@@ -14611,7 +14627,16 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", [email protected], wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
[email protected], wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==