Merge pull request #118 from spotify/patriko/serve-plugin
packages/cli: add serve and watch-deps
This commit is contained in:
@@ -14,9 +14,14 @@
|
||||
"devDependencies": {
|
||||
"@spotify/web-scripts": "^6.0.0",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/node": "^13.7.2",
|
||||
"@types/react-dev-utils": "^9.0.4",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/webpack": "^4.41.7",
|
||||
"@types/webpack-dev-server": "^3.10.0",
|
||||
"del": "^5.1.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"ts-node": "^8.6.2"
|
||||
},
|
||||
@@ -24,13 +29,20 @@
|
||||
"backstage-cli": "bin/backstage-cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^3.3.1",
|
||||
"commander": "^4.1.1",
|
||||
"dashify": "^2.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "^8.1.0",
|
||||
"handlebars": "^4.7.3",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"inquirer": "^7.0.4",
|
||||
"react-dev-utils": "^10.2.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"replace-in-file": "^5.0.2"
|
||||
"replace-in-file": "^5.0.2",
|
||||
"ts-loader": "^6.2.1",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import del from 'del';
|
||||
import {
|
||||
createFileFromTemplate,
|
||||
createFromTemplateDir,
|
||||
@@ -16,7 +17,7 @@ describe('createPlugin', () => {
|
||||
expect(fs.existsSync(pluginFolder)).toBe(true);
|
||||
expect(pluginFolder).toMatch(/packages\/plugins\/foo/);
|
||||
} finally {
|
||||
fs.rmdirSync(tempDir, { recursive: true });
|
||||
del.sync(tempDir, { force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,7 +30,7 @@ describe('createPlugin', () => {
|
||||
/A plugin with the same name already exists/,
|
||||
);
|
||||
} finally {
|
||||
fs.rmdirSync(tempDir, { recursive: true });
|
||||
del.sync(tempDir, { force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -49,7 +50,7 @@ describe('createPlugin', () => {
|
||||
expect(fs.existsSync(targetPath)).toBe(true);
|
||||
expect(fs.readFileSync(targetPath).toString()).toBe(targetData);
|
||||
} finally {
|
||||
fs.rmdirSync(tempDir, { recursive: true });
|
||||
del.sync(tempDir, { force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -77,10 +78,9 @@ describe('createPlugin', () => {
|
||||
expect(fs.existsSync(subDir)).toBe(true);
|
||||
expect(fs.existsSync(testFile)).toBe(true);
|
||||
} finally {
|
||||
fs.rmdirSync(templateRootDir, { recursive: true });
|
||||
fs.rmdirSync(destinationRootDir, { recursive: true });
|
||||
await del(templateRootDir, { force: true });
|
||||
await del(destinationRootDir, { force: true });
|
||||
}
|
||||
});
|
||||
xit('should handle errors on reading template directory', async () => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import webpack from 'webpack';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
|
||||
import { Paths } from './paths';
|
||||
// import checkRequiredFiles from 'react-dev-utils/checkRequiredFiles';
|
||||
// import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin';
|
||||
// import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware';
|
||||
// import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware';
|
||||
// import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
|
||||
|
||||
export function createConfig(paths: Paths): webpack.Configuration {
|
||||
return {
|
||||
mode: 'development',
|
||||
profile: false,
|
||||
bail: false,
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
context: paths.appPath,
|
||||
entry: [
|
||||
`${require.resolve('webpack-dev-server/client')}?/`,
|
||||
require.resolve('webpack/hot/dev-server'),
|
||||
paths.appDevEntry,
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
plugins: [
|
||||
new ModuleScopePlugin(
|
||||
[paths.appSrc, paths.appDev],
|
||||
[paths.appPackageJson],
|
||||
),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(tsx?|jsx?|mjs)$/,
|
||||
enforce: 'pre',
|
||||
include: [paths.appSrc, paths.appDev],
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
emitWarning: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(tsx?|jsx?|mjs)$/,
|
||||
include: [paths.appSrc, paths.appDev],
|
||||
exclude: /node_modules/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
// disable type checker - handled by ForkTsCheckerWebpackPlugin
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.frag/, /\.xml/],
|
||||
loader: 'url-loader',
|
||||
include: paths.appAssets,
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
use: 'yml-loader',
|
||||
},
|
||||
{
|
||||
include: /\.(md)$/,
|
||||
use: 'raw-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
publicPath: '/',
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: paths.appHtml,
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
tsconfig: paths.appTsConfig,
|
||||
eslint: true,
|
||||
eslintOptions: {
|
||||
parserOptions: {
|
||||
project: paths.appTsConfig,
|
||||
tsconfigRootDir: paths.appPath,
|
||||
},
|
||||
},
|
||||
reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
node: {
|
||||
module: 'empty',
|
||||
dgram: 'empty',
|
||||
dns: 'mock',
|
||||
fs: 'empty',
|
||||
http2: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import chalk from 'chalk';
|
||||
import { startDevServer } from './server';
|
||||
|
||||
export default async () => {
|
||||
try {
|
||||
await startDevServer();
|
||||
} catch (error) {
|
||||
process.stderr.write(`${chalk.red(error.message)}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { existsSync, realpathSync } from 'fs';
|
||||
|
||||
export function getPaths() {
|
||||
const appDir = realpathSync(process.cwd());
|
||||
|
||||
const resolveApp = (path: string) => resolvePath(appDir, path);
|
||||
const resolveOwn = (path: string) => resolvePath(__dirname, '..', path);
|
||||
const resolveAppModule = (path: string) => {
|
||||
for (const ext of ['mjs', 'js', 'ts', 'tsx', 'jsx']) {
|
||||
const filePath = resolveApp(`${path}.${ext}`);
|
||||
if (existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return resolveApp(`${path}.js`);
|
||||
};
|
||||
|
||||
let appHtml = resolveApp('dev/index.html');
|
||||
if (!existsSync(appHtml)) {
|
||||
appHtml = resolveOwn('../../templates/serve_index.html');
|
||||
}
|
||||
|
||||
return {
|
||||
appHtml,
|
||||
appPath: resolveApp('.'),
|
||||
appAssets: resolveApp('assets'),
|
||||
appSrc: resolveApp('src'),
|
||||
appDev: resolveApp('dev'),
|
||||
appDevEntry: resolveAppModule('dev/index'),
|
||||
appTsConfig: resolveApp('tsconfig.json'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
};
|
||||
}
|
||||
|
||||
export type Paths = ReturnType<typeof getPaths>;
|
||||
@@ -0,0 +1,43 @@
|
||||
import webpack from 'webpack';
|
||||
import WebpackDevServer from 'webpack-dev-server';
|
||||
import openBrowser from 'react-dev-utils/openBrowser';
|
||||
import { choosePort, prepareUrls } from 'react-dev-utils/WebpackDevServerUtils';
|
||||
import { getPaths } from './paths';
|
||||
import { createConfig } from './config';
|
||||
|
||||
export async function startDevServer() {
|
||||
const host = process.env.HOST ?? '0.0.0.0';
|
||||
const defaultPort = parseInt(process.env.PORT ?? '', 10) || 3000;
|
||||
|
||||
const port = await choosePort(host, defaultPort);
|
||||
if (!port) {
|
||||
return;
|
||||
}
|
||||
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const urls = prepareUrls(protocol, host, port);
|
||||
|
||||
const paths = getPaths();
|
||||
const config = createConfig(paths);
|
||||
const compiler = webpack(config);
|
||||
const server = new WebpackDevServer(compiler, {
|
||||
hot: true,
|
||||
publicPath: '/',
|
||||
quiet: true,
|
||||
https: protocol === 'https',
|
||||
host,
|
||||
port,
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
server.listen(port, host, (err?: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import { createLogger } from './logger';
|
||||
|
||||
export function startChild(args: string[]) {
|
||||
const [command, ...commandArgs] = args;
|
||||
const child = spawn(command, commandArgs, {
|
||||
env: { FORCE_COLOR: 'true', ...process.env },
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
// We need to avoid clearing the terminal, or the build feedback of dependencies will be lost
|
||||
const log = createLogger();
|
||||
child.stdout!.on('data', (data: Buffer) => {
|
||||
log.out(data.toString('utf8'));
|
||||
});
|
||||
child.stderr!.on('data', data => {
|
||||
log.err(data.toString('utf8'));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { Logger } from './logger';
|
||||
import chalk from 'chalk';
|
||||
import { Package } from './packages';
|
||||
|
||||
export function startCompiler(pkg: Package, log: Logger) {
|
||||
// First we figure out which yarn script is a available, falling back to "build --watch"
|
||||
const scriptName = ['build:watch', 'watch'].find(
|
||||
script => script in pkg.scripts,
|
||||
);
|
||||
const args = scriptName ? [scriptName] : ['build', '--watch'];
|
||||
|
||||
// Start the watch script inside the dependency
|
||||
const watch = spawn('yarn', ['run', ...args], {
|
||||
cwd: pkg.location,
|
||||
env: { FORCE_COLOR: 'true', ...process.env },
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
watch.stdin.end();
|
||||
watch.stdout!.on('data', (data: Buffer) => {
|
||||
log.out(data.toString('utf8'));
|
||||
});
|
||||
watch.stderr!.on('data', data => {
|
||||
log.err(data.toString('utf8'));
|
||||
});
|
||||
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
watch.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
watch.on('close', (code: number) => {
|
||||
if (code !== 0) {
|
||||
const msg = `Compiler exited with code ${code}`;
|
||||
log.err(chalk.red(msg));
|
||||
reject(new Error(msg));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
close() {
|
||||
watch.kill('SIGINT');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createLoggerFactory } from './logger';
|
||||
import { findAllDeps } from './packages';
|
||||
import { startWatchers } from './watcher';
|
||||
import { startCompiler } from './compiler';
|
||||
import { startChild } from './child';
|
||||
|
||||
const PACKAGE_BLACKLIST = [
|
||||
// We never want to watch for changes in the cli, but all packages will depend on it.
|
||||
'@spotify-backstage/cli',
|
||||
];
|
||||
|
||||
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[]) => {
|
||||
const localPackagePath = resolvePath('package.json');
|
||||
const packageJson = JSON.parse(readFileSync(localPackagePath, 'utf8'));
|
||||
|
||||
// Find all direct and transitive local dependencies of the current package.
|
||||
const allDeps = await findAllDeps(packageJson.name, PACKAGE_BLACKLIST);
|
||||
|
||||
// Rotate through different prefix colors to make it easier to differenciate between different deps
|
||||
const logFactory = createLoggerFactory([
|
||||
chalk.yellow,
|
||||
chalk.blue,
|
||||
chalk.magenta,
|
||||
chalk.green,
|
||||
chalk.cyan,
|
||||
]);
|
||||
|
||||
// We lazily watch all our deps, as in we don't start the actual watch compiler until a change is detected
|
||||
await startWatchers(allDeps, WATCH_LOCATIONS, pkg => {
|
||||
startCompiler(pkg, logFactory(pkg.name)).promise.catch(error => {
|
||||
process.stderr.write(`${error}\n`);
|
||||
});
|
||||
});
|
||||
|
||||
if (args.length) {
|
||||
startChild(args);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
export type Logger = {
|
||||
out(msg: string): void;
|
||||
err(msg: string): void;
|
||||
};
|
||||
|
||||
export type ColorFunc = (msg: string) => string;
|
||||
|
||||
// Logger utility that prefixes logs and removes terminal clear commands
|
||||
export function createLogger(prefix: string = ''): Logger {
|
||||
const write = (stream: NodeJS.WriteStream, msg: string) => {
|
||||
const noClearMsg = msg.startsWith('\x1b\x63') ? msg.slice(2) : msg;
|
||||
const prefixedMsg = noClearMsg.trimRight().replace(/^/gm, prefix);
|
||||
stream.write(`${prefixedMsg}\n`, 'utf8');
|
||||
};
|
||||
|
||||
return {
|
||||
out(msg: string) {
|
||||
write(process.stdout, msg);
|
||||
},
|
||||
err(msg: string) {
|
||||
write(process.stderr, msg);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// A factory for creating loggers that rotate between different coloring functions
|
||||
export function createLoggerFactory(colorFuncs: ColorFunc[]) {
|
||||
let colorIndex = 0;
|
||||
|
||||
return (name: string) => {
|
||||
const colorFunc = colorFuncs[colorIndex];
|
||||
|
||||
colorIndex = (colorIndex + 1) % colorFuncs.length;
|
||||
|
||||
const prefix = `${colorFunc(name)}: `;
|
||||
return createLogger(prefix);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
const LernaProject = require('@lerna/project');
|
||||
const PackageGraph = require('@lerna/package-graph');
|
||||
|
||||
export type Package = {
|
||||
name: string;
|
||||
location: string;
|
||||
scripts: { [name in string]: string };
|
||||
};
|
||||
|
||||
// Uses lerna to find all local deps of the root package, excluding itself or any package in the blacklist
|
||||
export async function findAllDeps(
|
||||
rootPackageName: string,
|
||||
blacklist: string[],
|
||||
): Promise<Package[]> {
|
||||
const project = new LernaProject(resolvePath('.'));
|
||||
const packages = await project.getPackages();
|
||||
const graph = new PackageGraph(packages);
|
||||
|
||||
const deps = new Map<string, any>();
|
||||
const searchNames = [rootPackageName];
|
||||
|
||||
while (searchNames.length) {
|
||||
const name = searchNames.pop()!;
|
||||
|
||||
if (deps.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = graph.get(name);
|
||||
if (!node) {
|
||||
throw new Error(`Package '${name}' not found`);
|
||||
}
|
||||
|
||||
searchNames.push(...node.localDependencies.keys());
|
||||
deps.set(name, node.pkg);
|
||||
}
|
||||
|
||||
deps.delete(rootPackageName);
|
||||
for (const name of blacklist) {
|
||||
deps.delete(name);
|
||||
}
|
||||
|
||||
return [...deps.values()];
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import chokidar from 'chokidar';
|
||||
import { Package } from './packages';
|
||||
|
||||
/*
|
||||
* Watch for changes inside a collection of packages. When a change is detected, stop
|
||||
* watching and call the callback with the package the change occured in.
|
||||
*
|
||||
* The returned promise is resolved once all watchers are ready.
|
||||
*/
|
||||
export async function startWatchers(
|
||||
packages: Package[],
|
||||
paths: string[],
|
||||
callback: (pkg: Package) => void,
|
||||
): Promise<void> {
|
||||
const readyPromises = [];
|
||||
|
||||
for (const pkg of packages) {
|
||||
let signalled = false;
|
||||
|
||||
const watchLocations = paths.map(path => resolvePath(pkg.location, path));
|
||||
const watcher = chokidar
|
||||
.watch(watchLocations, {
|
||||
cwd: pkg.location,
|
||||
ignoreInitial: true,
|
||||
disableGlobbing: true,
|
||||
})
|
||||
.on('all', () => {
|
||||
if (!signalled) {
|
||||
signalled = true;
|
||||
callback(pkg);
|
||||
}
|
||||
watcher.close();
|
||||
});
|
||||
|
||||
readyPromises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
watcher.on('ready', resolve);
|
||||
watcher.on('error', reject);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(readyPromises);
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import program from 'commander';
|
||||
import createPluginCommand from './commands/createPlugin';
|
||||
import watch from './commands/watch-deps';
|
||||
import serve from './commands/serve';
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
program
|
||||
@@ -7,7 +13,18 @@ const main = (argv: string[]) => {
|
||||
.description('Creates a new plugin in the current repository')
|
||||
.action(createPluginCommand);
|
||||
|
||||
program
|
||||
.command('serve')
|
||||
.description('Serves the dev/ folder of a package')
|
||||
.action(serve);
|
||||
|
||||
program
|
||||
.command('watch-deps')
|
||||
.description('Watch all dependencies while running another command')
|
||||
.action(watch);
|
||||
|
||||
program.on('command:*', () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Invalid command: %s\nSee --help for a list of available commands.',
|
||||
program.args.join(' '),
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Backstage is an open platform for building developer portals"
|
||||
/>
|
||||
<title>Backstage</title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
# compile-test-app
|
||||
|
||||
Used to try out different setups for building packages separately but supporting global watch mode.
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ExampleComponent } from '../src';
|
||||
|
||||
ReactDOM.render(
|
||||
<main>
|
||||
<h1>This is the dev app</h1>
|
||||
<ExampleComponent />
|
||||
</main>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
...require('@spotify/web-scripts/config/jest.config.js'),
|
||||
setupFilesAfterEnv: ['../jest.setup.ts'],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "compile-test-app",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/cjs/index.js",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc --outDir dist/cjs --noEmit false --module CommonJS",
|
||||
"start": "backstage-cli watch-deps -- backstage-cli serve",
|
||||
"lint": "web-scripts lint",
|
||||
"test": "web-scripts test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@spotify/web-scripts": "^6.0.0",
|
||||
"@spotify-backstage/cli": "^1.2.0",
|
||||
"compile-test-lib": "0.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React, { FC } from 'react';
|
||||
import { getMessage } from 'compile-test-lib';
|
||||
|
||||
const ExampleComponent: FC<{}> = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>This is the ExampleComponent</h2>
|
||||
<p>{getMessage()}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExampleComponent;
|
||||
@@ -0,0 +1,5 @@
|
||||
describe('dummy', () => {
|
||||
it('dummy', () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ExampleComponent } from './ExampleComponent';
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"include": ["dev", "src"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# compile-test-lib
|
||||
|
||||
Used to try out different setups for building packages separately but supporting global watch mode.
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
...require('@spotify/web-scripts/config/jest.config.js'),
|
||||
setupFilesAfterEnv: ['../jest.setup.ts'],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "compile-test-lib-2",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/cjs",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc --outDir dist/cjs --noEmit false --module CommonJS",
|
||||
"build:watch": "yarn run build --watch",
|
||||
"lint": "web-scripts lint",
|
||||
"test": "web-scripts test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@spotify/web-scripts": "^6.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
describe('dummy', () => {
|
||||
it('dummy', () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export function getPartialMessage(): string {
|
||||
return '<lib2/>';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# compile-test-lib
|
||||
|
||||
Used to try out different setups for building packages separately but supporting global watch mode.
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
...require('@spotify/web-scripts/config/jest.config.js'),
|
||||
setupFilesAfterEnv: ['../jest.setup.ts'],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "compile-test-lib",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/cjs/index.js",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc --outDir dist/cjs --noEmit false --module CommonJS",
|
||||
"build:watch": "yarn run build --watch",
|
||||
"lint": "web-scripts lint",
|
||||
"test": "web-scripts test"
|
||||
},
|
||||
"dependencies": {
|
||||
"compile-test-lib-2": "0.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@spotify/web-scripts": "^6.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
describe('dummy', () => {
|
||||
it('dummy', () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { getPartialMessage } from 'compile-test-lib-2';
|
||||
|
||||
export function getMessage(): string {
|
||||
return `<lib> ${getPartialMessage()} </lib>`;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
+993
-491
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user