From cca591ee96cfc910843e1857f490f98f514517e2 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Tue, 29 Nov 2022 12:55:29 -0500 Subject: [PATCH] Add e2e test case to test cli build options and webpack subroute handling. Signed-off-by: Aramis Sennyey --- packages/cli/e2e-test.config.js | 22 +++ packages/cli/e2e-tests/serve.test.ts | 160 ++++++++++++++++++ .../e2e-tests/test-project/app-config.yaml | 6 + .../cli/e2e-tests/test-project/backstage.json | 3 + .../cli/e2e-tests/test-project/package.json | 54 ++++++ .../test-project/packages/app/package.json | 82 +++++++++ .../packages/app/public/index.html | 79 +++++++++ .../test-project/packages/app/src/index.tsx | 22 +++ .../packages/backend/package.json | 51 ++++++ .../packages/backend/src/index.ts | 15 ++ packages/cli/e2e-tests/test-project/yarn.lock | 0 packages/cli/package.json | 5 +- packages/cli/src/commands/build/command.ts | 1 + packages/cli/src/commands/index.ts | 8 + packages/cli/src/commands/repo/build.ts | 5 +- packages/cli/src/lib/bundler/server.ts | 2 +- packages/config-loader/src/lib/cli.test.ts | 86 ++++++++++ packages/config-loader/src/lib/cli.ts | 2 + yarn.lock | 1 + 19 files changed, 598 insertions(+), 6 deletions(-) create mode 100644 packages/cli/e2e-test.config.js create mode 100644 packages/cli/e2e-tests/serve.test.ts create mode 100644 packages/cli/e2e-tests/test-project/app-config.yaml create mode 100644 packages/cli/e2e-tests/test-project/backstage.json create mode 100644 packages/cli/e2e-tests/test-project/package.json create mode 100644 packages/cli/e2e-tests/test-project/packages/app/package.json create mode 100644 packages/cli/e2e-tests/test-project/packages/app/public/index.html create mode 100644 packages/cli/e2e-tests/test-project/packages/app/src/index.tsx create mode 100644 packages/cli/e2e-tests/test-project/packages/backend/package.json create mode 100644 packages/cli/e2e-tests/test-project/packages/backend/src/index.ts create mode 100644 packages/cli/e2e-tests/test-project/yarn.lock create mode 100644 packages/config-loader/src/lib/cli.test.ts diff --git a/packages/cli/e2e-test.config.js b/packages/cli/e2e-test.config.js new file mode 100644 index 0000000000..1c1cf7a75b --- /dev/null +++ b/packages/cli/e2e-test.config.js @@ -0,0 +1,22 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); + +module.exports = require('./config/jest').then(baseConfig => ({ + ...baseConfig, + rootDir: path.resolve(__dirname, 'e2e-tests'), +})); diff --git a/packages/cli/e2e-tests/serve.test.ts b/packages/cli/e2e-tests/serve.test.ts new file mode 100644 index 0000000000..b18ff64251 --- /dev/null +++ b/packages/cli/e2e-tests/serve.test.ts @@ -0,0 +1,160 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { execSync, spawn, SpawnOptionsWithoutStdio } from 'child_process'; +import EventEmitter from 'events'; +import mock from 'mock-fs'; +import fetch from 'node-fetch'; +import path from 'path'; + +const executeCommand = ( + command: string, + args: string[], + options?: SpawnOptionsWithoutStdio, + events?: EventEmitter, + eventConfig?: { + killRegex?: RegExp; + signalRegex?: RegExp[]; + }, +): Promise<{ + exit: number; + stdout: string; + stderr: string; +}> => { + return new Promise((resolve, reject) => { + const stdout: Buffer[] = []; + const stderr: Buffer[] = []; + + const shell = process.platform === 'win32'; + const proc = spawn(command, args, { ...options, shell }); + + proc.stdout?.on('data', data => { + stdout.push(Buffer.from(data)); + }); + + proc.stderr?.on('data', data => { + stderr.push(Buffer.from(data)); + }); + let intervalId: NodeJS.Timer | undefined = undefined; + if (eventConfig) { + intervalId = setInterval(() => { + const stdoutStr = Buffer.concat(stdout).toString('utf8'); + if (eventConfig.killRegex?.test(stdoutStr)) { + proc.kill('SIGINT'); + } else if ( + eventConfig.signalRegex?.some(regex => regex.test(stdoutStr)) + ) { + events?.emit('hit'); + } + }, 1000); + } + + events?.on('stop', signal => { + console.log(signal); + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + proc.kill(signal); + }); + + proc.on('error', (...errorArgs) => { + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + reject(errorArgs); + }); + proc.on('exit', code => { + if (intervalId) { + try { + clearInterval(intervalId); + } catch (err) { + console.error(err); + } + } + resolve({ + exit: code ?? 0, + stdout: Buffer.concat(stdout).toString('utf8'), + stderr: Buffer.concat(stderr).toString('utf8'), + }); + }); + }); +}; + +const timeout = 40000; + +jest.setTimeout(timeout * 2); + +describe('end-to-end', () => { + const entryPoint = path.resolve(__dirname, '../bin/backstage-cli'); + + it.skip('shows help text', async () => { + const proc = await executeCommand(entryPoint, ['--help']); + expect(proc.stdout).toContain('Usage: backstage-cli [options]'); + expect(proc.exit).toEqual(0); + }); + + it('builds frontend with correct url overrides', async () => { + const cwd = path.resolve(__dirname, 'test-project/packages/app'); + const buildProc = await executeCommand( + entryPoint, + ['package', 'build', '--public-path', '/test', '--backend-url', '/api'], + { + cwd, + }, + ); + expect(buildProc.stderr).toContain( + 'Loaded config from app-config.yaml, cli', + ); + + console.log(buildProc.stderr, buildProc.stdout); + + expect(buildProc.exit).toEqual(0); + }); + + it('starts frontend on correct url', async () => { + const cwd = path.resolve(__dirname, 'test-project/packages/app'); + + const startEmitter = new EventEmitter(); + startEmitter.on('hit', async () => { + const response = await fetch('http://localhost:3000/test/catalog'); + const text = await response.text(); + startEmitter.emit('stop', 'SIGINT'); + expect(response.status).toBe(200); + expect(text.length).toBeGreaterThan(0); + }); + const startProc = await executeCommand( + entryPoint, + ['package', 'start'], + { + cwd, + }, + startEmitter, + { + signalRegex: [/webpack compiled/], + }, + ); + + expect(startProc.exit).toEqual(0); + }); +}); diff --git a/packages/cli/e2e-tests/test-project/app-config.yaml b/packages/cli/e2e-tests/test-project/app-config.yaml new file mode 100644 index 0000000000..477580da86 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/app-config.yaml @@ -0,0 +1,6 @@ +app: + baseUrl: http://localhost:3000/test + title: test + +backend: + baseUrl: http://localhost:7007 \ No newline at end of file diff --git a/packages/cli/e2e-tests/test-project/backstage.json b/packages/cli/e2e-tests/test-project/backstage.json new file mode 100644 index 0000000000..3e73a3c875 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/backstage.json @@ -0,0 +1,3 @@ +{ + "version": "1.7.0-next.0" +} diff --git a/packages/cli/e2e-tests/test-project/package.json b/packages/cli/e2e-tests/test-project/package.json new file mode 100644 index 0000000000..7ebf258618 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/package.json @@ -0,0 +1,54 @@ +{ + "name": "root", + "version": "1.0.0", + "private": true, + "engines": { + "node": "14 || 16" + }, + "scripts": { + "dev": "concurrently \"yarn start\" \"yarn start-backend\"", + "start": "yarn workspace app start", + "start-backend": "yarn workspace backend start", + "build": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", + "tsc": "tsc", + "tsc:full": "tsc --skipLibCheck false --incremental false", + "clean": "backstage-cli repo clean", + "test": "backstage-cli repo test", + "test:all": "backstage-cli repo test --coverage", + "lint": "backstage-cli repo lint --since origin/master", + "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", + "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" + }, + "workspaces": { + "packages": [ + "packages/*", + "plugins/*" + ] + }, + "devDependencies": { + "@backstage/cli": "^0.20.0-next.0", + "@spotify/prettier-config": "^12.0.0", + "concurrently": "^6.0.0", + "lerna": "^4.0.0", + "node-gyp": "^9.0.0", + "prettier": "^2.3.2", + "typescript": "~4.6.4" + }, + "resolutions": { + "@types/react": "^17", + "@types/react-dom": "^17" + }, + "prettier": "@spotify/prettier-config", + "lint-staged": { + "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} diff --git a/packages/cli/e2e-tests/test-project/packages/app/package.json b/packages/cli/e2e-tests/test-project/packages/app/package.json new file mode 100644 index 0000000000..1157a698f5 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/app/package.json @@ -0,0 +1,82 @@ +{ + "name": "app", + "version": "0.0.0", + "private": true, + "bundled": true, + "backstage": { + "role": "frontend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint", + "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", + "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", + "cy:dev": "cypress open", + "cy:run": "cypress run --browser chrome" + }, + "dependencies": { + "@backstage/app-defaults": "^1.0.7-next.0", + "@backstage/catalog-model": "^1.1.2-next.0", + "@backstage/cli": "^0.20.0-next.0", + "@backstage/core-app-api": "^1.1.1-next.0", + "@backstage/core-components": "^0.11.2-next.0", + "@backstage/core-plugin-api": "^1.0.7-next.0", + "@backstage/integration-react": "^1.1.5-next.0", + "@backstage/plugin-api-docs": "^0.8.10-next.0", + "@backstage/plugin-catalog": "^1.5.2-next.0", + "@backstage/plugin-catalog-common": "^1.0.7-next.0", + "@backstage/plugin-catalog-graph": "^0.2.22-next.0", + "@backstage/plugin-catalog-import": "^0.8.13-next.0", + "@backstage/plugin-catalog-react": "^1.1.5-next.0", + "@backstage/plugin-github-actions": "^0.5.10-next.0", + "@backstage/plugin-org": "^0.5.10-next.0", + "@backstage/plugin-permission-react": "^0.4.6-next.0", + "@backstage/plugin-scaffolder": "^1.7.0-next.0", + "@backstage/plugin-search": "^1.0.3-next.0", + "@backstage/plugin-search-react": "^1.1.1-next.0", + "@backstage/plugin-tech-radar": "^0.5.17-next.0", + "@backstage/plugin-techdocs": "^1.3.3-next.0", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.5-next.0", + "@backstage/plugin-techdocs-react": "^1.0.5-next.0", + "@backstage/plugin-user-settings": "^0.5.0-next.0", + "@backstage/theme": "^0.2.16", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "history": "^5.0.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router": "^6.3.0", + "react-router-dom": "^6.3.0", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/test-utils": "^1.2.1-next.0", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "^16.11.26", + "@types/react-dom": "*", + "cross-env": "^7.0.0", + "cypress": "^9.7.0", + "eslint-plugin-cypress": "^2.10.3", + "start-server-and-test": "^1.10.11" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/e2e-tests/test-project/packages/app/public/index.html b/packages/cli/e2e-tests/test-project/packages/app/public/index.html new file mode 100644 index 0000000000..a936c73602 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/app/public/index.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + <%= config.getString('app.title') %> + <% if (config.has('app.googleAnalyticsTrackingId')) { %> + + + <% } %> + + + +
+ + + diff --git a/packages/cli/e2e-tests/test-project/packages/app/src/index.tsx b/packages/cli/e2e-tests/test-project/packages/app/src/index.tsx new file mode 100644 index 0000000000..145adc1f48 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/app/src/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@backstage/cli/asset-types'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render(
, document.getElementById('root')); diff --git a/packages/cli/e2e-tests/test-project/packages/backend/package.json b/packages/cli/e2e-tests/test-project/packages/backend/package.json new file mode 100644 index 0000000000..5c649f08b6 --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/backend/package.json @@ -0,0 +1,51 @@ +{ + "name": "backend", + "version": "0.0.0", + "main": "dist/index.cjs.js", + "types": "src/index.ts", + "private": true, + "backstage": { + "role": "backend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.15.0", + "@backstage/backend-tasks": "^0.3.4", + "@backstage/catalog-client": "^1.0.4", + "@backstage/catalog-model": "^1.1.0", + "@backstage/config": "^1.0.1", + "@backstage/plugin-app-backend": "^0.3.35", + "@backstage/plugin-auth-backend": "^0.15.1", + "@backstage/plugin-catalog-backend": "^1.3.1", + "@backstage/plugin-permission-common": "^0.6.3", + "@backstage/plugin-permission-node": "^0.6.4", + "@backstage/plugin-proxy-backend": "^0.2.29", + "@backstage/plugin-scaffolder-backend": "^1.5.0", + "@backstage/plugin-search-backend": "^1.0.1", + "@backstage/plugin-search-backend-module-pg": "^0.3.6", + "@backstage/plugin-search-backend-node": "^1.0.1", + "@backstage/plugin-techdocs-backend": "^1.2.1", + "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "pg": "^8.3.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.18.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", + "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4", + "better-sqlite3": "^7.5.0" + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/e2e-tests/test-project/packages/backend/src/index.ts b/packages/cli/e2e-tests/test-project/packages/backend/src/index.ts new file mode 100644 index 0000000000..b61d59e88d --- /dev/null +++ b/packages/cli/e2e-tests/test-project/packages/backend/src/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/packages/cli/e2e-tests/test-project/yarn.lock b/packages/cli/e2e-tests/test-project/yarn.lock new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cli/package.json b/packages/cli/package.json index 5a65998301..d605c6f31f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,7 +24,9 @@ "lint": "backstage-cli package lint", "test": "backstage-cli package test", "clean": "backstage-cli package clean", - "start": "nodemon --" + "start": "nodemon --", + "test:e2e": "backstage-cli test --config e2e-test.config.js", + "test:e2e:ci": "backstage-cli test --config e2e-test.config.js --watchAll=false --ci" }, "bin": { "backstage-cli": "bin/backstage-cli" @@ -153,6 +155,7 @@ "@types/tar": "^6.1.1", "@types/terser-webpack-plugin": "^5.0.4", "@types/yarnpkg__lockfile": "^1.1.4", + "cross-fetch": "^3.1.5", "del": "^6.0.0", "mock-fs": "^5.1.0", "msw": "^0.49.0", diff --git a/packages/cli/src/commands/build/command.ts b/packages/cli/src/commands/build/command.ts index fcaf58e1ee..b5120f7ae0 100644 --- a/packages/cli/src/commands/build/command.ts +++ b/packages/cli/src/commands/build/command.ts @@ -29,6 +29,7 @@ export async function command(opts: OptionValues): Promise { targetDir: paths.targetDir, configPaths: opts.config as string[], writeStats: Boolean(opts.stats), + cliOptions: opts, }); } if (role === 'backend') { diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 33291cb70c..90d3afd4d5 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -134,6 +134,14 @@ export function registerScriptCommand(program: Command) { '--stats', 'If bundle stats are available, write them to the output directory. Applies to app packages only.', ) + .option( + '--public-path ', + 'Public path for hosting the website on, can be relative.', + ) + .option( + '--backend-url ', + 'Backend url, expects just the origin or sub-route. Do not include /api. Can be relative.', + ) .option( '--config ', 'Config files to load instead of app-config.yaml. Applies to app packages only.', diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 86d2e781a6..55574a53b0 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -153,10 +153,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { return; } await buildFrontend({ - cliOptions: { - publicPath: opts.publicPath, - backendUrl: opts.backendUrl, - }, + cliOptions: opts, targetDir: pkg.dir, configPaths: (buildOptions.config as string[]) ?? [], writeStats: Boolean(buildOptions.stats), diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 1e76fbc662..cef55d3265 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -62,7 +62,7 @@ export async function serveBundle(options: ServeOptions) { disableDotRule: true, // The index needs to be rewritten relative to the new public path, including subroutes. - index: config.output?.publicPath ?? '/index.html', + index: `${config.output?.publicPath}index.html`, }, https: url.protocol === 'https:' diff --git a/packages/config-loader/src/lib/cli.test.ts b/packages/config-loader/src/lib/cli.test.ts new file mode 100644 index 0000000000..4f4e76142a --- /dev/null +++ b/packages/config-loader/src/lib/cli.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { readCliConfig } from './cli'; + +describe('readCliConfig', () => { + it('should return empty config for empty cli', () => { + expect(readCliConfig({})).toEqual([]); + }); + + it('should return empty config for no matching keys', () => { + expect( + readCliConfig({ + test: '123', + } as any), + ).toEqual([]); + }); + + it('should return backend.baseUrl when backendUrl present in cli options', () => { + expect( + readCliConfig({ + backendUrl: 'http://localhost:3000', + }), + ).toEqual([ + { + data: { + backend: { + baseUrl: 'http://localhost:3000', + }, + }, + context: 'cli', + }, + ]); + }); + + it('should return app.baseUrl when publicPath present in cli options', () => { + expect( + readCliConfig({ + publicPath: 'http://localhost:3000', + }), + ).toEqual([ + { + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + }, + context: 'cli', + }, + ]); + }); + + it('should return app.baseUrl and backend.baseUrl when publicPath and backendUrl present in cli options', () => { + expect( + readCliConfig({ + publicPath: 'http://localhost:3000', + backendUrl: 'http://localhost:3000/api', + }), + ).toEqual([ + { + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + backend: { + baseUrl: 'http://localhost:3000/api', + }, + }, + context: 'cli', + }, + ]); + }); +}); diff --git a/packages/config-loader/src/lib/cli.ts b/packages/config-loader/src/lib/cli.ts index 9d935e2826..5e6eacf293 100644 --- a/packages/config-loader/src/lib/cli.ts +++ b/packages/config-loader/src/lib/cli.ts @@ -45,5 +45,7 @@ export function readCliConfig(opts?: CliConfigOptions): AppConfig[] { }; } + if (Object.keys(data).length === 0) return []; + return [{ data, context: 'cli' }]; } diff --git a/yarn.lock b/yarn.lock index 621ed3dd96..7fa022e4c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3653,6 +3653,7 @@ __metadata: chalk: ^4.0.0 chokidar: ^3.3.1 commander: ^9.1.0 + cross-fetch: ^3.1.5 css-loader: ^6.5.1 del: ^6.0.0 diff: ^5.0.0