Merge pull request #1813 from spotify/rugvip/cli-common

cli: separate out path discovery logic that's common to CLIs, backend and config
This commit is contained in:
Patrik Oldsberg
2020-08-05 12:10:25 +02:00
committed by GitHub
36 changed files with 266 additions and 470 deletions
+10 -6
View File
@@ -73,6 +73,8 @@ async function buildDistWorkspace(workspaceName, rootDir) {
'@backstage/core',
'@backstage/dev-utils',
'@backstage/test-utils',
// We don't use the backend itself, but want all of its dependencies
'example-backend',
]);
print('Pinning yarn version in workspace');
@@ -180,13 +182,15 @@ async function overrideModuleResolutions(appDir, workspaceDir) {
pkgJson.resolutions = pkgJson.resolutions || {};
pkgJson.dependencies = pkgJson.dependencies || {};
const packageNames = await fs.readdir(resolvePath(workspaceDir, 'packages'));
for (const name of packageNames) {
const pkgPath = joinPath('..', 'workspace', 'packages', name);
for (const dir of ['packages', 'plugins']) {
const packageNames = await fs.readdir(resolvePath(workspaceDir, dir));
for (const name of packageNames) {
const pkgPath = joinPath('..', 'workspace', dir, name);
pkgJson.dependencies[`@backstage/${name}`] = `file:${pkgPath}`;
pkgJson.resolutions[`@backstage/${name}`] = `file:${pkgPath}`;
delete pkgJson.devDependencies[`@backstage/${name}`];
pkgJson.dependencies[`@backstage/${name}`] = `file:${pkgPath}`;
pkgJson.resolutions[`@backstage/${name}`] = `file:${pkgPath}`;
delete pkgJson.devDependencies[`@backstage/${name}`];
}
}
fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
}
+1
View File
@@ -29,6 +29,7 @@
"backstage-cli": "bin/backstage-cli"
},
"dependencies": {
"@backstage/cli-common": "^0.1.1-alpha.16",
"@backstage/config": "^0.1.1-alpha.16",
"@backstage/config-loader": "^0.1.1-alpha.16",
"@hot-loader/react-dom": "^16.13.0",
+2 -1
View File
@@ -17,10 +17,11 @@
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { buildBundle } from '../../lib/bundler';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const appConfigs = await loadConfig({ rootPath: paths.targetRoot });
await buildBundle({
entry: 'src/index',
statsJsonEnabled: cmd.stats,
+2 -1
View File
@@ -17,10 +17,11 @@
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { serveBundle } from '../../lib/bundler';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const appConfigs = await loadConfig({ rootPath: paths.targetRoot });
const waitForExit = await serveBundle({
entry: 'src/index',
checksEnabled: cmd.check,
+2 -1
View File
@@ -17,10 +17,11 @@
import { ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
import { Command } from 'commander';
import { paths } from '../../lib/paths';
import { serveBackend } from '../../lib/bundler/backend';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const appConfigs = await loadConfig({ rootPath: paths.targetRoot });
const waitForExit = await serveBackend({
entry: 'src/index',
checksEnabled: cmd.check,
+2 -1
View File
@@ -17,10 +17,11 @@
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { buildBundle } from '../../lib/bundler';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const appConfigs = await loadConfig({ rootPath: paths.targetRoot });
await buildBundle({
entry: 'dev/index',
statsJsonEnabled: cmd.stats,
+2 -1
View File
@@ -17,10 +17,11 @@
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { serveBundle } from '../../lib/bundler';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const appConfigs = await loadConfig({ rootPath: paths.targetRoot });
const waitForExit = await serveBundle({
entry: 'dev/index',
checksEnabled: cmd.check,
+1 -1
View File
@@ -136,7 +136,7 @@ async function findTargetPackages(pkgNames: string[]): Promise<LernaPackage[]> {
// Don't include dependencies of packages that are marked as bundled
if (!node.pkg.get('bundled')) {
const pkgDeps = Object.keys(node.pkg.dependencies);
const pkgDeps = Object.keys(node.pkg.dependencies ?? {});
const localDeps: string[] = Array.from(node.localDependencies.keys());
const filteredDeps = localDeps.filter(dep => pkgDeps.includes(dep));
+2 -131
View File
@@ -14,135 +14,6 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { dirname, resolve as resolvePath } from 'path';
import { findPaths } from '@backstage/cli-common';
export type ResolveFunc = (...paths: string[]) => string;
// Common paths and resolve functions used by the cli.
// Currently assumes it is being executed within a monorepo.
export type Paths = {
// Root dir of the cli itself, containing package.json
ownDir: string;
// Monorepo root dir of the cli itself. Only accessible when running inside Backstage repo.
ownRoot: string;
// The location of the app that the cli is being executed in
targetDir: string;
// The monorepo root package of the app that the cli is being executed in.
targetRoot: string;
// Resolve a path relative to own repo
resolveOwn: ResolveFunc;
// Resolve a path relative to own monorepo root. Only accessible when running inside Backstage repo.
resolveOwnRoot: ResolveFunc;
// Resolve a path relative to the app
resolveTarget: ResolveFunc;
// Resolve a path relative to the app repo root
resolveTargetRoot: ResolveFunc;
};
// Looks for a package.json with a workspace config to identify the root of the monorepo
export function findRootPath(topPath: string): string {
let path = topPath;
// Some sanity check to avoid infinite loop
for (let i = 0; i < 1000; i++) {
const packagePath = resolvePath(path, 'package.json');
const exists = fs.pathExistsSync(packagePath);
if (exists) {
try {
const data = fs.readJsonSync(packagePath);
if (data.workspaces?.packages) {
return path;
}
} catch (error) {
throw new Error(
`Failed to parse package.json file while searching for root, ${error}`,
);
}
}
const newPath = dirname(path);
if (newPath === path) {
return topPath; // We didn't find any root package.json, assume we're not in a monorepo
}
path = newPath;
}
throw new Error(
`Iteration limit reached when searching for root package.json at ${topPath}`,
);
}
// Finds the root of the cli package itself
export function findOwnDir() {
// Known relative locations of package in dist/dev
const pathDist = '..';
const pathDev = '../..';
// Check the closest dir first
const pkgInDist = resolvePath(__dirname, pathDist, 'package.json');
const isDist = fs.pathExistsSync(pkgInDist);
const path = isDist ? pathDist : pathDev;
return resolvePath(__dirname, path);
}
// Finds the root of the monorepo that the cli exists in. Only accessible when running inside Backstage repo.
export function findOwnRootPath(ownDir: string) {
const isLocal = fs.pathExistsSync(resolvePath(ownDir, 'src'));
if (!isLocal) {
throw new Error(
'Tried to access monorepo package root dir outside of Backstage repository',
);
}
return resolvePath(ownDir, '../..');
}
export function findPaths(): Paths {
const ownDir = findOwnDir();
const targetDir = fs.realpathSync(process.cwd());
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
let ownRoot = '';
const getOwnRoot = () => {
if (!ownRoot) {
ownRoot = findOwnRootPath(ownDir);
}
return ownRoot;
};
// We're not always running in a monorepo, so we lazy init this to only crash commands
// that require a monorepo when we're not in one.
let targetRoot = '';
const getTargetRoot = () => {
if (!targetRoot) {
targetRoot = findRootPath(targetDir);
}
return targetRoot;
};
return {
ownDir,
get ownRoot() {
return getOwnRoot();
},
targetDir,
get targetRoot() {
return getTargetRoot();
},
resolveOwn: (...paths) => resolvePath(ownDir, ...paths),
resolveOwnRoot: (...paths) => resolvePath(getOwnRoot(), ...paths),
resolveTarget: (...paths) => resolvePath(targetDir, ...paths),
resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths),
};
}
export const paths = findPaths();
export const paths = findPaths(__dirname);