diff --git a/packages/app/package.json b/packages/app/package.json index 4a7d0a5e4c..90ff7fa0b9 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -20,13 +20,12 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-router-dom": "^5.1.2", - "react-scripts": "^3.4.0", "react-use": "^13.24.0", "zen-observable": "^0.8.15" }, "scripts": { - "start": "cross-env EXTEND_ESLINT=true SKIP_PREFLIGHT_CHECK=true backstage-cli watch-deps -- react-scripts start", - "build": "cross-env EXTEND_ESLINT=true SKIP_PREFLIGHT_CHECK=true react-scripts build", + "start": "backstage-cli app:serve", + "build": "backstage-cli app:build", "test": "backstage-cli test", "lint": "backstage-cli lint" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 7612ab1c35..9cc9ee6aa7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,6 +42,7 @@ "inquirer": "^7.0.4", "ora": "^4.0.3", "react-dev-utils": "^10.2.0", + "react-scripts": "^3.4.0", "recursive-readdir": "^2.2.2", "replace-in-file": "^5.0.2", "ts-loader": "^6.2.1", diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts new file mode 100644 index 0000000000..5dc623831e --- /dev/null +++ b/packages/cli/src/commands/app/build.ts @@ -0,0 +1,28 @@ +/* + * 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 { run } from '../../helpers/run'; + +export default async () => { + const args = ['build']; + + await run('react-scripts', args, { + env: { + EXTEND_ESLINT: 'true', + SKIP_PREFLIGHT_CHECK: 'true', + }, + }); +}; diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts new file mode 100644 index 0000000000..21241c99e3 --- /dev/null +++ b/packages/cli/src/commands/app/serve.ts @@ -0,0 +1,31 @@ +/* + * 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 { run } from '../../helpers/run'; +import { watchDeps } from '../watch-deps'; + +export default async () => { + const args = ['start']; + + // Start dynamic watch and build of dependencies, then serve the app + await watchDeps(); + await run('react-scripts', args, { + env: { + EXTEND_ESLINT: 'true', + SKIP_PREFLIGHT_CHECK: 'true', + }, + }); +}; diff --git a/packages/cli/src/commands/watch-deps/index.ts b/packages/cli/src/commands/watch-deps/index.ts index 6ea1fae23b..e5993ae9d6 100644 --- a/packages/cli/src/commands/watch-deps/index.ts +++ b/packages/cli/src/commands/watch-deps/index.ts @@ -31,16 +31,9 @@ const PACKAGE_BLACKLIST = [ const WATCH_LOCATIONS = ['package.json', 'src', 'assets']; -/* - * The watch-deps command is meant to improve iteration speed while working in a large monorepo - * with packages that are built independently, meaning packages depends on each other's build output. - * - * The command traverses all dependencies of the current package within the monorepo, and starts - * watching for updates in all those packages. If a change is detected, we stop listening for changes, - * and instead start up watch mode for that package. Starting watch mode means running the first - * available yarn script out of "build:watch", "watch", or "build" --watch. - */ -export default async (_command: any, args: string[]) => { +// 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'); // Rotate through different prefix colors to make it easier to differenciate between different deps @@ -66,6 +59,19 @@ export default async (_command: any, args: string[]) => { const newDeps = await getPackageDeps(localPackagePath, PACKAGE_BLACKLIST); await watcher.update(newDeps); }); +} + +/* + * The watch-deps command is meant to improve iteration speed while working in a large monorepo + * with packages that are built independently, meaning packages depends on each other's build output. + * + * The command traverses all dependencies of the current package within the monorepo, and starts + * watching for updates in all those packages. If a change is detected, we stop listening for changes, + * and instead start up watch mode for that package. Starting watch mode means running the first + * available yarn script out of "build:watch", "watch", or "build" --watch. + */ +export default async (_command: any, args: string[]) => { + await watchDeps(); if (args?.length) { await waitForExit(startChild(args)); diff --git a/packages/cli/src/helpers/run.ts b/packages/cli/src/helpers/run.ts index b3ce7ad2c1..3eec19a148 100644 --- a/packages/cli/src/helpers/run.ts +++ b/packages/cli/src/helpers/run.ts @@ -14,14 +14,18 @@ * limitations under the License. */ -import { SpawnSyncOptions, spawn, ChildProcess } from 'child_process'; +import { SpawnOptions, spawn, ChildProcess } from 'child_process'; import { ExitCodeError } from './errors'; +type SpawnOptionsPartialEnv = Omit & { + env?: Partial; +}; + // Runs a child command, returning a promise that is only resolved if the child exits with code 0. export async function run( name: string, args: string[] = [], - options: SpawnSyncOptions = {}, + options: SpawnOptionsPartialEnv = {}, ) { const env: NodeJS.ProcessEnv = { ...process.env, diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c9a7a14393..4797cceaf2 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -21,6 +21,8 @@ import createPluginCommand from './commands/createPlugin'; import watch from './commands/watch-deps'; import lintCommand from './commands/lint'; import testCommand from './commands/testCommand'; +import appBuild from './commands/app/build'; +import appServe from './commands/app/serve'; import pluginBuild from './commands/plugin/build'; import pluginServe from './commands/plugin/serve'; import { exitWithError } from './helpers/errors'; @@ -30,6 +32,16 @@ const main = (argv: string[]) => { program.name('backstage-cli').version(packageJson.version ?? '0.0.0'); + program + .command('app:build') + .description('Build an app for a production release') + .action(actionHandler(appBuild)); + + program + .command('app:serve') + .description('Serve an app for local development') + .action(actionHandler(appServe)); + program .command('create-plugin') .description('Creates a new plugin in the current repository')