diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts new file mode 100644 index 0000000000..71e8ec50e9 --- /dev/null +++ b/packages/cli/src/commands/create-app/createApp.ts @@ -0,0 +1,150 @@ +/* + * 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 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'; +const exec = promisify(execCb); + +async function checkExists(rootDir: string, name: string) { + await Task.forItem('checking', name, async () => { + const destination = path.join(rootDir, 'plugins', name); + + if (await fs.pathExists(destination)) { + const existing = chalk.cyan(destination.replace(`${rootDir}/`, '')); + throw new Error( + `A directory with the same name already exists: ${existing}\nPlease try again with a different app name`, + ); + } + }); +} + +export async function createTemporaryAppFolder(tempDir: string) { + await Task.forItem('creating', 'temporary directory', async () => { + try { + await fs.mkdir(tempDir); + } catch (error) { + throw new Error( + `Failed to create temporary app directory: ${error.message}`, + ); + } + }); +} + +async function cleanUp(tempDir: string) { + await Task.forItem('remove', 'temporary directory', async () => { + await fs.remove(tempDir); + }); +} + +async function buildApp(pluginFolder: string) { + const commands = ['yarn install', 'yarn build']; + for (const command of commands) { + await Task.forItem('executing', command, async () => { + process.chdir(pluginFolder); + + await exec(command).catch(error => { + process.stdout.write(error.stderr); + process.stdout.write(error.stdout); + throw new Error(`Could not execute command ${chalk.cyan(command)}`); + }); + }); + } +} + +export 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 plugin from ${tempDir} to ${destination}: ${error.message}`, + ); + }); + }); +} + +export default async () => { + const questions: Question[] = [ + { + type: 'input', + name: 'name', + message: chalk.blue('Enter a name for the app [required]'), + validate: (value: any) => { + if (!value) { + return chalk.red('Please enter a name for the app'); + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return chalk.red( + 'Plugin name must be kebab-cased and contain only letters, digits, and dashes.', + ); + } + return true; + }, + }, + ]; + 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; + + Task.log(); + Task.log('Creating the app...'); + + try { + Task.section('Checking if the directory is available'); + await checkExists(rootDir, answers.name); + + Task.section('Creating a temporary app directory'); + await createTemporaryAppFolder(tempDir); + + Task.section('Preparing files'); + await templatingTask(templateDir, tempDir, { ...answers, version }); + + Task.section('Moving to final location'); + await moveApp(tempDir, appDir, answers.name); + + Task.section('Building the app'); + await buildApp(appDir); + + Task.log(); + Task.log( + chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`), + ); + Task.log(); + } catch (error) { + Task.error(error.message); + + 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!'); + } +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5f24190237..3eafc6dc6b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -17,6 +17,7 @@ import program from 'commander'; import chalk from 'chalk'; import fs from 'fs'; +import createAppCommand from './commands/create-app/createApp'; import createPluginCommand from './commands/create-plugin/createPlugin'; import watch from './commands/watch-deps'; import lintCommand from './commands/lint'; @@ -32,6 +33,11 @@ const main = (argv: string[]) => { program.name('backstage-cli').version(packageJson.version ?? '0.0.0'); + program + .command('create-app') + .description('Creates a new app in a new directory') + .action(actionHandler(createAppCommand)); + program .command('app:build') .description('Build an app for a production release') diff --git a/packages/cli/templates/default-app/README.md b/packages/cli/templates/default-app/README.md new file mode 100644 index 0000000000..b2594d1e53 --- /dev/null +++ b/packages/cli/templates/default-app/README.md @@ -0,0 +1,3 @@ +# [Backstage](https://backstage.io) + +This is your newly scaffolded Backstage App, Good Luck! diff --git a/packages/cli/templates/default-app/lerna.json b/packages/cli/templates/default-app/lerna.json new file mode 100644 index 0000000000..322929db1d --- /dev/null +++ b/packages/cli/templates/default-app/lerna.json @@ -0,0 +1,6 @@ +{ + "packages": ["packages/*", "plugins/*"], + "npmClient": "yarn", + "useWorkspaces": true, + "version": "0.1.0" +} diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs new file mode 100644 index 0000000000..def7b3178b --- /dev/null +++ b/packages/cli/templates/default-app/package.json.hbs @@ -0,0 +1,24 @@ +{ + "name": "root", + "private": true, + "scripts": { + "start": "yarn build && yarn workspace app start", + "build": "lerna run build", + "test": "cross-env CI=true lerna run test -- --coverage", + "create-plugin": "backstage-cli create-plugin", + "lint": "lerna run lint" + }, + "workspaces": { + "packages": [ + "packages/*", + "plugins/*" + ] + }, + "version": "1.0.0", + "devDependencies": { + "@backstage/cli": "^{{version}}", + "cross-env": "^7.0.0", + "lerna": "^3.20.2", + "prettier": "^1.19.1" + } +} diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs new file mode 100644 index 0000000000..fd0a19fed0 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -0,0 +1,39 @@ +{ + "name": "app", + "version": "0.0.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", + "@backstage/cli": "^{{version}}", + "@backstage/core": "^{{version}}", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^24.0.0", + "@types/node": "^12.0.0", + "@types/react-router-dom": "^5.1.3", + "plugin-welcome": "0.0.0", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "react-router-dom": "^5.1.2" + }, + "scripts": { + "start": "backstage-cli app:serve", + "build": "backstage-cli app:build", + "test": "backstage-cli test", + "lint": "backstage-cli lint" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/packages/cli/templates/default-app/packages/app/public/android-chrome-192x192.png b/packages/cli/templates/default-app/packages/app/public/android-chrome-192x192.png new file mode 100644 index 0000000000..3d56edbb0d Binary files /dev/null and b/packages/cli/templates/default-app/packages/app/public/android-chrome-192x192.png differ diff --git a/packages/cli/templates/default-app/packages/app/public/apple-touch-icon.png b/packages/cli/templates/default-app/packages/app/public/apple-touch-icon.png new file mode 100644 index 0000000000..0977175f6f Binary files /dev/null and b/packages/cli/templates/default-app/packages/app/public/apple-touch-icon.png differ diff --git a/packages/cli/templates/default-app/packages/app/public/favicon-16x16.png b/packages/cli/templates/default-app/packages/app/public/favicon-16x16.png new file mode 100644 index 0000000000..a455ffac7b Binary files /dev/null and b/packages/cli/templates/default-app/packages/app/public/favicon-16x16.png differ diff --git a/packages/cli/templates/default-app/packages/app/public/favicon-32x32.png b/packages/cli/templates/default-app/packages/app/public/favicon-32x32.png new file mode 100644 index 0000000000..e2707f2d1e Binary files /dev/null and b/packages/cli/templates/default-app/packages/app/public/favicon-32x32.png differ diff --git a/packages/cli/templates/default-app/packages/app/public/favicon.ico b/packages/cli/templates/default-app/packages/app/public/favicon.ico new file mode 100644 index 0000000000..5b582704a1 Binary files /dev/null and b/packages/cli/templates/default-app/packages/app/public/favicon.ico differ diff --git a/packages/cli/templates/default-app/packages/app/public/index.html b/packages/cli/templates/default-app/packages/app/public/index.html new file mode 100644 index 0000000000..ac2e9a6a73 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/public/index.html @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + Backstage + + + +
+ + + diff --git a/packages/cli/templates/default-app/packages/app/public/manifest.json b/packages/cli/templates/default-app/packages/app/public/manifest.json new file mode 100644 index 0000000000..4a7c1b4ec4 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Backstage", + "name": "Backstage", + "icons": [ + { + "src": "favicon.ico", + "sizes": "48x48", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/packages/cli/templates/default-app/packages/app/public/robots.txt b/packages/cli/templates/default-app/packages/app/public/robots.txt new file mode 100644 index 0000000000..01b0f9a107 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/packages/cli/templates/default-app/packages/app/public/safari-pinned-tab.svg b/packages/cli/templates/default-app/packages/app/public/safari-pinned-tab.svg new file mode 100644 index 0000000000..3b0f666390 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/public/safari-pinned-tab.svg @@ -0,0 +1,28 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + diff --git a/packages/cli/templates/default-app/packages/app/src/App.test.tsx b/packages/cli/templates/default-app/packages/app/src/App.test.tsx new file mode 100644 index 0000000000..0074416375 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/src/App.test.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import App from './App'; + +describe('App', () => { + it('should render', () => { + const rendered = render(); + expect(rendered.baseElement).toBeInTheDocument(); + }); +}); diff --git a/packages/cli/templates/default-app/packages/app/src/App.tsx b/packages/cli/templates/default-app/packages/app/src/App.tsx new file mode 100644 index 0000000000..14a07870b2 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/src/App.tsx @@ -0,0 +1,42 @@ +import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core'; +import { BackstageTheme, createApp } from '@backstage/core'; +import React, { FC } from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import * as plugins from './plugins'; + +const useStyles = makeStyles(theme => ({ + '@global': { + html: { + height: '100%', + fontFamily: theme.typography.fontFamily, + }, + body: { + height: '100%', + fontFamily: theme.typography.fontFamily, + 'overscroll-behavior-y': 'none', + }, + a: { + color: 'inherit', + textDecoration: 'none', + }, + }, +})); + +const app = createApp(); +app.registerPlugin(...Object.values(plugins)); +const AppComponent = app.build(); + +const App: FC<{}> = () => { + useStyles(); + return ( + + + + + + + + ); +}; + +export default App; diff --git a/packages/cli/templates/default-app/packages/app/src/index.tsx b/packages/cli/templates/default-app/packages/app/src/index.tsx new file mode 100644 index 0000000000..b597a44232 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/src/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); diff --git a/packages/cli/templates/default-app/packages/app/src/plugins.ts b/packages/cli/templates/default-app/packages/app/src/plugins.ts new file mode 100644 index 0000000000..6639319bbd --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/src/plugins.ts @@ -0,0 +1 @@ +export { default as WelcomePlugin } from 'plugin-welcome'; diff --git a/packages/cli/templates/default-app/packages/app/src/setupTests.ts b/packages/cli/templates/default-app/packages/app/src/setupTests.ts new file mode 100644 index 0000000000..666127af39 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/extend-expect'; diff --git a/packages/cli/templates/default-app/packages/app/tsconfig.json b/packages/cli/templates/default-app/packages/app/tsconfig.json new file mode 100644 index 0000000000..0457810645 --- /dev/null +++ b/packages/cli/templates/default-app/packages/app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@spotify/web-scripts/config/tsconfig.json", + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react", + "incremental": false + }, + "include": ["src"] +} diff --git a/packages/cli/templates/default-app/plugins/welcome/README.md b/packages/cli/templates/default-app/plugins/welcome/README.md new file mode 100644 index 0000000000..e9b9dd0edf --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/README.md @@ -0,0 +1,3 @@ +# Title + +Welcome to the welcome plugin! diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs new file mode 100644 index 0000000000..1aa7f2c5c7 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -0,0 +1,20 @@ +{ + "name": "plugin-welcome", + "version": "0.0.0", + "main": "dist/cjs/index.js", + "types": "dist/cjs/index.d.ts", + "private": true, + "scripts": { + "build": "backstage-cli plugin:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test" + }, + "devDependencies": { + "@backstage/core": "^{{version}}", + "@backstage/cli": "^{{version}}", + "@types/testing-library__jest-dom": "5.0.2" + }, + "dependencies": { + "@material-ui/lab": "4.0.0-alpha.45" + } +} diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/Timer.tsx b/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/Timer.tsx new file mode 100644 index 0000000000..24c79f91ee --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/Timer.tsx @@ -0,0 +1,58 @@ +import React, { FC } from 'react'; +import { HeaderLabel } from '@backstage/core'; + +const timeFormat = { hour: '2-digit', minute: '2-digit' }; +const utcOptions = { timeZone: 'UTC', ...timeFormat }; +const nycOptions = { timeZone: 'America/New_York', ...timeFormat }; +const tyoOptions = { timeZone: 'Asia/Tokyo', ...timeFormat }; +const stoOptions = { timeZone: 'Europe/Stockholm', ...timeFormat }; + +const defaultTimes = { + timeNY: '', + timeUTC: '', + timeTYO: '', + timeSTO: '', +}; + +function getTimes() { + const d = new Date(); + const lang = window.navigator.language; + + // Using the browser native toLocaleTimeString instead of huge moment-tz + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString + const timeNY = d.toLocaleTimeString(lang, nycOptions); + const timeUTC = d.toLocaleTimeString(lang, utcOptions); + const timeTYO = d.toLocaleTimeString(lang, tyoOptions); + const timeSTO = d.toLocaleTimeString(lang, stoOptions); + + return { timeNY, timeUTC, timeTYO, timeSTO }; +} + +const HomePageTimer: FC<{}> = () => { + const [{ timeNY, timeUTC, timeTYO, timeSTO }, setTimes] = React.useState( + defaultTimes, + ); + + React.useEffect(() => { + setTimes(getTimes()); + + const intervalId = setInterval(() => { + setTimes(getTimes()); + }, 1000); + + return () => { + clearInterval(intervalId); + }; + }, []); + + return ( + <> + + + + + + ); +}; + +export default HomePageTimer; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/index.ts b/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/index.ts new file mode 100644 index 0000000000..f1fc55bfe9 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/Timer/index.ts @@ -0,0 +1 @@ +export { default } from './Timer'; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx new file mode 100644 index 0000000000..4b9b12913d --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import WelcomePage from './WelcomePage'; +import { ThemeProvider } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/core'; + +describe('WelcomePage', () => { + it('should render', () => { + const rendered = render( + + + , + ); + expect(rendered.baseElement).toBeInTheDocument(); + }); +}); diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx new file mode 100644 index 0000000000..19f9ee55e2 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -0,0 +1,106 @@ +import React, { FC } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { + Typography, + Grid, + List, + ListItem, + ListItemText, + Link, +} from '@material-ui/core'; +import Timer from '../Timer'; +import { + Content, + InfoCard, + Header, + Page, + pageTheme, + ContentHeader, + SupportButton, +} from '@backstage/core'; + +const WelcomePage: FC<{}> = () => { + const profile = { givenName: '' }; + + return ( + +
+ +
+ + + + + + + + + You now have a running instance of Backstage! + + 🎉 + + Let's make sure you get the most out of this platform by walking + you through the basics. + + + The Setup + + + Backstage is put together from three base concepts: the core, + the app and the plugins. + + + + + + + + + + + + + + Try It Out + + + We suggest you either check out the documentation for{' '} + + creating a plugin + {' '} + or have a look in the code for the{' '} + + Home Page + {' '} + in the directory "plugins/home-page/src". + + + + + + Quick Links + + + backstage.io + + + + Create a plugin + + + + + + + +
+ ); +}; + +export default WelcomePage; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/index.ts b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/index.ts new file mode 100644 index 0000000000..b031301e7e --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/index.ts @@ -0,0 +1 @@ +export { default } from './WelcomePage'; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/index.ts b/packages/cli/templates/default-app/plugins/welcome/src/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts b/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts new file mode 100644 index 0000000000..f61dee5690 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts @@ -0,0 +1,7 @@ +import plugin from './plugin'; + +describe('welcome', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts b/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts new file mode 100644 index 0000000000..35ceddd65f --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts @@ -0,0 +1,9 @@ +import { createPlugin } from '@backstage/core'; +import WelcomePage from './components/WelcomePage'; + +export default createPlugin({ + id: 'welcome', + register({ router }) { + router.registerRoute('/', WelcomePage); + }, +}); diff --git a/packages/cli/templates/default-app/plugins/welcome/src/setupTests.ts b/packages/cli/templates/default-app/plugins/welcome/src/setupTests.ts new file mode 100644 index 0000000000..666127af39 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/extend-expect'; diff --git a/packages/cli/templates/default-app/plugins/welcome/tsconfig.json b/packages/cli/templates/default-app/plugins/welcome/tsconfig.json new file mode 100644 index 0000000000..596e2cf729 --- /dev/null +++ b/packages/cli/templates/default-app/plugins/welcome/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"] +} diff --git a/packages/cli/templates/default-app/prettier.config.js b/packages/cli/templates/default-app/prettier.config.js new file mode 100644 index 0000000000..93df970dd6 --- /dev/null +++ b/packages/cli/templates/default-app/prettier.config.js @@ -0,0 +1 @@ +module.exports = require('@spotify/web-scripts/config/prettier.config.js'); diff --git a/packages/cli/templates/default-app/tsconfig.json b/packages/cli/templates/default-app/tsconfig.json new file mode 100644 index 0000000000..25f61cc933 --- /dev/null +++ b/packages/cli/templates/default-app/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@spotify/web-scripts/config/tsconfig.json", + "exclude": ["**/*.test.*"], + "compilerOptions": { + "allowJs": true, + "noEmit": false, + "declarationMap": true, + "incremental": true + } +}