Merge branch 'master' of https://github.com/spotify/backstage into lintMod

This commit is contained in:
Debajyoti Halder
2021-02-02 17:02:33 +05:30
294 changed files with 5833 additions and 1341 deletions
+31
View File
@@ -15,12 +15,43 @@
*/
import fs from 'fs-extra';
import chalk from 'chalk';
import uniq from 'lodash/uniq';
import { Command } from 'commander';
import { serveBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
import { paths } from '../../lib/paths';
import { Lockfile } from '../../lib/versioning';
import { includedFilter } from '../versions/lint';
export default async (cmd: Command) => {
const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
const result = lockfile.analyze({
filter: includedFilter,
});
const problemPackages = [...result.newVersions, ...result.newRanges].map(
({ name }) => name,
);
if (problemPackages.length > 1) {
console.log(
chalk.yellow(
`⚠️ Some of the following packages may be outdated or have duplicate installations:
${uniq(problemPackages).join(', ')}
`,
),
);
console.log(
chalk.yellow(
`⚠️ This can be resolved using the following command:
yarn backstage-cli versions:check --fix
`,
),
);
}
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
const waitForExit = await serveBundle({
entry: 'src/index',
@@ -20,6 +20,8 @@ import chalk from 'chalk';
import inquirer, { Answers, Question } from 'inquirer';
import { exec as execCb } from 'child_process';
import { resolve as resolvePath, join as joinPath } from 'path';
import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import os from 'os';
import { Command } from 'commander';
import {
@@ -104,16 +106,12 @@ export async function addPluginDependencyToApp(
});
}
export async function addPluginToApp(
export async function addPluginImportToApp(
rootDir: string,
pluginName: string,
pluginVar: string,
pluginPackage: string,
) {
const pluginNameCapitalized = pluginName
.split('-')
.map(name => capitalize(name))
.join('');
const pluginExport = `export { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`;
const pluginExport = `export { ${pluginVar} } from '${pluginPackage}';`;
const pluginsFilePath = 'packages/app/src/plugins.ts';
const pluginsFile = resolvePath(rootDir, pluginsFilePath);
@@ -126,6 +124,46 @@ export async function addPluginToApp(
});
}
export async function addPluginExtensionToApp(
pluginId: string,
extensionName: string,
pluginPackage: string,
) {
const pluginsFilePath = paths.resolveTargetRoot('packages/app/src/App.tsx');
if (!(await fs.pathExists(pluginsFilePath))) {
return;
}
await Task.forItem('processing', pluginsFilePath, async () => {
const content = await fs.readFile(pluginsFilePath, 'utf8');
const revLines = content.split('\n').reverse();
const lastImportIndex = revLines.findIndex(line =>
line.match(/ from ("|').*("|')/),
);
const lastRouteIndex = revLines.findIndex(line =>
line.match(/<\/FlatRoutes/),
);
if (lastImportIndex !== -1 && lastRouteIndex !== -1) {
revLines.splice(
lastImportIndex,
0,
`import { ${extensionName} } from '${pluginPackage}';`,
);
const [indentation] = revLines[lastRouteIndex + 1].match(/^\s*/) ?? [];
revLines.splice(
lastRouteIndex + 1,
0,
`${indentation}<Route path="/${pluginId}" element={<${extensionName} />}/>`,
);
const newContent = revLines.reverse().join('\n');
await fs.writeFile(pluginsFilePath, newContent, 'utf8');
}
});
}
async function cleanUp(tempDir: string) {
await Task.forItem('remove', 'temporary directory', async () => {
await fs.remove(tempDir);
@@ -223,6 +261,8 @@ export default async (cmd: Command) => {
const name = cmd.scope
? `@${cmd.scope.replace(/^@/, '')}/plugin-${pluginId}`
: `plugin-${pluginId}`;
const pluginVar = `${camelCase(answers.id)}Plugin`;
const extensionName = `${upperFirst(camelCase(answers.id))}Page`;
const npmRegistry = cmd.npmRegistry && cmd.scope ? cmd.npmRegistry : '';
const privatePackage = cmd.private === false ? false : true;
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
@@ -259,7 +299,9 @@ export default async (cmd: Command) => {
tempDir,
{
...answers,
pluginVar,
pluginVersion,
extensionName,
name,
privatePackage,
npmRegistry,
@@ -278,7 +320,8 @@ export default async (cmd: Command) => {
await addPluginDependencyToApp(paths.targetRoot, name, pluginVersion);
Task.section('Import plugin in app');
await addPluginToApp(paths.targetRoot, pluginId, name);
await addPluginImportToApp(paths.targetRoot, pluginVar, name);
await addPluginExtensionToApp(pluginId, extensionName, name);
}
if (ownerIds && ownerIds.length) {
+5
View File
@@ -39,6 +39,11 @@ const fileHandlers = [
patterns: ['package.json'],
handler: handlers.packageJson,
},
{
// Not all plugins have routes
patterns: ['src/routes.ts'],
handler: handlers.skip,
},
{
// make sure files in 1st level of src/ and dev/ exist
patterns: ['.eslintrc.js', /^(src|dev)\/[^/]+$/],
+30 -1
View File
@@ -219,7 +219,7 @@ export async function createBackendConfig(
}
: {}),
externals: [
nodeExternals({
nodeExternalsWithResolve({
modulesDir: paths.rootNodeModules,
additionalModuleDirs: moduleDirs,
allowlist: ['webpack/hot/poll?100', ...localPackageNames],
@@ -296,3 +296,32 @@ export async function createBackendConfig(
],
};
}
// This makes the module resolution happen from the context of each non-external module, rather
// than the main entrypoint. This fixes a bug where dependencies would be resolved from the backend
// package rather than each individual backend package and plugin.
//
// TODO(Rugvip): Feature suggestion/contribute this to webpack-externals
function nodeExternalsWithResolve(
options: Parameters<typeof nodeExternals>[0],
) {
let currentContext: string;
const externals = nodeExternals({
...options,
importType(request) {
const resolved = require.resolve(request, {
paths: [currentContext],
});
return `commonjs ${resolved}`;
},
});
return (
context: string,
request: string,
callback: webpack.ExternalsFunctionCallback,
) => {
currentContext = context;
return externals(context, request, callback);
};
}