Merge pull request #453 from spotify/rugvip/paths
packages/cli: add utility for resolving paths and use everywhere
This commit is contained in:
@@ -19,6 +19,7 @@ import { resolve as resolvePath, relative as relativePath } from 'path';
|
||||
import { runPlain, runCheck } from '../../helpers/run';
|
||||
import { Options } from './options';
|
||||
import { extractArchive, createArchive } from './archive';
|
||||
import { paths } from '../../helpers/paths';
|
||||
|
||||
const INFO_FILE = '.backstage-build-cache';
|
||||
|
||||
@@ -58,8 +59,8 @@ type CacheInfo = {
|
||||
export class Cache {
|
||||
// Read the current cache state form the filesystem.
|
||||
static async read(options: Options) {
|
||||
const repoPath = relativePath(options.repoRoot, process.cwd());
|
||||
const location = resolvePath(options.cacheDir, repoPath);
|
||||
const relativePackagePath = relativePath(paths.targetRoot, paths.targetDir);
|
||||
const location = resolvePath(options.cacheDir, relativePackagePath);
|
||||
|
||||
const outputInfo = await readCacheInfo(options.output);
|
||||
const localKey = outputInfo?.key;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { Command } from 'commander';
|
||||
import { runPlain } from '../../helpers/run';
|
||||
import { paths } from '../../helpers/paths';
|
||||
|
||||
const DEFAULT_MAX_ENTRIES = 10;
|
||||
|
||||
@@ -25,13 +25,11 @@ export type Options = {
|
||||
output: string;
|
||||
cacheDir: string;
|
||||
maxCacheEntries: number;
|
||||
repoRoot: string;
|
||||
};
|
||||
|
||||
export async function parseOptions(cmd: Command): Promise<Options> {
|
||||
const repoRoot = await runPlain('git rev-parse --show-toplevel');
|
||||
const argTransformer = (arg: string) =>
|
||||
resolvePath(arg.replace(/<repoRoot>/g, repoRoot).replace(/'/g, ''));
|
||||
resolvePath(arg.replace(/<repoRoot>/g, paths.targetRoot).replace(/'/g, ''));
|
||||
|
||||
const inputs = cmd.input.map(argTransformer) as string[];
|
||||
if (inputs.length === 0) {
|
||||
@@ -43,5 +41,5 @@ export async function parseOptions(cmd: Command): Promise<Options> {
|
||||
);
|
||||
const maxCacheEntries =
|
||||
Number(process.env.BACKSTAGE_CACHE_MAX_ENTRIES) || DEFAULT_MAX_ENTRIES;
|
||||
return { inputs, output, cacheDir, repoRoot, maxCacheEntries };
|
||||
return { inputs, output, cacheDir, maxCacheEntries };
|
||||
}
|
||||
|
||||
@@ -15,20 +15,19 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import chalk from 'chalk';
|
||||
import inquirer, { Answers, Question } from 'inquirer';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { realpathSync } from 'fs';
|
||||
import os from 'os';
|
||||
import { Task, templatingTask } from '../../helpers/tasks';
|
||||
import { paths } from '../../helpers/paths';
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function checkExists(rootDir: string, name: string) {
|
||||
await Task.forItem('checking', name, async () => {
|
||||
const destination = path.join(rootDir, 'plugins', name);
|
||||
const destination = resolvePath(rootDir, name);
|
||||
|
||||
if (await fs.pathExists(destination)) {
|
||||
const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));
|
||||
@@ -106,19 +105,17 @@ export default async () => {
|
||||
];
|
||||
const answers: Answers = await inquirer.prompt(questions);
|
||||
|
||||
const rootDir = realpathSync(process.cwd());
|
||||
const cliPackage = resolvePath(__dirname, '../../..');
|
||||
const templateDir = resolvePath(cliPackage, 'templates', 'default-app');
|
||||
const tempDir = path.join(os.tmpdir(), answers.name);
|
||||
const appDir = path.join(rootDir, answers.name);
|
||||
const version = require(resolvePath(cliPackage, 'package.json')).version;
|
||||
const templateDir = paths.resolveOwn('templates/default-app');
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.name);
|
||||
const appDir = resolvePath(paths.targetDir, answers.name);
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
|
||||
Task.log();
|
||||
Task.log('Creating the app...');
|
||||
|
||||
try {
|
||||
Task.section('Checking if the directory is available');
|
||||
await checkExists(rootDir, answers.name);
|
||||
await checkExists(paths.targetDir, answers.name);
|
||||
|
||||
Task.section('Creating a temporary app directory');
|
||||
await createTemporaryAppFolder(tempDir);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import chalk from 'chalk';
|
||||
import inquirer, { Answers, Question } from 'inquirer';
|
||||
@@ -27,12 +26,13 @@ import {
|
||||
addCodeownersEntry,
|
||||
getCodeownersFilePath,
|
||||
} from './lib/codeowners';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { Task, templatingTask } from '../../helpers/tasks';
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function checkExists(rootDir: string, id: string) {
|
||||
await Task.forItem('checking', id, async () => {
|
||||
const destination = path.join(rootDir, 'plugins', id);
|
||||
const destination = resolvePath(rootDir, 'plugins', id);
|
||||
|
||||
if (await fs.pathExists(destination)) {
|
||||
const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));
|
||||
@@ -90,7 +90,7 @@ export async function addPluginDependencyToApp(
|
||||
) {
|
||||
const pluginPackage = `@backstage/plugin-${pluginName}`;
|
||||
const packageFilePath = 'packages/app/package.json';
|
||||
const packageFile = path.join(rootDir, packageFilePath);
|
||||
const packageFile = resolvePath(rootDir, packageFilePath);
|
||||
|
||||
await Task.forItem('processing', packageFilePath, async () => {
|
||||
const packageFileContent = await fs.readFile(packageFile, 'utf-8');
|
||||
@@ -173,8 +173,7 @@ export async function movePlugin(
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
const rootDir = await fs.realpath(process.cwd());
|
||||
const codeownersPath = await getCodeownersFilePath(rootDir);
|
||||
const codeownersPath = await getCodeownersFilePath(paths.targetRoot);
|
||||
|
||||
const questions: Question[] = [
|
||||
{
|
||||
@@ -220,12 +219,11 @@ export default async () => {
|
||||
|
||||
const answers: Answers = await inquirer.prompt(questions);
|
||||
|
||||
const appPackage = resolvePath(rootDir, 'packages', 'app');
|
||||
const cliPackage = resolvePath(__dirname, '..', '..', '..');
|
||||
const templateDir = resolvePath(cliPackage, 'templates', 'default-plugin');
|
||||
const tempDir = path.join(os.tmpdir(), answers.id);
|
||||
const pluginDir = path.join(rootDir, 'plugins', answers.id);
|
||||
const version = require(resolvePath(cliPackage, 'package.json')).version;
|
||||
const appPackage = paths.resolveTargetRoot('packages/app');
|
||||
const templateDir = paths.resolveOwn('templates/default-plugin');
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.id);
|
||||
const pluginDir = paths.resolveTargetRoot('plugins', answers.id);
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
const ownerIds = parseOwnerIds(answers.owner);
|
||||
|
||||
Task.log();
|
||||
@@ -233,7 +231,7 @@ export default async () => {
|
||||
|
||||
try {
|
||||
Task.section('Checking if the plugin ID is available');
|
||||
await checkExists(rootDir, answers.id);
|
||||
await checkExists(paths.targetRoot, answers.id);
|
||||
|
||||
Task.section('Creating a temporary plugin directory');
|
||||
await createTemporaryPluginFolder(tempDir);
|
||||
@@ -249,10 +247,10 @@ export default async () => {
|
||||
|
||||
if (await fs.pathExists(appPackage)) {
|
||||
Task.section('Adding plugin as dependency in app');
|
||||
await addPluginDependencyToApp(rootDir, answers.id, version);
|
||||
await addPluginDependencyToApp(paths.targetRoot, answers.id, version);
|
||||
|
||||
Task.section('Import plugin in app');
|
||||
await addPluginToApp(rootDir, answers.id);
|
||||
await addPluginToApp(paths.targetRoot, answers.id);
|
||||
}
|
||||
|
||||
if (ownerIds && ownerIds.length) {
|
||||
|
||||
@@ -15,15 +15,11 @@
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { run } from '../helpers/run';
|
||||
import { paths } from '../helpers/paths';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const args = [
|
||||
'test',
|
||||
'--config',
|
||||
resolvePath(__dirname, '../../config/jest.js'),
|
||||
];
|
||||
const args = ['test', '--config', paths.resolveOwn('config/jest.js')];
|
||||
|
||||
if (cmd.watch) {
|
||||
args.push('--watch');
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createLoggerFactory } from './logger';
|
||||
@@ -23,6 +22,7 @@ import { startWatcher, startPackageWatcher } from './watcher';
|
||||
import { startCompiler } from './compiler';
|
||||
import { startChild } from './child';
|
||||
import { waitForExit } from '../../helpers/run';
|
||||
import { paths } from '../../helpers/paths';
|
||||
|
||||
const PACKAGE_BLACKLIST = [
|
||||
// We never want to watch for changes in the cli, but all packages will depend on it.
|
||||
@@ -34,7 +34,7 @@ const WATCH_LOCATIONS = ['package.json', 'src', 'assets'];
|
||||
// Start watching for dependency changes.
|
||||
// The returned promise resolves when watchers have started for all current dependencies.
|
||||
export async function watchDeps() {
|
||||
const localPackagePath = resolvePath('package.json');
|
||||
const localPackagePath = paths.resolveTarget('package.json');
|
||||
|
||||
// Rotate through different prefix colors to make it easier to differenciate between different deps
|
||||
const logFactory = createLoggerFactory([
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { paths } from '../../helpers/paths';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function findAllDeps(
|
||||
rootPackageName: string,
|
||||
blacklist: string[],
|
||||
): Promise<Package[]> {
|
||||
const project = new LernaProject(resolvePath('.'));
|
||||
const project = new LernaProject(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const graph = new PackageGraph(packages);
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 fs from 'fs-extra';
|
||||
import { dirname, resolve as resolvePath } from 'path';
|
||||
|
||||
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;
|
||||
|
||||
// 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 the app
|
||||
resolveTarget: ResolveFunc;
|
||||
|
||||
// Resolve a path relative to the app repo root
|
||||
resolveTargetRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
// Looks for a package.json that has name: "root" 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 contents = fs.readFileSync(packagePath, 'utf8');
|
||||
const data = JSON.parse(contents);
|
||||
if (data.name === 'root') {
|
||||
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) {
|
||||
throw new Error(
|
||||
`No package.json with name "root" found as a parent of ${topPath}`,
|
||||
);
|
||||
}
|
||||
path = newPath;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Iteration limit reached when searching for root package.json at ${topPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function findPaths(): Paths {
|
||||
const ownDir = resolvePath(__dirname, '../..');
|
||||
const targetDir = fs.realpathSync(process.cwd());
|
||||
|
||||
// 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,
|
||||
targetDir,
|
||||
get targetRoot() {
|
||||
return getTargetRoot();
|
||||
},
|
||||
resolveOwn: (...paths) => resolvePath(ownDir, ...paths),
|
||||
resolveTarget: (...paths) => resolvePath(targetDir, ...paths),
|
||||
resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths),
|
||||
};
|
||||
}
|
||||
|
||||
export const paths = findPaths();
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
import program from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import createAppCommand from './commands/create-app/createApp';
|
||||
import createPluginCommand from './commands/create-plugin/createPlugin';
|
||||
import watch from './commands/watch-deps';
|
||||
@@ -29,13 +27,12 @@ import appServe from './commands/app/serve';
|
||||
import pluginBuild from './commands/plugin/build';
|
||||
import pluginServe from './commands/plugin/serve';
|
||||
import { exitWithError } from './helpers/errors';
|
||||
import { paths } from './helpers/paths';
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(resolvePath(__dirname, '../package.json'), 'utf-8'),
|
||||
);
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
|
||||
program.name('backstage-cli').version(packageJson.version ?? '0.0.0');
|
||||
program.name('backstage-cli').version(version);
|
||||
|
||||
program
|
||||
.command('create-app')
|
||||
|
||||
Reference in New Issue
Block a user