mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
819f0b4c6f | ||
|
|
9ffd3a096b | ||
|
|
e3ff26e1ca | ||
|
|
bbd26a8030 | ||
|
|
57dd372957 | ||
|
|
5232641217 | ||
|
|
3c3d3150c9 | ||
|
|
fd322d4189 | ||
|
|
77254f6be1 |
No files matched your search
@@ -8,6 +8,19 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add styling to SSO auth redirect completion page. ([#1929](https://github.com/expo/eas-cli/pull/1929) by [@wschurman](https://github.com/wschurman))
|
||||
- Ignore entitlements from native template when `/ios` is gitignored. ([#1906](https://github.com/expo/eas-cli/pull/1906) by [@byCedric](https://github.com/byCedric))
|
||||
- Use node server default port selection for SSO login server. ([#1930](https://github.com/expo/eas-cli/pull/1930) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Limit project file upload size to 2GB. ([#1928](https://github.com/expo/eas-cli/pull/1928) by [@khamilowicz](https://github.com/khamilowicz))
|
||||
- Bump urql graphql client major version. ([#1936](https://github.com/expo/eas-cli/pull/1936) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [3.15.1](https://github.com/expo/eas-cli/releases/tag/v3.15.1) - 2023-07-11
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Ensure useClassicUpdates is not set when using EAS Update commands. ([#1915](https://github.com/expo/eas-cli/pull/1915) by [@ide](https://github.com/ide))
|
||||
|
||||
@@ -32,8 +32,9 @@
|
||||
"@oclif/core": "1.23.2",
|
||||
"@oclif/plugin-autocomplete": "1.3.10",
|
||||
"@segment/ajv-human-errors": "^2.1.2",
|
||||
"@urql/core": "3.1.1",
|
||||
"@urql/exchange-retry": "1.0.0",
|
||||
"@urql/core": "4.0.11",
|
||||
"@urql/exchange-graphcache": "6.1.4",
|
||||
"@urql/exchange-retry": "1.2.0",
|
||||
"ajv": "8.11.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"better-opn": "3.0.2",
|
||||
@@ -47,7 +48,6 @@
|
||||
"fast-glob": "3.2.12",
|
||||
"figures": "3.2.0",
|
||||
"form-data": "4.0.0",
|
||||
"freeport-async": "2.0.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"getenv": "1.0.0",
|
||||
"gradle-to-js": "2.0.1",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { JSONValue } from '@expo/json-file';
|
||||
|
||||
import { ApiV2Error } from './ApiV2Error';
|
||||
import fetch, { RequestError, RequestInit } from './fetch';
|
||||
import { getFreePortAsync } from './utils/port';
|
||||
|
||||
interface RequestOptions {
|
||||
body: JSONValue;
|
||||
@@ -105,14 +104,3 @@ export function getEASUpdateURL(projectId: string): string {
|
||||
return new URL(projectId, `https://u.expo.dev`).href;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSsoLocalServerPortAsync(): Promise<number> {
|
||||
let startPort: number;
|
||||
if (process.env.SSO_LOCAL_SERVER_PORT) {
|
||||
startPort = Number(process.env.SSO_LOCAL_SERVER_PORT);
|
||||
} else {
|
||||
startPort = 19300;
|
||||
}
|
||||
const port = await getFreePortAsync(startPort);
|
||||
return port;
|
||||
}
|
||||
@@ -27,12 +27,18 @@ beforeEach(() => {
|
||||
|
||||
const EXPECTED_STRINGIFIED_GRAPHQL_ERROR_JSON = `[
|
||||
{
|
||||
"name": "GraphQLError",
|
||||
"extensions": {},
|
||||
"message": "Error 1"
|
||||
},
|
||||
{
|
||||
"name": "GraphQLError",
|
||||
"extensions": {},
|
||||
"message": "Error 2"
|
||||
},
|
||||
{
|
||||
"name": "GraphQLError",
|
||||
"extensions": {},
|
||||
"message": "Error 3"
|
||||
}
|
||||
]`;
|
||||
|
||||
@@ -255,6 +255,10 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
);
|
||||
}
|
||||
|
||||
if (projectTarball.size > 2 * 1024 * 1024 * 1024) {
|
||||
throw new Error('Project archive is too big. Maximum allowed size is 2GB.');
|
||||
}
|
||||
|
||||
projectTarballPath = projectTarball.path;
|
||||
|
||||
const bucketKey = await uploadFileAtPathToGCSAsync(
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export class ListChannels {
|
||||
constructor() {}
|
||||
|
||||
async runAsync(): Promise<void> {}
|
||||
|
||||
// TODO return channels fragment
|
||||
async listChannelsAsync(): Promise<void> {}
|
||||
}
|
||||
@@ -3,13 +3,14 @@ import {
|
||||
Client,
|
||||
OperationContext,
|
||||
OperationResult,
|
||||
PromisifiedSource,
|
||||
OperationResultSource,
|
||||
TypedDocumentNode,
|
||||
cacheExchange,
|
||||
createClient as createUrqlClient,
|
||||
dedupExchange,
|
||||
cacheExchange as defaultCacheExchange,
|
||||
fetchExchange,
|
||||
} from '@urql/core';
|
||||
import { cacheExchange as graphqlCacheExchange } from '@urql/exchange-graphcache';
|
||||
import { relayPagination } from '@urql/exchange-graphcache/extras';
|
||||
import { retryExchange } from '@urql/exchange-retry';
|
||||
import { DocumentNode } from 'graphql';
|
||||
import fetch from 'node-fetch';
|
||||
@@ -22,18 +23,28 @@ export interface ExpoGraphqlClient extends Client {
|
||||
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
|
||||
variables: Variables,
|
||||
context: Partial<OperationContext> & { additionalTypenames: string[] }
|
||||
): PromisifiedSource<OperationResult<Data, Variables>>;
|
||||
): OperationResultSource<OperationResult<Data, Variables>>;
|
||||
}
|
||||
|
||||
export function createGraphqlClient(authInfo: {
|
||||
accessToken: string | null;
|
||||
sessionSecret: string | null;
|
||||
}): ExpoGraphqlClient {
|
||||
// TODO: plumb this down, maybe use a feature gate for this?
|
||||
export function createGraphqlClient(
|
||||
authInfo: {
|
||||
accessToken: string | null;
|
||||
sessionSecret: string | null;
|
||||
},
|
||||
_options: { useGraphqlCachePolicy?: boolean } = {}
|
||||
): ExpoGraphqlClient {
|
||||
return createUrqlClient({
|
||||
url: getExpoApiBaseUrl() + '/graphql',
|
||||
exchanges: [
|
||||
dedupExchange,
|
||||
cacheExchange,
|
||||
//options.useGraphqlCachePolicy ? graphqlCacheExchange() : defaultCacheExchange,
|
||||
graphqlCacheExchange({
|
||||
resolvers: {
|
||||
App: {
|
||||
updateChannels: relayPagination(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
retryExchange({
|
||||
maxDelayMs: 4000,
|
||||
retryIf: (err, operation) => {
|
||||
|
||||
@@ -106,6 +106,41 @@ export const ChannelQuery = {
|
||||
throw new Error(`Could not find channels on project with id ${appId}`);
|
||||
}
|
||||
|
||||
return updateChannels;
|
||||
},
|
||||
async viewUpdateChannelsPaginatedOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, limit, offset }: ViewUpdateChannelsOnAppQueryVariables
|
||||
): Promise<UpdateChannelObject[]> {
|
||||
const response = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ViewUpdateChannelsOnAppQuery, ViewUpdateChannelsOnAppQueryVariables>(
|
||||
gql`
|
||||
query ViewUpdateChannelsOnApp($appId: String!, $offset: Int!, $limit: Int!) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
updateChannels(offset: $offset, limit: $limit) {
|
||||
id
|
||||
name
|
||||
branchMapping
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateFragmentNode)}
|
||||
`,
|
||||
{ appId, offset, limit },
|
||||
{ additionalTypenames: ['UpdateChannel', 'UpdateBranch', 'Update'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const { updateChannels } = response.app.byId;
|
||||
|
||||
if (!updateChannels) {
|
||||
throw new Error(`Could not find channels on project with id ${appId}`);
|
||||
}
|
||||
|
||||
return updateChannels;
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { JSONObject } from '@expo/json-file';
|
||||
import { getPrebuildConfigAsync } from '@expo/prebuild-config';
|
||||
|
||||
import { readPlistAsync } from '../../utils/plist';
|
||||
import { hasIgnoredIosProjectAsync } from '../workflow';
|
||||
|
||||
interface Target {
|
||||
buildConfiguration?: string;
|
||||
@@ -13,6 +14,7 @@ export async function getManagedApplicationTargetEntitlementsAsync(
|
||||
env: Record<string, string>
|
||||
): Promise<JSONObject> {
|
||||
const originalProcessEnv: NodeJS.ProcessEnv = process.env;
|
||||
|
||||
try {
|
||||
process.env = {
|
||||
...process.env,
|
||||
@@ -24,6 +26,7 @@ export async function getManagedApplicationTargetEntitlementsAsync(
|
||||
projectRoot: projectDir,
|
||||
platforms: ['ios'],
|
||||
introspect: true,
|
||||
ignoreExistingNativeFiles: await hasIgnoredIosProjectAsync(projectDir),
|
||||
});
|
||||
return expWithMods.ios?.entitlements || {};
|
||||
} finally {
|
||||
|
||||
@@ -44,3 +44,15 @@ export async function resolveWorkflowPerPlatformAsync(
|
||||
]);
|
||||
return { android, ios };
|
||||
}
|
||||
|
||||
export async function hasIgnoredIosProjectAsync(projectDir: string): Promise<boolean> {
|
||||
const vcsClient = getVcsClient();
|
||||
const vcsRootPath = path.normalize(await vcsClient.getRootPathAsync());
|
||||
|
||||
try {
|
||||
const pbxProjectPath = IOSConfig.Paths.getPBXProjectPath(projectDir);
|
||||
return await vcsClient.isFileIgnoredAsync(path.relative(vcsRootPath, pbxProjectPath));
|
||||
} finally {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import assert from 'assert';
|
||||
import openBrowserAsync from 'better-opn';
|
||||
import http from 'http';
|
||||
import { Socket } from 'node:net';
|
||||
@@ -5,22 +6,48 @@ import querystring from 'querystring';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
export async function getSessionUsingBrowserAuthFlowAsync(options: {
|
||||
const successBody = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Expo SSO Login</title>
|
||||
<meta charset="utf-8">
|
||||
<style type="text/css">
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #fff;
|
||||
font-family: Tahoma,Verdana;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: .5rem;
|
||||
margin: 1em;
|
||||
overflow-wrap: break-word
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
SSO login complete. You may now close this tab and return to the command prompt.
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
export async function getSessionUsingBrowserAuthFlowAsync({
|
||||
expoWebsiteUrl,
|
||||
}: {
|
||||
expoWebsiteUrl: string;
|
||||
serverPort: number;
|
||||
}): Promise<string> {
|
||||
const { expoWebsiteUrl, serverPort } = options;
|
||||
if (!expoWebsiteUrl || !serverPort) {
|
||||
throw new Error('Expo website URL and local server port are required.');
|
||||
}
|
||||
const scheme = 'http';
|
||||
const hostname = 'localhost';
|
||||
const path = '/auth/callback';
|
||||
const redirectUri = `${scheme}://${hostname}:${serverPort}${path}`;
|
||||
|
||||
const buildExpoSsoLoginUrl = (): string => {
|
||||
const buildExpoSsoLoginUrl = (port: number): string => {
|
||||
const data = {
|
||||
app_redirect_uri: redirectUri,
|
||||
app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,
|
||||
};
|
||||
const params = querystring.stringify(data);
|
||||
return `${expoWebsiteUrl}/sso-login?${params}`;
|
||||
@@ -44,8 +71,8 @@ export async function getSessionUsingBrowserAuthFlowAsync(options: {
|
||||
throw new Error('Request missing session_secret search parameter.');
|
||||
}
|
||||
resolve(sessionSecret);
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.write(`Website login has completed. You can now close this tab.`);
|
||||
response.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
response.write(successBody);
|
||||
response.end();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
@@ -59,8 +86,17 @@ export async function getSessionUsingBrowserAuthFlowAsync(options: {
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(serverPort, hostname, () => {
|
||||
server.listen(0, hostname, () => {
|
||||
Log.log('Waiting for browser login...');
|
||||
|
||||
const address = server.address();
|
||||
assert(
|
||||
address !== null && typeof address === 'object',
|
||||
'Server address and port should be set after listening has begun'
|
||||
);
|
||||
const port = address.port;
|
||||
const authorizeUrl = buildExpoSsoLoginUrl(port);
|
||||
openBrowserAsync(authorizeUrl);
|
||||
});
|
||||
|
||||
server.on('connection', connection => {
|
||||
@@ -70,9 +106,6 @@ export async function getSessionUsingBrowserAuthFlowAsync(options: {
|
||||
connections.delete(connection);
|
||||
});
|
||||
});
|
||||
|
||||
const authorizeUrl = buildExpoSsoLoginUrl();
|
||||
openBrowserAsync(authorizeUrl);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getExpoWebsiteBaseUrl, getSsoLocalServerPortAsync } from '../api';
|
||||
import { getExpoWebsiteBaseUrl } from '../api';
|
||||
import { getSessionUsingBrowserAuthFlowAsync } from './expoSsoLauncher';
|
||||
import { fetchUserAsync } from './fetchUser';
|
||||
|
||||
@@ -7,11 +7,9 @@ export async function fetchSessionSecretAndSsoUserAsync(): Promise<{
|
||||
id: string;
|
||||
username: string;
|
||||
}> {
|
||||
const config = {
|
||||
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({
|
||||
expoWebsiteUrl: getExpoWebsiteBaseUrl(),
|
||||
serverPort: await getSsoLocalServerPortAsync(),
|
||||
};
|
||||
const sessionSecret = await getSessionUsingBrowserAuthFlowAsync(config);
|
||||
});
|
||||
|
||||
const userData = await fetchUserAsync({ sessionSecret });
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import freeportAsync from 'freeport-async';
|
||||
|
||||
/** Get a free port or throw an error. */
|
||||
export async function getFreePortAsync(rangeStart: number): Promise<number> {
|
||||
const port = await freeportAsync(rangeStart, { hostnames: [null, 'localhost'] });
|
||||
if (!port) {
|
||||
throw new Error('No available port found');
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
declare module 'freeport-async' {
|
||||
interface FreePortOptions {
|
||||
hostnames?: (string | null)[];
|
||||
}
|
||||
|
||||
function freePortAsync(rangeStart: number, options?: FreePortOptions): Promise<number>;
|
||||
export = freePortAsync;
|
||||
}
|
||||
@@ -2,6 +2,11 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@0no-co/graphql.web@^1.0.1":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@0no-co/graphql.web/-/graphql.web-1.0.4.tgz#9606eb651955499525d068ce0ad8bea596286ce2"
|
||||
integrity sha512-W3ezhHGfO0MS1PtGloaTpg0PbaT8aZSmmaerL7idtU5F7oCI+uu25k+MsMS31BVFlp4aMkHSrNRxiD72IlK8TA==
|
||||
|
||||
"@ampproject/remapping@^2.1.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
|
||||
@@ -4537,20 +4542,30 @@
|
||||
"@typescript-eslint/types" "5.33.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@urql/core@3.1.1", "@urql/core@>=3.0.0":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@urql/core/-/core-3.1.1.tgz#a49cd572360d01f2469a786b294fba2269a65e53"
|
||||
integrity sha512-Mnxtq4I4QeFJsgs7Iytw+HyhiGxISR6qtyk66c9tipozLZ6QVxrCiUPF2HY4BxNIabaxcp+rivadvm8NAnXj4Q==
|
||||
"@urql/core@4.0.11", "@urql/core@>=4.0.0":
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@urql/core/-/core-4.0.11.tgz#b4e8bf3a3ff1a2519bb3aec38c7be99d2523308d"
|
||||
integrity sha512-FFdY97vF5xnUrElcGw9erOLvtu+KGMLfwrLNDfv4IPgdp2IBsiGe+Kb7Aypfd3kH//BETewVSLm3+y2sSzjX6A==
|
||||
dependencies:
|
||||
wonka "^6.1.2"
|
||||
"@0no-co/graphql.web" "^1.0.1"
|
||||
wonka "^6.3.2"
|
||||
|
||||
"@urql/exchange-[email protected]":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-1.0.0.tgz#25a48ef0c2f6854f5617e2ca4ea25ae240f1fcbc"
|
||||
integrity sha512-UGyyGAMXzop9C/fIoe7Ij63DkPSy1uMw2jipB5dnB8R3kl80za7LYzVnA1HvBEt2ZPWfMuwez/VGLOQ7XX4bTA==
|
||||
"@urql/exchange-[email protected]":
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@urql/exchange-graphcache/-/exchange-graphcache-6.1.4.tgz#5d69c838f7a1d313018f63abb8ead79f8c1ea133"
|
||||
integrity sha512-GQ1scnYjMVoUHrPoYOlw3ZfU2nXuLTEhWhKzPquhZz87GliVpZPuFMf+o64cI1dKy9xDlbU7aQnKiSaGeT248w==
|
||||
dependencies:
|
||||
"@urql/core" ">=3.0.0"
|
||||
wonka "^6.0.0"
|
||||
"@0no-co/graphql.web" "^1.0.1"
|
||||
"@urql/core" ">=4.0.0"
|
||||
wonka "^6.3.2"
|
||||
|
||||
"@urql/[email protected]":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-1.2.0.tgz#23d3ec711754e3c67a843e0106f2cabddff2e5ff"
|
||||
integrity sha512-1O/biKiVhhn0EtvDF4UOvz325K4RrLupfL8rHcmqD2TBLv4qVDWQuzx4JGa1FfqjjRb+C9TNZ6w19f32Mq85Ug==
|
||||
dependencies:
|
||||
"@urql/core" ">=4.0.0"
|
||||
wonka "^6.3.2"
|
||||
|
||||
"@whatwg-node/[email protected]", "@whatwg-node/fetch@^0.5.0":
|
||||
version "0.5.4"
|
||||
@@ -7441,11 +7456,6 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||
|
||||
[email protected]:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4"
|
||||
integrity sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==
|
||||
|
||||
[email protected]:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
@@ -13621,15 +13631,10 @@ widest-line@^3.1.0:
|
||||
dependencies:
|
||||
string-width "^4.0.0"
|
||||
|
||||
wonka@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.1.0.tgz#65297ebf7031ae46d4b0c56da93950fb3ae5baaa"
|
||||
integrity sha512-VgiMCz7BXOiDbgpVhf5iNhK7hurteY5Jv0fDJewUkY0s4fbxQD2iKqfGxNXNTwp2v3bgT8QVu2l5H7YdkZ5WIA==
|
||||
|
||||
wonka@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.1.2.tgz#2c66fa5b26a12f002a03619b988258313d0b5352"
|
||||
integrity sha512-zNrXPMccg/7OEp9tSfFkMgTvhhowqasiSHdJ3eCZolXxVTV/aT6HUTofoZk9gwRbGoFey/Nss3JaZKUMKMbofg==
|
||||
wonka@^6.3.2:
|
||||
version "6.3.2"
|
||||
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.2.tgz#6f32992b332251d7b696b038990f4dc284b3b33d"
|
||||
integrity sha512-2xXbQ1LnwNS7egVm1HPhW2FyKrekolzhpM3mCwXdQr55gO+tAiY76rhb32OL9kKsW8taj++iP7C6hxlVzbnvrw==
|
||||
|
||||
word-wrap@^1.2.3:
|
||||
version "1.2.3"
|
||||
|
||||
Reference in new issue
Block a user