Remove CLI-related changes.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2022-11-22 14:42:57 -05:00
parent 83696693d3
commit 63310e3987
18 changed files with 11 additions and 406 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
'@backstage/core-app-api': patch
---
Apps will now detect when a relative `backend.baseUrl` is provided and update the config accordingly.
Apps will now detect when a relative `backend.baseUrl` or `app.baseUrl` is provided and update the config to produce a full URL.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/app-defaults': minor
---
Added support for parsing relative backend urls to the api factory. Allows relative backend urls to be specified in config options.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': patch
---
Apps will now detect when a relative `backend.baseUrl` or `app.baseUrl` is provided and update it to be a full URL. This means that you can provide relative URLs and they will be resolved as expected across the application.
+1 -2
View File
@@ -154,7 +154,6 @@
"@types/terser-webpack-plugin": "^5.0.4",
"@types/yarnpkg__lockfile": "^1.1.4",
"del": "^6.0.0",
"get-port": "^6.1.2",
"mock-fs": "^5.1.0",
"msw": "^0.49.0",
"nodemon": "^2.0.2",
@@ -273,4 +272,4 @@
}
}
}
}
}
@@ -1,6 +0,0 @@
app:
baseUrl: http://localhost:${PORT}/test
title: test
backend:
baseUrl: http://localhost:${BACKEND_PORT}
@@ -1,3 +0,0 @@
{
"version": "1.0.0"
}
@@ -1,19 +0,0 @@
{
"name": "root",
"version": "1.0.0",
"private": true,
"engines": {
"node": "14 || 16"
},
"workspaces": {
"packages": [
"packages/*"
]
},
"devDependencies": {
"@backstage/cli": "^0.20.0-next.0",
"lerna": "^4.0.0",
"node-gyp": "^9.0.0",
"typescript": "~4.6.4"
}
}
@@ -1,12 +0,0 @@
{
"name": "app",
"version": "0.0.0",
"private": true,
"bundled": true,
"backstage": {
"role": "frontend"
},
"files": [
"dist"
]
}
@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
</head>
<body>
<div id="root"></div>
</body>
</html>
@@ -1,17 +0,0 @@
/*
* 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.
*/
export {};
console.log('Hello World.');
@@ -29,7 +29,6 @@ export async function command(opts: OptionValues): Promise<void> {
targetDir: paths.targetDir,
configPaths: opts.config as string[],
writeStats: Boolean(opts.stats),
cliOptions: opts,
});
}
if (role === 'backend') {
-8
View File
@@ -126,14 +126,6 @@ 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 <path>',
'Public path for hosting the website on, can be relative.',
)
.option(
'--backend-url <url>',
'Backend url, expects just the origin or sub-route. Do not include /api. Can be relative.',
)
.option(
'--config <path>',
'Config files to load instead of app-config.yaml. Applies to app packages only.',
-1
View File
@@ -153,7 +153,6 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
return;
}
await buildFrontend({
cliOptions: opts,
targetDir: pkg.dir,
configPaths: (buildOptions.config as string[]) ?? [],
writeStats: Boolean(buildOptions.stats),
+3 -3
View File
@@ -36,12 +36,12 @@ import { runPlain } from '../run';
import ESLintPlugin from 'eslint-webpack-plugin';
import pickBy from 'lodash/pickBy';
const DUMMY_URL = 'http://dummyurl.org';
const NO_OP_URL = 'http://test-site-asdf.org';
export function resolveBaseUrl(config: Config): URL {
const baseUrl = config.getString('app.baseUrl');
try {
return new URL(baseUrl, DUMMY_URL);
return new URL(baseUrl, NO_OP_URL);
} catch (error) {
throw new Error(`Invalid app.baseUrl, ${error}`);
}
@@ -90,7 +90,7 @@ export async function createConfig(
const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir));
const baseUrl = frontendConfig.getString('app.baseUrl');
const validBaseUrl = new URL(baseUrl, DUMMY_URL);
const validBaseUrl = new URL(baseUrl, NO_OP_URL);
const publicPath = validBaseUrl.pathname.replace(/\/$/, '');
if (checksEnabled) {
plugins.push(
-106
View File
@@ -1,106 +0,0 @@
/*
* 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 './config';
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: '/',
}),
).toEqual([
{
data: {
backend: {
baseUrl: '/',
},
},
context: 'cli',
},
]);
});
it('should return app.baseUrl when publicPath present in cli options', () => {
expect(
readCliConfig({
publicPath: '/',
}),
).toEqual([
{
data: {
app: {
baseUrl: '/',
},
},
context: 'cli',
},
]);
});
it('should return app.baseUrl and backend.baseUrl when publicPath and backendUrl present in cli options', () => {
expect(
readCliConfig({
publicPath: '/',
backendUrl: '/api',
}),
).toEqual([
{
data: {
app: {
baseUrl: '/',
},
backend: {
baseUrl: '/api',
},
},
context: 'cli',
},
]);
});
it('should throw for public paths that do NOT start with /', () => {
['http://localhost:3000', './', '../..'].forEach(publicPath =>
expect(() => {
readCliConfig({
publicPath,
});
}).toThrow('Public path must be relative'),
);
});
it('should throw for backend urls that do NOT start with /', () => {
['http://localhost:3000', './', '../..'].forEach(backendUrl =>
expect(() => {
readCliConfig({
backendUrl,
});
}).toThrow('Backend URL must be relative'),
);
});
});
+1 -48
View File
@@ -19,17 +19,11 @@ import {
loadConfig,
loadConfigSchema,
} from '@backstage/config-loader';
import { AppConfig, ConfigReader } from '@backstage/config';
import { ConfigReader } from '@backstage/config';
import { paths } from './paths';
import { isValidUrl } from './urls';
import { getPackages } from '@manypkg/get-packages';
import { PackageGraph } from './monorepo';
import { JsonObject } from '@backstage/types';
export type CliConfigOptions = {
publicPath?: string;
backendUrl?: string;
};
type Options = {
args: string[];
@@ -38,44 +32,8 @@ type Options = {
withFilteredKeys?: boolean;
withDeprecatedKeys?: boolean;
fullVisibility?: boolean;
cliOptions?: CliConfigOptions;
};
/**
* Read specific parameters from the CLI and add them to the build config.
* @param opts CLI passed parameters.
* @returns Array of config, empty if there is no relevant passed in cli options.
*
* @public
*/
export function readCliConfig(opts?: CliConfigOptions): AppConfig[] {
if (!opts || Object.keys(opts).length === 0) return [];
const data: JsonObject = {};
if (opts.publicPath?.startsWith('/')) {
data.app = {
baseUrl: opts.publicPath,
};
} else if (opts.publicPath) {
throw new Error(
'Public path must be relative and start with "/" when specified through CLI options. Path traversals like "./" and assumed relative endpoints like "backstage" are not supported.',
);
}
if (opts.backendUrl?.startsWith('/')) {
data.backend = {
baseUrl: opts.backendUrl,
};
} else if (opts.backendUrl) {
throw new Error(
'Backend URL must be relative and start with "/" when specified through CLI options. Path traversals like "./" and assumed relative endpoint like "api" are not supported.',
);
}
if (Object.keys(data).length === 0) return [];
return [{ data, context: 'cli' }];
}
export async function loadCliConfig(options: Options) {
const configTargets: ConfigTarget[] = [];
options.args.forEach(arg => {
@@ -114,8 +72,6 @@ export async function loadCliConfig(options: Options) {
packagePaths: [paths.resolveTargetRoot('package.json')],
});
const cliConfigs = readCliConfig(options.cliOptions);
const { appConfigs } = await loadConfig({
experimentalEnvFunc: options.mockEnv
? async name => process.env[name] || 'x'
@@ -124,9 +80,6 @@ export async function loadCliConfig(options: Options) {
configTargets: configTargets,
});
// Add the cliConfigs to the end of the appConfigs array for final overriding.
appConfigs.push(...cliConfigs);
// printing to stderr to not clobber stdout in case the cli command
// outputs structured data (e.g. as config:schema does)
process.stderr.write(
-163
View File
@@ -1,163 +0,0 @@
/*
* 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 { spawn, SpawnOptionsWithoutStdio } from 'child_process';
import EventEmitter from 'events';
import fetch from 'node-fetch';
import path from 'path';
import getPort from 'get-port';
jest.setTimeout(150000);
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));
});
/**
* Set an interval to check if we should kill the process.
* This was the easiest way I could think of of testing across two processes.
*/
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);
}
const clearEventInterval = () => {
if (intervalId) {
try {
clearInterval(intervalId);
} catch (err) {
console.error(err);
}
}
};
/**
* Need a way to kill the process from another process.
*/
events?.on('stop', signal => {
clearEventInterval();
proc.kill(signal);
});
proc.on('error', (...errorArgs) => {
clearEventInterval();
reject(errorArgs);
});
proc.on('exit', code => {
clearEventInterval();
resolve({
exit: code ?? 0,
stdout: Buffer.concat(stdout).toString('utf8'),
stderr: Buffer.concat(stderr).toString('utf8'),
});
});
});
};
const testProjectDir = path.resolve(
__dirname,
'__fixtures__/test-project/packages/app',
);
describe('end-to-end', () => {
const entryPoint = path.resolve(__dirname, '../bin/backstage-cli');
it('builds frontend with correct url overrides', async () => {
const buildProc = await executeCommand(
entryPoint,
['package', 'build', '--public-path', '/test', '--backend-url', '/api'],
{
cwd: testProjectDir,
},
);
expect(buildProc.stderr).toContain(
'Loaded config from app-config.yaml, cli',
);
expect(buildProc.exit).toEqual(0);
});
it('starts frontend on correct url', async () => {
expect.assertions(4);
const startEmitter = new EventEmitter();
const frontendPort = await getPort();
startEmitter.on('hit', async () => {
const response = await fetch(
`http://localhost:${frontendPort}/test/catalog`,
);
const text = await response.text();
startEmitter.emit('stop', 'SIGINT');
expect(response.status).toBe(200);
expect(text.length).toBeGreaterThan(0);
expect(text).toContain('id="root"');
});
const startProc = await executeCommand(
entryPoint,
['package', 'start'],
{
cwd: testProjectDir,
env: {
...process.env,
PORT: `${frontendPort}`,
BACKEND_PORT: `${await getPort()}`,
},
},
startEmitter,
{
// Need to match console colors as well, so use .* instead of just ' '.
signalRegex: [/compiled.*successfully/],
},
);
expect(startProc.exit).toEqual(0);
});
});