diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts index b638c22353..e28cb376b9 100644 --- a/packages/cli/src/commands/create-app/createApp.ts +++ b/packages/cli/src/commands/create-app/createApp.ts @@ -21,21 +21,11 @@ import inquirer, { Answers, Question } from 'inquirer'; import { exec as execCb } from 'child_process'; import { resolve as resolvePath } from 'path'; import os from 'os'; -import { Task, templatingTask } from '../../lib/tasks'; +import { Task, templatingTask, installWithLocalDeps } from '../../lib/tasks'; import { paths } from '../../lib/paths'; import { version } from '../../lib/version'; const exec = promisify(execCb); -// List of local packages that we need to modify as a part of an E2E test -const PATCH_PACKAGES = [ - 'cli', - 'core', - 'dev-utils', - 'test-utils', - 'test-utils-core', - 'theme', -]; - async function checkExists(rootDir: string, name: string) { await Task.forItem('checking', name, async () => { const destination = resolvePath(rootDir, name); @@ -80,19 +70,7 @@ async function buildApp(appDir: string) { }); }; - // e2e testing needs special treatment - if (process.env.BACKSTAGE_E2E_CLI_TEST) { - Task.section('Linking packages locally for e2e tests'); - await patchPackageResolutions(appDir); - } - - await runCmd('yarn install'); - - if (process.env.BACKSTAGE_E2E_CLI_TEST) { - Task.section('Patchling local dependencies for e2e tests'); - await patchLocalDependencies(appDir); - } - + await installWithLocalDeps(appDir); await runCmd('yarn tsc'); await runCmd('yarn build'); } @@ -111,65 +89,6 @@ export async function moveApp( }); } -async function patchPackageResolutions(appDir: string) { - const pkgJsonPath = resolvePath(appDir, 'package.json'); - const pkgJson = await fs.readJson(pkgJsonPath); - - pkgJson.resolutions = pkgJson.resolutions || {}; - pkgJson.dependencies = pkgJson.dependencies || {}; - - for (const name of PATCH_PACKAGES) { - await Task.forItem( - 'adding', - `@backstage/${name} link to package.json`, - async () => { - const pkgPath = paths.resolveOwnRoot('packages', name); - // Add to both resolutions and dependencies, or transitive dependencies will still be fetched from the registry. - pkgJson.dependencies[`@backstage/${name}`] = `file:${pkgPath}`; - pkgJson.resolutions[`@backstage/${name}`] = `file:${pkgPath}`; - - await fs - .writeJSON(pkgJsonPath, pkgJson, { encoding: 'utf8', spaces: 2 }) - .catch((error) => { - throw new Error( - `Failed to add resolutions to package.json: ${error.message}`, - ); - }); - }, - ); - } -} - -async function patchLocalDependencies(appDir: string) { - for (const name of PATCH_PACKAGES) { - await Task.forItem( - 'patching', - `node_modules/@backstage/${name} package.json`, - async () => { - const depJsonPath = resolvePath( - appDir, - 'node_modules/@backstage', - name, - 'package.json', - ); - const depJson = await fs.readJson(depJsonPath); - - // We want dist to be used for e2e tests - delete depJson['main:src']; - depJson.types = 'dist/index.d.ts'; - - await fs - .writeJSON(depJsonPath, depJson, { encoding: 'utf8', spaces: 2 }) - .catch((error) => { - throw new Error( - `Failed to add resolutions to package.json: ${error.message}`, - ); - }); - }, - ); - } -} - export default async () => { const questions: Question[] = [ { diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index b0bf1d85b5..1441b16d05 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -28,7 +28,7 @@ import { } from '../../lib/codeowners'; import { paths } from '../../lib/paths'; import { version } from '../../lib/version'; -import { Task, templatingTask } from '../../lib/tasks'; +import { Task, templatingTask, installWithLocalDeps } from '../../lib/tasks'; const exec = promisify(execCb); async function checkExists(rootDir: string, id: string) { @@ -141,7 +141,9 @@ async function cleanUp(tempDir: string) { } async function buildPlugin(pluginFolder: string) { - const commands = ['yarn install', 'yarn build']; + await installWithLocalDeps(paths.targetRoot); + + const commands = ['yarn tsc', 'yarn build']; for (const command of commands) { await Task.forItem('executing', command, async () => { process.chdir(pluginFolder); diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 0753301b78..2fa6220f18 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -18,8 +18,12 @@ import chalk from 'chalk'; import fs from 'fs-extra'; import handlebars from 'handlebars'; import ora from 'ora'; -import { basename, dirname } from 'path'; +import { resolve as resolvePath, basename, dirname } from 'path'; import recursive from 'recursive-readdir'; +import { promisify } from 'util'; +import { exec as execCb } from 'child_process'; +import { paths } from './paths'; +const exec = promisify(execCb); const TASK_NAME_MAX_LENGTH = 14; @@ -69,7 +73,7 @@ export async function templatingTask( destinationDir: string, context: any, ) { - const files = await recursive(templateDir).catch(error => { + const files = await recursive(templateDir).catch((error) => { throw new Error(`Failed to read template directory: ${error.message}`); }); @@ -85,7 +89,7 @@ export async function templatingTask( const compiled = handlebars.compile(template.toString()); const contents = compiled({ name: basename(destination), ...context }); - await fs.writeFile(destination, contents).catch(error => { + await fs.writeFile(destination, contents).catch((error) => { throw new Error( `Failed to create file: ${destination}: ${error.message}`, ); @@ -93,7 +97,7 @@ export async function templatingTask( }); } else { await Task.forItem('copying', basename(file), async () => { - await fs.copyFile(file, destinationFile).catch(error => { + await fs.copyFile(file, destinationFile).catch((error) => { const destination = destinationFile; throw new Error( `Failed to copy file to ${destination} : ${error.message}`, @@ -103,3 +107,92 @@ export async function templatingTask( } } } + +// List of local packages that we need to modify as a part of an E2E test +const PATCH_PACKAGES = [ + 'cli', + 'core', + 'dev-utils', + 'test-utils', + 'test-utils-core', + 'theme', +]; + +export async function installWithLocalDeps(dir: string) { + // e2e testing needs special treatment + if (process.env.BACKSTAGE_E2E_CLI_TEST) { + Task.section('Linking packages locally for e2e tests'); + + const pkgJsonPath = resolvePath(dir, 'package.json'); + const pkgJson = await fs.readJson(pkgJsonPath); + + pkgJson.resolutions = pkgJson.resolutions || {}; + pkgJson.dependencies = pkgJson.dependencies || {}; + + if (!pkgJson.resolutions[`@backstage/${PATCH_PACKAGES[0]}`]) { + for (const name of PATCH_PACKAGES) { + await Task.forItem( + 'adding', + `@backstage/${name} link to package.json`, + async () => { + const pkgPath = paths.resolveOwnRoot('packages', name); + // Add to both resolutions and dependencies, or transitive dependencies will still be fetched from the registry. + pkgJson.dependencies[`@backstage/${name}`] = `file:${pkgPath}`; + pkgJson.resolutions[`@backstage/${name}`] = `file:${pkgPath}`; + + await fs + .writeJSON(pkgJsonPath, pkgJson, { encoding: 'utf8', spaces: 2 }) + .catch((error) => { + throw new Error( + `Failed to add resolutions to package.json: ${error.message}`, + ); + }); + }, + ); + } + } + } + + await Task.forItem('executing', 'yarn install', async () => { + await exec('yarn install', { cwd: dir }).catch((error) => { + process.stdout.write(error.stderr); + process.stdout.write(error.stdout); + throw new Error( + `Could not execute command ${chalk.cyan('yarn install')}`, + ); + }); + }); + + if (process.env.BACKSTAGE_E2E_CLI_TEST) { + Task.section('Patchling local dependencies for e2e tests'); + + for (const name of PATCH_PACKAGES) { + await Task.forItem( + 'patching', + `node_modules/@backstage/${name} package.json`, + async () => { + const depJsonPath = resolvePath( + dir, + 'node_modules/@backstage', + name, + 'package.json', + ); + console.log('DEBUG: depJsonPath =', depJsonPath); + const depJson = await fs.readJson(depJsonPath); + + // We want dist to be used for e2e tests + delete depJson['main:src']; + depJson.types = 'dist/index.d.ts'; + + await fs + .writeJSON(depJsonPath, depJson, { encoding: 'utf8', spaces: 2 }) + .catch((error) => { + throw new Error( + `Failed to add resolutions to package.json: ${error.message}`, + ); + }); + }, + ); + } + } +}