diff --git a/.changeset/neat-rings-protect.md b/.changeset/neat-rings-protect.md new file mode 100644 index 0000000000..9242d4ce6c --- /dev/null +++ b/.changeset/neat-rings-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Support optional path argument when generating a project using `create-app` diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 562ebf0e22..d9719742fb 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -27,7 +27,7 @@ import { Task, templatingTask } from './lib/tasks'; const exec = promisify(execCb); -async function checkExists(rootDir: string, name: string) { +async function checkAppExists(rootDir: string, name: string) { await Task.forItem('checking', name, async () => { const destination = resolvePath(rootDir, name); @@ -40,6 +40,17 @@ async function checkExists(rootDir: string, name: string) { }); } +async function checkPathExists(path: string) { + await Task.forItem('checking', path, async () => { + try { + await fs.mkdirs(path); + } catch (error) { + // will fail if a file already exists at given `path` + throw new Error(`Failed to create app directory: ${error.message}`); + } + }); +} + async function createTemporaryAppFolder(tempDir: string) { await Task.forItem('creating', 'temporary directory', async () => { try { @@ -50,12 +61,6 @@ async function createTemporaryAppFolder(tempDir: string) { }); } -async function cleanUp(tempDir: string) { - await Task.forItem('remove', 'temporary directory', async () => { - await fs.remove(tempDir); - }); -} - async function buildApp(appDir: string) { const runCmd = async (cmd: string) => { await Task.forItem('executing', cmd, async () => { @@ -75,11 +80,17 @@ async function buildApp(appDir: string) { async function moveApp(tempDir: string, destination: string, id: string) { await Task.forItem('moving', id, async () => { - await fs.move(tempDir, destination).catch(error => { - throw new Error( - `Failed to move app from ${tempDir} to ${destination}: ${error.message}`, - ); - }); + await fs + .move(tempDir, destination) + .catch(error => { + throw new Error( + `Failed to move app from ${tempDir} to ${destination}: ${error.message}`, + ); + }) + .finally(() => { + // remove temporary files on both success and failure + fs.removeSync(tempDir); + }); }); } @@ -117,23 +128,40 @@ export default async (cmd: Command): Promise => { const templateDir = paths.resolveOwn('templates/default-app'); const tempDir = resolvePath(os.tmpdir(), answers.name); - const appDir = resolvePath(paths.targetDir, answers.name); + + // Use `--path` argument as applicaiton directory when specified, otherwise + // create a directory using `answers.name` + const appDir = cmd.path + ? resolvePath(paths.targetDir, cmd.path) + : resolvePath(paths.targetDir, answers.name); Task.log(); Task.log('Creating the app...'); try { - Task.section('Checking if the directory is available'); - await checkExists(paths.targetDir, answers.name); + if (cmd.path) { + // Template directly to specified path - Task.section('Creating a temporary app directory'); - await createTemporaryAppFolder(tempDir); + Task.section('Checking that supplied path exists'); + await checkPathExists(appDir); - Task.section('Preparing files'); - await templatingTask(templateDir, tempDir, answers); + Task.section('Preparing files'); + await templatingTask(templateDir, cmd.path, answers); + } else { + // Template to temporary location, and then move files - Task.section('Moving to final location'); - await moveApp(tempDir, appDir, answers.name); + Task.section('Checking if the directory is available'); + await checkAppExists(paths.targetDir, answers.name); + + Task.section('Creating a temporary app directory'); + await createTemporaryAppFolder(tempDir); + + Task.section('Preparing files'); + await templatingTask(templateDir, tempDir, answers); + + Task.section('Moving to final location'); + await moveApp(tempDir, appDir, answers.name); + } if (!cmd.skipInstall) { Task.section('Building the app'); @@ -157,10 +185,7 @@ export default async (cmd: Command): Promise => { Task.error(String(error)); Task.log('It seems that something went wrong when creating the app 🤔'); - Task.log('We are going to clean up, and then you can try again.'); - Task.section('Cleanup'); - await cleanUp(tempDir); Task.error('🔥 Failed to create app!'); Task.exit(1); } diff --git a/packages/create-app/src/index.ts b/packages/create-app/src/index.ts index 98a3cee908..1f37be56d2 100644 --- a/packages/create-app/src/index.ts +++ b/packages/create-app/src/index.ts @@ -26,10 +26,14 @@ import { version } from '../package.json'; import createApp from './createApp'; const main = (argv: string[]) => { - program.name('backstage-create-app').version(version); - program - .description('Creates a new app in a new directory') + .name('backstage-create-app') + .version(version) + .description('Creates a new app in a new directory or specified path') + .option( + '--path [directory]', + 'Location to store the app defaulting to a new folder with the app name', + ) .option( '--skip-install', 'Skip the install and builds steps after creating the app',