diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 41b120eca7..9b92540488 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -2,7 +2,7 @@ "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "private": false, "publishConfig": { @@ -32,6 +32,7 @@ "express": "^4.17.1", "helmet": "^3.22.0", "morgan": "^1.10.0", + "stoppable": "^1.1.0", "winston": "^3.2.1" }, "devDependencies": { @@ -41,7 +42,9 @@ "@types/express": "^4.17.6", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", + "@types/stoppable": "^1.1.0", "@types/supertest": "^2.0.8", + "@types/webpack-env": "^1.15.2", "@types/yaml": "^1.9.7", "get-port": "^5.1.1", "http-errors": "^1.7.3", diff --git a/packages/backend-common/src/hot.ts b/packages/backend-common/src/hot.ts new file mode 100644 index 0000000000..f684b5cba1 --- /dev/null +++ b/packages/backend-common/src/hot.ts @@ -0,0 +1,92 @@ +/* + * 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. + */ + +/** + * This function allows devs to cleanup + * ongoing effects when module gets hot-reloaded + * Useful for cleaning intervals, timers, requests etc + * @example + * ```ts + * const intervalId = setInterval(doStuff, 1000); + * useHotCleanup(module, () => clearInterval(intervalId)); + * ``` + * @param _module Reference to the current module where you invoke the fn + * @param cancelEffect Fn that cleans up the ongoing effects + */ +export function useHotCleanup(_module: NodeModule, cancelEffect: () => void) { + if (_module.hot) { + _module.hot.addDisposeHandler(() => { + cancelEffect(); + }); + } +} + +/** + * This function allows devs to preserve + * some value between hot-reloads. + * Useful for stateful parts of the backend + * @example + * ```ts + * const db = useHotMemoize(module, () => createDB(dbParams)); + * ``` + * @param _module Reference to the current module where you invoke the fn + * @param valueFactory Fn that returns the value you want to memoize + * @warning Don't use inside conditionals or loops, + * same rules as for hooks apply (https://reactjs.org/docs/hooks-rules.html) + */ +export function useHotMemoize( + _module: NodeModule, + valueFactory: () => T, +): T { + const CURRENT_HOT_MEMOIZE_INDEX_KEY = 'backstage.io/hmr-memoize-key'; + + if (!_module.hot) { + // Just return value straight away + return valueFactory(); + } + + if (_module.hot && typeof _module.hot.data === 'undefined') { + // First run, init the module data + _module.hot.data = { + [CURRENT_HOT_MEMOIZE_INDEX_KEY]: 0, + }; + } + + // Let's store data per module based on the order of the code invocation + const index = _module.hot.data?.[CURRENT_HOT_MEMOIZE_INDEX_KEY]; + // Increasing the counter after each call + _module.hot.data[CURRENT_HOT_MEMOIZE_INDEX_KEY] += 1; + + const prevValue = _module.hot.data?.[index]; + const createDisposeHandler = (value: any) => (data: { + [key: number]: any; + [indexKey: string]: number; + }) => { + // Preserving the value through the HMR process + data[index] = value; + // Decreasing the counter after each handler + data[CURRENT_HOT_MEMOIZE_INDEX_KEY] = index - 1; + }; + + if (prevValue) { + _module.hot!.addDisposeHandler(createDisposeHandler(prevValue)); + return prevValue; + } + + const newValue = valueFactory(); + _module.hot.addDisposeHandler(createDisposeHandler(newValue)); + return newValue; +} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 11689aafff..0e4e5b2c6b 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -18,3 +18,4 @@ export * from './errors'; export * from './logging'; export * from './middleware'; export * from './service'; +export * from './hot'; diff --git a/packages/backend-common/src/service/ServiceBuilderImpl.ts b/packages/backend-common/src/service/ServiceBuilderImpl.ts index ac35d52112..931d29b841 100644 --- a/packages/backend-common/src/service/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/ServiceBuilderImpl.ts @@ -19,6 +19,7 @@ import cors from 'cors'; import express, { Router } from 'express'; import helmet from 'helmet'; import { Server } from 'http'; +import stoppable from 'stoppable'; import { Logger } from 'winston'; import { getRootLogger } from '../logging'; import { @@ -27,6 +28,7 @@ import { requestLoggingHandler, } from '../middleware'; import { ServiceBuilder } from './types'; +import { useHotCleanup } from '../hot'; const DEFAULT_PORT = 7000; @@ -35,9 +37,14 @@ export class ServiceBuilderImpl implements ServiceBuilder { private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; private routers: [string, Router][]; - - constructor() { + /** + * Reference to the module where builder is created + * Needed for the HMR + */ + private module: NodeModule; + constructor(module: NodeModule) { this.routers = []; + this.module = module; } setPort(port: number): ServiceBuilder { @@ -82,9 +89,19 @@ export class ServiceBuilderImpl implements ServiceBuilder { logger.error(`Failed to start up on port ${port}, ${e}`); reject(e); }); - const server = app.listen(port, () => { - logger.info(`Listening on port ${port}`); - }); + const server = stoppable( + app.listen(port, () => { + logger.info(`Listening on port ${port}`); + }), + 0, + ); + + useHotCleanup(this.module, () => + server.stop((e: any) => { + if (e) console.error(e); + }), + ); + resolve(server); }); } diff --git a/packages/backend-common/src/service/createServiceBuilder.ts b/packages/backend-common/src/service/createServiceBuilder.ts index ffd8901def..daef612fcf 100644 --- a/packages/backend-common/src/service/createServiceBuilder.ts +++ b/packages/backend-common/src/service/createServiceBuilder.ts @@ -19,6 +19,6 @@ import { ServiceBuilderImpl } from './ServiceBuilderImpl'; /** * Creates a new service builder. */ -export function createServiceBuilder() { - return new ServiceBuilderImpl(); +export function createServiceBuilder(_module: NodeModule) { + return new ServiceBuilderImpl(_module); } diff --git a/packages/backend-common/tsconfig.json b/packages/backend-common/tsconfig.json index ca39bf9964..2e645bef0b 100644 --- a/packages/backend-common/tsconfig.json +++ b/packages/backend-common/tsconfig.json @@ -9,6 +9,6 @@ "target": "ES2019", "module": "commonjs", "esModuleInterop": true, - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 58d405ab2f..9079c3d503 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -10,7 +10,7 @@ }, "scripts": { "build": "tsc", - "start": "backstage-cli watch-deps --build -- tsc-watch --onFirstSuccess \\\"cross-env NODE_ENV=development nodemon -r esm\\\"", + "start": "backstage-cli backend:dev", "lint": "backstage-cli lint", "test": "backstage-cli test", "clean": "backstage-cli clean", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d583db672b..0a9d852222 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -22,7 +22,11 @@ * Happy hacking! */ -import { createServiceBuilder, getRootLogger } from '@backstage/backend-common'; +import { + createServiceBuilder, + getRootLogger, + useHotMemoize, +} from '@backstage/backend-common'; import knex from 'knex'; import auth from './plugins/auth'; import catalog from './plugins/catalog'; @@ -45,19 +49,24 @@ function createEnv(plugin: string): PluginEnvironment { } async function main() { - const service = createServiceBuilder() + const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); + const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); + const authEnv = useHotMemoize(module, () => createEnv('auth')); + const identityEnv = useHotMemoize(module, () => createEnv('identity')); + + const service = createServiceBuilder(module) .enableCors({ origin: 'http://localhost:3000', credentials: true, }) - .addRouter('/catalog', await catalog(createEnv('catalog'))) - .addRouter('/scaffolder', await scaffolder(createEnv('scaffolder'))) + .addRouter('/catalog', await catalog(catalogEnv)) + .addRouter('/scaffolder', await scaffolder(scaffolderEnv)) .addRouter( '/sentry', await sentry(getRootLogger().child({ type: 'plugin', plugin: 'sentry' })), ) - .addRouter('/auth', await auth(createEnv('auth'))) - .addRouter('/identity', await identity(createEnv('identity'))); + .addRouter('/auth', await auth(authEnv)) + .addRouter('/identity', await identity(identityEnv)); await service.start().catch(err => { console.log(err); @@ -65,6 +74,7 @@ async function main() { }); } +module.hot?.accept(); main().catch(error => { console.error(`Backend failed to start up, ${error}`); process.exit(1); diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index c472623c3d..41e4226001 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -24,6 +24,7 @@ import { runPeriodically, } from '@backstage/plugin-catalog-backend'; import { PluginEnvironment } from '../types'; +import { useHotCleanup } from '@backstage/backend-common'; export default async function createPlugin({ logger, @@ -41,7 +42,10 @@ export default async function createPlugin({ logger, ); - runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000); + useHotCleanup( + module, + runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000), + ); return await createRouter({ entitiesCatalog, diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 04701a5502..269b22607c 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -10,6 +10,6 @@ "module": "commonjs", "esModuleInterop": true, "lib": ["es2019", "dom"], - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index f6d622ee16..ce031bc5e7 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -32,6 +32,6 @@ "strictPropertyInitialization": true, "stripInternal": true, "target": "ES2019", - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 8a50839934..a4c862351e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,6 +39,9 @@ "@rollup/plugin-node-resolve": "^7.1.1", "@spotify/eslint-config": "^7.0.1", "@sucrase/webpack-loader": "^2.0.0", + "@types/start-server-webpack-plugin": "^2.2.0", + "@types/webpack-node-externals": "^1.7.1", + "@types/webpack-env": "^1.15.2", "bfj": "^7.0.2", "chalk": "^4.0.0", "chokidar": "^3.3.1", @@ -72,6 +75,7 @@ "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.26.0", + "start-server-webpack-plugin": "^2.2.5", "style-loader": "^1.2.1", "sucrase": "^3.14.1", "tar": "^6.0.1", @@ -81,6 +85,7 @@ "url-loader": "^4.1.0", "webpack": "^4.41.6", "webpack-dev-server": "^3.10.3", + "webpack-node-externals": "^1.7.2", "yaml": "^1.10.0", "yml-loader": "^2.1.0", "yn": "^4.0.0" diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts new file mode 100644 index 0000000000..f04f904e09 --- /dev/null +++ b/packages/cli/src/commands/backend/dev.ts @@ -0,0 +1,32 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { loadConfig } from '@backstage/config-loader'; +import { Command } from 'commander'; +import { serveBackend } from '../../lib/bundler/backend'; + +export default async (cmd: Command) => { + const appConfigs = await loadConfig(); + const waitForExit = await serveBackend({ + entry: 'src/index', + checksEnabled: cmd.check, + config: ConfigReader.fromConfigs(appConfigs), + appConfigs, + }); + + await waitForExit(); +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a0302c4713..ea71dc6485 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -41,6 +41,12 @@ const main = (argv: string[]) => { .option('--check', 'Enable type checking and linting') .action(lazyAction(() => import('./commands/app/serve'), 'default')); + program + .command('backend:dev') + .description('Start local development server with HMR for the backend') + .option('--check', 'Enable type checking and linting') + .action(lazyAction(() => import('./commands/backend/dev'), 'default')); + program .command('app:diff') .option('--check', 'Fail if changes are required') diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts new file mode 100644 index 0000000000..ce821a585b --- /dev/null +++ b/packages/cli/src/lib/bundler/backend.ts @@ -0,0 +1,56 @@ +/* + * 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 webpack from 'webpack'; +import { createBackendConfig } from './config'; +import { resolveBundlingPaths } from './paths'; +import { ServeOptions } from './types'; + +export async function serveBackend(options: ServeOptions) { + const paths = resolveBundlingPaths(options); + const config = createBackendConfig(paths, { + ...options, + isDev: true, + }); + + const compiler = webpack(config); + + const watcher = compiler.watch( + { + poll: true, + }, + (err: Error) => { + if (err) { + console.error(err); + } else console.log('Build succeeded'); + }, + ); + + const waitForExit = async () => { + for (const signal of ['SIGINT', 'SIGTERM'] as const) { + process.on(signal, () => { + watcher.close(() => console.log('Stopped watcher')); + // exit instead of resolve. The process is shutting down and resolving a promise here logs an error + process.exit(); + }); + } + + // Block indefinitely and wait for the interrupt signal + return new Promise(() => {}); + }; + + return waitForExit; +} diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index ba99c49c62..e6d47573ef 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -14,15 +14,17 @@ * limitations under the License. */ -import webpack from 'webpack'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; -import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; +import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; +import StartServerPlugin from 'start-server-webpack-plugin'; +import webpack from 'webpack'; +import nodeExternals from 'webpack-node-externals'; +import { optimization } from './optimization'; import { Config } from '@backstage/config'; import { BundlingPaths } from './paths'; import { transforms } from './transforms'; -import { optimization } from './optimization'; -import { BundlingOptions } from './types'; +import { BundlingOptions, BackendBundlingOptions } from './types'; // import checkRequiredFiles from 'react-dev-utils/checkRequiredFiles'; // import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin'; // import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware'; @@ -93,6 +95,17 @@ export function createConfig( return { mode: isDev ? 'development' : 'production', profile: false, + node: { + module: 'empty', + dgram: 'empty', + dns: 'mock', + fs: 'empty', + http2: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty', + }, + optimization: optimization(options), bail: false, performance: { hints: false, // we check the gzip size instead @@ -124,17 +137,94 @@ export function createConfig( ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js', }, - optimization: optimization(options), plugins, - node: { - module: 'empty', - dgram: 'empty', - dns: 'mock', - fs: 'empty', - http2: 'empty', - net: 'empty', - tls: 'empty', - child_process: 'empty', - }, + }; +} + +export function createBackendConfig( + paths: BundlingPaths, + options: BackendBundlingOptions, +): webpack.Configuration { + const { checksEnabled, isDev } = options; + + const { loaders } = transforms(options); + + return { + mode: isDev ? 'development' : 'production', + profile: false, + ...(isDev + ? { + watch: true, + watchOptions: { + ignored: [/node_modules\/(?!\@backstage)/], + }, + } + : {}), + externals: [ + nodeExternals({ + modulesDir: paths.rootNodeModules, + whitelist: ['webpack/hot/poll?100', /\@backstage\/.*/], + }), + nodeExternals({ + modulesDir: paths.targetNodeModules, + whitelist: ['webpack/hot/poll?100', /\@backstage\/.*/], + }), + ], + target: 'node' as const, + node: { + __dirname: true, + __filename: true, + global: true, + }, + bail: false, + performance: { + hints: false, // we check the gzip size instead + }, + devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map', + context: paths.targetPath, + entry: ['webpack/hot/poll?100', paths.targetEntry], + resolve: { + extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'], + mainFields: ['main:src', 'browser', 'module', 'main'], + modules: [paths.targetNodeModules, paths.rootNodeModules], + plugins: [ + new ModuleScopePlugin( + [paths.targetSrc, paths.targetDev], + [paths.targetPackageJson], + ), + ], + alias: { + 'react-dom': '@hot-loader/react-dom', + }, + }, + module: { + rules: loaders, + }, + output: { + path: paths.targetDist, + filename: isDev ? '[name].js' : '[name].[hash:8].js', + chunkFilename: isDev + ? '[name].chunk.js' + : '[name].[chunkhash:8].chunk.js', + }, + plugins: [ + new StartServerPlugin('main.js'), + new webpack.HotModuleReplacementPlugin(), + ...(checksEnabled + ? [ + new ForkTsCheckerWebpackPlugin({ + tsconfig: paths.targetTsConfig, + eslint: true, + eslintOptions: { + parserOptions: { + project: paths.targetTsConfig, + tsconfigRootDir: paths.targetPath, + }, + }, + reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'], + }), + ] + : []), + ], }; } diff --git a/packages/cli/src/lib/bundler/index.ts b/packages/cli/src/lib/bundler/index.ts index cdfb5f3897..2030b4bb96 100644 --- a/packages/cli/src/lib/bundler/index.ts +++ b/packages/cli/src/lib/bundler/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ +export { serveBackend } from './backend'; export { buildBundle } from './bundle'; export { serveBundle } from './server'; diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index cec270178a..1e1cd1298a 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -60,6 +60,8 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) { targetTsConfig: paths.resolveTargetRoot('tsconfig.json'), targetNodeModules: paths.resolveTarget('node_modules'), targetPackageJson: paths.resolveTarget('package.json'), + rootNodeModules: paths.resolveTargetRoot('node_modules'), + root: paths.targetRoot, }; } diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 64fe823c2d..c7bacc0c7d 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -30,7 +30,11 @@ export async function serveBundle(options: ServeOptions) { const paths = resolveBundlingPaths(options); const pkgPath = paths.targetPackageJson; const pkg = await fs.readJson(pkgPath); - const config = createConfig(paths, { ...options, isDev: true, baseUrl: url }); + const config = createConfig(paths, { + ...options, + isDev: true, + baseUrl: url, + }); const compiler = webpack(config); const server = new WebpackDevServer(compiler, { diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index 98bcfe49ef..0cd800f2ea 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -16,14 +16,16 @@ import webpack, { Module, Plugin } from 'webpack'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; -import { BundlingOptions } from './types'; +import { BundlingOptions, BackendBundlingOptions } from './types'; type Transforms = { loaders: Module['rules']; plugins: Plugin[]; }; -export const transforms = (options: BundlingOptions): Transforms => { +export const transforms = ( + options: BundlingOptions | BackendBundlingOptions, +): Transforms => { const { isDev } = options; const loaders = [ diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index c1692ca997..5d0ba66a51 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -25,6 +25,8 @@ export type BundlingOptions = { baseUrl: URL; }; +export type BackendBundlingOptions = Omit; + export type ServeOptions = BundlingPathsOptions & { checksEnabled: boolean; config: Config; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 9ee11eca52..5eff22126d 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, diff --git a/plugins/auth-backend/tsconfig.json b/plugins/auth-backend/tsconfig.json index 015a967f76..6f1acf8dce 100644 --- a/plugins/auth-backend/tsconfig.json +++ b/plugins/auth-backend/tsconfig.json @@ -10,6 +10,6 @@ "module": "commonjs", "esModuleInterop": true, "lib": ["es2019"], - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d98afbe4d3..9abfb1646a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 6a9755c0c4..5c7ccb23f5 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -54,7 +54,7 @@ export async function startStandaloneServer( higherOrderOperation, logger, }); - const service = createServiceBuilder() + const service = createServiceBuilder(module) .enableCors({ origin: 'http://localhost:3000' }) .addRouter('/catalog', router); return await service.start().catch(err => { @@ -62,3 +62,5 @@ export async function startStandaloneServer( process.exit(1); }); } + +module.hot?.accept(); diff --git a/plugins/catalog-backend/tsconfig.json b/plugins/catalog-backend/tsconfig.json index 95c3a656e6..9ca38dca27 100644 --- a/plugins/catalog-backend/tsconfig.json +++ b/plugins/catalog-backend/tsconfig.json @@ -11,6 +11,6 @@ "esModuleInterop": true, "allowJs": true, "lib": ["es2019"], - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/plugins/identity-backend/package.json b/plugins/identity-backend/package.json index 0d1f6b99e5..ae7526daa2 100644 --- a/plugins/identity-backend/package.json +++ b/plugins/identity-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-identity-backend", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, diff --git a/plugins/identity-backend/tsconfig.json b/plugins/identity-backend/tsconfig.json index 015a967f76..6f1acf8dce 100644 --- a/plugins/identity-backend/tsconfig.json +++ b/plugins/identity-backend/tsconfig.json @@ -10,6 +10,6 @@ "module": "commonjs", "esModuleInterop": true, "lib": ["es2019"], - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 1d7e3d4388..da87c707aa 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json index fd9638afe3..03846ea5a9 100644 --- a/plugins/sentry-backend/package.json +++ b/plugins/sentry-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry-backend", "version": "0.1.1-alpha.8", - "main": "dist", + "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, diff --git a/plugins/sentry-backend/tsconfig.json b/plugins/sentry-backend/tsconfig.json index 015a967f76..6f1acf8dce 100644 --- a/plugins/sentry-backend/tsconfig.json +++ b/plugins/sentry-backend/tsconfig.json @@ -10,6 +10,6 @@ "module": "commonjs", "esModuleInterop": true, "lib": ["es2019"], - "types": ["node", "jest"] + "types": ["node", "jest", "webpack-env"] } } diff --git a/yarn.lock b/yarn.lock index 087252a8c3..2afeb2eb37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4020,6 +4020,20 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/start-server-webpack-plugin@^2.2.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@types/start-server-webpack-plugin/-/start-server-webpack-plugin-2.2.0.tgz#a7c4595c715eda083d92ca1ea184d97db3d8fc7b" + integrity sha512-TFiZWMPuiMR/utvjk6ENi0HPtQl38HnPMYfJqm04ztpzITHzTCXt7T7LyXnP9eTwg4lLQkmRUaFy04iEyjoJmw== + dependencies: + "@types/webpack" "*" + +"@types/stoppable@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.0.tgz#a5fa6a48120b109ca9233eed05c67c50bc4f3b91" + integrity sha512-BRR23Q9CJduH7AM6mk4JRttd8XyFkb4qIPZu4mdLF+VoP+wcjIxIWIKiBbN78NBbEuynrAyMPtzOHnIp2B/JPQ== + dependencies: + "@types/node" "*" + "@types/styled-jsx@^2.2.8": version "2.2.8" resolved "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234" @@ -4143,6 +4157,18 @@ resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.1.tgz#c8e84705e08eed430b5e15b39c65b0944e4d1422" integrity sha512-eWN5ElDTeBc5lRDh95SqA8x18D0ll2pWudU3uWiyfsRmIZcmUXpEsxPU+7+BsdCrO2vfLRC629u/MmjbmF+2tA== +"@types/webpack-env@^1.15.2": + version "1.15.2" + resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" + integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== + +"@types/webpack-node-externals@^1.7.1": + version "1.7.1" + resolved "https://registry.npmjs.org/@types/webpack-node-externals/-/webpack-node-externals-1.7.1.tgz#73d0d7ae0e98cfbd69b7443388302cd69217494a" + integrity sha512-kbO2gYPKvMb5j1KOgnIuUH52CKul9Ud4b10J5n+JX8oHmgu86hYpBVfrV4bMDe5lhCaO64h8QrKz7WnRZzqkbA== + dependencies: + "@types/webpack" "*" + "@types/webpack-sources@*": version "0.1.6" resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb" @@ -9932,7 +9958,7 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-utils@^1.1.3, headers-utils@^1.1.9: +headers-utils@^1.1.9, headers-utils@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== @@ -13169,9 +13195,9 @@ ms@^2.0.0, ms@^2.1.1: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== msw@^0.19.0: - version "0.19.0" - resolved "https://registry.npmjs.org/msw/-/msw-0.19.0.tgz#fd37015787d40db82d243a2853be66c466675e72" - integrity sha512-1TpmJzJ+afBWTRNJYoeW8KwLQbCVlvvhw2u/eRuIYfel+bPqcut5NaSgo+Bi4C0Q/7M5wza00w1GEuOXQu6FCA== + version "0.19.3" + resolved "https://registry.npmjs.org/msw/-/msw-0.19.3.tgz#88f39edbd37313bff15a0e7cd00c71406cbdbbae" + integrity sha512-HYLnyrCDDPP72GG/CeHPhBjHsZFYkz36rJLXDWccZWNA24gYjgrcp9iVqqitk2cI6NAJwsupRml9GkfpJBB74w== dependencies: "@open-draft/until" "^1.0.0" "@types/cookie" "^0.3.3" @@ -13180,7 +13206,7 @@ msw@^0.19.0: graphql "^15.0.0" headers-utils "^1.1.9" node-match-path "^0.4.2" - node-request-interceptor "^0.2.4" + node-request-interceptor "^0.2.5" statuses "^2.0.0" yargs "^15.3.1" @@ -13436,13 +13462,13 @@ node-releases@^1.1.29, node-releases@^1.1.52: dependencies: semver "^6.3.0" -node-request-interceptor@^0.2.4: - version "0.2.4" - resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.4.tgz#f03a1b874823d0bea311a14280227707be946298" - integrity sha512-/htjDLmygBczT5qYPaSxfAEtMkc0LGuH6jqAP1o+TKfQh6yQfFyTtac25cpY8+pb4EawHljCLUN7dCeed9SdPA== +node-request-interceptor@^0.2.5: + version "0.2.6" + resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.6.tgz#541278d7033bb6a8befb5dd793f83428cf6446a2" + integrity sha512-aJW1tPSM7nzuZFRe+C/KSz22GJO3CVFMxHHmMGX8Z+tjP7TCIVbzeckLFVfJG68BdVgrdOOP7Ejc57ag820eyA== dependencies: debug "^4.1.1" - headers-utils "^1.1.3" + headers-utils "^1.2.0" nodemon@^2.0.2: version "2.0.4" @@ -17299,6 +17325,11 @@ start-server-and-test@^1.10.11: ps-tree "1.2.0" wait-on "4.0.0" +start-server-webpack-plugin@^2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/start-server-webpack-plugin/-/start-server-webpack-plugin-2.2.5.tgz#4a2838759b0f36acd11b0b2f5f196f289ae29d31" + integrity sha512-DRCkciwCJoCFZ+wt3wWMkR1M2mpVhJbUKFXqhK3FWyIUKYb42NnocH5sMwqgo+nPNHupqNwK/v8lgfBbr2NKdg== + state-toggle@^1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" @@ -17327,6 +17358,11 @@ stealthy-require@^1.1.1: resolved "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +stoppable@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== + store2@^2.7.1: version "2.10.0" resolved "https://registry.npmjs.org/store2/-/store2-2.10.0.tgz#46b82bb91878daf1b0d56dec2f1d41e54d5103cf" @@ -19067,6 +19103,11 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" +webpack-node-externals@^1.7.2: + version "1.7.2" + resolved "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3" + integrity sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg== + webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"